@rubytech/create-maxy 1.0.678 → 1.0.680

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 (52) hide show
  1. package/dist/index.js +23 -0
  2. package/package.json +1 -1
  3. package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate.test.d.ts +2 -0
  4. package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate.test.d.ts.map +1 -0
  5. package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate.test.js +112 -0
  6. package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate.test.js.map +1 -0
  7. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cache.test.d.ts +2 -0
  8. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cache.test.d.ts.map +1 -0
  9. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cache.test.js +163 -0
  10. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cache.test.js.map +1 -0
  11. package/payload/platform/lib/graph-mcp/dist/cypher-validate.d.ts +38 -0
  12. package/payload/platform/lib/graph-mcp/dist/cypher-validate.d.ts.map +1 -0
  13. package/payload/platform/lib/graph-mcp/dist/cypher-validate.js +130 -0
  14. package/payload/platform/lib/graph-mcp/dist/cypher-validate.js.map +1 -0
  15. package/payload/platform/lib/graph-mcp/dist/index.js +201 -45
  16. package/payload/platform/lib/graph-mcp/dist/index.js.map +1 -1
  17. package/payload/platform/lib/graph-mcp/dist/schema-cache.d.ts +78 -0
  18. package/payload/platform/lib/graph-mcp/dist/schema-cache.d.ts.map +1 -0
  19. package/payload/platform/lib/graph-mcp/dist/schema-cache.js +194 -0
  20. package/payload/platform/lib/graph-mcp/dist/schema-cache.js.map +1 -0
  21. package/payload/platform/lib/graph-mcp/src/__tests__/cypher-validate.test.ts +141 -0
  22. package/payload/platform/lib/graph-mcp/src/__tests__/schema-cache.test.ts +169 -0
  23. package/payload/platform/lib/graph-mcp/src/cypher-validate.ts +157 -0
  24. package/payload/platform/lib/graph-mcp/src/index.ts +247 -47
  25. package/payload/platform/lib/graph-mcp/src/schema-cache.ts +212 -0
  26. package/payload/platform/lib/graph-trash/dist/index.d.ts +8 -0
  27. package/payload/platform/lib/graph-trash/dist/index.d.ts.map +1 -1
  28. package/payload/platform/lib/graph-trash/dist/index.js +109 -14
  29. package/payload/platform/lib/graph-trash/dist/index.js.map +1 -1
  30. package/payload/platform/lib/graph-trash/src/index.ts +136 -21
  31. package/payload/platform/plugins/docs/references/memory-guide.md +5 -1
  32. package/payload/platform/plugins/docs/references/platform.md +1 -1
  33. package/payload/platform/plugins/docs/references/troubleshooting.md +18 -0
  34. package/payload/platform/plugins/memory/PLUGIN.md +1 -0
  35. package/payload/platform/plugins/memory/mcp/dist/index.js +54 -6
  36. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  37. package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.d.ts +36 -0
  38. package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.d.ts.map +1 -0
  39. package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.js +86 -0
  40. package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.js.map +1 -0
  41. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.d.ts +23 -0
  42. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.d.ts.map +1 -1
  43. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js +47 -1
  44. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js.map +1 -1
  45. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.d.ts +58 -0
  46. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.d.ts.map +1 -0
  47. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.js +125 -0
  48. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.js.map +1 -0
  49. package/payload/platform/templates/agents/admin/IDENTITY.md +16 -0
  50. package/payload/server/chunk-3RBKKDHC.js +783 -0
  51. package/payload/server/maxy-edge.js +11 -3
  52. package/payload/server/server.js +284 -112
@@ -13,7 +13,6 @@ import {
13
13
  clearRateLimit,
14
14
  createRemoteSession,
15
15
  hashPassword,
16
- invalidateRemoteSession,
17
16
  isPasswordValid,
18
17
  isRemoteAuthConfigured,
19
18
  recordFailedAttempt,
@@ -25,7 +24,7 @@ import {
25
24
  verifyPassword,
26
25
  verifyRemotePassword,
27
26
  vncLog
28
- } from "./chunk-5YIXIF6C.js";
27
+ } from "./chunk-3RBKKDHC.js";
29
28
 
30
29
  // ../lib/models/dist/index.js
31
30
  var require_dist = __commonJS({
@@ -2895,7 +2894,7 @@ var serveStatic = (options = { root: "" }) => {
2895
2894
  };
2896
2895
 
2897
2896
  // server/index.ts
2898
- import { readFileSync as readFileSync24, existsSync as existsSync23, watchFile } from "fs";
2897
+ import { readFileSync as readFileSync25, existsSync as existsSync23, watchFile } from "fs";
2899
2898
  import { resolve as resolve27, join as join12, basename as basename7 } from "path";
2900
2899
  import { homedir as homedir4 } from "os";
2901
2900
 
@@ -2908,7 +2907,7 @@ import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
2908
2907
  import { randomUUID as randomUUID2 } from "crypto";
2909
2908
  import { resolve as resolve5, join as join3 } from "path";
2910
2909
  import { platform as osPlatform } from "os";
2911
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, readdirSync as readdirSync2, existsSync as existsSync5, mkdirSync as mkdirSync4, createWriteStream, statSync as statSync3, unlinkSync as unlinkSync3, cpSync, rmSync as rmSync2, appendFileSync, openSync as openSync2, readSync as readSync2, closeSync as closeSync2 } from "fs";
2910
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, readdirSync as readdirSync2, existsSync as existsSync5, mkdirSync as mkdirSync4, createWriteStream, statSync as statSync3, unlinkSync as unlinkSync3, cpSync, rmSync as rmSync2, appendFileSync, openSync as openSync2, readSync as readSync2, closeSync as closeSync2 } from "fs";
2912
2911
  import { lookup as dnsLookup } from "dns/promises";
2913
2912
  import { createConnection as netConnect } from "net";
2914
2913
  import { StringDecoder } from "string_decoder";
@@ -5803,6 +5802,101 @@ async function criticAndRecord(opts) {
5803
5802
  }
5804
5803
  }
5805
5804
 
5805
+ // app/lib/admin-schema-block.ts
5806
+ import { readFileSync as readFileSync6 } from "fs";
5807
+ var EDGE_TAXONOMY = [
5808
+ { type: "PART_OF", direction: "(Message)-[:PART_OF]->(Conversation)", note: "Messages belong to exactly one Conversation. Do NOT use :HAS_MESSAGE \u2014 that edge does not exist." },
5809
+ { type: "NEXT", direction: "(Message)-[:NEXT]->(Message)", note: "Chain order within a Conversation. Each Message has at most one NEXT successor." },
5810
+ { type: "BELONGS_TO", direction: "(Conversation|Task|...)-[:BELONGS_TO]->(LocalBusiness)", note: "Account scope. Universal \u2014 almost every tenant-owned node has one." },
5811
+ { type: "ADMIN_OF", direction: "(AdminUser)-[:ADMIN_OF]->(LocalBusiness)", note: "Device-level admin membership (see admin-add / admin-remove)." },
5812
+ { type: "AUTHORED_BY", direction: "(Message)-[:AUTHORED_BY]->(Person)", note: "Message authorship (when the author is an identified Person)." },
5813
+ { type: "HAS_TOOL_CALL", direction: "(Message)-[:HAS_TOOL_CALL]->(ToolCall)", note: "Tool calls attached to an assistant message." },
5814
+ { type: "HAS_RESULT", direction: "(WorkflowStep|ToolCall)-[:HAS_RESULT]->(StepResult)", note: "Workflow step / tool-call outputs." },
5815
+ { type: "HAS_STEP", direction: "(Workflow)-[:HAS_STEP]->(WorkflowStep)", note: "Workflow composition." },
5816
+ { type: "RUN_OF", direction: "(WorkflowRun)-[:RUN_OF]->(Workflow)", note: "Workflow execution instance." },
5817
+ { type: "HAS_SECTION", direction: "(KnowledgeDocument)-[:HAS_SECTION]->(Section)", note: "Document hierarchy." },
5818
+ { type: "HAS_CHUNK", direction: "(Section)-[:HAS_CHUNK]->(Chunk)", note: "Section-to-embeddable-chunk." },
5819
+ { type: "HAS_PREFERENCE", direction: "(Person)-[:HAS_PREFERENCE]->(Preference)", note: "Person-scoped preferences (profile memory)." },
5820
+ { type: "HAS_ACCESS", direction: "(Person)-[:HAS_ACCESS]->(AccessGrant)", note: "Public agent access grants." },
5821
+ { type: "HAS_TASK", direction: "(Person|LocalBusiness)-[:HAS_TASK]->(Task)", note: "Task ownership / assignment." },
5822
+ { type: "HAS_PRICING", direction: "(Service)-[:HAS_PRICING]->(PriceSpecification)", note: "Service pricing." },
5823
+ { type: "HAS_HOURS", direction: "(LocalBusiness)-[:HAS_HOURS]->(OpeningHoursSpecification)", note: "Opening hours." },
5824
+ { type: "HAS_FAQ", direction: "(LocalBusiness)-[:HAS_FAQ]->(Question)", note: "FAQ attached to business." },
5825
+ { type: "HAS_BRAND_ASSET", direction: "(LocalBusiness)-[:HAS_BRAND_ASSET]->(DigitalDocument|ImageObject)", note: "Branding assets." },
5826
+ { type: "OFFERS", direction: "(LocalBusiness)-[:OFFERS]->(Service)", note: "Service catalogue." },
5827
+ { type: "CONTAINS", direction: "(KnowledgeDocument)-[:CONTAINS]->(Chunk)", note: "Flat document-to-chunk (alternative to HAS_SECTION then HAS_CHUNK)." },
5828
+ { type: "REFERENCES", direction: "(Message|KnowledgeDocument)-[:REFERENCES]->(*)", note: "Soft reference link." },
5829
+ { type: "ABOUT", direction: "(Review|Message)-[:ABOUT]->(*)", note: "Subject pointer." },
5830
+ { type: "REPLY_TO", direction: "(Email)-[:REPLY_TO]->(Email)", note: "Email threading." },
5831
+ { type: "RECEIVED_BY", direction: "(Email)-[:RECEIVED_BY]->(Person)", note: "Email recipient." },
5832
+ { type: "RAISED_BY", direction: "(ReviewAlert)-[:RAISED_BY]->(*)", note: "Alert provenance." },
5833
+ { type: "AFFECTS", direction: "(ReviewAlert)-[:AFFECTS]->(*)", note: "Alert impact surface." },
5834
+ { type: "BLOCKS", direction: "(Task)-[:BLOCKS]->(Task)", note: "Task dependency." },
5835
+ { type: "OBSERVED_IN", direction: "(*)-[:OBSERVED_IN]->(Conversation)", note: "Observation provenance." }
5836
+ ];
5837
+ var SUBLABELS = [
5838
+ { base: "Conversation", sub: "AdminConversation", note: "Admin-only conversation (admin chat, not public)." },
5839
+ { base: "Conversation", sub: "PublicConversation", note: "Public agent conversation (visitor-facing)." },
5840
+ { base: "Message", sub: "UserMessage", note: "Message authored by the human user." },
5841
+ { base: "Message", sub: "AssistantMessage", note: "Message authored by the assistant." }
5842
+ ];
5843
+ var LABEL_PATTERN = /FOR\s*\(\s*\w+\s*:\s*(\w+)\s*\)/g;
5844
+ function parseLabelsFromSchemaCypher(schemaCypher) {
5845
+ const found = /* @__PURE__ */ new Set();
5846
+ for (const match2 of schemaCypher.matchAll(LABEL_PATTERN)) {
5847
+ found.add(match2[1]);
5848
+ }
5849
+ return [...found].sort();
5850
+ }
5851
+ function buildSchemaBlock(schemaCypherText) {
5852
+ const baseLabels = parseLabelsFromSchemaCypher(schemaCypherText);
5853
+ const sublabels = SUBLABELS.map((s) => s.sub);
5854
+ const lines = [];
5855
+ lines.push("# SCHEMA (Neo4j graph, canonical reference)");
5856
+ lines.push("");
5857
+ lines.push("The Neo4j instance backing this admin session contains exactly the labels and relationship types below. Never invent names outside this list. If you need cypher against a token not listed here, first invoke `maxy-graph-get_neo4j_schema` to confirm it exists \u2014 do not guess.");
5858
+ lines.push("");
5859
+ lines.push("## Labels (from platform/neo4j/schema.cypher)");
5860
+ lines.push("");
5861
+ for (const label of baseLabels) {
5862
+ lines.push(`- :${label}`);
5863
+ }
5864
+ lines.push("");
5865
+ lines.push("### Sublabels (Task 633 role-cue variants)");
5866
+ lines.push("");
5867
+ for (const s of SUBLABELS) {
5868
+ lines.push(`- :${s.sub} (on :${s.base}) \u2014 ${s.note}`);
5869
+ }
5870
+ lines.push("");
5871
+ lines.push("## Relationship types (from .docs/neo4j.md)");
5872
+ lines.push("");
5873
+ for (const e of EDGE_TAXONOMY) {
5874
+ lines.push(`- :${e.type} \u2014 ${e.direction} \u2014 ${e.note}`);
5875
+ }
5876
+ lines.push("");
5877
+ lines.push("The cypher MCP validator (graph-mcp proxy, Task 654) rejects write cypher that references unknown labels or edges and warns on read cypher. Stick to the taxonomy above; if you believe a token is missing, invoke `maxy-graph-get_neo4j_schema` for the live snapshot.");
5878
+ const text = lines.join("\n");
5879
+ return {
5880
+ text,
5881
+ labelCount: baseLabels.length + sublabels.length,
5882
+ relationshipTypeCount: EDGE_TAXONOMY.length,
5883
+ bytes: Buffer.byteLength(text, "utf-8")
5884
+ };
5885
+ }
5886
+ function loadAdminSchemaBlock(schemaCypherPath) {
5887
+ let text;
5888
+ try {
5889
+ text = readFileSync6(schemaCypherPath, "utf-8");
5890
+ } catch (err) {
5891
+ const msg = err instanceof Error ? err.message : String(err);
5892
+ console.error(
5893
+ `[admin-identity] schema.cypher unreadable path=${schemaCypherPath} error="${msg.replace(/"/g, "'")}" block=omitted`
5894
+ );
5895
+ return null;
5896
+ }
5897
+ return buildSchemaBlock(text);
5898
+ }
5899
+
5806
5900
  // app/lib/claude-agent.ts
5807
5901
  var LOG_RETENTION_DAYS = 7;
5808
5902
  var BROWSER_TOOL_PREFIXES = [
@@ -6063,7 +6157,7 @@ function sampleProcState(pid) {
6063
6157
  let sockets2 = 0;
6064
6158
  for (const tcpFile of ["/proc/" + pid + "/net/tcp", "/proc/" + pid + "/net/tcp6"]) {
6065
6159
  try {
6066
- const raw2 = readFileSync6(tcpFile, "utf-8");
6160
+ const raw2 = readFileSync7(tcpFile, "utf-8");
6067
6161
  const lines2 = raw2.split("\n");
6068
6162
  for (let i = 1; i < lines2.length; i++) {
6069
6163
  const line = lines2[i].trim();
@@ -6079,7 +6173,7 @@ function sampleProcState(pid) {
6079
6173
  }
6080
6174
  let rssMb = 0;
6081
6175
  try {
6082
- const statm = readFileSync6(`/proc/${pid}/statm`, "utf-8").trim().split(/\s+/);
6176
+ const statm = readFileSync7(`/proc/${pid}/statm`, "utf-8").trim().split(/\s+/);
6083
6177
  const rssPages = parseInt(statm[1] ?? "0", 10);
6084
6178
  if (Number.isFinite(rssPages)) rssMb = Math.round(rssPages * 4096 / (1024 * 1024));
6085
6179
  } catch {
@@ -6126,7 +6220,7 @@ function resolveAccount() {
6126
6220
  let usersJsonUserId = null;
6127
6221
  if (existsSync5(usersFilePath)) {
6128
6222
  try {
6129
- const raw2 = readFileSync6(usersFilePath, "utf-8").trim();
6223
+ const raw2 = readFileSync7(usersFilePath, "utf-8").trim();
6130
6224
  if (raw2) {
6131
6225
  const users = JSON.parse(raw2);
6132
6226
  if (users.length > 0) {
@@ -6142,7 +6236,7 @@ function resolveAccount() {
6142
6236
  if (!entry.isDirectory()) continue;
6143
6237
  const configPath2 = resolve5(ACCOUNTS_DIR, entry.name, "account.json");
6144
6238
  if (!existsSync5(configPath2)) continue;
6145
- const raw2 = readFileSync6(configPath2, "utf-8");
6239
+ const raw2 = readFileSync7(configPath2, "utf-8");
6146
6240
  let config;
6147
6241
  try {
6148
6242
  config = JSON.parse(raw2);
@@ -6177,7 +6271,7 @@ function resolveAccount() {
6177
6271
  function readAgentFile(accountDir, agentName, filename) {
6178
6272
  const filePath = resolve5(accountDir, "agents", agentName, filename);
6179
6273
  if (!existsSync5(filePath)) return null;
6180
- return readFileSync6(filePath, "utf-8");
6274
+ return readFileSync7(filePath, "utf-8");
6181
6275
  }
6182
6276
  function readIdentity(accountDir, agentName) {
6183
6277
  return readAgentFile(accountDir, agentName, "IDENTITY.md");
@@ -6219,7 +6313,7 @@ function resolveDefaultAgentSlug(accountDir) {
6219
6313
  }
6220
6314
  let config;
6221
6315
  try {
6222
- config = JSON.parse(readFileSync6(configPath2, "utf-8"));
6316
+ config = JSON.parse(readFileSync7(configPath2, "utf-8"));
6223
6317
  } catch (err) {
6224
6318
  console.error("[agent-resolve] failed to read account.json:", err);
6225
6319
  return null;
@@ -6310,14 +6404,14 @@ function resolveAgentConfig(accountDir, agentName) {
6310
6404
  const knowledgeMtime = statSync3(knowledgePath).mtimeMs;
6311
6405
  const summaryMtime = statSync3(summaryPath).mtimeMs;
6312
6406
  if (summaryMtime >= knowledgeMtime) {
6313
- knowledge = readFileSync6(summaryPath, "utf-8");
6407
+ knowledge = readFileSync7(summaryPath, "utf-8");
6314
6408
  } else {
6315
6409
  console.warn(`[agent-config] ${agentName}: KNOWLEDGE-SUMMARY.md is stale (KNOWLEDGE.md is newer) \u2014 using full knowledge`);
6316
- knowledge = readFileSync6(knowledgePath, "utf-8");
6410
+ knowledge = readFileSync7(knowledgePath, "utf-8");
6317
6411
  }
6318
6412
  knowledgeBaked = true;
6319
6413
  } else if (hasKnowledge) {
6320
- knowledge = readFileSync6(knowledgePath, "utf-8");
6414
+ knowledge = readFileSync7(knowledgePath, "utf-8");
6321
6415
  knowledgeBaked = true;
6322
6416
  }
6323
6417
  let budget = null;
@@ -6343,7 +6437,7 @@ function parsePluginFrontmatter(pluginDir) {
6343
6437
  if (!existsSync5(pluginPath)) return null;
6344
6438
  let raw2;
6345
6439
  try {
6346
- raw2 = readFileSync6(pluginPath, "utf-8");
6440
+ raw2 = readFileSync7(pluginPath, "utf-8");
6347
6441
  } catch {
6348
6442
  console.warn(`[plugins] cannot read ${pluginPath}`);
6349
6443
  return null;
@@ -6419,7 +6513,7 @@ function autoDeliverPremiumPlugins(purchasedPlugins) {
6419
6513
  if (isBundle) {
6420
6514
  let bundleRaw;
6421
6515
  try {
6422
- bundleRaw = readFileSync6(bundlePath, "utf-8");
6516
+ bundleRaw = readFileSync7(bundlePath, "utf-8");
6423
6517
  } catch (err) {
6424
6518
  console.log(`${TAG19} ${pluginName}: cannot read BUNDLE.md \u2014 ${err instanceof Error ? err.message : String(err)}`);
6425
6519
  continue;
@@ -6512,7 +6606,7 @@ function migratePluginRenames(accountDir, config) {
6512
6606
  if (!changed) return;
6513
6607
  const configPath2 = resolve5(accountDir, "account.json");
6514
6608
  try {
6515
- const raw2 = readFileSync6(configPath2, "utf-8");
6609
+ const raw2 = readFileSync7(configPath2, "utf-8");
6516
6610
  const parsed = JSON.parse(raw2);
6517
6611
  parsed.enabledPlugins = migrated;
6518
6612
  writeFileSync5(configPath2, JSON.stringify(parsed, null, 2) + "\n");
@@ -6546,7 +6640,7 @@ function autoDeliverBundleAgents(accountDir, purchasedPlugins) {
6546
6640
  const agentsmdPath = resolve5(accountDir, "agents", "admin", "AGENTS.md");
6547
6641
  let agentsmd = "";
6548
6642
  try {
6549
- agentsmd = existsSync5(agentsmdPath) ? readFileSync6(agentsmdPath, "utf-8") : "";
6643
+ agentsmd = existsSync5(agentsmdPath) ? readFileSync7(agentsmdPath, "utf-8") : "";
6550
6644
  } catch {
6551
6645
  }
6552
6646
  let delivered = 0;
@@ -6570,7 +6664,7 @@ function autoDeliverBundleAgents(accountDir, purchasedPlugins) {
6570
6664
  continue;
6571
6665
  }
6572
6666
  try {
6573
- const content = readFileSync6(target, "utf-8");
6667
+ const content = readFileSync7(target, "utf-8");
6574
6668
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
6575
6669
  if (fmMatch) {
6576
6670
  const nameMatch = fmMatch[1].match(/^name:\s*(.+)/m);
@@ -6606,7 +6700,7 @@ function assemblePublicPluginContent(pluginDir) {
6606
6700
  const pluginPath = resolve5(pluginRoot, "PLUGIN.md");
6607
6701
  let raw2;
6608
6702
  try {
6609
- raw2 = readFileSync6(pluginPath, "utf-8");
6703
+ raw2 = readFileSync7(pluginPath, "utf-8");
6610
6704
  } catch {
6611
6705
  return null;
6612
6706
  }
@@ -6627,7 +6721,7 @@ function assemblePublicPluginContent(pluginDir) {
6627
6721
  const skillMdPath = resolve5(skillDir, "SKILL.md");
6628
6722
  let skillRaw;
6629
6723
  try {
6630
- skillRaw = readFileSync6(skillMdPath, "utf-8");
6724
+ skillRaw = readFileSync7(skillMdPath, "utf-8");
6631
6725
  } catch {
6632
6726
  continue;
6633
6727
  }
@@ -6678,7 +6772,7 @@ function assemblePublicPluginContent(pluginDir) {
6678
6772
  }
6679
6773
  for (const refFile of refFiles) {
6680
6774
  try {
6681
- const refContent = readFileSync6(resolve5(refsDir, refFile), "utf-8").trim();
6775
+ const refContent = readFileSync7(resolve5(refsDir, refFile), "utf-8").trim();
6682
6776
  if (refContent) {
6683
6777
  parts.push(`
6684
6778
  <!-- reference: ${refFile} -->`);
@@ -6752,7 +6846,7 @@ function loadEmbeddedPlugins(agentType, selectedPlugins, enabledPlugins) {
6752
6846
  const pluginPath = resolve5(pluginsDir, dir, "PLUGIN.md");
6753
6847
  let raw2;
6754
6848
  try {
6755
- raw2 = readFileSync6(pluginPath, "utf-8");
6849
+ raw2 = readFileSync7(pluginPath, "utf-8");
6756
6850
  } catch (err) {
6757
6851
  console.warn(`[plugins] ${dir}: failed to read PLUGIN.md for ${agentType} embed: ${String(err)}`);
6758
6852
  continue;
@@ -7098,7 +7192,7 @@ function resolveUserAccounts(userId) {
7098
7192
  if (!existsSync5(configPath2)) continue;
7099
7193
  let config;
7100
7194
  try {
7101
- config = JSON.parse(readFileSync6(configPath2, "utf-8"));
7195
+ config = JSON.parse(readFileSync7(configPath2, "utf-8"));
7102
7196
  } catch {
7103
7197
  console.error(`[session] account.json corrupt at ${configPath2} \u2014 skipping`);
7104
7198
  continue;
@@ -7414,7 +7508,7 @@ function readBrandHostname() {
7414
7508
  if (cachedBrandHostname !== null) return cachedBrandHostname;
7415
7509
  try {
7416
7510
  const brandPath = resolve5(PLATFORM_ROOT3, "config", "brand.json");
7417
- const parsed = JSON.parse(readFileSync6(brandPath, "utf-8"));
7511
+ const parsed = JSON.parse(readFileSync7(brandPath, "utf-8"));
7418
7512
  cachedBrandHostname = typeof parsed.hostname === "string" && parsed.hostname.length > 0 ? parsed.hostname : "maxy";
7419
7513
  } catch {
7420
7514
  cachedBrandHostname = "maxy";
@@ -10219,7 +10313,7 @@ ${sessionContext}`;
10219
10313
  const skillPath = resolve5(PLATFORM_ROOT3, "plugins/admin/skills/onboarding/SKILL.md");
10220
10314
  let skillContent = "";
10221
10315
  try {
10222
- skillContent = readFileSync6(skillPath, "utf-8");
10316
+ skillContent = readFileSync7(skillPath, "utf-8");
10223
10317
  } catch (err) {
10224
10318
  console.error(`[onboarding-inject] accountId=${accountId.slice(0, 8)}\u2026 error=skill-read-failed path=${skillPath} reason=${err instanceof Error ? err.message : String(err)}`);
10225
10319
  }
@@ -10271,13 +10365,23 @@ ${manifest}`;
10271
10365
  }
10272
10366
  const graphRefPath = resolve5(PLATFORM_ROOT3, "plugins/memory/references/graph-primitives.md");
10273
10367
  try {
10274
- const graphRef = readFileSync6(graphRefPath, "utf-8");
10368
+ const graphRef = readFileSync7(graphRefPath, "utf-8");
10275
10369
  baseSystemPrompt += `
10276
10370
 
10277
10371
  ${graphRef}`;
10278
10372
  } catch (err) {
10279
10373
  console.error(`[graph-primitives] reference missing at ${graphRefPath} \u2014 admin session will have no Cypher cookbook: ${err instanceof Error ? err.message : String(err)}`);
10280
10374
  }
10375
+ const schemaCypherPath = resolve5(PLATFORM_ROOT3, "neo4j/schema.cypher");
10376
+ const schemaBlock = loadAdminSchemaBlock(schemaCypherPath);
10377
+ if (schemaBlock) {
10378
+ baseSystemPrompt += `
10379
+
10380
+ ${schemaBlock.text}`;
10381
+ console.log(
10382
+ `[admin-identity] schemaBlockBytes=${schemaBlock.bytes} labels=${schemaBlock.labelCount} relationshipTypes=${schemaBlock.relationshipTypeCount}`
10383
+ );
10384
+ }
10281
10385
  }
10282
10386
  if (agentConfig?.budget) {
10283
10387
  const pluginTokens = embeddedPlugins.reduce((sum, p) => sum + estimateTokens(p.body), 0);
@@ -10486,7 +10590,7 @@ var clientIpMiddleware = async (c, next) => {
10486
10590
  };
10487
10591
 
10488
10592
  // server/routes/health.ts
10489
- import { existsSync as existsSync10, readFileSync as readFileSync11 } from "fs";
10593
+ import { existsSync as existsSync10, readFileSync as readFileSync12 } from "fs";
10490
10594
  import { createConnection as createConnection2 } from "net";
10491
10595
 
10492
10596
  // app/lib/network.ts
@@ -10511,7 +10615,7 @@ function getLanIp() {
10511
10615
  import { basename as basename2 } from "path";
10512
10616
 
10513
10617
  // app/lib/review-detector/rules.ts
10514
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync6, statSync as statSync4, mkdirSync as mkdirSync5, renameSync as renameSync2 } from "fs";
10618
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync6, statSync as statSync4, mkdirSync as mkdirSync5, renameSync as renameSync2 } from "fs";
10515
10619
  import { resolve as resolve6, dirname as dirname3 } from "path";
10516
10620
  var DEFAULT_SCAN_INTERVAL_MS = 5e3;
10517
10621
  var RATE_LIMIT_PATTERN = "rate[- ]?limit(?:ed| reached| hit)|(?:HTTP|status)[^a-z]{0,3}429|too many requests";
@@ -10959,7 +11063,7 @@ function loadRules(configDir2) {
10959
11063
  if (!existsSync6(path2)) {
10960
11064
  throw new Error(`rules file missing at ${path2}`);
10961
11065
  }
10962
- const raw2 = readFileSync7(path2, "utf-8");
11066
+ const raw2 = readFileSync8(path2, "utf-8");
10963
11067
  let parsed;
10964
11068
  try {
10965
11069
  parsed = JSON.parse(raw2);
@@ -11124,7 +11228,7 @@ function validateRule(input, label, seenIds) {
11124
11228
  }
11125
11229
 
11126
11230
  // app/lib/review-detector/sources.ts
11127
- import { existsSync as existsSync7, readdirSync as readdirSync3, statSync as statSync5, writeFileSync as writeFileSync7, renameSync as renameSync3, mkdirSync as mkdirSync6, openSync as openSync3, readSync as readSync3, closeSync as closeSync3, readFileSync as readFileSync8 } from "fs";
11231
+ import { existsSync as existsSync7, readdirSync as readdirSync3, statSync as statSync5, writeFileSync as writeFileSync7, renameSync as renameSync3, mkdirSync as mkdirSync6, openSync as openSync3, readSync as readSync3, closeSync as closeSync3, readFileSync as readFileSync9 } from "fs";
11128
11232
  import { resolve as resolve7, join as join4, basename, dirname as dirname4 } from "path";
11129
11233
  function tailStatePath(configDir2) {
11130
11234
  return resolve7(configDir2, "review-state.json");
@@ -11133,7 +11237,7 @@ function loadTailState(configDir2) {
11133
11237
  const path2 = tailStatePath(configDir2);
11134
11238
  if (!existsSync7(path2)) return {};
11135
11239
  try {
11136
- const raw2 = readFileSync8(path2, "utf-8");
11240
+ const raw2 = readFileSync9(path2, "utf-8");
11137
11241
  const parsed = JSON.parse(raw2);
11138
11242
  if (!parsed || typeof parsed !== "object") return {};
11139
11243
  const clean = {};
@@ -11303,7 +11407,7 @@ function sourceKey(file) {
11303
11407
  }
11304
11408
 
11305
11409
  // app/lib/review-detector/writer.ts
11306
- import { appendFileSync as appendFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync8, renameSync as renameSync4, statSync as statSync6 } from "fs";
11410
+ import { appendFileSync as appendFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync8, renameSync as renameSync4, statSync as statSync6 } from "fs";
11307
11411
  import { resolve as resolve8, dirname as dirname5 } from "path";
11308
11412
  import { randomUUID as randomUUID3 } from "crypto";
11309
11413
  function reviewLogPath(configDir2) {
@@ -11442,7 +11546,7 @@ function queueAlert(configDir2, accountId, match2) {
11442
11546
  async function drainPendingAlerts(configDir2) {
11443
11547
  const path2 = pendingAlertsPath(configDir2);
11444
11548
  if (!existsSync8(path2)) return { drained: 0, remaining: 0 };
11445
- const raw2 = readFileSync9(path2, "utf-8");
11549
+ const raw2 = readFileSync10(path2, "utf-8");
11446
11550
  const lines = raw2.split("\n").filter((l) => l.trim().length > 0);
11447
11551
  if (lines.length === 0) return { drained: 0, remaining: 0 };
11448
11552
  const remaining = [];
@@ -12105,7 +12209,7 @@ var WhatsAppConfigSchema = z.object({
12105
12209
  });
12106
12210
 
12107
12211
  // app/lib/whatsapp/config-persist.ts
12108
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync9, existsSync as existsSync9 } from "fs";
12212
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync9, existsSync as existsSync9 } from "fs";
12109
12213
  import { resolve as resolve10, join as join5 } from "path";
12110
12214
  var TAG3 = "[whatsapp:config]";
12111
12215
  function configPath(accountDir) {
@@ -12114,7 +12218,7 @@ function configPath(accountDir) {
12114
12218
  function readConfig(accountDir) {
12115
12219
  const path2 = configPath(accountDir);
12116
12220
  if (!existsSync9(path2)) throw new Error(`account.json not found at ${path2}`);
12117
- return JSON.parse(readFileSync10(path2, "utf-8"));
12221
+ return JSON.parse(readFileSync11(path2, "utf-8"));
12118
12222
  }
12119
12223
  function writeConfig(accountDir, config) {
12120
12224
  const path2 = configPath(accountDir);
@@ -14406,7 +14510,7 @@ app.get("/", async (c) => {
14406
14510
  let pinConfigured = false;
14407
14511
  try {
14408
14512
  if (existsSync10(USERS_FILE)) {
14409
- const raw2 = readFileSync11(USERS_FILE, "utf-8").trim();
14513
+ const raw2 = readFileSync12(USERS_FILE, "utf-8").trim();
14410
14514
  if (raw2) {
14411
14515
  const users = JSON.parse(raw2);
14412
14516
  pinConfigured = Array.isArray(users) && users.length > 0;
@@ -15523,7 +15627,7 @@ var group_default = app4;
15523
15627
 
15524
15628
  // app/lib/access-gate.ts
15525
15629
  import neo4j2 from "neo4j-driver";
15526
- import { readFileSync as readFileSync12 } from "fs";
15630
+ import { readFileSync as readFileSync13 } from "fs";
15527
15631
  import { resolve as resolve13 } from "path";
15528
15632
  import { randomUUID as randomUUID7, randomInt } from "crypto";
15529
15633
  var PLATFORM_ROOT5 = process.env.MAXY_PLATFORM_ROOT ?? resolve13(process.cwd(), "..");
@@ -15532,7 +15636,7 @@ function readPassword2() {
15532
15636
  if (process.env.NEO4J_PASSWORD) return process.env.NEO4J_PASSWORD;
15533
15637
  const passwordFile = resolve13(PLATFORM_ROOT5, "config/.neo4j-password");
15534
15638
  try {
15535
- return readFileSync12(passwordFile, "utf-8").trim();
15639
+ return readFileSync13(passwordFile, "utf-8").trim();
15536
15640
  } catch {
15537
15641
  throw new Error(
15538
15642
  `Neo4j password not found. Expected at ${passwordFile} or in NEO4J_PASSWORD env var.`
@@ -15843,7 +15947,7 @@ async function findActiveGrantByContact(contactValue, agentSlug, accountId) {
15843
15947
  }
15844
15948
 
15845
15949
  // app/lib/brevo-sms.ts
15846
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync11, mkdirSync as mkdirSync9, existsSync as existsSync12, chmodSync } from "fs";
15950
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync11, mkdirSync as mkdirSync9, existsSync as existsSync12, chmodSync } from "fs";
15847
15951
  import { dirname as dirname6 } from "path";
15848
15952
  import { resolve as resolve14 } from "path";
15849
15953
  var BREVO_API_KEY_FILE = resolve14(MAXY_DIR, ".brevo-api-key");
@@ -15855,7 +15959,7 @@ if (platformRoot) {
15855
15959
  try {
15856
15960
  const brandPath = resolve14(platformRoot, "config", "brand.json");
15857
15961
  if (existsSync12(brandPath)) {
15858
- const brand = JSON.parse(readFileSync13(brandPath, "utf-8"));
15962
+ const brand = JSON.parse(readFileSync14(brandPath, "utf-8"));
15859
15963
  if (brand.productName) BREVO_SENDER = brand.productName;
15860
15964
  }
15861
15965
  } catch {
@@ -15863,7 +15967,7 @@ if (platformRoot) {
15863
15967
  }
15864
15968
  function readBrevoApiKey() {
15865
15969
  try {
15866
- const key = readFileSync13(BREVO_API_KEY_FILE, "utf-8").trim();
15970
+ const key = readFileSync14(BREVO_API_KEY_FILE, "utf-8").trim();
15867
15971
  if (!key) {
15868
15972
  throw new Error(`Brevo API key file is empty: ${BREVO_API_KEY_FILE}`);
15869
15973
  }
@@ -16294,7 +16398,7 @@ app5.post("/send-otp", async (c) => {
16294
16398
  var access_default = app5;
16295
16399
 
16296
16400
  // server/routes/telegram.ts
16297
- import { existsSync as existsSync13, readFileSync as readFileSync14 } from "fs";
16401
+ import { existsSync as existsSync13, readFileSync as readFileSync15 } from "fs";
16298
16402
  import { timingSafeEqual } from "crypto";
16299
16403
 
16300
16404
  // app/lib/telegram/access-control.ts
@@ -16332,7 +16436,7 @@ function getWebhookSecret(botType) {
16332
16436
  const filePath = botType === "admin" ? TELEGRAM_ADMIN_WEBHOOK_SECRET_FILE : TELEGRAM_WEBHOOK_SECRET_FILE;
16333
16437
  try {
16334
16438
  if (!existsSync13(filePath)) return null;
16335
- const secret = readFileSync14(filePath, "utf-8").trim();
16439
+ const secret = readFileSync15(filePath, "utf-8").trim();
16336
16440
  return secret || null;
16337
16441
  } catch {
16338
16442
  return null;
@@ -16492,7 +16596,7 @@ var telegram_default = app6;
16492
16596
  // server/routes/whatsapp.ts
16493
16597
  import { join as join8, resolve as resolve15, basename as basename4 } from "path";
16494
16598
  import { readFile as readFile2, stat as stat3 } from "fs/promises";
16495
- import { realpathSync as realpathSync2, readdirSync as readdirSync4, readFileSync as readFileSync15, existsSync as existsSync14 } from "fs";
16599
+ import { realpathSync as realpathSync2, readdirSync as readdirSync4, readFileSync as readFileSync16, existsSync as existsSync14 } from "fs";
16496
16600
 
16497
16601
  // app/lib/whatsapp/login.ts
16498
16602
  import { randomUUID as randomUUID8 } from "crypto";
@@ -16980,7 +17084,7 @@ app7.post("/config", async (c) => {
16980
17084
  const configPath2 = resolve15(agentsDir, entry.name, "config.json");
16981
17085
  if (!existsSync14(configPath2)) continue;
16982
17086
  try {
16983
- const config = JSON.parse(readFileSync15(configPath2, "utf-8"));
17087
+ const config = JSON.parse(readFileSync16(configPath2, "utf-8"));
16984
17088
  agents.push({ slug: entry.name, displayName: config.displayName ?? entry.name });
16985
17089
  } catch {
16986
17090
  console.error(`${TAG18} config action=list-public-agents error="failed to parse config.json for agent ${entry.name}" \u2014 skipping`);
@@ -17192,7 +17296,7 @@ var whatsapp_default = app7;
17192
17296
 
17193
17297
  // server/routes/onboarding.ts
17194
17298
  import { spawn as spawn3, execFileSync as execFileSync2 } from "child_process";
17195
- import { openSync as openSync4, closeSync as closeSync4, writeFileSync as writeFileSync12, writeSync, existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync16, unlinkSync as unlinkSync4 } from "fs";
17299
+ import { openSync as openSync4, closeSync as closeSync4, writeFileSync as writeFileSync12, writeSync, existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync17, unlinkSync as unlinkSync4 } from "fs";
17196
17300
  import { resolve as resolve16, dirname as dirname7 } from "path";
17197
17301
  import { createHash, randomUUID as randomUUID9 } from "crypto";
17198
17302
  var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT || "";
@@ -17201,7 +17305,7 @@ function hashPin(pin) {
17201
17305
  }
17202
17306
  function readUsersFile() {
17203
17307
  if (!existsSync15(USERS_FILE)) return null;
17204
- const raw2 = readFileSync16(USERS_FILE, "utf-8").trim();
17308
+ const raw2 = readFileSync17(USERS_FILE, "utf-8").trim();
17205
17309
  if (!raw2) return [];
17206
17310
  return JSON.parse(raw2);
17207
17311
  }
@@ -17312,7 +17416,7 @@ app8.post("/set-pin", async (c) => {
17312
17416
  const account = resolveAccount();
17313
17417
  if (account) {
17314
17418
  try {
17315
- const config = JSON.parse(readFileSync16(`${account.accountDir}/account.json`, "utf-8"));
17419
+ const config = JSON.parse(readFileSync17(`${account.accountDir}/account.json`, "utf-8"));
17316
17420
  if (!config.admins) config.admins = [];
17317
17421
  if (!config.admins.some((a) => a.userId === userId)) {
17318
17422
  config.admins.push({ userId, role: "owner" });
@@ -17370,7 +17474,7 @@ app8.post("/skip", async (c) => {
17370
17474
  const brandPath = PLATFORM_ROOT7 ? resolve16(PLATFORM_ROOT7, "config", "brand.json") : "";
17371
17475
  if (brandPath && existsSync15(brandPath)) {
17372
17476
  try {
17373
- const brand = JSON.parse(readFileSync16(brandPath, "utf-8"));
17477
+ const brand = JSON.parse(readFileSync17(brandPath, "utf-8"));
17374
17478
  if (brand.productName) agentName = brand.productName;
17375
17479
  } catch (err) {
17376
17480
  console.error(`[onboarding-skip] brand.json read failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -17564,14 +17668,14 @@ app9.post("/", async (c) => {
17564
17668
  var client_error_default = app9;
17565
17669
 
17566
17670
  // server/routes/admin/session.ts
17567
- import { readFileSync as readFileSync17, existsSync as existsSync17 } from "fs";
17671
+ import { readFileSync as readFileSync18, existsSync as existsSync17 } from "fs";
17568
17672
  import { createHash as createHash2 } from "crypto";
17569
17673
  function hashPin2(pin) {
17570
17674
  return createHash2("sha256").update(pin).digest("hex");
17571
17675
  }
17572
17676
  function readUsersFile2() {
17573
17677
  if (!existsSync17(USERS_FILE)) return null;
17574
- const raw2 = readFileSync17(USERS_FILE, "utf-8").trim();
17678
+ const raw2 = readFileSync18(USERS_FILE, "utf-8").trim();
17575
17679
  if (!raw2) return [];
17576
17680
  return JSON.parse(raw2);
17577
17681
  }
@@ -18267,7 +18371,7 @@ app12.post("/", requireAdminSession, async (c) => {
18267
18371
  var compact_default = app12;
18268
18372
 
18269
18373
  // server/routes/admin/logs.ts
18270
- import { existsSync as existsSync18, readdirSync as readdirSync5, readFileSync as readFileSync18, statSync as statSync9 } from "fs";
18374
+ import { existsSync as existsSync18, readdirSync as readdirSync5, readFileSync as readFileSync19, statSync as statSync9 } from "fs";
18271
18375
  import { resolve as resolve18, basename as basename5 } from "path";
18272
18376
  var TAIL_BYTES = 8192;
18273
18377
  var app13 = new Hono2();
@@ -18286,7 +18390,7 @@ app13.get("/", async (c) => {
18286
18390
  const filePath = resolve18(dir, safe);
18287
18391
  searched.push(filePath);
18288
18392
  try {
18289
- const content = readFileSync18(filePath, "utf-8");
18393
+ const content = readFileSync19(filePath, "utf-8");
18290
18394
  const headers = { "Content-Type": "text/plain; charset=utf-8" };
18291
18395
  if (download) headers["Content-Disposition"] = `attachment; filename="${safe}"`;
18292
18396
  return new Response(content, { headers });
@@ -18328,7 +18432,7 @@ app13.get("/", async (c) => {
18328
18432
  const filePath = resolve18(dir, fileName);
18329
18433
  searched.push(filePath);
18330
18434
  try {
18331
- const content = readFileSync18(filePath, "utf-8");
18435
+ const content = readFileSync19(filePath, "utf-8");
18332
18436
  const headers = { "Content-Type": "text/plain; charset=utf-8" };
18333
18437
  if (download) headers["Content-Disposition"] = `attachment; filename="${fileName}"`;
18334
18438
  return new Response(content, { headers });
@@ -18355,7 +18459,7 @@ app13.get("/", async (c) => {
18355
18459
  files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync9(resolve18(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).forEach(({ name }) => {
18356
18460
  seen.add(name);
18357
18461
  try {
18358
- const content = readFileSync18(resolve18(dir, name));
18462
+ const content = readFileSync19(resolve18(dir, name));
18359
18463
  const tail = content.length > TAIL_BYTES ? content.subarray(content.length - TAIL_BYTES).toString("utf-8") : content.toString("utf-8");
18360
18464
  logs[name] = tail.trim() || "(empty)";
18361
18465
  } catch (err) {
@@ -18440,7 +18544,7 @@ app15.get("/:attachmentId", requireAdminSession, async (c) => {
18440
18544
  var attachment_default = app15;
18441
18545
 
18442
18546
  // server/routes/admin/account.ts
18443
- import { readFileSync as readFileSync19, writeFileSync as writeFileSync13 } from "fs";
18547
+ import { readFileSync as readFileSync20, writeFileSync as writeFileSync13 } from "fs";
18444
18548
  import { resolve as resolve20 } from "path";
18445
18549
  var VALID_CONTEXT_MODES = ["managed", "claude-code"];
18446
18550
  var app16 = new Hono2();
@@ -18459,7 +18563,7 @@ app16.patch("/", requireAdminSession, async (c) => {
18459
18563
  if (!account) return c.json({ error: "No account configured" }, 500);
18460
18564
  const configPath2 = resolve20(account.accountDir, "account.json");
18461
18565
  try {
18462
- const raw2 = readFileSync19(configPath2, "utf-8");
18566
+ const raw2 = readFileSync20(configPath2, "utf-8");
18463
18567
  const config = JSON.parse(raw2);
18464
18568
  config.contextMode = contextMode;
18465
18569
  writeFileSync13(configPath2, JSON.stringify(config, null, 2) + "\n", "utf-8");
@@ -18474,7 +18578,7 @@ var account_default = app16;
18474
18578
 
18475
18579
  // server/routes/admin/agents.ts
18476
18580
  import { resolve as resolve21 } from "path";
18477
- import { readdirSync as readdirSync6, readFileSync as readFileSync20, existsSync as existsSync20, rmSync as rmSync3 } from "fs";
18581
+ import { readdirSync as readdirSync6, readFileSync as readFileSync21, existsSync as existsSync20, rmSync as rmSync3 } from "fs";
18478
18582
  var app17 = new Hono2();
18479
18583
  app17.get("/", (c) => {
18480
18584
  const account = resolveAccount();
@@ -18490,7 +18594,7 @@ app17.get("/", (c) => {
18490
18594
  const configPath2 = resolve21(agentsDir, entry.name, "config.json");
18491
18595
  if (!existsSync20(configPath2)) continue;
18492
18596
  try {
18493
- const config = JSON.parse(readFileSync20(configPath2, "utf-8"));
18597
+ const config = JSON.parse(readFileSync21(configPath2, "utf-8"));
18494
18598
  agents.push({
18495
18599
  slug: entry.name,
18496
18600
  displayName: config.displayName ?? entry.name,
@@ -18532,7 +18636,7 @@ app17.delete("/:slug", (c) => {
18532
18636
  var agents_default = app17;
18533
18637
 
18534
18638
  // server/routes/admin/version.ts
18535
- import { existsSync as existsSync21, readFileSync as readFileSync21 } from "fs";
18639
+ import { existsSync as existsSync21, readFileSync as readFileSync22 } from "fs";
18536
18640
  import { resolve as resolve22, join as join10 } from "path";
18537
18641
  var PLATFORM_ROOT8 = process.env.MAXY_PLATFORM_ROOT ?? resolve22(process.cwd(), "..");
18538
18642
  var brandHostname = "maxy";
@@ -18540,7 +18644,7 @@ var brandNpmPackage = "@rubytech/create-maxy";
18540
18644
  var brandJsonPath = join10(PLATFORM_ROOT8, "config", "brand.json");
18541
18645
  if (existsSync21(brandJsonPath)) {
18542
18646
  try {
18543
- const brand = JSON.parse(readFileSync21(brandJsonPath, "utf-8"));
18647
+ const brand = JSON.parse(readFileSync22(brandJsonPath, "utf-8"));
18544
18648
  if (brand.hostname) brandHostname = brand.hostname;
18545
18649
  if (brand.npm?.packageName) brandNpmPackage = brand.npm.packageName;
18546
18650
  } catch {
@@ -18552,7 +18656,7 @@ var REGISTRY_URL = `https://registry.npmjs.org/${NPM_PACKAGE}/latest`;
18552
18656
  var FETCH_TIMEOUT_MS = 5e3;
18553
18657
  function readInstalled() {
18554
18658
  if (!existsSync21(VERSION_FILE)) return "unknown";
18555
- const content = readFileSync21(VERSION_FILE, "utf-8").trim();
18659
+ const content = readFileSync22(VERSION_FILE, "utf-8").trim();
18556
18660
  return content || "unknown";
18557
18661
  }
18558
18662
  async function fetchLatest() {
@@ -19041,7 +19145,7 @@ var events_default = app23;
19041
19145
  // server/routes/admin/cloudflare.ts
19042
19146
  import { homedir as homedir3 } from "os";
19043
19147
  import { resolve as resolve24 } from "path";
19044
- import { readFileSync as readFileSync23 } from "fs";
19148
+ import { readFileSync as readFileSync24 } from "fs";
19045
19149
 
19046
19150
  // app/lib/dns-label.ts
19047
19151
  var VALID_LABEL = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;
@@ -19057,14 +19161,14 @@ function isValidDomain(value) {
19057
19161
  }
19058
19162
 
19059
19163
  // app/lib/alias-domains.ts
19060
- import { existsSync as existsSync22, mkdirSync as mkdirSync12, readFileSync as readFileSync22, writeFileSync as writeFileSync14 } from "fs";
19164
+ import { existsSync as existsSync22, mkdirSync as mkdirSync12, readFileSync as readFileSync23, writeFileSync as writeFileSync14 } from "fs";
19061
19165
  import { dirname as dirname9 } from "path";
19062
19166
  import { resolve as resolve23 } from "path";
19063
19167
  var ALIAS_DOMAINS_PATH = resolve23(MAXY_DIR, "alias-domains.json");
19064
19168
  function readExisting() {
19065
19169
  if (!existsSync22(ALIAS_DOMAINS_PATH)) return /* @__PURE__ */ new Set();
19066
19170
  try {
19067
- const parsed = JSON.parse(readFileSync22(ALIAS_DOMAINS_PATH, "utf-8"));
19171
+ const parsed = JSON.parse(readFileSync23(ALIAS_DOMAINS_PATH, "utf-8"));
19068
19172
  if (!Array.isArray(parsed)) return /* @__PURE__ */ new Set();
19069
19173
  return new Set(parsed.filter((h) => typeof h === "string"));
19070
19174
  } catch {
@@ -19086,7 +19190,7 @@ function loadBrandInfo() {
19086
19190
  const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? resolve24(process.cwd(), "..");
19087
19191
  const brandPath = resolve24(platformRoot2, "config", "brand.json");
19088
19192
  try {
19089
- const parsed = JSON.parse(readFileSync23(brandPath, "utf-8"));
19193
+ const parsed = JSON.parse(readFileSync24(brandPath, "utf-8"));
19090
19194
  const hostname2 = typeof parsed.hostname === "string" && parsed.hostname ? parsed.hostname : "maxy";
19091
19195
  const configDir2 = typeof parsed.configDir === "string" && parsed.configDir ? parsed.configDir : ".maxy";
19092
19196
  return { hostname: hostname2, configDir: configDir2 };
@@ -19489,25 +19593,72 @@ async function trashNode(params) {
19489
19593
  const setNullClauses = Object.keys(originalKeys).map((k) => `n.\`${k}\` = null`).join(", ");
19490
19594
  const setNullSuffix = setNullClauses ? `, ${setNullClauses}` : "";
19491
19595
  const trashedAt = (/* @__PURE__ */ new Date()).toISOString();
19492
- await session.run(
19493
- `MATCH (n) WHERE elementId(n) = $eid
19494
- SET n:Trashed,
19495
- n.trashedAt = datetime($trashedAt),
19496
- n.trashedBy = $by,
19497
- n.trashReason = $reason,
19498
- n._trashedKeys = $trashedKeysJson${setNullSuffix}`,
19499
- {
19500
- eid: elementId,
19501
- trashedAt,
19502
- by,
19503
- reason: reason ?? null,
19504
- trashedKeysJson: JSON.stringify(originalKeys)
19596
+ const isConversation = baseLabels.includes("Conversation");
19597
+ const messageUniqueKeys = UNIQUE_KEYS_BY_LABEL["Message"] ?? [];
19598
+ let cascadedMessageCount = 0;
19599
+ await session.executeWrite(async (tx) => {
19600
+ await tx.run(
19601
+ `MATCH (n) WHERE elementId(n) = $eid
19602
+ SET n:Trashed,
19603
+ n.trashedAt = datetime($trashedAt),
19604
+ n.trashedBy = $by,
19605
+ n.trashReason = $reason,
19606
+ n._trashedKeys = $trashedKeysJson${setNullSuffix}`,
19607
+ {
19608
+ eid: elementId,
19609
+ trashedAt,
19610
+ by,
19611
+ reason: reason ?? null,
19612
+ trashedKeysJson: JSON.stringify(originalKeys)
19613
+ }
19614
+ );
19615
+ if (isConversation) {
19616
+ const collectKeys = messageUniqueKeys.length > 0 ? messageUniqueKeys.map((k) => `\`${k}\`: m.\`${k}\``).join(", ") : "";
19617
+ const collectMsgProps = collectKeys ? `, {${collectKeys}}` : ", {}";
19618
+ const collected = await tx.run(
19619
+ `MATCH (c) WHERE elementId(c) = $eid
19620
+ MATCH (m:Message)-[:PART_OF]->(c)
19621
+ WHERE NOT m:Trashed
19622
+ RETURN elementId(m) AS meid${collectMsgProps} AS keys`,
19623
+ { eid: elementId }
19624
+ );
19625
+ for (const rec of collected.records) {
19626
+ const meid = rec.get("meid");
19627
+ const keys = rec.get("keys");
19628
+ const liveKeys = {};
19629
+ for (const k of messageUniqueKeys) {
19630
+ if (keys[k] !== void 0 && keys[k] !== null) liveKeys[k] = keys[k];
19631
+ }
19632
+ const msgSetNulls = Object.keys(liveKeys).length > 0 ? ", " + Object.keys(liveKeys).map((k) => `m.\`${k}\` = null`).join(", ") : "";
19633
+ await tx.run(
19634
+ `MATCH (m) WHERE elementId(m) = $meid
19635
+ SET m:Trashed,
19636
+ m.trashedAt = datetime($trashedAt),
19637
+ m.trashedBy = $by,
19638
+ m.trashReason = $reason,
19639
+ m._trashedKeys = $trashedKeysJson${msgSetNulls}`,
19640
+ {
19641
+ meid,
19642
+ trashedAt,
19643
+ by: `${by}:cascade-from-conversation`,
19644
+ reason: reason ?? `cascade from Conversation ${elementId}`,
19645
+ trashedKeysJson: JSON.stringify(liveKeys)
19646
+ }
19647
+ );
19648
+ }
19649
+ cascadedMessageCount = collected.records.length;
19505
19650
  }
19506
- );
19651
+ });
19507
19652
  process.stderr.write(
19508
19653
  `[trash:marked] accountId=${accountId} elementId=${elementId} labels=${baseLabels.join(",")} by=${by} reason=${reason ?? "null"}
19509
19654
  `
19510
19655
  );
19656
+ if (isConversation) {
19657
+ process.stderr.write(
19658
+ `[trash:cascaded] accountId=${accountId} conversationElementId=${elementId} messageCount=${cascadedMessageCount} by=${by}
19659
+ `
19660
+ );
19661
+ }
19511
19662
  return {
19512
19663
  trashed: true,
19513
19664
  alreadyTrashed: false,
@@ -19558,16 +19709,50 @@ async function restoreNode(params) {
19558
19709
  const setSuffix = setClauses ? `, ${setClauses}` : "";
19559
19710
  const setParams = { eid: elementId };
19560
19711
  for (const [k, v] of Object.entries(originalKeys)) setParams[`val_${k}`] = v;
19561
- await session.run(
19562
- `MATCH (n:Trashed) WHERE elementId(n) = $eid
19563
- REMOVE n:Trashed, n.trashedAt, n.trashedBy, n.trashReason, n._trashedKeys
19564
- SET n.restoredAt = datetime()${setSuffix}`,
19565
- setParams
19566
- );
19712
+ const isConversation = baseLabels.includes("Conversation");
19713
+ let cascadedMessageCount = 0;
19714
+ await session.executeWrite(async (tx) => {
19715
+ await tx.run(
19716
+ `MATCH (n:Trashed) WHERE elementId(n) = $eid
19717
+ REMOVE n:Trashed, n.trashedAt, n.trashedBy, n.trashReason, n._trashedKeys
19718
+ SET n.restoredAt = datetime()${setSuffix}`,
19719
+ setParams
19720
+ );
19721
+ if (isConversation) {
19722
+ const collected = await tx.run(
19723
+ `MATCH (c) WHERE elementId(c) = $eid
19724
+ MATCH (m:Trashed:Message)-[:PART_OF]->(c)
19725
+ WHERE m.trashedBy ENDS WITH ':cascade-from-conversation'
19726
+ RETURN elementId(m) AS meid, m._trashedKeys AS keysJson`,
19727
+ { eid: elementId }
19728
+ );
19729
+ for (const rec of collected.records) {
19730
+ const meid = rec.get("meid");
19731
+ const keysJson2 = rec.get("keysJson");
19732
+ const keys = keysJson2 ? JSON.parse(keysJson2) : {};
19733
+ const setClause = Object.keys(keys).length > 0 ? ", " + Object.keys(keys).map((k) => `m.\`${k}\` = $val_${k}`).join(", ") : "";
19734
+ const msgParams = { meid };
19735
+ for (const [k, v] of Object.entries(keys)) msgParams[`val_${k}`] = v;
19736
+ await tx.run(
19737
+ `MATCH (m) WHERE elementId(m) = $meid
19738
+ REMOVE m:Trashed, m.trashedAt, m.trashedBy, m.trashReason, m._trashedKeys
19739
+ SET m.restoredAt = datetime()${setClause}`,
19740
+ msgParams
19741
+ );
19742
+ }
19743
+ cascadedMessageCount = collected.records.length;
19744
+ }
19745
+ });
19567
19746
  process.stderr.write(
19568
19747
  `[trash:restored] accountId=${accountId} elementId=${elementId} labels=${baseLabels.join(",")}
19569
19748
  `
19570
19749
  );
19750
+ if (isConversation) {
19751
+ process.stderr.write(
19752
+ `[trash:cascade-restored] accountId=${accountId} conversationElementId=${elementId} messageCount=${cascadedMessageCount}
19753
+ `
19754
+ );
19755
+ }
19571
19756
  return {
19572
19757
  restored: true,
19573
19758
  nodeId: elementId,
@@ -20825,7 +21010,7 @@ if (BRAND_JSON_PATH && !existsSync23(BRAND_JSON_PATH)) {
20825
21010
  }
20826
21011
  if (BRAND_JSON_PATH && existsSync23(BRAND_JSON_PATH)) {
20827
21012
  try {
20828
- const parsed = JSON.parse(readFileSync24(BRAND_JSON_PATH, "utf-8"));
21013
+ const parsed = JSON.parse(readFileSync25(BRAND_JSON_PATH, "utf-8"));
20829
21014
  BRAND = { ...BRAND, ...parsed };
20830
21015
  } catch (err) {
20831
21016
  console.error(`[brand] Failed to parse brand.json: ${err.message}`);
@@ -20848,7 +21033,7 @@ var ALIAS_DOMAINS_PATH2 = join12(homedir4(), BRAND.configDir, "alias-domains.jso
20848
21033
  function loadAliasDomains() {
20849
21034
  try {
20850
21035
  if (!existsSync23(ALIAS_DOMAINS_PATH2)) return null;
20851
- const parsed = JSON.parse(readFileSync24(ALIAS_DOMAINS_PATH2, "utf-8"));
21036
+ const parsed = JSON.parse(readFileSync25(ALIAS_DOMAINS_PATH2, "utf-8"));
20852
21037
  if (!Array.isArray(parsed)) {
20853
21038
  console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
20854
21039
  return null;
@@ -20973,10 +21158,7 @@ app35.post("/__remote-auth/login", async (c) => {
20973
21158
  }
20974
21159
  });
20975
21160
  });
20976
- app35.get("/__remote-auth/logout", (c) => {
20977
- const cookieHeader = c.req.header("cookie");
20978
- const token = parseCookie(cookieHeader, "__remote_session");
20979
- if (token) invalidateRemoteSession(token);
21161
+ app35.get("/__remote-auth/logout", () => {
20980
21162
  return new Response(null, {
20981
21163
  status: 302,
20982
21164
  headers: {
@@ -21142,16 +21324,6 @@ app35.use("*", async (c, next) => {
21142
21324
  console.error(`[remote-auth] login required ip=${clientIp} path=${path2}`);
21143
21325
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), redirect: path2 }), 200);
21144
21326
  });
21145
- function parseCookie(cookieHeader, name) {
21146
- if (!cookieHeader) return null;
21147
- const match2 = cookieHeader.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
21148
- if (!match2) return null;
21149
- try {
21150
- return decodeURIComponent(match2[1]);
21151
- } catch {
21152
- return null;
21153
- }
21154
- }
21155
21327
  app35.route("/api/health", health_default);
21156
21328
  app35.route("/api/session", session_default);
21157
21329
  app35.route("/api/chat", chat_default);
@@ -21201,7 +21373,7 @@ app35.get("/agent-assets/:slug/:filename", (c) => {
21201
21373
  const ext = "." + filename.split(".").pop()?.toLowerCase();
21202
21374
  const contentType = IMAGE_MIME[ext] || "application/octet-stream";
21203
21375
  console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
21204
- const body = readFileSync24(filePath);
21376
+ const body = readFileSync25(filePath);
21205
21377
  return c.body(body, 200, {
21206
21378
  "Content-Type": contentType,
21207
21379
  "Cache-Control": "public, max-age=3600"
@@ -21231,7 +21403,7 @@ app35.get("/generated/:filename", (c) => {
21231
21403
  const ext = "." + filename.split(".").pop()?.toLowerCase();
21232
21404
  const contentType = IMAGE_MIME[ext] || "application/octet-stream";
21233
21405
  console.log(`[generated] serve file=${filename} status=200`);
21234
- const body = readFileSync24(filePath);
21406
+ const body = readFileSync25(filePath);
21235
21407
  return c.body(body, 200, {
21236
21408
  "Content-Type": contentType,
21237
21409
  "Cache-Control": "public, max-age=86400"
@@ -21242,7 +21414,7 @@ var brandLogoPath = "/brand/maxy-monochrome.png";
21242
21414
  var brandIconPath = "/brand/maxy-monochrome.png";
21243
21415
  if (BRAND_JSON_PATH && existsSync23(BRAND_JSON_PATH)) {
21244
21416
  try {
21245
- const fullBrand = JSON.parse(readFileSync24(BRAND_JSON_PATH, "utf-8"));
21417
+ const fullBrand = JSON.parse(readFileSync25(BRAND_JSON_PATH, "utf-8"));
21246
21418
  if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
21247
21419
  brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
21248
21420
  } catch {
@@ -21261,7 +21433,7 @@ function readInstalledVersion() {
21261
21433
  if (!PLATFORM_ROOT10) return "unknown";
21262
21434
  const versionFile = join12(PLATFORM_ROOT10, "config", `.${BRAND.hostname}-version`);
21263
21435
  if (!existsSync23(versionFile)) return "unknown";
21264
- const content = readFileSync24(versionFile, "utf-8").trim();
21436
+ const content = readFileSync25(versionFile, "utf-8").trim();
21265
21437
  return content || "unknown";
21266
21438
  } catch {
21267
21439
  return "unknown";
@@ -21302,7 +21474,7 @@ var clientErrorReporterScript = `<script>
21302
21474
  function cachedHtml(file) {
21303
21475
  let html = htmlCache.get(file);
21304
21476
  if (!html) {
21305
- html = readFileSync24(resolve27(process.cwd(), "public", file), "utf-8");
21477
+ html = readFileSync25(resolve27(process.cwd(), "public", file), "utf-8");
21306
21478
  html = html.replace("<title>Maxy</title>", `<title>${escapeHtml(BRAND.productName)}</title>`);
21307
21479
  html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
21308
21480
  const headInjection = file === "index.html" ? `${brandScript}
@@ -21321,12 +21493,12 @@ function loadBrandingCache(agentSlug) {
21321
21493
  try {
21322
21494
  const accountJsonPath = join12(configDir2, "account.json");
21323
21495
  if (!existsSync23(accountJsonPath)) return null;
21324
- const account = JSON.parse(readFileSync24(accountJsonPath, "utf-8"));
21496
+ const account = JSON.parse(readFileSync25(accountJsonPath, "utf-8"));
21325
21497
  const accountId = account.accountId;
21326
21498
  if (!accountId) return null;
21327
21499
  const cachePath = join12(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
21328
21500
  if (!existsSync23(cachePath)) return null;
21329
- return JSON.parse(readFileSync24(cachePath, "utf-8"));
21501
+ return JSON.parse(readFileSync25(cachePath, "utf-8"));
21330
21502
  } catch {
21331
21503
  return null;
21332
21504
  }
@@ -21336,7 +21508,7 @@ function resolveDefaultSlug() {
21336
21508
  const configDir2 = join12(homedir4(), BRAND.configDir);
21337
21509
  const accountJsonPath = join12(configDir2, "account.json");
21338
21510
  if (!existsSync23(accountJsonPath)) return null;
21339
- const account = JSON.parse(readFileSync24(accountJsonPath, "utf-8"));
21511
+ const account = JSON.parse(readFileSync25(accountJsonPath, "utf-8"));
21340
21512
  return account.defaultAgent || null;
21341
21513
  } catch {
21342
21514
  return null;
@@ -21409,7 +21581,7 @@ app35.use("/vnc-popout.html", logViewerFetch);
21409
21581
  app35.get("/vnc-popout.html", (c) => {
21410
21582
  let html = htmlCache.get("vnc-popout.html");
21411
21583
  if (!html) {
21412
- html = readFileSync24(resolve27(process.cwd(), "public", "vnc-popout.html"), "utf-8");
21584
+ html = readFileSync25(resolve27(process.cwd(), "public", "vnc-popout.html"), "utf-8");
21413
21585
  const name = escapeHtml(BRAND.productName);
21414
21586
  html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
21415
21587
  html = html.replace("</head>", ` ${brandScript}
@@ -21500,7 +21672,7 @@ try {
21500
21672
  try {
21501
21673
  let userId = "";
21502
21674
  if (existsSync23(USERS_FILE)) {
21503
- const users = JSON.parse(readFileSync24(USERS_FILE, "utf-8").trim() || "[]");
21675
+ const users = JSON.parse(readFileSync25(USERS_FILE, "utf-8").trim() || "[]");
21504
21676
  userId = users[0]?.userId ?? "";
21505
21677
  }
21506
21678
  await backfillNullUserIdConversations(userId);