@rubytech/create-maxy-code 0.1.238 → 0.1.239

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy-code",
3
- "version": "0.1.238",
3
+ "version": "0.1.239",
4
4
  "description": "Install Maxy — AI for Productive People",
5
5
  "bin": {
6
6
  "create-maxy-code": "./dist/index.js"
@@ -3591,6 +3591,13 @@ function readAgentFile(accountDir, agentName, filename) {
3591
3591
  }
3592
3592
  return raw2;
3593
3593
  }
3594
+ var RESERVED_SLUGS = /* @__PURE__ */ new Set(["admin", "api", "assets", "brand", "bot", "privacy"]);
3595
+ var SLUG_PATTERN = /^[a-z][a-z0-9-]{2,49}$/;
3596
+ function validateAgentSlug(slug) {
3597
+ if (!SLUG_PATTERN.test(slug)) return false;
3598
+ if (RESERVED_SLUGS.has(slug)) return false;
3599
+ return true;
3600
+ }
3594
3601
  function resolveDefaultAgentSlug(accountDir) {
3595
3602
  const configPath = resolve3(accountDir, "account.json");
3596
3603
  if (!existsSync4(configPath)) {
@@ -3720,6 +3727,9 @@ function resolveAgentConfig(accountDir, agentName) {
3720
3727
  }
3721
3728
  return { model, plugins, status, displayName, image, imageShape, showAgentName, knowledge, knowledgeBaked, liveMemory, knowledgeKeywords, budget, accessMode };
3722
3729
  }
3730
+ function getDefaultAccountId() {
3731
+ return resolveAccount()?.accountId ?? null;
3732
+ }
3723
3733
  function resolveUserAccounts(userId) {
3724
3734
  if (!existsSync4(ACCOUNTS_DIR)) return [];
3725
3735
  const results = [];
@@ -4195,6 +4205,129 @@ async function ensureConversation(accountId, agentType, cacheKey2, visitorId, ag
4195
4205
  await session.close();
4196
4206
  }
4197
4207
  }
4208
+ async function findRecentConversation(visitorId, accountId, agentSlug, maxAgeHours = 24) {
4209
+ const session = getSession();
4210
+ try {
4211
+ const result = await session.run(
4212
+ `MATCH (c:Conversation {visitorId: $visitorId, accountId: $accountId, agentType: 'public'})
4213
+ WHERE c.agentSlug = $agentSlug
4214
+ AND c.updatedAt > datetime() - duration({hours: $maxAgeHours})
4215
+ AND NOT c:Trashed
4216
+ RETURN c.sessionId AS sessionId
4217
+ ORDER BY c.updatedAt DESC
4218
+ LIMIT 1`,
4219
+ { visitorId, accountId, agentSlug, maxAgeHours: neo4j.int(maxAgeHours) },
4220
+ { timeout: 2e3 }
4221
+ );
4222
+ const record = result.records[0];
4223
+ if (!record) return null;
4224
+ const sessionId = record.get("sessionId");
4225
+ if (!sessionId) return null;
4226
+ console.log(`[persist] found recent conversation ${sessionId.slice(0, 8)}\u2026 for visitor ${visitorId.slice(0, 8)}\u2026`);
4227
+ return { sessionId };
4228
+ } catch (err) {
4229
+ console.error(`[persist] findRecentConversation failed: ${err instanceof Error ? err.message : String(err)}`);
4230
+ return null;
4231
+ } finally {
4232
+ await session.close();
4233
+ }
4234
+ }
4235
+ async function findGroupBySlug(groupSlug, accountId) {
4236
+ const session = getSession();
4237
+ try {
4238
+ const result = await session.run(
4239
+ `MATCH (c:Conversation {groupSlug: $groupSlug, accountId: $accountId, type: 'group'})
4240
+ WHERE NOT c:Trashed
4241
+ RETURN c.sessionId AS sessionId, c.groupName AS groupName, c.agentSlug AS agentSlug`,
4242
+ { groupSlug, accountId },
4243
+ { timeout: 2e3 }
4244
+ );
4245
+ const record = result.records[0];
4246
+ if (!record) return null;
4247
+ return {
4248
+ sessionId: record.get("sessionId"),
4249
+ groupName: record.get("groupName"),
4250
+ agentSlug: record.get("agentSlug")
4251
+ };
4252
+ } catch (err) {
4253
+ console.error(`[group] findGroupBySlug failed: ${err instanceof Error ? err.message : String(err)}`);
4254
+ return null;
4255
+ } finally {
4256
+ await session.close();
4257
+ }
4258
+ }
4259
+ async function getGroupParticipants(sessionId) {
4260
+ const session = getSession();
4261
+ try {
4262
+ const result = await session.run(
4263
+ `MATCH (p:Person)-[r:PARTICIPATES_IN]->(c:Conversation {sessionId: $sessionId})
4264
+ RETURN p.givenName AS givenName, p.familyName AS familyName,
4265
+ r.displayName AS displayName, r.joinedAt AS joinedAt, r.visitorId AS visitorId`,
4266
+ { sessionId }
4267
+ );
4268
+ return result.records.map((r) => ({
4269
+ displayName: r.get("displayName") || r.get("givenName"),
4270
+ givenName: r.get("givenName"),
4271
+ familyName: r.get("familyName"),
4272
+ joinedAt: String(r.get("joinedAt")),
4273
+ visitorId: r.get("visitorId")
4274
+ }));
4275
+ } catch (err) {
4276
+ console.error(`[group] getGroupParticipants failed: ${err instanceof Error ? err.message : String(err)}`);
4277
+ return [];
4278
+ } finally {
4279
+ await session.close();
4280
+ }
4281
+ }
4282
+ async function checkGroupMembership(sessionId, visitorId) {
4283
+ const session = getSession();
4284
+ try {
4285
+ const result = await session.run(
4286
+ `MATCH (p:Person)-[r:PARTICIPATES_IN]->(c:Conversation {sessionId: $sessionId})
4287
+ WHERE r.visitorId = $visitorId
4288
+ RETURN r.displayName AS displayName
4289
+ LIMIT 1`,
4290
+ { sessionId, visitorId }
4291
+ );
4292
+ return result.records[0]?.get("displayName") ?? null;
4293
+ } catch (err) {
4294
+ console.error(`[group] checkGroupMembership failed: ${err instanceof Error ? err.message : String(err)}`);
4295
+ return null;
4296
+ } finally {
4297
+ await session.close();
4298
+ }
4299
+ }
4300
+ async function bindVisitorToGroup(sessionId, visitorId, personEmail, personPhone) {
4301
+ const session = getSession();
4302
+ try {
4303
+ const result = await session.run(
4304
+ `MATCH (p:Person)-[r:PARTICIPATES_IN]->(c:Conversation {sessionId: $sessionId})
4305
+ WHERE ($email IS NOT NULL AND p.email = $email)
4306
+ OR ($phone IS NOT NULL AND p.telephone = $phone)
4307
+ SET r.visitorId = $visitorId
4308
+ RETURN r.displayName AS displayName
4309
+ LIMIT 1`,
4310
+ {
4311
+ sessionId,
4312
+ visitorId,
4313
+ email: personEmail ?? null,
4314
+ phone: personPhone ?? null
4315
+ }
4316
+ );
4317
+ const name = result.records[0]?.get("displayName");
4318
+ if (name) {
4319
+ console.error(`[group] joined id=${sessionId.slice(0, 8)}\u2026 visitor=${visitorId.slice(0, 8)}\u2026`);
4320
+ } else {
4321
+ console.error(`[group] auth-denied id=${sessionId.slice(0, 8)}\u2026 visitor=${visitorId.slice(0, 8)}\u2026`);
4322
+ }
4323
+ return name ? { displayName: name } : null;
4324
+ } catch (err) {
4325
+ console.error(`[group] bindVisitorToGroup failed: ${err instanceof Error ? err.message : String(err)}`);
4326
+ return null;
4327
+ } finally {
4328
+ await session.close();
4329
+ }
4330
+ }
4198
4331
  var HEX_COLOR_RE = /^#[0-9a-fA-F]{3,8}$/;
4199
4332
  async function fetchBranding(accountId) {
4200
4333
  const session = getSession();
@@ -5482,8 +5615,10 @@ export {
5482
5615
  ACCOUNTS_DIR,
5483
5616
  listValidAccounts,
5484
5617
  resolveAccount,
5618
+ validateAgentSlug,
5485
5619
  resolveDefaultAgentSlug,
5486
5620
  resolveAgentConfig,
5621
+ getDefaultAccountId,
5487
5622
  resolveUserAccounts,
5488
5623
  fingerprintSessionKey,
5489
5624
  registerSession,
@@ -5510,6 +5645,11 @@ export {
5510
5645
  embed,
5511
5646
  GREETING_DIRECTIVE,
5512
5647
  ensureConversation,
5648
+ findRecentConversation,
5649
+ findGroupBySlug,
5650
+ getGroupParticipants,
5651
+ checkGroupMembership,
5652
+ bindVisitorToGroup,
5513
5653
  fetchBranding,
5514
5654
  persistMessage,
5515
5655
  getAgentSessionIdForConversation,
@@ -14,7 +14,7 @@ import {
14
14
  sanitizeClientCorrId,
15
15
  vncLog,
16
16
  websockifyLog
17
- } from "./chunk-63M3ILJ6.js";
17
+ } from "./chunk-ZY6W3UA2.js";
18
18
  import "./chunk-PFF6I7KP.js";
19
19
 
20
20
  // server/edge.ts
@@ -19,7 +19,9 @@ import {
19
19
  VNC_DISPLAY,
20
20
  WEBSOCKIFY_PORT,
21
21
  autoDeliverPremiumPlugins,
22
+ bindVisitorToGroup,
22
23
  canAccessAdmin,
24
+ checkGroupMembership,
23
25
  checkRateLimit,
24
26
  cleanupLeakedPremiumSubs,
25
27
  clearRateLimit,
@@ -31,11 +33,15 @@ import {
31
33
  emitMissingOnResolve,
32
34
  ensureConversation,
33
35
  fetchBranding,
36
+ findGroupBySlug,
34
37
  findMissingPlugins,
38
+ findRecentConversation,
35
39
  fingerprintSessionKey,
36
40
  getAccountIdForSession,
37
41
  getAgentSessionIdForConversation,
38
42
  getConversationClaudeSessionIds,
43
+ getDefaultAccountId,
44
+ getGroupParticipants,
39
45
  getRecentMessages,
40
46
  getRoleForSession,
41
47
  getSession,
@@ -76,6 +82,7 @@ import {
76
82
  stripAttachmentMetaSuffix,
77
83
  tryCookieBridgeForConversation,
78
84
  unregisterSession,
85
+ validateAgentSlug,
79
86
  validatePasswordStrength,
80
87
  validateSession,
81
88
  verifyAndGetConversationUpdatedAt,
@@ -84,7 +91,7 @@ import {
84
91
  vncLog,
85
92
  walkPremiumBundles,
86
93
  writeAdminUserAndPerson
87
- } from "./chunk-63M3ILJ6.js";
94
+ } from "./chunk-ZY6W3UA2.js";
88
95
  import {
89
96
  __commonJS,
90
97
  __toESM
@@ -816,8 +823,8 @@ var serveStatic = (options = { root: "" }) => {
816
823
  };
817
824
 
818
825
  // server/index.ts
819
- import { readFileSync as readFileSync22, existsSync as existsSync22, watchFile } from "fs";
820
- import { resolve as resolve24, join as join17, basename as basename6 } from "path";
826
+ import { readFileSync as readFileSync22, existsSync as existsSync23, watchFile } from "fs";
827
+ import { resolve as resolve25, join as join17, basename as basename6 } from "path";
821
828
  import { homedir as homedir2 } from "os";
822
829
  import { monitorEventLoopDelay } from "perf_hooks";
823
830
 
@@ -1317,7 +1324,7 @@ var credsSaveQueue = Promise.resolve();
1317
1324
  async function drainCredsSaveQueue(timeoutMs = 5e3) {
1318
1325
  console.error(`${TAG2} draining credential save queue\u2026`);
1319
1326
  const timer2 = new Promise(
1320
- (resolve25) => setTimeout(() => resolve25("timeout"), timeoutMs)
1327
+ (resolve26) => setTimeout(() => resolve26("timeout"), timeoutMs)
1321
1328
  );
1322
1329
  const result = await Promise.race([
1323
1330
  credsSaveQueue.then(() => "drained"),
@@ -1445,11 +1452,11 @@ async function createWaSocket(opts) {
1445
1452
  return sock;
1446
1453
  }
1447
1454
  async function waitForConnection(sock) {
1448
- return new Promise((resolve25, reject) => {
1455
+ return new Promise((resolve26, reject) => {
1449
1456
  const handler = (update) => {
1450
1457
  if (update.connection === "open") {
1451
1458
  sock.ev.off("connection.update", handler);
1452
- resolve25();
1459
+ resolve26();
1453
1460
  }
1454
1461
  if (update.connection === "close") {
1455
1462
  sock.ev.off("connection.update", handler);
@@ -1563,14 +1570,14 @@ ${inspected}`;
1563
1570
  return inspect2(err, INSPECT_OPTS2);
1564
1571
  }
1565
1572
  function withTimeout(label, promise, timeoutMs) {
1566
- return new Promise((resolve25, reject) => {
1573
+ return new Promise((resolve26, reject) => {
1567
1574
  const timer2 = setTimeout(() => {
1568
1575
  reject(new Error(`${label} timed out after ${timeoutMs}ms`));
1569
1576
  }, timeoutMs);
1570
1577
  promise.then(
1571
1578
  (value) => {
1572
1579
  clearTimeout(timer2);
1573
- resolve25(value);
1580
+ resolve26(value);
1574
1581
  },
1575
1582
  (err) => {
1576
1583
  clearTimeout(timer2);
@@ -2105,8 +2112,8 @@ async function persistWhatsAppMessage(input) {
2105
2112
  const { givenName, familyName } = splitName(input.pushName);
2106
2113
  const prev = sessionWriteLocks.get(input.cacheKey);
2107
2114
  let release;
2108
- const mine = new Promise((resolve25) => {
2109
- release = resolve25;
2115
+ const mine = new Promise((resolve26) => {
2116
+ release = resolve26;
2110
2117
  });
2111
2118
  const chained = (prev ?? Promise.resolve()).then(() => mine);
2112
2119
  sessionWriteLocks.set(input.cacheKey, chained);
@@ -3142,11 +3149,11 @@ async function connectWithReconnect(conn) {
3142
3149
  console.error(
3143
3150
  `${TAG12} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
3144
3151
  );
3145
- await new Promise((resolve25) => {
3146
- const timer2 = setTimeout(resolve25, delay);
3152
+ await new Promise((resolve26) => {
3153
+ const timer2 = setTimeout(resolve26, delay);
3147
3154
  conn.abortController.signal.addEventListener("abort", () => {
3148
3155
  clearTimeout(timer2);
3149
- resolve25();
3156
+ resolve26();
3150
3157
  }, { once: true });
3151
3158
  });
3152
3159
  }
@@ -3154,16 +3161,16 @@ async function connectWithReconnect(conn) {
3154
3161
  }
3155
3162
  }
3156
3163
  function waitForDisconnectEvent(conn) {
3157
- return new Promise((resolve25) => {
3164
+ return new Promise((resolve26) => {
3158
3165
  if (!conn.sock) {
3159
- resolve25();
3166
+ resolve26();
3160
3167
  return;
3161
3168
  }
3162
3169
  const sock = conn.sock;
3163
3170
  const handler = (update) => {
3164
3171
  if (update.connection === "close") {
3165
3172
  sock.ev.off("connection.update", handler);
3166
- resolve25();
3173
+ resolve26();
3167
3174
  }
3168
3175
  };
3169
3176
  sock.ev.on("connection.update", handler);
@@ -3425,8 +3432,8 @@ async function handleInboundMessage(conn, msg) {
3425
3432
  const conversationKey = isGroup ? remoteJid : senderPhone;
3426
3433
  const debounceKey = `${conn.accountId}:${conversationKey}:${senderPhone}`;
3427
3434
  let resolvePending;
3428
- const sttPending = new Promise((resolve25) => {
3429
- resolvePending = resolve25;
3435
+ const sttPending = new Promise((resolve26) => {
3436
+ resolvePending = resolve26;
3430
3437
  });
3431
3438
  if (conn.debouncer) conn.debouncer.registerPending(debounceKey, sttPending);
3432
3439
  try {
@@ -3830,20 +3837,20 @@ function buildX11Env(chromiumWrapperPath, transport = "vnc") {
3830
3837
 
3831
3838
  // server/routes/health.ts
3832
3839
  function checkPort(port2, timeoutMs = 500) {
3833
- return new Promise((resolve25) => {
3840
+ return new Promise((resolve26) => {
3834
3841
  const socket = createConnection2(port2, "127.0.0.1");
3835
3842
  socket.setTimeout(timeoutMs);
3836
3843
  socket.once("connect", () => {
3837
3844
  socket.destroy();
3838
- resolve25(true);
3845
+ resolve26(true);
3839
3846
  });
3840
3847
  socket.once("error", () => {
3841
3848
  socket.destroy();
3842
- resolve25(false);
3849
+ resolve26(false);
3843
3850
  });
3844
3851
  socket.once("timeout", () => {
3845
3852
  socket.destroy();
3846
- resolve25(false);
3853
+ resolve26(false);
3847
3854
  });
3848
3855
  });
3849
3856
  }
@@ -4579,12 +4586,12 @@ async function dispatchOnce(input) {
4579
4586
  });
4580
4587
  if (!entry) return { error: "spawn-failed" };
4581
4588
  entry.lastInboundAt = Date.now();
4582
- let resolve25;
4589
+ let resolve26;
4583
4590
  const turnPromise = new Promise((r) => {
4584
- resolve25 = r;
4591
+ resolve26 = r;
4585
4592
  });
4586
4593
  const listener = (text) => {
4587
- resolve25(text);
4594
+ resolve26(text);
4588
4595
  };
4589
4596
  entry.subscribers.add(listener);
4590
4597
  const writeOk = await writeInput(entry, input.text);
@@ -5026,8 +5033,8 @@ async function startLogin(opts) {
5026
5033
  resetActiveLogin(accountId);
5027
5034
  let resolveQr = null;
5028
5035
  let rejectQr = null;
5029
- const qrPromise = new Promise((resolve25, reject) => {
5030
- resolveQr = resolve25;
5036
+ const qrPromise = new Promise((resolve26, reject) => {
5037
+ resolveQr = resolve26;
5031
5038
  rejectQr = reject;
5032
5039
  });
5033
5040
  const qrTimer = setTimeout(
@@ -7147,7 +7154,7 @@ app10.post("/:slug/project", async (c) => {
7147
7154
  var agents_default = app10;
7148
7155
 
7149
7156
  // server/routes/admin/sessions.ts
7150
- import crypto from "crypto";
7157
+ import crypto2 from "crypto";
7151
7158
  import { resolve as resolvePath } from "path";
7152
7159
  import { existsSync as existsSync13, mkdirSync as mkdirSync3, createWriteStream } from "fs";
7153
7160
 
@@ -7456,7 +7463,7 @@ app11.post("/new", requireAdminSession, async (c) => {
7456
7463
  console.error(`[admin-chat] new-conversation outcome=fail reason=missing-account-or-user cacheKey=${oldCacheKey.slice(0, 8)}`);
7457
7464
  return c.json({ error: "Session missing account or user context" }, 400);
7458
7465
  }
7459
- const newSignedSessionToken = crypto.randomUUID();
7466
+ const newSignedSessionToken = crypto2.randomUUID();
7460
7467
  const newCacheKey = fingerprintSessionKey(newSignedSessionToken);
7461
7468
  const userName = getUserNameForSession(oldCacheKey);
7462
7469
  registerSession(newCacheKey, "admin", accountId, void 0, userId, userName);
@@ -12055,7 +12062,7 @@ function isPidAlive(pid) {
12055
12062
  }
12056
12063
  }
12057
12064
  function sleep2(ms) {
12058
- return new Promise((resolve25) => setTimeout(resolve25, ms));
12065
+ return new Promise((resolve26) => setTimeout(resolve26, ms));
12059
12066
  }
12060
12067
  function sendSignal(pid, signal) {
12061
12068
  try {
@@ -14037,6 +14044,244 @@ async function writeEvent(opts) {
14037
14044
  }
14038
14045
  var visitor_event_default = app40;
14039
14046
 
14047
+ // server/routes/session.ts
14048
+ import { resolve as resolve22 } from "path";
14049
+ import { existsSync as existsSync21, writeFileSync as writeFileSync7, mkdirSync as mkdirSync5 } from "fs";
14050
+ var UUID_RE4 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
14051
+ function writeBrandingCache(accountId, agentSlug, branding) {
14052
+ try {
14053
+ const cacheDir = resolve22(MAXY_DIR, "branding-cache", accountId);
14054
+ mkdirSync5(cacheDir, { recursive: true });
14055
+ writeFileSync7(resolve22(cacheDir, `${agentSlug}.json`), JSON.stringify(branding), "utf-8");
14056
+ } catch (err) {
14057
+ console.error(`[branding] cache write failed: ${err instanceof Error ? err.message : String(err)}`);
14058
+ }
14059
+ }
14060
+ function parseVisitorCookie(cookieHeader) {
14061
+ if (!cookieHeader) return null;
14062
+ const match = cookieHeader.match(/(?:^|;\s*)maxy_visitor=([^;]*)/);
14063
+ if (!match) return null;
14064
+ const value = decodeURIComponent(match[1]).trim();
14065
+ return UUID_RE4.test(value) ? value : null;
14066
+ }
14067
+ function parseAccessSessionId(cookieHeader) {
14068
+ if (!cookieHeader) return null;
14069
+ const part = cookieHeader.split(";").map((p) => p.trim()).find((p) => p.startsWith("__access_session="));
14070
+ return part ? part.slice("__access_session=".length) : null;
14071
+ }
14072
+ function withVisitorCookie(response, visitorId) {
14073
+ if (!visitorId) return response;
14074
+ const headers = new Headers(response.headers);
14075
+ headers.set("Set-Cookie", `maxy_visitor=${visitorId}; Path=/; Max-Age=86400; SameSite=Lax`);
14076
+ return new Response(response.body, {
14077
+ status: response.status,
14078
+ statusText: response.statusText,
14079
+ headers
14080
+ });
14081
+ }
14082
+ var app41 = new Hono();
14083
+ app41.post("/", async (c) => {
14084
+ let body;
14085
+ try {
14086
+ body = await c.req.json();
14087
+ } catch {
14088
+ return c.json({ error: "Invalid request" }, 400);
14089
+ }
14090
+ if (!body.session_id || typeof body.session_id !== "string") {
14091
+ return c.json({ error: "session_id required" }, 400);
14092
+ }
14093
+ const accountId = getDefaultAccountId();
14094
+ if (!accountId) {
14095
+ console.error("[session] getDefaultAccountId() returned null \u2014 no account configured");
14096
+ return c.json({ error: "No account configured" }, 503);
14097
+ }
14098
+ const account = resolveAccount();
14099
+ const cookieHeader = c.req.header("cookie");
14100
+ const visitorId = parseVisitorCookie(cookieHeader);
14101
+ const groupSlug = body.group_slug && typeof body.group_slug === "string" ? body.group_slug : null;
14102
+ let groupInfo = null;
14103
+ if (groupSlug) {
14104
+ groupInfo = await findGroupBySlug(groupSlug, accountId).catch(() => null);
14105
+ if (!groupInfo) {
14106
+ console.error(`[session] group not found: slug=${groupSlug} account=${accountId.slice(0, 8)}\u2026`);
14107
+ return c.json({ error: "Group not found" }, 404);
14108
+ }
14109
+ }
14110
+ let agentSlug = null;
14111
+ if (groupInfo) {
14112
+ agentSlug = groupInfo.agentSlug;
14113
+ } else if (body.agent) {
14114
+ if (!validateAgentSlug(body.agent)) {
14115
+ return c.json({ error: "Invalid agent name" }, 400);
14116
+ }
14117
+ agentSlug = body.agent;
14118
+ } else {
14119
+ agentSlug = account ? resolveDefaultAgentSlug(account.accountDir) : null;
14120
+ if (agentSlug) {
14121
+ console.log(`[session] using defaultAgent=${agentSlug} (no agent param)`);
14122
+ }
14123
+ }
14124
+ if (!agentSlug) {
14125
+ console.error("[session] no agent resolved");
14126
+ return c.json({ error: "No agents configured" }, 404);
14127
+ }
14128
+ let agentConfig = null;
14129
+ if (account) {
14130
+ const agentDir = resolve22(account.accountDir, "agents", agentSlug);
14131
+ if (!existsSync21(agentDir) || !existsSync21(resolve22(agentDir, "config.json"))) {
14132
+ return c.json({ error: "Agent not found" }, 404);
14133
+ }
14134
+ agentConfig = resolveAgentConfig(account.accountDir, agentSlug);
14135
+ }
14136
+ const agentIdentity = agentConfig ? {
14137
+ ...agentConfig.displayName ? { displayName: agentConfig.displayName } : {},
14138
+ ...agentConfig.image ? { agentImage: agentConfig.image } : {},
14139
+ ...agentConfig.imageShape ? { agentImageShape: agentConfig.imageShape } : {},
14140
+ ...agentConfig.showAgentName ? { showAgentName: agentConfig.showAgentName } : {}
14141
+ } : {};
14142
+ const groupFields = groupInfo ? {
14143
+ group: true,
14144
+ groupName: groupInfo.groupName,
14145
+ groupSlug
14146
+ } : {};
14147
+ const branding = await fetchBranding(accountId).catch(() => null);
14148
+ if (branding) writeBrandingCache(accountId, agentSlug, branding);
14149
+ let accessSession = null;
14150
+ if (agentConfig && agentConfig.accessMode !== "open") {
14151
+ const accessSessionId = parseAccessSessionId(cookieHeader);
14152
+ accessSession = accessSessionId ? getAccessSession(accessSessionId) : null;
14153
+ if (!accessSession || accessSession.agentSlug !== agentSlug) {
14154
+ console.log(`[session] agent=${agentSlug} accessMode=${agentConfig.accessMode} \u2014 auth required`);
14155
+ return withVisitorCookie(
14156
+ Response.json({
14157
+ auth_required: true,
14158
+ agent_id: agentSlug,
14159
+ ...groupFields,
14160
+ ...branding ? { branding } : {},
14161
+ ...agentIdentity
14162
+ }),
14163
+ null
14164
+ );
14165
+ }
14166
+ }
14167
+ if (body.session_key && typeof body.session_key === "string" && UUID_RE4.test(body.session_key)) {
14168
+ if (visitorId) {
14169
+ const recent = await findRecentConversation(visitorId, accountId, agentSlug).catch(() => null);
14170
+ if (recent) {
14171
+ const neo4jMessages = await getRecentMessages(recent.sessionId).catch(() => []);
14172
+ const uiMessages = neo4jMessages.filter((m) => m.content !== GREETING_DIRECTIVE).map((m) => ({
14173
+ role: m.role === "user" ? "visitor" : "maxy",
14174
+ content: m.content,
14175
+ timestamp: new Date(m.createdAt).getTime() || Date.now()
14176
+ }));
14177
+ const resumed = uiMessages.length > 0;
14178
+ console.log(`[session] hot-resume session=${body.session_key.slice(0, 8)}\u2026 messages=${uiMessages.length}`);
14179
+ return c.json({
14180
+ session_key: body.session_key,
14181
+ agent_id: agentSlug,
14182
+ resumed,
14183
+ ...resumed ? { messages: uiMessages } : {},
14184
+ ...groupFields,
14185
+ ...branding ? { branding } : {},
14186
+ ...agentIdentity
14187
+ });
14188
+ }
14189
+ }
14190
+ console.log(`[session] hot-resume session=${body.session_key.slice(0, 8)}\u2026 (no cookie/conversation)`);
14191
+ return c.json({
14192
+ session_key: body.session_key,
14193
+ agent_id: agentSlug,
14194
+ resumed: false,
14195
+ ...groupFields,
14196
+ ...branding ? { branding } : {},
14197
+ ...agentIdentity
14198
+ });
14199
+ }
14200
+ if (groupInfo && visitorId) {
14201
+ let memberName = await checkGroupMembership(groupInfo.sessionId, visitorId).catch(() => null);
14202
+ if (!memberName && accessSession) {
14203
+ const contactValue = accessSession.contactValue;
14204
+ const isEmail = contactValue?.includes("@");
14205
+ const bindResult = await bindVisitorToGroup(
14206
+ groupInfo.sessionId,
14207
+ visitorId,
14208
+ isEmail ? contactValue : void 0,
14209
+ !isEmail ? contactValue : void 0
14210
+ ).catch(() => null);
14211
+ memberName = bindResult?.displayName ?? null;
14212
+ }
14213
+ if (memberName) {
14214
+ const neo4jMessages = await getRecentMessages(groupInfo.sessionId).catch(() => []);
14215
+ const uiMessages = neo4jMessages.filter((m) => m.content !== GREETING_DIRECTIVE).map((m) => ({
14216
+ role: m.role === "user" ? "visitor" : "maxy",
14217
+ content: m.content,
14218
+ timestamp: new Date(m.createdAt).getTime() || Date.now()
14219
+ }));
14220
+ const participants = await getGroupParticipants(groupInfo.sessionId).catch(() => []);
14221
+ const cacheKey2 = crypto.randomUUID();
14222
+ const resumed = uiMessages.length > 0;
14223
+ console.log(`[session] cold-resume group=${groupSlug} visitor=${visitorId.slice(0, 8)}\u2026 messages=${uiMessages.length}`);
14224
+ return withVisitorCookie(
14225
+ Response.json({
14226
+ session_key: cacheKey2,
14227
+ agent_id: agentSlug,
14228
+ resumed,
14229
+ ...resumed ? { messages: uiMessages } : {},
14230
+ ...groupFields,
14231
+ participants: participants.map((p) => ({ displayName: p.displayName, joinedAt: p.joinedAt })),
14232
+ ...branding ? { branding } : {},
14233
+ ...agentIdentity
14234
+ }),
14235
+ visitorId
14236
+ );
14237
+ }
14238
+ console.log(`[session] group cold-resume denied: visitor=${visitorId.slice(0, 8)}\u2026 not a member of group=${groupSlug}`);
14239
+ return c.json({ error: "Not a member of this group" }, 403);
14240
+ }
14241
+ if (groupInfo) {
14242
+ console.error(`[session] group=${groupSlug} requires visitor cookie`);
14243
+ return c.json({ error: "Groups require member authentication" }, 400);
14244
+ }
14245
+ if (visitorId) {
14246
+ const recent = await findRecentConversation(visitorId, accountId, agentSlug).catch(() => null);
14247
+ if (recent) {
14248
+ const neo4jMessages = await getRecentMessages(recent.sessionId).catch(() => []);
14249
+ const uiMessages = neo4jMessages.filter((m) => m.content !== GREETING_DIRECTIVE).map((m) => ({
14250
+ role: m.role === "user" ? "visitor" : "maxy",
14251
+ content: m.content,
14252
+ timestamp: new Date(m.createdAt).getTime() || Date.now()
14253
+ }));
14254
+ const cacheKey2 = crypto.randomUUID();
14255
+ const resumed = uiMessages.length > 0;
14256
+ console.log(`[session] cold-resume visitor=${visitorId.slice(0, 8)}\u2026 session=${recent.sessionId.slice(0, 8)}\u2026 messages=${uiMessages.length}`);
14257
+ return withVisitorCookie(
14258
+ Response.json({
14259
+ session_key: cacheKey2,
14260
+ agent_id: agentSlug,
14261
+ resumed,
14262
+ ...resumed ? { messages: uiMessages } : {},
14263
+ ...branding ? { branding } : {},
14264
+ ...agentIdentity
14265
+ }),
14266
+ visitorId
14267
+ );
14268
+ }
14269
+ }
14270
+ const newVisitorId = visitorId ?? crypto.randomUUID();
14271
+ const cacheKey = crypto.randomUUID();
14272
+ console.log(`[session] new-session visitor=${newVisitorId.slice(0, 8)}\u2026 session=${cacheKey.slice(0, 8)}\u2026 agent=${agentSlug} image=${agentConfig?.image ? "yes" : "no"}`);
14273
+ return withVisitorCookie(
14274
+ Response.json({
14275
+ session_key: cacheKey,
14276
+ agent_id: agentSlug,
14277
+ ...branding ? { branding } : {},
14278
+ ...agentIdentity
14279
+ }),
14280
+ newVisitorId
14281
+ );
14282
+ });
14283
+ var session_default2 = app41;
14284
+
14040
14285
  // app/lib/graph-health.ts
14041
14286
  var HOUR_MS = 60 * 60 * 1e3;
14042
14287
  var timer = null;
@@ -14120,7 +14365,7 @@ function startGraphHealthTimer() {
14120
14365
 
14121
14366
  // app/lib/file-watcher.ts
14122
14367
  import * as fsp2 from "fs/promises";
14123
- import { resolve as resolve22, sep as sep6 } from "path";
14368
+ import { resolve as resolve23, sep as sep6 } from "path";
14124
14369
  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;
14125
14370
  var DEFAULT_COALESCE_MS = 500;
14126
14371
  var ROOTS = ["uploads", "accounts"];
@@ -14139,7 +14384,7 @@ async function startFileWatcher(opts = {}) {
14139
14384
  const dropFn = opts.drop ?? dropFileIndex;
14140
14385
  for (const r of ROOTS) {
14141
14386
  try {
14142
- await fsp2.mkdir(resolve22(dataRoot, r), { recursive: true });
14387
+ await fsp2.mkdir(resolve23(dataRoot, r), { recursive: true });
14143
14388
  } catch (err) {
14144
14389
  console.error(
14145
14390
  `[file-watcher] start-failed root="${r}" err="${err.message}" \u2014 index will be maintained by the 5-min reconcile backstop only`
@@ -14160,7 +14405,7 @@ async function startFileWatcher(opts = {}) {
14160
14405
  timers.set(relativePath, t);
14161
14406
  }
14162
14407
  async function runHook(relativePath, accountId) {
14163
- const absolute = resolve22(dataRoot, relativePath);
14408
+ const absolute = resolve23(dataRoot, relativePath);
14164
14409
  let exists = false;
14165
14410
  try {
14166
14411
  const st = await fsp2.stat(absolute);
@@ -14184,7 +14429,7 @@ async function startFileWatcher(opts = {}) {
14184
14429
  }
14185
14430
  }
14186
14431
  async function watchRoot(rootName) {
14187
- const absRoot = resolve22(dataRoot, rootName);
14432
+ const absRoot = resolve23(dataRoot, rootName);
14188
14433
  try {
14189
14434
  const iter = fsp2.watch(absRoot, { recursive: true, signal: controller.signal });
14190
14435
  for await (const event of iter) {
@@ -14282,8 +14527,8 @@ function broadcastAdminShutdown(reason) {
14282
14527
 
14283
14528
  // ../lib/entitlement/src/index.ts
14284
14529
  import { createPublicKey, createHash as createHash4, verify as cryptoVerify } from "crypto";
14285
- import { existsSync as existsSync21, readFileSync as readFileSync21, statSync as statSync9 } from "fs";
14286
- import { resolve as resolve23 } from "path";
14530
+ import { existsSync as existsSync22, readFileSync as readFileSync21, statSync as statSync9 } from "fs";
14531
+ import { resolve as resolve24 } from "path";
14287
14532
 
14288
14533
  // ../lib/entitlement/src/canonicalize.ts
14289
14534
  function canonicalize(value) {
@@ -14318,7 +14563,7 @@ var PUBKEY_SHA256 = "8eee6bcb33545fd13b16d3199a5735ca5db5062834c7b49dfe4f23801d9
14318
14563
  var GRACE_DAYS = 7;
14319
14564
  var GRACE_MS = GRACE_DAYS * 24 * 60 * 60 * 1e3;
14320
14565
  function pubkeyPath(brand) {
14321
- return resolve23(brand.platformRoot, "lib", "entitlement", "rubytech-pubkey.pem");
14566
+ return resolve24(brand.platformRoot, "lib", "entitlement", "rubytech-pubkey.pem");
14322
14567
  }
14323
14568
  var memo = null;
14324
14569
  function memoKey(mtimeMs, account) {
@@ -14330,8 +14575,8 @@ function resolveEntitlement(brand, account) {
14330
14575
  if (brand.commercialMode !== true) {
14331
14576
  return logResolved(implicitTrust(account), null);
14332
14577
  }
14333
- const entitlementPath = resolve23(brand.configDir, "entitlement.json");
14334
- if (!existsSync21(entitlementPath)) {
14578
+ const entitlementPath = resolve24(brand.configDir, "entitlement.json");
14579
+ if (!existsSync22(entitlementPath)) {
14335
14580
  return logResolved(anonymousFallback("missing"), { reason: "missing" });
14336
14581
  }
14337
14582
  const stat7 = statSync9(entitlementPath);
@@ -14523,10 +14768,10 @@ function clientFrom(c) {
14523
14768
  var PLATFORM_ROOT9 = process.env.MAXY_PLATFORM_ROOT || "";
14524
14769
  var BRAND_JSON_PATH = PLATFORM_ROOT9 ? join17(PLATFORM_ROOT9, "config", "brand.json") : "";
14525
14770
  var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", marketingUrl: "https://getmaxy.com" };
14526
- if (BRAND_JSON_PATH && !existsSync22(BRAND_JSON_PATH)) {
14771
+ if (BRAND_JSON_PATH && !existsSync23(BRAND_JSON_PATH)) {
14527
14772
  console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
14528
14773
  }
14529
- if (BRAND_JSON_PATH && existsSync22(BRAND_JSON_PATH)) {
14774
+ if (BRAND_JSON_PATH && existsSync23(BRAND_JSON_PATH)) {
14530
14775
  try {
14531
14776
  const parsed = JSON.parse(readFileSync22(BRAND_JSON_PATH, "utf-8"));
14532
14777
  BRAND = { ...BRAND, ...parsed };
@@ -14550,7 +14795,7 @@ var brandLoginOpts = {
14550
14795
  var ALIAS_DOMAINS_PATH = join17(homedir2(), BRAND.configDir, "alias-domains.json");
14551
14796
  function loadAliasDomains() {
14552
14797
  try {
14553
- if (!existsSync22(ALIAS_DOMAINS_PATH)) return null;
14798
+ if (!existsSync23(ALIAS_DOMAINS_PATH)) return null;
14554
14799
  const parsed = JSON.parse(readFileSync22(ALIAS_DOMAINS_PATH, "utf-8"));
14555
14800
  if (!Array.isArray(parsed)) {
14556
14801
  console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
@@ -14575,9 +14820,9 @@ watchFile(ALIAS_DOMAINS_PATH, { interval: 2e3 }, () => {
14575
14820
  function isPublicHost(host) {
14576
14821
  return host.startsWith("public.") || aliasDomains.has(host);
14577
14822
  }
14578
- var app41 = new Hono();
14579
- app41.use("*", clientIpMiddleware);
14580
- app41.use("*", async (c, next) => {
14823
+ var app42 = new Hono();
14824
+ app42.use("*", clientIpMiddleware);
14825
+ app42.use("*", async (c, next) => {
14581
14826
  await next();
14582
14827
  c.header("X-Content-Type-Options", "nosniff");
14583
14828
  c.header("Referrer-Policy", "strict-origin-when-cross-origin");
@@ -14587,7 +14832,7 @@ app41.use("*", async (c, next) => {
14587
14832
  );
14588
14833
  });
14589
14834
  var HTTP_LOG_PATHS = /* @__PURE__ */ new Set(["/vnc-viewer.html", "/vnc-popout.html"]);
14590
- app41.use("*", async (c, next) => {
14835
+ app42.use("*", async (c, next) => {
14591
14836
  if (!HTTP_LOG_PATHS.has(c.req.path)) {
14592
14837
  await next();
14593
14838
  return;
@@ -14620,7 +14865,7 @@ var PUBLIC_ALLOWED_PREFIXES = [
14620
14865
  "/sites/"
14621
14866
  ];
14622
14867
  var PUBLIC_ALLOWED_EXACT = ["/favicon.ico"];
14623
- app41.use("*", async (c, next) => {
14868
+ app42.use("*", async (c, next) => {
14624
14869
  const host = (c.req.header("host") ?? "").split(":")[0];
14625
14870
  if (!isPublicHost(host)) {
14626
14871
  await next();
@@ -14660,7 +14905,7 @@ function resolveRemoteAuthOpts() {
14660
14905
  return brandLoginOpts;
14661
14906
  }
14662
14907
  var MAX_LOGIN_BODY = 8 * 1024;
14663
- app41.post("/__remote-auth/login", async (c) => {
14908
+ app42.post("/__remote-auth/login", async (c) => {
14664
14909
  const client = clientFrom(c);
14665
14910
  const clientIp = client.ip || "unknown";
14666
14911
  if (!requestIsTlsTerminated(c)) {
@@ -14705,7 +14950,7 @@ app41.post("/__remote-auth/login", async (c) => {
14705
14950
  }
14706
14951
  });
14707
14952
  });
14708
- app41.get("/__remote-auth/logout", (c) => {
14953
+ app42.get("/__remote-auth/logout", (c) => {
14709
14954
  const client = clientFrom(c);
14710
14955
  const clientIp = client.ip || "unknown";
14711
14956
  console.error(`[remote-auth] logout ip=${clientIp}`);
@@ -14718,7 +14963,7 @@ app41.get("/__remote-auth/logout", (c) => {
14718
14963
  }
14719
14964
  });
14720
14965
  });
14721
- app41.post("/__remote-auth/change-password", async (c) => {
14966
+ app42.post("/__remote-auth/change-password", async (c) => {
14722
14967
  const client = clientFrom(c);
14723
14968
  const clientIp = client.ip || "unknown";
14724
14969
  const rateLimited = checkRateLimit(client);
@@ -14769,13 +15014,13 @@ app41.post("/__remote-auth/change-password", async (c) => {
14769
15014
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), mode: "change", changeError: "Failed to save password", redirect }), 200);
14770
15015
  }
14771
15016
  });
14772
- app41.get("/__remote-auth/setup", (c) => {
15017
+ app42.get("/__remote-auth/setup", (c) => {
14773
15018
  if (isRemoteAuthConfigured()) {
14774
15019
  return c.redirect("/");
14775
15020
  }
14776
15021
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), mode: "setup" }), 200);
14777
15022
  });
14778
- app41.post("/__remote-auth/set-initial-password", async (c) => {
15023
+ app42.post("/__remote-auth/set-initial-password", async (c) => {
14779
15024
  if (isRemoteAuthConfigured()) {
14780
15025
  return c.redirect("/");
14781
15026
  }
@@ -14813,10 +15058,10 @@ app41.post("/__remote-auth/set-initial-password", async (c) => {
14813
15058
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), mode: "setup", setupError: "Failed to save password. Please try again." }), 200);
14814
15059
  }
14815
15060
  });
14816
- app41.get("/api/remote-auth/status", (c) => {
15061
+ app42.get("/api/remote-auth/status", (c) => {
14817
15062
  return c.json({ configured: isRemoteAuthConfigured() });
14818
15063
  });
14819
- app41.post("/api/remote-auth/set-password", async (c) => {
15064
+ app42.post("/api/remote-auth/set-password", async (c) => {
14820
15065
  let body;
14821
15066
  try {
14822
15067
  body = await c.req.json();
@@ -14847,9 +15092,9 @@ app41.post("/api/remote-auth/set-password", async (c) => {
14847
15092
  return c.json({ error: "Failed to save password" }, 500);
14848
15093
  }
14849
15094
  });
14850
- app41.route("/api/_client-error", client_error_default);
15095
+ app42.route("/api/_client-error", client_error_default);
14851
15096
  console.log("[client-error-route] mounted");
14852
- app41.use("*", async (c, next) => {
15097
+ app42.use("*", async (c, next) => {
14853
15098
  const host = (c.req.header("host") ?? "").split(":")[0];
14854
15099
  const path2 = c.req.path;
14855
15100
  if (path2 === "/favicon.ico" || path2.startsWith("/assets/") || path2.startsWith("/brand/")) {
@@ -14882,12 +15127,13 @@ app41.use("*", async (c, next) => {
14882
15127
  console.error(`[remote-auth] login required ip=${clientIp} path=${path2} ${disambig}`);
14883
15128
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), redirect: path2 }), 200);
14884
15129
  });
14885
- app41.route("/api/health", health_default);
14886
- app41.route("/api/chat", chat_default);
14887
- app41.route("/api/whatsapp", whatsapp_default);
14888
- app41.route("/api/onboarding", onboarding_default);
14889
- app41.route("/api/admin", admin_default);
14890
- app41.route("/api/access", access_default);
15130
+ app42.route("/api/health", health_default);
15131
+ app42.route("/api/chat", chat_default);
15132
+ app42.route("/api/whatsapp", whatsapp_default);
15133
+ app42.route("/api/onboarding", onboarding_default);
15134
+ app42.route("/api/admin", admin_default);
15135
+ app42.route("/api/access", access_default);
15136
+ app42.route("/api/session", session_default2);
14891
15137
  var SAFE_SLUG_RE2 = /^[a-z][a-z0-9-]{2,49}$/;
14892
15138
  var SAFE_FILENAME_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
14893
15139
  var IMAGE_MIME = {
@@ -14899,7 +15145,7 @@ var IMAGE_MIME = {
14899
15145
  ".svg": "image/svg+xml",
14900
15146
  ".ico": "image/x-icon"
14901
15147
  };
14902
- app41.get("/agent-assets/:slug/:filename", (c) => {
15148
+ app42.get("/agent-assets/:slug/:filename", (c) => {
14903
15149
  const slug = c.req.param("slug");
14904
15150
  const filename = c.req.param("filename");
14905
15151
  if (!SAFE_SLUG_RE2.test(slug)) {
@@ -14915,13 +15161,13 @@ app41.get("/agent-assets/:slug/:filename", (c) => {
14915
15161
  console.error(`[agent-assets] no-account slug=${slug} file=${filename}`);
14916
15162
  return c.text("Not found", 404);
14917
15163
  }
14918
- const filePath = resolve24(account.accountDir, "agents", slug, "assets", filename);
14919
- const expectedDir = resolve24(account.accountDir, "agents", slug, "assets");
15164
+ const filePath = resolve25(account.accountDir, "agents", slug, "assets", filename);
15165
+ const expectedDir = resolve25(account.accountDir, "agents", slug, "assets");
14920
15166
  if (!filePath.startsWith(expectedDir + "/")) {
14921
15167
  console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
14922
15168
  return c.text("Forbidden", 403);
14923
15169
  }
14924
- if (!existsSync22(filePath)) {
15170
+ if (!existsSync23(filePath)) {
14925
15171
  console.error(`[agent-assets] serve slug=${slug} file=${filename} status=404`);
14926
15172
  return c.text("Not found", 404);
14927
15173
  }
@@ -14934,7 +15180,7 @@ app41.get("/agent-assets/:slug/:filename", (c) => {
14934
15180
  "Cache-Control": "public, max-age=3600"
14935
15181
  });
14936
15182
  });
14937
- app41.get("/generated/:filename", (c) => {
15183
+ app42.get("/generated/:filename", (c) => {
14938
15184
  const filename = c.req.param("filename");
14939
15185
  if (!SAFE_FILENAME_RE.test(filename) || filename.includes("..")) {
14940
15186
  console.error(`[generated] serve file=${filename} status=403`);
@@ -14945,13 +15191,13 @@ app41.get("/generated/:filename", (c) => {
14945
15191
  console.error(`[generated] serve file=${filename} status=404`);
14946
15192
  return c.text("Not found", 404);
14947
15193
  }
14948
- const filePath = resolve24(account.accountDir, "generated", filename);
14949
- const expectedDir = resolve24(account.accountDir, "generated");
15194
+ const filePath = resolve25(account.accountDir, "generated", filename);
15195
+ const expectedDir = resolve25(account.accountDir, "generated");
14950
15196
  if (!filePath.startsWith(expectedDir + "/")) {
14951
15197
  console.error(`[generated] serve file=${filename} status=403`);
14952
15198
  return c.text("Forbidden", 403);
14953
15199
  }
14954
- if (!existsSync22(filePath)) {
15200
+ if (!existsSync23(filePath)) {
14955
15201
  console.error(`[generated] serve file=${filename} status=404`);
14956
15202
  return c.text("Not found", 404);
14957
15203
  }
@@ -14964,14 +15210,14 @@ app41.get("/generated/:filename", (c) => {
14964
15210
  "Cache-Control": "public, max-age=86400"
14965
15211
  });
14966
15212
  });
14967
- app41.route("/sites", sites_default);
14968
- app41.route("/listings", listings_default);
14969
- app41.route("/v", visitor_event_default);
14970
- app41.route("/v", visitor_consent_default);
15213
+ app42.route("/sites", sites_default);
15214
+ app42.route("/listings", listings_default);
15215
+ app42.route("/v", visitor_event_default);
15216
+ app42.route("/v", visitor_consent_default);
14971
15217
  var htmlCache = /* @__PURE__ */ new Map();
14972
15218
  var brandLogoPath = "/brand/maxy-monochrome.png";
14973
15219
  var brandIconPath = "/brand/maxy-monochrome.png";
14974
- if (BRAND_JSON_PATH && existsSync22(BRAND_JSON_PATH)) {
15220
+ if (BRAND_JSON_PATH && existsSync23(BRAND_JSON_PATH)) {
14975
15221
  try {
14976
15222
  const fullBrand = JSON.parse(readFileSync22(BRAND_JSON_PATH, "utf-8"));
14977
15223
  if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
@@ -14991,7 +15237,7 @@ function readInstalledVersion() {
14991
15237
  try {
14992
15238
  if (!PLATFORM_ROOT9) return "unknown";
14993
15239
  const versionFile = join17(PLATFORM_ROOT9, "config", `.${BRAND.hostname}-version`);
14994
- if (!existsSync22(versionFile)) return "unknown";
15240
+ if (!existsSync23(versionFile)) return "unknown";
14995
15241
  const content = readFileSync22(versionFile, "utf-8").trim();
14996
15242
  return content || "unknown";
14997
15243
  } catch {
@@ -15033,7 +15279,7 @@ var clientErrorReporterScript = `<script>
15033
15279
  function cachedHtml(file) {
15034
15280
  let html = htmlCache.get(file);
15035
15281
  if (!html) {
15036
- html = readFileSync22(resolve24(process.cwd(), "public", file), "utf-8");
15282
+ html = readFileSync22(resolve25(process.cwd(), "public", file), "utf-8");
15037
15283
  const productNameEsc = escapeHtml(BRAND.productName);
15038
15284
  html = html.replace(/<title>([^<]*)<\/title>/, (_match, inner) => `<title>${escapeHtml(inner).replace(/Maxy/g, productNameEsc)}</title>`);
15039
15285
  html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
@@ -15052,12 +15298,12 @@ function loadBrandingCache(agentSlug) {
15052
15298
  const configDir2 = join17(homedir2(), BRAND.configDir);
15053
15299
  try {
15054
15300
  const accountJsonPath = join17(configDir2, "account.json");
15055
- if (!existsSync22(accountJsonPath)) return null;
15301
+ if (!existsSync23(accountJsonPath)) return null;
15056
15302
  const account = JSON.parse(readFileSync22(accountJsonPath, "utf-8"));
15057
15303
  const accountId = account.accountId;
15058
15304
  if (!accountId) return null;
15059
15305
  const cachePath = join17(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
15060
- if (!existsSync22(cachePath)) return null;
15306
+ if (!existsSync23(cachePath)) return null;
15061
15307
  return JSON.parse(readFileSync22(cachePath, "utf-8"));
15062
15308
  } catch {
15063
15309
  return null;
@@ -15067,7 +15313,7 @@ function resolveDefaultSlug() {
15067
15313
  try {
15068
15314
  const configDir2 = join17(homedir2(), BRAND.configDir);
15069
15315
  const accountJsonPath = join17(configDir2, "account.json");
15070
- if (!existsSync22(accountJsonPath)) return null;
15316
+ if (!existsSync23(accountJsonPath)) return null;
15071
15317
  const account = JSON.parse(readFileSync22(accountJsonPath, "utf-8"));
15072
15318
  return account.defaultAgent || null;
15073
15319
  } catch {
@@ -15104,7 +15350,7 @@ function brandedPublicHtml(agentSlug) {
15104
15350
  function escapeHtml(s) {
15105
15351
  return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
15106
15352
  }
15107
- app41.get("/", (c) => {
15353
+ app42.get("/", (c) => {
15108
15354
  const host = (c.req.header("host") ?? "").split(":")[0];
15109
15355
  if (isPublicHost(host)) {
15110
15356
  const defaultSlug = resolveDefaultSlug();
@@ -15112,12 +15358,12 @@ app41.get("/", (c) => {
15112
15358
  }
15113
15359
  return c.html(cachedHtml("index.html"));
15114
15360
  });
15115
- app41.get("/public", (c) => {
15361
+ app42.get("/public", (c) => {
15116
15362
  const host = (c.req.header("host") ?? "").split(":")[0];
15117
15363
  if (isPublicHost(host)) return c.text("Not found", 404);
15118
15364
  return c.html(cachedHtml("public.html"));
15119
15365
  });
15120
- app41.get("/chat", (c) => {
15366
+ app42.get("/chat", (c) => {
15121
15367
  const host = (c.req.header("host") ?? "").split(":")[0];
15122
15368
  if (isPublicHost(host)) return c.text("Not found", 404);
15123
15369
  return c.html(cachedHtml("public.html"));
@@ -15136,12 +15382,12 @@ async function logViewerFetch(c, next) {
15136
15382
  duration_ms: Date.now() - start
15137
15383
  });
15138
15384
  }
15139
- app41.use("/vnc-viewer.html", logViewerFetch);
15140
- app41.use("/vnc-popout.html", logViewerFetch);
15141
- app41.get("/vnc-popout.html", (c) => {
15385
+ app42.use("/vnc-viewer.html", logViewerFetch);
15386
+ app42.use("/vnc-popout.html", logViewerFetch);
15387
+ app42.get("/vnc-popout.html", (c) => {
15142
15388
  let html = htmlCache.get("vnc-popout.html");
15143
15389
  if (!html) {
15144
- html = readFileSync22(resolve24(process.cwd(), "public", "vnc-popout.html"), "utf-8");
15390
+ html = readFileSync22(resolve25(process.cwd(), "public", "vnc-popout.html"), "utf-8");
15145
15391
  const name = escapeHtml(BRAND.productName);
15146
15392
  html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
15147
15393
  html = html.replace("</head>", ` ${brandScript}
@@ -15151,7 +15397,7 @@ app41.get("/vnc-popout.html", (c) => {
15151
15397
  }
15152
15398
  return c.html(html);
15153
15399
  });
15154
- app41.post("/api/vnc/client-event", async (c) => {
15400
+ app42.post("/api/vnc/client-event", async (c) => {
15155
15401
  let body;
15156
15402
  try {
15157
15403
  body = await c.req.json();
@@ -15172,25 +15418,25 @@ app41.post("/api/vnc/client-event", async (c) => {
15172
15418
  });
15173
15419
  return c.json({ ok: true });
15174
15420
  });
15175
- app41.get("/g/:slug", (c) => {
15421
+ app42.get("/g/:slug", (c) => {
15176
15422
  return c.html(brandedPublicHtml());
15177
15423
  });
15178
- app41.get("/graph", (c) => {
15424
+ app42.get("/graph", (c) => {
15179
15425
  const host = (c.req.header("host") ?? "").split(":")[0];
15180
15426
  if (isPublicHost(host)) return c.text("Not found", 404);
15181
15427
  return c.html(cachedHtml("graph.html"));
15182
15428
  });
15183
- app41.get("/sessions", (c) => {
15429
+ app42.get("/sessions", (c) => {
15184
15430
  const host = (c.req.header("host") ?? "").split(":")[0];
15185
15431
  if (isPublicHost(host)) return c.text("Not found", 404);
15186
15432
  return c.html(cachedHtml("sessions.html"));
15187
15433
  });
15188
- app41.get("/data", (c) => {
15434
+ app42.get("/data", (c) => {
15189
15435
  const host = (c.req.header("host") ?? "").split(":")[0];
15190
15436
  if (isPublicHost(host)) return c.text("Not found", 404);
15191
15437
  return c.html(cachedHtml("data.html"));
15192
15438
  });
15193
- app41.get("/:slug", async (c, next) => {
15439
+ app42.get("/:slug", async (c, next) => {
15194
15440
  const slug = c.req.param("slug");
15195
15441
  if (AGENT_SLUG_PATTERN.test(`/${slug}`)) {
15196
15442
  const branding = loadBrandingCache(slug);
@@ -15200,15 +15446,27 @@ app41.get("/:slug", async (c, next) => {
15200
15446
  await next();
15201
15447
  });
15202
15448
  if (brandFaviconPath !== "/favicon.ico") {
15203
- app41.get("/favicon.ico", (c) => {
15449
+ app42.get("/favicon.ico", (c) => {
15204
15450
  c.header("Cache-Control", "public, max-age=300");
15205
15451
  return c.redirect(brandFaviconPath, 302);
15206
15452
  });
15207
15453
  }
15208
- app41.use("/*", serveStatic({ root: "./public" }));
15454
+ app42.use("/*", serveStatic({ root: "./public" }));
15455
+ app42.all("*", (c) => {
15456
+ const host = (c.req.header("host") ?? "").split(":")[0];
15457
+ const path2 = c.req.path;
15458
+ if (isPublicHost(host)) {
15459
+ const inAllowedList = PUBLIC_ALLOWED_EXACT.includes(path2) || PUBLIC_ALLOWED_PREFIXES.some((prefix) => path2.startsWith(prefix));
15460
+ if (inAllowedList) {
15461
+ console.error(`[public-host] unmatched-allowed path=${path2}`);
15462
+ return c.json({ error: "not-found" }, 404);
15463
+ }
15464
+ }
15465
+ return c.text("Not found", 404);
15466
+ });
15209
15467
  var port = requirePortEnv("MAXY_UI_INTERNAL_PORT", { tag: "ui-server" });
15210
15468
  var hostname = process.env.HOSTNAME ?? "127.0.0.1";
15211
- var httpServer = serve({ fetch: app41.fetch, port, hostname });
15469
+ var httpServer = serve({ fetch: app42.fetch, port, hostname });
15212
15470
  console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
15213
15471
  startAuthHealthHeartbeat();
15214
15472
  {
@@ -15234,6 +15492,7 @@ var SUBAPP_MANIFEST = [
15234
15492
  { prefix: "/api/onboarding", file: "server/routes/onboarding.ts", subapp: onboarding_default },
15235
15493
  { prefix: "/api/admin", file: "server/routes/admin/index.ts", subapp: admin_default },
15236
15494
  { prefix: "/api/access", file: "server/routes/access/index.ts", subapp: access_default },
15495
+ { prefix: "/api/session", file: "server/routes/session.ts", subapp: session_default2 },
15237
15496
  { prefix: "/api/_client-error", file: "server/routes/client-error.ts", subapp: client_error_default },
15238
15497
  { prefix: "/listings", file: "server/routes/listings.ts", subapp: listings_default },
15239
15498
  { prefix: "/v", file: "server/routes/visitor-event.ts", subapp: visitor_event_default },
@@ -15245,7 +15504,7 @@ for (const m of SUBAPP_MANIFEST) {
15245
15504
  }
15246
15505
  try {
15247
15506
  const registered = [];
15248
- for (const r of app41.routes ?? []) {
15507
+ for (const r of app42.routes ?? []) {
15249
15508
  if (typeof r.path !== "string" || r.path.includes(":") || r.path.includes("*")) continue;
15250
15509
  if (AGENT_SLUG_PATTERN.test(r.path)) {
15251
15510
  registered.push({ method: (r.method ?? "ALL").toUpperCase(), path: r.path });
@@ -15267,7 +15526,7 @@ try {
15267
15526
  }
15268
15527
  (async () => {
15269
15528
  try {
15270
- if (!existsSync22(USERS_FILE)) return;
15529
+ if (!existsSync23(USERS_FILE)) return;
15271
15530
  const usersRaw = readFileSync22(USERS_FILE, "utf-8").trim();
15272
15531
  if (!usersRaw) return;
15273
15532
  const users = JSON.parse(usersRaw);
@@ -15363,7 +15622,7 @@ if (bootAccountConfig?.whatsapp) {
15363
15622
  }
15364
15623
  init({
15365
15624
  configDir: configDirForWhatsApp,
15366
- platformRoot: resolve24(process.env.MAXY_PLATFORM_ROOT ?? join17(__dirname, "..")),
15625
+ platformRoot: resolve25(process.env.MAXY_PLATFORM_ROOT ?? join17(__dirname, "..")),
15367
15626
  accountConfig: bootAccountConfig,
15368
15627
  onMessage: async (msg) => {
15369
15628
  if (process.env.WHATSAPP_PTY_BRIDGE_ENABLED === "true" && msg.text && !msg.isOwnerMirror) {