@rubytech/create-realagent 1.0.677 → 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 (69) 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/internals.md +4 -0
  32. package/payload/platform/plugins/docs/references/memory-guide.md +5 -1
  33. package/payload/platform/plugins/docs/references/platform.md +1 -1
  34. package/payload/platform/plugins/docs/references/troubleshooting.md +18 -0
  35. package/payload/platform/plugins/memory/PLUGIN.md +1 -0
  36. package/payload/platform/plugins/memory/mcp/dist/index.js +54 -6
  37. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  38. package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.d.ts +36 -0
  39. package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.d.ts.map +1 -0
  40. package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.js +86 -0
  41. package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.js.map +1 -0
  42. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.d.ts +23 -0
  43. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.d.ts.map +1 -1
  44. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js +47 -1
  45. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js.map +1 -1
  46. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.d.ts +58 -0
  47. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.d.ts.map +1 -0
  48. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.js +125 -0
  49. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.js.map +1 -0
  50. package/payload/platform/templates/agents/admin/IDENTITY.md +16 -0
  51. package/payload/server/chunk-3RBKKDHC.js +783 -0
  52. package/payload/server/maxy-edge.js +11 -3
  53. package/payload/server/public/assets/{admin-DQmUdTBa.js → admin-BBL1no_g.js} +1 -1
  54. package/payload/server/public/assets/{data-DVlvxbTt.js → data-DUSyrydY.js} +1 -1
  55. package/payload/server/public/assets/{file-OY_hX2wu.js → file-CDJ6dUV3.js} +1 -1
  56. package/payload/server/public/assets/graph-CWcYp5bE.js +50 -0
  57. package/payload/server/public/assets/{house-CgENfOCP.js → house-CNP_bwvT.js} +1 -1
  58. package/payload/server/public/assets/{jsx-runtime-Bu4vXoe7.css → jsx-runtime-BFFQvkdQ.css} +1 -1
  59. package/payload/server/public/assets/{public-Clp4VPwo.js → public-sHoAccvb.js} +1 -1
  60. package/payload/server/public/assets/{share-2-RSIR3MmX.js → share-2-DBcb9j6E.js} +1 -1
  61. package/payload/server/public/assets/{useVoiceRecorder-B0FI_hts.js → useVoiceRecorder-CtSgpc95.js} +1 -1
  62. package/payload/server/public/assets/{x-DKZ5NR3n.js → x-CTVJaC_u.js} +1 -1
  63. package/payload/server/public/data.html +6 -6
  64. package/payload/server/public/graph.html +6 -6
  65. package/payload/server/public/index.html +7 -7
  66. package/payload/server/public/public.html +4 -4
  67. package/payload/server/server.js +446 -178
  68. package/payload/server/public/assets/graph-BDaM4Qer.js +0 -49
  69. /package/payload/server/public/assets/{jsx-runtime-C_VUlXvu.js → jsx-runtime-BVKWELH6.js} +0 -0
@@ -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;
@@ -7168,8 +7262,75 @@ function clearSessionHistory(sessionKey) {
7168
7262
  session.stalledSubagents = void 0;
7169
7263
  session.pendingTrimmedMessages = void 0;
7170
7264
  session.pendingCommitmentOffers = void 0;
7265
+ session.pendingTurns = void 0;
7171
7266
  return previousConversationId;
7172
7267
  }
7268
+ function bufferPendingTurn(sessionKey, turn) {
7269
+ const session = sessionStore.get(sessionKey);
7270
+ if (!session) {
7271
+ console.error(`[conversation-gate] bufferPendingTurn: session not found sessionKey=${sessionKey.slice(0, 8)}\u2026`);
7272
+ return;
7273
+ }
7274
+ if (!session.pendingTurns) session.pendingTurns = [];
7275
+ session.pendingTurns.push(turn);
7276
+ console.log(`[conversation-gate] ${(/* @__PURE__ */ new Date()).toISOString()} buffered sessionKey=${sessionKey.slice(0, 8)} role=${turn.role} turnCount=${session.pendingTurns.filter((t) => t.role === "user").length}`);
7277
+ }
7278
+ function getPendingTurnCount(sessionKey) {
7279
+ const buf = sessionStore.get(sessionKey)?.pendingTurns;
7280
+ if (!buf) return 0;
7281
+ let n = 0;
7282
+ for (const t of buf) if (t.role === "user") n++;
7283
+ return n;
7284
+ }
7285
+ function drainPendingTurns(sessionKey) {
7286
+ const session = sessionStore.get(sessionKey);
7287
+ if (!session?.pendingTurns || session.pendingTurns.length === 0) return void 0;
7288
+ const drained = session.pendingTurns;
7289
+ session.pendingTurns = void 0;
7290
+ return drained;
7291
+ }
7292
+ async function maybeFlushConversationBuffer(sessionKey, agentType, accountId) {
7293
+ const session = sessionStore.get(sessionKey);
7294
+ if (!session) return null;
7295
+ if (session.conversationId) return session.conversationId;
7296
+ if (getPendingTurnCount(sessionKey) < 2) return null;
7297
+ if (session.flushInFlight) return session.flushInFlight;
7298
+ const attempt = (async () => {
7299
+ let conversationId = null;
7300
+ if (agentType === "admin") {
7301
+ const userId = session.userId;
7302
+ if (!userId) {
7303
+ console.error(`[conversation-gate] flush aborted: admin session missing userId sessionKey=${sessionKey.slice(0, 8)}\u2026`);
7304
+ return null;
7305
+ }
7306
+ conversationId = await createNewAdminConversation(userId, accountId, sessionKey);
7307
+ } else {
7308
+ conversationId = await ensureConversation(accountId, "public", sessionKey, void 0, void 0, void 0);
7309
+ }
7310
+ if (!conversationId) return null;
7311
+ session.conversationId = conversationId;
7312
+ const buffered = drainPendingTurns(sessionKey) ?? [];
7313
+ for (const turn of buffered) {
7314
+ persistMessage(conversationId, turn.role, turn.content, accountId, turn.tokens, turn.timestamp, turn.sender).catch((err) => {
7315
+ console.error(`[conversation-gate] replay persistMessage failed role=${turn.role}: ${err instanceof Error ? err.message : String(err)}`);
7316
+ });
7317
+ }
7318
+ console.log(`[conversation-gate] ${(/* @__PURE__ */ new Date()).toISOString()} flushed sessionKey=${sessionKey.slice(0, 8)} conversationId=${conversationId.slice(0, 8)} bufferedMessages=${buffered.length} agentType=${agentType}`);
7319
+ return conversationId;
7320
+ })();
7321
+ session.flushInFlight = attempt;
7322
+ try {
7323
+ return await attempt;
7324
+ } finally {
7325
+ if (session.flushInFlight === attempt) session.flushInFlight = void 0;
7326
+ }
7327
+ }
7328
+ function isDmChannelSessionKey(sessionKey) {
7329
+ return sessionKey.startsWith("whatsapp:") || sessionKey.startsWith("telegram:");
7330
+ }
7331
+ function preflushStreamLogKey(sessionKey) {
7332
+ return `preflush-${sessionKey.slice(0, 12)}`;
7333
+ }
7173
7334
  function getAgentNameForSession(sessionKey) {
7174
7335
  return sessionStore.get(sessionKey)?.agentName;
7175
7336
  }
@@ -7347,7 +7508,7 @@ function readBrandHostname() {
7347
7508
  if (cachedBrandHostname !== null) return cachedBrandHostname;
7348
7509
  try {
7349
7510
  const brandPath = resolve5(PLATFORM_ROOT3, "config", "brand.json");
7350
- const parsed = JSON.parse(readFileSync6(brandPath, "utf-8"));
7511
+ const parsed = JSON.parse(readFileSync7(brandPath, "utf-8"));
7351
7512
  cachedBrandHostname = typeof parsed.hostname === "string" && parsed.hostname.length > 0 ? parsed.hostname : "maxy";
7352
7513
  } catch {
7353
7514
  cachedBrandHostname = "maxy";
@@ -9062,20 +9223,24 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
9062
9223
  const userTimestamp = clientTimestamp ?? (/* @__PURE__ */ new Date()).toISOString();
9063
9224
  const resumeSessionId = sessionKey ? getAgentSessionId(sessionKey) : void 0;
9064
9225
  const spawnConvId = sessionKey ? getConversationIdForSession(sessionKey) : void 0;
9065
- if (!spawnConvId) {
9066
- throw new Error(`invokeAdminAgent: conversationId missing for sessionKey=${sessionKey?.slice(0, 8) ?? "none"} \u2014 ensureConversation must run before invoking the agent`);
9226
+ if (!spawnConvId && sessionKey && getPendingTurnCount(sessionKey) >= 2) {
9227
+ throw new Error(`invokeAdminAgent: conversationId missing post-flush for sessionKey=${sessionKey.slice(0, 8)} \u2014 maybeFlushConversationBuffer must bind it before invoking the agent`);
9228
+ }
9229
+ const spawnLogKey = spawnConvId ?? (sessionKey ? preflushStreamLogKey(sessionKey) : void 0);
9230
+ if (!spawnLogKey) {
9231
+ throw new Error(`invokeAdminAgent: sessionKey required \u2014 cannot resolve log stream without one`);
9067
9232
  }
9068
9233
  const cdpOk = await ensureCdp();
9069
9234
  if (!cdpOk) {
9070
- const cdpLog = agentLogStream("claude-agent-stream", accountDir, spawnConvId);
9235
+ const cdpLog = agentLogStream("claude-agent-stream", accountDir, spawnLogKey);
9071
9236
  cdpLog.write(`[${isoTs()}] [warn] ensureCdp failed \u2014 browser-specialist degraded
9072
9237
  `);
9073
9238
  cdpLog.end();
9074
9239
  }
9075
9240
  const ccUserId = sessionKey ? getUserIdForSession(sessionKey) : void 0;
9076
- const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, spawnConvId, ccUserId, enabledPlugins) });
9241
+ const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, spawnLogKey, ccUserId, enabledPlugins) });
9077
9242
  const specialistsDir = resolve5(accountDir, "specialists");
9078
- if (!existsSync5(specialistsDir)) agentLogStream("claude-agent-stream", accountDir, spawnConvId).write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
9243
+ if (!existsSync5(specialistsDir)) agentLogStream("claude-agent-stream", accountDir, spawnLogKey).write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
9079
9244
  `);
9080
9245
  const args = [
9081
9246
  "--print",
@@ -9106,19 +9271,19 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
9106
9271
  cwd: accountDir,
9107
9272
  stdio: ["ignore", "pipe", "pipe"],
9108
9273
  // Task 556: STREAM_LOG_PATH inherited by Bash-tool subprocesses.
9109
- env: buildSpawnEnv(accountId, accountDir, spawnConvId)
9274
+ env: buildSpawnEnv(accountId, accountDir, spawnLogKey)
9110
9275
  });
9111
- const stderrLog = agentLogStream("claude-agent-stderr", accountDir, spawnConvId);
9276
+ const stderrLog = agentLogStream("claude-agent-stderr", accountDir, spawnLogKey);
9112
9277
  stderrLog.on("error", () => {
9113
9278
  });
9114
9279
  proc.stderr?.pipe(stderrLog);
9115
- const streamLog = agentLogStream("claude-agent-stream", accountDir, spawnConvId);
9280
+ const streamLog = agentLogStream("claude-agent-stream", accountDir, spawnLogKey);
9116
9281
  streamLog.on("error", () => {
9117
9282
  });
9118
9283
  teeProcStderrToStreamLog(proc, streamLog);
9119
9284
  streamLog.write(`[${isoTs()}] [subproc-debug-unavailable] reason=bundled-bun-binary-ignores-node-debug pid=${proc.pid} cli=claude
9120
9285
  `);
9121
- streamLog.write(`[${isoTs()}] [spawn-env] STREAM_LOG_PATH=set pid=${proc.pid} conversationId=${spawnConvId} site=admin
9286
+ streamLog.write(`[${isoTs()}] [spawn-env] STREAM_LOG_PATH=set pid=${proc.pid} conversationId=${spawnConvId ?? "preflush"} logKey=${spawnLogKey} site=admin
9122
9287
  `);
9123
9288
  if (sessionKey) {
9124
9289
  const prev = activeProcesses.get(sessionKey);
@@ -9128,7 +9293,7 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
9128
9293
  }
9129
9294
  activeProcesses.set(sessionKey, { pid: proc.pid, spawnedAt: Date.now() });
9130
9295
  }
9131
- streamLog.write(`[${isoTs()}] [spawn] pid=${proc.pid} resume=${resumeSessionId ?? "none"} sessionKey=${sessionKey ?? "none"} conversationId=${spawnConvId} pluginDir=${specialistsDir}
9296
+ streamLog.write(`[${isoTs()}] [spawn] pid=${proc.pid} resume=${resumeSessionId ?? "none"} sessionKey=${sessionKey ?? "none"} conversationId=${spawnConvId ?? "preflush"} logKey=${spawnLogKey} pluginDir=${specialistsDir}
9132
9297
  `);
9133
9298
  streamLog.write(`[${isoTs()}] [stdin] len=${fullMessage.length} preview=${JSON.stringify(fullMessage.slice(0, 80))}
9134
9299
  `);
@@ -9200,7 +9365,7 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
9200
9365
  }
9201
9366
  if (event.type === "usage" && sessionKey && currentAgentSessionId) {
9202
9367
  const peakReqPct = event.peak_request_pct ?? 0;
9203
- if (peakReqPct >= COMPACTION_THRESHOLD) {
9368
+ if (peakReqPct >= COMPACTION_THRESHOLD && spawnConvId) {
9204
9369
  const compactionIter = runCompactionTurn(accountDir, accountId, systemPrompt, currentAgentSessionId, adminModel, spawnConvId, enabledPlugins);
9205
9370
  let step = await compactionIter.next();
9206
9371
  while (!step.done) {
@@ -9310,9 +9475,9 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
9310
9475
  } else {
9311
9476
  gotDone = true;
9312
9477
  if (!sessionWasReset) {
9478
+ const assistantTimestamp = (/* @__PURE__ */ new Date()).toISOString();
9313
9479
  const convId = sessionKey ? sessionStore.get(sessionKey)?.conversationId : void 0;
9314
9480
  if (convId) {
9315
- const assistantTimestamp = (/* @__PURE__ */ new Date()).toISOString();
9316
9481
  persistMessage(convId, "user", fullMessage, accountId, void 0, userTimestamp).catch(() => {
9317
9482
  });
9318
9483
  autoLabelSession(convId, fullMessage).catch(() => {
@@ -9320,7 +9485,14 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
9320
9485
  if (responseText) persistMessage(convId, "assistant", responseText, accountId, capturedTokens, assistantTimestamp).catch(() => {
9321
9486
  });
9322
9487
  } else if (sessionKey) {
9323
- console.warn(`[persist] skipped: no conversationId for sessionKey=${sessionKey.slice(0, 12)}\u2026 (session registered=${sessionStore.has(sessionKey)})`);
9488
+ bufferPendingTurn(sessionKey, { role: "user", content: fullMessage, timestamp: userTimestamp });
9489
+ if (responseText) bufferPendingTurn(sessionKey, { role: "assistant", content: responseText, timestamp: assistantTimestamp, tokens: capturedTokens });
9490
+ const flushedId = await maybeFlushConversationBuffer(sessionKey, "admin", accountId);
9491
+ if (flushedId) {
9492
+ autoLabelSession(flushedId, fullMessage).catch(() => {
9493
+ });
9494
+ yield { type: "conversation_attributed", conversationId: flushedId };
9495
+ }
9324
9496
  }
9325
9497
  if (sessionKey) {
9326
9498
  const commitSession = sessionStore.get(sessionKey);
@@ -9383,9 +9555,10 @@ ${summary}`;
9383
9555
  async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accountId, adminModel, sessionKey, maxTurns = 20, attachments = [], retryCount = 0, enabledPlugins, clientTimestamp, adherenceConstraints, agentName) {
9384
9556
  const userTimestamp = clientTimestamp ?? (/* @__PURE__ */ new Date()).toISOString();
9385
9557
  const managedConvId = getConversationIdForSession(sessionKey);
9386
- if (!managedConvId) {
9387
- throw new Error(`invokeManagedAdminAgent: conversationId missing for sessionKey=${sessionKey.slice(0, 8)} \u2014 ensureConversation must run first`);
9558
+ if (!managedConvId && getPendingTurnCount(sessionKey) >= 2) {
9559
+ throw new Error(`invokeManagedAdminAgent: conversationId missing post-flush for sessionKey=${sessionKey.slice(0, 8)} \u2014 maybeFlushConversationBuffer must bind it first`);
9388
9560
  }
9561
+ const managedLogKey = managedConvId ?? preflushStreamLogKey(sessionKey);
9389
9562
  const pendingTrimmed = consumePendingTrimmedMessages(sessionKey);
9390
9563
  if (pendingTrimmed && pendingTrimmed.length > 0) {
9391
9564
  const ok = await compactTrimmedMessages(accountId, pendingTrimmed);
@@ -9393,7 +9566,7 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
9393
9566
  storePendingTrimmedMessages(sessionKey, pendingTrimmed);
9394
9567
  }
9395
9568
  }
9396
- const streamLog = agentLogStream("claude-agent-stream", accountDir, managedConvId);
9569
+ const streamLog = agentLogStream("claude-agent-stream", accountDir, managedLogKey);
9397
9570
  streamLog.on("error", () => {
9398
9571
  });
9399
9572
  const systemPromptTokens = estimateTokens(systemPrompt);
@@ -9424,7 +9597,7 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
9424
9597
  if (!cdpOk) streamLog.write(`[${isoTs()}] [warn] ensureCdp failed \u2014 browser-specialist degraded
9425
9598
  `);
9426
9599
  const managedUserId = getUserIdForSession(sessionKey);
9427
- const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, managedConvId, managedUserId, enabledPlugins) });
9600
+ const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, managedLogKey, managedUserId, enabledPlugins) });
9428
9601
  const specialistsDir = resolve5(accountDir, "specialists");
9429
9602
  if (!existsSync5(specialistsDir)) streamLog.write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
9430
9603
  `);
@@ -9455,16 +9628,16 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
9455
9628
  cwd: accountDir,
9456
9629
  stdio: ["ignore", "pipe", "pipe"],
9457
9630
  // Task 556: STREAM_LOG_PATH inherited by Bash-tool subprocesses.
9458
- env: buildSpawnEnv(accountId, accountDir, managedConvId)
9631
+ env: buildSpawnEnv(accountId, accountDir, managedLogKey)
9459
9632
  });
9460
- const stderrLog = agentLogStream("claude-agent-stderr", accountDir, managedConvId);
9633
+ const stderrLog = agentLogStream("claude-agent-stderr", accountDir, managedLogKey);
9461
9634
  stderrLog.on("error", () => {
9462
9635
  });
9463
9636
  proc.stderr?.pipe(stderrLog);
9464
9637
  teeProcStderrToStreamLog(proc, streamLog);
9465
9638
  streamLog.write(`[${isoTs()}] [subproc-debug-unavailable] reason=bundled-bun-binary-ignores-node-debug pid=${proc.pid} cli=claude
9466
9639
  `);
9467
- streamLog.write(`[${isoTs()}] [spawn-env] STREAM_LOG_PATH=set pid=${proc.pid} conversationId=${managedConvId} site=managed
9640
+ streamLog.write(`[${isoTs()}] [spawn-env] STREAM_LOG_PATH=set pid=${proc.pid} conversationId=${managedConvId ?? "preflush"} logKey=${managedLogKey} site=managed
9468
9641
  `);
9469
9642
  if (sessionKey) {
9470
9643
  const prev = activeProcesses.get(sessionKey);
@@ -9474,13 +9647,13 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
9474
9647
  }
9475
9648
  activeProcesses.set(sessionKey, { pid: proc.pid, spawnedAt: Date.now() });
9476
9649
  }
9477
- streamLog.write(`[${isoTs()}] [managed-spawn] pid=${proc.pid} sessionKey=${sessionKey} conversationId=${managedConvId} historyMessages=${history.length} pluginDir=${specialistsDir}
9650
+ streamLog.write(`[${isoTs()}] [managed-spawn] pid=${proc.pid} sessionKey=${sessionKey} conversationId=${managedConvId ?? "preflush"} logKey=${managedLogKey} historyMessages=${history.length} pluginDir=${specialistsDir}
9478
9651
  `);
9479
9652
  streamLog.write(`[${isoTs()}] [stdin] len=${fullMessage.length} preview=${JSON.stringify(fullMessage.slice(0, 80))}
9480
9653
  `);
9481
9654
  proc.on("exit", (code, signal) => {
9482
- console.log(`[process-exit] pid=${proc.pid} code=${code} signal=${signal} sessionKey=${sessionKey ?? "none"} conversationId=${managedConvId}`);
9483
- if (!streamLog.destroyed && !streamLog.writableEnded) streamLog.write(`[${isoTs()}] [process-exit] pid=${proc.pid} code=${code} signal=${signal} conversationId=${managedConvId}
9655
+ console.log(`[process-exit] pid=${proc.pid} code=${code} signal=${signal} sessionKey=${sessionKey ?? "none"} conversationId=${managedConvId ?? "preflush"}`);
9656
+ if (!streamLog.destroyed && !streamLog.writableEnded) streamLog.write(`[${isoTs()}] [process-exit] pid=${proc.pid} code=${code} signal=${signal} conversationId=${managedConvId ?? "preflush"}
9484
9657
  `);
9485
9658
  if (sessionKey) activeProcesses.delete(sessionKey);
9486
9659
  });
@@ -9630,17 +9803,24 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
9630
9803
  `);
9631
9804
  const successSession = sessionStore.get(sessionKey);
9632
9805
  if (successSession) successSession.lastPeakContextPct = peakContextPct;
9806
+ const assistantTimestamp = (/* @__PURE__ */ new Date()).toISOString();
9633
9807
  const convId = sessionStore.get(sessionKey)?.conversationId;
9634
9808
  if (convId) {
9635
- const assistantTimestamp = (/* @__PURE__ */ new Date()).toISOString();
9636
9809
  persistMessage(convId, "user", fullMessage, accountId, void 0, userTimestamp).catch(() => {
9637
9810
  });
9638
9811
  autoLabelSession(convId, fullMessage).catch(() => {
9639
9812
  });
9640
9813
  if (responseText) persistMessage(convId, "assistant", responseText, accountId, capturedTokens, assistantTimestamp).catch(() => {
9641
9814
  });
9642
- } else if (sessionKey) {
9643
- console.warn(`[persist] skipped: no conversationId for sessionKey=${sessionKey.slice(0, 12)}\u2026 (session registered=${sessionStore.has(sessionKey)})`);
9815
+ } else {
9816
+ bufferPendingTurn(sessionKey, { role: "user", content: fullMessage, timestamp: userTimestamp });
9817
+ if (responseText) bufferPendingTurn(sessionKey, { role: "assistant", content: responseText, timestamp: assistantTimestamp, tokens: capturedTokens });
9818
+ const flushedId = await maybeFlushConversationBuffer(sessionKey, "admin", accountId);
9819
+ if (flushedId) {
9820
+ autoLabelSession(flushedId, fullMessage).catch(() => {
9821
+ });
9822
+ yield { type: "conversation_attributed", conversationId: flushedId };
9823
+ }
9644
9824
  }
9645
9825
  const commitSession = sessionStore.get(sessionKey);
9646
9826
  if (commitSession?.pendingCommitmentOffers && commitSession.pendingCommitmentOffers.length > 0) {
@@ -9712,10 +9892,14 @@ async function* invokePublicAgent(message, systemPrompt, accountId, accountDir,
9712
9892
  return;
9713
9893
  }
9714
9894
  const publicConvId = sessionKey ? getConversationIdForSession(sessionKey) : void 0;
9715
- if (!publicConvId) {
9716
- throw new Error(`invokePublicAgent: conversationId missing for sessionKey=${sessionKey?.slice(0, 8) ?? "none"} \u2014 ensureConversation must run first`);
9895
+ if (!publicConvId && sessionKey && getPendingTurnCount(sessionKey) >= 2) {
9896
+ throw new Error(`invokePublicAgent: conversationId missing post-flush for sessionKey=${sessionKey.slice(0, 8)} \u2014 maybeFlushConversationBuffer must bind it first`);
9897
+ }
9898
+ const publicLogKey = publicConvId ?? (sessionKey ? preflushStreamLogKey(sessionKey) : void 0);
9899
+ if (!publicLogKey) {
9900
+ throw new Error(`invokePublicAgent: sessionKey required \u2014 cannot resolve log stream without one`);
9717
9901
  }
9718
- const streamLog = agentLogStream("public-agent-stream", accountDir, publicConvId);
9902
+ const streamLog = agentLogStream("public-agent-stream", accountDir, publicLogKey);
9719
9903
  streamLog.write(`[${isoTs()}] [public-user-message] ${JSON.stringify(message)}
9720
9904
  `);
9721
9905
  if (sessionKey) {
@@ -9952,10 +10136,10 @@ User messages are prefixed with the sender's name in brackets. Address participa
9952
10136
  `);
9953
10137
  }
9954
10138
  const conversationId = sessionKey ? sessionStore.get(sessionKey)?.conversationId : void 0;
10139
+ const assistantTimestamp = (/* @__PURE__ */ new Date()).toISOString();
10140
+ const sess = sessionKey ? sessionStore.get(sessionKey) : void 0;
10141
+ const sender = sess?.groupSlug && sess.visitorId && sess.senderDisplayName ? { visitorId: sess.visitorId, displayName: sess.senderDisplayName } : void 0;
9955
10142
  if (conversationId) {
9956
- const assistantTimestamp = (/* @__PURE__ */ new Date()).toISOString();
9957
- const sess = sessionKey ? sessionStore.get(sessionKey) : void 0;
9958
- const sender = sess?.groupSlug && sess.visitorId && sess.senderDisplayName ? { visitorId: sess.visitorId, displayName: sess.senderDisplayName } : void 0;
9959
10143
  persistMessage(conversationId, "user", message, accountId, void 0, userTimestamp, sender).catch(() => {
9960
10144
  });
9961
10145
  autoLabelSession(conversationId, message).catch(() => {
@@ -9963,7 +10147,14 @@ User messages are prefixed with the sender's name in brackets. Address participa
9963
10147
  if (fullText) persistMessage(conversationId, "assistant", fullText, accountId, void 0, assistantTimestamp).catch(() => {
9964
10148
  });
9965
10149
  } else if (sessionKey) {
9966
- console.warn(`[persist] skipped: no conversationId for sessionKey=${sessionKey.slice(0, 12)}\u2026 (session registered=${sessionStore.has(sessionKey)})`);
10150
+ bufferPendingTurn(sessionKey, { role: "user", content: message, timestamp: userTimestamp, sender });
10151
+ if (fullText) bufferPendingTurn(sessionKey, { role: "assistant", content: fullText, timestamp: assistantTimestamp });
10152
+ const flushedId = await maybeFlushConversationBuffer(sessionKey, "public", accountId);
10153
+ if (flushedId) {
10154
+ autoLabelSession(flushedId, message).catch(() => {
10155
+ });
10156
+ yield { type: "conversation_attributed", conversationId: flushedId };
10157
+ }
9967
10158
  }
9968
10159
  streamLog.end();
9969
10160
  }
@@ -10122,7 +10313,7 @@ ${sessionContext}`;
10122
10313
  const skillPath = resolve5(PLATFORM_ROOT3, "plugins/admin/skills/onboarding/SKILL.md");
10123
10314
  let skillContent = "";
10124
10315
  try {
10125
- skillContent = readFileSync6(skillPath, "utf-8");
10316
+ skillContent = readFileSync7(skillPath, "utf-8");
10126
10317
  } catch (err) {
10127
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)}`);
10128
10319
  }
@@ -10174,13 +10365,23 @@ ${manifest}`;
10174
10365
  }
10175
10366
  const graphRefPath = resolve5(PLATFORM_ROOT3, "plugins/memory/references/graph-primitives.md");
10176
10367
  try {
10177
- const graphRef = readFileSync6(graphRefPath, "utf-8");
10368
+ const graphRef = readFileSync7(graphRefPath, "utf-8");
10178
10369
  baseSystemPrompt += `
10179
10370
 
10180
10371
  ${graphRef}`;
10181
10372
  } catch (err) {
10182
10373
  console.error(`[graph-primitives] reference missing at ${graphRefPath} \u2014 admin session will have no Cypher cookbook: ${err instanceof Error ? err.message : String(err)}`);
10183
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
+ }
10184
10385
  }
10185
10386
  if (agentConfig?.budget) {
10186
10387
  const pluginTokens = embeddedPlugins.reduce((sum, p) => sum + estimateTokens(p.body), 0);
@@ -10260,7 +10461,7 @@ Current session key: ${sessionKey}` : systemPromptBase;
10260
10461
 
10261
10462
  ${gwParts.join("\n")}`;
10262
10463
  }
10263
- if (sessionKey) {
10464
+ if (sessionKey && isDmChannelSessionKey(sessionKey)) {
10264
10465
  try {
10265
10466
  await ensureConversation(accountId, agentType, sessionKey, void 0, void 0, sessionUserId);
10266
10467
  } catch (err) {
@@ -10389,7 +10590,7 @@ var clientIpMiddleware = async (c, next) => {
10389
10590
  };
10390
10591
 
10391
10592
  // server/routes/health.ts
10392
- import { existsSync as existsSync10, readFileSync as readFileSync11 } from "fs";
10593
+ import { existsSync as existsSync10, readFileSync as readFileSync12 } from "fs";
10393
10594
  import { createConnection as createConnection2 } from "net";
10394
10595
 
10395
10596
  // app/lib/network.ts
@@ -10414,7 +10615,7 @@ function getLanIp() {
10414
10615
  import { basename as basename2 } from "path";
10415
10616
 
10416
10617
  // app/lib/review-detector/rules.ts
10417
- 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";
10418
10619
  import { resolve as resolve6, dirname as dirname3 } from "path";
10419
10620
  var DEFAULT_SCAN_INTERVAL_MS = 5e3;
10420
10621
  var RATE_LIMIT_PATTERN = "rate[- ]?limit(?:ed| reached| hit)|(?:HTTP|status)[^a-z]{0,3}429|too many requests";
@@ -10862,7 +11063,7 @@ function loadRules(configDir2) {
10862
11063
  if (!existsSync6(path2)) {
10863
11064
  throw new Error(`rules file missing at ${path2}`);
10864
11065
  }
10865
- const raw2 = readFileSync7(path2, "utf-8");
11066
+ const raw2 = readFileSync8(path2, "utf-8");
10866
11067
  let parsed;
10867
11068
  try {
10868
11069
  parsed = JSON.parse(raw2);
@@ -11027,7 +11228,7 @@ function validateRule(input, label, seenIds) {
11027
11228
  }
11028
11229
 
11029
11230
  // app/lib/review-detector/sources.ts
11030
- 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";
11031
11232
  import { resolve as resolve7, join as join4, basename, dirname as dirname4 } from "path";
11032
11233
  function tailStatePath(configDir2) {
11033
11234
  return resolve7(configDir2, "review-state.json");
@@ -11036,7 +11237,7 @@ function loadTailState(configDir2) {
11036
11237
  const path2 = tailStatePath(configDir2);
11037
11238
  if (!existsSync7(path2)) return {};
11038
11239
  try {
11039
- const raw2 = readFileSync8(path2, "utf-8");
11240
+ const raw2 = readFileSync9(path2, "utf-8");
11040
11241
  const parsed = JSON.parse(raw2);
11041
11242
  if (!parsed || typeof parsed !== "object") return {};
11042
11243
  const clean = {};
@@ -11206,7 +11407,7 @@ function sourceKey(file) {
11206
11407
  }
11207
11408
 
11208
11409
  // app/lib/review-detector/writer.ts
11209
- 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";
11210
11411
  import { resolve as resolve8, dirname as dirname5 } from "path";
11211
11412
  import { randomUUID as randomUUID3 } from "crypto";
11212
11413
  function reviewLogPath(configDir2) {
@@ -11345,7 +11546,7 @@ function queueAlert(configDir2, accountId, match2) {
11345
11546
  async function drainPendingAlerts(configDir2) {
11346
11547
  const path2 = pendingAlertsPath(configDir2);
11347
11548
  if (!existsSync8(path2)) return { drained: 0, remaining: 0 };
11348
- const raw2 = readFileSync9(path2, "utf-8");
11549
+ const raw2 = readFileSync10(path2, "utf-8");
11349
11550
  const lines = raw2.split("\n").filter((l) => l.trim().length > 0);
11350
11551
  if (lines.length === 0) return { drained: 0, remaining: 0 };
11351
11552
  const remaining = [];
@@ -12008,7 +12209,7 @@ var WhatsAppConfigSchema = z.object({
12008
12209
  });
12009
12210
 
12010
12211
  // app/lib/whatsapp/config-persist.ts
12011
- 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";
12012
12213
  import { resolve as resolve10, join as join5 } from "path";
12013
12214
  var TAG3 = "[whatsapp:config]";
12014
12215
  function configPath(accountDir) {
@@ -12017,7 +12218,7 @@ function configPath(accountDir) {
12017
12218
  function readConfig(accountDir) {
12018
12219
  const path2 = configPath(accountDir);
12019
12220
  if (!existsSync9(path2)) throw new Error(`account.json not found at ${path2}`);
12020
- return JSON.parse(readFileSync10(path2, "utf-8"));
12221
+ return JSON.parse(readFileSync11(path2, "utf-8"));
12021
12222
  }
12022
12223
  function writeConfig(accountDir, config) {
12023
12224
  const path2 = configPath(accountDir);
@@ -14309,7 +14510,7 @@ app.get("/", async (c) => {
14309
14510
  let pinConfigured = false;
14310
14511
  try {
14311
14512
  if (existsSync10(USERS_FILE)) {
14312
- const raw2 = readFileSync11(USERS_FILE, "utf-8").trim();
14513
+ const raw2 = readFileSync12(USERS_FILE, "utf-8").trim();
14313
14514
  if (raw2) {
14314
14515
  const users = JSON.parse(raw2);
14315
14516
  pinConfigured = Array.isArray(users) && users.length > 0;
@@ -14665,8 +14866,6 @@ app2.post("/", async (c) => {
14665
14866
  const newVisitorId = visitorId ?? crypto.randomUUID();
14666
14867
  const sessionKey = crypto.randomUUID();
14667
14868
  registerSession(sessionKey, "public", accountId, agentSlug);
14668
- ensureConversation(accountId, "public", sessionKey, newVisitorId, agentSlug).catch(() => {
14669
- });
14670
14869
  const hasImage = agentConfig?.image ? "yes" : "no";
14671
14870
  console.log(`[session] new-session visitor=${newVisitorId.slice(0, 8)}\u2026 session=${sessionKey.slice(0, 8)}\u2026 agent=${agentSlug} image=${hasImage} showAgentName=${agentConfig?.showAgentName ?? false}`);
14672
14871
  return withVisitorCookie(
@@ -15428,7 +15627,7 @@ var group_default = app4;
15428
15627
 
15429
15628
  // app/lib/access-gate.ts
15430
15629
  import neo4j2 from "neo4j-driver";
15431
- import { readFileSync as readFileSync12 } from "fs";
15630
+ import { readFileSync as readFileSync13 } from "fs";
15432
15631
  import { resolve as resolve13 } from "path";
15433
15632
  import { randomUUID as randomUUID7, randomInt } from "crypto";
15434
15633
  var PLATFORM_ROOT5 = process.env.MAXY_PLATFORM_ROOT ?? resolve13(process.cwd(), "..");
@@ -15437,7 +15636,7 @@ function readPassword2() {
15437
15636
  if (process.env.NEO4J_PASSWORD) return process.env.NEO4J_PASSWORD;
15438
15637
  const passwordFile = resolve13(PLATFORM_ROOT5, "config/.neo4j-password");
15439
15638
  try {
15440
- return readFileSync12(passwordFile, "utf-8").trim();
15639
+ return readFileSync13(passwordFile, "utf-8").trim();
15441
15640
  } catch {
15442
15641
  throw new Error(
15443
15642
  `Neo4j password not found. Expected at ${passwordFile} or in NEO4J_PASSWORD env var.`
@@ -15748,7 +15947,7 @@ async function findActiveGrantByContact(contactValue, agentSlug, accountId) {
15748
15947
  }
15749
15948
 
15750
15949
  // app/lib/brevo-sms.ts
15751
- 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";
15752
15951
  import { dirname as dirname6 } from "path";
15753
15952
  import { resolve as resolve14 } from "path";
15754
15953
  var BREVO_API_KEY_FILE = resolve14(MAXY_DIR, ".brevo-api-key");
@@ -15760,7 +15959,7 @@ if (platformRoot) {
15760
15959
  try {
15761
15960
  const brandPath = resolve14(platformRoot, "config", "brand.json");
15762
15961
  if (existsSync12(brandPath)) {
15763
- const brand = JSON.parse(readFileSync13(brandPath, "utf-8"));
15962
+ const brand = JSON.parse(readFileSync14(brandPath, "utf-8"));
15764
15963
  if (brand.productName) BREVO_SENDER = brand.productName;
15765
15964
  }
15766
15965
  } catch {
@@ -15768,7 +15967,7 @@ if (platformRoot) {
15768
15967
  }
15769
15968
  function readBrevoApiKey() {
15770
15969
  try {
15771
- const key = readFileSync13(BREVO_API_KEY_FILE, "utf-8").trim();
15970
+ const key = readFileSync14(BREVO_API_KEY_FILE, "utf-8").trim();
15772
15971
  if (!key) {
15773
15972
  throw new Error(`Brevo API key file is empty: ${BREVO_API_KEY_FILE}`);
15774
15973
  }
@@ -16199,7 +16398,7 @@ app5.post("/send-otp", async (c) => {
16199
16398
  var access_default = app5;
16200
16399
 
16201
16400
  // server/routes/telegram.ts
16202
- import { existsSync as existsSync13, readFileSync as readFileSync14 } from "fs";
16401
+ import { existsSync as existsSync13, readFileSync as readFileSync15 } from "fs";
16203
16402
  import { timingSafeEqual } from "crypto";
16204
16403
 
16205
16404
  // app/lib/telegram/access-control.ts
@@ -16237,7 +16436,7 @@ function getWebhookSecret(botType) {
16237
16436
  const filePath = botType === "admin" ? TELEGRAM_ADMIN_WEBHOOK_SECRET_FILE : TELEGRAM_WEBHOOK_SECRET_FILE;
16238
16437
  try {
16239
16438
  if (!existsSync13(filePath)) return null;
16240
- const secret = readFileSync14(filePath, "utf-8").trim();
16439
+ const secret = readFileSync15(filePath, "utf-8").trim();
16241
16440
  return secret || null;
16242
16441
  } catch {
16243
16442
  return null;
@@ -16397,7 +16596,7 @@ var telegram_default = app6;
16397
16596
  // server/routes/whatsapp.ts
16398
16597
  import { join as join8, resolve as resolve15, basename as basename4 } from "path";
16399
16598
  import { readFile as readFile2, stat as stat3 } from "fs/promises";
16400
- 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";
16401
16600
 
16402
16601
  // app/lib/whatsapp/login.ts
16403
16602
  import { randomUUID as randomUUID8 } from "crypto";
@@ -16885,7 +17084,7 @@ app7.post("/config", async (c) => {
16885
17084
  const configPath2 = resolve15(agentsDir, entry.name, "config.json");
16886
17085
  if (!existsSync14(configPath2)) continue;
16887
17086
  try {
16888
- const config = JSON.parse(readFileSync15(configPath2, "utf-8"));
17087
+ const config = JSON.parse(readFileSync16(configPath2, "utf-8"));
16889
17088
  agents.push({ slug: entry.name, displayName: config.displayName ?? entry.name });
16890
17089
  } catch {
16891
17090
  console.error(`${TAG18} config action=list-public-agents error="failed to parse config.json for agent ${entry.name}" \u2014 skipping`);
@@ -17097,7 +17296,7 @@ var whatsapp_default = app7;
17097
17296
 
17098
17297
  // server/routes/onboarding.ts
17099
17298
  import { spawn as spawn3, execFileSync as execFileSync2 } from "child_process";
17100
- 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";
17101
17300
  import { resolve as resolve16, dirname as dirname7 } from "path";
17102
17301
  import { createHash, randomUUID as randomUUID9 } from "crypto";
17103
17302
  var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT || "";
@@ -17106,7 +17305,7 @@ function hashPin(pin) {
17106
17305
  }
17107
17306
  function readUsersFile() {
17108
17307
  if (!existsSync15(USERS_FILE)) return null;
17109
- const raw2 = readFileSync16(USERS_FILE, "utf-8").trim();
17308
+ const raw2 = readFileSync17(USERS_FILE, "utf-8").trim();
17110
17309
  if (!raw2) return [];
17111
17310
  return JSON.parse(raw2);
17112
17311
  }
@@ -17217,7 +17416,7 @@ app8.post("/set-pin", async (c) => {
17217
17416
  const account = resolveAccount();
17218
17417
  if (account) {
17219
17418
  try {
17220
- const config = JSON.parse(readFileSync16(`${account.accountDir}/account.json`, "utf-8"));
17419
+ const config = JSON.parse(readFileSync17(`${account.accountDir}/account.json`, "utf-8"));
17221
17420
  if (!config.admins) config.admins = [];
17222
17421
  if (!config.admins.some((a) => a.userId === userId)) {
17223
17422
  config.admins.push({ userId, role: "owner" });
@@ -17275,7 +17474,7 @@ app8.post("/skip", async (c) => {
17275
17474
  const brandPath = PLATFORM_ROOT7 ? resolve16(PLATFORM_ROOT7, "config", "brand.json") : "";
17276
17475
  if (brandPath && existsSync15(brandPath)) {
17277
17476
  try {
17278
- const brand = JSON.parse(readFileSync16(brandPath, "utf-8"));
17477
+ const brand = JSON.parse(readFileSync17(brandPath, "utf-8"));
17279
17478
  if (brand.productName) agentName = brand.productName;
17280
17479
  } catch (err) {
17281
17480
  console.error(`[onboarding-skip] brand.json read failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -17469,14 +17668,14 @@ app9.post("/", async (c) => {
17469
17668
  var client_error_default = app9;
17470
17669
 
17471
17670
  // server/routes/admin/session.ts
17472
- import { readFileSync as readFileSync17, existsSync as existsSync17 } from "fs";
17671
+ import { readFileSync as readFileSync18, existsSync as existsSync17 } from "fs";
17473
17672
  import { createHash as createHash2 } from "crypto";
17474
17673
  function hashPin2(pin) {
17475
17674
  return createHash2("sha256").update(pin).digest("hex");
17476
17675
  }
17477
17676
  function readUsersFile2() {
17478
17677
  if (!existsSync17(USERS_FILE)) return null;
17479
- const raw2 = readFileSync17(USERS_FILE, "utf-8").trim();
17678
+ const raw2 = readFileSync18(USERS_FILE, "utf-8").trim();
17480
17679
  if (!raw2) return [];
17481
17680
  return JSON.parse(raw2);
17482
17681
  }
@@ -17498,11 +17697,7 @@ async function createAdminSession(accountId, thinkingView, userId, userName) {
17498
17697
  businessName = branding?.name || void 0;
17499
17698
  } catch {
17500
17699
  }
17501
- let conversationId = null;
17502
- if (userId) {
17503
- conversationId = await createNewAdminConversation(userId, accountId, sessionKey);
17504
- }
17505
- console.log(`[session] ${(/* @__PURE__ */ new Date()).toISOString()} admin session created: userId=${userId ?? "\u2013"} userName=${userName ?? "\u2013"} accountId=${accountId} conversationId=${conversationId?.slice(0, 8) ?? "\u2013"} sessionKey=${sessionKey.slice(0, 8)}`);
17700
+ console.log(`[session] ${(/* @__PURE__ */ new Date()).toISOString()} admin session created: userId=${userId ?? "\u2013"} userName=${userName ?? "\u2013"} accountId=${accountId} conversationId=deferred sessionKey=${sessionKey.slice(0, 8)}`);
17506
17701
  return {
17507
17702
  session_key: sessionKey,
17508
17703
  agent_id: "admin",
@@ -17511,7 +17706,7 @@ async function createAdminSession(accountId, thinkingView, userId, userName) {
17511
17706
  thinkingView: effectiveThinkingView,
17512
17707
  onboardingComplete,
17513
17708
  businessName,
17514
- conversationId
17709
+ conversationId: null
17515
17710
  };
17516
17711
  }
17517
17712
  var app10 = new Hono2();
@@ -18176,7 +18371,7 @@ app12.post("/", requireAdminSession, async (c) => {
18176
18371
  var compact_default = app12;
18177
18372
 
18178
18373
  // server/routes/admin/logs.ts
18179
- 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";
18180
18375
  import { resolve as resolve18, basename as basename5 } from "path";
18181
18376
  var TAIL_BYTES = 8192;
18182
18377
  var app13 = new Hono2();
@@ -18195,7 +18390,7 @@ app13.get("/", async (c) => {
18195
18390
  const filePath = resolve18(dir, safe);
18196
18391
  searched.push(filePath);
18197
18392
  try {
18198
- const content = readFileSync18(filePath, "utf-8");
18393
+ const content = readFileSync19(filePath, "utf-8");
18199
18394
  const headers = { "Content-Type": "text/plain; charset=utf-8" };
18200
18395
  if (download) headers["Content-Disposition"] = `attachment; filename="${safe}"`;
18201
18396
  return new Response(content, { headers });
@@ -18237,7 +18432,7 @@ app13.get("/", async (c) => {
18237
18432
  const filePath = resolve18(dir, fileName);
18238
18433
  searched.push(filePath);
18239
18434
  try {
18240
- const content = readFileSync18(filePath, "utf-8");
18435
+ const content = readFileSync19(filePath, "utf-8");
18241
18436
  const headers = { "Content-Type": "text/plain; charset=utf-8" };
18242
18437
  if (download) headers["Content-Disposition"] = `attachment; filename="${fileName}"`;
18243
18438
  return new Response(content, { headers });
@@ -18264,7 +18459,7 @@ app13.get("/", async (c) => {
18264
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 }) => {
18265
18460
  seen.add(name);
18266
18461
  try {
18267
- const content = readFileSync18(resolve18(dir, name));
18462
+ const content = readFileSync19(resolve18(dir, name));
18268
18463
  const tail = content.length > TAIL_BYTES ? content.subarray(content.length - TAIL_BYTES).toString("utf-8") : content.toString("utf-8");
18269
18464
  logs[name] = tail.trim() || "(empty)";
18270
18465
  } catch (err) {
@@ -18349,7 +18544,7 @@ app15.get("/:attachmentId", requireAdminSession, async (c) => {
18349
18544
  var attachment_default = app15;
18350
18545
 
18351
18546
  // server/routes/admin/account.ts
18352
- import { readFileSync as readFileSync19, writeFileSync as writeFileSync13 } from "fs";
18547
+ import { readFileSync as readFileSync20, writeFileSync as writeFileSync13 } from "fs";
18353
18548
  import { resolve as resolve20 } from "path";
18354
18549
  var VALID_CONTEXT_MODES = ["managed", "claude-code"];
18355
18550
  var app16 = new Hono2();
@@ -18368,7 +18563,7 @@ app16.patch("/", requireAdminSession, async (c) => {
18368
18563
  if (!account) return c.json({ error: "No account configured" }, 500);
18369
18564
  const configPath2 = resolve20(account.accountDir, "account.json");
18370
18565
  try {
18371
- const raw2 = readFileSync19(configPath2, "utf-8");
18566
+ const raw2 = readFileSync20(configPath2, "utf-8");
18372
18567
  const config = JSON.parse(raw2);
18373
18568
  config.contextMode = contextMode;
18374
18569
  writeFileSync13(configPath2, JSON.stringify(config, null, 2) + "\n", "utf-8");
@@ -18383,7 +18578,7 @@ var account_default = app16;
18383
18578
 
18384
18579
  // server/routes/admin/agents.ts
18385
18580
  import { resolve as resolve21 } from "path";
18386
- 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";
18387
18582
  var app17 = new Hono2();
18388
18583
  app17.get("/", (c) => {
18389
18584
  const account = resolveAccount();
@@ -18399,7 +18594,7 @@ app17.get("/", (c) => {
18399
18594
  const configPath2 = resolve21(agentsDir, entry.name, "config.json");
18400
18595
  if (!existsSync20(configPath2)) continue;
18401
18596
  try {
18402
- const config = JSON.parse(readFileSync20(configPath2, "utf-8"));
18597
+ const config = JSON.parse(readFileSync21(configPath2, "utf-8"));
18403
18598
  agents.push({
18404
18599
  slug: entry.name,
18405
18600
  displayName: config.displayName ?? entry.name,
@@ -18441,7 +18636,7 @@ app17.delete("/:slug", (c) => {
18441
18636
  var agents_default = app17;
18442
18637
 
18443
18638
  // server/routes/admin/version.ts
18444
- import { existsSync as existsSync21, readFileSync as readFileSync21 } from "fs";
18639
+ import { existsSync as existsSync21, readFileSync as readFileSync22 } from "fs";
18445
18640
  import { resolve as resolve22, join as join10 } from "path";
18446
18641
  var PLATFORM_ROOT8 = process.env.MAXY_PLATFORM_ROOT ?? resolve22(process.cwd(), "..");
18447
18642
  var brandHostname = "maxy";
@@ -18449,7 +18644,7 @@ var brandNpmPackage = "@rubytech/create-maxy";
18449
18644
  var brandJsonPath = join10(PLATFORM_ROOT8, "config", "brand.json");
18450
18645
  if (existsSync21(brandJsonPath)) {
18451
18646
  try {
18452
- const brand = JSON.parse(readFileSync21(brandJsonPath, "utf-8"));
18647
+ const brand = JSON.parse(readFileSync22(brandJsonPath, "utf-8"));
18453
18648
  if (brand.hostname) brandHostname = brand.hostname;
18454
18649
  if (brand.npm?.packageName) brandNpmPackage = brand.npm.packageName;
18455
18650
  } catch {
@@ -18461,7 +18656,7 @@ var REGISTRY_URL = `https://registry.npmjs.org/${NPM_PACKAGE}/latest`;
18461
18656
  var FETCH_TIMEOUT_MS = 5e3;
18462
18657
  function readInstalled() {
18463
18658
  if (!existsSync21(VERSION_FILE)) return "unknown";
18464
- const content = readFileSync21(VERSION_FILE, "utf-8").trim();
18659
+ const content = readFileSync22(VERSION_FILE, "utf-8").trim();
18465
18660
  return content || "unknown";
18466
18661
  }
18467
18662
  async function fetchLatest() {
@@ -18571,16 +18766,10 @@ app19.post("/new", requireAdminSession, async (c) => {
18571
18766
  const newSessionKey = crypto3.randomUUID();
18572
18767
  const userName = getUserNameForSession(oldSessionKey);
18573
18768
  registerSession(newSessionKey, "admin", accountId, void 0, userId, userName);
18574
- const conversationId = await createNewAdminConversation(userId, accountId, newSessionKey);
18575
- if (!conversationId) {
18576
- unregisterSession(newSessionKey);
18577
- console.error(`[session] ${(/* @__PURE__ */ new Date()).toISOString()} new-conversation-failed: userId=${userId} accountId=${accountId.slice(0, 8)}\u2026 oldSessionKey=${oldSessionKey.slice(0, 8)}\u2026 newSessionKey=${newSessionKey.slice(0, 8)}\u2026`);
18578
- return c.json({ error: "Failed to create conversation" }, 500);
18579
- }
18580
18769
  const previousConversationId = clearSessionHistory(oldSessionKey);
18581
18770
  unregisterSession(oldSessionKey);
18582
- console.log(`[session] ${(/* @__PURE__ */ new Date()).toISOString()} session reset for new conversation: oldSessionKey=${oldSessionKey.slice(0, 8)}\u2026 newSessionKey=${newSessionKey.slice(0, 8)}\u2026 previousConversationId=${previousConversationId?.slice(0, 8) ?? "none"}\u2026 newConversationId=${conversationId.slice(0, 8)}\u2026`);
18583
- return c.json({ session_key: newSessionKey, conversationId });
18771
+ console.log(`[session] ${(/* @__PURE__ */ new Date()).toISOString()} session reset for new conversation: oldSessionKey=${oldSessionKey.slice(0, 8)}\u2026 newSessionKey=${newSessionKey.slice(0, 8)}\u2026 previousConversationId=${previousConversationId?.slice(0, 8) ?? "none"}\u2026 newConversationId=deferred`);
18772
+ return c.json({ session_key: newSessionKey, conversationId: null });
18584
18773
  });
18585
18774
  app19.delete("/:id", requireAdminSession, async (c) => {
18586
18775
  const conversationId = c.req.param("id");
@@ -18956,7 +19145,7 @@ var events_default = app23;
18956
19145
  // server/routes/admin/cloudflare.ts
18957
19146
  import { homedir as homedir3 } from "os";
18958
19147
  import { resolve as resolve24 } from "path";
18959
- import { readFileSync as readFileSync23 } from "fs";
19148
+ import { readFileSync as readFileSync24 } from "fs";
18960
19149
 
18961
19150
  // app/lib/dns-label.ts
18962
19151
  var VALID_LABEL = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;
@@ -18972,14 +19161,14 @@ function isValidDomain(value) {
18972
19161
  }
18973
19162
 
18974
19163
  // app/lib/alias-domains.ts
18975
- 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";
18976
19165
  import { dirname as dirname9 } from "path";
18977
19166
  import { resolve as resolve23 } from "path";
18978
19167
  var ALIAS_DOMAINS_PATH = resolve23(MAXY_DIR, "alias-domains.json");
18979
19168
  function readExisting() {
18980
19169
  if (!existsSync22(ALIAS_DOMAINS_PATH)) return /* @__PURE__ */ new Set();
18981
19170
  try {
18982
- const parsed = JSON.parse(readFileSync22(ALIAS_DOMAINS_PATH, "utf-8"));
19171
+ const parsed = JSON.parse(readFileSync23(ALIAS_DOMAINS_PATH, "utf-8"));
18983
19172
  if (!Array.isArray(parsed)) return /* @__PURE__ */ new Set();
18984
19173
  return new Set(parsed.filter((h) => typeof h === "string"));
18985
19174
  } catch {
@@ -19001,7 +19190,7 @@ function loadBrandInfo() {
19001
19190
  const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? resolve24(process.cwd(), "..");
19002
19191
  const brandPath = resolve24(platformRoot2, "config", "brand.json");
19003
19192
  try {
19004
- const parsed = JSON.parse(readFileSync23(brandPath, "utf-8"));
19193
+ const parsed = JSON.parse(readFileSync24(brandPath, "utf-8"));
19005
19194
  const hostname2 = typeof parsed.hostname === "string" && parsed.hostname ? parsed.hostname : "maxy";
19006
19195
  const configDir2 = typeof parsed.configDir === "string" && parsed.configDir ? parsed.configDir : ".maxy";
19007
19196
  return { hostname: hostname2, configDir: configDir2 };
@@ -19404,25 +19593,72 @@ async function trashNode(params) {
19404
19593
  const setNullClauses = Object.keys(originalKeys).map((k) => `n.\`${k}\` = null`).join(", ");
19405
19594
  const setNullSuffix = setNullClauses ? `, ${setNullClauses}` : "";
19406
19595
  const trashedAt = (/* @__PURE__ */ new Date()).toISOString();
19407
- await session.run(
19408
- `MATCH (n) WHERE elementId(n) = $eid
19409
- SET n:Trashed,
19410
- n.trashedAt = datetime($trashedAt),
19411
- n.trashedBy = $by,
19412
- n.trashReason = $reason,
19413
- n._trashedKeys = $trashedKeysJson${setNullSuffix}`,
19414
- {
19415
- eid: elementId,
19416
- trashedAt,
19417
- by,
19418
- reason: reason ?? null,
19419
- 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;
19420
19650
  }
19421
- );
19651
+ });
19422
19652
  process.stderr.write(
19423
19653
  `[trash:marked] accountId=${accountId} elementId=${elementId} labels=${baseLabels.join(",")} by=${by} reason=${reason ?? "null"}
19424
19654
  `
19425
19655
  );
19656
+ if (isConversation) {
19657
+ process.stderr.write(
19658
+ `[trash:cascaded] accountId=${accountId} conversationElementId=${elementId} messageCount=${cascadedMessageCount} by=${by}
19659
+ `
19660
+ );
19661
+ }
19426
19662
  return {
19427
19663
  trashed: true,
19428
19664
  alreadyTrashed: false,
@@ -19473,16 +19709,50 @@ async function restoreNode(params) {
19473
19709
  const setSuffix = setClauses ? `, ${setClauses}` : "";
19474
19710
  const setParams = { eid: elementId };
19475
19711
  for (const [k, v] of Object.entries(originalKeys)) setParams[`val_${k}`] = v;
19476
- await session.run(
19477
- `MATCH (n:Trashed) WHERE elementId(n) = $eid
19478
- REMOVE n:Trashed, n.trashedAt, n.trashedBy, n.trashReason, n._trashedKeys
19479
- SET n.restoredAt = datetime()${setSuffix}`,
19480
- setParams
19481
- );
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
+ });
19482
19746
  process.stderr.write(
19483
19747
  `[trash:restored] accountId=${accountId} elementId=${elementId} labels=${baseLabels.join(",")}
19484
19748
  `
19485
19749
  );
19750
+ if (isConversation) {
19751
+ process.stderr.write(
19752
+ `[trash:cascade-restored] accountId=${accountId} conversationElementId=${elementId} messageCount=${cascadedMessageCount}
19753
+ `
19754
+ );
19755
+ }
19486
19756
  return {
19487
19757
  restored: true,
19488
19758
  nodeId: elementId,
@@ -19940,24 +20210,35 @@ var GRAPH_LABEL_COLOURS = {
19940
20210
  Review: "#059669",
19941
20211
  ImageObject: "#6EE7B7",
19942
20212
  // Conversational
20213
+ //
20214
+ // Task 649 palette — the four sublabelled conversation/message labels
20215
+ // (AdminConversation, PublicConversation, UserMessage, AssistantMessage)
20216
+ // are assigned four pairwise-distinguishable hues: indigo / purple /
20217
+ // orange / yellow. The Task 633 violet-shade split (darker for admin,
20218
+ // lighter for public) was indistinguishable on canvas and in the legend;
20219
+ // hue-shift replaces lightness-shift.
20220
+ //
20221
+ // Constraints honoured:
20222
+ // - Pairwise distinct among the four in-family labels.
20223
+ // - UserMessage=#F97316 reserved (Person/Preference orange cue) —
20224
+ // the other three are non-orange.
20225
+ // - No collision with Task/Project/Event pinks (#DB2777/#BE185D/#EC4899),
20226
+ // Knowledge greens, Workflow cyans, LocalBusiness blues.
20227
+ // - Conversation base (#7C3AED) kept as pre-backfill fallback for
20228
+ // nodes that pre-date the AdminConversation/PublicConversation split.
20229
+ // - Message base reassigned away from old #A78BFA to avoid near-
20230
+ // collision with the new PublicConversation=#A855F7; slate signals
20231
+ // "legacy/system/tool" — nodes carrying only :Message without a
20232
+ // role-sublabel.
20233
+ // - Out of family (intentionally not touched): OnboardingState=#8B5CF6
20234
+ // sits in the violet neighbourhood; palette reassignment outside
20235
+ // the conversation/message family is out of scope for Task 649.
19943
20236
  Conversation: "#7C3AED",
19944
- // Task 633 — admin vs public disambiguation on /graph. Darker violet for
19945
- // admin conversations (the operator-facing side), lighter for public
19946
- // (customer-facing) so the operator can read chat topology by colour
19947
- // alone. Both within the Conversation family — the hue shift is subtle
19948
- // enough to stay legible on adjacent nodes yet distinct enough to be
19949
- // unmistakable in the popover legend.
19950
- AdminConversation: "#5B21B6",
19951
- PublicConversation: "#A78BFA",
19952
- Message: "#A78BFA",
19953
- // Task 633 — user vs assistant disambiguation on /graph. UserMessage
19954
- // picks up the Person/Preference orange family cue (the user is a
19955
- // person); AssistantMessage stays violet-adjacent to reinforce the
19956
- // AdminConversation cue (both are admin-side). system/tool Messages
19957
- // (not written by the persist path today; reserved shape) stay on the
19958
- // base `:Message` colour.
20237
+ AdminConversation: "#4338CA",
20238
+ PublicConversation: "#A855F7",
20239
+ Message: "#64748B",
19959
20240
  UserMessage: "#F97316",
19960
- AssistantMessage: "#8B5CF6",
20241
+ AssistantMessage: "#FACC15",
19961
20242
  ToolCall: "#C4B5FD",
19962
20243
  // Tasks / projects / events
19963
20244
  Task: "#DB2777",
@@ -20729,7 +21010,7 @@ if (BRAND_JSON_PATH && !existsSync23(BRAND_JSON_PATH)) {
20729
21010
  }
20730
21011
  if (BRAND_JSON_PATH && existsSync23(BRAND_JSON_PATH)) {
20731
21012
  try {
20732
- const parsed = JSON.parse(readFileSync24(BRAND_JSON_PATH, "utf-8"));
21013
+ const parsed = JSON.parse(readFileSync25(BRAND_JSON_PATH, "utf-8"));
20733
21014
  BRAND = { ...BRAND, ...parsed };
20734
21015
  } catch (err) {
20735
21016
  console.error(`[brand] Failed to parse brand.json: ${err.message}`);
@@ -20752,7 +21033,7 @@ var ALIAS_DOMAINS_PATH2 = join12(homedir4(), BRAND.configDir, "alias-domains.jso
20752
21033
  function loadAliasDomains() {
20753
21034
  try {
20754
21035
  if (!existsSync23(ALIAS_DOMAINS_PATH2)) return null;
20755
- const parsed = JSON.parse(readFileSync24(ALIAS_DOMAINS_PATH2, "utf-8"));
21036
+ const parsed = JSON.parse(readFileSync25(ALIAS_DOMAINS_PATH2, "utf-8"));
20756
21037
  if (!Array.isArray(parsed)) {
20757
21038
  console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
20758
21039
  return null;
@@ -20877,10 +21158,7 @@ app35.post("/__remote-auth/login", async (c) => {
20877
21158
  }
20878
21159
  });
20879
21160
  });
20880
- app35.get("/__remote-auth/logout", (c) => {
20881
- const cookieHeader = c.req.header("cookie");
20882
- const token = parseCookie(cookieHeader, "__remote_session");
20883
- if (token) invalidateRemoteSession(token);
21161
+ app35.get("/__remote-auth/logout", () => {
20884
21162
  return new Response(null, {
20885
21163
  status: 302,
20886
21164
  headers: {
@@ -21046,16 +21324,6 @@ app35.use("*", async (c, next) => {
21046
21324
  console.error(`[remote-auth] login required ip=${clientIp} path=${path2}`);
21047
21325
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), redirect: path2 }), 200);
21048
21326
  });
21049
- function parseCookie(cookieHeader, name) {
21050
- if (!cookieHeader) return null;
21051
- const match2 = cookieHeader.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
21052
- if (!match2) return null;
21053
- try {
21054
- return decodeURIComponent(match2[1]);
21055
- } catch {
21056
- return null;
21057
- }
21058
- }
21059
21327
  app35.route("/api/health", health_default);
21060
21328
  app35.route("/api/session", session_default);
21061
21329
  app35.route("/api/chat", chat_default);
@@ -21105,7 +21373,7 @@ app35.get("/agent-assets/:slug/:filename", (c) => {
21105
21373
  const ext = "." + filename.split(".").pop()?.toLowerCase();
21106
21374
  const contentType = IMAGE_MIME[ext] || "application/octet-stream";
21107
21375
  console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
21108
- const body = readFileSync24(filePath);
21376
+ const body = readFileSync25(filePath);
21109
21377
  return c.body(body, 200, {
21110
21378
  "Content-Type": contentType,
21111
21379
  "Cache-Control": "public, max-age=3600"
@@ -21135,7 +21403,7 @@ app35.get("/generated/:filename", (c) => {
21135
21403
  const ext = "." + filename.split(".").pop()?.toLowerCase();
21136
21404
  const contentType = IMAGE_MIME[ext] || "application/octet-stream";
21137
21405
  console.log(`[generated] serve file=${filename} status=200`);
21138
- const body = readFileSync24(filePath);
21406
+ const body = readFileSync25(filePath);
21139
21407
  return c.body(body, 200, {
21140
21408
  "Content-Type": contentType,
21141
21409
  "Cache-Control": "public, max-age=86400"
@@ -21146,7 +21414,7 @@ var brandLogoPath = "/brand/maxy-monochrome.png";
21146
21414
  var brandIconPath = "/brand/maxy-monochrome.png";
21147
21415
  if (BRAND_JSON_PATH && existsSync23(BRAND_JSON_PATH)) {
21148
21416
  try {
21149
- const fullBrand = JSON.parse(readFileSync24(BRAND_JSON_PATH, "utf-8"));
21417
+ const fullBrand = JSON.parse(readFileSync25(BRAND_JSON_PATH, "utf-8"));
21150
21418
  if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
21151
21419
  brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
21152
21420
  } catch {
@@ -21165,7 +21433,7 @@ function readInstalledVersion() {
21165
21433
  if (!PLATFORM_ROOT10) return "unknown";
21166
21434
  const versionFile = join12(PLATFORM_ROOT10, "config", `.${BRAND.hostname}-version`);
21167
21435
  if (!existsSync23(versionFile)) return "unknown";
21168
- const content = readFileSync24(versionFile, "utf-8").trim();
21436
+ const content = readFileSync25(versionFile, "utf-8").trim();
21169
21437
  return content || "unknown";
21170
21438
  } catch {
21171
21439
  return "unknown";
@@ -21206,7 +21474,7 @@ var clientErrorReporterScript = `<script>
21206
21474
  function cachedHtml(file) {
21207
21475
  let html = htmlCache.get(file);
21208
21476
  if (!html) {
21209
- html = readFileSync24(resolve27(process.cwd(), "public", file), "utf-8");
21477
+ html = readFileSync25(resolve27(process.cwd(), "public", file), "utf-8");
21210
21478
  html = html.replace("<title>Maxy</title>", `<title>${escapeHtml(BRAND.productName)}</title>`);
21211
21479
  html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
21212
21480
  const headInjection = file === "index.html" ? `${brandScript}
@@ -21225,12 +21493,12 @@ function loadBrandingCache(agentSlug) {
21225
21493
  try {
21226
21494
  const accountJsonPath = join12(configDir2, "account.json");
21227
21495
  if (!existsSync23(accountJsonPath)) return null;
21228
- const account = JSON.parse(readFileSync24(accountJsonPath, "utf-8"));
21496
+ const account = JSON.parse(readFileSync25(accountJsonPath, "utf-8"));
21229
21497
  const accountId = account.accountId;
21230
21498
  if (!accountId) return null;
21231
21499
  const cachePath = join12(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
21232
21500
  if (!existsSync23(cachePath)) return null;
21233
- return JSON.parse(readFileSync24(cachePath, "utf-8"));
21501
+ return JSON.parse(readFileSync25(cachePath, "utf-8"));
21234
21502
  } catch {
21235
21503
  return null;
21236
21504
  }
@@ -21240,7 +21508,7 @@ function resolveDefaultSlug() {
21240
21508
  const configDir2 = join12(homedir4(), BRAND.configDir);
21241
21509
  const accountJsonPath = join12(configDir2, "account.json");
21242
21510
  if (!existsSync23(accountJsonPath)) return null;
21243
- const account = JSON.parse(readFileSync24(accountJsonPath, "utf-8"));
21511
+ const account = JSON.parse(readFileSync25(accountJsonPath, "utf-8"));
21244
21512
  return account.defaultAgent || null;
21245
21513
  } catch {
21246
21514
  return null;
@@ -21313,7 +21581,7 @@ app35.use("/vnc-popout.html", logViewerFetch);
21313
21581
  app35.get("/vnc-popout.html", (c) => {
21314
21582
  let html = htmlCache.get("vnc-popout.html");
21315
21583
  if (!html) {
21316
- html = readFileSync24(resolve27(process.cwd(), "public", "vnc-popout.html"), "utf-8");
21584
+ html = readFileSync25(resolve27(process.cwd(), "public", "vnc-popout.html"), "utf-8");
21317
21585
  const name = escapeHtml(BRAND.productName);
21318
21586
  html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
21319
21587
  html = html.replace("</head>", ` ${brandScript}
@@ -21404,7 +21672,7 @@ try {
21404
21672
  try {
21405
21673
  let userId = "";
21406
21674
  if (existsSync23(USERS_FILE)) {
21407
- const users = JSON.parse(readFileSync24(USERS_FILE, "utf-8").trim() || "[]");
21675
+ const users = JSON.parse(readFileSync25(USERS_FILE, "utf-8").trim() || "[]");
21408
21676
  userId = users[0]?.userId ?? "";
21409
21677
  }
21410
21678
  await backfillNullUserIdConversations(userId);