@rubytech/create-maxy-code 0.1.439 → 0.1.440

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 (60) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +14 -6
  3. package/payload/platform/plugins/docs/references/admin-identity-gate.md +32 -17
  4. package/payload/platform/plugins/docs/references/admin-ui.md +1 -1
  5. package/payload/platform/plugins/docs/references/internals.md +2 -2
  6. package/payload/platform/plugins/docs/references/outlook-guide.md +2 -2
  7. package/payload/platform/plugins/docs/references/troubleshooting.md +8 -0
  8. package/payload/platform/plugins/graph/.claude-plugin/plugin.json +19 -0
  9. package/payload/platform/plugins/graph/PLUGIN.md +10 -4
  10. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-list.test.d.ts +2 -0
  11. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-list.test.d.ts.map +1 -0
  12. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-list.test.js +55 -0
  13. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-list.test.js.map +1 -0
  14. package/payload/platform/plugins/outlook/mcp/dist/index.js +1 -1
  15. package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -1
  16. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.d.ts +17 -0
  17. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.d.ts.map +1 -1
  18. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.js +84 -21
  19. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.js.map +1 -1
  20. package/payload/platform/plugins/outlook/references/graph-surfaces.md +15 -1
  21. package/payload/platform/scripts/__tests__/account-schema-owned-dirs.test.sh +100 -0
  22. package/payload/platform/scripts/lib/account-schema-owned-dirs.py +207 -0
  23. package/payload/platform/scripts/lib/account-schema-owned-dirs.sh +33 -0
  24. package/payload/platform/scripts/lib/provision-account-dir.sh +7 -0
  25. package/payload/platform/scripts/setup-account.sh +10 -0
  26. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  27. package/payload/platform/services/claude-session-manager/dist/http-server.js +6 -4
  28. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  29. package/payload/platform/services/claude-session-manager/dist/tool-registration-drift.d.ts +7 -6
  30. package/payload/platform/services/claude-session-manager/dist/tool-registration-drift.d.ts.map +1 -1
  31. package/payload/platform/services/claude-session-manager/dist/tool-registration-drift.js +56 -7
  32. package/payload/platform/services/claude-session-manager/dist/tool-registration-drift.js.map +1 -1
  33. package/payload/server/{chunk-UGPQCVIW.js → chunk-WC4WJR6X.js} +0 -6
  34. package/payload/server/maxy-edge.js +1 -1
  35. package/payload/server/public/assets/{AdminLoginScreens-DeR71H29.js → AdminLoginScreens-CukwQufW.js} +1 -1
  36. package/payload/server/public/assets/AdminShell-BwEBH6tT.js +2 -0
  37. package/payload/server/public/assets/{admin-C15h-NL0.js → admin-CdFRCUUZ.js} +1 -1
  38. package/payload/server/public/assets/{browser-B7xQLIRK.js → browser-BWR59IkB.js} +1 -1
  39. package/payload/server/public/assets/{calendar-A-vGm6pL.js → calendar-kuTcxX1t.js} +1 -1
  40. package/payload/server/public/assets/chat-ejnq2rck.js +1 -0
  41. package/payload/server/public/assets/data-dZbOyLdT.js +1 -0
  42. package/payload/server/public/assets/{graph-D0orIRAh.js → graph-NgQDY0Pb.js} +1 -1
  43. package/payload/server/public/assets/{operator-DZzuotDB.js → operator-oSaB1mya.js} +1 -1
  44. package/payload/server/public/assets/{page-Ck_pLhAk.js → page-DEkI-Gl1.js} +1 -1
  45. package/payload/server/public/assets/{page-3g0A19Rv.js → page-DMeJHVfA.js} +5 -5
  46. package/payload/server/public/assets/{public-WFUQw9AD.js → public-C54tl6V4.js} +1 -1
  47. package/payload/server/public/assets/{useCopyFeedback-CSk9KjhY.js → useCopyFeedback-DHoYHiK9.js} +1 -1
  48. package/payload/server/public/assets/{useVoiceRecorder-DCyJQkFK.js → useVoiceRecorder-b7HzTFs0.js} +1 -1
  49. package/payload/server/public/browser.html +2 -2
  50. package/payload/server/public/calendar.html +2 -2
  51. package/payload/server/public/chat.html +5 -5
  52. package/payload/server/public/data.html +3 -3
  53. package/payload/server/public/graph.html +3 -3
  54. package/payload/server/public/index.html +5 -5
  55. package/payload/server/public/operator.html +6 -6
  56. package/payload/server/public/public.html +5 -5
  57. package/payload/server/server.js +186 -112
  58. package/payload/server/public/assets/AdminShell-DlAVQUda.js +0 -2
  59. package/payload/server/public/assets/chat-DAae6Z51.js +0 -1
  60. package/payload/server/public/assets/data-BNWpKx1d.js +0 -1
@@ -89,7 +89,6 @@ import {
89
89
  resolveCallback,
90
90
  resolveClientIp,
91
91
  resolveDefaultAgentSlug,
92
- retagAccountForUser,
93
92
  runAdminUserSelfHeal,
94
93
  runUserProfileReconcile,
95
94
  safeJson,
@@ -111,7 +110,7 @@ import {
111
110
  vncLog,
112
111
  walkPremiumBundles,
113
112
  writeAdminUserAndPerson
114
- } from "./chunk-UGPQCVIW.js";
113
+ } from "./chunk-WC4WJR6X.js";
115
114
  import {
116
115
  __commonJS,
117
116
  __toESM
@@ -126,7 +125,7 @@ var require_dist = __commonJS({
126
125
  exports.cosine = cosine;
127
126
  exports.deconflictBySubject = deconflictBySubject;
128
127
  exports.formatStandingRulesBlock = formatStandingRulesBlock2;
129
- exports.resolveOwnerUserId = resolveOwnerUserId;
128
+ exports.resolveOwnerUserId = resolveOwnerUserId2;
130
129
  exports.queryActiveRuleCandidates = queryActiveRuleCandidates;
131
130
  exports.resolveActiveRules = resolveActiveRules2;
132
131
  exports.activeRuleSet = activeRuleSet;
@@ -166,7 +165,7 @@ var require_dist = __commonJS({
166
165
  return `## Standing rules
167
166
  ${lines.join("\n")}`;
168
167
  }
169
- async function resolveOwnerUserId(session, accountId) {
168
+ async function resolveOwnerUserId2(session, accountId) {
170
169
  const res = await session.run(`
171
170
  MATCH (au:AdminUser {accountId: $accountId, role: 'owner'})
172
171
  RETURN au.userId AS userId
@@ -202,7 +201,7 @@ ${lines.join("\n")}`;
202
201
  async function resolveCandidates(session, accountId) {
203
202
  let ownerUserId;
204
203
  try {
205
- ownerUserId = await resolveOwnerUserId(session, accountId);
204
+ ownerUserId = await resolveOwnerUserId2(session, accountId);
206
205
  } catch {
207
206
  return { candidates: [], ownerUserId: null, source: "owner-error" };
208
207
  }
@@ -4756,7 +4755,10 @@ function buildSelfChatDispatch(input) {
4756
4755
  }),
4757
4756
  media: [],
4758
4757
  // Self-chat is the owner: the session scopes to the house account (Task 1383).
4759
- effectiveAccountId: input.accountId
4758
+ effectiveAccountId: input.accountId,
4759
+ // Task 1575 — self-chat is unambiguously the owner, so attribute the spawn
4760
+ // to the owner userId even when the owner phone is unbound in users.json.
4761
+ ownerSelfPhone: true
4760
4762
  };
4761
4763
  }
4762
4764
  async function init(opts) {
@@ -5566,7 +5568,11 @@ async function handleInboundMessage(conn, msg) {
5566
5568
  replyContext: extracted.quotedMessage,
5567
5569
  // Task 1383 — the gate's single resolution feeds the spawn (house or the
5568
5570
  // bound sub-account); the router never re-resolves.
5569
- effectiveAccountId: accessResult.effectiveAccountId
5571
+ effectiveAccountId: accessResult.effectiveAccountId,
5572
+ // Task 1575 — the gate's owner-self verdict. Only reason:'self-phone' is the
5573
+ // owner's own number; adminPhones/account-manager admits are other admins
5574
+ // and must not inherit the owner userId.
5575
+ ownerSelfPhone: accessResult.reason === "self-phone"
5570
5576
  };
5571
5577
  if (accessResult.agentType === "public") {
5572
5578
  const hoursResult = await isBusinessOpen(conn.accountId);
@@ -8199,8 +8205,8 @@ import { readFileSync as readFileSync15, watch, statSync as statSync5, openSync,
8199
8205
  import { basename as basename4, dirname as dirname4, isAbsolute, join as join15, relative as relative2, resolve as resolve12, sep as sep3 } from "path";
8200
8206
 
8201
8207
  // server/routes/admin/sidebar-sessions.ts
8202
- import { readdirSync as readdirSync6, readFileSync as readFileSync12, statSync as statSync3 } from "fs";
8203
- import { dirname as dirname3, join as join12, resolve as resolve11 } from "path";
8208
+ import { readdirSync as readdirSync7, readFileSync as readFileSync13, statSync as statSync4 } from "fs";
8209
+ import { dirname as dirname3, join as join13, resolve as resolve11 } from "path";
8204
8210
 
8205
8211
  // ../services/claude-session-manager/src/jsonl-path.ts
8206
8212
  import { existsSync as existsSync8, readdirSync as readdirSync5 } from "fs";
@@ -8225,6 +8231,96 @@ function findExistingJsonlForSessionId(claudeConfigDir2, sessionId) {
8225
8231
  return null;
8226
8232
  }
8227
8233
 
8234
+ // app/lib/admin-identity/pin-validator.ts
8235
+ import { createHash as createHash3 } from "crypto";
8236
+ import { existsSync as existsSync9, readFileSync as readFileSync12, readdirSync as readdirSync6, statSync as statSync3 } from "fs";
8237
+ import { join as join12 } from "path";
8238
+ function hashPin(pin) {
8239
+ return createHash3("sha256").update(pin).digest("hex");
8240
+ }
8241
+ function readUsersFile(usersFilePath) {
8242
+ if (!existsSync9(usersFilePath)) return null;
8243
+ const raw = readFileSync12(usersFilePath, "utf-8").trim();
8244
+ if (!raw) return [];
8245
+ return JSON.parse(raw);
8246
+ }
8247
+ function validatePin(pin, usersFilePath) {
8248
+ let users;
8249
+ try {
8250
+ users = readUsersFile(usersFilePath);
8251
+ } catch (err) {
8252
+ return { kind: "corrupt", message: err instanceof Error ? err.message : String(err) };
8253
+ }
8254
+ if (users === null || users.length === 0) return { kind: "unavailable" };
8255
+ const inputHash = hashPin(pin);
8256
+ const matched = users.find((u) => u.pin === inputHash);
8257
+ return matched ? { kind: "match", userId: matched.userId } : { kind: "miss" };
8258
+ }
8259
+
8260
+ // app/lib/whatsapp/inbound/resolve-admin-userid.ts
8261
+ function classifyAdminUserId(users, admins, senderPhone) {
8262
+ const matched = users.find((u) => typeof u.phone === "string" && phonesMatch(u.phone, senderPhone));
8263
+ if (!matched) return { kind: "no-users-binding" };
8264
+ return admins.some((a) => a.userId === matched.userId) ? { kind: "resolved", userId: matched.userId } : { kind: "userid-not-admin", userId: matched.userId };
8265
+ }
8266
+ function pickAdminUserId(users, admins, senderPhone) {
8267
+ const r = classifyAdminUserId(users, admins, senderPhone);
8268
+ return r.kind === "resolved" ? r.userId : null;
8269
+ }
8270
+ function pickOwnerUserId(admins) {
8271
+ return admins.find((a) => a.role === "owner")?.userId ?? null;
8272
+ }
8273
+ function resolveOwnerUserId(accountId) {
8274
+ try {
8275
+ const account = listValidAccounts().find((a) => a.accountId === accountId);
8276
+ return pickOwnerUserId(account?.config.admins ?? []);
8277
+ } catch {
8278
+ return null;
8279
+ }
8280
+ }
8281
+ function resolveAdminUserId(params) {
8282
+ try {
8283
+ const users = readUsersFile(USERS_FILE);
8284
+ if (!users) return null;
8285
+ const account = listValidAccounts().find((a) => a.accountId === params.accountId);
8286
+ const admins = account?.config.admins ?? [];
8287
+ const direct = pickAdminUserId(users, admins, params.senderPhone);
8288
+ if (direct) return direct;
8289
+ const mapped = resolveViaLidMap(readLidMap() ?? {}, params.senderPhone);
8290
+ if (!mapped) return null;
8291
+ const viaLid = pickAdminUserId(users, admins, mapped);
8292
+ if (viaLid) {
8293
+ console.error(`[lid-map] resolved-via-cache lid=${params.senderPhone} userId=${viaLid.slice(0, 8)}`);
8294
+ }
8295
+ return viaLid;
8296
+ } catch {
8297
+ return null;
8298
+ }
8299
+ }
8300
+
8301
+ // app/lib/whatsapp-reader/resolve-operator-name.ts
8302
+ function resolveOperatorDisplay(userIdRes, nameRes) {
8303
+ if (userIdRes.kind === "no-users-binding") return { name: null, reason: "no-binding" };
8304
+ if (userIdRes.kind === "userid-not-admin") return { name: null, reason: "not-admin" };
8305
+ if (nameRes && nameRes.source === "neo4j") return { name: nameRes.joined, reason: null };
8306
+ if (nameRes && nameRes.reason === "neo4j-unreachable") return { name: null, reason: "neo4j-unreachable" };
8307
+ return { name: null, reason: "no-person-name" };
8308
+ }
8309
+
8310
+ // app/lib/whatsapp-reader/resolve-operator-names.ts
8311
+ async function resolveOperatorNamesBySender(users, admins, senderIds) {
8312
+ const out = /* @__PURE__ */ new Map();
8313
+ const distinct = [...new Set([...senderIds].filter((s) => !!s))];
8314
+ await Promise.all(
8315
+ distinct.map(async (senderId) => {
8316
+ const idRes = classifyAdminUserId(users, admins, senderId);
8317
+ const nameRes = idRes.kind === "resolved" ? await loadAdminUserName(idRes.userId) : null;
8318
+ out.set(senderId, resolveOperatorDisplay(idRes, nameRes));
8319
+ })
8320
+ );
8321
+ return out;
8322
+ }
8323
+
8228
8324
  // server/routes/admin/sidebar-sessions.ts
8229
8325
  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;
8230
8326
  function lastAssistantModel(body) {
@@ -8261,16 +8357,16 @@ function enumerateJsonls(projectsRoot) {
8261
8357
  const out = [];
8262
8358
  let slugs;
8263
8359
  try {
8264
- slugs = readdirSync6(projectsRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
8360
+ slugs = readdirSync7(projectsRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
8265
8361
  } catch (err) {
8266
8362
  if (err.code === "ENOENT") return out;
8267
8363
  throw err;
8268
8364
  }
8269
8365
  for (const slug of slugs) {
8270
- const slugDir = join12(projectsRoot, slug);
8366
+ const slugDir = join13(projectsRoot, slug);
8271
8367
  let entries;
8272
8368
  try {
8273
- entries = readdirSync6(slugDir, { withFileTypes: true });
8369
+ entries = readdirSync7(slugDir, { withFileTypes: true });
8274
8370
  } catch (err) {
8275
8371
  const code = err.code ?? "unknown";
8276
8372
  console.error(`[admin-sessions-list] slug-skipped slug=${slug} code=${code}`);
@@ -8278,12 +8374,12 @@ function enumerateJsonls(projectsRoot) {
8278
8374
  }
8279
8375
  for (const entry of entries) {
8280
8376
  if (entry.isFile() && SESSION_ID_RE.test(entry.name)) {
8281
- out.push({ path: join12(slugDir, entry.name), isSubagent: false, archived: false });
8377
+ out.push({ path: join13(slugDir, entry.name), isSubagent: false, archived: false });
8282
8378
  } else if (entry.isDirectory() && entry.name === "subagents") {
8283
- const subDir = join12(slugDir, entry.name);
8379
+ const subDir = join13(slugDir, entry.name);
8284
8380
  let subEntries;
8285
8381
  try {
8286
- subEntries = readdirSync6(subDir, { withFileTypes: true });
8382
+ subEntries = readdirSync7(subDir, { withFileTypes: true });
8287
8383
  } catch (err) {
8288
8384
  const code = err.code ?? "unknown";
8289
8385
  console.error(`[admin-sessions-list] subagents-skipped slug=${slug} code=${code}`);
@@ -8291,14 +8387,14 @@ function enumerateJsonls(projectsRoot) {
8291
8387
  }
8292
8388
  for (const sub of subEntries) {
8293
8389
  if (sub.isFile() && SESSION_ID_RE.test(sub.name)) {
8294
- out.push({ path: join12(subDir, sub.name), isSubagent: true, archived: false });
8390
+ out.push({ path: join13(subDir, sub.name), isSubagent: true, archived: false });
8295
8391
  }
8296
8392
  }
8297
8393
  } else if (entry.isDirectory() && entry.name === "archive") {
8298
- const archiveDir = join12(slugDir, entry.name);
8394
+ const archiveDir = join13(slugDir, entry.name);
8299
8395
  let archiveEntries;
8300
8396
  try {
8301
- archiveEntries = readdirSync6(archiveDir, { withFileTypes: true });
8397
+ archiveEntries = readdirSync7(archiveDir, { withFileTypes: true });
8302
8398
  } catch (err) {
8303
8399
  const code = err.code ?? "unknown";
8304
8400
  console.error(`[admin-sessions-list] archive-skipped slug=${slug} code=${code}`);
@@ -8306,7 +8402,7 @@ function enumerateJsonls(projectsRoot) {
8306
8402
  }
8307
8403
  for (const arc of archiveEntries) {
8308
8404
  if (arc.isFile() && SESSION_ID_RE.test(arc.name)) {
8309
- out.push({ path: join12(archiveDir, arc.name), isSubagent: false, archived: true });
8405
+ out.push({ path: join13(archiveDir, arc.name), isSubagent: false, archived: true });
8310
8406
  }
8311
8407
  }
8312
8408
  }
@@ -8339,10 +8435,10 @@ async function fetchLiveSessions() {
8339
8435
  function loadUserTitles(accountDir) {
8340
8436
  const out = /* @__PURE__ */ new Map();
8341
8437
  if (!accountDir) return out;
8342
- const path2 = join12(accountDir, "session-titles.json");
8438
+ const path2 = join13(accountDir, "session-titles.json");
8343
8439
  let raw;
8344
8440
  try {
8345
- raw = readFileSync12(path2, "utf8");
8441
+ raw = readFileSync13(path2, "utf8");
8346
8442
  } catch {
8347
8443
  return out;
8348
8444
  }
@@ -8368,7 +8464,7 @@ function readSidecarMeta(metaPath) {
8368
8464
  const empty = { bridgeIds: [], role: null, channel: null, senderId: null, startedAt: null, personId: null, adminUserId: null, visitorId: null, agentSlug: null, accountId: null };
8369
8465
  let raw;
8370
8466
  try {
8371
- raw = readFileSync12(metaPath, "utf8");
8467
+ raw = readFileSync13(metaPath, "utf8");
8372
8468
  } catch {
8373
8469
  return empty;
8374
8470
  }
@@ -8466,11 +8562,22 @@ app3.get("/", requireAdminSession, async (c) => {
8466
8562
  console.error("[admin-sessions-list] CLAUDE_CONFIG_DIR not set; returning empty list");
8467
8563
  return c.json({ sessions: [], accountId: scopeAccountId });
8468
8564
  }
8469
- const projectsRoot = join12(configDir2, "projects");
8565
+ const projectsRoot = join13(configDir2, "projects");
8470
8566
  const jsonls = enumerateJsonls(projectsRoot);
8471
8567
  const liveSessions = await fetchLiveSessions();
8472
8568
  const accountDir = scopeAccountId ? resolve11(ACCOUNTS_DIR, scopeAccountId) : null;
8473
8569
  const userTitles = loadUserTitles(accountDir);
8570
+ const account = multiAccount ? validAccounts.find((a) => a.accountId === scopeAccountId) ?? null : resolveAccount();
8571
+ let channelUsers = [];
8572
+ if (account) {
8573
+ try {
8574
+ channelUsers = readUsersFile(USERS_FILE) ?? [];
8575
+ } catch (err) {
8576
+ console.error(`[admin-sessions-list] op=users-file-unreadable reason=${err instanceof Error ? err.message : String(err)}`);
8577
+ channelUsers = [];
8578
+ }
8579
+ }
8580
+ const channelAdmins = account?.config.admins ?? [];
8474
8581
  const rows = [];
8475
8582
  const seenIds = /* @__PURE__ */ new Set();
8476
8583
  let liveCount = 0;
@@ -8484,6 +8591,8 @@ app3.get("/", requireAdminSession, async (c) => {
8484
8591
  const titleSourceCounts = { user: 0, ai: 0, "first-message": 0, prefix: 0 };
8485
8592
  const personIdBySession = /* @__PURE__ */ new Map();
8486
8593
  const adminUserIdBySession = /* @__PURE__ */ new Map();
8594
+ const channelSenderBySession = /* @__PURE__ */ new Map();
8595
+ const channelMissNoSender = [];
8487
8596
  for (const { path: path2, isSubagent, archived } of jsonls) {
8488
8597
  const lastSlash = path2.lastIndexOf("/");
8489
8598
  const base = path2.slice(lastSlash + 1);
@@ -8497,8 +8606,8 @@ app3.get("/", requireAdminSession, async (c) => {
8497
8606
  let body;
8498
8607
  let mtimeMs;
8499
8608
  try {
8500
- body = readFileSync12(path2, "utf8");
8501
- mtimeMs = statSync3(path2).mtimeMs;
8609
+ body = readFileSync13(path2, "utf8");
8610
+ mtimeMs = statSync4(path2).mtimeMs;
8502
8611
  } catch {
8503
8612
  continue;
8504
8613
  }
@@ -8506,8 +8615,8 @@ app3.get("/", requireAdminSession, async (c) => {
8506
8615
  const pid = live ? liveSessions.get(sessionId) ?? null : null;
8507
8616
  const resolved = resolveTitle(body, sessionId, userTitles);
8508
8617
  titleSourceCounts[resolved.source] += 1;
8509
- const metaPath = join12(projectDir, `${sessionId}.meta.json`);
8510
- const { bridgeIds, role, channel: sidecarChannel, personId, adminUserId, accountId: rowAccountId } = readSidecarMeta(metaPath);
8618
+ const metaPath = join13(projectDir, `${sessionId}.meta.json`);
8619
+ const { bridgeIds, role, channel: sidecarChannel, senderId, personId, adminUserId, accountId: rowAccountId } = readSidecarMeta(metaPath);
8511
8620
  if (multiAccount) {
8512
8621
  if (rowAccountId === null) untaggedCount += 1;
8513
8622
  if (rowAccountId !== scopeAccountId) continue;
@@ -8539,8 +8648,13 @@ app3.get("/", requireAdminSession, async (c) => {
8539
8648
  channel: sidecarChannel,
8540
8649
  personName: null,
8541
8650
  model: lastAssistantModel(body),
8542
- accountId: rowAccountId
8651
+ accountId: rowAccountId,
8652
+ senderId
8543
8653
  });
8654
+ if ((sidecarChannel === "whatsapp" || sidecarChannel === "telegram") && resolved.source === "prefix") {
8655
+ if (senderId) channelSenderBySession.set(sessionId, senderId);
8656
+ else channelMissNoSender.push({ sessionId, channel: sidecarChannel });
8657
+ }
8544
8658
  if (sidecarChannel === "whatsapp") channelInList.whatsapp += 1;
8545
8659
  else if (sidecarChannel === "telegram") channelInList.telegram += 1;
8546
8660
  else if (sidecarChannel === "webchat") channelInList.webchat += 1;
@@ -8575,9 +8689,29 @@ app3.get("/", requireAdminSession, async (c) => {
8575
8689
  })
8576
8690
  );
8577
8691
  }
8692
+ let channelNamesResolved = 0;
8693
+ const channelNameCandidates = channelSenderBySession.size + channelMissNoSender.length;
8694
+ if (account && channelSenderBySession.size > 0) {
8695
+ const resolvedNames = await resolveOperatorNamesBySender(channelUsers, channelAdmins, channelSenderBySession.values());
8696
+ for (const row of rows) {
8697
+ const rowSenderId = channelSenderBySession.get(row.sessionId);
8698
+ if (!rowSenderId) continue;
8699
+ const r = resolvedNames.get(rowSenderId) ?? { name: null, reason: "no-binding" };
8700
+ if (r.name) {
8701
+ row.personName = r.name;
8702
+ channelNamesResolved += 1;
8703
+ } else {
8704
+ const reason = r.reason === "neo4j-unreachable" ? "neo4j-down" : "classify-miss";
8705
+ console.error(`[admin-sessions-list] op=channel-name-unresolved sessionId=${row.sessionId.slice(0, 8)} channel=${row.channel} senderId=${rowSenderId} reason=${reason}`);
8706
+ }
8707
+ }
8708
+ }
8709
+ for (const m of channelMissNoSender) {
8710
+ console.error(`[admin-sessions-list] op=channel-name-unresolved sessionId=${m.sessionId.slice(0, 8)} channel=${m.channel} senderId= reason=no-senderId`);
8711
+ }
8578
8712
  rows.sort((a, b) => a.startedAt < b.startedAt ? 1 : -1);
8579
8713
  console.log(
8580
- `[admin-sessions-list] rows=${rows.length} live=${liveCount} subagents=${subagentCount} archived=${archivedCount} channelInList=whatsapp:${channelInList.whatsapp},telegram:${channelInList.telegram},webchat:${channelInList.webchat} unknownChannel=${unknownChannelCount} excludedPublicWebchat=${excludedPublicWebchatCount} titles user=${titleSourceCounts.user} ai=${titleSourceCounts.ai} first=${titleSourceCounts["first-message"]} prefix=${titleSourceCounts.prefix} personNames=${personNameCount} adminNames=${adminNameCount} accountId=${scopeAccountId ? scopeAccountId.slice(0, 8) : "unset"}`
8714
+ `[admin-sessions-list] rows=${rows.length} live=${liveCount} subagents=${subagentCount} archived=${archivedCount} channelInList=whatsapp:${channelInList.whatsapp},telegram:${channelInList.telegram},webchat:${channelInList.webchat} channelNameCandidates=${channelNameCandidates} channelNamesResolved=${channelNamesResolved} unknownChannel=${unknownChannelCount} excludedPublicWebchat=${excludedPublicWebchatCount} titles user=${titleSourceCounts.user} ai=${titleSourceCounts.ai} first=${titleSourceCounts["first-message"]} prefix=${titleSourceCounts.prefix} personNames=${personNameCount} adminNames=${adminNameCount} accountId=${scopeAccountId ? scopeAccountId.slice(0, 8) : "unset"}`
8581
8715
  );
8582
8716
  console.log(
8583
8717
  `[admin-sessions-list] accountId=${scopeAccountId ? scopeAccountId.slice(0, 8) : "unset"} scope=${multiAccount ? "scoped" : "all"} rows=${rows.length}`
@@ -8589,71 +8723,6 @@ app3.get("/", requireAdminSession, async (c) => {
8589
8723
  });
8590
8724
  var sidebar_sessions_default = app3;
8591
8725
 
8592
- // app/lib/admin-identity/pin-validator.ts
8593
- import { createHash as createHash3 } from "crypto";
8594
- import { existsSync as existsSync9, readFileSync as readFileSync13, readdirSync as readdirSync7, statSync as statSync4 } from "fs";
8595
- import { join as join13 } from "path";
8596
- function hashPin(pin) {
8597
- return createHash3("sha256").update(pin).digest("hex");
8598
- }
8599
- function readUsersFile(usersFilePath) {
8600
- if (!existsSync9(usersFilePath)) return null;
8601
- const raw = readFileSync13(usersFilePath, "utf-8").trim();
8602
- if (!raw) return [];
8603
- return JSON.parse(raw);
8604
- }
8605
- function validatePin(pin, usersFilePath) {
8606
- let users;
8607
- try {
8608
- users = readUsersFile(usersFilePath);
8609
- } catch (err) {
8610
- return { kind: "corrupt", message: err instanceof Error ? err.message : String(err) };
8611
- }
8612
- if (users === null || users.length === 0) return { kind: "unavailable" };
8613
- const inputHash = hashPin(pin);
8614
- const matched = users.find((u) => u.pin === inputHash);
8615
- return matched ? { kind: "match", userId: matched.userId } : { kind: "miss" };
8616
- }
8617
-
8618
- // app/lib/whatsapp/inbound/resolve-admin-userid.ts
8619
- function classifyAdminUserId(users, admins, senderPhone) {
8620
- const matched = users.find((u) => typeof u.phone === "string" && phonesMatch(u.phone, senderPhone));
8621
- if (!matched) return { kind: "no-users-binding" };
8622
- return admins.some((a) => a.userId === matched.userId) ? { kind: "resolved", userId: matched.userId } : { kind: "userid-not-admin", userId: matched.userId };
8623
- }
8624
- function pickAdminUserId(users, admins, senderPhone) {
8625
- const r = classifyAdminUserId(users, admins, senderPhone);
8626
- return r.kind === "resolved" ? r.userId : null;
8627
- }
8628
- function resolveAdminUserId(params) {
8629
- try {
8630
- const users = readUsersFile(USERS_FILE);
8631
- if (!users) return null;
8632
- const account = listValidAccounts().find((a) => a.accountId === params.accountId);
8633
- const admins = account?.config.admins ?? [];
8634
- const direct = pickAdminUserId(users, admins, params.senderPhone);
8635
- if (direct) return direct;
8636
- const mapped = resolveViaLidMap(readLidMap() ?? {}, params.senderPhone);
8637
- if (!mapped) return null;
8638
- const viaLid = pickAdminUserId(users, admins, mapped);
8639
- if (viaLid) {
8640
- console.error(`[lid-map] resolved-via-cache lid=${params.senderPhone} userId=${viaLid.slice(0, 8)}`);
8641
- }
8642
- return viaLid;
8643
- } catch {
8644
- return null;
8645
- }
8646
- }
8647
-
8648
- // app/lib/whatsapp-reader/resolve-operator-name.ts
8649
- function resolveOperatorDisplay(userIdRes, nameRes) {
8650
- if (userIdRes.kind === "no-users-binding") return { name: null, reason: "no-binding" };
8651
- if (userIdRes.kind === "userid-not-admin") return { name: null, reason: "not-admin" };
8652
- if (nameRes && nameRes.source === "neo4j") return { name: nameRes.joined, reason: null };
8653
- if (nameRes && nameRes.reason === "neo4j-unreachable") return { name: null, reason: "neo4j-unreachable" };
8654
- return { name: null, reason: "no-person-name" };
8655
- }
8656
-
8657
8726
  // app/lib/agent-errors.ts
8658
8727
  function classifyAgentError(err) {
8659
8728
  const msg = err instanceof Error ? err.message : String(err);
@@ -9063,19 +9132,16 @@ app4.get("/conversations", requireAdminSession, async (c) => {
9063
9132
  const admins = account?.config.admins ?? [];
9064
9133
  const nameBySender = /* @__PURE__ */ new Map();
9065
9134
  const distinct = [...new Set(sessionRows.map((r) => r.senderId).filter((s) => !!s))];
9066
- await Promise.all(
9067
- distinct.map(async (senderId) => {
9068
- if (!account) {
9069
- nameBySender.set(senderId, null);
9070
- return;
9071
- }
9072
- const idRes = classifyAdminUserId(users, admins, senderId);
9073
- const nameRes = idRes.kind === "resolved" ? await loadAdminUserName(idRes.userId) : null;
9074
- const { name, reason } = resolveOperatorDisplay(idRes, nameRes);
9135
+ if (account) {
9136
+ const resolved = await resolveOperatorNamesBySender(users, admins, distinct);
9137
+ for (const senderId of distinct) {
9138
+ const { name, reason } = resolved.get(senderId) ?? { name: null, reason: "no-binding" };
9075
9139
  if (reason) console.error(`[wa-reader] op=operator-unresolved senderId=${senderId} reason=${reason}`);
9076
9140
  nameBySender.set(senderId, name);
9077
- })
9078
- );
9141
+ }
9142
+ } else {
9143
+ for (const senderId of distinct) nameBySender.set(senderId, null);
9144
+ }
9079
9145
  const sessionConversations = sessionRows.map((r) => ({
9080
9146
  ...r,
9081
9147
  operatorName: r.senderId ? nameBySender.get(r.senderId) ?? null : null
@@ -12399,8 +12465,8 @@ app11.post("/switch-account", requireAdminSession, async (c) => {
12399
12465
  const { userName, avatar } = await resolveUserIdentity(target.accountId, userId);
12400
12466
  const payload = await createAdminSession(target.accountId, target.config.thinkingView, userId, userName, operatorRoleFor(target.config, userId), avatar);
12401
12467
  unregisterSession(oldCacheKey);
12402
- retagAccountForUser(userId, target.accountId);
12403
12468
  console.log(`[sub-account] op=switch userId=${userId.slice(0, 8)} from=${oldCacheKey.slice(0, 8)} to=${target.accountId.slice(0, 8)}`);
12469
+ console.log(`[sub-account] op=rebind cacheKey=${payload.session_key.slice(0, 8)} account=${target.accountId.slice(0, 8)} siblingsTouched=0`);
12404
12470
  return c.json(payload);
12405
12471
  });
12406
12472
  var session_default = app11;
@@ -22627,7 +22693,8 @@ var WaGateway = class {
22627
22693
  serverPath: this.deps.serverPath,
22628
22694
  passive: true,
22629
22695
  text: input.text,
22630
- msgId
22696
+ msgId,
22697
+ ownerSelfPhone: input.ownerSelfPhone
22631
22698
  });
22632
22699
  return;
22633
22700
  }
@@ -22682,7 +22749,10 @@ var WaGateway = class {
22682
22749
  gatewayUrl: this.deps.gatewayUrl,
22683
22750
  serverPath: this.deps.serverPath,
22684
22751
  // Task 1526 — a scheduled firing's per-firing seat id; absent on real inbound.
22685
- sessionIdOverride: input.scheduledSessionId
22752
+ sessionIdOverride: input.scheduledSessionId,
22753
+ // Task 1575 — the gate's owner-self verdict; drives owner attribution
22754
+ // for an unbound owner self-phone at the spawn site.
22755
+ ownerSelfPhone: input.ownerSelfPhone
22686
22756
  });
22687
22757
  } finally {
22688
22758
  this.spawning.delete(input.senderId);
@@ -24846,7 +24916,7 @@ var waGateway = new WaGateway({
24846
24916
  });
24847
24917
  return result.ok ? { ok: true, messageId: result.messageId } : { ok: false, error: result.error };
24848
24918
  },
24849
- ensureChannelSession: async ({ accountId, effectiveAccountId, senderId, role, personId, gatewayUrl, serverPath, passive, text, msgId, sessionIdOverride }) => {
24919
+ ensureChannelSession: async ({ accountId, effectiveAccountId, senderId, role, personId, gatewayUrl, serverPath, passive, text, msgId, sessionIdOverride, ownerSelfPhone }) => {
24850
24920
  if (passive) {
24851
24921
  const phoneHash = createHash7("sha256").update(senderId).digest("hex").slice(0, 12);
24852
24922
  const prompt = (text ?? "").trim();
@@ -24895,9 +24965,10 @@ var waGateway = new WaGateway({
24895
24965
  console.error(`[whatsapp-native] op=account-manager-route senderId=${senderId} managesAccount=${managesAccount} effectiveAccount=${effectiveAccountId} source=gate`);
24896
24966
  }
24897
24967
  const req = buildWaSpawnRequest({ accountId: effectiveAccountId, senderId, role, personId, gatewayUrl, serverPath, sessionIdOverride });
24898
- const userId = role === "admin" ? resolveAdminUserId({ accountId: effectiveAccountId, senderPhone: senderId }) ?? void 0 : void 0;
24968
+ const userId = role === "admin" ? resolveAdminUserId({ accountId: effectiveAccountId, senderPhone: senderId }) ?? (ownerSelfPhone ? resolveOwnerUserId(effectiveAccountId) ?? void 0 : void 0) : void 0;
24899
24969
  if (role === "admin") {
24900
- console.error(`[whatsapp-native] op=admin-identity senderId=${senderId} userId=${userId ?? "owner-fallback"}`);
24970
+ const attribution = userId ?? (ownerSelfPhone ? "owner-self-unresolved" : "unbound-empty");
24971
+ console.error(`[whatsapp-native] op=admin-identity senderId=${senderId} userId=${attribution}`);
24901
24972
  }
24902
24973
  const result = await managerRcSpawn({ ...req, userId });
24903
24974
  if ("error" in result) {
@@ -26159,7 +26230,10 @@ init({
26159
26230
  passive: decision.passive,
26160
26231
  text: msg.text,
26161
26232
  reply: msg.reply,
26162
- media: msg.media
26233
+ media: msg.media,
26234
+ // Task 1575 — carry the gate's owner-self verdict to the spawn site so an
26235
+ // unbound owner self-phone attributes to the owner userId.
26236
+ ownerSelfPhone: msg.ownerSelfPhone
26163
26237
  });
26164
26238
  } catch (err) {
26165
26239
  console.error(