@rubytech/create-maxy 1.0.477 → 1.0.479

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 (34) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/neo4j/schema.cypher +34 -6
  3. package/payload/platform/plugins/admin/PLUGIN.md +4 -1
  4. package/payload/platform/plugins/admin/mcp/dist/index.js +221 -1
  5. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  6. package/payload/platform/plugins/anthropic/PLUGIN.md +3 -2
  7. package/payload/platform/plugins/anthropic/references/console-api.md +186 -0
  8. package/payload/platform/plugins/anthropic/references/setup-guide.md +3 -3
  9. package/payload/platform/plugins/anthropic/skills/get-api-key/SKILL.md +89 -20
  10. package/payload/platform/plugins/memory/mcp/dist/index.js +13 -2
  11. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  12. package/payload/platform/plugins/memory/mcp/dist/tools/profile-delete.d.ts +1 -0
  13. package/payload/platform/plugins/memory/mcp/dist/tools/profile-delete.d.ts.map +1 -1
  14. package/payload/platform/plugins/memory/mcp/dist/tools/profile-delete.js +3 -3
  15. package/payload/platform/plugins/memory/mcp/dist/tools/profile-delete.js.map +1 -1
  16. package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.d.ts +1 -0
  17. package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.d.ts.map +1 -1
  18. package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.js +3 -2
  19. package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.js.map +1 -1
  20. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.d.ts +1 -0
  21. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.d.ts.map +1 -1
  22. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js +14 -11
  23. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js.map +1 -1
  24. package/payload/platform/scripts/seed-neo4j.sh +101 -0
  25. package/payload/platform/templates/account.json +2 -1
  26. package/payload/platform/templates/agents/admin/IDENTITY.md +10 -0
  27. package/payload/server/public/assets/ChatInput-D_Am-NZI.css +1 -0
  28. package/payload/server/public/assets/{admin-DpmnCxNk.js → admin-ZfkvBfx_.js} +60 -60
  29. package/payload/server/public/assets/{public-BBxDqQvQ.js → public-C0LOoku3.js} +1 -1
  30. package/payload/server/public/index.html +3 -3
  31. package/payload/server/public/public.html +3 -3
  32. package/payload/server/server.js +252 -126
  33. package/payload/server/public/assets/ChatInput-sDYraTun.css +0 -1
  34. /package/payload/server/public/assets/{ChatInput-DZ0j0Gdp.js → ChatInput-B2MUVSm4.js} +0 -0
@@ -2877,6 +2877,8 @@ if (platformRoot) {
2877
2877
  }
2878
2878
  var MAXY_DIR = resolve(homedir(), configDirName);
2879
2879
  var PIN_FILE = resolve(MAXY_DIR, ".admin-pin");
2880
+ var PLATFORM_ROOT = process.env.MAXY_PLATFORM_ROOT ?? resolve(process.cwd(), "..");
2881
+ var USERS_FILE = resolve(PLATFORM_ROOT, "config", "users.json");
2880
2882
  var LOG_DIR = resolve(MAXY_DIR, "logs");
2881
2883
  var BIN_DIR = resolve(MAXY_DIR, "bin");
2882
2884
  var API_KEY_FILE = resolve(MAXY_DIR, ".anthropic-api-key");
@@ -3144,8 +3146,8 @@ import { spawnSync, execFileSync } from "child_process";
3144
3146
  import { createConnection as createConnection2 } from "net";
3145
3147
  import { mkdirSync, writeFileSync as writeFileSync2 } from "fs";
3146
3148
  import { resolve as resolve2 } from "path";
3147
- var PLATFORM_ROOT = process.env.MAXY_PLATFORM_ROOT ?? resolve2(process.cwd(), "..");
3148
- var VNC_SCRIPT = resolve2(PLATFORM_ROOT, "scripts/vnc.sh");
3149
+ var PLATFORM_ROOT2 = process.env.MAXY_PLATFORM_ROOT ?? resolve2(process.cwd(), "..");
3150
+ var VNC_SCRIPT = resolve2(PLATFORM_ROOT2, "scripts/vnc.sh");
3149
3151
  function sleep(ms) {
3150
3152
  return new Promise((r) => setTimeout(r, ms));
3151
3153
  }
@@ -3277,11 +3279,11 @@ import { randomUUID } from "crypto";
3277
3279
  import { spawn } from "child_process";
3278
3280
  import { readFileSync as readFileSync4 } from "fs";
3279
3281
  import { resolve as resolve3 } from "path";
3280
- var PLATFORM_ROOT2 = process.env.MAXY_PLATFORM_ROOT ?? resolve3(process.cwd(), "..");
3282
+ var PLATFORM_ROOT3 = process.env.MAXY_PLATFORM_ROOT ?? resolve3(process.cwd(), "..");
3281
3283
  var driver = null;
3282
3284
  function readPassword() {
3283
3285
  if (process.env.NEO4J_PASSWORD) return process.env.NEO4J_PASSWORD;
3284
- const passwordFile = resolve3(PLATFORM_ROOT2, "config/.neo4j-password");
3286
+ const passwordFile = resolve3(PLATFORM_ROOT3, "config/.neo4j-password");
3285
3287
  try {
3286
3288
  return readFileSync4(passwordFile, "utf-8").trim();
3287
3289
  } catch {
@@ -3340,7 +3342,7 @@ function cacheConversationId(sessionKey, conversationId) {
3340
3342
  }
3341
3343
  }
3342
3344
  var GREETING_DIRECTIVE = "[New session. Greet the visitor.]";
3343
- async function ensureConversation(accountId, agentType, sessionKey, visitorId, agentSlug) {
3345
+ async function ensureConversation(accountId, agentType, sessionKey, visitorId, agentSlug, userId) {
3344
3346
  const cached2 = getCachedConversationId(sessionKey);
3345
3347
  if (cached2) return cached2;
3346
3348
  const conversationId = randomUUID();
@@ -3354,6 +3356,7 @@ async function ensureConversation(accountId, agentType, sessionKey, visitorId, a
3354
3356
  c.agentType = $agentType,
3355
3357
  ${visitorId ? "c.visitorId = $visitorId," : ""}
3356
3358
  ${agentSlug ? "c.agentSlug = $agentSlug," : ""}
3359
+ ${userId ? "c.userId = $userId," : ""}
3357
3360
  c.createdAt = datetime(),
3358
3361
  c.updatedAt = datetime()
3359
3362
  ON MATCH SET
@@ -3370,13 +3373,14 @@ async function ensureConversation(accountId, agentType, sessionKey, visitorId, a
3370
3373
  accountId,
3371
3374
  agentType,
3372
3375
  ...visitorId ? { visitorId } : {},
3373
- ...agentSlug ? { agentSlug } : {}
3376
+ ...agentSlug ? { agentSlug } : {},
3377
+ ...userId ? { userId } : {}
3374
3378
  }
3375
3379
  );
3376
3380
  const id = result.records[0]?.get("conversationId");
3377
3381
  if (id) {
3378
3382
  cacheConversationId(sessionKey, id);
3379
- console.error(`[persist] Conversation ${id.slice(0, 8)}\u2026 ensured for ${agentType}/${accountId.slice(0, 8)}\u2026`);
3383
+ console.error(`[session] conversation attributed: conversationId=${id.slice(0, 8)}\u2026 userId=${userId ?? "none"} ${agentType}/${accountId.slice(0, 8)}\u2026`);
3380
3384
  }
3381
3385
  return id ?? null;
3382
3386
  } catch (err) {
@@ -3582,17 +3586,18 @@ async function searchMessages(accountId, queryEmbedding, limit = 10) {
3582
3586
  await session.close();
3583
3587
  }
3584
3588
  }
3585
- async function listAdminSessions(accountId, limit = 20) {
3589
+ async function listAdminSessions(accountId, userId, limit = 20) {
3586
3590
  const session = getSession();
3587
3591
  try {
3588
3592
  const result = await session.run(
3589
3593
  `MATCH (c:Conversation {accountId: $accountId, agentType: 'admin'})
3594
+ WHERE c.userId = $userId OR c.userId IS NULL
3590
3595
  RETURN c.conversationId AS conversationId,
3591
3596
  c.name AS name,
3592
3597
  c.updatedAt AS updatedAt
3593
3598
  ORDER BY c.updatedAt DESC
3594
3599
  LIMIT $limit`,
3595
- { accountId, limit: neo4j.int(limit) }
3600
+ { accountId, userId, limit: neo4j.int(limit) }
3596
3601
  );
3597
3602
  return result.records.map((r) => ({
3598
3603
  conversationId: r.get("conversationId"),
@@ -3800,14 +3805,13 @@ var VALID_CATEGORIES = /* @__PURE__ */ new Set([
3800
3805
  "content",
3801
3806
  "interaction"
3802
3807
  ]);
3803
- async function getUserTimezone(accountId) {
3808
+ async function getUserTimezone(accountId, userId) {
3804
3809
  const session = getSession();
3805
3810
  try {
3806
- const result = await session.run(
3807
- `MATCH (up:UserProfile {accountId: $accountId})
3808
- RETURN up.timezone AS timezone`,
3809
- { accountId }
3810
- );
3811
+ const query = userId ? `MATCH (up:UserProfile {accountId: $accountId, userId: $userId})
3812
+ RETURN up.timezone AS timezone` : `MATCH (up:UserProfile {accountId: $accountId})
3813
+ RETURN up.timezone AS timezone LIMIT 1`;
3814
+ const result = await session.run(query, { accountId, userId: userId ?? "" });
3811
3815
  if (result.records.length === 0) return null;
3812
3816
  const tz = result.records[0].get("timezone");
3813
3817
  return tz && tz.trim().length > 0 ? tz : null;
@@ -3818,11 +3822,11 @@ async function getUserTimezone(accountId) {
3818
3822
  await session.close();
3819
3823
  }
3820
3824
  }
3821
- async function loadUserProfile(accountId) {
3825
+ async function loadUserProfile(accountId, userId) {
3822
3826
  const session = getSession();
3823
3827
  try {
3824
- await session.run(
3825
- `MERGE (up:UserProfile {accountId: $accountId})
3828
+ const mergeResult = await session.run(
3829
+ `MERGE (up:UserProfile {accountId: $accountId, userId: $userId})
3826
3830
  ON CREATE SET
3827
3831
  up.createdAt = $now,
3828
3832
  up.updatedAt = $now,
@@ -3832,18 +3836,23 @@ async function loadUserProfile(accountId) {
3832
3836
  OPTIONAL MATCH (b:LocalBusiness {accountId: $accountId})
3833
3837
  FOREACH (_ IN CASE WHEN b IS NOT NULL THEN [1] ELSE [] END |
3834
3838
  MERGE (up)-[:BELONGS_TO]->(b)
3835
- )`,
3836
- { accountId, now: (/* @__PURE__ */ new Date()).toISOString() }
3839
+ )
3840
+ RETURN up.createdAt = $now AS isNew`,
3841
+ { accountId, userId, now: (/* @__PURE__ */ new Date()).toISOString() }
3837
3842
  );
3843
+ const isNew = mergeResult.records[0]?.get("isNew");
3844
+ if (isNew) {
3845
+ console.error(`[profile] created new profile: userId=${userId} accountId=${accountId.slice(0, 8)}\u2026`);
3846
+ }
3838
3847
  const nowMs = Date.now();
3839
3848
  const thresholdMs = DECAY_THRESHOLD_DAYS * 24 * 60 * 60 * 1e3;
3840
3849
  const staleResult = await session.run(
3841
- `MATCH (up:UserProfile {accountId: $accountId})-[:HAS_PREFERENCE]->(pref:Preference)
3850
+ `MATCH (up:UserProfile {accountId: $accountId, userId: $userId})-[:HAS_PREFERENCE]->(pref:Preference)
3842
3851
  WHERE pref.observedAt IS NOT NULL
3843
3852
  RETURN pref.preferenceId AS preferenceId,
3844
3853
  pref.observedAt AS observedAt,
3845
3854
  pref.confidence AS confidence`,
3846
- { accountId }
3855
+ { accountId, userId }
3847
3856
  );
3848
3857
  let decayCount = 0;
3849
3858
  for (const record2 of staleResult.records) {
@@ -3869,7 +3878,7 @@ async function loadUserProfile(accountId) {
3869
3878
  }
3870
3879
  }
3871
3880
  const result = await session.run(
3872
- `MATCH (up:UserProfile {accountId: $accountId})
3881
+ `MATCH (up:UserProfile {accountId: $accountId, userId: $userId})
3873
3882
  OPTIONAL MATCH (up)-[:HAS_PREFERENCE]->(pref:Preference)
3874
3883
  WHERE pref.confidence >= $threshold
3875
3884
  WITH up, pref
@@ -3877,7 +3886,7 @@ async function loadUserProfile(accountId) {
3877
3886
  WITH up, collect(pref) AS allPrefs
3878
3887
  WITH up, allPrefs[0..$limit] AS prefs
3879
3888
  RETURN up, prefs`,
3880
- { accountId, threshold: INJECTION_THRESHOLD, limit: MAX_SUMMARY_PREFERENCES }
3889
+ { accountId, userId, threshold: INJECTION_THRESHOLD, limit: MAX_SUMMARY_PREFERENCES }
3881
3890
  );
3882
3891
  if (result.records.length === 0 || !result.records[0].get("up")) {
3883
3892
  return null;
@@ -3896,7 +3905,7 @@ async function loadUserProfile(accountId) {
3896
3905
  }));
3897
3906
  const summary = formatProfileSummary(profileProps, preferences);
3898
3907
  console.error(
3899
- `[profile] Loaded ${preferences.length} preferences for ${accountId.slice(0, 8)}\u2026 (decay: ${decayCount} updated)`
3908
+ `[profile] loaded for userId=${userId} accountId=${accountId.slice(0, 8)}\u2026 preferences=${preferences.length} (decay: ${decayCount} updated)`
3900
3909
  );
3901
3910
  return summary;
3902
3911
  } catch (err) {
@@ -4083,7 +4092,7 @@ async function loadOnboardingStep(accountId) {
4083
4092
  await session.close();
4084
4093
  }
4085
4094
  }
4086
- async function writeReflectionPreferences(accountId, conversationId, updates) {
4095
+ async function writeReflectionPreferences(accountId, userId, conversationId, updates) {
4087
4096
  if (updates.length === 0) return 0;
4088
4097
  const session = getSession();
4089
4098
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -4135,6 +4144,7 @@ async function writeReflectionPreferences(accountId, conversationId, updates) {
4135
4144
  const props = {
4136
4145
  preferenceId,
4137
4146
  accountId,
4147
+ userId,
4138
4148
  category: update.category,
4139
4149
  key: update.key,
4140
4150
  value: update.value,
@@ -4147,10 +4157,10 @@ async function writeReflectionPreferences(accountId, conversationId, updates) {
4147
4157
  };
4148
4158
  if (embedding2) props.embedding = embedding2;
4149
4159
  await txc.run(
4150
- `MATCH (up:UserProfile {accountId: $accountId})
4160
+ `MATCH (up:UserProfile {accountId: $accountId, userId: $userId})
4151
4161
  CREATE (pref:Preference $props)
4152
4162
  CREATE (up)-[:HAS_PREFERENCE]->(pref)`,
4153
- { accountId, props }
4163
+ { accountId, userId, props }
4154
4164
  );
4155
4165
  for (const convId of allConvIds) {
4156
4166
  await txc.run(
@@ -4183,6 +4193,7 @@ async function writeReflectionPreferences(accountId, conversationId, updates) {
4183
4193
  const mode = update.mode || "reinforce";
4184
4194
  const mergeParams = {
4185
4195
  accountId,
4196
+ userId,
4186
4197
  category: update.category,
4187
4198
  key: update.key,
4188
4199
  newPrefId,
@@ -4196,8 +4207,8 @@ async function writeReflectionPreferences(accountId, conversationId, updates) {
4196
4207
  };
4197
4208
  if (embedding) mergeParams.embedding = embedding;
4198
4209
  const mergeResult = await session.run(
4199
- `MATCH (up:UserProfile {accountId: $accountId})
4200
- MERGE (up)-[:HAS_PREFERENCE]->(pref:Preference {accountId: $accountId, category: $category, key: $key})
4210
+ `MATCH (up:UserProfile {accountId: $accountId, userId: $userId})
4211
+ MERGE (up)-[:HAS_PREFERENCE]->(pref:Preference {accountId: $accountId, userId: $userId, category: $category, key: $key})
4201
4212
  ON CREATE SET
4202
4213
  pref.preferenceId = $newPrefId,
4203
4214
  pref.value = $value,
@@ -4237,13 +4248,13 @@ async function writeReflectionPreferences(accountId, conversationId, updates) {
4237
4248
  }
4238
4249
  if (written > 0) {
4239
4250
  await session.run(
4240
- `MATCH (up:UserProfile {accountId: $accountId})
4251
+ `MATCH (up:UserProfile {accountId: $accountId, userId: $userId})
4241
4252
  SET up.profileVersion = coalesce(up.profileVersion, 0) + 1,
4242
4253
  up.updatedAt = $now`,
4243
- { accountId, now }
4254
+ { accountId, userId, now }
4244
4255
  );
4245
4256
  }
4246
- console.error(`[profile-reflection] Wrote ${written}/${updates.length} preference updates for ${accountId.slice(0, 8)}\u2026`);
4257
+ console.error(`[profile-reflection] Wrote ${written}/${updates.length} preference updates for userId=${userId} accountId=${accountId.slice(0, 8)}\u2026`);
4247
4258
  return written;
4248
4259
  } catch (err) {
4249
4260
  console.error(`[profile-reflection] writeReflectionPreferences failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -4270,11 +4281,11 @@ function agentLogStream(name, accountDir) {
4270
4281
  }
4271
4282
  return createWriteStream(resolve4(logDir, `${name}-${date5}.log`), { flags: "a" });
4272
4283
  }
4273
- var PLATFORM_ROOT3 = process.env.MAXY_PLATFORM_ROOT ?? resolve4(process.cwd(), "..");
4274
- var ACCOUNTS_DIR = resolve4(PLATFORM_ROOT3, "..", "data/accounts");
4275
- if (!existsSync5(PLATFORM_ROOT3)) {
4284
+ var PLATFORM_ROOT4 = process.env.MAXY_PLATFORM_ROOT ?? resolve4(process.cwd(), "..");
4285
+ var ACCOUNTS_DIR = resolve4(PLATFORM_ROOT4, "..", "data/accounts");
4286
+ if (!existsSync5(PLATFORM_ROOT4)) {
4276
4287
  throw new Error(
4277
- `PLATFORM_ROOT does not exist: ${PLATFORM_ROOT3}
4288
+ `PLATFORM_ROOT does not exist: ${PLATFORM_ROOT4}
4278
4289
  Set the MAXY_PLATFORM_ROOT environment variable to the absolute path of the platform directory.`
4279
4290
  );
4280
4291
  }
@@ -4452,7 +4463,7 @@ function resolveAgentConfig(accountDir, agentName) {
4452
4463
  return { model, plugins, status, displayName, image, imageShape, showAgentName, knowledge, knowledgeBaked, liveMemory, knowledgeKeywords, budget, accessMode };
4453
4464
  }
4454
4465
  function parsePluginFrontmatter(pluginDir) {
4455
- const pluginPath = resolve4(PLATFORM_ROOT3, "plugins", pluginDir, "PLUGIN.md");
4466
+ const pluginPath = resolve4(PLATFORM_ROOT4, "plugins", pluginDir, "PLUGIN.md");
4456
4467
  if (!existsSync5(pluginPath)) return null;
4457
4468
  let raw2;
4458
4469
  try {
@@ -4513,7 +4524,7 @@ function parsePluginFrontmatter(pluginDir) {
4513
4524
  };
4514
4525
  }
4515
4526
  function assemblePublicPluginContent(pluginDir) {
4516
- const pluginRoot = resolve4(PLATFORM_ROOT3, "plugins", pluginDir);
4527
+ const pluginRoot = resolve4(PLATFORM_ROOT4, "plugins", pluginDir);
4517
4528
  const pluginPath = resolve4(pluginRoot, "PLUGIN.md");
4518
4529
  let raw2;
4519
4530
  try {
@@ -4607,7 +4618,7 @@ function assemblePublicPluginContent(pluginDir) {
4607
4618
  return { body: parts.join("\n"), skillCount, refCount };
4608
4619
  }
4609
4620
  function loadEmbeddedPlugins(agentType, selectedPlugins, enabledPlugins) {
4610
- const pluginsDir = resolve4(PLATFORM_ROOT3, "plugins");
4621
+ const pluginsDir = resolve4(PLATFORM_ROOT4, "plugins");
4611
4622
  let dirs;
4612
4623
  try {
4613
4624
  dirs = readdirSync(pluginsDir);
@@ -4687,14 +4698,14 @@ var mcpToolsCache = /* @__PURE__ */ new Map();
4687
4698
  function fetchMcpToolsList(pluginDir) {
4688
4699
  const cached2 = mcpToolsCache.get(pluginDir);
4689
4700
  if (cached2) return Promise.resolve(cached2);
4690
- const serverPath = resolve4(PLATFORM_ROOT3, "plugins", pluginDir, "mcp/dist/index.js");
4701
+ const serverPath = resolve4(PLATFORM_ROOT4, "plugins", pluginDir, "mcp/dist/index.js");
4691
4702
  if (!existsSync5(serverPath)) return Promise.resolve([]);
4692
4703
  const startMs = Date.now();
4693
4704
  return new Promise((resolvePromise) => {
4694
4705
  const proc = spawn2(process.execPath, [serverPath], {
4695
4706
  env: {
4696
4707
  ...process.env,
4697
- PLATFORM_ROOT: PLATFORM_ROOT3,
4708
+ PLATFORM_ROOT: PLATFORM_ROOT4,
4698
4709
  ACCOUNT_ID: "__toolslist__",
4699
4710
  PLATFORM_PORT: process.env.PORT ?? "19200"
4700
4711
  }
@@ -4778,7 +4789,7 @@ function fetchMcpToolsList(pluginDir) {
4778
4789
  });
4779
4790
  }
4780
4791
  async function buildPluginManifest(enabledPlugins) {
4781
- const pluginsDir = resolve4(PLATFORM_ROOT3, "plugins");
4792
+ const pluginsDir = resolve4(PLATFORM_ROOT4, "plugins");
4782
4793
  let dirs;
4783
4794
  try {
4784
4795
  dirs = readdirSync(pluginsDir);
@@ -4849,7 +4860,7 @@ async function buildPluginManifest(enabledPlugins) {
4849
4860
  toolLines.push(desc ? ` ${tool.name} \u2014 ${desc}` : ` ${tool.name}`);
4850
4861
  }
4851
4862
  } else if (parsed.tools.length > 0) {
4852
- const serverPath = resolve4(PLATFORM_ROOT3, "plugins", dir, "mcp/dist/index.js");
4863
+ const serverPath = resolve4(PLATFORM_ROOT4, "plugins", dir, "mcp/dist/index.js");
4853
4864
  if (existsSync5(serverPath)) {
4854
4865
  fallbackSourced++;
4855
4866
  console.error(`[plugin-manifest] ${dir}: tools/list empty \u2014 fallback to frontmatter (${parsed.tools.length} tools)`);
@@ -4924,10 +4935,37 @@ function validateComponentData(componentName, data) {
4924
4935
  function getDefaultAccountId() {
4925
4936
  return resolveAccount()?.accountId ?? null;
4926
4937
  }
4938
+ function resolveUserAccounts(userId) {
4939
+ if (!existsSync5(ACCOUNTS_DIR)) return [];
4940
+ const results = [];
4941
+ const entries = readdirSync(ACCOUNTS_DIR, { withFileTypes: true });
4942
+ for (const entry of entries) {
4943
+ if (!entry.isDirectory()) continue;
4944
+ const configPath2 = resolve4(ACCOUNTS_DIR, entry.name, "account.json");
4945
+ if (!existsSync5(configPath2)) continue;
4946
+ let config2;
4947
+ try {
4948
+ config2 = JSON.parse(readFileSync5(configPath2, "utf-8"));
4949
+ } catch {
4950
+ console.error(`[session] account.json corrupt at ${configPath2} \u2014 skipping`);
4951
+ continue;
4952
+ }
4953
+ const adminEntry = config2.admins?.find((a) => a.userId === userId);
4954
+ if (adminEntry) {
4955
+ results.push({
4956
+ accountId: config2.accountId,
4957
+ accountDir: resolve4(ACCOUNTS_DIR, entry.name),
4958
+ config: config2,
4959
+ role: adminEntry.role
4960
+ });
4961
+ }
4962
+ }
4963
+ return results;
4964
+ }
4927
4965
  var sessionStore = /* @__PURE__ */ new Map();
4928
4966
  setSessionStoreRef(sessionStore);
4929
- function registerSession(sessionKey, agentType, accountId, agentName) {
4930
- sessionStore.set(sessionKey, { createdAt: Date.now(), agentType, accountId, agentName });
4967
+ function registerSession(sessionKey, agentType, accountId, agentName, userId, userName) {
4968
+ sessionStore.set(sessionKey, { createdAt: Date.now(), agentType, accountId, agentName, userId, userName });
4931
4969
  }
4932
4970
  function registerResumedSession(sessionKey, accountId, agentName, conversationId, messages) {
4933
4971
  const messageHistory = messages.map((m) => ({
@@ -4996,6 +5034,12 @@ function consumePendingCompactionSummary(sessionKey) {
4996
5034
  function getAccountIdForSession(sessionKey) {
4997
5035
  return sessionStore.get(sessionKey)?.accountId;
4998
5036
  }
5037
+ function getUserIdForSession(sessionKey) {
5038
+ return sessionStore.get(sessionKey)?.userId;
5039
+ }
5040
+ function getUserNameForSession(sessionKey) {
5041
+ return sessionStore.get(sessionKey)?.userName;
5042
+ }
4999
5043
  function registerGrantSession(sessionKey, accountId, agentName, opts) {
5000
5044
  sessionStore.set(sessionKey, {
5001
5045
  createdAt: Date.now(),
@@ -5072,42 +5116,42 @@ function consumeStalledSubagents(sessionKey) {
5072
5116
  delete session.stalledSubagents;
5073
5117
  return stalls && stalls.length > 0 ? stalls : void 0;
5074
5118
  }
5075
- function getMcpServers(accountId, enabledPlugins) {
5119
+ function getMcpServers(accountId, userId, enabledPlugins) {
5076
5120
  const servers = {
5077
5121
  "memory": {
5078
5122
  command: "node",
5079
- args: [resolve4(PLATFORM_ROOT3, "plugins/memory/mcp/dist/index.js")],
5080
- env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT3 }
5123
+ args: [resolve4(PLATFORM_ROOT4, "plugins/memory/mcp/dist/index.js")],
5124
+ env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT4, ...userId ? { USER_ID: userId } : {} }
5081
5125
  },
5082
5126
  "contacts": {
5083
5127
  command: "node",
5084
- args: [resolve4(PLATFORM_ROOT3, "plugins/contacts/mcp/dist/index.js")],
5085
- env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT3 }
5128
+ args: [resolve4(PLATFORM_ROOT4, "plugins/contacts/mcp/dist/index.js")],
5129
+ env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT4 }
5086
5130
  },
5087
5131
  "whatsapp": {
5088
5132
  command: "node",
5089
- args: [resolve4(PLATFORM_ROOT3, "plugins/whatsapp/mcp/dist/index.js")],
5090
- env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT3, PLATFORM_PORT: process.env.PORT ?? "19200" }
5133
+ args: [resolve4(PLATFORM_ROOT4, "plugins/whatsapp/mcp/dist/index.js")],
5134
+ env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT4, PLATFORM_PORT: process.env.PORT ?? "19200" }
5091
5135
  },
5092
5136
  "admin": {
5093
5137
  command: "node",
5094
- args: [resolve4(PLATFORM_ROOT3, "plugins/admin/mcp/dist/index.js")],
5095
- env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT3, PLATFORM_PORT: process.env.PORT ?? "19200" }
5138
+ args: [resolve4(PLATFORM_ROOT4, "plugins/admin/mcp/dist/index.js")],
5139
+ env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT4, PLATFORM_PORT: process.env.PORT ?? "19200", ...userId ? { USER_ID: userId } : {} }
5096
5140
  },
5097
5141
  "scheduling": {
5098
5142
  command: "node",
5099
- args: [resolve4(PLATFORM_ROOT3, "plugins/scheduling/mcp/dist/index.js")],
5100
- env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT3 }
5143
+ args: [resolve4(PLATFORM_ROOT4, "plugins/scheduling/mcp/dist/index.js")],
5144
+ env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT4 }
5101
5145
  },
5102
5146
  "tasks": {
5103
5147
  command: "node",
5104
- args: [resolve4(PLATFORM_ROOT3, "plugins/tasks/mcp/dist/index.js")],
5105
- env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT3 }
5148
+ args: [resolve4(PLATFORM_ROOT4, "plugins/tasks/mcp/dist/index.js")],
5149
+ env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT4 }
5106
5150
  },
5107
5151
  "email": {
5108
5152
  command: "node",
5109
- args: [resolve4(PLATFORM_ROOT3, "plugins/email/mcp/dist/index.js")],
5110
- env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT3 }
5153
+ args: [resolve4(PLATFORM_ROOT4, "plugins/email/mcp/dist/index.js")],
5154
+ env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT4 }
5111
5155
  },
5112
5156
  // Playwright MCP server — browser automation for browser-specialist.
5113
5157
  // Key matches Claude Code's plugin naming: plugin_{plugin}_{server} so tools
@@ -5123,8 +5167,8 @@ function getMcpServers(accountId, enabledPlugins) {
5123
5167
  if (process.env.TELEGRAM_PUBLIC_BOT_TOKEN) {
5124
5168
  servers["telegram"] = {
5125
5169
  command: "node",
5126
- args: [resolve4(PLATFORM_ROOT3, "plugins/telegram/mcp/dist/index.js")],
5127
- env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT3 }
5170
+ args: [resolve4(PLATFORM_ROOT4, "plugins/telegram/mcp/dist/index.js")],
5171
+ env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT4 }
5128
5172
  };
5129
5173
  } else {
5130
5174
  console.error("[plugins] telegram MCP: skipped (no TELEGRAM_PUBLIC_BOT_TOKEN)");
@@ -5141,14 +5185,14 @@ function getMcpServers(accountId, enabledPlugins) {
5141
5185
  if (!tunnelConfigured) {
5142
5186
  servers["cloudflare"] = {
5143
5187
  command: "node",
5144
- args: [resolve4(PLATFORM_ROOT3, "plugins/cloudflare/mcp/dist/index.js")],
5145
- env: { PLATFORM_ROOT: PLATFORM_ROOT3 }
5188
+ args: [resolve4(PLATFORM_ROOT4, "plugins/cloudflare/mcp/dist/index.js")],
5189
+ env: { PLATFORM_ROOT: PLATFORM_ROOT4 }
5146
5190
  };
5147
5191
  } else {
5148
5192
  console.error("[plugins] cloudflare MCP: skipped (tunnel already configured)");
5149
5193
  }
5150
5194
  if (Array.isArray(enabledPlugins) && enabledPlugins.length > 0) {
5151
- const pluginsDir = resolve4(PLATFORM_ROOT3, "plugins");
5195
+ const pluginsDir = resolve4(PLATFORM_ROOT4, "plugins");
5152
5196
  let dirs;
5153
5197
  try {
5154
5198
  dirs = readdirSync(pluginsDir);
@@ -5171,12 +5215,12 @@ function getMcpServers(accountId, enabledPlugins) {
5171
5215
  continue;
5172
5216
  }
5173
5217
  }
5174
- const mcpEntry = resolve4(PLATFORM_ROOT3, "plugins", dir, "mcp/dist/index.js");
5218
+ const mcpEntry = resolve4(PLATFORM_ROOT4, "plugins", dir, "mcp/dist/index.js");
5175
5219
  if (!existsSync5(mcpEntry)) continue;
5176
5220
  servers[dir] = {
5177
5221
  command: "node",
5178
5222
  args: [mcpEntry],
5179
- env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT3 }
5223
+ env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT4 }
5180
5224
  };
5181
5225
  console.log(`[plugins] optional MCP server started: ${dir}`);
5182
5226
  }
@@ -5224,6 +5268,9 @@ var ADMIN_CORE_TOOLS = [
5224
5268
  "mcp__admin__render-component",
5225
5269
  "mcp__admin__session-reset",
5226
5270
  "mcp__admin__session-resume",
5271
+ "mcp__admin__admin-add",
5272
+ "mcp__admin__admin-remove",
5273
+ "mcp__admin__admin-list",
5227
5274
  "mcp__admin__api-key-store",
5228
5275
  "mcp__admin__api-key-verify",
5229
5276
  "mcp__admin__file-attach",
@@ -5265,7 +5312,7 @@ var ADMIN_CORE_TOOLS = [
5265
5312
  function getAdminAllowedTools(enabledPlugins) {
5266
5313
  const tools = [...ADMIN_CORE_TOOLS];
5267
5314
  if (Array.isArray(enabledPlugins) && enabledPlugins.length > 0) {
5268
- const pluginsDir = resolve4(PLATFORM_ROOT3, "plugins");
5315
+ const pluginsDir = resolve4(PLATFORM_ROOT4, "plugins");
5269
5316
  let dirs;
5270
5317
  try {
5271
5318
  dirs = readdirSync(pluginsDir);
@@ -5358,7 +5405,7 @@ ${message.slice(0, QUERY_CLASSIFIER_MSG_CAP)}`
5358
5405
  }
5359
5406
  }
5360
5407
  async function fetchMemoryContext(accountId, query, sessionKey, options) {
5361
- const serverPath = resolve4(PLATFORM_ROOT3, "plugins/memory/mcp/dist/index.js");
5408
+ const serverPath = resolve4(PLATFORM_ROOT4, "plugins/memory/mcp/dist/index.js");
5362
5409
  if (!existsSync5(serverPath)) {
5363
5410
  console.error(`[fetchMemoryContext] MCP server not found: ${serverPath}`);
5364
5411
  return null;
@@ -5369,7 +5416,7 @@ async function fetchMemoryContext(accountId, query, sessionKey, options) {
5369
5416
  env: {
5370
5417
  ...process.env,
5371
5418
  ACCOUNT_ID: accountId,
5372
- PLATFORM_ROOT: PLATFORM_ROOT3,
5419
+ PLATFORM_ROOT: PLATFORM_ROOT4,
5373
5420
  READ_ONLY: "true",
5374
5421
  ALLOWED_SCOPES: "public,shared",
5375
5422
  ...sessionKey ? { SESSION_ID: sessionKey } : {},
@@ -5456,7 +5503,7 @@ async function fetchMemoryContext(accountId, query, sessionKey, options) {
5456
5503
  }
5457
5504
  async function compactTrimmedMessages(accountId, trimmedMessages) {
5458
5505
  if (trimmedMessages.length === 0) return true;
5459
- const serverPath = resolve4(PLATFORM_ROOT3, "plugins/memory/mcp/dist/index.js");
5506
+ const serverPath = resolve4(PLATFORM_ROOT4, "plugins/memory/mcp/dist/index.js");
5460
5507
  if (!existsSync5(serverPath)) return false;
5461
5508
  const briefing = trimmedMessages.map((m) => `[${m.role.toUpperCase()}] ${m.content}`).join("\n\n");
5462
5509
  return new Promise((resolvePromise) => {
@@ -5657,7 +5704,7 @@ Respond with ONLY a JSON array. Each element:
5657
5704
 
5658
5705
  mergeSourceIds is only for mode "merge" \u2014 list the preferenceIds of the sources to combine.
5659
5706
  If no preferences are found, respond with an empty array: []`;
5660
- async function reflectOnSessionProfile(accountId, sessionKey, profileSummary) {
5707
+ async function reflectOnSessionProfile(accountId, userId, sessionKey, profileSummary) {
5661
5708
  const history = getMessageHistory(sessionKey);
5662
5709
  if (history.length === 0) return 0;
5663
5710
  let apiKey = process.env.ANTHROPIC_API_KEY;
@@ -5739,7 +5786,7 @@ Extract preference updates as JSON array.`
5739
5786
  }
5740
5787
  console.error(`[profile-reflection] Extracted ${sanitized.length} preference updates via Haiku (${updates.length - sanitized.length} filtered)`);
5741
5788
  const convId = sessionStore.get(sessionKey)?.conversationId;
5742
- return await writeReflectionPreferences(accountId, convId, sanitized);
5789
+ return await writeReflectionPreferences(accountId, userId, convId, sanitized);
5743
5790
  } catch (err) {
5744
5791
  const reason = err instanceof Error ? err.message : String(err);
5745
5792
  if (err instanceof Error && err.name === "AbortError") {
@@ -5858,7 +5905,7 @@ var COMPACTION_PROMPT = `You are about to reach your context limit. Call session
5858
5905
  Then respond with only: [COMPACTED]`;
5859
5906
  var COMPACTION_TIMEOUT_MS = 45e3;
5860
5907
  async function* runCompactionTurn(accountDir, accountId, systemPrompt, resumeSessionId, adminModel, enabledPlugins) {
5861
- const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, enabledPlugins) });
5908
+ const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, void 0, enabledPlugins) });
5862
5909
  const specialistsDir = resolve4(accountDir, "specialists");
5863
5910
  if (!existsSync5(specialistsDir)) agentLogStream("claude-agent-compaction-stream", accountDir).write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
5864
5911
  `);
@@ -5888,7 +5935,7 @@ async function* runCompactionTurn(accountDir, accountId, systemPrompt, resumeSes
5888
5935
  const proc = spawn2("claude", args, {
5889
5936
  cwd: accountDir,
5890
5937
  stdio: ["ignore", "pipe", "pipe"],
5891
- env: { ...process.env, PLATFORM_ROOT: PLATFORM_ROOT3, ACCOUNT_DIR: accountDir }
5938
+ env: { ...process.env, PLATFORM_ROOT: PLATFORM_ROOT4, ACCOUNT_DIR: accountDir }
5892
5939
  });
5893
5940
  const stderrLog = agentLogStream("claude-agent-compaction-stderr", accountDir);
5894
5941
  proc.stderr?.pipe(stderrLog);
@@ -6497,7 +6544,8 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
6497
6544
  `);
6498
6545
  cdpLog.end();
6499
6546
  }
6500
- const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, enabledPlugins) });
6547
+ const ccUserId = sessionKey ? getUserIdForSession(sessionKey) : void 0;
6548
+ const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, ccUserId, enabledPlugins) });
6501
6549
  const specialistsDir = resolve4(accountDir, "specialists");
6502
6550
  if (!existsSync5(specialistsDir)) agentLogStream("claude-agent-stream", accountDir).write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
6503
6551
  `);
@@ -6529,7 +6577,7 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
6529
6577
  const proc = spawn2("claude", args, {
6530
6578
  cwd: accountDir,
6531
6579
  stdio: ["ignore", "pipe", "pipe"],
6532
- env: { ...process.env, PLATFORM_ROOT: PLATFORM_ROOT3, ACCOUNT_DIR: accountDir }
6580
+ env: { ...process.env, PLATFORM_ROOT: PLATFORM_ROOT4, ACCOUNT_DIR: accountDir }
6533
6581
  });
6534
6582
  const stderrLog = agentLogStream("claude-agent-stderr", accountDir);
6535
6583
  proc.stderr?.pipe(stderrLog);
@@ -6581,10 +6629,13 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
6581
6629
  if (step.value.summary) {
6582
6630
  storePendingCompactionSummary(sessionKey, step.value.summary);
6583
6631
  }
6584
- const profileForReflection = await loadUserProfile(accountId);
6585
- reflectOnSessionProfile(accountId, sessionKey, profileForReflection).catch((err) => {
6586
- console.error(`[profile-reflection] Unhandled error: ${err instanceof Error ? err.message : String(err)}`);
6587
- });
6632
+ const reflectionUserId = getUserIdForSession(sessionKey);
6633
+ if (reflectionUserId) {
6634
+ const profileForReflection = await loadUserProfile(accountId, reflectionUserId);
6635
+ reflectOnSessionProfile(accountId, reflectionUserId, sessionKey, profileForReflection).catch((err) => {
6636
+ console.error(`[profile-reflection] Unhandled error: ${err instanceof Error ? err.message : String(err)}`);
6637
+ });
6638
+ }
6588
6639
  clearAgentSessionId(sessionKey);
6589
6640
  const convId = sessionStore.get(sessionKey)?.conversationId;
6590
6641
  if (convId) {
@@ -6749,7 +6800,8 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
6749
6800
  const cdpOk = await ensureCdp();
6750
6801
  if (!cdpOk) streamLog.write(`[${isoTs()}] [warn] ensureCdp failed \u2014 browser-specialist degraded
6751
6802
  `);
6752
- const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, enabledPlugins) });
6803
+ const managedUserId = getUserIdForSession(sessionKey);
6804
+ const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, managedUserId, enabledPlugins) });
6753
6805
  const specialistsDir = resolve4(accountDir, "specialists");
6754
6806
  if (!existsSync5(specialistsDir)) streamLog.write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
6755
6807
  `);
@@ -6778,7 +6830,7 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
6778
6830
  const proc = spawn2("claude", args, {
6779
6831
  cwd: accountDir,
6780
6832
  stdio: ["ignore", "pipe", "pipe"],
6781
- env: { ...process.env, PLATFORM_ROOT: PLATFORM_ROOT3, ACCOUNT_DIR: accountDir }
6833
+ env: { ...process.env, PLATFORM_ROOT: PLATFORM_ROOT4, ACCOUNT_DIR: accountDir }
6782
6834
  });
6783
6835
  const stderrLog = agentLogStream("claude-agent-stderr", accountDir);
6784
6836
  proc.stderr?.pipe(stderrLog);
@@ -7216,10 +7268,11 @@ ${EXPLANATORY_STYLE_INSTRUCTIONS}` : baseSystemPrompt;
7216
7268
  if (step.value.summary) {
7217
7269
  storePendingCompactionSummary(sessionKey, step.value.summary);
7218
7270
  }
7219
- const agentType = sessionStore.get(sessionKey)?.agentType;
7220
- if (agentType === "admin") {
7221
- const currentProfile = await loadUserProfile(account.accountId);
7222
- reflectOnSessionProfile(account.accountId, sessionKey, currentProfile).catch((err) => {
7271
+ const compactAgentType = sessionStore.get(sessionKey)?.agentType;
7272
+ const compactUserId = getUserIdForSession(sessionKey);
7273
+ if (compactAgentType === "admin" && compactUserId) {
7274
+ const currentProfile = await loadUserProfile(account.accountId, compactUserId);
7275
+ reflectOnSessionProfile(account.accountId, compactUserId, sessionKey, currentProfile).catch((err) => {
7223
7276
  console.error(`[profile-reflection] Unhandled error: ${err instanceof Error ? err.message : String(err)}`);
7224
7277
  });
7225
7278
  }
@@ -7236,11 +7289,13 @@ async function* invokeAgent(config2, message, sessionKey, attachments = [], user
7236
7289
  return;
7237
7290
  }
7238
7291
  const accountId = sessionAccountId ?? account.accountId;
7292
+ const sessionUserId = sessionKey ? getUserIdForSession(sessionKey) : void 0;
7293
+ const sessionUserName = sessionKey ? getUserNameForSession(sessionKey) : void 0;
7239
7294
  const resolvedAgentName = agentName ?? agentType;
7240
7295
  const identity = readIdentity(account.accountDir, resolvedAgentName);
7241
7296
  const rawSoul = readAgentFile(account.accountDir, resolvedAgentName, "SOUL.md");
7242
7297
  const soul = rawSoul && hasSoulContent(rawSoul) ? rawSoul : null;
7243
- console.log(`[invoke-agent] agent=${resolvedAgentName} type=${agentType} session=${sessionKey?.slice(0, 8) ?? "none"} identity=${identity ? `${identity.length}b` : "MISSING"} soul=${rawSoul ? soul ? `${soul.length}b` : "EMPTY(header-only)" : "MISSING"}`);
7298
+ console.log(`[invoke-agent] agent=${resolvedAgentName} type=${agentType} session=${sessionKey?.slice(0, 8) ?? "none"} userId=${sessionUserId ?? "none"} identity=${identity ? `${identity.length}b` : "MISSING"} soul=${rawSoul ? soul ? `${soul.length}b` : "EMPTY(header-only)" : "MISSING"}`);
7244
7299
  const defaultSystemPrompt = agentType === "public" ? "You are a public assistant. Answer only from the memory context provided. Never use training data. British English. Be concise." : "You are an admin agent. Full access. Professional, concise. British English. Be proactive \u2014 tell the user what needs doing and do it.";
7245
7300
  const identityPrompt = identity ?? defaultSystemPrompt;
7246
7301
  let baseSystemPrompt = soul ? `${identityPrompt}
@@ -7248,7 +7303,7 @@ async function* invokeAgent(config2, message, sessionKey, attachments = [], user
7248
7303
  ${soul}` : identityPrompt;
7249
7304
  const now = /* @__PURE__ */ new Date();
7250
7305
  const isoTimestamp = now.toISOString();
7251
- const userTimezone = await getUserTimezone(accountId);
7306
+ const userTimezone = await getUserTimezone(accountId, sessionUserId);
7252
7307
  const effectiveTimezone = userTimezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
7253
7308
  if (!userTimezone) {
7254
7309
  console.error(`[datetime] getUserTimezone returned null for ${accountId.slice(0, 8)}\u2026, falling back to server timezone: ${effectiveTimezone}`);
@@ -7272,16 +7327,23 @@ ${isoTimestamp}
7272
7327
  ${humanReadable} (${effectiveTimezone})
7273
7328
  </datetime>`;
7274
7329
  if (agentType === "admin") {
7275
- const [profileSummary, sessionContext, onboardingStep] = await Promise.all([
7276
- loadUserProfile(accountId),
7277
- loadSessionContext(accountId),
7278
- loadOnboardingStep(accountId)
7279
- ]);
7280
- if (profileSummary) {
7330
+ if (sessionUserId) {
7281
7331
  baseSystemPrompt += `
7282
7332
 
7333
+ <admin-identity>
7334
+ You are talking to ${sessionUserName ?? "an admin"}. Their userId is ${sessionUserId}.
7335
+ </admin-identity>`;
7336
+ const profileSummary = await loadUserProfile(accountId, sessionUserId);
7337
+ if (profileSummary) {
7338
+ baseSystemPrompt += `
7339
+
7283
7340
  ${profileSummary}`;
7341
+ }
7284
7342
  }
7343
+ const [sessionContext, onboardingStep] = await Promise.all([
7344
+ loadSessionContext(accountId),
7345
+ loadOnboardingStep(accountId)
7346
+ ]);
7285
7347
  if (sessionContext) {
7286
7348
  baseSystemPrompt += `
7287
7349
 
@@ -7376,7 +7438,7 @@ ${stallContext}`;
7376
7438
  Current session key: ${sessionKey}` : systemPromptBase;
7377
7439
  if (sessionKey) {
7378
7440
  try {
7379
- await ensureConversation(accountId, agentType, sessionKey);
7441
+ await ensureConversation(accountId, agentType, sessionKey, void 0, void 0, sessionUserId);
7380
7442
  } catch (err) {
7381
7443
  console.error(`[persist] ensureConversation failed in invokeAgent: ${err instanceof Error ? err.message : String(err)}`);
7382
7444
  }
@@ -7606,8 +7668,8 @@ import { randomUUID as randomUUID2 } from "crypto";
7606
7668
  import { mkdir, readFile, stat, writeFile } from "fs/promises";
7607
7669
  import { realpathSync } from "fs";
7608
7670
  import { resolve as resolve6, extname, basename } from "path";
7609
- var PLATFORM_ROOT4 = process.env.MAXY_PLATFORM_ROOT ?? resolve6(process.cwd(), "../platform");
7610
- var ATTACHMENTS_ROOT = resolve6(PLATFORM_ROOT4, "..", "data/uploads");
7671
+ var PLATFORM_ROOT5 = process.env.MAXY_PLATFORM_ROOT ?? resolve6(process.cwd(), "../platform");
7672
+ var ATTACHMENTS_ROOT = resolve6(PLATFORM_ROOT5, "..", "data/uploads");
7611
7673
  var SUPPORTED_MIME_TYPES = /* @__PURE__ */ new Set([
7612
7674
  "image/jpeg",
7613
7675
  "image/png",
@@ -8463,11 +8525,11 @@ import neo4j2 from "neo4j-driver";
8463
8525
  import { readFileSync as readFileSync7 } from "fs";
8464
8526
  import { resolve as resolve7 } from "path";
8465
8527
  import { randomUUID as randomUUID3, randomInt } from "crypto";
8466
- var PLATFORM_ROOT5 = process.env.MAXY_PLATFORM_ROOT ?? resolve7(process.cwd(), "..");
8528
+ var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT ?? resolve7(process.cwd(), "..");
8467
8529
  var driver2 = null;
8468
8530
  function readPassword2() {
8469
8531
  if (process.env.NEO4J_PASSWORD) return process.env.NEO4J_PASSWORD;
8470
- const passwordFile = resolve7(PLATFORM_ROOT5, "config/.neo4j-password");
8532
+ const passwordFile = resolve7(PLATFORM_ROOT6, "config/.neo4j-password");
8471
8533
  try {
8472
8534
  return readFileSync7(passwordFile, "utf-8").trim();
8473
8535
  } catch {
@@ -25619,7 +25681,7 @@ import { readFile as readFile2, stat as stat2 } from "fs/promises";
25619
25681
  import { realpathSync as realpathSync2 } from "fs";
25620
25682
  import { resolve as resolve11, basename as basename2 } from "path";
25621
25683
  var TAG11 = "[whatsapp:api]";
25622
- var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT || "";
25684
+ var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT || "";
25623
25685
  async function POST15(req) {
25624
25686
  try {
25625
25687
  const body = await req.json();
@@ -25628,13 +25690,13 @@ async function POST15(req) {
25628
25690
  if (!to || !filePath) {
25629
25691
  return Response.json({ error: "Missing required fields: to, filePath" }, { status: 400 });
25630
25692
  }
25631
- if (!maxyAccountId || !PLATFORM_ROOT6) {
25693
+ if (!maxyAccountId || !PLATFORM_ROOT7) {
25632
25694
  return Response.json(
25633
25695
  { error: "Cannot validate file path: missing account or platform context" },
25634
25696
  { status: 400 }
25635
25697
  );
25636
25698
  }
25637
- const accountDir = resolve11(PLATFORM_ROOT6, "..", "data/accounts", maxyAccountId);
25699
+ const accountDir = resolve11(PLATFORM_ROOT7, "..", "data/accounts", maxyAccountId);
25638
25700
  let resolvedPath;
25639
25701
  try {
25640
25702
  resolvedPath = realpathSync2(filePath);
@@ -25877,35 +25939,93 @@ function getStoredPinHash() {
25877
25939
  }
25878
25940
  return null;
25879
25941
  }
25942
+ function readUsersFile() {
25943
+ if (!existsSync13(USERS_FILE)) return null;
25944
+ const raw2 = readFileSync13(USERS_FILE, "utf-8").trim();
25945
+ if (!raw2) return [];
25946
+ return JSON.parse(raw2);
25947
+ }
25880
25948
  async function POST19(req) {
25881
- const storedHash = getStoredPinHash();
25882
- if (!storedHash) {
25883
- return Response.json(
25884
- { error: "PIN not configured. Complete onboarding first." },
25885
- { status: 503 }
25886
- );
25887
- }
25888
25949
  let body;
25889
25950
  try {
25890
25951
  body = await req.json();
25891
25952
  } catch {
25892
25953
  return Response.json({ error: "Invalid request" }, { status: 400 });
25893
25954
  }
25955
+ if (!body.pin) {
25956
+ return Response.json({ error: "Invalid request" }, { status: 400 });
25957
+ }
25894
25958
  const inputHash = hashPin2(body.pin);
25895
- if (!body.pin || inputHash !== storedHash) {
25959
+ let users = null;
25960
+ try {
25961
+ users = readUsersFile();
25962
+ } catch (err) {
25963
+ console.error(`[session] users.json corrupt: ${err instanceof Error ? err.message : String(err)}`);
25964
+ return Response.json(
25965
+ { error: "User configuration is corrupt. Re-run the seed script." },
25966
+ { status: 503 }
25967
+ );
25968
+ }
25969
+ if (users !== null) {
25970
+ const matchedUser = users.find((u) => u.pin === inputHash);
25971
+ if (!matchedUser) {
25972
+ console.log(`[session] PIN auth failed: no matching user`);
25973
+ return Response.json({ error: "Invalid PIN" }, { status: 401 });
25974
+ }
25975
+ const { userId, name: userName } = matchedUser;
25976
+ const accounts = resolveUserAccounts(userId);
25977
+ if (accounts.length === 0) {
25978
+ console.log(`[session] user has no accounts: userId=${userId}`);
25979
+ return Response.json(
25980
+ { error: "No account access for this user." },
25981
+ { status: 403 }
25982
+ );
25983
+ }
25984
+ if (accounts.length > 1 && !body.accountId) {
25985
+ console.log(`[session] multi-account user: userId=${userId} accounts=${accounts.length}`);
25986
+ const accountList = await Promise.all(
25987
+ accounts.map(async (a) => {
25988
+ let businessName;
25989
+ try {
25990
+ const branding = await fetchBranding(a.accountId);
25991
+ businessName = branding?.name || void 0;
25992
+ } catch {
25993
+ }
25994
+ return { accountId: a.accountId, businessName, role: a.role };
25995
+ })
25996
+ );
25997
+ return Response.json({ accounts: accountList, userId, userName });
25998
+ }
25999
+ const selected = body.accountId ? accounts.find((a) => a.accountId === body.accountId) : accounts[0];
26000
+ if (!selected) {
26001
+ console.log(`[session] account selection invalid: userId=${userId} requested=${body.accountId}`);
26002
+ return Response.json({ error: "Invalid account selection." }, { status: 403 });
26003
+ }
26004
+ return createAdminSession(selected.accountId, selected.config.thinkingView, userId, userName);
26005
+ }
26006
+ const storedHash = getStoredPinHash();
26007
+ if (!storedHash) {
26008
+ return Response.json(
26009
+ { error: "PIN not configured. Complete onboarding first." },
26010
+ { status: 503 }
26011
+ );
26012
+ }
26013
+ if (inputHash !== storedHash) {
25896
26014
  console.log(`[session] PIN mismatch \u2014 stored=${storedHash.slice(0, 8)}\u2026 input=${inputHash.slice(0, 8)}\u2026 file=${PIN_FILE}`);
25897
26015
  return Response.json({ error: "Invalid PIN" }, { status: 401 });
25898
26016
  }
25899
26017
  const accountId = getDefaultAccountId() ?? "default";
25900
- const sessionKey = crypto.randomUUID();
25901
- registerSession(sessionKey, "admin", accountId);
26018
+ return createAdminSession(accountId);
26019
+ }
26020
+ async function createAdminSession(accountId, thinkingView, userId, userName) {
25902
26021
  const account = resolveAccount();
25903
- const thinkingView = account?.config.thinkingView ?? "default";
26022
+ const effectiveThinkingView = thinkingView ?? account?.config.thinkingView ?? "default";
26023
+ const sessionKey = crypto.randomUUID();
26024
+ registerSession(sessionKey, "admin", accountId, void 0, userId, userName);
25904
26025
  let onboardingComplete = true;
25905
26026
  try {
25906
26027
  const step = await loadOnboardingStep(accountId);
25907
26028
  onboardingComplete = step === null || step >= 7;
25908
- console.log(`[session] accountId=${accountId} onboardingComplete=${onboardingComplete}`);
25909
26029
  } catch (err) {
25910
26030
  console.error(`[session] onboarding query failed: ${err instanceof Error ? err.message : String(err)}`);
25911
26031
  }
@@ -25915,11 +26035,13 @@ async function POST19(req) {
25915
26035
  businessName = branding?.name || void 0;
25916
26036
  } catch {
25917
26037
  }
25918
- console.log(`[session] businessName=${businessName ?? "\u2013"}`);
26038
+ console.log(`[session] admin session created: userId=${userId ?? "\u2013"} userName=${userName ?? "\u2013"} accountId=${accountId}`);
25919
26039
  return Response.json({
25920
26040
  session_key: sessionKey,
25921
26041
  agent_id: "admin",
25922
- thinkingView,
26042
+ userId,
26043
+ userName,
26044
+ thinkingView: effectiveThinkingView,
25923
26045
  onboardingComplete,
25924
26046
  businessName
25925
26047
  });
@@ -26433,10 +26555,10 @@ async function GET8() {
26433
26555
  // app/api/admin/version/route.ts
26434
26556
  import { readFileSync as readFileSync17, existsSync as existsSync17 } from "fs";
26435
26557
  import { resolve as resolve16, join as join7 } from "path";
26436
- var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT ?? resolve16(process.cwd(), "..");
26558
+ var PLATFORM_ROOT8 = process.env.MAXY_PLATFORM_ROOT ?? resolve16(process.cwd(), "..");
26437
26559
  var brandHostname = "maxy";
26438
26560
  var brandNpmPackage = "@rubytech/create-maxy";
26439
- var brandJsonPath = join7(PLATFORM_ROOT7, "config", "brand.json");
26561
+ var brandJsonPath = join7(PLATFORM_ROOT8, "config", "brand.json");
26440
26562
  if (existsSync17(brandJsonPath)) {
26441
26563
  try {
26442
26564
  const brand = JSON.parse(readFileSync17(brandJsonPath, "utf-8"));
@@ -26445,7 +26567,7 @@ if (existsSync17(brandJsonPath)) {
26445
26567
  } catch {
26446
26568
  }
26447
26569
  }
26448
- var VERSION_FILE = resolve16(PLATFORM_ROOT7, `config/.${brandHostname}-version`);
26570
+ var VERSION_FILE = resolve16(PLATFORM_ROOT8, `config/.${brandHostname}-version`);
26449
26571
  var NPM_PACKAGE = brandNpmPackage;
26450
26572
  var REGISTRY_URL = `https://registry.npmjs.org/${NPM_PACKAGE}/latest`;
26451
26573
  var CACHE_TTL_MS = 60 * 60 * 1e3;
@@ -26509,10 +26631,10 @@ async function GET9() {
26509
26631
  import { spawn as spawn4 } from "child_process";
26510
26632
  import { existsSync as existsSync18, statSync as statSync4, writeFileSync as writeFileSync10, readFileSync as readFileSync18, openSync as openSync2, closeSync as closeSync2 } from "fs";
26511
26633
  import { resolve as resolve17, join as join8 } from "path";
26512
- var PLATFORM_ROOT8 = process.env.MAXY_PLATFORM_ROOT ?? resolve17(process.cwd(), "..");
26634
+ var PLATFORM_ROOT9 = process.env.MAXY_PLATFORM_ROOT ?? resolve17(process.cwd(), "..");
26513
26635
  var upgradePkg = "@rubytech/create-maxy";
26514
26636
  var upgradeHostname = "maxy";
26515
- var brandPath = join8(PLATFORM_ROOT8, "config", "brand.json");
26637
+ var brandPath = join8(PLATFORM_ROOT9, "config", "brand.json");
26516
26638
  if (existsSync18(brandPath)) {
26517
26639
  try {
26518
26640
  const brand = JSON.parse(readFileSync18(brandPath, "utf-8"));
@@ -26591,8 +26713,12 @@ async function GET10(req) {
26591
26713
  if (!accountId) {
26592
26714
  return Response.json({ error: "Account not found for session" }, { status: 401 });
26593
26715
  }
26716
+ const userId = getUserIdForSession(sessionKey);
26717
+ if (!userId) {
26718
+ return Response.json({ error: "User identity required \u2014 authenticate with users.json PIN" }, { status: 401 });
26719
+ }
26594
26720
  try {
26595
- const sessions = await listAdminSessions(accountId, 20);
26721
+ const sessions = await listAdminSessions(accountId, userId, 20);
26596
26722
  return Response.json({ sessions });
26597
26723
  } catch (err) {
26598
26724
  console.error(`[sessions-list] Failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -26678,8 +26804,8 @@ async function POST23() {
26678
26804
  }
26679
26805
 
26680
26806
  // server/index.ts
26681
- var PLATFORM_ROOT9 = process.env.MAXY_PLATFORM_ROOT || "";
26682
- var BRAND_JSON_PATH = PLATFORM_ROOT9 ? join9(PLATFORM_ROOT9, "config", "brand.json") : "";
26807
+ var PLATFORM_ROOT10 = process.env.MAXY_PLATFORM_ROOT || "";
26808
+ var BRAND_JSON_PATH = PLATFORM_ROOT10 ? join9(PLATFORM_ROOT10, "config", "brand.json") : "";
26683
26809
  var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", domain: "getmaxy.com" };
26684
26810
  if (BRAND_JSON_PATH && !existsSync19(BRAND_JSON_PATH)) {
26685
26811
  console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);