@rubytech/create-maxy-code 0.1.439 → 0.1.441

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 (107) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +41 -12
  3. package/payload/platform/plugins/cloudflare/skills/calendar-site/SKILL.md +15 -5
  4. package/payload/platform/plugins/cloudflare/skills/calendar-site/template/functions/api/calendar/free-busy.ts +152 -0
  5. package/payload/platform/plugins/cloudflare/skills/calendar-site/template/public/index.html +6 -3
  6. package/payload/platform/plugins/cloudflare/skills/calendar-site/template/schema.sql +11 -0
  7. package/payload/platform/plugins/docs/references/admin-identity-gate.md +32 -17
  8. package/payload/platform/plugins/docs/references/admin-ui.md +1 -1
  9. package/payload/platform/plugins/docs/references/calendar-booking.md +1 -1
  10. package/payload/platform/plugins/docs/references/graph.md +19 -3
  11. package/payload/platform/plugins/docs/references/internals.md +2 -2
  12. package/payload/platform/plugins/docs/references/outlook-guide.md +9 -4
  13. package/payload/platform/plugins/docs/references/troubleshooting.md +8 -0
  14. package/payload/platform/plugins/graph/.claude-plugin/plugin.json +19 -0
  15. package/payload/platform/plugins/graph/PLUGIN.md +10 -4
  16. package/payload/platform/plugins/outlook/.claude-plugin/plugin.json +1 -1
  17. package/payload/platform/plugins/outlook/PLUGIN.md +7 -3
  18. package/payload/platform/plugins/outlook/mcp/dist/__tests__/account-register.test.js +100 -10
  19. package/payload/platform/plugins/outlook/mcp/dist/__tests__/account-register.test.js.map +1 -1
  20. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-list.test.d.ts +2 -0
  21. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-list.test.d.ts.map +1 -0
  22. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-list.test.js +55 -0
  23. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-list.test.js.map +1 -0
  24. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-list.test.d.ts +2 -0
  25. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-list.test.d.ts.map +1 -0
  26. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-list.test.js +64 -0
  27. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-list.test.js.map +1 -0
  28. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-registry.test.d.ts +2 -0
  29. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-registry.test.d.ts.map +1 -0
  30. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-registry.test.js +193 -0
  31. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-registry.test.js.map +1 -0
  32. package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.js +58 -1
  33. package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.js.map +1 -1
  34. package/payload/platform/plugins/outlook/mcp/dist/auth/mailbox-registry.d.ts +81 -0
  35. package/payload/platform/plugins/outlook/mcp/dist/auth/mailbox-registry.d.ts.map +1 -0
  36. package/payload/platform/plugins/outlook/mcp/dist/auth/mailbox-registry.js +171 -0
  37. package/payload/platform/plugins/outlook/mcp/dist/auth/mailbox-registry.js.map +1 -0
  38. package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.d.ts +16 -1
  39. package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.d.ts.map +1 -1
  40. package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.js +25 -3
  41. package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.js.map +1 -1
  42. package/payload/platform/plugins/outlook/mcp/dist/index.js +119 -50
  43. package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -1
  44. package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js +1 -1
  45. package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js.map +1 -1
  46. package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.d.ts +8 -4
  47. package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.d.ts.map +1 -1
  48. package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.js +22 -3
  49. package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.js.map +1 -1
  50. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.d.ts +17 -0
  51. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.d.ts.map +1 -1
  52. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.js +84 -21
  53. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.js.map +1 -1
  54. package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-list.d.ts +26 -0
  55. package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-list.d.ts.map +1 -0
  56. package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-list.js +28 -0
  57. package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-list.js.map +1 -0
  58. package/payload/platform/plugins/outlook/references/auth.md +36 -8
  59. package/payload/platform/plugins/outlook/references/graph-surfaces.md +15 -1
  60. package/payload/platform/plugins/scheduling/mcp/dist/scripts/publish-availability.d.ts +27 -0
  61. package/payload/platform/plugins/scheduling/mcp/dist/scripts/publish-availability.d.ts.map +1 -0
  62. package/payload/platform/plugins/scheduling/mcp/dist/scripts/publish-availability.js +241 -0
  63. package/payload/platform/plugins/scheduling/mcp/dist/scripts/publish-availability.js.map +1 -0
  64. package/payload/platform/scripts/__tests__/account-schema-owned-dirs.test.sh +100 -0
  65. package/payload/platform/scripts/lib/account-schema-owned-dirs.py +207 -0
  66. package/payload/platform/scripts/lib/account-schema-owned-dirs.sh +33 -0
  67. package/payload/platform/scripts/lib/provision-account-dir.sh +7 -0
  68. package/payload/platform/scripts/setup-account.sh +10 -0
  69. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  70. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +1 -0
  71. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  72. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  73. package/payload/platform/services/claude-session-manager/dist/http-server.js +6 -4
  74. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  75. package/payload/platform/services/claude-session-manager/dist/tool-registration-drift.d.ts +7 -6
  76. package/payload/platform/services/claude-session-manager/dist/tool-registration-drift.d.ts.map +1 -1
  77. package/payload/platform/services/claude-session-manager/dist/tool-registration-drift.js +56 -7
  78. package/payload/platform/services/claude-session-manager/dist/tool-registration-drift.js.map +1 -1
  79. package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
  80. package/payload/server/{chunk-UGPQCVIW.js → chunk-WC4WJR6X.js} +0 -6
  81. package/payload/server/maxy-edge.js +1 -1
  82. package/payload/server/public/assets/{AdminLoginScreens-DeR71H29.js → AdminLoginScreens-CukwQufW.js} +1 -1
  83. package/payload/server/public/assets/AdminShell-BwEBH6tT.js +2 -0
  84. package/payload/server/public/assets/{admin-C15h-NL0.js → admin-CdFRCUUZ.js} +1 -1
  85. package/payload/server/public/assets/{browser-B7xQLIRK.js → browser-BWR59IkB.js} +1 -1
  86. package/payload/server/public/assets/{calendar-A-vGm6pL.js → calendar-kuTcxX1t.js} +1 -1
  87. package/payload/server/public/assets/chat-ejnq2rck.js +1 -0
  88. package/payload/server/public/assets/data-dZbOyLdT.js +1 -0
  89. package/payload/server/public/assets/{graph-D0orIRAh.js → graph-CboWM2_-.js} +2 -2
  90. package/payload/server/public/assets/{operator-DZzuotDB.js → operator-oSaB1mya.js} +1 -1
  91. package/payload/server/public/assets/{page-Ck_pLhAk.js → page-DEkI-Gl1.js} +1 -1
  92. package/payload/server/public/assets/{page-3g0A19Rv.js → page-DMeJHVfA.js} +5 -5
  93. package/payload/server/public/assets/{public-WFUQw9AD.js → public-C54tl6V4.js} +1 -1
  94. package/payload/server/public/assets/{useCopyFeedback-CSk9KjhY.js → useCopyFeedback-DHoYHiK9.js} +1 -1
  95. package/payload/server/public/assets/{useVoiceRecorder-DCyJQkFK.js → useVoiceRecorder-b7HzTFs0.js} +1 -1
  96. package/payload/server/public/browser.html +2 -2
  97. package/payload/server/public/calendar.html +2 -2
  98. package/payload/server/public/chat.html +5 -5
  99. package/payload/server/public/data.html +3 -3
  100. package/payload/server/public/graph.html +3 -3
  101. package/payload/server/public/index.html +5 -5
  102. package/payload/server/public/operator.html +6 -6
  103. package/payload/server/public/public.html +5 -5
  104. package/payload/server/server.js +255 -112
  105. package/payload/server/public/assets/AdminShell-DlAVQUda.js +0 -2
  106. package/payload/server/public/assets/chat-DAae6Z51.js +0 -1
  107. 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,98 @@ 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
+ if (account?.config.role === "client") return null;
8277
+ return pickOwnerUserId(account?.config.admins ?? []);
8278
+ } catch {
8279
+ return null;
8280
+ }
8281
+ }
8282
+ function resolveAdminUserId(params) {
8283
+ try {
8284
+ const users = readUsersFile(USERS_FILE);
8285
+ if (!users) return null;
8286
+ const account = listValidAccounts().find((a) => a.accountId === params.accountId);
8287
+ if (account?.config.role === "client") return null;
8288
+ const admins = account?.config.admins ?? [];
8289
+ const direct = pickAdminUserId(users, admins, params.senderPhone);
8290
+ if (direct) return direct;
8291
+ const mapped = resolveViaLidMap(readLidMap() ?? {}, params.senderPhone);
8292
+ if (!mapped) return null;
8293
+ const viaLid = pickAdminUserId(users, admins, mapped);
8294
+ if (viaLid) {
8295
+ console.error(`[lid-map] resolved-via-cache lid=${params.senderPhone} userId=${viaLid.slice(0, 8)}`);
8296
+ }
8297
+ return viaLid;
8298
+ } catch {
8299
+ return null;
8300
+ }
8301
+ }
8302
+
8303
+ // app/lib/whatsapp-reader/resolve-operator-name.ts
8304
+ function resolveOperatorDisplay(userIdRes, nameRes) {
8305
+ if (userIdRes.kind === "no-users-binding") return { name: null, reason: "no-binding" };
8306
+ if (userIdRes.kind === "userid-not-admin") return { name: null, reason: "not-admin" };
8307
+ if (nameRes && nameRes.source === "neo4j") return { name: nameRes.joined, reason: null };
8308
+ if (nameRes && nameRes.reason === "neo4j-unreachable") return { name: null, reason: "neo4j-unreachable" };
8309
+ return { name: null, reason: "no-person-name" };
8310
+ }
8311
+
8312
+ // app/lib/whatsapp-reader/resolve-operator-names.ts
8313
+ async function resolveOperatorNamesBySender(users, admins, senderIds) {
8314
+ const out = /* @__PURE__ */ new Map();
8315
+ const distinct = [...new Set([...senderIds].filter((s) => !!s))];
8316
+ await Promise.all(
8317
+ distinct.map(async (senderId) => {
8318
+ const idRes = classifyAdminUserId(users, admins, senderId);
8319
+ const nameRes = idRes.kind === "resolved" ? await loadAdminUserName(idRes.userId) : null;
8320
+ out.set(senderId, resolveOperatorDisplay(idRes, nameRes));
8321
+ })
8322
+ );
8323
+ return out;
8324
+ }
8325
+
8228
8326
  // server/routes/admin/sidebar-sessions.ts
8229
8327
  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
8328
  function lastAssistantModel(body) {
@@ -8261,16 +8359,16 @@ function enumerateJsonls(projectsRoot) {
8261
8359
  const out = [];
8262
8360
  let slugs;
8263
8361
  try {
8264
- slugs = readdirSync6(projectsRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
8362
+ slugs = readdirSync7(projectsRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
8265
8363
  } catch (err) {
8266
8364
  if (err.code === "ENOENT") return out;
8267
8365
  throw err;
8268
8366
  }
8269
8367
  for (const slug of slugs) {
8270
- const slugDir = join12(projectsRoot, slug);
8368
+ const slugDir = join13(projectsRoot, slug);
8271
8369
  let entries;
8272
8370
  try {
8273
- entries = readdirSync6(slugDir, { withFileTypes: true });
8371
+ entries = readdirSync7(slugDir, { withFileTypes: true });
8274
8372
  } catch (err) {
8275
8373
  const code = err.code ?? "unknown";
8276
8374
  console.error(`[admin-sessions-list] slug-skipped slug=${slug} code=${code}`);
@@ -8278,12 +8376,12 @@ function enumerateJsonls(projectsRoot) {
8278
8376
  }
8279
8377
  for (const entry of entries) {
8280
8378
  if (entry.isFile() && SESSION_ID_RE.test(entry.name)) {
8281
- out.push({ path: join12(slugDir, entry.name), isSubagent: false, archived: false });
8379
+ out.push({ path: join13(slugDir, entry.name), isSubagent: false, archived: false });
8282
8380
  } else if (entry.isDirectory() && entry.name === "subagents") {
8283
- const subDir = join12(slugDir, entry.name);
8381
+ const subDir = join13(slugDir, entry.name);
8284
8382
  let subEntries;
8285
8383
  try {
8286
- subEntries = readdirSync6(subDir, { withFileTypes: true });
8384
+ subEntries = readdirSync7(subDir, { withFileTypes: true });
8287
8385
  } catch (err) {
8288
8386
  const code = err.code ?? "unknown";
8289
8387
  console.error(`[admin-sessions-list] subagents-skipped slug=${slug} code=${code}`);
@@ -8291,14 +8389,14 @@ function enumerateJsonls(projectsRoot) {
8291
8389
  }
8292
8390
  for (const sub of subEntries) {
8293
8391
  if (sub.isFile() && SESSION_ID_RE.test(sub.name)) {
8294
- out.push({ path: join12(subDir, sub.name), isSubagent: true, archived: false });
8392
+ out.push({ path: join13(subDir, sub.name), isSubagent: true, archived: false });
8295
8393
  }
8296
8394
  }
8297
8395
  } else if (entry.isDirectory() && entry.name === "archive") {
8298
- const archiveDir = join12(slugDir, entry.name);
8396
+ const archiveDir = join13(slugDir, entry.name);
8299
8397
  let archiveEntries;
8300
8398
  try {
8301
- archiveEntries = readdirSync6(archiveDir, { withFileTypes: true });
8399
+ archiveEntries = readdirSync7(archiveDir, { withFileTypes: true });
8302
8400
  } catch (err) {
8303
8401
  const code = err.code ?? "unknown";
8304
8402
  console.error(`[admin-sessions-list] archive-skipped slug=${slug} code=${code}`);
@@ -8306,7 +8404,7 @@ function enumerateJsonls(projectsRoot) {
8306
8404
  }
8307
8405
  for (const arc of archiveEntries) {
8308
8406
  if (arc.isFile() && SESSION_ID_RE.test(arc.name)) {
8309
- out.push({ path: join12(archiveDir, arc.name), isSubagent: false, archived: true });
8407
+ out.push({ path: join13(archiveDir, arc.name), isSubagent: false, archived: true });
8310
8408
  }
8311
8409
  }
8312
8410
  }
@@ -8339,10 +8437,10 @@ async function fetchLiveSessions() {
8339
8437
  function loadUserTitles(accountDir) {
8340
8438
  const out = /* @__PURE__ */ new Map();
8341
8439
  if (!accountDir) return out;
8342
- const path2 = join12(accountDir, "session-titles.json");
8440
+ const path2 = join13(accountDir, "session-titles.json");
8343
8441
  let raw;
8344
8442
  try {
8345
- raw = readFileSync12(path2, "utf8");
8443
+ raw = readFileSync13(path2, "utf8");
8346
8444
  } catch {
8347
8445
  return out;
8348
8446
  }
@@ -8368,7 +8466,7 @@ function readSidecarMeta(metaPath) {
8368
8466
  const empty = { bridgeIds: [], role: null, channel: null, senderId: null, startedAt: null, personId: null, adminUserId: null, visitorId: null, agentSlug: null, accountId: null };
8369
8467
  let raw;
8370
8468
  try {
8371
- raw = readFileSync12(metaPath, "utf8");
8469
+ raw = readFileSync13(metaPath, "utf8");
8372
8470
  } catch {
8373
8471
  return empty;
8374
8472
  }
@@ -8466,11 +8564,22 @@ app3.get("/", requireAdminSession, async (c) => {
8466
8564
  console.error("[admin-sessions-list] CLAUDE_CONFIG_DIR not set; returning empty list");
8467
8565
  return c.json({ sessions: [], accountId: scopeAccountId });
8468
8566
  }
8469
- const projectsRoot = join12(configDir2, "projects");
8567
+ const projectsRoot = join13(configDir2, "projects");
8470
8568
  const jsonls = enumerateJsonls(projectsRoot);
8471
8569
  const liveSessions = await fetchLiveSessions();
8472
8570
  const accountDir = scopeAccountId ? resolve11(ACCOUNTS_DIR, scopeAccountId) : null;
8473
8571
  const userTitles = loadUserTitles(accountDir);
8572
+ const account = multiAccount ? validAccounts.find((a) => a.accountId === scopeAccountId) ?? null : resolveAccount();
8573
+ let channelUsers = [];
8574
+ if (account) {
8575
+ try {
8576
+ channelUsers = readUsersFile(USERS_FILE) ?? [];
8577
+ } catch (err) {
8578
+ console.error(`[admin-sessions-list] op=users-file-unreadable reason=${err instanceof Error ? err.message : String(err)}`);
8579
+ channelUsers = [];
8580
+ }
8581
+ }
8582
+ const channelAdmins = account?.config.admins ?? [];
8474
8583
  const rows = [];
8475
8584
  const seenIds = /* @__PURE__ */ new Set();
8476
8585
  let liveCount = 0;
@@ -8484,6 +8593,8 @@ app3.get("/", requireAdminSession, async (c) => {
8484
8593
  const titleSourceCounts = { user: 0, ai: 0, "first-message": 0, prefix: 0 };
8485
8594
  const personIdBySession = /* @__PURE__ */ new Map();
8486
8595
  const adminUserIdBySession = /* @__PURE__ */ new Map();
8596
+ const channelSenderBySession = /* @__PURE__ */ new Map();
8597
+ const channelMissNoSender = [];
8487
8598
  for (const { path: path2, isSubagent, archived } of jsonls) {
8488
8599
  const lastSlash = path2.lastIndexOf("/");
8489
8600
  const base = path2.slice(lastSlash + 1);
@@ -8497,8 +8608,8 @@ app3.get("/", requireAdminSession, async (c) => {
8497
8608
  let body;
8498
8609
  let mtimeMs;
8499
8610
  try {
8500
- body = readFileSync12(path2, "utf8");
8501
- mtimeMs = statSync3(path2).mtimeMs;
8611
+ body = readFileSync13(path2, "utf8");
8612
+ mtimeMs = statSync4(path2).mtimeMs;
8502
8613
  } catch {
8503
8614
  continue;
8504
8615
  }
@@ -8506,8 +8617,8 @@ app3.get("/", requireAdminSession, async (c) => {
8506
8617
  const pid = live ? liveSessions.get(sessionId) ?? null : null;
8507
8618
  const resolved = resolveTitle(body, sessionId, userTitles);
8508
8619
  titleSourceCounts[resolved.source] += 1;
8509
- const metaPath = join12(projectDir, `${sessionId}.meta.json`);
8510
- const { bridgeIds, role, channel: sidecarChannel, personId, adminUserId, accountId: rowAccountId } = readSidecarMeta(metaPath);
8620
+ const metaPath = join13(projectDir, `${sessionId}.meta.json`);
8621
+ const { bridgeIds, role, channel: sidecarChannel, senderId, personId, adminUserId, accountId: rowAccountId } = readSidecarMeta(metaPath);
8511
8622
  if (multiAccount) {
8512
8623
  if (rowAccountId === null) untaggedCount += 1;
8513
8624
  if (rowAccountId !== scopeAccountId) continue;
@@ -8539,8 +8650,13 @@ app3.get("/", requireAdminSession, async (c) => {
8539
8650
  channel: sidecarChannel,
8540
8651
  personName: null,
8541
8652
  model: lastAssistantModel(body),
8542
- accountId: rowAccountId
8653
+ accountId: rowAccountId,
8654
+ senderId
8543
8655
  });
8656
+ if ((sidecarChannel === "whatsapp" || sidecarChannel === "telegram") && resolved.source === "prefix") {
8657
+ if (senderId) channelSenderBySession.set(sessionId, senderId);
8658
+ else channelMissNoSender.push({ sessionId, channel: sidecarChannel });
8659
+ }
8544
8660
  if (sidecarChannel === "whatsapp") channelInList.whatsapp += 1;
8545
8661
  else if (sidecarChannel === "telegram") channelInList.telegram += 1;
8546
8662
  else if (sidecarChannel === "webchat") channelInList.webchat += 1;
@@ -8575,9 +8691,29 @@ app3.get("/", requireAdminSession, async (c) => {
8575
8691
  })
8576
8692
  );
8577
8693
  }
8694
+ let channelNamesResolved = 0;
8695
+ const channelNameCandidates = channelSenderBySession.size + channelMissNoSender.length;
8696
+ if (account && channelSenderBySession.size > 0) {
8697
+ const resolvedNames = await resolveOperatorNamesBySender(channelUsers, channelAdmins, channelSenderBySession.values());
8698
+ for (const row of rows) {
8699
+ const rowSenderId = channelSenderBySession.get(row.sessionId);
8700
+ if (!rowSenderId) continue;
8701
+ const r = resolvedNames.get(rowSenderId) ?? { name: null, reason: "no-binding" };
8702
+ if (r.name) {
8703
+ row.personName = r.name;
8704
+ channelNamesResolved += 1;
8705
+ } else {
8706
+ const reason = r.reason === "neo4j-unreachable" ? "neo4j-down" : "classify-miss";
8707
+ console.error(`[admin-sessions-list] op=channel-name-unresolved sessionId=${row.sessionId.slice(0, 8)} channel=${row.channel} senderId=${rowSenderId} reason=${reason}`);
8708
+ }
8709
+ }
8710
+ }
8711
+ for (const m of channelMissNoSender) {
8712
+ console.error(`[admin-sessions-list] op=channel-name-unresolved sessionId=${m.sessionId.slice(0, 8)} channel=${m.channel} senderId= reason=no-senderId`);
8713
+ }
8578
8714
  rows.sort((a, b) => a.startedAt < b.startedAt ? 1 : -1);
8579
8715
  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"}`
8716
+ `[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
8717
  );
8582
8718
  console.log(
8583
8719
  `[admin-sessions-list] accountId=${scopeAccountId ? scopeAccountId.slice(0, 8) : "unset"} scope=${multiAccount ? "scoped" : "all"} rows=${rows.length}`
@@ -8589,71 +8725,6 @@ app3.get("/", requireAdminSession, async (c) => {
8589
8725
  });
8590
8726
  var sidebar_sessions_default = app3;
8591
8727
 
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
8728
  // app/lib/agent-errors.ts
8658
8729
  function classifyAgentError(err) {
8659
8730
  const msg = err instanceof Error ? err.message : String(err);
@@ -9063,19 +9134,16 @@ app4.get("/conversations", requireAdminSession, async (c) => {
9063
9134
  const admins = account?.config.admins ?? [];
9064
9135
  const nameBySender = /* @__PURE__ */ new Map();
9065
9136
  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);
9137
+ if (account) {
9138
+ const resolved = await resolveOperatorNamesBySender(users, admins, distinct);
9139
+ for (const senderId of distinct) {
9140
+ const { name, reason } = resolved.get(senderId) ?? { name: null, reason: "no-binding" };
9075
9141
  if (reason) console.error(`[wa-reader] op=operator-unresolved senderId=${senderId} reason=${reason}`);
9076
9142
  nameBySender.set(senderId, name);
9077
- })
9078
- );
9143
+ }
9144
+ } else {
9145
+ for (const senderId of distinct) nameBySender.set(senderId, null);
9146
+ }
9079
9147
  const sessionConversations = sessionRows.map((r) => ({
9080
9148
  ...r,
9081
9149
  operatorName: r.senderId ? nameBySender.get(r.senderId) ?? null : null
@@ -12399,8 +12467,8 @@ app11.post("/switch-account", requireAdminSession, async (c) => {
12399
12467
  const { userName, avatar } = await resolveUserIdentity(target.accountId, userId);
12400
12468
  const payload = await createAdminSession(target.accountId, target.config.thinkingView, userId, userName, operatorRoleFor(target.config, userId), avatar);
12401
12469
  unregisterSession(oldCacheKey);
12402
- retagAccountForUser(userId, target.accountId);
12403
12470
  console.log(`[sub-account] op=switch userId=${userId.slice(0, 8)} from=${oldCacheKey.slice(0, 8)} to=${target.accountId.slice(0, 8)}`);
12471
+ console.log(`[sub-account] op=rebind cacheKey=${payload.session_key.slice(0, 8)} account=${target.accountId.slice(0, 8)} siblingsTouched=0`);
12404
12472
  return c.json(payload);
12405
12473
  });
12406
12474
  var session_default = app11;
@@ -22627,7 +22695,8 @@ var WaGateway = class {
22627
22695
  serverPath: this.deps.serverPath,
22628
22696
  passive: true,
22629
22697
  text: input.text,
22630
- msgId
22698
+ msgId,
22699
+ ownerSelfPhone: input.ownerSelfPhone
22631
22700
  });
22632
22701
  return;
22633
22702
  }
@@ -22682,7 +22751,10 @@ var WaGateway = class {
22682
22751
  gatewayUrl: this.deps.gatewayUrl,
22683
22752
  serverPath: this.deps.serverPath,
22684
22753
  // Task 1526 — a scheduled firing's per-firing seat id; absent on real inbound.
22685
- sessionIdOverride: input.scheduledSessionId
22754
+ sessionIdOverride: input.scheduledSessionId,
22755
+ // Task 1575 — the gate's owner-self verdict; drives owner attribution
22756
+ // for an unbound owner self-phone at the spawn site.
22757
+ ownerSelfPhone: input.ownerSelfPhone
22686
22758
  });
22687
22759
  } finally {
22688
22760
  this.spawning.delete(input.senderId);
@@ -24846,7 +24918,7 @@ var waGateway = new WaGateway({
24846
24918
  });
24847
24919
  return result.ok ? { ok: true, messageId: result.messageId } : { ok: false, error: result.error };
24848
24920
  },
24849
- ensureChannelSession: async ({ accountId, effectiveAccountId, senderId, role, personId, gatewayUrl, serverPath, passive, text, msgId, sessionIdOverride }) => {
24921
+ ensureChannelSession: async ({ accountId, effectiveAccountId, senderId, role, personId, gatewayUrl, serverPath, passive, text, msgId, sessionIdOverride, ownerSelfPhone }) => {
24850
24922
  if (passive) {
24851
24923
  const phoneHash = createHash7("sha256").update(senderId).digest("hex").slice(0, 12);
24852
24924
  const prompt = (text ?? "").trim();
@@ -24895,9 +24967,10 @@ var waGateway = new WaGateway({
24895
24967
  console.error(`[whatsapp-native] op=account-manager-route senderId=${senderId} managesAccount=${managesAccount} effectiveAccount=${effectiveAccountId} source=gate`);
24896
24968
  }
24897
24969
  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;
24970
+ const userId = role === "admin" ? resolveAdminUserId({ accountId: effectiveAccountId, senderPhone: senderId }) ?? (ownerSelfPhone ? resolveOwnerUserId(effectiveAccountId) ?? void 0 : void 0) : void 0;
24899
24971
  if (role === "admin") {
24900
- console.error(`[whatsapp-native] op=admin-identity senderId=${senderId} userId=${userId ?? "owner-fallback"}`);
24972
+ const attribution = userId ?? (ownerSelfPhone ? "owner-self-unresolved" : "unbound-empty");
24973
+ console.error(`[whatsapp-native] op=admin-identity senderId=${senderId} userId=${attribution}`);
24901
24974
  }
24902
24975
  const result = await managerRcSpawn({ ...req, userId });
24903
24976
  if ("error" in result) {
@@ -25850,6 +25923,73 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
25850
25923
  const loop = setInterval(runReconcile, RECONCILE_INTERVAL_MS);
25851
25924
  loop.unref();
25852
25925
  }
25926
+ {
25927
+ const publishPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join36(__dirname, "..");
25928
+ const publishScript = resolve33(publishPlatformRoot, "plugins/scheduling/mcp/dist/scripts/publish-availability.js");
25929
+ const PUBLISH_INTERVAL_MS = 3e5;
25930
+ const currentAccount = () => {
25931
+ try {
25932
+ return resolveAccount();
25933
+ } catch (err) {
25934
+ console.error(`[calendar-availability] op=publish result=error reason=account-resolve-failed err="${err.message}"`);
25935
+ return null;
25936
+ }
25937
+ };
25938
+ const spawnPublish = (account) => {
25939
+ if (!existsSync35(publishScript)) return;
25940
+ try {
25941
+ const child = spawn3(process.execPath, [publishScript], {
25942
+ env: {
25943
+ ...process.env,
25944
+ PLATFORM_ROOT: publishPlatformRoot,
25945
+ PUBLISH_ACCOUNT_ID: account.accountId,
25946
+ PUBLISH_ACCOUNT_DIR: account.accountDir
25947
+ },
25948
+ stdio: "inherit"
25949
+ });
25950
+ child.unref();
25951
+ child.on("error", (err) => console.error(`[calendar-availability] op=publish result=error reason=spawn-failed err="${err.message}"`));
25952
+ } catch (err) {
25953
+ console.error(`[calendar-availability] op=publish result=error reason=spawn-failed err="${err.message}"`);
25954
+ }
25955
+ };
25956
+ const auditSnapshotAge = (account) => {
25957
+ const availPath = resolve33(account.accountDir, "calendar-availability.json");
25958
+ let hasBookingSite = false;
25959
+ try {
25960
+ if (existsSync35(availPath)) {
25961
+ const cfg = JSON.parse(readFileSync35(availPath, "utf-8"));
25962
+ hasBookingSite = typeof cfg.bookingDbName === "string" && cfg.bookingDbName.length > 0;
25963
+ }
25964
+ } catch {
25965
+ }
25966
+ if (!hasBookingSite) return;
25967
+ const statePath = resolve33(account.accountDir, "state", "booking-availability", "last-publish.json");
25968
+ let lastSuccessAt = null;
25969
+ if (existsSync35(statePath)) {
25970
+ try {
25971
+ const rec = JSON.parse(readFileSync35(statePath, "utf-8"));
25972
+ lastSuccessAt = rec.lastSuccessAt ?? null;
25973
+ } catch {
25974
+ console.error(`[calendar-availability] op=audit accountId=${account.accountId} snapshotAgeMs=na reason=bad-state-file`);
25975
+ return;
25976
+ }
25977
+ }
25978
+ const parsed = lastSuccessAt ? Date.parse(lastSuccessAt) : NaN;
25979
+ const ageMs = Number.isNaN(parsed) ? null : Date.now() - parsed;
25980
+ console.error(`[calendar-availability] op=audit accountId=${account.accountId} snapshotAgeMs=${ageMs ?? "na"}`);
25981
+ };
25982
+ const publishTick = () => {
25983
+ const account = currentAccount();
25984
+ if (!account) return;
25985
+ spawnPublish(account);
25986
+ auditSnapshotAge(account);
25987
+ };
25988
+ const firstPublish = setTimeout(publishTick, 2e4);
25989
+ firstPublish.unref();
25990
+ const publishLoop = setInterval(publishTick, PUBLISH_INTERVAL_MS);
25991
+ publishLoop.unref();
25992
+ }
25853
25993
  startTimeEntryCensus(() => getSession());
25854
25994
  {
25855
25995
  const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join36(__dirname, "..");
@@ -26159,7 +26299,10 @@ init({
26159
26299
  passive: decision.passive,
26160
26300
  text: msg.text,
26161
26301
  reply: msg.reply,
26162
- media: msg.media
26302
+ media: msg.media,
26303
+ // Task 1575 — carry the gate's owner-self verdict to the spawn site so an
26304
+ // unbound owner self-phone attributes to the owner userId.
26305
+ ownerSelfPhone: msg.ownerSelfPhone
26163
26306
  });
26164
26307
  } catch (err) {
26165
26308
  console.error(