@rubytech/create-maxy-code 0.1.441 → 0.1.442

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 (63) hide show
  1. package/dist/index.js +99 -1
  2. package/package.json +1 -1
  3. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +37 -2
  4. package/payload/platform/plugins/docs/references/admin-ui.md +1 -1
  5. package/payload/platform/plugins/docs/references/troubleshooting.md +35 -0
  6. package/payload/platform/plugins/outlook/mcp/dist/__tests__/device-flow.test.js +37 -4
  7. package/payload/platform/plugins/outlook/mcp/dist/__tests__/device-flow.test.js.map +1 -1
  8. package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.d.ts +27 -3
  9. package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.d.ts.map +1 -1
  10. package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.js +52 -11
  11. package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.js.map +1 -1
  12. package/payload/platform/plugins/outlook/mcp/dist/index.js +1 -1
  13. package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -1
  14. package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.d.ts +5 -0
  15. package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.d.ts.map +1 -1
  16. package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.js +32 -0
  17. package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.js.map +1 -1
  18. package/payload/platform/plugins/whatsapp/mcp/dist/index.js +20 -22
  19. package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
  20. package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +3 -1
  21. package/payload/platform/scripts/logs-read.sh +24 -6
  22. package/payload/platform/scripts/logs-read.test.sh +42 -0
  23. package/payload/platform/scripts/rss-sampler.sh +107 -0
  24. package/payload/server/public/assets/{AdminLoginScreens-CukwQufW.js → AdminLoginScreens-BJVwtpLH.js} +1 -1
  25. package/payload/server/public/assets/{AdminShell-BwEBH6tT.js → AdminShell-CMu8_Xwn.js} +1 -1
  26. package/payload/server/public/assets/{Checkbox-B629mOPp.js → Checkbox-CzD0ORqA.js} +1 -1
  27. package/payload/server/public/assets/{admin-CdFRCUUZ.js → admin-D0yk2LMq.js} +1 -1
  28. package/payload/server/public/assets/{browser-BWR59IkB.js → browser-CvfbgznO.js} +1 -1
  29. package/payload/server/public/assets/{calendar-kuTcxX1t.js → calendar-fw4WethJ.js} +1 -1
  30. package/payload/server/public/assets/chat-DhD-NES_.js +1 -0
  31. package/payload/server/public/assets/chevron-left-B3xSeEDR.js +1 -0
  32. package/payload/server/public/assets/data-CUnDzDox.js +1 -0
  33. package/payload/server/public/assets/{file-download-4mKJxUi-.js → file-download-Px4s0Bhp.js} +1 -1
  34. package/payload/server/public/assets/{graph-CboWM2_-.js → graph-DbT8nA6R.js} +1 -1
  35. package/payload/server/public/assets/{graph-labels-B8YAdo3Z.js → graph-labels-C1YIyLiA.js} +1 -1
  36. package/payload/server/public/assets/{maximize-2-CxstTgoK.js → maximize-2-BwphzI9S.js} +1 -1
  37. package/payload/server/public/assets/{operator-oSaB1mya.js → operator-zcIfeB14.js} +1 -1
  38. package/payload/server/public/assets/{page-DMeJHVfA.js → page-6fCnIb45.js} +2 -2
  39. package/payload/server/public/assets/page-CfQFAJia.js +1 -0
  40. package/payload/server/public/assets/{public-C54tl6V4.js → public-C0ehZCdE.js} +1 -1
  41. package/payload/server/public/assets/{rotate-ccw-COGQTi7n.js → rotate-ccw-C7BqkMec.js} +1 -1
  42. package/payload/server/public/assets/{tasks-B97VpwEJ.js → tasks-DlgiCafL.js} +1 -1
  43. package/payload/server/public/assets/{time-entry-format-C09rDHo9.js → time-entry-format-CyNYVDkq.js} +1 -1
  44. package/payload/server/public/assets/{useCopyFeedback-DHoYHiK9.js → useCopyFeedback-ByGUMN94.js} +1 -1
  45. package/payload/server/public/assets/useSubAccountSwitcher-1izQvPiD.css +1 -0
  46. package/payload/server/public/assets/useVoiceRecorder-XgpWvXpk.js +2 -0
  47. package/payload/server/public/browser.html +5 -5
  48. package/payload/server/public/calendar.html +6 -6
  49. package/payload/server/public/chat.html +12 -12
  50. package/payload/server/public/data.html +10 -10
  51. package/payload/server/public/graph.html +11 -11
  52. package/payload/server/public/index.html +13 -13
  53. package/payload/server/public/operator.html +14 -14
  54. package/payload/server/public/public.html +12 -12
  55. package/payload/server/public/tasks.html +5 -5
  56. package/payload/server/server.js +431 -182
  57. package/payload/server/public/assets/chat-ejnq2rck.js +0 -1
  58. package/payload/server/public/assets/chevron-left-u4tjq7fI.js +0 -1
  59. package/payload/server/public/assets/data-dZbOyLdT.js +0 -1
  60. package/payload/server/public/assets/page-DEkI-Gl1.js +0 -1
  61. package/payload/server/public/assets/useSubAccountSwitcher-Dzmd7Aip.css +0 -1
  62. package/payload/server/public/assets/useVoiceRecorder-b7HzTFs0.js +0 -2
  63. /package/payload/server/public/assets/{useSubAccountSwitcher-DLJFISqf.js → useSubAccountSwitcher-s6WlRn52.js} +0 -0
@@ -1413,7 +1413,7 @@ var serveStatic = (options = { root: "" }) => {
1413
1413
  };
1414
1414
 
1415
1415
  // server/index.ts
1416
- import { readFileSync as readFileSync35, existsSync as existsSync35, watchFile } from "fs";
1416
+ import { readFileSync as readFileSync36, existsSync as existsSync35, watchFile } from "fs";
1417
1417
  import { spawn as spawn3 } from "child_process";
1418
1418
  import { createHash as createHash7 } from "crypto";
1419
1419
  import { resolve as resolve33, join as join36, basename as basename12 } from "path";
@@ -1793,7 +1793,7 @@ async function ensureAuth() {
1793
1793
  }
1794
1794
 
1795
1795
  // server/routes/health.ts
1796
- import { existsSync as existsSync5, readFileSync as readFileSync9 } from "fs";
1796
+ import { existsSync as existsSync5, readFileSync as readFileSync10 } from "fs";
1797
1797
  import { createConnection as createConnection2 } from "net";
1798
1798
 
1799
1799
  // app/lib/network.ts
@@ -3843,7 +3843,7 @@ import {
3843
3843
  // app/lib/attachments.ts
3844
3844
  import { randomUUID as randomUUID3 } from "crypto";
3845
3845
  import { mkdir, writeFile } from "fs/promises";
3846
- import { realpathSync as realpathSync2, existsSync as existsSync4 } from "fs";
3846
+ import { realpathSync as realpathSync2, existsSync as existsSync4, readFileSync as readFileSync8, readdirSync as readdirSync3 } from "fs";
3847
3847
  import { resolve as resolve6, extname } from "path";
3848
3848
 
3849
3849
  // app/lib/data-path.ts
@@ -3926,11 +3926,15 @@ function resolveScratchpadPath(raw, scratchpadRootAbs) {
3926
3926
  import { unzipSync, strFromU8 } from "fflate";
3927
3927
  var DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
3928
3928
  var ODT = "application/vnd.oasis.opendocument.text";
3929
+ var XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
3930
+ var ODS = "application/vnd.oasis.opendocument.spreadsheet";
3929
3931
  var CONVERTIBLE_DOC_MIME = /* @__PURE__ */ new Set([
3930
3932
  DOCX,
3931
3933
  ODT,
3932
3934
  "text/rtf",
3933
- "application/rtf"
3935
+ "application/rtf",
3936
+ XLSX,
3937
+ ODS
3934
3938
  ]);
3935
3939
  function bareMime(mimeType) {
3936
3940
  return mimeType.split(";")[0].trim().toLowerCase();
@@ -3964,6 +3968,80 @@ function odtToText(buffer) {
3964
3968
  const stripped = withBreaks.replace(/<[^>]+>/g, "");
3965
3969
  return unescapeXml(stripped).replace(/\n{3,}/g, "\n\n").trim();
3966
3970
  }
3971
+ function sheetOrdinal(name) {
3972
+ const m = name.match(/sheet(\d+)\.xml$/);
3973
+ return m ? parseInt(m[1], 10) : 0;
3974
+ }
3975
+ function xlsxToText(buffer) {
3976
+ const files = unzipSync(new Uint8Array(buffer));
3977
+ const shared = [];
3978
+ const sharedBytes = files["xl/sharedStrings.xml"];
3979
+ if (sharedBytes) {
3980
+ for (const si of strFromU8(sharedBytes).split("</si>")) {
3981
+ if (!si.includes("<si")) continue;
3982
+ const base = si.replace(/<rPh\b[\s\S]*?<\/rPh>/g, "");
3983
+ let s = "";
3984
+ for (const m of base.matchAll(/<t\b[^>]*>([\s\S]*?)<\/t>/g)) s += m[1];
3985
+ shared.push(unescapeXml(s));
3986
+ }
3987
+ }
3988
+ const sheetKeys = Object.keys(files).filter((n) => /^xl\/worksheets\/sheet\d+\.xml$/.test(n)).sort((a, b) => sheetOrdinal(a) - sheetOrdinal(b));
3989
+ const sheets = [];
3990
+ for (const key of sheetKeys) {
3991
+ const xml = strFromU8(files[key]);
3992
+ const rows = [];
3993
+ for (const rowXml of xml.split("</row>")) {
3994
+ if (!rowXml.includes("<row")) continue;
3995
+ const cells = [];
3996
+ for (const c of rowXml.matchAll(/<c\b([^>]*)>([\s\S]*?)<\/c>/g)) {
3997
+ const attrs = c[1];
3998
+ const body = c[2];
3999
+ if (/\bt="inlineStr"/.test(attrs)) {
4000
+ let s = "";
4001
+ for (const m of body.matchAll(/<t\b[^>]*>([\s\S]*?)<\/t>/g)) s += m[1];
4002
+ cells.push(unescapeXml(s));
4003
+ continue;
4004
+ }
4005
+ const v = body.match(/<v\b[^>]*>([\s\S]*?)<\/v>/);
4006
+ if (!v) {
4007
+ cells.push("");
4008
+ continue;
4009
+ }
4010
+ if (/\bt="s"/.test(attrs)) {
4011
+ const idx = parseInt(v[1], 10);
4012
+ cells.push(Number.isInteger(idx) && idx >= 0 && idx < shared.length ? shared[idx] : "");
4013
+ } else {
4014
+ cells.push(unescapeXml(v[1]));
4015
+ }
4016
+ }
4017
+ while (cells.length && cells[cells.length - 1] === "") cells.pop();
4018
+ if (cells.length) rows.push(cells.join(" "));
4019
+ }
4020
+ if (rows.length) sheets.push(rows.join("\n"));
4021
+ }
4022
+ return sheets.join("\n\n");
4023
+ }
4024
+ function odsToText(buffer) {
4025
+ const xml = unzipEntry(buffer, "content.xml");
4026
+ const tables = [];
4027
+ for (const tableXml of xml.split(/<table:table(?=[\s>])/).slice(1)) {
4028
+ const rows = [];
4029
+ for (const rowXml of tableXml.split(/<table:table-row\b/).slice(1)) {
4030
+ const cells = rowXml.split(/<table:(?:covered-)?table-cell\b/).slice(1).map((frag) => {
4031
+ const withBreaks = frag.replace(/<text:line-break\b[^>]*\/?>/g, "\n").replace(/<text:tab\b[^>]*\/?>/g, " ");
4032
+ let s = "";
4033
+ for (const m of withBreaks.matchAll(/<text:p\b[^>]*>([\s\S]*?)<\/text:p>/g)) {
4034
+ s += (s ? "\n" : "") + m[1].replace(/<[^>]+>/g, "");
4035
+ }
4036
+ return unescapeXml(s);
4037
+ });
4038
+ while (cells.length && cells[cells.length - 1] === "") cells.pop();
4039
+ if (cells.length) rows.push(cells.join(" "));
4040
+ }
4041
+ if (rows.length) tables.push(rows.join("\n"));
4042
+ }
4043
+ return tables.join("\n\n");
4044
+ }
3967
4045
  var RTF_DROP_DEST = /^\\(?:\*|fonttbl|colortbl|stylesheet|listtable|info|pict)\b/;
3968
4046
  function stripRtfDestinations(s) {
3969
4047
  let out = "";
@@ -4004,6 +4082,8 @@ function docToText(buffer, mimeType) {
4004
4082
  const mime = bareMime(mimeType);
4005
4083
  if (mime === DOCX) return docxToText(buffer);
4006
4084
  if (mime === ODT) return odtToText(buffer);
4085
+ if (mime === XLSX) return xlsxToText(buffer);
4086
+ if (mime === ODS) return odsToText(buffer);
4007
4087
  if (mime === "text/rtf" || mime === "application/rtf") return rtfToText(buffer.toString("latin1"));
4008
4088
  return null;
4009
4089
  }
@@ -4033,6 +4113,21 @@ var SUPPORTED_MIME_TYPES = /* @__PURE__ */ new Set([
4033
4113
  "application/vnd.oasis.opendocument.text",
4034
4114
  "text/rtf",
4035
4115
  "application/rtf",
4116
+ // Task 1607 — spreadsheets + presentations operators send. These land
4117
+ // download-only in the reader; the Read tool cannot parse the binary
4118
+ // containers and agent-readable text extraction for them is task 1608.
4119
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
4120
+ // xlsx
4121
+ "application/vnd.ms-excel",
4122
+ // xls
4123
+ "application/vnd.oasis.opendocument.spreadsheet",
4124
+ // ods
4125
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
4126
+ // pptx
4127
+ "application/vnd.ms-powerpoint",
4128
+ // ppt
4129
+ "application/vnd.oasis.opendocument.presentation",
4130
+ // odp
4036
4131
  // Task 1240 — voice recordings persist as playable clips in the admin bubble.
4037
4132
  // These match AUDIO_MIME_TYPES in app/lib/stt/audio-mime.ts exactly.
4038
4133
  "audio/ogg",
@@ -4049,6 +4144,7 @@ var MAX_FILES_PER_MESSAGE = 5;
4049
4144
  var MAX_DATA_UPLOAD_BYTES = 2 * 1024 * 1024 * 1024;
4050
4145
  var MAX_ZIP_UNCOMPRESSED_BYTES = 100 * 1024 * 1024;
4051
4146
  var EXTRACTED_TEXT_SUFFIX = ".extracted.txt";
4147
+ var ATTACHMENT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4052
4148
  function assertSupportedMime(mimeType) {
4053
4149
  const bare = mimeType.split(";")[0].trim().toLowerCase();
4054
4150
  if (!SUPPORTED_MIME_TYPES.has(bare)) {
@@ -4057,22 +4153,47 @@ function assertSupportedMime(mimeType) {
4057
4153
  );
4058
4154
  }
4059
4155
  }
4060
- function isSupportedMime(mimeType) {
4061
- return SUPPORTED_MIME_TYPES.has(mimeType.split(";")[0].trim().toLowerCase());
4156
+ function isInlineSafeMime(mimeType) {
4157
+ const bare = mimeType.split(";")[0].trim().toLowerCase();
4158
+ if (bare === "image/svg+xml") return false;
4159
+ return bare.startsWith("image/") || bare === "application/pdf" || bare.startsWith("audio/") || bare === "video/mp4";
4062
4160
  }
4063
4161
  async function storeMediaBuffer(accountId, attachmentId, filename, mimeType, buffer) {
4064
- assertSupportedMime(mimeType);
4065
4162
  if (buffer.byteLength > MAX_FILE_SIZE_BYTES) {
4066
4163
  throw new Error(
4067
4164
  `Media exceeds the 50 MB limit (${(buffer.byteLength / 1024 / 1024).toFixed(1)} MB).`
4068
4165
  );
4069
4166
  }
4070
- return writeAttachment(accountId, "", filename, mimeType, buffer.byteLength, buffer, attachmentId, true);
4167
+ return writeAttachment(accountId, "", filename, mimeType, buffer.byteLength, buffer, attachmentId);
4071
4168
  }
4072
4169
  function attachmentExists(accountId, attachmentId) {
4073
4170
  return existsSync4(resolve6(uploadsDirFor(accountId, "", attachmentId), `${attachmentId}.meta.json`));
4074
4171
  }
4075
- async function writeAttachment(accountId, subPath, filename, mimeType, sizeBytes, buffer, explicitId, skipDocToText = false) {
4172
+ function resolveStoredAttachment(accountId, attachmentId) {
4173
+ if (!ATTACHMENT_ID_RE.test(attachmentId)) return null;
4174
+ const dir = uploadsDirFor(accountId, "", attachmentId);
4175
+ let meta;
4176
+ try {
4177
+ meta = JSON.parse(readFileSync8(resolve6(dir, `${attachmentId}.meta.json`), "utf8"));
4178
+ } catch {
4179
+ return null;
4180
+ }
4181
+ if (!meta || typeof meta !== "object") return null;
4182
+ const { filename, mimeType } = meta;
4183
+ if (typeof filename !== "string" || typeof mimeType !== "string") return null;
4184
+ let entries;
4185
+ try {
4186
+ entries = readdirSync3(dir, { withFileTypes: true });
4187
+ } catch {
4188
+ return null;
4189
+ }
4190
+ const dataFile = entries.find((e) => e.isFile() && !e.name.endsWith(".meta.json") && !e.name.endsWith(EXTRACTED_TEXT_SUFFIX));
4191
+ if (!dataFile) return null;
4192
+ const readableSibling = `${attachmentId}${EXTRACTED_TEXT_SUFFIX}`;
4193
+ const readableTextPath = entries.some((e) => e.isFile() && e.name === readableSibling) ? resolve6(dir, readableSibling) : void 0;
4194
+ return { attachmentId, filename, mimeType, path: resolve6(dir, dataFile.name), readableTextPath };
4195
+ }
4196
+ async function writeAttachment(accountId, subPath, filename, mimeType, sizeBytes, buffer, explicitId) {
4076
4197
  const attachmentId = explicitId ?? randomUUID3();
4077
4198
  const dir = uploadsDirFor(accountId, subPath, attachmentId);
4078
4199
  await mkdir(dir, { recursive: true });
@@ -4090,11 +4211,23 @@ async function writeAttachment(accountId, subPath, filename, mimeType, sizeBytes
4090
4211
  await writeFile(storagePath, buffer);
4091
4212
  await writeFile(metaPath, JSON.stringify(meta, null, 2));
4092
4213
  let readableTextPath;
4093
- if (!skipDocToText && CONVERTIBLE_DOC_MIME.has(mimeType.split(";")[0].trim().toLowerCase())) {
4094
- const text = docToText(buffer, mimeType);
4214
+ const bare = mimeType.split(";")[0].trim().toLowerCase();
4215
+ if (CONVERTIBLE_DOC_MIME.has(bare)) {
4216
+ let text = null;
4217
+ try {
4218
+ text = docToText(buffer, mimeType);
4219
+ } catch {
4220
+ text = null;
4221
+ }
4222
+ console.error(`[doc-to-text] op=convert mime=${bare} ok=${text !== null} chars=${text?.length ?? 0}`);
4095
4223
  if (text) {
4096
- readableTextPath = resolve6(dir, `${attachmentId}${EXTRACTED_TEXT_SUFFIX}`);
4097
- await writeFile(readableTextPath, text);
4224
+ const siblingPath = resolve6(dir, `${attachmentId}${EXTRACTED_TEXT_SUFFIX}`);
4225
+ try {
4226
+ await writeFile(siblingPath, text);
4227
+ readableTextPath = siblingPath;
4228
+ } catch (err) {
4229
+ console.error(`[doc-to-text] op=sibling-write mime=${bare} ok=false reason=${String(err).slice(0, 80)}`);
4230
+ }
4098
4231
  }
4099
4232
  }
4100
4233
  return { attachmentId, filename, storagePath, metaPath, mimeType, sizeBytes, readableTextPath };
@@ -4141,6 +4274,13 @@ var MIME_BY_EXT = {
4141
4274
  ".odt": "application/vnd.oasis.opendocument.text",
4142
4275
  ".rtf": "text/rtf",
4143
4276
  ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
4277
+ // Task 1607 — legacy + OpenDocument office extensions, so a device-stored file
4278
+ // attached to a WhatsApp reply (detectMimeType → isMimeAdmitted, whatsapp-reader.ts)
4279
+ // resolves to the mime now admitted in SUPPORTED_MIME_TYPES rather than octet-stream.
4280
+ ".xls": "application/vnd.ms-excel",
4281
+ ".ods": "application/vnd.oasis.opendocument.spreadsheet",
4282
+ ".ppt": "application/vnd.ms-powerpoint",
4283
+ ".odp": "application/vnd.oasis.opendocument.presentation",
4144
4284
  ".ics": "text/calendar"
4145
4285
  };
4146
4286
  function detectMimeType(filePath) {
@@ -4978,20 +5118,6 @@ function attachSelfIdRefreshOnCredsUpdate(conn) {
4978
5118
  function reloadConfig(accountConfig) {
4979
5119
  loadConfig(accountConfig);
4980
5120
  }
4981
- function getMessages(accountId, jid, limit) {
4982
- const entries = messageStore.get(messageStoreKey(accountId, jid)) ?? [];
4983
- return limit ? entries.slice(-limit) : entries.slice();
4984
- }
4985
- function listConversationJids(accountId) {
4986
- const prefix = messageStoreKey(accountId, "");
4987
- const jids = [];
4988
- for (const key of messageStore.keys()) {
4989
- if (key.startsWith(prefix)) {
4990
- jids.push(key.slice(prefix.length));
4991
- }
4992
- }
4993
- return jids;
4994
- }
4995
5121
  function listStoreAccountKeys() {
4996
5122
  const accounts = /* @__PURE__ */ new Set();
4997
5123
  for (const key of messageStore.keys()) {
@@ -5205,7 +5331,7 @@ function monitorInbound(conn) {
5205
5331
  if (!msg.message) continue;
5206
5332
  let receiptDeferredToWorker = false;
5207
5333
  const extracted = extractMessage(msg);
5208
- const mediaServable = !!(extracted.mediaType && extracted.mimetype && isSupportedMime(extracted.mimetype));
5334
+ const mediaServable = !!(extracted.mediaType && extracted.mimetype);
5209
5335
  if (extracted.text || mediaServable) {
5210
5336
  const isGroup = isGroupJid(remoteJid);
5211
5337
  const senderJid = isGroup ? msg.key.participant ?? remoteJid : remoteJid;
@@ -5602,7 +5728,7 @@ async function handleInboundMessage(conn, msg) {
5602
5728
  // app/lib/vnc.ts
5603
5729
  import { spawnSync, execFileSync } from "child_process";
5604
5730
  import { createConnection } from "net";
5605
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
5731
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "fs";
5606
5732
  import { resolve as resolve7 } from "path";
5607
5733
  var PLATFORM_ROOT4 = process.env.MAXY_PLATFORM_ROOT ?? resolve7(process.cwd(), "..");
5608
5734
  var VNC_SCRIPT = resolve7(PLATFORM_ROOT4, "scripts/vnc.sh");
@@ -5660,7 +5786,7 @@ function discoverNativeDisplay() {
5660
5786
  const leaderPid = leaderResult.stdout?.trim();
5661
5787
  if (leaderPid) {
5662
5788
  try {
5663
- const environ = readFileSync8(`/proc/${leaderPid}/environ`, "utf8");
5789
+ const environ = readFileSync9(`/proc/${leaderPid}/environ`, "utf8");
5664
5790
  const match = environ.split("\0").find((e) => e.startsWith("WAYLAND_DISPLAY="));
5665
5791
  if (match) waylandDisplay = match.split("=")[1];
5666
5792
  } catch {
@@ -5928,7 +6054,7 @@ app.get("/", async (c) => {
5928
6054
  let pinConfigured = false;
5929
6055
  try {
5930
6056
  if (existsSync5(USERS_FILE)) {
5931
- const raw = readFileSync9(USERS_FILE, "utf-8").trim();
6057
+ const raw = readFileSync10(USERS_FILE, "utf-8").trim();
5932
6058
  if (raw) {
5933
6059
  const users = JSON.parse(raw);
5934
6060
  pinConfigured = Array.isArray(users) && users.length > 0;
@@ -6600,13 +6726,13 @@ async function sendTelegramText(botToken, chatId, text) {
6600
6726
 
6601
6727
  // server/routes/whatsapp.ts
6602
6728
  import { join as join10, resolve as resolve10 } from "path";
6603
- import { readdirSync as readdirSync4, readFileSync as readFileSync11, existsSync as existsSync7 } from "fs";
6729
+ import { readdirSync as readdirSync5, readFileSync as readFileSync12, existsSync as existsSync7 } from "fs";
6604
6730
 
6605
6731
  // app/lib/whatsapp/login.ts
6606
6732
  import { randomUUID as randomUUID6 } from "crypto";
6607
6733
 
6608
6734
  // app/lib/whatsapp/config-persist.ts
6609
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync4, existsSync as existsSync6 } from "fs";
6735
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync4, existsSync as existsSync6 } from "fs";
6610
6736
  import { resolve as resolve8, join as join8, dirname as dirname2, basename as basename2 } from "path";
6611
6737
  var TAG17 = "[whatsapp:config]";
6612
6738
  function configPath(accountDir) {
@@ -6615,7 +6741,7 @@ function configPath(accountDir) {
6615
6741
  function readConfig(accountDir) {
6616
6742
  const path2 = configPath(accountDir);
6617
6743
  if (!existsSync6(path2)) throw new Error(`account.json not found at ${path2}`);
6618
- return JSON.parse(readFileSync10(path2, "utf-8"));
6744
+ return JSON.parse(readFileSync11(path2, "utf-8"));
6619
6745
  }
6620
6746
  function writeConfig(accountDir, config) {
6621
6747
  const path2 = configPath(accountDir);
@@ -7536,13 +7662,13 @@ function serializeWhatsAppSchema() {
7536
7662
  }
7537
7663
 
7538
7664
  // app/lib/whatsapp/status-reconcile.ts
7539
- import { readdirSync as readdirSync3 } from "fs";
7665
+ import { readdirSync as readdirSync4 } from "fs";
7540
7666
  import { join as join9 } from "path";
7541
7667
  var TAG20 = "[whatsapp:reconcile]";
7542
7668
  var HALF_REGISTERED_MIN_AGE_MS = 5 * 6e4;
7543
7669
  function listCredsAccountIds(credsRoot) {
7544
7670
  try {
7545
- return readdirSync3(credsRoot, { withFileTypes: true }).filter((e) => e.isDirectory() && hasCredsSync(join9(credsRoot, e.name))).map((e) => e.name);
7671
+ return readdirSync4(credsRoot, { withFileTypes: true }).filter((e) => e.isDirectory() && hasCredsSync(join9(credsRoot, e.name))).map((e) => e.name);
7546
7672
  } catch {
7547
7673
  return [];
7548
7674
  }
@@ -7689,6 +7815,15 @@ function authorizeRecallRead(input) {
7689
7815
  // server/routes/whatsapp.ts
7690
7816
  var TAG21 = "[whatsapp:api]";
7691
7817
  var PLATFORM_ROOT5 = process.env.MAXY_PLATFORM_ROOT || "";
7818
+ function recallAttachment(storeAccountId, attachmentId) {
7819
+ if (!attachmentId) return void 0;
7820
+ const a = resolveStoredAttachment(storeAccountId, attachmentId);
7821
+ if (!a) {
7822
+ console.error(`[whatsapp-read-tool] op=attachment-miss attachmentId=${attachmentId.slice(0, 8)} storeAccount=${storeAccountId}`);
7823
+ return void 0;
7824
+ }
7825
+ return a;
7826
+ }
7692
7827
  function projectionFor(requestedAccountId) {
7693
7828
  const validAccounts = listValidAccounts();
7694
7829
  if (validAccounts.length <= 1) return { storeAccountId: requestedAccountId, managerPhones: null };
@@ -7880,13 +8015,13 @@ app2.post("/config", async (c) => {
7880
8015
  const agents = [];
7881
8016
  if (existsSync7(agentsDir)) {
7882
8017
  try {
7883
- const entries = readdirSync4(agentsDir, { withFileTypes: true });
8018
+ const entries = readdirSync5(agentsDir, { withFileTypes: true });
7884
8019
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
7885
8020
  if (!entry.isDirectory() || entry.name === "admin") continue;
7886
8021
  const configPath2 = resolve10(agentsDir, entry.name, "config.json");
7887
8022
  if (!existsSync7(configPath2)) continue;
7888
8023
  try {
7889
- const config = JSON.parse(readFileSync11(configPath2, "utf-8"));
8024
+ const config = JSON.parse(readFileSync12(configPath2, "utf-8"));
7890
8025
  agents.push({ slug: entry.name, displayName: config.displayName ?? entry.name });
7891
8026
  } catch {
7892
8027
  console.error(`${TAG21} config action=list-public-agents error="failed to parse config.json for agent ${entry.name}" \u2014 skipping`);
@@ -8031,20 +8166,23 @@ app2.get("/conversations", (c) => {
8031
8166
  const requestedAccountId = validateAccountId(c.req.query("accountId") ?? null);
8032
8167
  const denial = callerScopeDenial(c, requestedAccountId);
8033
8168
  if (denial) return denial;
8034
- const accountId = resolveReadStoreKey(requestedAccountId);
8035
- const jids = listConversationJids(accountId);
8036
- const conversations = jids.map((jid) => {
8037
- const messages = getMessages(accountId, jid);
8038
- const lastMessage = messages.length > 0 ? messages[messages.length - 1] : null;
8169
+ const projection = projectionFor(requestedAccountId);
8170
+ const storeAccountId = resolveReadStoreKey(projection.storeAccountId);
8171
+ const projecting = projection.managerPhones != null;
8172
+ const managerPhonesBound = projection.managerPhones?.length ?? 0;
8173
+ const allConversations = readAllConversations(storeAccountId);
8174
+ const scoped = projection.managerPhones ? filterConversationsForSubAccount(allConversations, projection.managerPhones) : allConversations;
8175
+ const conversations = [...scoped.entries()].map(([jid, messages]) => {
8176
+ const last = messages.length > 0 ? messages[messages.length - 1] : null;
8039
8177
  return {
8040
8178
  jid,
8041
8179
  type: isGroupJid(jid) ? "group" : "dm",
8042
- lastMessageTimestamp: lastMessage?.timestamp ?? null,
8180
+ lastMessageTimestamp: last ? Math.floor(Date.parse(last.dateSent) / 1e3) : null,
8043
8181
  messageCount: messages.length
8044
8182
  };
8045
8183
  });
8046
8184
  conversations.sort((a, b) => (b.lastMessageTimestamp ?? 0) - (a.lastMessageTimestamp ?? 0));
8047
- console.error(`${TAG21} conversations requested=${requestedAccountId} account=${accountId} storeKeyFound=${jids.length > 0} count=${conversations.length}`);
8185
+ console.error(`${TAG21} conversations requested=${requestedAccountId} account=${storeAccountId} projected=${projecting} storeAccount=${projecting ? storeAccountId : "self"} managerPhonesBound=${managerPhonesBound} storeKeyFound=${allConversations.size > 0} count=${conversations.length}`);
8048
8186
  return c.json({ conversations });
8049
8187
  } catch (err) {
8050
8188
  console.error(`${TAG21} conversations error: ${String(err)}`);
@@ -8056,7 +8194,6 @@ app2.get("/messages", (c) => {
8056
8194
  const requestedAccountId = validateAccountId(c.req.query("accountId") ?? null);
8057
8195
  const denial = callerScopeDenial(c, requestedAccountId);
8058
8196
  if (denial) return denial;
8059
- const accountId = resolveReadStoreKey(requestedAccountId);
8060
8197
  const jid = c.req.query("jid");
8061
8198
  if (!jid) {
8062
8199
  return c.json({ error: "Missing required parameter: jid" }, 400);
@@ -8064,9 +8201,33 @@ app2.get("/messages", (c) => {
8064
8201
  const limitParam = c.req.query("limit");
8065
8202
  const limit = limitParam ? parseInt(limitParam, 10) : void 0;
8066
8203
  const effectiveLimit = limit && !Number.isNaN(limit) && limit > 0 ? limit : void 0;
8067
- const messages = getMessages(accountId, jid, effectiveLimit);
8068
- const storeKeyFound = listConversationJids(accountId).length > 0;
8069
- console.error(`${TAG21} messages requested=${requestedAccountId} account=${accountId} jid=${jid} storeKeyFound=${storeKeyFound} limit=${effectiveLimit ?? "all"} returned=${messages.length}`);
8204
+ const projection = projectionFor(requestedAccountId);
8205
+ const storeAccountId = resolveReadStoreKey(projection.storeAccountId);
8206
+ const projecting = projection.managerPhones != null;
8207
+ const managerPhonesBound = projection.managerPhones?.length ?? 0;
8208
+ const storeMessages = readConversation(storeAccountId, jid);
8209
+ const counterpartyBound = !projecting || counterpartyBoundToSubAccount(
8210
+ projection.managerPhones,
8211
+ conversationCounterpartyPhone(jid, storeMessages[0]?.senderTelephone ?? "")
8212
+ );
8213
+ const bounded = counterpartyBound ? storeMessages : [];
8214
+ const limited = effectiveLimit ? bounded.slice(-effectiveLimit) : bounded;
8215
+ let withAttachments = 0;
8216
+ const messages = limited.map((m) => {
8217
+ const attachment = recallAttachment(storeAccountId, m.attachmentId);
8218
+ if (attachment) withAttachments += 1;
8219
+ return {
8220
+ id: m.messageId,
8221
+ sender: m.senderTelephone,
8222
+ senderName: m.senderName ?? void 0,
8223
+ body: m.body,
8224
+ timestamp: Math.floor(Date.parse(m.dateSent) / 1e3),
8225
+ fromMe: m.fromMe,
8226
+ quoted: m.quotedId ? { id: m.quotedId, sender: m.quotedSender ?? void 0 } : void 0,
8227
+ attachment
8228
+ };
8229
+ });
8230
+ console.error(`${TAG21} messages requested=${requestedAccountId} account=${storeAccountId} jid=${jid} projected=${projecting} storeAccount=${projecting ? storeAccountId : "self"} managerPhonesBound=${managerPhonesBound} storeKeyFound=${storeMessages.length > 0} limit=${effectiveLimit ?? "all"} returned=${messages.length} withAttachments=${withAttachments}`);
8070
8231
  return c.json({ messages });
8071
8232
  } catch (err) {
8072
8233
  console.error(`${TAG21} messages error: ${String(err)}`);
@@ -8090,6 +8251,7 @@ app2.get("/conversation-graph-state", async (c) => {
8090
8251
  const rows2 = [];
8091
8252
  const nameCache = /* @__PURE__ */ new Map();
8092
8253
  let unresolvedSenders = 0;
8254
+ let withAttachments2 = 0;
8093
8255
  let session = null;
8094
8256
  try {
8095
8257
  session = getSession();
@@ -8105,6 +8267,8 @@ app2.get("/conversation-graph-state", async (c) => {
8105
8267
  nameCache.set(m.senderTelephone, resolved);
8106
8268
  if (!resolved) unresolvedSenders += 1;
8107
8269
  }
8270
+ const attachment = recallAttachment(projection2.storeAccountId, m.attachmentId);
8271
+ if (attachment) withAttachments2 += 1;
8108
8272
  rows2.push({
8109
8273
  remoteJid: remoteJid2,
8110
8274
  messageId: m.messageId,
@@ -8112,7 +8276,8 @@ app2.get("/conversation-graph-state", async (c) => {
8112
8276
  fromMe: m.fromMe,
8113
8277
  senderTelephone: m.senderTelephone,
8114
8278
  senderName: nameCache.get(m.senderTelephone) ?? m.senderName,
8115
- body: m.body
8279
+ body: m.body,
8280
+ attachment
8116
8281
  });
8117
8282
  }
8118
8283
  }
@@ -8120,7 +8285,7 @@ app2.get("/conversation-graph-state", async (c) => {
8120
8285
  if (session) await session.close();
8121
8286
  }
8122
8287
  const ms2 = Date.now() - t0;
8123
- console.error(`[whatsapp-read-tool] op=query scope=${requestedAccountId} storeAccount=${projection2.storeAccountId} managerPhonesBound=${managerPhonesBound} resultCount=${rows2.length} unresolvedSenders=${unresolvedSenders} ms=${ms2}`);
8288
+ console.error(`[whatsapp-read-tool] op=query scope=${requestedAccountId} storeAccount=${projection2.storeAccountId} managerPhonesBound=${managerPhonesBound} resultCount=${rows2.length} unresolvedSenders=${unresolvedSenders} withAttachments=${withAttachments2} ms=${ms2}`);
8124
8289
  return c.json({ accountId: requestedAccountId, rows: rows2, unresolvedSenders, ms: ms2, managerPhonesBound });
8125
8290
  }
8126
8291
  let remoteJid;
@@ -8144,15 +8309,21 @@ app2.get("/conversation-graph-state", async (c) => {
8144
8309
  );
8145
8310
  const messages = counterpartyBound ? storeMessages : [];
8146
8311
  const sessionId = messages[0]?.sessionId ?? null;
8147
- const rows = messages.map((m) => ({
8148
- messageId: m.messageId,
8149
- dateSent: m.dateSent,
8150
- fromMe: m.fromMe,
8151
- senderTelephone: m.senderTelephone,
8152
- body: m.body
8153
- }));
8312
+ let withAttachments = 0;
8313
+ const rows = messages.map((m) => {
8314
+ const attachment = recallAttachment(projection.storeAccountId, m.attachmentId);
8315
+ if (attachment) withAttachments += 1;
8316
+ return {
8317
+ messageId: m.messageId,
8318
+ dateSent: m.dateSent,
8319
+ fromMe: m.fromMe,
8320
+ senderTelephone: m.senderTelephone,
8321
+ body: m.body,
8322
+ attachment
8323
+ };
8324
+ });
8154
8325
  const ms = Date.now() - t0;
8155
- console.error(`[whatsapp-read-tool] op=query mode=single scope=${accountId} storeAccount=${projection.storeAccountId} graphRows=${rows.length} sessionId=${sessionId ?? "null"} ms=${ms}`);
8326
+ console.error(`[whatsapp-read-tool] op=query mode=single scope=${accountId} storeAccount=${projection.storeAccountId} graphRows=${rows.length} sessionId=${sessionId ?? "null"} withAttachments=${withAttachments} ms=${ms}`);
8156
8327
  return c.json({
8157
8328
  cacheKey,
8158
8329
  sessionId,
@@ -8201,15 +8372,15 @@ app2.get("/group-info", async (c) => {
8201
8372
  var whatsapp_default = app2;
8202
8373
 
8203
8374
  // server/routes/whatsapp-reader.ts
8204
- import { readFileSync as readFileSync15, watch, statSync as statSync5, openSync, readSync, closeSync, existsSync as existsSync10, readdirSync as readdirSync9, realpathSync as realpathSync4 } from "fs";
8375
+ import { readFileSync as readFileSync16, watch, statSync as statSync5, openSync, readSync, closeSync, existsSync as existsSync10, readdirSync as readdirSync10, realpathSync as realpathSync4 } from "fs";
8205
8376
  import { basename as basename4, dirname as dirname4, isAbsolute, join as join15, relative as relative2, resolve as resolve12, sep as sep3 } from "path";
8206
8377
 
8207
8378
  // server/routes/admin/sidebar-sessions.ts
8208
- import { readdirSync as readdirSync7, readFileSync as readFileSync13, statSync as statSync4 } from "fs";
8379
+ import { readdirSync as readdirSync8, readFileSync as readFileSync14, statSync as statSync4 } from "fs";
8209
8380
  import { dirname as dirname3, join as join13, resolve as resolve11 } from "path";
8210
8381
 
8211
8382
  // ../services/claude-session-manager/src/jsonl-path.ts
8212
- import { existsSync as existsSync8, readdirSync as readdirSync5 } from "fs";
8383
+ import { existsSync as existsSync8, readdirSync as readdirSync6 } from "fs";
8213
8384
  import { homedir } from "os";
8214
8385
  import { join as join11 } from "path";
8215
8386
  function findExistingJsonlForSessionId(claudeConfigDir2, sessionId) {
@@ -8217,7 +8388,7 @@ function findExistingJsonlForSessionId(claudeConfigDir2, sessionId) {
8217
8388
  if (!existsSync8(projectsRoot)) return null;
8218
8389
  let slugs;
8219
8390
  try {
8220
- slugs = readdirSync5(projectsRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
8391
+ slugs = readdirSync6(projectsRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
8221
8392
  } catch {
8222
8393
  return null;
8223
8394
  }
@@ -8233,14 +8404,14 @@ function findExistingJsonlForSessionId(claudeConfigDir2, sessionId) {
8233
8404
 
8234
8405
  // app/lib/admin-identity/pin-validator.ts
8235
8406
  import { createHash as createHash3 } from "crypto";
8236
- import { existsSync as existsSync9, readFileSync as readFileSync12, readdirSync as readdirSync6, statSync as statSync3 } from "fs";
8407
+ import { existsSync as existsSync9, readFileSync as readFileSync13, readdirSync as readdirSync7, statSync as statSync3 } from "fs";
8237
8408
  import { join as join12 } from "path";
8238
8409
  function hashPin(pin) {
8239
8410
  return createHash3("sha256").update(pin).digest("hex");
8240
8411
  }
8241
8412
  function readUsersFile(usersFilePath) {
8242
8413
  if (!existsSync9(usersFilePath)) return null;
8243
- const raw = readFileSync12(usersFilePath, "utf-8").trim();
8414
+ const raw = readFileSync13(usersFilePath, "utf-8").trim();
8244
8415
  if (!raw) return [];
8245
8416
  return JSON.parse(raw);
8246
8417
  }
@@ -8359,7 +8530,7 @@ function enumerateJsonls(projectsRoot) {
8359
8530
  const out = [];
8360
8531
  let slugs;
8361
8532
  try {
8362
- slugs = readdirSync7(projectsRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
8533
+ slugs = readdirSync8(projectsRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
8363
8534
  } catch (err) {
8364
8535
  if (err.code === "ENOENT") return out;
8365
8536
  throw err;
@@ -8368,7 +8539,7 @@ function enumerateJsonls(projectsRoot) {
8368
8539
  const slugDir = join13(projectsRoot, slug);
8369
8540
  let entries;
8370
8541
  try {
8371
- entries = readdirSync7(slugDir, { withFileTypes: true });
8542
+ entries = readdirSync8(slugDir, { withFileTypes: true });
8372
8543
  } catch (err) {
8373
8544
  const code = err.code ?? "unknown";
8374
8545
  console.error(`[admin-sessions-list] slug-skipped slug=${slug} code=${code}`);
@@ -8381,7 +8552,7 @@ function enumerateJsonls(projectsRoot) {
8381
8552
  const subDir = join13(slugDir, entry.name);
8382
8553
  let subEntries;
8383
8554
  try {
8384
- subEntries = readdirSync7(subDir, { withFileTypes: true });
8555
+ subEntries = readdirSync8(subDir, { withFileTypes: true });
8385
8556
  } catch (err) {
8386
8557
  const code = err.code ?? "unknown";
8387
8558
  console.error(`[admin-sessions-list] subagents-skipped slug=${slug} code=${code}`);
@@ -8396,7 +8567,7 @@ function enumerateJsonls(projectsRoot) {
8396
8567
  const archiveDir = join13(slugDir, entry.name);
8397
8568
  let archiveEntries;
8398
8569
  try {
8399
- archiveEntries = readdirSync7(archiveDir, { withFileTypes: true });
8570
+ archiveEntries = readdirSync8(archiveDir, { withFileTypes: true });
8400
8571
  } catch (err) {
8401
8572
  const code = err.code ?? "unknown";
8402
8573
  console.error(`[admin-sessions-list] archive-skipped slug=${slug} code=${code}`);
@@ -8440,7 +8611,7 @@ function loadUserTitles(accountDir) {
8440
8611
  const path2 = join13(accountDir, "session-titles.json");
8441
8612
  let raw;
8442
8613
  try {
8443
- raw = readFileSync13(path2, "utf8");
8614
+ raw = readFileSync14(path2, "utf8");
8444
8615
  } catch {
8445
8616
  return out;
8446
8617
  }
@@ -8466,7 +8637,7 @@ function readSidecarMeta(metaPath) {
8466
8637
  const empty = { bridgeIds: [], role: null, channel: null, senderId: null, startedAt: null, personId: null, adminUserId: null, visitorId: null, agentSlug: null, accountId: null };
8467
8638
  let raw;
8468
8639
  try {
8469
- raw = readFileSync13(metaPath, "utf8");
8640
+ raw = readFileSync14(metaPath, "utf8");
8470
8641
  } catch {
8471
8642
  return empty;
8472
8643
  }
@@ -8608,7 +8779,7 @@ app3.get("/", requireAdminSession, async (c) => {
8608
8779
  let body;
8609
8780
  let mtimeMs;
8610
8781
  try {
8611
- body = readFileSync13(path2, "utf8");
8782
+ body = readFileSync14(path2, "utf8");
8612
8783
  mtimeMs = statSync4(path2).mtimeMs;
8613
8784
  } catch {
8614
8785
  continue;
@@ -8968,7 +9139,7 @@ function parseTranscript(lines, queuedPendingSuppress = /* @__PURE__ */ new Map(
8968
9139
  }
8969
9140
 
8970
9141
  // app/lib/whatsapp-reader/enrich-attachments.ts
8971
- import { readFileSync as readFileSync14, readdirSync as readdirSync8 } from "fs";
9142
+ import { readFileSync as readFileSync15, readdirSync as readdirSync9 } from "fs";
8972
9143
  import { join as join14 } from "path";
8973
9144
  var ADMIN_UPLOAD_PATH = /\/uploads\/(?!public\/)([0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})\//gi;
8974
9145
  function adminAttachmentIds(text) {
@@ -8990,7 +9161,7 @@ function adminAttachmentsFromText(text, uploadsBase) {
8990
9161
  function readSidecar(dir, attachmentId) {
8991
9162
  let parsed;
8992
9163
  try {
8993
- parsed = JSON.parse(readFileSync14(join14(dir, `${attachmentId}.meta.json`), "utf8"));
9164
+ parsed = JSON.parse(readFileSync15(join14(dir, `${attachmentId}.meta.json`), "utf8"));
8994
9165
  } catch {
8995
9166
  return null;
8996
9167
  }
@@ -9013,7 +9184,7 @@ function readAttachmentMeta(dir) {
9013
9184
  function listSessionAttachmentsInDir(dir) {
9014
9185
  let entries;
9015
9186
  try {
9016
- entries = readdirSync8(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
9187
+ entries = readdirSync9(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
9017
9188
  } catch {
9018
9189
  return [];
9019
9190
  }
@@ -9060,7 +9231,7 @@ function pickActivity(entries, grown, nowMs, concurrencyWindowMs) {
9060
9231
  }
9061
9232
 
9062
9233
  // app/admin-types.ts
9063
- var ACCEPT_MIME = "image/jpeg,image/png,image/gif,image/webp,application/pdf,text/plain,text/markdown,text/csv,text/html,text/calendar,application/zip,application/x-zip-compressed,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.oasis.opendocument.text,text/rtf,application/rtf,audio/ogg,audio/opus,audio/mp4,audio/x-m4a,audio/mpeg,audio/webm,audio/wav,video/mp4,.docx,.odt,.rtf,.opus,.ogg,.m4a,.mp4,.mp3,.wav,.webm";
9234
+ var ACCEPT_MIME = "image/jpeg,image/png,image/gif,image/webp,application/pdf,text/plain,text/markdown,text/csv,text/html,text/calendar,application/zip,application/x-zip-compressed,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.oasis.opendocument.text,text/rtf,application/rtf,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel,application/vnd.oasis.opendocument.spreadsheet,application/vnd.openxmlformats-officedocument.presentationml.presentation,application/vnd.ms-powerpoint,application/vnd.oasis.opendocument.presentation,audio/ogg,audio/opus,audio/mp4,audio/x-m4a,audio/mpeg,audio/webm,audio/wav,video/mp4,.docx,.odt,.rtf,.xlsx,.xls,.ods,.pptx,.ppt,.odp,.opus,.ogg,.m4a,.mp4,.mp3,.wav,.webm";
9064
9235
  var SUPPORTED_MIME_SET = new Set(ACCEPT_MIME.split(",").filter((t) => !t.startsWith(".")));
9065
9236
  var MAX_ATTACHMENTS = 5;
9066
9237
  var MAX_FILE_BYTES = 50 * 1024 * 1024;
@@ -9099,7 +9270,7 @@ app4.get("/conversations", requireAdminSession, async (c) => {
9099
9270
  }
9100
9271
  let body = "";
9101
9272
  try {
9102
- body = readFileSync15(path2, "utf8");
9273
+ body = readFileSync16(path2, "utf8");
9103
9274
  } catch {
9104
9275
  }
9105
9276
  const { title } = resolveTitle(body, sessionId, userTitles);
@@ -9268,7 +9439,7 @@ function openTaskKey(turns) {
9268
9439
  }
9269
9440
  function readSubagentMeta(dir, hex) {
9270
9441
  try {
9271
- const m = JSON.parse(readFileSync15(join15(dir, `agent-${hex}.meta.json`), "utf8"));
9442
+ const m = JSON.parse(readFileSync16(join15(dir, `agent-${hex}.meta.json`), "utf8"));
9272
9443
  if (typeof m.agentType === "string" && typeof m.description === "string" && typeof m.toolUseId === "string") {
9273
9444
  return { agentType: m.agentType, description: m.description, toolUseId: m.toolUseId };
9274
9445
  }
@@ -9291,7 +9462,7 @@ function runActivityTick(c) {
9291
9462
  c.parentGrewThisTick = false;
9292
9463
  let names = [];
9293
9464
  try {
9294
- names = readdirSync9(c.subagentsDir).filter((n) => AGENT_JSONL_RE.test(n));
9465
+ names = readdirSync10(c.subagentsDir).filter((n) => AGENT_JSONL_RE.test(n));
9295
9466
  } catch {
9296
9467
  }
9297
9468
  const entries = [];
@@ -9486,7 +9657,7 @@ app4.get("/directives", requireAdminSession, (c) => {
9486
9657
  if (!dir || !existsSync10(dir)) return c.json({ entries: [] });
9487
9658
  let names;
9488
9659
  try {
9489
- names = readdirSync9(dir).filter((n) => DIRECTIVE_NAME_RE.test(n));
9660
+ names = readdirSync10(dir).filter((n) => DIRECTIVE_NAME_RE.test(n));
9490
9661
  } catch {
9491
9662
  return c.json({ entries: [] });
9492
9663
  }
@@ -9515,7 +9686,7 @@ app4.get("/directive", requireAdminSession, (c) => {
9515
9686
  if (!path2.startsWith(dir + sep3)) return c.json({ error: "bad reference" }, 400);
9516
9687
  let body;
9517
9688
  try {
9518
- body = readFileSync15(path2, "utf8");
9689
+ body = readFileSync16(path2, "utf8");
9519
9690
  } catch {
9520
9691
  return c.json({ error: "not found" }, 404);
9521
9692
  }
@@ -9872,7 +10043,7 @@ app4.post("/reply", requireAdminSession, async (c) => {
9872
10043
  }
9873
10044
  for (let i = 0; i < resolvedRefs.length; i++) {
9874
10045
  const { absolute, mime } = resolvedRefs[i];
9875
- const file = new File([readFileSync15(absolute)], basename4(absolute), { type: mime });
10046
+ const file = new File([readFileSync16(absolute)], basename4(absolute), { type: mime });
9876
10047
  const out = await sendStagedFile(file, `ref=${i}`);
9877
10048
  if (!out.ok) {
9878
10049
  exit("error");
@@ -9891,7 +10062,7 @@ app4.post("/reply", requireAdminSession, async (c) => {
9891
10062
  var whatsapp_reader_default = app4;
9892
10063
 
9893
10064
  // server/routes/public-reader.ts
9894
- import { statSync as statSync6, openSync as openSync2, readSync as readSync2, closeSync as closeSync2, watch as watch2, readFileSync as readFileSync16, readdirSync as readdirSync10 } from "fs";
10065
+ import { statSync as statSync6, openSync as openSync2, readSync as readSync2, closeSync as closeSync2, watch as watch2, readFileSync as readFileSync17, readdirSync as readdirSync11 } from "fs";
9895
10066
  import { basename as basename5, dirname as dirname5, join as join16, resolve as resolve13, sep as sep4 } from "path";
9896
10067
 
9897
10068
  // app/lib/whatsapp-reader/delivered-kinds.ts
@@ -10125,7 +10296,7 @@ data: ${JSON.stringify(turn)}
10125
10296
  });
10126
10297
  });
10127
10298
  var ATT_TAG = "[public-attachment]";
10128
- var ATTACHMENT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
10299
+ var ATTACHMENT_ID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
10129
10300
  app5.get("/attachment/:attachmentId", (c) => {
10130
10301
  const visitor = resolveVisitor(c);
10131
10302
  const attachmentId = c.req.param("attachmentId");
@@ -10137,7 +10308,7 @@ app5.get("/attachment/:attachmentId", (c) => {
10137
10308
  return new Response("Unauthorized", { status: 401 });
10138
10309
  }
10139
10310
  console.log(`${ATT_TAG} op=request session=${ses} att=${at}`);
10140
- if (!ATTACHMENT_ID_RE.test(attachmentId) || !SESSION_ID_RE2.test(sessionId)) {
10311
+ if (!ATTACHMENT_ID_RE2.test(attachmentId) || !SESSION_ID_RE2.test(sessionId)) {
10141
10312
  console.error(`${ATT_TAG} op=denied session=${ses} att=${at} reason=bad-id`);
10142
10313
  return new Response("Not found", { status: 404 });
10143
10314
  }
@@ -10166,19 +10337,22 @@ app5.get("/attachment/:attachmentId", (c) => {
10166
10337
  let dataFile;
10167
10338
  let buffer;
10168
10339
  try {
10169
- dataFile = readdirSync10(dir).find((f) => !f.endsWith(".meta.json") && !f.endsWith(EXTRACTED_TEXT_SUFFIX));
10340
+ dataFile = readdirSync11(dir).find((f) => !f.endsWith(".meta.json") && !f.endsWith(EXTRACTED_TEXT_SUFFIX));
10170
10341
  if (!dataFile) throw new Error("no data file");
10171
- buffer = readFileSync16(resolve13(dir, dataFile));
10342
+ buffer = readFileSync17(resolve13(dir, dataFile));
10172
10343
  } catch {
10173
10344
  console.error(`${ATT_TAG} op=denied session=${ses} att=${at} reason=not-found`);
10174
10345
  return new Response("Not found", { status: 404 });
10175
10346
  }
10176
- console.log(`${ATT_TAG} op=served session=${ses} att=${at} bytes=${buffer.length} mime=${meta.mimeType}`);
10347
+ const inlineSafe = isInlineSafeMime(meta.mimeType);
10348
+ const disposition = inlineSafe ? "inline" : "attachment";
10349
+ console.log(`${ATT_TAG} op=served session=${ses} att=${at} bytes=${buffer.length} mime=${meta.mimeType} disposition=${disposition}${inlineSafe ? "" : " reason=non-preview-mime"}`);
10177
10350
  const safeName = meta.filename.replace(/["\\\r\n]/g, "_");
10178
10351
  return new Response(new Uint8Array(buffer), {
10179
10352
  headers: {
10180
10353
  "Content-Type": meta.mimeType,
10181
- "Content-Disposition": `inline; filename="${safeName}"`,
10354
+ "Content-Disposition": `${disposition}; filename="${safeName}"`,
10355
+ "X-Content-Type-Options": "nosniff",
10182
10356
  "Cache-Control": "private, max-age=3600"
10183
10357
  }
10184
10358
  });
@@ -10188,10 +10362,10 @@ var public_reader_default = app5;
10188
10362
  // server/routes/webchat.ts
10189
10363
  import { basename as basename6, dirname as dirname6 } from "path";
10190
10364
  import { join as join19 } from "path";
10191
- import { existsSync as existsSync12, readdirSync as readdirSync12, readFileSync as readFileSync19, renameSync as renameSync4, statSync as statSync7, writeFileSync as writeFileSync7 } from "fs";
10365
+ import { existsSync as existsSync12, readdirSync as readdirSync13, readFileSync as readFileSync20, renameSync as renameSync4, statSync as statSync7, writeFileSync as writeFileSync7 } from "fs";
10192
10366
 
10193
10367
  // server/canonical-webchat-override.ts
10194
- import { readFileSync as readFileSync17, writeFileSync as writeFileSync5, renameSync as renameSync2 } from "fs";
10368
+ import { readFileSync as readFileSync18, writeFileSync as writeFileSync5, renameSync as renameSync2 } from "fs";
10195
10369
  import { join as join17 } from "path";
10196
10370
  var FILE = "canonical-webchat-session.json";
10197
10371
  var UUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
@@ -10200,7 +10374,7 @@ function canonicalOverridePath(accountDir) {
10200
10374
  }
10201
10375
  function readRaw(accountDir) {
10202
10376
  try {
10203
- return JSON.parse(readFileSync17(canonicalOverridePath(accountDir), "utf8"));
10377
+ return JSON.parse(readFileSync18(canonicalOverridePath(accountDir), "utf8"));
10204
10378
  } catch {
10205
10379
  return null;
10206
10380
  }
@@ -10275,7 +10449,7 @@ function isNewSessionEffortLevel(value) {
10275
10449
  }
10276
10450
 
10277
10451
  // ../services/claude-session-manager/src/sidecar-store.ts
10278
- import { existsSync as existsSync11, mkdirSync as mkdirSync3, readdirSync as readdirSync11, readFileSync as readFileSync18, renameSync as renameSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync6 } from "fs";
10452
+ import { existsSync as existsSync11, mkdirSync as mkdirSync3, readdirSync as readdirSync12, readFileSync as readFileSync19, renameSync as renameSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync6 } from "fs";
10279
10453
  import { join as join18 } from "path";
10280
10454
  function escapeRegExp(s) {
10281
10455
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -10305,7 +10479,7 @@ function createSidecarStore(config) {
10305
10479
  function readFileAt(path2) {
10306
10480
  let raw;
10307
10481
  try {
10308
- raw = readFileSync18(path2, "utf8");
10482
+ raw = readFileSync19(path2, "utf8");
10309
10483
  } catch {
10310
10484
  return null;
10311
10485
  }
@@ -10321,7 +10495,7 @@ function createSidecarStore(config) {
10321
10495
  function readAll(sessionsDir, onSkip) {
10322
10496
  let names;
10323
10497
  try {
10324
- names = readdirSync11(sessionsDir, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name);
10498
+ names = readdirSync12(sessionsDir, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name);
10325
10499
  } catch {
10326
10500
  return [];
10327
10501
  }
@@ -10331,7 +10505,7 @@ function createSidecarStore(config) {
10331
10505
  const path2 = join18(sessionsDir, name);
10332
10506
  let raw;
10333
10507
  try {
10334
- raw = readFileSync18(path2, "utf8");
10508
+ raw = readFileSync19(path2, "utf8");
10335
10509
  } catch {
10336
10510
  onSkip?.(name, "unreadable");
10337
10511
  continue;
@@ -10414,7 +10588,7 @@ function locateSidecar(sessionId) {
10414
10588
  const projectsRoot = join19(cfg, "projects");
10415
10589
  let slugs;
10416
10590
  try {
10417
- slugs = readdirSync12(projectsRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
10591
+ slugs = readdirSync13(projectsRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
10418
10592
  } catch {
10419
10593
  return null;
10420
10594
  }
@@ -10897,7 +11071,7 @@ ${note}` : note;
10897
11071
  }
10898
11072
  const accountJsonPath = join19(account.accountDir, "account.json");
10899
11073
  try {
10900
- const parsed = JSON.parse(readFileSync19(accountJsonPath, "utf8"));
11074
+ const parsed = JSON.parse(readFileSync20(accountJsonPath, "utf8"));
10901
11075
  if (op === "model") parsed.adminModel = value;
10902
11076
  else if (op === "effort") parsed.effort = value;
10903
11077
  else parsed.adminPermissionMode = value;
@@ -11015,7 +11189,7 @@ ${note}` : note;
11015
11189
  import { resolve as resolve14 } from "path";
11016
11190
 
11017
11191
  // app/lib/claude-agent/specialist-roster.ts
11018
- import { existsSync as existsSync13, readFileSync as readFileSync20, readdirSync as readdirSync13 } from "fs";
11192
+ import { existsSync as existsSync13, readFileSync as readFileSync21, readdirSync as readdirSync14 } from "fs";
11019
11193
  import { join as join20 } from "path";
11020
11194
  function field(fm, name) {
11021
11195
  const m = fm.match(new RegExp(`^${name}:\\s*(.*?)\\r?$`, "m"));
@@ -11028,14 +11202,14 @@ function field(fm, name) {
11028
11202
  }
11029
11203
  function readSpecialistRoster(specialistsDir) {
11030
11204
  if (!existsSync13(specialistsDir)) return { specialists: [], skipped: [] };
11031
- const entries = readdirSync13(specialistsDir);
11205
+ const entries = readdirSync14(specialistsDir);
11032
11206
  const specialists = [];
11033
11207
  const skipped = [];
11034
11208
  for (const file of entries.sort()) {
11035
11209
  if (!file.endsWith(".md")) continue;
11036
11210
  let raw;
11037
11211
  try {
11038
- raw = readFileSync20(join20(specialistsDir, file), "utf-8");
11212
+ raw = readFileSync21(join20(specialistsDir, file), "utf-8");
11039
11213
  } catch (err) {
11040
11214
  skipped.push({ file, reason: err instanceof Error ? err.message : String(err) });
11041
11215
  continue;
@@ -11153,7 +11327,7 @@ var webchat_greeting_default = app6;
11153
11327
  // server/routes/telegram.ts
11154
11328
  import { homedir as homedir2 } from "os";
11155
11329
  import { resolve as resolve15, join as join21 } from "path";
11156
- import { existsSync as existsSync14, readFileSync as readFileSync21 } from "fs";
11330
+ import { existsSync as existsSync14, readFileSync as readFileSync22 } from "fs";
11157
11331
 
11158
11332
  // app/lib/telegram/gateway/instance.ts
11159
11333
  var instance = null;
@@ -11215,7 +11389,7 @@ function configDirName() {
11215
11389
  const brandPath = join21(platformRoot3, "config", "brand.json");
11216
11390
  if (existsSync14(brandPath)) {
11217
11391
  try {
11218
- return JSON.parse(readFileSync21(brandPath, "utf-8")).configDir ?? ".maxy";
11392
+ return JSON.parse(readFileSync22(brandPath, "utf-8")).configDir ?? ".maxy";
11219
11393
  } catch {
11220
11394
  }
11221
11395
  }
@@ -11238,7 +11412,7 @@ app7.post("/", async (c) => {
11238
11412
  console.error(`${TAG23} op=reject reason=no-secret-file botType=${botType}`);
11239
11413
  return c.json({ ok: false }, 401);
11240
11414
  }
11241
- const expected = readFileSync21(sp, "utf-8").trim();
11415
+ const expected = readFileSync22(sp, "utf-8").trim();
11242
11416
  const got = c.req.header("x-telegram-bot-api-secret-token") ?? "";
11243
11417
  if (got !== expected) {
11244
11418
  console.error(`${TAG23} op=reject reason=bad-secret botType=${botType}`);
@@ -11302,7 +11476,7 @@ var telegram_default = app7;
11302
11476
 
11303
11477
  // server/routes/quickbooks.ts
11304
11478
  import { join as join22 } from "path";
11305
- import { existsSync as existsSync15, readFileSync as readFileSync22, writeFileSync as writeFileSync8, mkdirSync as mkdirSync4, rmSync, renameSync as renameSync5 } from "fs";
11479
+ import { existsSync as existsSync15, readFileSync as readFileSync23, writeFileSync as writeFileSync8, mkdirSync as mkdirSync4, rmSync, renameSync as renameSync5 } from "fs";
11306
11480
  var TAG24 = "[quickbooks]";
11307
11481
  var INTUIT_TOKEN_URL = "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer";
11308
11482
  var STATE_RE = /^[A-Za-z0-9_-]+$/;
@@ -11329,7 +11503,7 @@ async function completeConsent(args) {
11329
11503
  try {
11330
11504
  let pending;
11331
11505
  try {
11332
- pending = JSON.parse(readFileSync22(claimFile, "utf-8"));
11506
+ pending = JSON.parse(readFileSync23(claimFile, "utf-8"));
11333
11507
  } catch {
11334
11508
  console.error(`${TAG24} op=callback state=${state} stateValid=false realmId=${realmId ?? ""}`);
11335
11509
  return { ok: false, status: 400, message: "Authorization state could not be read.", stateValid: false };
@@ -11345,7 +11519,7 @@ async function completeConsent(args) {
11345
11519
  if (!existsSync15(storePath(accountDir))) {
11346
11520
  return { ok: false, status: 500, message: "No QuickBooks credentials are stored for this account.", stateValid: true };
11347
11521
  }
11348
- const store2 = JSON.parse(readFileSync22(storePath(accountDir), "utf-8"));
11522
+ const store2 = JSON.parse(readFileSync23(storePath(accountDir), "utf-8"));
11349
11523
  const form = new URLSearchParams({
11350
11524
  grant_type: "authorization_code",
11351
11525
  code,
@@ -11379,7 +11553,7 @@ async function completeConsent(args) {
11379
11553
  };
11380
11554
  mkdirSync4(join22(accountDir, "secrets"), { recursive: true });
11381
11555
  writeFileSync8(storePath(accountDir), JSON.stringify(store2, null, 2), { mode: 384 });
11382
- const persisted = JSON.parse(readFileSync22(storePath(accountDir), "utf-8")).connections?.[realmId]?.refreshToken === body.refresh_token;
11556
+ const persisted = JSON.parse(readFileSync23(storePath(accountDir), "utf-8")).connections?.[realmId]?.refreshToken === body.refresh_token;
11383
11557
  console.error(`${TAG24} op=token-exchange realmId=${realmId} persisted=${persisted}`);
11384
11558
  return { ok: persisted, status: persisted ? 200 : 500, message: persisted ? `Connected company ${realmId}.` : "Failed to persist the connection.", stateValid: true };
11385
11559
  } finally {
@@ -11410,7 +11584,7 @@ var quickbooks_default = app8;
11410
11584
 
11411
11585
  // server/routes/onboarding.ts
11412
11586
  import { spawn, spawnSync as spawnSync2, execFileSync as execFileSync3 } from "child_process";
11413
- import { openSync as openSync3, closeSync as closeSync3, writeFileSync as writeFileSync10, writeSync, existsSync as existsSync17, readFileSync as readFileSync24, unlinkSync as unlinkSync3, renameSync as renameSync7 } from "fs";
11587
+ import { openSync as openSync3, closeSync as closeSync3, writeFileSync as writeFileSync10, writeSync, existsSync as existsSync17, readFileSync as readFileSync25, unlinkSync as unlinkSync3, renameSync as renameSync7 } from "fs";
11414
11588
  import { createHash as createHash4, randomUUID as randomUUID7 } from "crypto";
11415
11589
 
11416
11590
  // app/lib/claude-spawn-env.ts
@@ -11548,7 +11722,7 @@ function readHostCredsBlob(service) {
11548
11722
  }
11549
11723
 
11550
11724
  // ../lib/admins-write/src/index.ts
11551
- import { existsSync as existsSync16, readFileSync as readFileSync23, writeFileSync as writeFileSync9, renameSync as renameSync6, mkdirSync as mkdirSync5, readdirSync as readdirSync14, statSync as statSync8, appendFileSync as appendFileSync2 } from "fs";
11725
+ import { existsSync as existsSync16, readFileSync as readFileSync24, writeFileSync as writeFileSync9, renameSync as renameSync6, mkdirSync as mkdirSync5, readdirSync as readdirSync15, statSync as statSync8, appendFileSync as appendFileSync2 } from "fs";
11552
11726
  import { dirname as dirname7, join as join23 } from "path";
11553
11727
  function id8(value) {
11554
11728
  return value.slice(0, 8);
@@ -11591,7 +11765,7 @@ function writeAdminEntry(input) {
11591
11765
  try {
11592
11766
  let users = [];
11593
11767
  if (existsSync16(input.usersFile)) {
11594
- const raw = readFileSync23(input.usersFile, "utf-8").trim();
11768
+ const raw = readFileSync24(input.usersFile, "utf-8").trim();
11595
11769
  if (raw) {
11596
11770
  const parsed = JSON.parse(raw);
11597
11771
  if (!Array.isArray(parsed)) {
@@ -11626,7 +11800,7 @@ function writeAdminEntry(input) {
11626
11800
  if (!existsSync16(accountJsonPath)) {
11627
11801
  throw new Error(`account.json not found at ${accountJsonPath}`);
11628
11802
  }
11629
- const config = JSON.parse(readFileSync23(accountJsonPath, "utf-8"));
11803
+ const config = JSON.parse(readFileSync24(accountJsonPath, "utf-8"));
11630
11804
  const admins = config.admins ?? [];
11631
11805
  if (admins.some((a) => a.userId === input.userId)) {
11632
11806
  result.accountJsonResult = "noop";
@@ -11648,7 +11822,7 @@ function computeAdminStoreDivergence(input) {
11648
11822
  const usersUserIds = /* @__PURE__ */ new Set();
11649
11823
  if (existsSync16(input.usersFile)) {
11650
11824
  try {
11651
- const raw = readFileSync23(input.usersFile, "utf-8").trim();
11825
+ const raw = readFileSync24(input.usersFile, "utf-8").trim();
11652
11826
  if (raw) {
11653
11827
  const users = JSON.parse(raw);
11654
11828
  for (const u of users) {
@@ -11663,7 +11837,7 @@ function computeAdminStoreDivergence(input) {
11663
11837
  if (existsSync16(input.accountsDir)) {
11664
11838
  let entries;
11665
11839
  try {
11666
- entries = readdirSync14(input.accountsDir);
11840
+ entries = readdirSync15(input.accountsDir);
11667
11841
  } catch (err) {
11668
11842
  result.errors.push({ source: input.accountsDir, detail: err instanceof Error ? err.message : String(err) });
11669
11843
  return result;
@@ -11680,7 +11854,7 @@ function computeAdminStoreDivergence(input) {
11680
11854
  if (!existsSync16(accountJsonPath)) continue;
11681
11855
  let admins = [];
11682
11856
  try {
11683
- const config = JSON.parse(readFileSync23(accountJsonPath, "utf-8"));
11857
+ const config = JSON.parse(readFileSync24(accountJsonPath, "utf-8"));
11684
11858
  admins = config.admins ?? [];
11685
11859
  } catch (err) {
11686
11860
  result.errors.push({ source: accountJsonPath, detail: err instanceof Error ? err.message : String(err) });
@@ -11729,7 +11903,7 @@ function hashPin2(pin) {
11729
11903
  }
11730
11904
  function readUsersFile2() {
11731
11905
  if (!existsSync17(USERS_FILE)) return null;
11732
- const raw = readFileSync24(USERS_FILE, "utf-8").trim();
11906
+ const raw = readFileSync25(USERS_FILE, "utf-8").trim();
11733
11907
  if (!raw) return [];
11734
11908
  return JSON.parse(raw);
11735
11909
  }
@@ -11939,7 +12113,7 @@ app9.post("/set-pin", async (c) => {
11939
12113
  let installOwner = null;
11940
12114
  if (existsSync17(INSTALL_OWNER_FILE)) {
11941
12115
  try {
11942
- const raw = readFileSync24(INSTALL_OWNER_FILE, "utf-8").trim();
12116
+ const raw = readFileSync25(INSTALL_OWNER_FILE, "utf-8").trim();
11943
12117
  if (raw.length > 0) installOwner = raw;
11944
12118
  } catch (err) {
11945
12119
  console.error(`[set-pin] install-owner-read-failed path=${INSTALL_OWNER_FILE} error=${err instanceof Error ? err.message : String(err)}`);
@@ -12087,7 +12261,7 @@ app9.put("/set-pin", requireAdminSession, async (c) => {
12087
12261
  let installOwner = null;
12088
12262
  if (existsSync17(INSTALL_OWNER_FILE)) {
12089
12263
  try {
12090
- const raw = readFileSync24(INSTALL_OWNER_FILE, "utf-8").trim();
12264
+ const raw = readFileSync25(INSTALL_OWNER_FILE, "utf-8").trim();
12091
12265
  if (raw.length > 0) installOwner = raw;
12092
12266
  } catch (err) {
12093
12267
  console.error(`[set-pin] install-owner-read-failed path=${INSTALL_OWNER_FILE} error=${err instanceof Error ? err.message : String(err)}`);
@@ -12489,7 +12663,7 @@ app12.get("/", requireAdminSession, async (c) => {
12489
12663
  var accounts_default = app12;
12490
12664
 
12491
12665
  // server/routes/admin/logs.ts
12492
- import { existsSync as existsSync20, readdirSync as readdirSync15, readFileSync as readFileSync25, statSync as statSync10 } from "fs";
12666
+ import { existsSync as existsSync20, readdirSync as readdirSync16, readFileSync as readFileSync26, statSync as statSync10 } from "fs";
12493
12667
  import { resolve as resolve16, basename as basename7 } from "path";
12494
12668
 
12495
12669
  // app/lib/logs-read-resolve.ts
@@ -12528,7 +12702,7 @@ app13.get("/", async (c) => {
12528
12702
  const filePath = resolve16(dir, safe);
12529
12703
  searched.push(filePath);
12530
12704
  try {
12531
- const buffer = readFileSync25(filePath);
12705
+ const buffer = readFileSync26(filePath);
12532
12706
  const onDiskBytes = statSync10(filePath).size;
12533
12707
  const headers = {
12534
12708
  "Content-Type": "text/plain; charset=utf-8",
@@ -12593,7 +12767,7 @@ app13.get("/", async (c) => {
12593
12767
  console.info(`[admin/logs] resolved cacheKey=${cacheKeySlice} sessionId=${sessionIdSlice} via=${primaryId === cacheKey ? "cacheKey" : primaryId === sessionKeyFromConv ? "reverse-lookup" : "sessionId-fallback"}`);
12594
12768
  try {
12595
12769
  const filename = basename7(hit.path);
12596
- const buffer = readFileSync25(hit.path);
12770
+ const buffer = readFileSync26(hit.path);
12597
12771
  const onDiskBytes = statSync10(hit.path).size;
12598
12772
  const headers = {
12599
12773
  "Content-Type": "text/plain; charset=utf-8",
@@ -12631,7 +12805,7 @@ app13.get("/", async (c) => {
12631
12805
  if (!existsSync20(dir)) continue;
12632
12806
  let files;
12633
12807
  try {
12634
- files = readdirSync15(dir).filter((f) => f.endsWith(".log"));
12808
+ files = readdirSync16(dir).filter((f) => f.endsWith(".log"));
12635
12809
  } catch (err) {
12636
12810
  const reason = err instanceof Error ? err.message : String(err);
12637
12811
  console.warn(`[admin/logs] readdir-fail dir=${dir} reason=${reason}`);
@@ -12640,7 +12814,7 @@ app13.get("/", async (c) => {
12640
12814
  files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync10(resolve16(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).forEach(({ name }) => {
12641
12815
  seen.add(name);
12642
12816
  try {
12643
- const content = readFileSync25(resolve16(dir, name));
12817
+ const content = readFileSync26(resolve16(dir, name));
12644
12818
  const tail = content.length > TAIL_BYTES ? content.subarray(content.length - TAIL_BYTES).toString("utf-8") : content.toString("utf-8");
12645
12819
  logs[name] = tail.trim() || "(empty)";
12646
12820
  } catch (err) {
@@ -12683,7 +12857,7 @@ import { readFile as readFile2, readdir } from "fs/promises";
12683
12857
  import { existsSync as existsSync21 } from "fs";
12684
12858
  import { resolve as resolve17 } from "path";
12685
12859
  var app15 = new Hono();
12686
- var ATTACHMENT_ID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
12860
+ var ATTACHMENT_ID_RE3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
12687
12861
  function uploadsDirFor2(accountId, attachmentId) {
12688
12862
  return resolve17(DATA_ROOT, "accounts", accountId, "uploads", attachmentId);
12689
12863
  }
@@ -12697,7 +12871,7 @@ app15.get("/:attachmentId", requireAdminSession, async (c) => {
12697
12871
  console.log(`[admin-attachment] op=miss attachmentId=${shortId2} session=${shortSession} status=401 reason=no-account`);
12698
12872
  return new Response("Unauthorized", { status: 401 });
12699
12873
  }
12700
- if (!ATTACHMENT_ID_RE2.test(attachmentId)) {
12874
+ if (!ATTACHMENT_ID_RE3.test(attachmentId)) {
12701
12875
  console.log(`[admin-attachment] op=miss attachmentId=${shortId2} session=${shortSession} status=404 reason=bad-id`);
12702
12876
  return new Response("Not found", { status: 404 });
12703
12877
  }
@@ -12739,10 +12913,16 @@ app15.get("/:attachmentId", requireAdminSession, async (c) => {
12739
12913
  }
12740
12914
  const filePath = resolve17(dir, dataFile);
12741
12915
  const buffer = await readFile2(filePath);
12916
+ const inlineSafe = isInlineSafeMime(meta.mimeType);
12917
+ const safeName = meta.filename.replace(/["\\\r\n]/g, "_");
12918
+ if (!inlineSafe) {
12919
+ console.log(`[admin-attachment] op=served attachmentId=${shortId2} disposition=attachment reason=non-preview-mime mime=${meta.mimeType}`);
12920
+ }
12742
12921
  return new Response(new Uint8Array(buffer), {
12743
12922
  headers: {
12744
12923
  "Content-Type": meta.mimeType,
12745
- "Content-Disposition": `inline; filename="${meta.filename}"`,
12924
+ "Content-Disposition": `${inlineSafe ? "inline" : "attachment"}; filename="${safeName}"`,
12925
+ "X-Content-Type-Options": "nosniff",
12746
12926
  "Cache-Control": "private, max-age=3600"
12747
12927
  }
12748
12928
  });
@@ -12751,7 +12931,7 @@ var attachment_default = app15;
12751
12931
 
12752
12932
  // server/routes/admin/agents.ts
12753
12933
  import { resolve as resolve18 } from "path";
12754
- import { readdirSync as readdirSync16, readFileSync as readFileSync26, existsSync as existsSync22, rmSync as rmSync2 } from "fs";
12934
+ import { readdirSync as readdirSync17, readFileSync as readFileSync27, existsSync as existsSync22, rmSync as rmSync2 } from "fs";
12755
12935
  var app16 = new Hono();
12756
12936
  app16.get("/", (c) => {
12757
12937
  const account = resolveAccount();
@@ -12760,14 +12940,14 @@ app16.get("/", (c) => {
12760
12940
  if (!existsSync22(agentsDir)) return c.json({ agents: [] });
12761
12941
  const agents = [];
12762
12942
  try {
12763
- const entries = readdirSync16(agentsDir, { withFileTypes: true });
12943
+ const entries = readdirSync17(agentsDir, { withFileTypes: true });
12764
12944
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
12765
12945
  if (!entry.isDirectory()) continue;
12766
12946
  if (entry.name === "admin") continue;
12767
12947
  const configPath2 = resolve18(agentsDir, entry.name, "config.json");
12768
12948
  if (!existsSync22(configPath2)) continue;
12769
12949
  try {
12770
- const config = JSON.parse(readFileSync26(configPath2, "utf-8"));
12950
+ const config = JSON.parse(readFileSync27(configPath2, "utf-8"));
12771
12951
  agents.push({
12772
12952
  slug: entry.name,
12773
12953
  displayName: config.displayName ?? entry.name,
@@ -13526,7 +13706,7 @@ app17.put("/:id/label", requireAdminSession, async (c) => {
13526
13706
  var sessions_default = app17;
13527
13707
 
13528
13708
  // app/lib/claude-agent/spawn-context.ts
13529
- import { existsSync as existsSync24, readFileSync as readFileSync27, readdirSync as readdirSync17, statSync as statSync11 } from "fs";
13709
+ import { existsSync as existsSync24, readFileSync as readFileSync28, readdirSync as readdirSync18, statSync as statSync11 } from "fs";
13530
13710
  import { dirname as dirname9, resolve as resolve19, join as join27 } from "path";
13531
13711
  async function resolveOwnerProfileBlock(accountId, userId) {
13532
13712
  if (!userId) return { ok: false, reason: "missing-user-id" };
@@ -13575,7 +13755,7 @@ function extractPluginToolDescriptions(pluginDir) {
13575
13755
  for (const path2 of candidates) {
13576
13756
  if (!existsSync24(path2)) continue;
13577
13757
  try {
13578
- raw = readFileSync27(path2, "utf-8");
13758
+ raw = readFileSync28(path2, "utf-8");
13579
13759
  break;
13580
13760
  } catch {
13581
13761
  }
@@ -13626,7 +13806,7 @@ function listPluginDirs() {
13626
13806
  const out = /* @__PURE__ */ new Map();
13627
13807
  const platformPlugins = resolve19(PLATFORM_ROOT, "plugins");
13628
13808
  if (existsSync24(platformPlugins)) {
13629
- for (const name of readdirSync17(platformPlugins)) {
13809
+ for (const name of readdirSync18(platformPlugins)) {
13630
13810
  const dir = join27(platformPlugins, name);
13631
13811
  try {
13632
13812
  if (statSync11(dir).isDirectory()) out.set(name, dir);
@@ -13636,11 +13816,11 @@ function listPluginDirs() {
13636
13816
  }
13637
13817
  const premiumRoot = resolve19(dirname9(PLATFORM_ROOT), "premium-plugins");
13638
13818
  if (existsSync24(premiumRoot)) {
13639
- for (const bundle of readdirSync17(premiumRoot)) {
13819
+ for (const bundle of readdirSync18(premiumRoot)) {
13640
13820
  const bundlePlugins = join27(premiumRoot, bundle, "plugins");
13641
13821
  if (!existsSync24(bundlePlugins)) continue;
13642
13822
  try {
13643
- for (const name of readdirSync17(bundlePlugins)) {
13823
+ for (const name of readdirSync18(bundlePlugins)) {
13644
13824
  const dir = join27(bundlePlugins, name);
13645
13825
  try {
13646
13826
  if (statSync11(dir).isDirectory()) out.set(name, dir);
@@ -13657,7 +13837,7 @@ function readEnabledPlugins(accountId) {
13657
13837
  const accountFile = resolve19(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
13658
13838
  if (!existsSync24(accountFile)) return /* @__PURE__ */ new Set();
13659
13839
  try {
13660
- const parsed = JSON.parse(readFileSync27(accountFile, "utf-8"));
13840
+ const parsed = JSON.parse(readFileSync28(accountFile, "utf-8"));
13661
13841
  return new Set(
13662
13842
  Array.isArray(parsed.enabledPlugins) ? parsed.enabledPlugins.filter((p) => typeof p === "string") : []
13663
13843
  );
@@ -13669,7 +13849,7 @@ function readFrontmatter(path2) {
13669
13849
  if (!existsSync24(path2)) return null;
13670
13850
  let raw;
13671
13851
  try {
13672
- raw = readFileSync27(path2, "utf-8");
13852
+ raw = readFileSync28(path2, "utf-8");
13673
13853
  } catch {
13674
13854
  return null;
13675
13855
  }
@@ -13731,7 +13911,7 @@ function computeSpecialistDomains(accountId) {
13731
13911
  if (!existsSync24(specialistsDir)) return [];
13732
13912
  let entries;
13733
13913
  try {
13734
- entries = readdirSync17(specialistsDir);
13914
+ entries = readdirSync18(specialistsDir);
13735
13915
  } catch {
13736
13916
  return [];
13737
13917
  }
@@ -13777,7 +13957,7 @@ function computeDormantPlugins(accountId) {
13777
13957
  if (!existsSync24(accountFile)) return [];
13778
13958
  let enabled;
13779
13959
  try {
13780
- const raw = readFileSync27(accountFile, "utf-8");
13960
+ const raw = readFileSync28(accountFile, "utf-8");
13781
13961
  const parsed = JSON.parse(raw);
13782
13962
  enabled = new Set(
13783
13963
  Array.isArray(parsed.enabledPlugins) ? parsed.enabledPlugins.filter((p) => typeof p === "string") : []
@@ -13792,7 +13972,7 @@ function computeDormantPlugins(accountId) {
13792
13972
  if (!existsSync24(pluginsDir)) return [];
13793
13973
  let entries;
13794
13974
  try {
13795
- entries = readdirSync17(pluginsDir);
13975
+ entries = readdirSync18(pluginsDir);
13796
13976
  } catch {
13797
13977
  return [];
13798
13978
  }
@@ -13803,7 +13983,7 @@ function computeDormantPlugins(accountId) {
13803
13983
  if (!existsSync24(manifestPath)) continue;
13804
13984
  let manifest;
13805
13985
  try {
13806
- manifest = readFileSync27(manifestPath, "utf-8");
13986
+ manifest = readFileSync28(manifestPath, "utf-8");
13807
13987
  } catch {
13808
13988
  continue;
13809
13989
  }
@@ -13817,13 +13997,13 @@ function computeDormantPlugins(accountId) {
13817
13997
  }
13818
13998
 
13819
13999
  // server/routes/admin/claude-sessions.ts
13820
- import { existsSync as existsSync25, readFileSync as readFileSync28 } from "fs";
14000
+ import { existsSync as existsSync25, readFileSync as readFileSync29 } from "fs";
13821
14001
  import { resolve as resolve20 } from "path";
13822
14002
  function readTunnelState(brandConfigDir) {
13823
14003
  const statePath = `${process.env.HOME ?? ""}/${brandConfigDir}/cloudflared/tunnel.state`;
13824
14004
  if (!existsSync25(statePath)) return null;
13825
14005
  try {
13826
- const parsed = JSON.parse(readFileSync28(statePath, "utf-8"));
14006
+ const parsed = JSON.parse(readFileSync29(statePath, "utf-8"));
13827
14007
  const tunnelId = typeof parsed.tunnelId === "string" ? parsed.tunnelId : null;
13828
14008
  const tunnelName = typeof parsed.tunnelName === "string" ? parsed.tunnelName : null;
13829
14009
  const domain = typeof parsed.domain === "string" ? parsed.domain : null;
@@ -13837,7 +14017,7 @@ function resolveTunnelUrl() {
13837
14017
  const platformRoot3 = process.env.MAXY_PLATFORM_ROOT ?? process.env.PLATFORM_ROOT ?? resolve20(process.cwd(), "..");
13838
14018
  let brand;
13839
14019
  try {
13840
- brand = JSON.parse(readFileSync28(resolve20(platformRoot3, "config", "brand.json"), "utf-8"));
14020
+ brand = JSON.parse(readFileSync29(resolve20(platformRoot3, "config", "brand.json"), "utf-8"));
13841
14021
  } catch {
13842
14022
  return null;
13843
14023
  }
@@ -14080,7 +14260,7 @@ var events_default = app20;
14080
14260
 
14081
14261
  // server/routes/admin/files.ts
14082
14262
  import { createReadStream as createReadStream2, createWriteStream as createWriteStream2, existsSync as existsSync26 } from "fs";
14083
- import { readdir as readdir3, readFile as readFile4, stat as stat4, mkdir as mkdir3, unlink as unlink2, rename } from "fs/promises";
14263
+ import { readdir as readdir3, readFile as readFile4, stat as stat4, mkdir as mkdir3, unlink as unlink2, rename, rmdir } from "fs/promises";
14084
14264
  import { realpathSync as realpathSync5 } from "fs";
14085
14265
  import { randomUUID as randomUUID9 } from "crypto";
14086
14266
  import { basename as basename9, dirname as dirname10, join as join29, relative as relative4, resolve as resolve22, sep as sep6 } from "path";
@@ -14958,19 +15138,55 @@ async function readAccountNames() {
14958
15138
  }
14959
15139
  return map;
14960
15140
  }
15141
+ async function servableFilesIn(dirAbs, uuid) {
15142
+ const metaName = `${uuid}.meta.json`;
15143
+ let dirents;
15144
+ try {
15145
+ dirents = await readdir3(dirAbs, { withFileTypes: true });
15146
+ } catch {
15147
+ return [];
15148
+ }
15149
+ const out = [];
15150
+ for (const d of dirents) {
15151
+ if (!d.isFile()) continue;
15152
+ if (d.name === metaName) continue;
15153
+ if (d.name.endsWith(EXTRACTED_TEXT_SUFFIX)) continue;
15154
+ out.push(d.name);
15155
+ }
15156
+ return out;
15157
+ }
14961
15158
  async function enrich(absolute, entry, accountNames) {
14962
15159
  if (entry.kind === "directory" && UUID_RE3.test(entry.name)) {
14963
- const meta = await readMeta(join29(absolute, entry.name), entry.name);
15160
+ const dirAbs = join29(absolute, entry.name);
15161
+ const meta = await readMeta(dirAbs, entry.name);
14964
15162
  if (meta?.filename) {
15163
+ const servable = await servableFilesIn(dirAbs, entry.name);
15164
+ if (servable.length === 1) {
15165
+ const innerName = servable[0];
15166
+ let size = null;
15167
+ let modifiedAt = entry.modifiedAt;
15168
+ try {
15169
+ const st = await stat4(join29(dirAbs, innerName));
15170
+ size = st.size;
15171
+ modifiedAt = st.mtime.toISOString();
15172
+ } catch {
15173
+ }
15174
+ entry.kind = "file";
15175
+ entry.displayName = meta.filename;
15176
+ entry.mimeType = meta.mimeType;
15177
+ entry.sizeBytes = size;
15178
+ entry.modifiedAt = modifiedAt;
15179
+ entry.entryPath = `${entry.name}/${innerName}`;
15180
+ return "flattened";
15181
+ }
14965
15182
  entry.displayName = meta.filename;
14966
- entry.mimeType = meta.mimeType;
14967
- return;
15183
+ return "kept-as-dir";
14968
15184
  }
14969
15185
  const accountName = accountNames.get(entry.name);
14970
15186
  if (accountName) {
14971
15187
  entry.displayName = accountName;
14972
15188
  }
14973
- return;
15189
+ return null;
14974
15190
  }
14975
15191
  if (entry.kind === "file") {
14976
15192
  const dot = entry.name.lastIndexOf(".");
@@ -14983,6 +15199,7 @@ async function enrich(absolute, entry, accountNames) {
14983
15199
  }
14984
15200
  }
14985
15201
  }
15202
+ return null;
14986
15203
  }
14987
15204
  function buildDisplayPath(relPath, accountNames) {
14988
15205
  if (relPath === "." || relPath === "") return [];
@@ -15138,8 +15355,18 @@ app21.get("/", requireAdminSession, async (c) => {
15138
15355
  }
15139
15356
  }
15140
15357
  const accountNames = await readAccountNames();
15141
- await Promise.all(entries.map((e) => enrich(absolute, e, accountNames)));
15358
+ const classes = await Promise.all(entries.map((e) => enrich(absolute, e, accountNames)));
15142
15359
  sortDirEntries(entries);
15360
+ let flattened = 0;
15361
+ let keptAsDir = 0;
15362
+ for (const cls of classes) {
15363
+ if (cls === "flattened") flattened++;
15364
+ else if (cls === "kept-as-dir") keptAsDir++;
15365
+ }
15366
+ const containers = flattened + keptAsDir;
15367
+ if (containers > 0) {
15368
+ console.error(`[data] op=uploads-flatten containers=${containers} flattened=${flattened} kept-as-dir=${keptAsDir}`);
15369
+ }
15143
15370
  const displayPath = buildDisplayPath(relPath, accountNames);
15144
15371
  console.error(`[data] file-list path="${relPath}" entries=${entries.length}`);
15145
15372
  return c.json({ path: relPath, displayPath, entries });
@@ -15228,8 +15455,16 @@ app21.get("/download", requireAdminSession, async (c) => {
15228
15455
  });
15229
15456
  }
15230
15457
  console.error(`[data] file-download root="${root}" path="${relPath}" size=${info.size}`);
15231
- const safeQuotedName = filename.replace(/[\r\n"\\]/g, "_");
15232
- const encodedName = encodeURIComponent(filename);
15458
+ let downloadName = filename;
15459
+ if (root === "data") {
15460
+ const parsed = parseAttachmentPath(relPath);
15461
+ if (parsed) {
15462
+ const meta = await readMeta(dirname10(absolute), parsed.attachmentId);
15463
+ if (meta?.filename) downloadName = meta.filename;
15464
+ }
15465
+ }
15466
+ const safeQuotedName = downloadName.replace(/[\r\n"\\]/g, "_");
15467
+ const encodedName = encodeURIComponent(downloadName);
15233
15468
  return new Response(webStream, {
15234
15469
  status: 200,
15235
15470
  headers: {
@@ -15324,7 +15559,13 @@ app21.get("/download-zip", requireAdminSession, async (c) => {
15324
15559
  if (over) return over;
15325
15560
  }
15326
15561
  } else if (info.isFile()) {
15327
- const over = await addFile(absolute, info.size, basename9(absolute));
15562
+ let member = basename9(absolute);
15563
+ const parsed = parseAttachmentPath(relPath);
15564
+ if (parsed) {
15565
+ const meta = await readMeta(dirname10(absolute), parsed.attachmentId);
15566
+ if (meta?.filename) member = meta.filename;
15567
+ }
15568
+ const over = await addFile(absolute, info.size, member);
15328
15569
  if (over) return over;
15329
15570
  } else {
15330
15571
  return c.json({ error: "Path is not a file or directory" }, 400);
@@ -15744,7 +15985,9 @@ app21.delete("/", requireAdminSession, async (c) => {
15744
15985
  }
15745
15986
  const dot = base.lastIndexOf(".");
15746
15987
  const stem = dot === -1 ? base : base.slice(0, dot);
15747
- const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ? join29(dirname10(absolute), `${stem}.meta.json`) : null;
15988
+ const containerDir = dirname10(absolute);
15989
+ const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ? join29(containerDir, `${stem}.meta.json`) : null;
15990
+ const isAttachmentContainer = UUID_RE3.test(stem) && basename9(containerDir) === stem;
15748
15991
  await unlink2(absolute);
15749
15992
  if (sidecarPath) {
15750
15993
  try {
@@ -15752,6 +15995,12 @@ app21.delete("/", requireAdminSession, async (c) => {
15752
15995
  } catch {
15753
15996
  }
15754
15997
  }
15998
+ if (isAttachmentContainer) {
15999
+ await unlink2(join29(containerDir, `${stem}${EXTRACTED_TEXT_SUFFIX}`)).catch(() => {
16000
+ });
16001
+ await rmdir(containerDir).catch(() => {
16002
+ });
16003
+ }
15755
16004
  console.error(`[data] file-delete path="${relPath}" bytes=${info.size}`);
15756
16005
  try {
15757
16006
  await dropFileIndex(accountId, relPath);
@@ -18708,14 +18957,14 @@ app36.get("/", async (c) => {
18708
18957
  var system_stats_default = app36;
18709
18958
 
18710
18959
  // server/routes/admin/health.ts
18711
- import { existsSync as existsSync28, readFileSync as readFileSync29 } from "fs";
18960
+ import { existsSync as existsSync28, readFileSync as readFileSync30 } from "fs";
18712
18961
  import { resolve as resolve24, join as join30 } from "path";
18713
18962
  var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT ?? resolve24(process.cwd(), "..");
18714
18963
  var brandHostname = "maxy";
18715
18964
  var brandJsonPath = join30(PLATFORM_ROOT6, "config", "brand.json");
18716
18965
  if (existsSync28(brandJsonPath)) {
18717
18966
  try {
18718
- const brand = JSON.parse(readFileSync29(brandJsonPath, "utf-8"));
18967
+ const brand = JSON.parse(readFileSync30(brandJsonPath, "utf-8"));
18719
18968
  if (brand.hostname) brandHostname = brand.hostname;
18720
18969
  } catch {
18721
18970
  }
@@ -18725,7 +18974,7 @@ var PROCESS_STARTED_AT = (/* @__PURE__ */ new Date()).toISOString();
18725
18974
  var PROBE_TIMEOUT_MS = 1e3;
18726
18975
  function readVersion() {
18727
18976
  if (!existsSync28(VERSION_FILE)) return "unknown";
18728
- return readFileSync29(VERSION_FILE, "utf-8").trim() || "unknown";
18977
+ return readFileSync30(VERSION_FILE, "utf-8").trim() || "unknown";
18729
18978
  }
18730
18979
  async function probeConversationDb() {
18731
18980
  let session;
@@ -19601,7 +19850,7 @@ function countVEvents(ics) {
19601
19850
  }
19602
19851
 
19603
19852
  // server/lib/calendar-availability.ts
19604
- import { readFileSync as readFileSync30 } from "fs";
19853
+ import { readFileSync as readFileSync31 } from "fs";
19605
19854
  import { join as join31 } from "path";
19606
19855
  var AVAILABILITY_FILENAME = "calendar-availability.json";
19607
19856
  var REQUIRED_KEYS = [
@@ -19614,7 +19863,7 @@ function readAvailabilityConfig(accountDir) {
19614
19863
  const path2 = join31(accountDir, AVAILABILITY_FILENAME);
19615
19864
  let raw;
19616
19865
  try {
19617
- raw = readFileSync30(path2, "utf-8");
19866
+ raw = readFileSync31(path2, "utf-8");
19618
19867
  } catch {
19619
19868
  throw new Error(`availability not configured: ${path2} not found`);
19620
19869
  }
@@ -20591,7 +20840,7 @@ app54.route("/request-magic-link", request_magic_link_default);
20591
20840
  var access_default = app54;
20592
20841
 
20593
20842
  // server/routes/sites.ts
20594
- import { existsSync as existsSync30, readFileSync as readFileSync31, realpathSync as realpathSync6, statSync as statSync12 } from "fs";
20843
+ import { existsSync as existsSync30, readFileSync as readFileSync32, realpathSync as realpathSync6, statSync as statSync12 } from "fs";
20595
20844
  import { resolve as resolve26 } from "path";
20596
20845
  var SAFE_SEG_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
20597
20846
  var MIME = {
@@ -20695,7 +20944,7 @@ app55.get("/:rel{.*}", (c) => {
20695
20944
  }
20696
20945
  let body;
20697
20946
  try {
20698
- body = readFileSync31(realPath);
20947
+ body = readFileSync32(realPath);
20699
20948
  } catch (err) {
20700
20949
  const code = err?.code;
20701
20950
  if (code === "EISDIR") {
@@ -20731,7 +20980,7 @@ var sites_default = app55;
20731
20980
 
20732
20981
  // app/lib/visitor-token.ts
20733
20982
  import { createHmac, randomBytes, timingSafeEqual } from "crypto";
20734
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync32, writeFileSync as writeFileSync11 } from "fs";
20983
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync33, writeFileSync as writeFileSync11 } from "fs";
20735
20984
  import { dirname as dirname11 } from "path";
20736
20985
  var TOKEN_PREFIX = "v1.";
20737
20986
  var SECRET_BYTES = 32;
@@ -20740,7 +20989,7 @@ var cachedSecret = null;
20740
20989
  function getSecret() {
20741
20990
  if (cachedSecret) return cachedSecret;
20742
20991
  try {
20743
- const hex2 = readFileSync32(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
20992
+ const hex2 = readFileSync33(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
20744
20993
  if (hex2.length === SECRET_BYTES * 2) {
20745
20994
  cachedSecret = Buffer.from(hex2, "hex");
20746
20995
  return cachedSecret;
@@ -20754,7 +21003,7 @@ function getSecret() {
20754
21003
  console.log(`[visitor-token] secret minted path=${VISITOR_TOKEN_SECRET_FILE}`);
20755
21004
  } catch {
20756
21005
  }
20757
- const hex = readFileSync32(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
21006
+ const hex = readFileSync33(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
20758
21007
  cachedSecret = Buffer.from(hex, "hex");
20759
21008
  return cachedSecret;
20760
21009
  }
@@ -20807,7 +21056,7 @@ var VISITOR_COOKIE_NAME = "mxy_v";
20807
21056
  var VISITOR_COOKIE_MAX_AGE_SECONDS = Math.floor(DEFAULT_TTL_MS / 1e3);
20808
21057
 
20809
21058
  // app/lib/brand-config.ts
20810
- import { existsSync as existsSync31, readFileSync as readFileSync33 } from "fs";
21059
+ import { existsSync as existsSync31, readFileSync as readFileSync34 } from "fs";
20811
21060
  import { join as join33 } from "path";
20812
21061
  var cached = null;
20813
21062
  var cachedAttempted = false;
@@ -20819,7 +21068,7 @@ function readBrandConfig() {
20819
21068
  const brandPath = join33(platformRoot3, "config", "brand.json");
20820
21069
  if (!existsSync31(brandPath)) return null;
20821
21070
  try {
20822
- cached = JSON.parse(readFileSync33(brandPath, "utf-8"));
21071
+ cached = JSON.parse(readFileSync34(brandPath, "utf-8"));
20823
21072
  return cached;
20824
21073
  } catch {
20825
21074
  return null;
@@ -24554,7 +24803,7 @@ function broadcastAdminShutdown(reason) {
24554
24803
 
24555
24804
  // ../lib/entitlement/src/index.ts
24556
24805
  import { createPublicKey, createHash as createHash6, verify as cryptoVerify } from "crypto";
24557
- import { existsSync as existsSync34, readFileSync as readFileSync34, statSync as statSync13 } from "fs";
24806
+ import { existsSync as existsSync34, readFileSync as readFileSync35, statSync as statSync13 } from "fs";
24558
24807
  import { resolve as resolve32 } from "path";
24559
24808
 
24560
24809
  // ../lib/entitlement/src/canonicalize.ts
@@ -24618,7 +24867,7 @@ function resolveEntitlement(brand, account) {
24618
24867
  function verifyAndResolve(brand, entitlementPath, account) {
24619
24868
  let pubkeyPem;
24620
24869
  try {
24621
- pubkeyPem = readFileSync34(pubkeyPath(brand), "utf-8");
24870
+ pubkeyPem = readFileSync35(pubkeyPath(brand), "utf-8");
24622
24871
  } catch (err) {
24623
24872
  return logResolved(anonymousFallback("pubkey-missing"), {
24624
24873
  reason: "pubkey-missing"
@@ -24632,7 +24881,7 @@ function verifyAndResolve(brand, entitlementPath, account) {
24632
24881
  }
24633
24882
  let envelope;
24634
24883
  try {
24635
- envelope = JSON.parse(readFileSync34(entitlementPath, "utf-8"));
24884
+ envelope = JSON.parse(readFileSync35(entitlementPath, "utf-8"));
24636
24885
  } catch {
24637
24886
  return logResolved(anonymousFallback("malformed"), { reason: "malformed" });
24638
24887
  }
@@ -24800,7 +25049,7 @@ if (BRAND_JSON_PATH && !existsSync35(BRAND_JSON_PATH)) {
24800
25049
  }
24801
25050
  if (BRAND_JSON_PATH && existsSync35(BRAND_JSON_PATH)) {
24802
25051
  try {
24803
- const parsed = JSON.parse(readFileSync35(BRAND_JSON_PATH, "utf-8"));
25052
+ const parsed = JSON.parse(readFileSync36(BRAND_JSON_PATH, "utf-8"));
24804
25053
  BRAND = { ...BRAND, ...parsed };
24805
25054
  } catch (err) {
24806
25055
  console.error(`[brand] Failed to parse brand.json: ${err.message}`);
@@ -24836,7 +25085,7 @@ var ALIAS_DOMAINS_PATH = join36(homedir4(), BRAND.configDir, "alias-domains.json
24836
25085
  function loadAliasDomains() {
24837
25086
  try {
24838
25087
  if (!existsSync35(ALIAS_DOMAINS_PATH)) return null;
24839
- const parsed = JSON.parse(readFileSync35(ALIAS_DOMAINS_PATH, "utf-8"));
25088
+ const parsed = JSON.parse(readFileSync36(ALIAS_DOMAINS_PATH, "utf-8"));
24840
25089
  if (!Array.isArray(parsed)) {
24841
25090
  console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
24842
25091
  return null;
@@ -25525,7 +25774,7 @@ app61.get("/agent-assets/:slug/:filename", (c) => {
25525
25774
  const ext = "." + filename.split(".").pop()?.toLowerCase();
25526
25775
  const contentType = IMAGE_MIME[ext] || "application/octet-stream";
25527
25776
  console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
25528
- const body = readFileSync35(filePath);
25777
+ const body = readFileSync36(filePath);
25529
25778
  return c.body(body, 200, {
25530
25779
  "Content-Type": contentType,
25531
25780
  "Cache-Control": "public, max-age=3600"
@@ -25555,7 +25804,7 @@ app61.get("/generated/:filename", (c) => {
25555
25804
  const ext = "." + filename.split(".").pop()?.toLowerCase();
25556
25805
  const contentType = IMAGE_MIME[ext] || "application/octet-stream";
25557
25806
  console.log(`[generated] serve file=${filename} status=200`);
25558
- const body = readFileSync35(filePath);
25807
+ const body = readFileSync36(filePath);
25559
25808
  return c.body(body, 200, {
25560
25809
  "Content-Type": contentType,
25561
25810
  "Cache-Control": "public, max-age=86400"
@@ -25570,7 +25819,7 @@ var brandLogoPath = "/brand/maxy-monochrome.png";
25570
25819
  var brandIconPath = "/brand/maxy-monochrome.png";
25571
25820
  if (BRAND_JSON_PATH && existsSync35(BRAND_JSON_PATH)) {
25572
25821
  try {
25573
- const fullBrand = JSON.parse(readFileSync35(BRAND_JSON_PATH, "utf-8"));
25822
+ const fullBrand = JSON.parse(readFileSync36(BRAND_JSON_PATH, "utf-8"));
25574
25823
  if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
25575
25824
  brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
25576
25825
  } catch {
@@ -25601,7 +25850,7 @@ var brandAppIconsExist = [brandAppIcon192Path, brandAppIcon512Path, brandMaskabl
25601
25850
  );
25602
25851
  var SW_SOURCE = (() => {
25603
25852
  try {
25604
- return readFileSync35(resolve33(process.cwd(), "public", "sw.js"), "utf-8");
25853
+ return readFileSync36(resolve33(process.cwd(), "public", "sw.js"), "utf-8");
25605
25854
  } catch {
25606
25855
  return null;
25607
25856
  }
@@ -25611,7 +25860,7 @@ function readInstalledVersion() {
25611
25860
  if (!PLATFORM_ROOT8) return "unknown";
25612
25861
  const versionFile = join36(PLATFORM_ROOT8, "config", `.${BRAND.hostname}-version`);
25613
25862
  if (!existsSync35(versionFile)) return "unknown";
25614
- const content = readFileSync35(versionFile, "utf-8").trim();
25863
+ const content = readFileSync36(versionFile, "utf-8").trim();
25615
25864
  return content || "unknown";
25616
25865
  } catch {
25617
25866
  return "unknown";
@@ -25652,7 +25901,7 @@ var clientErrorReporterScript = `<script>
25652
25901
  function cachedHtml(file) {
25653
25902
  let html = htmlCache.get(file);
25654
25903
  if (!html) {
25655
- html = readFileSync35(resolve33(process.cwd(), "public", file), "utf-8");
25904
+ html = readFileSync36(resolve33(process.cwd(), "public", file), "utf-8");
25656
25905
  const productNameEsc = escapeHtml(BRAND.productName);
25657
25906
  html = html.replace(/<title>([^<]*)<\/title>/, (_match, inner) => `<title>${escapeHtml(inner).replace(/Maxy/g, productNameEsc)}</title>`);
25658
25907
  html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
@@ -25675,7 +25924,7 @@ function loadBrandingCache(agentSlug) {
25675
25924
  if (!accountId) return null;
25676
25925
  const cachePath = join36(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
25677
25926
  if (!existsSync35(cachePath)) return null;
25678
- return JSON.parse(readFileSync35(cachePath, "utf-8"));
25927
+ return JSON.parse(readFileSync36(cachePath, "utf-8"));
25679
25928
  } catch {
25680
25929
  return null;
25681
25930
  }
@@ -25770,7 +26019,7 @@ app61.use("/vnc-popout.html", logViewerFetch);
25770
26019
  app61.get("/vnc-popout.html", (c) => {
25771
26020
  let html = htmlCache.get("vnc-popout.html");
25772
26021
  if (!html) {
25773
- html = readFileSync35(resolve33(process.cwd(), "public", "vnc-popout.html"), "utf-8");
26022
+ html = readFileSync36(resolve33(process.cwd(), "public", "vnc-popout.html"), "utf-8");
25774
26023
  const name = escapeHtml(BRAND.productName);
25775
26024
  html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
25776
26025
  html = html.replace("</head>", ` ${brandScript}
@@ -25958,7 +26207,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
25958
26207
  let hasBookingSite = false;
25959
26208
  try {
25960
26209
  if (existsSync35(availPath)) {
25961
- const cfg = JSON.parse(readFileSync35(availPath, "utf-8"));
26210
+ const cfg = JSON.parse(readFileSync36(availPath, "utf-8"));
25962
26211
  hasBookingSite = typeof cfg.bookingDbName === "string" && cfg.bookingDbName.length > 0;
25963
26212
  }
25964
26213
  } catch {
@@ -25968,7 +26217,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
25968
26217
  let lastSuccessAt = null;
25969
26218
  if (existsSync35(statePath)) {
25970
26219
  try {
25971
- const rec = JSON.parse(readFileSync35(statePath, "utf-8"));
26220
+ const rec = JSON.parse(readFileSync36(statePath, "utf-8"));
25972
26221
  lastSuccessAt = rec.lastSuccessAt ?? null;
25973
26222
  } catch {
25974
26223
  console.error(`[calendar-availability] op=audit accountId=${account.accountId} snapshotAgeMs=na reason=bad-state-file`);
@@ -26111,7 +26360,7 @@ async function runAdminUserReconcileTick() {
26111
26360
  console.error("[adminuser-self-heal] skip reason=no-users-file");
26112
26361
  return;
26113
26362
  }
26114
- const usersRaw = readFileSync35(USERS_FILE, "utf-8").trim();
26363
+ const usersRaw = readFileSync36(USERS_FILE, "utf-8").trim();
26115
26364
  if (!usersRaw) {
26116
26365
  console.error("[adminuser-self-heal] skip reason=empty-users-file");
26117
26366
  return;
@@ -26141,7 +26390,7 @@ if (typeof adminUserReconcileTimer.unref === "function") adminUserReconcileTimer
26141
26390
  var USERS_RECONCILE_INTERVAL_MS = 60 * 60 * 1e3;
26142
26391
  function countUsersRows() {
26143
26392
  if (!existsSync35(USERS_FILE)) return 0;
26144
- const raw = readFileSync35(USERS_FILE, "utf-8").trim();
26393
+ const raw = readFileSync36(USERS_FILE, "utf-8").trim();
26145
26394
  if (!raw) return 0;
26146
26395
  const users = JSON.parse(raw);
26147
26396
  return users.filter((u) => typeof u.userId === "string").length;