@rubytech/create-maxy-code 0.1.276 → 0.1.277

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 (28) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +3 -3
  3. package/payload/platform/plugins/docs/references/admin-identity-gate.md +44 -7
  4. package/payload/platform/plugins/docs/references/admin-ui.md +2 -2
  5. package/payload/platform/scripts/check-no-esm-require.mjs +3 -0
  6. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +5 -0
  7. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  8. package/payload/platform/services/claude-session-manager/dist/http-server.js +52 -10
  9. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  10. package/payload/platform/services/claude-session-manager/dist/index.js +8 -0
  11. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  12. package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.d.ts +8 -0
  13. package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.d.ts.map +1 -1
  14. package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.js +13 -0
  15. package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.js.map +1 -1
  16. package/payload/platform/services/claude-session-manager/dist/wa-channel-store.d.ts +29 -0
  17. package/payload/platform/services/claude-session-manager/dist/wa-channel-store.d.ts.map +1 -0
  18. package/payload/platform/services/claude-session-manager/dist/wa-channel-store.js +124 -0
  19. package/payload/platform/services/claude-session-manager/dist/wa-channel-store.js.map +1 -0
  20. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +23 -0
  21. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
  22. package/payload/platform/services/whatsapp-channel/dist/notification.js +26 -0
  23. package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
  24. package/payload/platform/services/whatsapp-channel/dist/server.js +41 -14
  25. package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
  26. package/payload/server/{chunk-W4EM7RK4.js → chunk-QHD5TKLQ.js} +2 -0
  27. package/payload/server/maxy-edge.js +1 -1
  28. package/payload/server/server.js +411 -172
@@ -11,6 +11,7 @@ import {
11
11
  Hono,
12
12
  HtmlEscapedCallbackPhase,
13
13
  INSTALL_OWNER_FILE,
14
+ LID_MAP_FILE,
14
15
  LOG_DIR,
15
16
  MAXY_DIR,
16
17
  PLATFORM_ROOT,
@@ -95,7 +96,7 @@ import {
95
96
  vncLog,
96
97
  walkPremiumBundles,
97
98
  writeAdminUserAndPerson
98
- } from "./chunk-W4EM7RK4.js";
99
+ } from "./chunk-QHD5TKLQ.js";
99
100
  import {
100
101
  __commonJS,
101
102
  __toESM
@@ -1200,7 +1201,7 @@ var serveStatic = (options = { root: "" }) => {
1200
1201
  };
1201
1202
 
1202
1203
  // server/index.ts
1203
- import { readFileSync as readFileSync22, existsSync as existsSync23, watchFile } from "fs";
1204
+ import { readFileSync as readFileSync23, existsSync as existsSync24, watchFile } from "fs";
1204
1205
  import { resolve as resolve26, join as join18, basename as basename6 } from "path";
1205
1206
  import { homedir as homedir2 } from "os";
1206
1207
  import { monitorEventLoopDelay } from "perf_hooks";
@@ -1488,7 +1489,7 @@ async function ensureAuth() {
1488
1489
  }
1489
1490
 
1490
1491
  // server/routes/health.ts
1491
- import { existsSync as existsSync2, readFileSync as readFileSync4 } from "fs";
1492
+ import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
1492
1493
  import { createConnection as createConnection2 } from "net";
1493
1494
 
1494
1495
  // app/lib/network.ts
@@ -2189,6 +2190,49 @@ async function resolveJidToE164(jid, lidMapping) {
2189
2190
  return null;
2190
2191
  }
2191
2192
 
2193
+ // app/lib/whatsapp/inbound/lid-map.ts
2194
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
2195
+ function resolveViaLidMap(map, senderPhone) {
2196
+ const key = normalizeE164(senderPhone);
2197
+ if (!key) return null;
2198
+ const mapped = map[key];
2199
+ return mapped && mapped.length > 0 ? mapped : null;
2200
+ }
2201
+ function lidCaptureFor(senderJid, senderPhone) {
2202
+ if (!isLidJid(senderJid)) return null;
2203
+ const numericLid = jidToE164(senderJid);
2204
+ if (!numericLid) return null;
2205
+ const e164 = normalizeE164(senderPhone);
2206
+ if (!e164 || e164 === numericLid) return null;
2207
+ return { numericLid, e164 };
2208
+ }
2209
+ function readLidMap() {
2210
+ try {
2211
+ if (!existsSync2(LID_MAP_FILE)) return null;
2212
+ const raw = readFileSync2(LID_MAP_FILE, "utf-8").trim();
2213
+ if (!raw) return null;
2214
+ const parsed = JSON.parse(raw);
2215
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
2216
+ return parsed;
2217
+ } catch {
2218
+ return null;
2219
+ }
2220
+ }
2221
+ function recordLidMapping(numericLid, e164) {
2222
+ try {
2223
+ const key = normalizeE164(numericLid);
2224
+ const val = normalizeE164(e164);
2225
+ if (!key || !val) return;
2226
+ const map = readLidMap() ?? {};
2227
+ if (map[key] === val) return;
2228
+ map[key] = val;
2229
+ writeFileSync2(LID_MAP_FILE, JSON.stringify(map, null, 2));
2230
+ console.error(`[lid-map] recorded lid=${key} userId-phone=present`);
2231
+ } catch (err) {
2232
+ console.error(`[lid-map] record-failed lid=${numericLid}: ${err instanceof Error ? err.message : String(err)}`);
2233
+ }
2234
+ }
2235
+
2192
2236
  // app/lib/whatsapp/inbound/extract.ts
2193
2237
  import {
2194
2238
  extractMessageContent,
@@ -2743,7 +2787,7 @@ async function ensureWhatsAppConversation(input) {
2743
2787
  }
2744
2788
 
2745
2789
  // ../lib/account-enumeration/src/index.ts
2746
- import { readdirSync, readFileSync as readFileSync2 } from "fs";
2790
+ import { readdirSync, readFileSync as readFileSync3 } from "fs";
2747
2791
  import { resolve } from "path";
2748
2792
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2749
2793
  var cache = /* @__PURE__ */ new Map();
@@ -2765,7 +2809,7 @@ function enumerateValidAccountIds(accountsDir) {
2765
2809
  if (!UUID_RE.test(name)) continue;
2766
2810
  const configPath2 = resolve(accountsDir, name, "account.json");
2767
2811
  try {
2768
- JSON.parse(readFileSync2(configPath2, "utf-8"));
2812
+ JSON.parse(readFileSync3(configPath2, "utf-8"));
2769
2813
  valid.push(name);
2770
2814
  } catch (err) {
2771
2815
  const code = err.code;
@@ -3877,6 +3921,8 @@ async function handleInboundMessage(conn, msg) {
3877
3921
  const isGroup = isGroupJid(remoteJid);
3878
3922
  const senderJid = isGroup ? msg.key.participant ?? remoteJid : remoteJid;
3879
3923
  const senderPhone = await resolveJidToE164(senderJid, conn.lidMapping) ?? senderJid;
3924
+ const lidCapture = lidCaptureFor(senderJid, senderPhone);
3925
+ if (lidCapture) recordLidMapping(lidCapture.numericLid, lidCapture.e164);
3880
3926
  const selfPhone = conn.selfPhone ?? "";
3881
3927
  if (extracted.mediaType === "audio" && mediaResult?.path) {
3882
3928
  const conversationKey = isGroup ? remoteJid : senderPhone;
@@ -3997,7 +4043,7 @@ async function handleInboundMessage(conn, msg) {
3997
4043
  // app/lib/vnc.ts
3998
4044
  import { spawnSync, execFileSync } from "child_process";
3999
4045
  import { createConnection } from "net";
4000
- import { mkdirSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
4046
+ import { mkdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
4001
4047
  import { resolve as resolve2 } from "path";
4002
4048
  var PLATFORM_ROOT2 = process.env.MAXY_PLATFORM_ROOT ?? resolve2(process.cwd(), "..");
4003
4049
  var VNC_SCRIPT = resolve2(PLATFORM_ROOT2, "scripts/vnc.sh");
@@ -4055,7 +4101,7 @@ function discoverNativeDisplay() {
4055
4101
  const leaderPid = leaderResult.stdout?.trim();
4056
4102
  if (leaderPid) {
4057
4103
  try {
4058
- const environ = readFileSync3(`/proc/${leaderPid}/environ`, "utf8");
4104
+ const environ = readFileSync4(`/proc/${leaderPid}/environ`, "utf8");
4059
4105
  const match = environ.split("\0").find((e) => e.startsWith("WAYLAND_DISPLAY="));
4060
4106
  if (match) waylandDisplay = match.split("=")[1];
4061
4107
  } catch {
@@ -4193,7 +4239,7 @@ function killChromium() {
4193
4239
  function writeChromiumWrapper() {
4194
4240
  mkdirSync(BIN_DIR, { recursive: true });
4195
4241
  const wrapperPath = resolve2(BIN_DIR, "chromium");
4196
- writeFileSync2(wrapperPath, `#!/bin/bash
4242
+ writeFileSync3(wrapperPath, `#!/bin/bash
4197
4243
  LOG="${LOG_DIR}/chromium.log"
4198
4244
  echo "==== [$(date)] chromium wrapper ====" >> "$LOG"
4199
4245
  echo " DISPLAY=$DISPLAY WAYLAND=$WAYLAND_DISPLAY XDG_SESSION_TYPE=$XDG_SESSION_TYPE" >> "$LOG"
@@ -4314,8 +4360,8 @@ app.get("/", async (c) => {
4314
4360
  const browserTransport = resolveBrowserTransport(c.req.raw, c.env?.incoming?.socket?.remoteAddress);
4315
4361
  let pinConfigured = false;
4316
4362
  try {
4317
- if (existsSync2(USERS_FILE)) {
4318
- const raw = readFileSync4(USERS_FILE, "utf-8").trim();
4363
+ if (existsSync3(USERS_FILE)) {
4364
+ const raw = readFileSync5(USERS_FILE, "utf-8").trim();
4319
4365
  if (raw) {
4320
4366
  const users = JSON.parse(raw);
4321
4367
  pinConfigured = Array.isArray(users) && users.length > 0;
@@ -4634,6 +4680,7 @@ async function managerRcSpawn(opts) {
4634
4680
  closeAfterTurn: opts.closeAfterTurn,
4635
4681
  sliceToken: opts.sliceToken,
4636
4682
  personId: opts.personId,
4683
+ userId: opts.userId,
4637
4684
  waChannel: opts.waChannel
4638
4685
  })
4639
4686
  }).catch((err) => ({ __throw: err instanceof Error ? err.message : String(err) }));
@@ -5753,13 +5800,13 @@ var chat_default = app2;
5753
5800
 
5754
5801
  // server/routes/whatsapp.ts
5755
5802
  import { join as join6, resolve as resolve7 } from "path";
5756
- import { readdirSync as readdirSync3, readFileSync as readFileSync6, existsSync as existsSync4 } from "fs";
5803
+ import { readdirSync as readdirSync3, readFileSync as readFileSync7, existsSync as existsSync5 } from "fs";
5757
5804
 
5758
5805
  // app/lib/whatsapp/login.ts
5759
5806
  import { randomUUID as randomUUID6 } from "crypto";
5760
5807
 
5761
5808
  // app/lib/whatsapp/config-persist.ts
5762
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync3 } from "fs";
5809
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync4 } from "fs";
5763
5810
  import { resolve as resolve6, join as join4 } from "path";
5764
5811
  var TAG17 = "[whatsapp:config]";
5765
5812
  function configPath(accountDir) {
@@ -5767,12 +5814,12 @@ function configPath(accountDir) {
5767
5814
  }
5768
5815
  function readConfig(accountDir) {
5769
5816
  const path2 = configPath(accountDir);
5770
- if (!existsSync3(path2)) throw new Error(`account.json not found at ${path2}`);
5771
- return JSON.parse(readFileSync5(path2, "utf-8"));
5817
+ if (!existsSync4(path2)) throw new Error(`account.json not found at ${path2}`);
5818
+ return JSON.parse(readFileSync6(path2, "utf-8"));
5772
5819
  }
5773
5820
  function writeConfig(accountDir, config) {
5774
5821
  const path2 = configPath(accountDir);
5775
- writeFileSync3(path2, JSON.stringify(config, null, 2) + "\n", "utf-8");
5822
+ writeFileSync4(path2, JSON.stringify(config, null, 2) + "\n", "utf-8");
5776
5823
  }
5777
5824
  function reloadManagerConfig(accountDir) {
5778
5825
  try {
@@ -5913,7 +5960,7 @@ function setPublicAgent(accountDir, slug) {
5913
5960
  return { ok: false, error: "Agent slug cannot be empty." };
5914
5961
  }
5915
5962
  const agentConfigPath = join4(accountDir, "agents", trimmed, "config.json");
5916
- if (!existsSync3(agentConfigPath)) {
5963
+ if (!existsSync4(agentConfigPath)) {
5917
5964
  return { ok: false, error: `Agent "${trimmed}" not found \u2014 no config.json at ${agentConfigPath}. Check the agent slug and try again.` };
5918
5965
  }
5919
5966
  try {
@@ -5977,7 +6024,7 @@ function setGroupPublicAgent(accountDir, accountId, groupJid, slug) {
5977
6024
  if (!trimmedGroup) return { ok: false, error: "Group JID cannot be empty." };
5978
6025
  if (!trimmedAccount) return { ok: false, error: "Account ID cannot be empty." };
5979
6026
  const agentConfigPath = join4(accountDir, "agents", trimmedSlug, "config.json");
5980
- if (!existsSync3(agentConfigPath)) {
6027
+ if (!existsSync4(agentConfigPath)) {
5981
6028
  return { ok: false, error: `Agent "${trimmedSlug}" not found \u2014 no config.json at ${agentConfigPath}. Check the agent slug and try again.` };
5982
6029
  }
5983
6030
  try {
@@ -6663,15 +6710,15 @@ app3.post("/config", async (c) => {
6663
6710
  case "list-public-agents": {
6664
6711
  const agentsDir = resolve7(account.accountDir, "agents");
6665
6712
  const agents = [];
6666
- if (existsSync4(agentsDir)) {
6713
+ if (existsSync5(agentsDir)) {
6667
6714
  try {
6668
6715
  const entries = readdirSync3(agentsDir, { withFileTypes: true });
6669
6716
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
6670
6717
  if (!entry.isDirectory() || entry.name === "admin") continue;
6671
6718
  const configPath2 = resolve7(agentsDir, entry.name, "config.json");
6672
- if (!existsSync4(configPath2)) continue;
6719
+ if (!existsSync5(configPath2)) continue;
6673
6720
  try {
6674
- const config = JSON.parse(readFileSync6(configPath2, "utf-8"));
6721
+ const config = JSON.parse(readFileSync7(configPath2, "utf-8"));
6675
6722
  agents.push({ slug: entry.name, displayName: config.displayName ?? entry.name });
6676
6723
  } catch {
6677
6724
  console.error(`${TAG20} config action=list-public-agents error="failed to parse config.json for agent ${entry.name}" \u2014 skipping`);
@@ -6941,11 +6988,11 @@ var whatsapp_default = app3;
6941
6988
 
6942
6989
  // server/routes/onboarding.ts
6943
6990
  import { spawn, spawnSync as spawnSync2, execFileSync as execFileSync2 } from "child_process";
6944
- import { openSync, closeSync, writeFileSync as writeFileSync5, writeSync, existsSync as existsSync6, readFileSync as readFileSync8, unlinkSync } from "fs";
6991
+ import { openSync, closeSync, writeFileSync as writeFileSync6, writeSync, existsSync as existsSync7, readFileSync as readFileSync9, unlinkSync } from "fs";
6945
6992
  import { createHash as createHash2, randomUUID as randomUUID7 } from "crypto";
6946
6993
 
6947
6994
  // ../lib/admins-write/src/index.ts
6948
- import { existsSync as existsSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync4, renameSync, mkdirSync as mkdirSync2, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
6995
+ import { existsSync as existsSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync5, renameSync, mkdirSync as mkdirSync2, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
6949
6996
  import { dirname, join as join7 } from "path";
6950
6997
  function logLine(input, result) {
6951
6998
  const userIdShort = input.userId.slice(0, 8);
@@ -6956,15 +7003,15 @@ function logLine(input, result) {
6956
7003
  function writeFileAtomic(filePath, contents, mode) {
6957
7004
  mkdirSync2(dirname(filePath), { recursive: true });
6958
7005
  const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
6959
- writeFileSync4(tempPath, contents, mode !== void 0 ? { mode } : void 0);
7006
+ writeFileSync5(tempPath, contents, mode !== void 0 ? { mode } : void 0);
6960
7007
  renameSync(tempPath, filePath);
6961
7008
  }
6962
7009
  function writeAdminEntry(input) {
6963
7010
  const result = { usersJsonResult: "noop", accountJsonResult: "noop" };
6964
7011
  try {
6965
7012
  let users = [];
6966
- if (existsSync5(input.usersFile)) {
6967
- const raw = readFileSync7(input.usersFile, "utf-8").trim();
7013
+ if (existsSync6(input.usersFile)) {
7014
+ const raw = readFileSync8(input.usersFile, "utf-8").trim();
6968
7015
  if (raw) {
6969
7016
  const parsed = JSON.parse(raw);
6970
7017
  if (!Array.isArray(parsed)) {
@@ -6989,10 +7036,10 @@ function writeAdminEntry(input) {
6989
7036
  }
6990
7037
  try {
6991
7038
  const accountJsonPath = join7(input.accountDir, "account.json");
6992
- if (!existsSync5(accountJsonPath)) {
7039
+ if (!existsSync6(accountJsonPath)) {
6993
7040
  throw new Error(`account.json not found at ${accountJsonPath}`);
6994
7041
  }
6995
- const config = JSON.parse(readFileSync7(accountJsonPath, "utf-8"));
7042
+ const config = JSON.parse(readFileSync8(accountJsonPath, "utf-8"));
6996
7043
  const admins = config.admins ?? [];
6997
7044
  if (admins.some((a) => a.userId === input.userId)) {
6998
7045
  result.accountJsonResult = "noop";
@@ -7016,9 +7063,9 @@ function checkAdminAuthInvariant(input) {
7016
7063
  usersWithoutAccount: []
7017
7064
  };
7018
7065
  const usersUserIds = /* @__PURE__ */ new Set();
7019
- if (existsSync5(input.usersFile)) {
7066
+ if (existsSync6(input.usersFile)) {
7020
7067
  try {
7021
- const raw = readFileSync7(input.usersFile, "utf-8").trim();
7068
+ const raw = readFileSync8(input.usersFile, "utf-8").trim();
7022
7069
  if (raw) {
7023
7070
  const users = JSON.parse(raw);
7024
7071
  for (const u of users) {
@@ -7031,7 +7078,7 @@ function checkAdminAuthInvariant(input) {
7031
7078
  }
7032
7079
  }
7033
7080
  const accountUserIds = /* @__PURE__ */ new Set();
7034
- if (existsSync5(input.accountsDir)) {
7081
+ if (existsSync6(input.accountsDir)) {
7035
7082
  let entries;
7036
7083
  try {
7037
7084
  entries = readdirSync4(input.accountsDir);
@@ -7050,10 +7097,10 @@ function checkAdminAuthInvariant(input) {
7050
7097
  continue;
7051
7098
  }
7052
7099
  const accountJsonPath = join7(accountDir, "account.json");
7053
- if (!existsSync5(accountJsonPath)) continue;
7100
+ if (!existsSync6(accountJsonPath)) continue;
7054
7101
  let admins = [];
7055
7102
  try {
7056
- const config = JSON.parse(readFileSync7(accountJsonPath, "utf-8"));
7103
+ const config = JSON.parse(readFileSync8(accountJsonPath, "utf-8"));
7057
7104
  admins = config.admins ?? [];
7058
7105
  } catch (err) {
7059
7106
  const msg = err instanceof Error ? err.message : String(err);
@@ -7093,8 +7140,8 @@ function hashPin(pin) {
7093
7140
  return createHash2("sha256").update(pin).digest("hex");
7094
7141
  }
7095
7142
  function readUsersFile() {
7096
- if (!existsSync6(USERS_FILE)) return null;
7097
- const raw = readFileSync8(USERS_FILE, "utf-8").trim();
7143
+ if (!existsSync7(USERS_FILE)) return null;
7144
+ const raw = readFileSync9(USERS_FILE, "utf-8").trim();
7098
7145
  if (!raw) return [];
7099
7146
  return JSON.parse(raw);
7100
7147
  }
@@ -7159,7 +7206,7 @@ app4.post("/claude-auth", async (c) => {
7159
7206
  return c.json({ launched: cdpReady });
7160
7207
  }
7161
7208
  ensureLogDir();
7162
- writeFileSync5(logPath("claude-auth"), "");
7209
+ writeFileSync6(logPath("claude-auth"), "");
7163
7210
  const claudeAuthLogFd = openSync(logPath("claude-auth"), "a");
7164
7211
  const onClaudeOutput = (chunk) => writeSync(claudeAuthLogFd, chunk);
7165
7212
  if (process.platform === "darwin") {
@@ -7248,9 +7295,9 @@ app4.post("/set-pin", async (c) => {
7248
7295
  console.log(`[set-pin] wrote users.json + account.json admins: userId=${userId.slice(0, 8)}\u2026 role=owner`);
7249
7296
  if (process.platform === "linux") {
7250
7297
  let installOwner = null;
7251
- if (existsSync6(INSTALL_OWNER_FILE)) {
7298
+ if (existsSync7(INSTALL_OWNER_FILE)) {
7252
7299
  try {
7253
- const raw = readFileSync8(INSTALL_OWNER_FILE, "utf-8").trim();
7300
+ const raw = readFileSync9(INSTALL_OWNER_FILE, "utf-8").trim();
7254
7301
  if (raw.length > 0) installOwner = raw;
7255
7302
  } catch (err) {
7256
7303
  console.error(`[set-pin] install-owner-read-failed path=${INSTALL_OWNER_FILE} error=${err instanceof Error ? err.message : String(err)}`);
@@ -7323,7 +7370,7 @@ app4.delete("/set-pin", async (c) => {
7323
7370
  unlinkSync(USERS_FILE);
7324
7371
  console.log(`[set-pin] cleared users.json (last entry removed): userId=${matchedUser.userId.slice(0, 8)}\u2026`);
7325
7372
  } else {
7326
- writeFileSync5(USERS_FILE, JSON.stringify(remaining), { mode: 384 });
7373
+ writeFileSync6(USERS_FILE, JSON.stringify(remaining), { mode: 384 });
7327
7374
  console.log(`[set-pin] removed entry from users.json: userId=${matchedUser.userId.slice(0, 8)}\u2026 remaining=${remaining.length}`);
7328
7375
  }
7329
7376
  return c.json({ ok: true });
@@ -7331,7 +7378,7 @@ app4.delete("/set-pin", async (c) => {
7331
7378
  var onboarding_default = app4;
7332
7379
 
7333
7380
  // server/routes/client-error.ts
7334
- import { appendFileSync, existsSync as existsSync7, renameSync as renameSync2, statSync as statSync3 } from "fs";
7381
+ import { appendFileSync, existsSync as existsSync8, renameSync as renameSync2, statSync as statSync3 } from "fs";
7335
7382
  import { join as join8 } from "path";
7336
7383
  var CLIENT_ERRORS_LOG = join8(LOG_DIR, "client-errors.log");
7337
7384
  var MAX_LOG_SIZE = 10 * 1024 * 1024;
@@ -7376,7 +7423,7 @@ function stackHead(stack) {
7376
7423
  }
7377
7424
  function rotateIfNeeded() {
7378
7425
  try {
7379
- if (!existsSync7(CLIENT_ERRORS_LOG)) return;
7426
+ if (!existsSync8(CLIENT_ERRORS_LOG)) return;
7380
7427
  const stats = statSync3(CLIENT_ERRORS_LOG);
7381
7428
  if (stats.size < MAX_LOG_SIZE) return;
7382
7429
  renameSync2(CLIENT_ERRORS_LOG, CLIENT_ERRORS_LOG + ".1");
@@ -7494,14 +7541,14 @@ import { randomUUID as randomUUID8 } from "crypto";
7494
7541
 
7495
7542
  // app/lib/admin-identity/pin-validator.ts
7496
7543
  import { createHash as createHash3 } from "crypto";
7497
- import { existsSync as existsSync8, readFileSync as readFileSync9, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
7544
+ import { existsSync as existsSync9, readFileSync as readFileSync10, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
7498
7545
  import { join as join9 } from "path";
7499
7546
  function hashPin2(pin) {
7500
7547
  return createHash3("sha256").update(pin).digest("hex");
7501
7548
  }
7502
7549
  function readUsersFile2(usersFilePath) {
7503
- if (!existsSync8(usersFilePath)) return null;
7504
- const raw = readFileSync9(usersFilePath, "utf-8").trim();
7550
+ if (!existsSync9(usersFilePath)) return null;
7551
+ const raw = readFileSync10(usersFilePath, "utf-8").trim();
7505
7552
  if (!raw) return [];
7506
7553
  return JSON.parse(raw);
7507
7554
  }
@@ -7520,7 +7567,7 @@ function validatePin(pin, usersFilePath) {
7520
7567
  function scanForOrphanedAccountAdmins(users, accountsDir) {
7521
7568
  const known = new Set(users.map((u) => u.userId));
7522
7569
  const orphans = [];
7523
- if (!existsSync8(accountsDir)) return orphans;
7570
+ if (!existsSync9(accountsDir)) return orphans;
7524
7571
  let entries;
7525
7572
  try {
7526
7573
  entries = readdirSync5(accountsDir);
@@ -7536,9 +7583,9 @@ function scanForOrphanedAccountAdmins(users, accountsDir) {
7536
7583
  continue;
7537
7584
  }
7538
7585
  const accountJsonPath = join9(accountDir, "account.json");
7539
- if (!existsSync8(accountJsonPath)) continue;
7586
+ if (!existsSync9(accountJsonPath)) continue;
7540
7587
  try {
7541
- const config = JSON.parse(readFileSync9(accountJsonPath, "utf-8"));
7588
+ const config = JSON.parse(readFileSync10(accountJsonPath, "utf-8"));
7542
7589
  const admins = config.admins ?? [];
7543
7590
  for (const a of admins) {
7544
7591
  if (typeof a.userId === "string" && !known.has(a.userId)) orphans.push(a.userId);
@@ -7718,18 +7765,18 @@ app6.post("/", async (c) => {
7718
7765
  var session_default = app6;
7719
7766
 
7720
7767
  // server/routes/admin/logs.ts
7721
- import { existsSync as existsSync10, readdirSync as readdirSync6, readFileSync as readFileSync10, statSync as statSync5 } from "fs";
7768
+ import { existsSync as existsSync11, readdirSync as readdirSync6, readFileSync as readFileSync11, statSync as statSync5 } from "fs";
7722
7769
  import { resolve as resolve8, basename as basename2 } from "path";
7723
7770
 
7724
7771
  // app/lib/logs-read-resolve.ts
7725
- import { existsSync as existsSync9 } from "fs";
7772
+ import { existsSync as existsSync10 } from "fs";
7726
7773
  import { join as join10 } from "path";
7727
7774
  function resolveSessionLogPaths(filename, logDirs) {
7728
7775
  const tried = [filename];
7729
7776
  const hits = [];
7730
7777
  for (const dir of logDirs) {
7731
7778
  const fullPath = join10(dir, filename);
7732
- if (existsSync9(fullPath)) {
7779
+ if (existsSync10(fullPath)) {
7733
7780
  hits.push({ path: fullPath, dir });
7734
7781
  }
7735
7782
  }
@@ -7757,7 +7804,7 @@ app7.get("/", async (c) => {
7757
7804
  const filePath = resolve8(dir, safe);
7758
7805
  searched.push(filePath);
7759
7806
  try {
7760
- const buffer = readFileSync10(filePath);
7807
+ const buffer = readFileSync11(filePath);
7761
7808
  const onDiskBytes = statSync5(filePath).size;
7762
7809
  const headers = {
7763
7810
  "Content-Type": "text/plain; charset=utf-8",
@@ -7822,7 +7869,7 @@ app7.get("/", async (c) => {
7822
7869
  console.info(`[admin/logs] resolved cacheKey=${cacheKeySlice} sessionId=${sessionIdSlice} via=${primaryId === cacheKey ? "cacheKey" : primaryId === sessionKeyFromConv ? "reverse-lookup" : "sessionId-fallback"}`);
7823
7870
  try {
7824
7871
  const filename = basename2(hit.path);
7825
- const buffer = readFileSync10(hit.path);
7872
+ const buffer = readFileSync11(hit.path);
7826
7873
  const onDiskBytes = statSync5(hit.path).size;
7827
7874
  const headers = {
7828
7875
  "Content-Type": "text/plain; charset=utf-8",
@@ -7857,7 +7904,7 @@ app7.get("/", async (c) => {
7857
7904
  const seen = /* @__PURE__ */ new Set();
7858
7905
  const logs = {};
7859
7906
  for (const dir of logDirs) {
7860
- if (!existsSync10(dir)) continue;
7907
+ if (!existsSync11(dir)) continue;
7861
7908
  let files;
7862
7909
  try {
7863
7910
  files = readdirSync6(dir).filter((f) => f.endsWith(".log"));
@@ -7869,7 +7916,7 @@ app7.get("/", async (c) => {
7869
7916
  files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync5(resolve8(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).forEach(({ name }) => {
7870
7917
  seen.add(name);
7871
7918
  try {
7872
- const content = readFileSync10(resolve8(dir, name));
7919
+ const content = readFileSync11(resolve8(dir, name));
7873
7920
  const tail = content.length > TAIL_BYTES ? content.subarray(content.length - TAIL_BYTES).toString("utf-8") : content.toString("utf-8");
7874
7921
  logs[name] = tail.trim() || "(empty)";
7875
7922
  } catch (err) {
@@ -7908,7 +7955,7 @@ var claude_info_default = app8;
7908
7955
 
7909
7956
  // server/routes/admin/attachment.ts
7910
7957
  import { readFile as readFile2, readdir } from "fs/promises";
7911
- import { existsSync as existsSync11 } from "fs";
7958
+ import { existsSync as existsSync12 } from "fs";
7912
7959
  import { resolve as resolve9 } from "path";
7913
7960
  var app9 = new Hono();
7914
7961
  app9.get("/:attachmentId", requireAdminSession, async (c) => {
@@ -7922,11 +7969,11 @@ app9.get("/:attachmentId", requireAdminSession, async (c) => {
7922
7969
  return new Response("Not found", { status: 404 });
7923
7970
  }
7924
7971
  const dir = resolve9(ATTACHMENTS_ROOT, accountId, attachmentId);
7925
- if (!existsSync11(dir)) {
7972
+ if (!existsSync12(dir)) {
7926
7973
  return new Response("Not found", { status: 404 });
7927
7974
  }
7928
7975
  const metaPath = resolve9(dir, `${attachmentId}.meta.json`);
7929
- if (!existsSync11(metaPath)) {
7976
+ if (!existsSync12(metaPath)) {
7930
7977
  return new Response("Not found", { status: 404 });
7931
7978
  }
7932
7979
  let meta;
@@ -7954,13 +8001,13 @@ var attachment_default = app9;
7954
8001
 
7955
8002
  // server/routes/admin/agents.ts
7956
8003
  import { resolve as resolve10 } from "path";
7957
- import { readdirSync as readdirSync7, readFileSync as readFileSync11, existsSync as existsSync12, rmSync } from "fs";
8004
+ import { readdirSync as readdirSync7, readFileSync as readFileSync12, existsSync as existsSync13, rmSync } from "fs";
7958
8005
  var app10 = new Hono();
7959
8006
  app10.get("/", (c) => {
7960
8007
  const account = resolveAccount();
7961
8008
  if (!account) return c.json({ agents: [] });
7962
8009
  const agentsDir = resolve10(account.accountDir, "agents");
7963
- if (!existsSync12(agentsDir)) return c.json({ agents: [] });
8010
+ if (!existsSync13(agentsDir)) return c.json({ agents: [] });
7964
8011
  const agents = [];
7965
8012
  try {
7966
8013
  const entries = readdirSync7(agentsDir, { withFileTypes: true });
@@ -7968,9 +8015,9 @@ app10.get("/", (c) => {
7968
8015
  if (!entry.isDirectory()) continue;
7969
8016
  if (entry.name === "admin") continue;
7970
8017
  const configPath2 = resolve10(agentsDir, entry.name, "config.json");
7971
- if (!existsSync12(configPath2)) continue;
8018
+ if (!existsSync13(configPath2)) continue;
7972
8019
  try {
7973
- const config = JSON.parse(readFileSync11(configPath2, "utf-8"));
8020
+ const config = JSON.parse(readFileSync12(configPath2, "utf-8"));
7974
8021
  agents.push({
7975
8022
  slug: entry.name,
7976
8023
  displayName: config.displayName ?? entry.name,
@@ -7997,7 +8044,7 @@ app10.delete("/:slug", async (c) => {
7997
8044
  return c.json({ error: "Invalid agent slug" }, 400);
7998
8045
  }
7999
8046
  const agentDir = resolve10(account.accountDir, "agents", slug);
8000
- if (!existsSync12(agentDir)) {
8047
+ if (!existsSync13(agentDir)) {
8001
8048
  return c.json({ error: "Agent not found" }, 404);
8002
8049
  }
8003
8050
  try {
@@ -8027,7 +8074,7 @@ app10.post("/:slug/project", async (c) => {
8027
8074
  return c.json({ error: "Invalid agent slug" }, 400);
8028
8075
  }
8029
8076
  const agentDir = resolve10(account.accountDir, "agents", slug);
8030
- if (!existsSync12(agentDir)) {
8077
+ if (!existsSync13(agentDir)) {
8031
8078
  return c.json({ error: "Agent not found on disk" }, 404);
8032
8079
  }
8033
8080
  try {
@@ -8043,7 +8090,7 @@ var agents_default = app10;
8043
8090
  // server/routes/admin/sessions.ts
8044
8091
  import crypto2 from "crypto";
8045
8092
  import { resolve as resolvePath } from "path";
8046
- import { existsSync as existsSync13, mkdirSync as mkdirSync3, createWriteStream } from "fs";
8093
+ import { existsSync as existsSync14, mkdirSync as mkdirSync3, createWriteStream } from "fs";
8047
8094
 
8048
8095
  // ../lib/admin-conversation-purge/src/index.ts
8049
8096
  async function purgeAdminConversationJsonls(args) {
@@ -8203,7 +8250,7 @@ function validateAndShapeAttachments(raws, conversationAccountId, sessionId, mes
8203
8250
  let reason = null;
8204
8251
  if (!a.attachmentId || !a.filename || !a.mimeType || !a.storagePath) reason = "schema-fail";
8205
8252
  else if (a.accountId !== conversationAccountId) reason = "account-mismatch";
8206
- else if (!existsSync13(a.storagePath)) reason = "missing-file";
8253
+ else if (!existsSync14(a.storagePath)) reason = "missing-file";
8207
8254
  if (reason) {
8208
8255
  invalid++;
8209
8256
  try {
@@ -8730,7 +8777,7 @@ app11.put("/:id/label", requireAdminSession, async (c) => {
8730
8777
  var sessions_default = app11;
8731
8778
 
8732
8779
  // app/lib/claude-agent/spawn-context.ts
8733
- import { existsSync as existsSync14, readFileSync as readFileSync12, readdirSync as readdirSync8, statSync as statSync6 } from "fs";
8780
+ import { existsSync as existsSync15, readFileSync as readFileSync13, readdirSync as readdirSync8, statSync as statSync6 } from "fs";
8734
8781
  import { dirname as dirname2, resolve as resolve11, join as join11 } from "path";
8735
8782
  async function resolveOwnerProfileBlock(accountId, userId) {
8736
8783
  if (!userId) return { ok: false, reason: "missing-user-id" };
@@ -8765,9 +8812,9 @@ function extractPluginToolDescriptions(pluginDir) {
8765
8812
  ];
8766
8813
  let raw = null;
8767
8814
  for (const path2 of candidates) {
8768
- if (!existsSync14(path2)) continue;
8815
+ if (!existsSync15(path2)) continue;
8769
8816
  try {
8770
- raw = readFileSync12(path2, "utf-8");
8817
+ raw = readFileSync13(path2, "utf-8");
8771
8818
  break;
8772
8819
  } catch {
8773
8820
  }
@@ -8817,7 +8864,7 @@ function parseSkillPathsFromFrontmatter(fm) {
8817
8864
  function listPluginDirs() {
8818
8865
  const out = /* @__PURE__ */ new Map();
8819
8866
  const platformPlugins = resolve11(PLATFORM_ROOT, "plugins");
8820
- if (existsSync14(platformPlugins)) {
8867
+ if (existsSync15(platformPlugins)) {
8821
8868
  for (const name of readdirSync8(platformPlugins)) {
8822
8869
  const dir = join11(platformPlugins, name);
8823
8870
  try {
@@ -8827,10 +8874,10 @@ function listPluginDirs() {
8827
8874
  }
8828
8875
  }
8829
8876
  const premiumRoot = resolve11(dirname2(PLATFORM_ROOT), "premium-plugins");
8830
- if (existsSync14(premiumRoot)) {
8877
+ if (existsSync15(premiumRoot)) {
8831
8878
  for (const bundle of readdirSync8(premiumRoot)) {
8832
8879
  const bundlePlugins = join11(premiumRoot, bundle, "plugins");
8833
- if (!existsSync14(bundlePlugins)) continue;
8880
+ if (!existsSync15(bundlePlugins)) continue;
8834
8881
  try {
8835
8882
  for (const name of readdirSync8(bundlePlugins)) {
8836
8883
  const dir = join11(bundlePlugins, name);
@@ -8847,9 +8894,9 @@ function listPluginDirs() {
8847
8894
  }
8848
8895
  function readEnabledPlugins(accountId) {
8849
8896
  const accountFile = resolve11(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
8850
- if (!existsSync14(accountFile)) return /* @__PURE__ */ new Set();
8897
+ if (!existsSync15(accountFile)) return /* @__PURE__ */ new Set();
8851
8898
  try {
8852
- const parsed = JSON.parse(readFileSync12(accountFile, "utf-8"));
8899
+ const parsed = JSON.parse(readFileSync13(accountFile, "utf-8"));
8853
8900
  return new Set(
8854
8901
  Array.isArray(parsed.enabledPlugins) ? parsed.enabledPlugins.filter((p) => typeof p === "string") : []
8855
8902
  );
@@ -8858,10 +8905,10 @@ function readEnabledPlugins(accountId) {
8858
8905
  }
8859
8906
  }
8860
8907
  function readFrontmatter(path2) {
8861
- if (!existsSync14(path2)) return null;
8908
+ if (!existsSync15(path2)) return null;
8862
8909
  let raw;
8863
8910
  try {
8864
- raw = readFileSync12(path2, "utf-8");
8911
+ raw = readFileSync13(path2, "utf-8");
8865
8912
  } catch {
8866
8913
  return null;
8867
8914
  }
@@ -8893,7 +8940,7 @@ ${fm}
8893
8940
  `plugin-manifest-tool-coverage plugin=${name} tools=${tools.length} with-desc=${withDesc}`
8894
8941
  );
8895
8942
  if (toolNames.length > 0 && descMap.size === 0) {
8896
- const hasSource = existsSync14(join11(dir, "mcp/dist/index.js")) || existsSync14(join11(dir, "mcp/src/index.ts"));
8943
+ const hasSource = existsSync15(join11(dir, "mcp/dist/index.js")) || existsSync15(join11(dir, "mcp/src/index.ts"));
8897
8944
  if (hasSource) {
8898
8945
  console.warn(
8899
8946
  `[spawn-context] WARN plugin=${name} mcp source present but tool-description regex matched zero entries \u2014 SDK upgrade may have changed the registration shape`
@@ -8920,7 +8967,7 @@ ${skillFm}
8920
8967
  }
8921
8968
  function computeSpecialistDomains(accountId) {
8922
8969
  const specialistsDir = resolve11(PLATFORM_ROOT, "..", "data/accounts", accountId, "specialists", "agents");
8923
- if (!existsSync14(specialistsDir)) return [];
8970
+ if (!existsSync15(specialistsDir)) return [];
8924
8971
  let entries;
8925
8972
  try {
8926
8973
  entries = readdirSync8(specialistsDir);
@@ -8966,10 +9013,10 @@ ${fm}
8966
9013
  }
8967
9014
  function computeDormantPlugins(accountId) {
8968
9015
  const accountFile = resolve11(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
8969
- if (!existsSync14(accountFile)) return [];
9016
+ if (!existsSync15(accountFile)) return [];
8970
9017
  let enabled;
8971
9018
  try {
8972
- const raw = readFileSync12(accountFile, "utf-8");
9019
+ const raw = readFileSync13(accountFile, "utf-8");
8973
9020
  const parsed = JSON.parse(raw);
8974
9021
  enabled = new Set(
8975
9022
  Array.isArray(parsed.enabledPlugins) ? parsed.enabledPlugins.filter((p) => typeof p === "string") : []
@@ -8981,7 +9028,7 @@ function computeDormantPlugins(accountId) {
8981
9028
  return [];
8982
9029
  }
8983
9030
  const pluginsDir = resolve11(PLATFORM_ROOT, "plugins");
8984
- if (!existsSync14(pluginsDir)) return [];
9031
+ if (!existsSync15(pluginsDir)) return [];
8985
9032
  let entries;
8986
9033
  try {
8987
9034
  entries = readdirSync8(pluginsDir);
@@ -8992,10 +9039,10 @@ function computeDormantPlugins(accountId) {
8992
9039
  for (const name of entries) {
8993
9040
  if (enabled.has(name)) continue;
8994
9041
  const manifestPath = resolve11(pluginsDir, name, "PLUGIN.md");
8995
- if (!existsSync14(manifestPath)) continue;
9042
+ if (!existsSync15(manifestPath)) continue;
8996
9043
  let manifest;
8997
9044
  try {
8998
- manifest = readFileSync12(manifestPath, "utf-8");
9045
+ manifest = readFileSync13(manifestPath, "utf-8");
8999
9046
  } catch {
9000
9047
  continue;
9001
9048
  }
@@ -9009,13 +9056,13 @@ function computeDormantPlugins(accountId) {
9009
9056
  }
9010
9057
 
9011
9058
  // server/routes/admin/claude-sessions.ts
9012
- import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
9059
+ import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
9013
9060
  import { resolve as resolve12 } from "path";
9014
9061
  function readTunnelState(brandConfigDir) {
9015
9062
  const statePath = `${process.env.HOME ?? ""}/${brandConfigDir}/cloudflared/tunnel.state`;
9016
- if (!existsSync15(statePath)) return null;
9063
+ if (!existsSync16(statePath)) return null;
9017
9064
  try {
9018
- const parsed = JSON.parse(readFileSync13(statePath, "utf-8"));
9065
+ const parsed = JSON.parse(readFileSync14(statePath, "utf-8"));
9019
9066
  const tunnelId = typeof parsed.tunnelId === "string" ? parsed.tunnelId : null;
9020
9067
  const tunnelName = typeof parsed.tunnelName === "string" ? parsed.tunnelName : null;
9021
9068
  const domain = typeof parsed.domain === "string" ? parsed.domain : null;
@@ -9029,7 +9076,7 @@ function resolveTunnelUrl() {
9029
9076
  const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? process.env.PLATFORM_ROOT ?? resolve12(process.cwd(), "..");
9030
9077
  let brand;
9031
9078
  try {
9032
- brand = JSON.parse(readFileSync13(resolve12(platformRoot2, "config", "brand.json"), "utf-8"));
9079
+ brand = JSON.parse(readFileSync14(resolve12(platformRoot2, "config", "brand.json"), "utf-8"));
9033
9080
  } catch {
9034
9081
  return null;
9035
9082
  }
@@ -12454,7 +12501,7 @@ var graph_default_view_default = app21;
12454
12501
  // server/routes/admin/sidebar-artefacts.ts
12455
12502
  import { readdir as readdir4, stat as stat5 } from "fs/promises";
12456
12503
  import { resolve as resolve16, relative as relative3, isAbsolute, sep as sep5, basename as basename5 } from "path";
12457
- import { existsSync as existsSync16 } from "fs";
12504
+ import { existsSync as existsSync17 } from "fs";
12458
12505
  var LIMIT = 50;
12459
12506
  var ADMIN_AGENT_FILES = ["IDENTITY.md", "SOUL.md", "KNOWLEDGE.md"];
12460
12507
  function toDataRootRelPath(absPath) {
@@ -12469,27 +12516,27 @@ app22.get("/", requireAdminSession, async (c) => {
12469
12516
  return c.json({ error: "Account not found for session" }, 401);
12470
12517
  }
12471
12518
  const start = Date.now();
12472
- const outputs = await fetchOutputArtefacts(accountId);
12473
- if (outputs === null) {
12519
+ const accountFiles = await fetchAccountFileArtefacts(accountId);
12520
+ if (accountFiles === null) {
12474
12521
  return c.json({ error: "Failed to load artefacts" }, 500);
12475
12522
  }
12476
12523
  const accountDir = resolve16(ACCOUNTS_DIR, accountId);
12477
12524
  const agents = await fetchAgentTemplateRows(accountDir);
12478
- const artefacts = [...outputs, ...agents].sort(
12525
+ const artefacts = [...accountFiles, ...agents].sort(
12479
12526
  (a, b) => (b.updatedAt ?? "").localeCompare(a.updatedAt ?? "")
12480
12527
  ).slice(0, LIMIT);
12481
12528
  const ms = Date.now() - start;
12482
12529
  console.log(
12483
- `[admin/sidebar-artefacts] account=${accountId} count=${artefacts.length} outputs=${outputs.length} agents=${agents.length} ms=${ms}`
12530
+ `[admin/sidebar-artefacts] account=${accountId} count=${artefacts.length} accountFiles=${accountFiles.length} agents=${agents.length} ms=${ms}`
12484
12531
  );
12485
12532
  return c.json({ artefacts });
12486
12533
  });
12487
- async function fetchOutputArtefacts(accountId) {
12534
+ async function fetchAccountFileArtefacts(accountId) {
12488
12535
  const session = getSession();
12489
12536
  try {
12490
12537
  const result = await session.run(
12491
12538
  `MATCH (f:FileArtifact { accountId: $accountId })
12492
- WHERE f.relativePath CONTAINS '/output/'
12539
+ WHERE f.relativePath STARTS WITH 'accounts/'
12493
12540
  RETURN f.relativePath AS relativePath,
12494
12541
  f.displayName AS displayName,
12495
12542
  f.mimeType AS mimeType,
@@ -12504,9 +12551,9 @@ async function fetchOutputArtefacts(accountId) {
12504
12551
  const mimeType = r.get("mimeType") ?? "";
12505
12552
  const modifiedAt = r.get("modifiedAt") ?? "";
12506
12553
  return {
12507
- id: `output-file:${relativePath}`,
12554
+ id: `account-file:${relativePath}`,
12508
12555
  name: displayName ?? basename5(relativePath),
12509
- kind: "output-file",
12556
+ kind: "account-file",
12510
12557
  updatedAt: modifiedAt,
12511
12558
  mimeType,
12512
12559
  downloadPath: relativePath,
@@ -12560,7 +12607,7 @@ async function fetchAgentTemplateRows(accountDir) {
12560
12607
  async function unionSpecialistFilenames(overrideDir, bundledDir) {
12561
12608
  const names = /* @__PURE__ */ new Set();
12562
12609
  for (const dir of [overrideDir, bundledDir]) {
12563
- if (!existsSync16(dir)) continue;
12610
+ if (!existsSync17(dir)) continue;
12564
12611
  try {
12565
12612
  const entries = await readdir4(dir);
12566
12613
  for (const entry of entries) {
@@ -12575,7 +12622,7 @@ async function unionSpecialistFilenames(overrideDir, bundledDir) {
12575
12622
  }
12576
12623
  async function readAgentTemplateRow(inp) {
12577
12624
  let chosenPath = null;
12578
- if (existsSync16(inp.overridePath)) {
12625
+ if (existsSync17(inp.overridePath)) {
12579
12626
  try {
12580
12627
  validateFilePathInAccount(inp.overridePath, inp.overrideRoot);
12581
12628
  chosenPath = inp.overridePath;
@@ -12586,7 +12633,7 @@ async function readAgentTemplateRow(inp) {
12586
12633
  );
12587
12634
  return null;
12588
12635
  }
12589
- } else if (existsSync16(inp.bundledPath)) {
12636
+ } else if (existsSync17(inp.bundledPath)) {
12590
12637
  if (!isWithin(inp.bundledPath, inp.bundledRoot)) {
12591
12638
  console.error(
12592
12639
  `[admin/sidebar-artefacts] agent-template-read-failed agent=${inp.displayName} kind=${inp.logName} error="bundled path outside PLATFORM_ROOT"`
@@ -12628,7 +12675,7 @@ function isWithin(target, root) {
12628
12675
  var sidebar_artefacts_default = app22;
12629
12676
 
12630
12677
  // server/routes/admin/sidebar-sessions.ts
12631
- import { readdirSync as readdirSync9, readFileSync as readFileSync14, statSync as statSync7 } from "fs";
12678
+ import { readdirSync as readdirSync9, readFileSync as readFileSync15, statSync as statSync7 } from "fs";
12632
12679
  import { join as join14, resolve as resolve17 } from "path";
12633
12680
  var SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.jsonl$/i;
12634
12681
  var PID_FILE_RE = /^([0-9]+)\.json$/;
@@ -12698,7 +12745,7 @@ function sessionIdToPidMap(configDir2) {
12698
12745
  if (!Number.isFinite(pid) || pid <= 0) continue;
12699
12746
  let body;
12700
12747
  try {
12701
- body = readFileSync14(join14(sessionsDir, entry.name), "utf8");
12748
+ body = readFileSync15(join14(sessionsDir, entry.name), "utf8");
12702
12749
  } catch {
12703
12750
  continue;
12704
12751
  }
@@ -12720,7 +12767,7 @@ function loadUserTitles(accountDir) {
12720
12767
  const path2 = join14(accountDir, "session-titles.json");
12721
12768
  let raw;
12722
12769
  try {
12723
- raw = readFileSync14(path2, "utf8");
12770
+ raw = readFileSync15(path2, "utf8");
12724
12771
  } catch {
12725
12772
  return out;
12726
12773
  }
@@ -12745,7 +12792,7 @@ function loadUserTitles(accountDir) {
12745
12792
  function readSidecarMeta(metaPath) {
12746
12793
  let raw;
12747
12794
  try {
12748
- raw = readFileSync14(metaPath, "utf8");
12795
+ raw = readFileSync15(metaPath, "utf8");
12749
12796
  } catch {
12750
12797
  return { bridgeIds: [], role: null, channel: null };
12751
12798
  }
@@ -12835,7 +12882,7 @@ app23.get("/", requireAdminSession, async (c) => {
12835
12882
  let body;
12836
12883
  let mtimeMs;
12837
12884
  try {
12838
- body = readFileSync14(path2, "utf8");
12885
+ body = readFileSync15(path2, "utf8");
12839
12886
  mtimeMs = statSync7(path2).mtimeMs;
12840
12887
  } catch {
12841
12888
  continue;
@@ -12872,7 +12919,7 @@ app23.get("/", requireAdminSession, async (c) => {
12872
12919
  var sidebar_sessions_default = app23;
12873
12920
 
12874
12921
  // server/routes/admin/session-delete.ts
12875
- import { existsSync as existsSync17, readdirSync as readdirSync10, readFileSync as readFileSync15, unlinkSync as unlinkSync2 } from "fs";
12922
+ import { existsSync as existsSync18, readdirSync as readdirSync10, readFileSync as readFileSync16, unlinkSync as unlinkSync2 } from "fs";
12876
12923
  import { join as join15, resolve as resolve18 } from "path";
12877
12924
  var app24 = new Hono();
12878
12925
  var SESSION_ID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -12896,7 +12943,7 @@ function findPidForSession(configDir2, sessionId) {
12896
12943
  if (!Number.isFinite(pid) || pid <= 0) continue;
12897
12944
  let body;
12898
12945
  try {
12899
- body = readFileSync15(join15(sessionsDir, entry.name), "utf8");
12946
+ body = readFileSync16(join15(sessionsDir, entry.name), "utf8");
12900
12947
  } catch {
12901
12948
  continue;
12902
12949
  }
@@ -13019,7 +13066,7 @@ app24.post("/", requireAdminSession, async (c) => {
13019
13066
  }
13020
13067
  let deleted = false;
13021
13068
  try {
13022
- if (existsSync17(jsonlPath)) {
13069
+ if (existsSync18(jsonlPath)) {
13023
13070
  unlinkSync2(jsonlPath);
13024
13071
  deleted = true;
13025
13072
  }
@@ -13032,17 +13079,17 @@ app24.post("/", requireAdminSession, async (c) => {
13032
13079
  return c.json({ error: `unlink failed: ${msg}` }, 500);
13033
13080
  }
13034
13081
  try {
13035
- if (existsSync17(sidecarPath)) unlinkSync2(sidecarPath);
13082
+ if (existsSync18(sidecarPath)) unlinkSync2(sidecarPath);
13036
13083
  } catch {
13037
13084
  }
13038
13085
  if (pid !== null) {
13039
13086
  try {
13040
13087
  const pidFile = join15(configDir2, "sessions", `${pid}.json`);
13041
- if (existsSync17(pidFile)) unlinkSync2(pidFile);
13088
+ if (existsSync18(pidFile)) unlinkSync2(pidFile);
13042
13089
  } catch {
13043
13090
  }
13044
13091
  }
13045
- if (existsSync17(jsonlPath)) {
13092
+ if (existsSync18(jsonlPath)) {
13046
13093
  console.error(`[sessions-delete] resurrection jsonlPath=${jsonlPath} sessionId=${sessionId}`);
13047
13094
  return c.json(
13048
13095
  { error: "jsonl-resurrected", jsonlPath, pidKilled, waitForExitMs, escalatedToSIGKILL },
@@ -13221,14 +13268,14 @@ app26.get("/", async (c) => {
13221
13268
  var system_stats_default = app26;
13222
13269
 
13223
13270
  // server/routes/admin/health.ts
13224
- import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
13271
+ import { existsSync as existsSync19, readFileSync as readFileSync17 } from "fs";
13225
13272
  import { resolve as resolve19, join as join16 } from "path";
13226
13273
  var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT ?? resolve19(process.cwd(), "..");
13227
13274
  var brandHostname = "maxy";
13228
13275
  var brandJsonPath = join16(PLATFORM_ROOT6, "config", "brand.json");
13229
- if (existsSync18(brandJsonPath)) {
13276
+ if (existsSync19(brandJsonPath)) {
13230
13277
  try {
13231
- const brand = JSON.parse(readFileSync16(brandJsonPath, "utf-8"));
13278
+ const brand = JSON.parse(readFileSync17(brandJsonPath, "utf-8"));
13232
13279
  if (brand.hostname) brandHostname = brand.hostname;
13233
13280
  } catch {
13234
13281
  }
@@ -13237,8 +13284,8 @@ var VERSION_FILE = resolve19(PLATFORM_ROOT6, `config/.${brandHostname}-version`)
13237
13284
  var PROCESS_STARTED_AT = (/* @__PURE__ */ new Date()).toISOString();
13238
13285
  var PROBE_TIMEOUT_MS = 1e3;
13239
13286
  function readVersion() {
13240
- if (!existsSync18(VERSION_FILE)) return "unknown";
13241
- return readFileSync16(VERSION_FILE, "utf-8").trim() || "unknown";
13287
+ if (!existsSync19(VERSION_FILE)) return "unknown";
13288
+ return readFileSync17(VERSION_FILE, "utf-8").trim() || "unknown";
13242
13289
  }
13243
13290
  async function probeConversationDb() {
13244
13291
  let session;
@@ -13778,7 +13825,7 @@ var admin_default = app34;
13778
13825
 
13779
13826
  // app/lib/access-gate.ts
13780
13827
  import neo4j4 from "neo4j-driver";
13781
- import { readFileSync as readFileSync17 } from "fs";
13828
+ import { readFileSync as readFileSync18 } from "fs";
13782
13829
  import { resolve as resolve20 } from "path";
13783
13830
  import { randomUUID as randomUUID10 } from "crypto";
13784
13831
  var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT ?? resolve20(process.cwd(), "..");
@@ -13787,7 +13834,7 @@ function readPassword() {
13787
13834
  if (process.env.NEO4J_PASSWORD) return process.env.NEO4J_PASSWORD;
13788
13835
  const passwordFile = resolve20(PLATFORM_ROOT7, "config/.neo4j-password");
13789
13836
  try {
13790
- return readFileSync17(passwordFile, "utf-8").trim();
13837
+ return readFileSync18(passwordFile, "utf-8").trim();
13791
13838
  } catch {
13792
13839
  throw new Error(
13793
13840
  `Neo4j password not found. Expected at ${passwordFile} or in NEO4J_PASSWORD env var.`
@@ -14219,7 +14266,7 @@ app37.route("/request-magic-link", request_magic_link_default);
14219
14266
  var access_default = app37;
14220
14267
 
14221
14268
  // server/routes/sites.ts
14222
- import { existsSync as existsSync19, readFileSync as readFileSync18, realpathSync as realpathSync5, statSync as statSync8 } from "fs";
14269
+ import { existsSync as existsSync20, readFileSync as readFileSync19, realpathSync as realpathSync5, statSync as statSync8 } from "fs";
14223
14270
  import { resolve as resolve22 } from "path";
14224
14271
  var SAFE_SEG_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
14225
14272
  var MIME = {
@@ -14285,7 +14332,7 @@ app38.get("/:rel{.*}", (c) => {
14285
14332
  }
14286
14333
  let stat7;
14287
14334
  try {
14288
- stat7 = existsSync19(filePath) ? statSync8(filePath) : null;
14335
+ stat7 = existsSync20(filePath) ? statSync8(filePath) : null;
14289
14336
  } catch {
14290
14337
  stat7 = null;
14291
14338
  }
@@ -14304,7 +14351,7 @@ app38.get("/:rel{.*}", (c) => {
14304
14351
  console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
14305
14352
  return c.text("Forbidden", 403);
14306
14353
  }
14307
- if (!existsSync19(filePath)) {
14354
+ if (!existsSync20(filePath)) {
14308
14355
  console.error(`[sites] not-found path=${reqPath} status=404`);
14309
14356
  return c.text("Not found", 404);
14310
14357
  }
@@ -14323,7 +14370,7 @@ app38.get("/:rel{.*}", (c) => {
14323
14370
  }
14324
14371
  let body;
14325
14372
  try {
14326
- body = readFileSync18(realPath);
14373
+ body = readFileSync19(realPath);
14327
14374
  } catch (err) {
14328
14375
  const code = err?.code;
14329
14376
  if (code === "EISDIR") {
@@ -14359,7 +14406,7 @@ var sites_default = app38;
14359
14406
 
14360
14407
  // app/lib/visitor-token.ts
14361
14408
  import { createHmac, randomBytes, timingSafeEqual } from "crypto";
14362
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync19, writeFileSync as writeFileSync6 } from "fs";
14409
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync20, writeFileSync as writeFileSync7 } from "fs";
14363
14410
  import { dirname as dirname4 } from "path";
14364
14411
  var TOKEN_PREFIX = "v1.";
14365
14412
  var SECRET_BYTES = 32;
@@ -14368,7 +14415,7 @@ var cachedSecret = null;
14368
14415
  function getSecret() {
14369
14416
  if (cachedSecret) return cachedSecret;
14370
14417
  try {
14371
- const hex2 = readFileSync19(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
14418
+ const hex2 = readFileSync20(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
14372
14419
  if (hex2.length === SECRET_BYTES * 2) {
14373
14420
  cachedSecret = Buffer.from(hex2, "hex");
14374
14421
  return cachedSecret;
@@ -14378,11 +14425,11 @@ function getSecret() {
14378
14425
  const fresh = randomBytes(SECRET_BYTES).toString("hex");
14379
14426
  try {
14380
14427
  mkdirSync4(dirname4(VISITOR_TOKEN_SECRET_FILE), { recursive: true, mode: 448 });
14381
- writeFileSync6(VISITOR_TOKEN_SECRET_FILE, fresh, { mode: 384, flag: "wx" });
14428
+ writeFileSync7(VISITOR_TOKEN_SECRET_FILE, fresh, { mode: 384, flag: "wx" });
14382
14429
  console.log(`[visitor-token] secret minted path=${VISITOR_TOKEN_SECRET_FILE}`);
14383
14430
  } catch {
14384
14431
  }
14385
- const hex = readFileSync19(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
14432
+ const hex = readFileSync20(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
14386
14433
  cachedSecret = Buffer.from(hex, "hex");
14387
14434
  return cachedSecret;
14388
14435
  }
@@ -14435,7 +14482,7 @@ var VISITOR_COOKIE_NAME = "mxy_v";
14435
14482
  var VISITOR_COOKIE_MAX_AGE_SECONDS = Math.floor(DEFAULT_TTL_MS / 1e3);
14436
14483
 
14437
14484
  // app/lib/brand-config.ts
14438
- import { existsSync as existsSync20, readFileSync as readFileSync20 } from "fs";
14485
+ import { existsSync as existsSync21, readFileSync as readFileSync21 } from "fs";
14439
14486
  import { join as join17 } from "path";
14440
14487
  var cached2 = null;
14441
14488
  var cachedAttempted = false;
@@ -14445,9 +14492,9 @@ function readBrandConfig() {
14445
14492
  const platformRoot2 = process.env.MAXY_PLATFORM_ROOT;
14446
14493
  if (!platformRoot2) return null;
14447
14494
  const brandPath = join17(platformRoot2, "config", "brand.json");
14448
- if (!existsSync20(brandPath)) return null;
14495
+ if (!existsSync21(brandPath)) return null;
14449
14496
  try {
14450
- cached2 = JSON.parse(readFileSync20(brandPath, "utf-8"));
14497
+ cached2 = JSON.parse(readFileSync21(brandPath, "utf-8"));
14451
14498
  return cached2;
14452
14499
  } catch {
14453
14500
  return null;
@@ -14936,13 +14983,13 @@ var visitor_event_default = app41;
14936
14983
 
14937
14984
  // server/routes/session.ts
14938
14985
  import { resolve as resolve23 } from "path";
14939
- import { existsSync as existsSync21, writeFileSync as writeFileSync7, mkdirSync as mkdirSync5 } from "fs";
14986
+ import { existsSync as existsSync22, writeFileSync as writeFileSync8, mkdirSync as mkdirSync5 } from "fs";
14940
14987
  var UUID_RE4 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
14941
14988
  function writeBrandingCache(accountId, agentSlug, branding) {
14942
14989
  try {
14943
14990
  const cacheDir = resolve23(MAXY_DIR, "branding-cache", accountId);
14944
14991
  mkdirSync5(cacheDir, { recursive: true });
14945
- writeFileSync7(resolve23(cacheDir, `${agentSlug}.json`), JSON.stringify(branding), "utf-8");
14992
+ writeFileSync8(resolve23(cacheDir, `${agentSlug}.json`), JSON.stringify(branding), "utf-8");
14946
14993
  } catch (err) {
14947
14994
  console.error(`[branding] cache write failed: ${err instanceof Error ? err.message : String(err)}`);
14948
14995
  }
@@ -15031,7 +15078,7 @@ app42.post("/", async (c) => {
15031
15078
  let agentConfig = null;
15032
15079
  if (account) {
15033
15080
  const agentDir = resolve23(account.accountDir, "agents", agentSlug);
15034
- if (!existsSync21(agentDir) || !existsSync21(resolve23(agentDir, "config.json"))) {
15081
+ if (!existsSync22(agentDir) || !existsSync22(resolve23(agentDir, "config.json"))) {
15035
15082
  return c.json({ error: "Agent not found" }, 404);
15036
15083
  }
15037
15084
  agentConfig = resolveAgentConfig(account.accountDir, agentSlug);
@@ -15423,44 +15470,115 @@ var InboundHub = class {
15423
15470
  state(senderId) {
15424
15471
  let s = this.senders.get(senderId);
15425
15472
  if (!s) {
15426
- s = { subscriber: null, queue: [] };
15473
+ s = { subscriber: null, ready: false, inFlight: [] };
15427
15474
  this.senders.set(senderId, s);
15428
15475
  }
15429
15476
  return s;
15430
15477
  }
15431
- /** Route an inbound message to its sender. Delivers immediately if that
15432
- * sender's channel server is attached; otherwise queues it (cold start). */
15433
- deliver(payload) {
15478
+ /** Route an inbound message. Delivers immediately only if the sender's channel
15479
+ * server is attached AND has signalled readiness; otherwise queues it (cold
15480
+ * start, or the attach->ready window) so it is never written before the
15481
+ * channel server can consume it. Every accepted message is tracked in-flight
15482
+ * until it reaches a terminal state. */
15483
+ deliver(payload, now) {
15434
15484
  const s = this.state(payload.senderId);
15435
- if (s.subscriber) {
15485
+ if (s.subscriber && s.ready) {
15436
15486
  s.subscriber(payload);
15487
+ s.inFlight.push({ payload, state: "drained", enqueuedAt: now, drainedAt: now });
15437
15488
  } else {
15438
- s.queue.push(payload);
15489
+ s.inFlight.push({ payload, state: "queued", enqueuedAt: now, drainedAt: null });
15439
15490
  }
15440
15491
  }
15441
- /** A sender's channel server connected. Drain any queued messages to it in
15442
- * arrival order, then route live. Replaces any prior subscriber. */
15492
+ /** A sender's channel server connected. Register the subscriber but do NOT
15493
+ * drain yet the drain waits for markReady so nothing is written before the
15494
+ * channel server is consuming. Replaces any prior subscriber. */
15443
15495
  attach(senderId, subscriber) {
15444
15496
  const s = this.state(senderId);
15445
15497
  s.subscriber = subscriber;
15446
- if (s.queue.length > 0) {
15447
- const pending = s.queue;
15448
- s.queue = [];
15449
- for (const payload of pending) subscriber(payload);
15498
+ s.ready = false;
15499
+ }
15500
+ /** The sender's channel server reported it is connected and consuming. Drain
15501
+ * every queued message to it in arrival order, marking each drained-awaiting-
15502
+ * ack, then route live. Returns the number drained. */
15503
+ markReady(senderId, now) {
15504
+ const s = this.state(senderId);
15505
+ s.ready = true;
15506
+ if (!s.subscriber) return 0;
15507
+ let count = 0;
15508
+ for (const e of s.inFlight) {
15509
+ if (e.state === "queued") {
15510
+ s.subscriber(e.payload);
15511
+ e.state = "drained";
15512
+ e.drainedAt = now;
15513
+ count++;
15514
+ }
15450
15515
  }
15516
+ return count;
15517
+ }
15518
+ /** The channel server confirmed it received (emitted) a drained message. The
15519
+ * message has reached the agent; drop it from in-flight. Returns whether a
15520
+ * matching in-flight message was found. */
15521
+ ack(senderId, waMessageId) {
15522
+ const s = this.senders.get(senderId);
15523
+ if (!s) return false;
15524
+ const i = s.inFlight.findIndex((e) => e.payload.waMessageId === waMessageId);
15525
+ if (i < 0) return false;
15526
+ s.inFlight.splice(i, 1);
15527
+ return true;
15451
15528
  }
15452
15529
  /** A sender's channel server disconnected (session ended/restarting).
15453
- * Subsequent messages queue again until re-attach, so nothing is lost
15454
- * across a restart. */
15530
+ * Subsequent messages queue again until re-attach + ready, so nothing is lost
15531
+ * across a restart. In-flight drained messages stay tracked for ack/sweep. */
15455
15532
  detach(senderId) {
15456
15533
  const s = this.senders.get(senderId);
15457
- if (s) s.subscriber = null;
15534
+ if (s) {
15535
+ s.subscriber = null;
15536
+ s.ready = false;
15537
+ }
15538
+ }
15539
+ /** Reconciliation from queue state alone — does not wait for a future inbound
15540
+ * to expose a loss. Returns (and removes) a terminal event for every message
15541
+ * that never reached the agent:
15542
+ * - a still-queued message older than queuedThresholdMs WHILE a subscriber
15543
+ * is attached -> inbound-undelivered (the readiness handoff broke);
15544
+ * - a still-queued message older than queuedThresholdMs with NO subscriber
15545
+ * attached -> spawn-undelivered (the channel server never attached: spawn
15546
+ * failed, or the session ended and never resumed);
15547
+ * - a drained message unacked past ackThresholdMs -> drain-undelivered (the
15548
+ * channel server never confirmed receipt — the silent cold-start drop).
15549
+ * Each message is emitted at most once (removed when emitted). */
15550
+ sweep(now, t) {
15551
+ const events = [];
15552
+ for (const [senderId, s] of this.senders) {
15553
+ s.inFlight = s.inFlight.filter((e) => {
15554
+ if (e.state === "queued" && s.subscriber && now - e.enqueuedAt > t.queuedThresholdMs) {
15555
+ events.push({ kind: "inbound-undelivered", senderId, waMessageId: e.payload.waMessageId, ageMs: now - e.enqueuedAt });
15556
+ return false;
15557
+ }
15558
+ if (e.state === "queued" && !s.subscriber && now - e.enqueuedAt > t.queuedThresholdMs) {
15559
+ events.push({ kind: "spawn-undelivered", senderId, waMessageId: e.payload.waMessageId, ageMs: now - e.enqueuedAt });
15560
+ return false;
15561
+ }
15562
+ if (e.state === "drained" && e.drainedAt != null && now - e.drainedAt > t.ackThresholdMs) {
15563
+ events.push({ kind: "drain-undelivered", senderId, waMessageId: e.payload.waMessageId, ageMs: now - e.drainedAt });
15564
+ return false;
15565
+ }
15566
+ return true;
15567
+ });
15568
+ }
15569
+ return events;
15458
15570
  }
15459
- /** Whether a sender currently has a live channel server. The gateway uses
15460
- * this to decide whether an inbound needs a cold-start spawn/resume. */
15571
+ /** Whether a sender currently has a live channel server attached. The gateway
15572
+ * uses this to decide whether an inbound needs a cold-start spawn/resume. */
15461
15573
  hasSubscriber(senderId) {
15462
15574
  return this.senders.get(senderId)?.subscriber != null;
15463
15575
  }
15576
+ /** Whether a sender's channel server is attached AND has signalled readiness.
15577
+ * Used only to log delivery=immediate vs queued accurately. */
15578
+ isReady(senderId) {
15579
+ const s = this.senders.get(senderId);
15580
+ return s?.subscriber != null && s.ready;
15581
+ }
15464
15582
  };
15465
15583
 
15466
15584
  // node_modules/hono/dist/utils/stream.js
@@ -15661,10 +15779,23 @@ function createWaChannelRoutes(deps) {
15661
15779
  deps.onReady?.(senderId);
15662
15780
  return c.json({ ok: true });
15663
15781
  });
15782
+ app44.post("/wa-channel/received", async (c) => {
15783
+ const body = await c.req.json().catch(() => null);
15784
+ const senderId = body?.senderId;
15785
+ const waMessageId = body?.waMessageId;
15786
+ if (typeof senderId !== "string" || typeof waMessageId !== "string") {
15787
+ return c.json({ error: "senderId and waMessageId required" }, 400);
15788
+ }
15789
+ deps.onReceived?.(senderId, waMessageId);
15790
+ return c.json({ ok: true });
15791
+ });
15664
15792
  return app44;
15665
15793
  }
15666
15794
 
15667
15795
  // app/lib/whatsapp/gateway/wa-gateway.ts
15796
+ var QUEUED_THRESHOLD_MS = 3e4;
15797
+ var ACK_THRESHOLD_MS = 3e4;
15798
+ var SWEEP_INTERVAL_MS = 15e3;
15668
15799
  var WaGateway = class {
15669
15800
  constructor(deps) {
15670
15801
  this.deps = deps;
@@ -15677,17 +15808,55 @@ var WaGateway = class {
15677
15808
  routes() {
15678
15809
  return createWaChannelRoutes({
15679
15810
  hub: this.hub,
15680
- sendOutbound: (senderId, text) => this.sendOutbound(senderId, text)
15811
+ sendOutbound: (senderId, text) => this.sendOutbound(senderId, text),
15812
+ onReady: (senderId) => {
15813
+ const count = this.hub.markReady(senderId, Date.now());
15814
+ if (count > 0) console.error(`[whatsapp-native] op=drained senderId=${senderId} count=${count}`);
15815
+ },
15816
+ onReceived: (senderId, waMessageId) => {
15817
+ const found = this.hub.ack(senderId, waMessageId);
15818
+ console.error(
15819
+ `[whatsapp-native] op=channel-received senderId=${senderId} waMessageId=${waMessageId}${found ? "" : " note=unknown"}`
15820
+ );
15821
+ }
15681
15822
  });
15682
15823
  }
15824
+ /** Run one reconciliation pass: log every terminal delivery event the sweep
15825
+ * surfaces — a queued message that never drained while a subscriber was
15826
+ * attached (inbound-undelivered), a queued message whose channel server never
15827
+ * attached (spawn-undelivered), or a drained message the channel server never
15828
+ * confirmed (drain-undelivered). Returns the events (for tests). Driven by
15829
+ * startSweeper() in production; called directly with an injected clock in
15830
+ * tests. */
15831
+ reconcile(now) {
15832
+ const events = this.hub.sweep(now, {
15833
+ queuedThresholdMs: QUEUED_THRESHOLD_MS,
15834
+ ackThresholdMs: ACK_THRESHOLD_MS
15835
+ });
15836
+ for (const e of events) {
15837
+ console.error(
15838
+ `[whatsapp-native] op=${e.kind} senderId=${e.senderId} waMessageId=${e.waMessageId} ageMs=${e.ageMs}`
15839
+ );
15840
+ }
15841
+ return events;
15842
+ }
15843
+ /** Start the periodic reconciliation sweep in the long-lived UI process.
15844
+ * Returns the interval handle. */
15845
+ startSweeper(intervalMs = SWEEP_INTERVAL_MS) {
15846
+ return setInterval(() => this.reconcile(Date.now()), intervalMs);
15847
+ }
15683
15848
  /** Handle one inbound admin WhatsApp message. */
15684
15849
  async handleInbound(input) {
15685
15850
  const bytes = Buffer.byteLength(input.text, "utf8");
15686
15851
  const hadSubscriber = this.hub.hasSubscriber(input.senderId);
15852
+ const willDeliverNow = this.hub.isReady(input.senderId);
15687
15853
  this.replies.set(input.senderId, input.reply);
15688
- this.hub.deliver({ senderId: input.senderId, text: input.text, waMessageId: `wa-${++this.seq}` });
15854
+ this.hub.deliver(
15855
+ { senderId: input.senderId, text: input.text, waMessageId: `wa-${++this.seq}` },
15856
+ Date.now()
15857
+ );
15689
15858
  console.error(
15690
- `[whatsapp-native] op=inbound senderId=${input.senderId} accountId=${input.accountId} bytes=${bytes} delivery=${hadSubscriber ? "immediate" : "queued"}`
15859
+ `[whatsapp-native] op=inbound senderId=${input.senderId} accountId=${input.accountId} bytes=${bytes} delivery=${willDeliverNow ? "immediate" : "queued"}`
15691
15860
  );
15692
15861
  if (!hadSubscriber && !this.spawning.has(input.senderId)) {
15693
15862
  this.spawning.add(input.senderId);
@@ -15711,6 +15880,71 @@ var WaGateway = class {
15711
15880
  }
15712
15881
  };
15713
15882
 
15883
+ // app/lib/whatsapp/inbound/resolve-admin-userid.ts
15884
+ function classifyAdminUserId(users, admins, senderPhone) {
15885
+ const matched = users.find((u) => typeof u.phone === "string" && phonesMatch(u.phone, senderPhone));
15886
+ if (!matched) return { kind: "no-users-binding" };
15887
+ return admins.some((a) => a.userId === matched.userId) ? { kind: "resolved", userId: matched.userId } : { kind: "userid-not-admin", userId: matched.userId };
15888
+ }
15889
+ function pickAdminUserId(users, admins, senderPhone) {
15890
+ const r = classifyAdminUserId(users, admins, senderPhone);
15891
+ return r.kind === "resolved" ? r.userId : null;
15892
+ }
15893
+ function resolveAdminUserId(params) {
15894
+ try {
15895
+ const users = readUsersFile2(USERS_FILE);
15896
+ if (!users) return null;
15897
+ const account = listValidAccounts().find((a) => a.accountId === params.accountId);
15898
+ const admins = account?.config.admins ?? [];
15899
+ const direct = pickAdminUserId(users, admins, params.senderPhone);
15900
+ if (direct) return direct;
15901
+ const mapped = resolveViaLidMap(readLidMap() ?? {}, params.senderPhone);
15902
+ if (!mapped) return null;
15903
+ const viaLid = pickAdminUserId(users, admins, mapped);
15904
+ if (viaLid) {
15905
+ console.error(`[lid-map] resolved-via-cache lid=${params.senderPhone} userId=${viaLid.slice(0, 8)}`);
15906
+ }
15907
+ return viaLid;
15908
+ } catch {
15909
+ return null;
15910
+ }
15911
+ }
15912
+
15913
+ // app/lib/whatsapp/inbound/channel-admin-binding-drift.ts
15914
+ function findChannelAdminBindingDrift(adminPhones, admins, users) {
15915
+ const drift = [];
15916
+ for (const phone of adminPhones) {
15917
+ const r = classifyAdminUserId(users, admins, phone);
15918
+ if (r.kind === "no-users-binding") drift.push({ phone, reason: "no-users-binding" });
15919
+ else if (r.kind === "userid-not-admin") drift.push({ phone, reason: "userid-not-admin", userId: r.userId });
15920
+ }
15921
+ return drift;
15922
+ }
15923
+ function warnOnChannelAdminBindingDrift() {
15924
+ let users;
15925
+ try {
15926
+ users = readUsersFile2(USERS_FILE) ?? [];
15927
+ } catch {
15928
+ return;
15929
+ }
15930
+ let accounts;
15931
+ try {
15932
+ accounts = listValidAccounts();
15933
+ } catch {
15934
+ return;
15935
+ }
15936
+ for (const acct of accounts) {
15937
+ const adminPhones = readAdminPhones(acct.accountDir);
15938
+ const drift = findChannelAdminBindingDrift(adminPhones, acct.config.admins ?? [], users);
15939
+ for (const d of drift) {
15940
+ const tail = d.reason === "userid-not-admin" ? ` userId=${d.userId}` : "";
15941
+ console.error(
15942
+ `[admin-identity] binding-drift accountId=${acct.accountId} phone=${d.phone} reason=${d.reason}${tail}`
15943
+ );
15944
+ }
15945
+ }
15946
+ }
15947
+
15714
15948
  // app/lib/admin-sse-registry.ts
15715
15949
  var activeAdminSSEControllers = /* @__PURE__ */ new Set();
15716
15950
  function broadcastAdminShutdown(reason) {
@@ -15741,7 +15975,7 @@ function broadcastAdminShutdown(reason) {
15741
15975
 
15742
15976
  // ../lib/entitlement/src/index.ts
15743
15977
  import { createPublicKey, createHash as createHash5, verify as cryptoVerify } from "crypto";
15744
- import { existsSync as existsSync22, readFileSync as readFileSync21, statSync as statSync9 } from "fs";
15978
+ import { existsSync as existsSync23, readFileSync as readFileSync22, statSync as statSync9 } from "fs";
15745
15979
  import { resolve as resolve25 } from "path";
15746
15980
 
15747
15981
  // ../lib/entitlement/src/canonicalize.ts
@@ -15790,7 +16024,7 @@ function resolveEntitlement(brand, account) {
15790
16024
  return logResolved(implicitTrust(account), null);
15791
16025
  }
15792
16026
  const entitlementPath = resolve25(brand.configDir, "entitlement.json");
15793
- if (!existsSync22(entitlementPath)) {
16027
+ if (!existsSync23(entitlementPath)) {
15794
16028
  return logResolved(anonymousFallback("missing"), { reason: "missing" });
15795
16029
  }
15796
16030
  const stat7 = statSync9(entitlementPath);
@@ -15805,7 +16039,7 @@ function resolveEntitlement(brand, account) {
15805
16039
  function verifyAndResolve(brand, entitlementPath, account) {
15806
16040
  let pubkeyPem;
15807
16041
  try {
15808
- pubkeyPem = readFileSync21(pubkeyPath(brand), "utf-8");
16042
+ pubkeyPem = readFileSync22(pubkeyPath(brand), "utf-8");
15809
16043
  } catch (err) {
15810
16044
  return logResolved(anonymousFallback("pubkey-missing"), {
15811
16045
  reason: "pubkey-missing"
@@ -15819,7 +16053,7 @@ function verifyAndResolve(brand, entitlementPath, account) {
15819
16053
  }
15820
16054
  let envelope;
15821
16055
  try {
15822
- envelope = JSON.parse(readFileSync21(entitlementPath, "utf-8"));
16056
+ envelope = JSON.parse(readFileSync22(entitlementPath, "utf-8"));
15823
16057
  } catch {
15824
16058
  return logResolved(anonymousFallback("malformed"), { reason: "malformed" });
15825
16059
  }
@@ -15982,12 +16216,12 @@ function clientFrom(c) {
15982
16216
  var PLATFORM_ROOT9 = process.env.MAXY_PLATFORM_ROOT || "";
15983
16217
  var BRAND_JSON_PATH = PLATFORM_ROOT9 ? join18(PLATFORM_ROOT9, "config", "brand.json") : "";
15984
16218
  var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", marketingUrl: "https://getmaxy.com" };
15985
- if (BRAND_JSON_PATH && !existsSync23(BRAND_JSON_PATH)) {
16219
+ if (BRAND_JSON_PATH && !existsSync24(BRAND_JSON_PATH)) {
15986
16220
  console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
15987
16221
  }
15988
- if (BRAND_JSON_PATH && existsSync23(BRAND_JSON_PATH)) {
16222
+ if (BRAND_JSON_PATH && existsSync24(BRAND_JSON_PATH)) {
15989
16223
  try {
15990
- const parsed = JSON.parse(readFileSync22(BRAND_JSON_PATH, "utf-8"));
16224
+ const parsed = JSON.parse(readFileSync23(BRAND_JSON_PATH, "utf-8"));
15991
16225
  BRAND = { ...BRAND, ...parsed };
15992
16226
  } catch (err) {
15993
16227
  console.error(`[brand] Failed to parse brand.json: ${err.message}`);
@@ -16009,8 +16243,8 @@ var brandLoginOpts = {
16009
16243
  var ALIAS_DOMAINS_PATH = join18(homedir2(), BRAND.configDir, "alias-domains.json");
16010
16244
  function loadAliasDomains() {
16011
16245
  try {
16012
- if (!existsSync23(ALIAS_DOMAINS_PATH)) return null;
16013
- const parsed = JSON.parse(readFileSync22(ALIAS_DOMAINS_PATH, "utf-8"));
16246
+ if (!existsSync24(ALIAS_DOMAINS_PATH)) return null;
16247
+ const parsed = JSON.parse(readFileSync23(ALIAS_DOMAINS_PATH, "utf-8"));
16014
16248
  if (!Array.isArray(parsed)) {
16015
16249
  console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
16016
16250
  return null;
@@ -16039,8 +16273,11 @@ var waGateway = new WaGateway({
16039
16273
  gatewayUrl: `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`,
16040
16274
  serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve26(process.env.MAXY_PLATFORM_ROOT ?? join18(__dirname, ".."), "services/whatsapp-channel/dist/server.js"),
16041
16275
  ensureChannelSession: async ({ accountId, senderId, gatewayUrl, serverPath }) => {
16276
+ const userId = resolveAdminUserId({ accountId, senderPhone: senderId }) ?? void 0;
16277
+ console.error(`[whatsapp-native] op=admin-identity senderId=${senderId} userId=${userId ?? "owner-fallback"}`);
16042
16278
  const result = await managerRcSpawn({
16043
16279
  sessionId: adminSessionIdFor(accountId, senderId),
16280
+ userId,
16044
16281
  waChannel: { senderId, gatewayUrl, serverPath },
16045
16282
  logContext: `channel=whatsapp-native senderId=${senderId}`
16046
16283
  });
@@ -16050,6 +16287,7 @@ var waGateway = new WaGateway({
16050
16287
  }
16051
16288
  });
16052
16289
  app43.route("/", waGateway.routes());
16290
+ waGateway.startSweeper();
16053
16291
  app43.use("*", clientIpMiddleware);
16054
16292
  app43.use("*", async (c, next) => {
16055
16293
  await next();
@@ -16372,14 +16610,14 @@ app43.get("/agent-assets/:slug/:filename", (c) => {
16372
16610
  console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
16373
16611
  return c.text("Forbidden", 403);
16374
16612
  }
16375
- if (!existsSync23(filePath)) {
16613
+ if (!existsSync24(filePath)) {
16376
16614
  console.error(`[agent-assets] serve slug=${slug} file=${filename} status=404`);
16377
16615
  return c.text("Not found", 404);
16378
16616
  }
16379
16617
  const ext = "." + filename.split(".").pop()?.toLowerCase();
16380
16618
  const contentType = IMAGE_MIME[ext] || "application/octet-stream";
16381
16619
  console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
16382
- const body = readFileSync22(filePath);
16620
+ const body = readFileSync23(filePath);
16383
16621
  return c.body(body, 200, {
16384
16622
  "Content-Type": contentType,
16385
16623
  "Cache-Control": "public, max-age=3600"
@@ -16402,14 +16640,14 @@ app43.get("/generated/:filename", (c) => {
16402
16640
  console.error(`[generated] serve file=${filename} status=403`);
16403
16641
  return c.text("Forbidden", 403);
16404
16642
  }
16405
- if (!existsSync23(filePath)) {
16643
+ if (!existsSync24(filePath)) {
16406
16644
  console.error(`[generated] serve file=${filename} status=404`);
16407
16645
  return c.text("Not found", 404);
16408
16646
  }
16409
16647
  const ext = "." + filename.split(".").pop()?.toLowerCase();
16410
16648
  const contentType = IMAGE_MIME[ext] || "application/octet-stream";
16411
16649
  console.log(`[generated] serve file=${filename} status=200`);
16412
- const body = readFileSync22(filePath);
16650
+ const body = readFileSync23(filePath);
16413
16651
  return c.body(body, 200, {
16414
16652
  "Content-Type": contentType,
16415
16653
  "Cache-Control": "public, max-age=86400"
@@ -16422,9 +16660,9 @@ app43.route("/v", visitor_consent_default);
16422
16660
  var htmlCache = /* @__PURE__ */ new Map();
16423
16661
  var brandLogoPath = "/brand/maxy-monochrome.png";
16424
16662
  var brandIconPath = "/brand/maxy-monochrome.png";
16425
- if (BRAND_JSON_PATH && existsSync23(BRAND_JSON_PATH)) {
16663
+ if (BRAND_JSON_PATH && existsSync24(BRAND_JSON_PATH)) {
16426
16664
  try {
16427
- const fullBrand = JSON.parse(readFileSync22(BRAND_JSON_PATH, "utf-8"));
16665
+ const fullBrand = JSON.parse(readFileSync23(BRAND_JSON_PATH, "utf-8"));
16428
16666
  if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
16429
16667
  brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
16430
16668
  } catch {
@@ -16442,8 +16680,8 @@ function readInstalledVersion() {
16442
16680
  try {
16443
16681
  if (!PLATFORM_ROOT9) return "unknown";
16444
16682
  const versionFile = join18(PLATFORM_ROOT9, "config", `.${BRAND.hostname}-version`);
16445
- if (!existsSync23(versionFile)) return "unknown";
16446
- const content = readFileSync22(versionFile, "utf-8").trim();
16683
+ if (!existsSync24(versionFile)) return "unknown";
16684
+ const content = readFileSync23(versionFile, "utf-8").trim();
16447
16685
  return content || "unknown";
16448
16686
  } catch {
16449
16687
  return "unknown";
@@ -16484,7 +16722,7 @@ var clientErrorReporterScript = `<script>
16484
16722
  function cachedHtml(file) {
16485
16723
  let html = htmlCache.get(file);
16486
16724
  if (!html) {
16487
- html = readFileSync22(resolve26(process.cwd(), "public", file), "utf-8");
16725
+ html = readFileSync23(resolve26(process.cwd(), "public", file), "utf-8");
16488
16726
  const productNameEsc = escapeHtml(BRAND.productName);
16489
16727
  html = html.replace(/<title>([^<]*)<\/title>/, (_match, inner) => `<title>${escapeHtml(inner).replace(/Maxy/g, productNameEsc)}</title>`);
16490
16728
  html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
@@ -16505,8 +16743,8 @@ function loadBrandingCache(agentSlug) {
16505
16743
  const accountId = getDefaultAccountId();
16506
16744
  if (!accountId) return null;
16507
16745
  const cachePath = join18(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
16508
- if (!existsSync23(cachePath)) return null;
16509
- return JSON.parse(readFileSync22(cachePath, "utf-8"));
16746
+ if (!existsSync24(cachePath)) return null;
16747
+ return JSON.parse(readFileSync23(cachePath, "utf-8"));
16510
16748
  } catch {
16511
16749
  return null;
16512
16750
  }
@@ -16594,7 +16832,7 @@ app43.use("/vnc-popout.html", logViewerFetch);
16594
16832
  app43.get("/vnc-popout.html", (c) => {
16595
16833
  let html = htmlCache.get("vnc-popout.html");
16596
16834
  if (!html) {
16597
- html = readFileSync22(resolve26(process.cwd(), "public", "vnc-popout.html"), "utf-8");
16835
+ html = readFileSync23(resolve26(process.cwd(), "public", "vnc-popout.html"), "utf-8");
16598
16836
  const name = escapeHtml(BRAND.productName);
16599
16837
  html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
16600
16838
  html = html.replace("</head>", ` ${brandScript}
@@ -16748,11 +16986,11 @@ try {
16748
16986
  var ADMINUSER_RECONCILE_INTERVAL_MS = 60 * 60 * 1e3;
16749
16987
  async function runAdminUserReconcileTick() {
16750
16988
  try {
16751
- if (!existsSync23(USERS_FILE)) {
16989
+ if (!existsSync24(USERS_FILE)) {
16752
16990
  console.error("[adminuser-self-heal] skip reason=no-users-file");
16753
16991
  return;
16754
16992
  }
16755
- const usersRaw = readFileSync22(USERS_FILE, "utf-8").trim();
16993
+ const usersRaw = readFileSync23(USERS_FILE, "utf-8").trim();
16756
16994
  if (!usersRaw) {
16757
16995
  console.error("[adminuser-self-heal] skip reason=empty-users-file");
16758
16996
  return;
@@ -16817,6 +17055,7 @@ reconcileEnabledPlugins(bootAccount?.accountDir, bootAccount?.config, bootAccoun
16817
17055
  for (const acct of listValidAccounts()) {
16818
17056
  cleanupLeakedPremiumSubs(acct.accountDir, acct.config, acct.accountId);
16819
17057
  }
17058
+ warnOnChannelAdminBindingDrift();
16820
17059
  var bootEnabled = Array.isArray(bootAccountConfig?.enabledPlugins) ? bootAccountConfig.enabledPlugins : [];
16821
17060
  var bootDelivered = [];
16822
17061
  var bootDistMissing = [];