@rubytech/create-maxy-code 0.1.440 → 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 (107) 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 +64 -8
  4. package/payload/platform/plugins/cloudflare/skills/calendar-site/SKILL.md +15 -5
  5. package/payload/platform/plugins/cloudflare/skills/calendar-site/template/functions/api/calendar/free-busy.ts +152 -0
  6. package/payload/platform/plugins/cloudflare/skills/calendar-site/template/public/index.html +6 -3
  7. package/payload/platform/plugins/cloudflare/skills/calendar-site/template/schema.sql +11 -0
  8. package/payload/platform/plugins/docs/references/admin-ui.md +1 -1
  9. package/payload/platform/plugins/docs/references/calendar-booking.md +1 -1
  10. package/payload/platform/plugins/docs/references/graph.md +19 -3
  11. package/payload/platform/plugins/docs/references/outlook-guide.md +7 -2
  12. package/payload/platform/plugins/docs/references/troubleshooting.md +35 -0
  13. package/payload/platform/plugins/outlook/.claude-plugin/plugin.json +1 -1
  14. package/payload/platform/plugins/outlook/PLUGIN.md +7 -3
  15. package/payload/platform/plugins/outlook/mcp/dist/__tests__/account-register.test.js +100 -10
  16. package/payload/platform/plugins/outlook/mcp/dist/__tests__/account-register.test.js.map +1 -1
  17. package/payload/platform/plugins/outlook/mcp/dist/__tests__/device-flow.test.js +37 -4
  18. package/payload/platform/plugins/outlook/mcp/dist/__tests__/device-flow.test.js.map +1 -1
  19. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-list.test.d.ts +2 -0
  20. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-list.test.d.ts.map +1 -0
  21. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-list.test.js +64 -0
  22. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-list.test.js.map +1 -0
  23. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-registry.test.d.ts +2 -0
  24. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-registry.test.d.ts.map +1 -0
  25. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-registry.test.js +193 -0
  26. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-registry.test.js.map +1 -0
  27. package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.js +58 -1
  28. package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.js.map +1 -1
  29. package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.d.ts +27 -3
  30. package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.d.ts.map +1 -1
  31. package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.js +52 -11
  32. package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.js.map +1 -1
  33. package/payload/platform/plugins/outlook/mcp/dist/auth/mailbox-registry.d.ts +81 -0
  34. package/payload/platform/plugins/outlook/mcp/dist/auth/mailbox-registry.d.ts.map +1 -0
  35. package/payload/platform/plugins/outlook/mcp/dist/auth/mailbox-registry.js +171 -0
  36. package/payload/platform/plugins/outlook/mcp/dist/auth/mailbox-registry.js.map +1 -0
  37. package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.d.ts +16 -1
  38. package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.d.ts.map +1 -1
  39. package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.js +25 -3
  40. package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.js.map +1 -1
  41. package/payload/platform/plugins/outlook/mcp/dist/index.js +119 -50
  42. package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -1
  43. package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js +1 -1
  44. package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js.map +1 -1
  45. package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.d.ts +13 -4
  46. package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.d.ts.map +1 -1
  47. package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.js +54 -3
  48. package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.js.map +1 -1
  49. package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-list.d.ts +26 -0
  50. package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-list.d.ts.map +1 -0
  51. package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-list.js +28 -0
  52. package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-list.js.map +1 -0
  53. package/payload/platform/plugins/outlook/references/auth.md +36 -8
  54. package/payload/platform/plugins/scheduling/mcp/dist/scripts/publish-availability.d.ts +27 -0
  55. package/payload/platform/plugins/scheduling/mcp/dist/scripts/publish-availability.d.ts.map +1 -0
  56. package/payload/platform/plugins/scheduling/mcp/dist/scripts/publish-availability.js +241 -0
  57. package/payload/platform/plugins/scheduling/mcp/dist/scripts/publish-availability.js.map +1 -0
  58. package/payload/platform/plugins/whatsapp/mcp/dist/index.js +20 -22
  59. package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
  60. package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +3 -1
  61. package/payload/platform/scripts/logs-read.sh +24 -6
  62. package/payload/platform/scripts/logs-read.test.sh +42 -0
  63. package/payload/platform/scripts/rss-sampler.sh +107 -0
  64. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  65. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +1 -0
  66. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  67. package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
  68. package/payload/server/public/assets/{AdminLoginScreens-CukwQufW.js → AdminLoginScreens-BJVwtpLH.js} +1 -1
  69. package/payload/server/public/assets/{AdminShell-BwEBH6tT.js → AdminShell-CMu8_Xwn.js} +1 -1
  70. package/payload/server/public/assets/{Checkbox-B629mOPp.js → Checkbox-CzD0ORqA.js} +1 -1
  71. package/payload/server/public/assets/{admin-CdFRCUUZ.js → admin-D0yk2LMq.js} +1 -1
  72. package/payload/server/public/assets/{browser-BWR59IkB.js → browser-CvfbgznO.js} +1 -1
  73. package/payload/server/public/assets/{calendar-kuTcxX1t.js → calendar-fw4WethJ.js} +1 -1
  74. package/payload/server/public/assets/chat-DhD-NES_.js +1 -0
  75. package/payload/server/public/assets/chevron-left-B3xSeEDR.js +1 -0
  76. package/payload/server/public/assets/data-CUnDzDox.js +1 -0
  77. package/payload/server/public/assets/{file-download-4mKJxUi-.js → file-download-Px4s0Bhp.js} +1 -1
  78. package/payload/server/public/assets/{graph-NgQDY0Pb.js → graph-DbT8nA6R.js} +2 -2
  79. package/payload/server/public/assets/{graph-labels-B8YAdo3Z.js → graph-labels-C1YIyLiA.js} +1 -1
  80. package/payload/server/public/assets/{maximize-2-CxstTgoK.js → maximize-2-BwphzI9S.js} +1 -1
  81. package/payload/server/public/assets/{operator-oSaB1mya.js → operator-zcIfeB14.js} +1 -1
  82. package/payload/server/public/assets/{page-DMeJHVfA.js → page-6fCnIb45.js} +2 -2
  83. package/payload/server/public/assets/page-CfQFAJia.js +1 -0
  84. package/payload/server/public/assets/{public-C54tl6V4.js → public-C0ehZCdE.js} +1 -1
  85. package/payload/server/public/assets/{rotate-ccw-COGQTi7n.js → rotate-ccw-C7BqkMec.js} +1 -1
  86. package/payload/server/public/assets/{tasks-B97VpwEJ.js → tasks-DlgiCafL.js} +1 -1
  87. package/payload/server/public/assets/{time-entry-format-C09rDHo9.js → time-entry-format-CyNYVDkq.js} +1 -1
  88. package/payload/server/public/assets/{useCopyFeedback-DHoYHiK9.js → useCopyFeedback-ByGUMN94.js} +1 -1
  89. package/payload/server/public/assets/useSubAccountSwitcher-1izQvPiD.css +1 -0
  90. package/payload/server/public/assets/useVoiceRecorder-XgpWvXpk.js +2 -0
  91. package/payload/server/public/browser.html +5 -5
  92. package/payload/server/public/calendar.html +6 -6
  93. package/payload/server/public/chat.html +12 -12
  94. package/payload/server/public/data.html +10 -10
  95. package/payload/server/public/graph.html +11 -11
  96. package/payload/server/public/index.html +13 -13
  97. package/payload/server/public/operator.html +14 -14
  98. package/payload/server/public/public.html +12 -12
  99. package/payload/server/public/tasks.html +5 -5
  100. package/payload/server/server.js +498 -180
  101. package/payload/server/public/assets/chat-ejnq2rck.js +0 -1
  102. package/payload/server/public/assets/chevron-left-u4tjq7fI.js +0 -1
  103. package/payload/server/public/assets/data-dZbOyLdT.js +0 -1
  104. package/payload/server/public/assets/page-DEkI-Gl1.js +0 -1
  105. package/payload/server/public/assets/useSubAccountSwitcher-Dzmd7Aip.css +0 -1
  106. package/payload/server/public/assets/useVoiceRecorder-b7HzTFs0.js +0 -2
  107. /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
  }
@@ -8273,6 +8444,7 @@ function pickOwnerUserId(admins) {
8273
8444
  function resolveOwnerUserId(accountId) {
8274
8445
  try {
8275
8446
  const account = listValidAccounts().find((a) => a.accountId === accountId);
8447
+ if (account?.config.role === "client") return null;
8276
8448
  return pickOwnerUserId(account?.config.admins ?? []);
8277
8449
  } catch {
8278
8450
  return null;
@@ -8283,6 +8455,7 @@ function resolveAdminUserId(params) {
8283
8455
  const users = readUsersFile(USERS_FILE);
8284
8456
  if (!users) return null;
8285
8457
  const account = listValidAccounts().find((a) => a.accountId === params.accountId);
8458
+ if (account?.config.role === "client") return null;
8286
8459
  const admins = account?.config.admins ?? [];
8287
8460
  const direct = pickAdminUserId(users, admins, params.senderPhone);
8288
8461
  if (direct) return direct;
@@ -8357,7 +8530,7 @@ function enumerateJsonls(projectsRoot) {
8357
8530
  const out = [];
8358
8531
  let slugs;
8359
8532
  try {
8360
- 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);
8361
8534
  } catch (err) {
8362
8535
  if (err.code === "ENOENT") return out;
8363
8536
  throw err;
@@ -8366,7 +8539,7 @@ function enumerateJsonls(projectsRoot) {
8366
8539
  const slugDir = join13(projectsRoot, slug);
8367
8540
  let entries;
8368
8541
  try {
8369
- entries = readdirSync7(slugDir, { withFileTypes: true });
8542
+ entries = readdirSync8(slugDir, { withFileTypes: true });
8370
8543
  } catch (err) {
8371
8544
  const code = err.code ?? "unknown";
8372
8545
  console.error(`[admin-sessions-list] slug-skipped slug=${slug} code=${code}`);
@@ -8379,7 +8552,7 @@ function enumerateJsonls(projectsRoot) {
8379
8552
  const subDir = join13(slugDir, entry.name);
8380
8553
  let subEntries;
8381
8554
  try {
8382
- subEntries = readdirSync7(subDir, { withFileTypes: true });
8555
+ subEntries = readdirSync8(subDir, { withFileTypes: true });
8383
8556
  } catch (err) {
8384
8557
  const code = err.code ?? "unknown";
8385
8558
  console.error(`[admin-sessions-list] subagents-skipped slug=${slug} code=${code}`);
@@ -8394,7 +8567,7 @@ function enumerateJsonls(projectsRoot) {
8394
8567
  const archiveDir = join13(slugDir, entry.name);
8395
8568
  let archiveEntries;
8396
8569
  try {
8397
- archiveEntries = readdirSync7(archiveDir, { withFileTypes: true });
8570
+ archiveEntries = readdirSync8(archiveDir, { withFileTypes: true });
8398
8571
  } catch (err) {
8399
8572
  const code = err.code ?? "unknown";
8400
8573
  console.error(`[admin-sessions-list] archive-skipped slug=${slug} code=${code}`);
@@ -8438,7 +8611,7 @@ function loadUserTitles(accountDir) {
8438
8611
  const path2 = join13(accountDir, "session-titles.json");
8439
8612
  let raw;
8440
8613
  try {
8441
- raw = readFileSync13(path2, "utf8");
8614
+ raw = readFileSync14(path2, "utf8");
8442
8615
  } catch {
8443
8616
  return out;
8444
8617
  }
@@ -8464,7 +8637,7 @@ function readSidecarMeta(metaPath) {
8464
8637
  const empty = { bridgeIds: [], role: null, channel: null, senderId: null, startedAt: null, personId: null, adminUserId: null, visitorId: null, agentSlug: null, accountId: null };
8465
8638
  let raw;
8466
8639
  try {
8467
- raw = readFileSync13(metaPath, "utf8");
8640
+ raw = readFileSync14(metaPath, "utf8");
8468
8641
  } catch {
8469
8642
  return empty;
8470
8643
  }
@@ -8606,7 +8779,7 @@ app3.get("/", requireAdminSession, async (c) => {
8606
8779
  let body;
8607
8780
  let mtimeMs;
8608
8781
  try {
8609
- body = readFileSync13(path2, "utf8");
8782
+ body = readFileSync14(path2, "utf8");
8610
8783
  mtimeMs = statSync4(path2).mtimeMs;
8611
8784
  } catch {
8612
8785
  continue;
@@ -8966,7 +9139,7 @@ function parseTranscript(lines, queuedPendingSuppress = /* @__PURE__ */ new Map(
8966
9139
  }
8967
9140
 
8968
9141
  // app/lib/whatsapp-reader/enrich-attachments.ts
8969
- import { readFileSync as readFileSync14, readdirSync as readdirSync8 } from "fs";
9142
+ import { readFileSync as readFileSync15, readdirSync as readdirSync9 } from "fs";
8970
9143
  import { join as join14 } from "path";
8971
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;
8972
9145
  function adminAttachmentIds(text) {
@@ -8988,7 +9161,7 @@ function adminAttachmentsFromText(text, uploadsBase) {
8988
9161
  function readSidecar(dir, attachmentId) {
8989
9162
  let parsed;
8990
9163
  try {
8991
- parsed = JSON.parse(readFileSync14(join14(dir, `${attachmentId}.meta.json`), "utf8"));
9164
+ parsed = JSON.parse(readFileSync15(join14(dir, `${attachmentId}.meta.json`), "utf8"));
8992
9165
  } catch {
8993
9166
  return null;
8994
9167
  }
@@ -9011,7 +9184,7 @@ function readAttachmentMeta(dir) {
9011
9184
  function listSessionAttachmentsInDir(dir) {
9012
9185
  let entries;
9013
9186
  try {
9014
- 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);
9015
9188
  } catch {
9016
9189
  return [];
9017
9190
  }
@@ -9058,7 +9231,7 @@ function pickActivity(entries, grown, nowMs, concurrencyWindowMs) {
9058
9231
  }
9059
9232
 
9060
9233
  // app/admin-types.ts
9061
- 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";
9062
9235
  var SUPPORTED_MIME_SET = new Set(ACCEPT_MIME.split(",").filter((t) => !t.startsWith(".")));
9063
9236
  var MAX_ATTACHMENTS = 5;
9064
9237
  var MAX_FILE_BYTES = 50 * 1024 * 1024;
@@ -9097,7 +9270,7 @@ app4.get("/conversations", requireAdminSession, async (c) => {
9097
9270
  }
9098
9271
  let body = "";
9099
9272
  try {
9100
- body = readFileSync15(path2, "utf8");
9273
+ body = readFileSync16(path2, "utf8");
9101
9274
  } catch {
9102
9275
  }
9103
9276
  const { title } = resolveTitle(body, sessionId, userTitles);
@@ -9266,7 +9439,7 @@ function openTaskKey(turns) {
9266
9439
  }
9267
9440
  function readSubagentMeta(dir, hex) {
9268
9441
  try {
9269
- 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"));
9270
9443
  if (typeof m.agentType === "string" && typeof m.description === "string" && typeof m.toolUseId === "string") {
9271
9444
  return { agentType: m.agentType, description: m.description, toolUseId: m.toolUseId };
9272
9445
  }
@@ -9289,7 +9462,7 @@ function runActivityTick(c) {
9289
9462
  c.parentGrewThisTick = false;
9290
9463
  let names = [];
9291
9464
  try {
9292
- names = readdirSync9(c.subagentsDir).filter((n) => AGENT_JSONL_RE.test(n));
9465
+ names = readdirSync10(c.subagentsDir).filter((n) => AGENT_JSONL_RE.test(n));
9293
9466
  } catch {
9294
9467
  }
9295
9468
  const entries = [];
@@ -9484,7 +9657,7 @@ app4.get("/directives", requireAdminSession, (c) => {
9484
9657
  if (!dir || !existsSync10(dir)) return c.json({ entries: [] });
9485
9658
  let names;
9486
9659
  try {
9487
- names = readdirSync9(dir).filter((n) => DIRECTIVE_NAME_RE.test(n));
9660
+ names = readdirSync10(dir).filter((n) => DIRECTIVE_NAME_RE.test(n));
9488
9661
  } catch {
9489
9662
  return c.json({ entries: [] });
9490
9663
  }
@@ -9513,7 +9686,7 @@ app4.get("/directive", requireAdminSession, (c) => {
9513
9686
  if (!path2.startsWith(dir + sep3)) return c.json({ error: "bad reference" }, 400);
9514
9687
  let body;
9515
9688
  try {
9516
- body = readFileSync15(path2, "utf8");
9689
+ body = readFileSync16(path2, "utf8");
9517
9690
  } catch {
9518
9691
  return c.json({ error: "not found" }, 404);
9519
9692
  }
@@ -9870,7 +10043,7 @@ app4.post("/reply", requireAdminSession, async (c) => {
9870
10043
  }
9871
10044
  for (let i = 0; i < resolvedRefs.length; i++) {
9872
10045
  const { absolute, mime } = resolvedRefs[i];
9873
- const file = new File([readFileSync15(absolute)], basename4(absolute), { type: mime });
10046
+ const file = new File([readFileSync16(absolute)], basename4(absolute), { type: mime });
9874
10047
  const out = await sendStagedFile(file, `ref=${i}`);
9875
10048
  if (!out.ok) {
9876
10049
  exit("error");
@@ -9889,7 +10062,7 @@ app4.post("/reply", requireAdminSession, async (c) => {
9889
10062
  var whatsapp_reader_default = app4;
9890
10063
 
9891
10064
  // server/routes/public-reader.ts
9892
- 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";
9893
10066
  import { basename as basename5, dirname as dirname5, join as join16, resolve as resolve13, sep as sep4 } from "path";
9894
10067
 
9895
10068
  // app/lib/whatsapp-reader/delivered-kinds.ts
@@ -10123,7 +10296,7 @@ data: ${JSON.stringify(turn)}
10123
10296
  });
10124
10297
  });
10125
10298
  var ATT_TAG = "[public-attachment]";
10126
- 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;
10127
10300
  app5.get("/attachment/:attachmentId", (c) => {
10128
10301
  const visitor = resolveVisitor(c);
10129
10302
  const attachmentId = c.req.param("attachmentId");
@@ -10135,7 +10308,7 @@ app5.get("/attachment/:attachmentId", (c) => {
10135
10308
  return new Response("Unauthorized", { status: 401 });
10136
10309
  }
10137
10310
  console.log(`${ATT_TAG} op=request session=${ses} att=${at}`);
10138
- if (!ATTACHMENT_ID_RE.test(attachmentId) || !SESSION_ID_RE2.test(sessionId)) {
10311
+ if (!ATTACHMENT_ID_RE2.test(attachmentId) || !SESSION_ID_RE2.test(sessionId)) {
10139
10312
  console.error(`${ATT_TAG} op=denied session=${ses} att=${at} reason=bad-id`);
10140
10313
  return new Response("Not found", { status: 404 });
10141
10314
  }
@@ -10164,19 +10337,22 @@ app5.get("/attachment/:attachmentId", (c) => {
10164
10337
  let dataFile;
10165
10338
  let buffer;
10166
10339
  try {
10167
- 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));
10168
10341
  if (!dataFile) throw new Error("no data file");
10169
- buffer = readFileSync16(resolve13(dir, dataFile));
10342
+ buffer = readFileSync17(resolve13(dir, dataFile));
10170
10343
  } catch {
10171
10344
  console.error(`${ATT_TAG} op=denied session=${ses} att=${at} reason=not-found`);
10172
10345
  return new Response("Not found", { status: 404 });
10173
10346
  }
10174
- 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"}`);
10175
10350
  const safeName = meta.filename.replace(/["\\\r\n]/g, "_");
10176
10351
  return new Response(new Uint8Array(buffer), {
10177
10352
  headers: {
10178
10353
  "Content-Type": meta.mimeType,
10179
- "Content-Disposition": `inline; filename="${safeName}"`,
10354
+ "Content-Disposition": `${disposition}; filename="${safeName}"`,
10355
+ "X-Content-Type-Options": "nosniff",
10180
10356
  "Cache-Control": "private, max-age=3600"
10181
10357
  }
10182
10358
  });
@@ -10186,10 +10362,10 @@ var public_reader_default = app5;
10186
10362
  // server/routes/webchat.ts
10187
10363
  import { basename as basename6, dirname as dirname6 } from "path";
10188
10364
  import { join as join19 } from "path";
10189
- 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";
10190
10366
 
10191
10367
  // server/canonical-webchat-override.ts
10192
- 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";
10193
10369
  import { join as join17 } from "path";
10194
10370
  var FILE = "canonical-webchat-session.json";
10195
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}$/;
@@ -10198,7 +10374,7 @@ function canonicalOverridePath(accountDir) {
10198
10374
  }
10199
10375
  function readRaw(accountDir) {
10200
10376
  try {
10201
- return JSON.parse(readFileSync17(canonicalOverridePath(accountDir), "utf8"));
10377
+ return JSON.parse(readFileSync18(canonicalOverridePath(accountDir), "utf8"));
10202
10378
  } catch {
10203
10379
  return null;
10204
10380
  }
@@ -10273,7 +10449,7 @@ function isNewSessionEffortLevel(value) {
10273
10449
  }
10274
10450
 
10275
10451
  // ../services/claude-session-manager/src/sidecar-store.ts
10276
- 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";
10277
10453
  import { join as join18 } from "path";
10278
10454
  function escapeRegExp(s) {
10279
10455
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -10303,7 +10479,7 @@ function createSidecarStore(config) {
10303
10479
  function readFileAt(path2) {
10304
10480
  let raw;
10305
10481
  try {
10306
- raw = readFileSync18(path2, "utf8");
10482
+ raw = readFileSync19(path2, "utf8");
10307
10483
  } catch {
10308
10484
  return null;
10309
10485
  }
@@ -10319,7 +10495,7 @@ function createSidecarStore(config) {
10319
10495
  function readAll(sessionsDir, onSkip) {
10320
10496
  let names;
10321
10497
  try {
10322
- 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);
10323
10499
  } catch {
10324
10500
  return [];
10325
10501
  }
@@ -10329,7 +10505,7 @@ function createSidecarStore(config) {
10329
10505
  const path2 = join18(sessionsDir, name);
10330
10506
  let raw;
10331
10507
  try {
10332
- raw = readFileSync18(path2, "utf8");
10508
+ raw = readFileSync19(path2, "utf8");
10333
10509
  } catch {
10334
10510
  onSkip?.(name, "unreadable");
10335
10511
  continue;
@@ -10412,7 +10588,7 @@ function locateSidecar(sessionId) {
10412
10588
  const projectsRoot = join19(cfg, "projects");
10413
10589
  let slugs;
10414
10590
  try {
10415
- 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);
10416
10592
  } catch {
10417
10593
  return null;
10418
10594
  }
@@ -10895,7 +11071,7 @@ ${note}` : note;
10895
11071
  }
10896
11072
  const accountJsonPath = join19(account.accountDir, "account.json");
10897
11073
  try {
10898
- const parsed = JSON.parse(readFileSync19(accountJsonPath, "utf8"));
11074
+ const parsed = JSON.parse(readFileSync20(accountJsonPath, "utf8"));
10899
11075
  if (op === "model") parsed.adminModel = value;
10900
11076
  else if (op === "effort") parsed.effort = value;
10901
11077
  else parsed.adminPermissionMode = value;
@@ -11013,7 +11189,7 @@ ${note}` : note;
11013
11189
  import { resolve as resolve14 } from "path";
11014
11190
 
11015
11191
  // app/lib/claude-agent/specialist-roster.ts
11016
- 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";
11017
11193
  import { join as join20 } from "path";
11018
11194
  function field(fm, name) {
11019
11195
  const m = fm.match(new RegExp(`^${name}:\\s*(.*?)\\r?$`, "m"));
@@ -11026,14 +11202,14 @@ function field(fm, name) {
11026
11202
  }
11027
11203
  function readSpecialistRoster(specialistsDir) {
11028
11204
  if (!existsSync13(specialistsDir)) return { specialists: [], skipped: [] };
11029
- const entries = readdirSync13(specialistsDir);
11205
+ const entries = readdirSync14(specialistsDir);
11030
11206
  const specialists = [];
11031
11207
  const skipped = [];
11032
11208
  for (const file of entries.sort()) {
11033
11209
  if (!file.endsWith(".md")) continue;
11034
11210
  let raw;
11035
11211
  try {
11036
- raw = readFileSync20(join20(specialistsDir, file), "utf-8");
11212
+ raw = readFileSync21(join20(specialistsDir, file), "utf-8");
11037
11213
  } catch (err) {
11038
11214
  skipped.push({ file, reason: err instanceof Error ? err.message : String(err) });
11039
11215
  continue;
@@ -11151,7 +11327,7 @@ var webchat_greeting_default = app6;
11151
11327
  // server/routes/telegram.ts
11152
11328
  import { homedir as homedir2 } from "os";
11153
11329
  import { resolve as resolve15, join as join21 } from "path";
11154
- import { existsSync as existsSync14, readFileSync as readFileSync21 } from "fs";
11330
+ import { existsSync as existsSync14, readFileSync as readFileSync22 } from "fs";
11155
11331
 
11156
11332
  // app/lib/telegram/gateway/instance.ts
11157
11333
  var instance = null;
@@ -11213,7 +11389,7 @@ function configDirName() {
11213
11389
  const brandPath = join21(platformRoot3, "config", "brand.json");
11214
11390
  if (existsSync14(brandPath)) {
11215
11391
  try {
11216
- return JSON.parse(readFileSync21(brandPath, "utf-8")).configDir ?? ".maxy";
11392
+ return JSON.parse(readFileSync22(brandPath, "utf-8")).configDir ?? ".maxy";
11217
11393
  } catch {
11218
11394
  }
11219
11395
  }
@@ -11236,7 +11412,7 @@ app7.post("/", async (c) => {
11236
11412
  console.error(`${TAG23} op=reject reason=no-secret-file botType=${botType}`);
11237
11413
  return c.json({ ok: false }, 401);
11238
11414
  }
11239
- const expected = readFileSync21(sp, "utf-8").trim();
11415
+ const expected = readFileSync22(sp, "utf-8").trim();
11240
11416
  const got = c.req.header("x-telegram-bot-api-secret-token") ?? "";
11241
11417
  if (got !== expected) {
11242
11418
  console.error(`${TAG23} op=reject reason=bad-secret botType=${botType}`);
@@ -11300,7 +11476,7 @@ var telegram_default = app7;
11300
11476
 
11301
11477
  // server/routes/quickbooks.ts
11302
11478
  import { join as join22 } from "path";
11303
- 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";
11304
11480
  var TAG24 = "[quickbooks]";
11305
11481
  var INTUIT_TOKEN_URL = "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer";
11306
11482
  var STATE_RE = /^[A-Za-z0-9_-]+$/;
@@ -11327,7 +11503,7 @@ async function completeConsent(args) {
11327
11503
  try {
11328
11504
  let pending;
11329
11505
  try {
11330
- pending = JSON.parse(readFileSync22(claimFile, "utf-8"));
11506
+ pending = JSON.parse(readFileSync23(claimFile, "utf-8"));
11331
11507
  } catch {
11332
11508
  console.error(`${TAG24} op=callback state=${state} stateValid=false realmId=${realmId ?? ""}`);
11333
11509
  return { ok: false, status: 400, message: "Authorization state could not be read.", stateValid: false };
@@ -11343,7 +11519,7 @@ async function completeConsent(args) {
11343
11519
  if (!existsSync15(storePath(accountDir))) {
11344
11520
  return { ok: false, status: 500, message: "No QuickBooks credentials are stored for this account.", stateValid: true };
11345
11521
  }
11346
- const store2 = JSON.parse(readFileSync22(storePath(accountDir), "utf-8"));
11522
+ const store2 = JSON.parse(readFileSync23(storePath(accountDir), "utf-8"));
11347
11523
  const form = new URLSearchParams({
11348
11524
  grant_type: "authorization_code",
11349
11525
  code,
@@ -11377,7 +11553,7 @@ async function completeConsent(args) {
11377
11553
  };
11378
11554
  mkdirSync4(join22(accountDir, "secrets"), { recursive: true });
11379
11555
  writeFileSync8(storePath(accountDir), JSON.stringify(store2, null, 2), { mode: 384 });
11380
- 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;
11381
11557
  console.error(`${TAG24} op=token-exchange realmId=${realmId} persisted=${persisted}`);
11382
11558
  return { ok: persisted, status: persisted ? 200 : 500, message: persisted ? `Connected company ${realmId}.` : "Failed to persist the connection.", stateValid: true };
11383
11559
  } finally {
@@ -11408,7 +11584,7 @@ var quickbooks_default = app8;
11408
11584
 
11409
11585
  // server/routes/onboarding.ts
11410
11586
  import { spawn, spawnSync as spawnSync2, execFileSync as execFileSync3 } from "child_process";
11411
- 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";
11412
11588
  import { createHash as createHash4, randomUUID as randomUUID7 } from "crypto";
11413
11589
 
11414
11590
  // app/lib/claude-spawn-env.ts
@@ -11546,7 +11722,7 @@ function readHostCredsBlob(service) {
11546
11722
  }
11547
11723
 
11548
11724
  // ../lib/admins-write/src/index.ts
11549
- 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";
11550
11726
  import { dirname as dirname7, join as join23 } from "path";
11551
11727
  function id8(value) {
11552
11728
  return value.slice(0, 8);
@@ -11589,7 +11765,7 @@ function writeAdminEntry(input) {
11589
11765
  try {
11590
11766
  let users = [];
11591
11767
  if (existsSync16(input.usersFile)) {
11592
- const raw = readFileSync23(input.usersFile, "utf-8").trim();
11768
+ const raw = readFileSync24(input.usersFile, "utf-8").trim();
11593
11769
  if (raw) {
11594
11770
  const parsed = JSON.parse(raw);
11595
11771
  if (!Array.isArray(parsed)) {
@@ -11624,7 +11800,7 @@ function writeAdminEntry(input) {
11624
11800
  if (!existsSync16(accountJsonPath)) {
11625
11801
  throw new Error(`account.json not found at ${accountJsonPath}`);
11626
11802
  }
11627
- const config = JSON.parse(readFileSync23(accountJsonPath, "utf-8"));
11803
+ const config = JSON.parse(readFileSync24(accountJsonPath, "utf-8"));
11628
11804
  const admins = config.admins ?? [];
11629
11805
  if (admins.some((a) => a.userId === input.userId)) {
11630
11806
  result.accountJsonResult = "noop";
@@ -11646,7 +11822,7 @@ function computeAdminStoreDivergence(input) {
11646
11822
  const usersUserIds = /* @__PURE__ */ new Set();
11647
11823
  if (existsSync16(input.usersFile)) {
11648
11824
  try {
11649
- const raw = readFileSync23(input.usersFile, "utf-8").trim();
11825
+ const raw = readFileSync24(input.usersFile, "utf-8").trim();
11650
11826
  if (raw) {
11651
11827
  const users = JSON.parse(raw);
11652
11828
  for (const u of users) {
@@ -11661,7 +11837,7 @@ function computeAdminStoreDivergence(input) {
11661
11837
  if (existsSync16(input.accountsDir)) {
11662
11838
  let entries;
11663
11839
  try {
11664
- entries = readdirSync14(input.accountsDir);
11840
+ entries = readdirSync15(input.accountsDir);
11665
11841
  } catch (err) {
11666
11842
  result.errors.push({ source: input.accountsDir, detail: err instanceof Error ? err.message : String(err) });
11667
11843
  return result;
@@ -11678,7 +11854,7 @@ function computeAdminStoreDivergence(input) {
11678
11854
  if (!existsSync16(accountJsonPath)) continue;
11679
11855
  let admins = [];
11680
11856
  try {
11681
- const config = JSON.parse(readFileSync23(accountJsonPath, "utf-8"));
11857
+ const config = JSON.parse(readFileSync24(accountJsonPath, "utf-8"));
11682
11858
  admins = config.admins ?? [];
11683
11859
  } catch (err) {
11684
11860
  result.errors.push({ source: accountJsonPath, detail: err instanceof Error ? err.message : String(err) });
@@ -11727,7 +11903,7 @@ function hashPin2(pin) {
11727
11903
  }
11728
11904
  function readUsersFile2() {
11729
11905
  if (!existsSync17(USERS_FILE)) return null;
11730
- const raw = readFileSync24(USERS_FILE, "utf-8").trim();
11906
+ const raw = readFileSync25(USERS_FILE, "utf-8").trim();
11731
11907
  if (!raw) return [];
11732
11908
  return JSON.parse(raw);
11733
11909
  }
@@ -11937,7 +12113,7 @@ app9.post("/set-pin", async (c) => {
11937
12113
  let installOwner = null;
11938
12114
  if (existsSync17(INSTALL_OWNER_FILE)) {
11939
12115
  try {
11940
- const raw = readFileSync24(INSTALL_OWNER_FILE, "utf-8").trim();
12116
+ const raw = readFileSync25(INSTALL_OWNER_FILE, "utf-8").trim();
11941
12117
  if (raw.length > 0) installOwner = raw;
11942
12118
  } catch (err) {
11943
12119
  console.error(`[set-pin] install-owner-read-failed path=${INSTALL_OWNER_FILE} error=${err instanceof Error ? err.message : String(err)}`);
@@ -12085,7 +12261,7 @@ app9.put("/set-pin", requireAdminSession, async (c) => {
12085
12261
  let installOwner = null;
12086
12262
  if (existsSync17(INSTALL_OWNER_FILE)) {
12087
12263
  try {
12088
- const raw = readFileSync24(INSTALL_OWNER_FILE, "utf-8").trim();
12264
+ const raw = readFileSync25(INSTALL_OWNER_FILE, "utf-8").trim();
12089
12265
  if (raw.length > 0) installOwner = raw;
12090
12266
  } catch (err) {
12091
12267
  console.error(`[set-pin] install-owner-read-failed path=${INSTALL_OWNER_FILE} error=${err instanceof Error ? err.message : String(err)}`);
@@ -12487,7 +12663,7 @@ app12.get("/", requireAdminSession, async (c) => {
12487
12663
  var accounts_default = app12;
12488
12664
 
12489
12665
  // server/routes/admin/logs.ts
12490
- 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";
12491
12667
  import { resolve as resolve16, basename as basename7 } from "path";
12492
12668
 
12493
12669
  // app/lib/logs-read-resolve.ts
@@ -12526,7 +12702,7 @@ app13.get("/", async (c) => {
12526
12702
  const filePath = resolve16(dir, safe);
12527
12703
  searched.push(filePath);
12528
12704
  try {
12529
- const buffer = readFileSync25(filePath);
12705
+ const buffer = readFileSync26(filePath);
12530
12706
  const onDiskBytes = statSync10(filePath).size;
12531
12707
  const headers = {
12532
12708
  "Content-Type": "text/plain; charset=utf-8",
@@ -12591,7 +12767,7 @@ app13.get("/", async (c) => {
12591
12767
  console.info(`[admin/logs] resolved cacheKey=${cacheKeySlice} sessionId=${sessionIdSlice} via=${primaryId === cacheKey ? "cacheKey" : primaryId === sessionKeyFromConv ? "reverse-lookup" : "sessionId-fallback"}`);
12592
12768
  try {
12593
12769
  const filename = basename7(hit.path);
12594
- const buffer = readFileSync25(hit.path);
12770
+ const buffer = readFileSync26(hit.path);
12595
12771
  const onDiskBytes = statSync10(hit.path).size;
12596
12772
  const headers = {
12597
12773
  "Content-Type": "text/plain; charset=utf-8",
@@ -12629,7 +12805,7 @@ app13.get("/", async (c) => {
12629
12805
  if (!existsSync20(dir)) continue;
12630
12806
  let files;
12631
12807
  try {
12632
- files = readdirSync15(dir).filter((f) => f.endsWith(".log"));
12808
+ files = readdirSync16(dir).filter((f) => f.endsWith(".log"));
12633
12809
  } catch (err) {
12634
12810
  const reason = err instanceof Error ? err.message : String(err);
12635
12811
  console.warn(`[admin/logs] readdir-fail dir=${dir} reason=${reason}`);
@@ -12638,7 +12814,7 @@ app13.get("/", async (c) => {
12638
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 }) => {
12639
12815
  seen.add(name);
12640
12816
  try {
12641
- const content = readFileSync25(resolve16(dir, name));
12817
+ const content = readFileSync26(resolve16(dir, name));
12642
12818
  const tail = content.length > TAIL_BYTES ? content.subarray(content.length - TAIL_BYTES).toString("utf-8") : content.toString("utf-8");
12643
12819
  logs[name] = tail.trim() || "(empty)";
12644
12820
  } catch (err) {
@@ -12681,7 +12857,7 @@ import { readFile as readFile2, readdir } from "fs/promises";
12681
12857
  import { existsSync as existsSync21 } from "fs";
12682
12858
  import { resolve as resolve17 } from "path";
12683
12859
  var app15 = new Hono();
12684
- 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}$/;
12685
12861
  function uploadsDirFor2(accountId, attachmentId) {
12686
12862
  return resolve17(DATA_ROOT, "accounts", accountId, "uploads", attachmentId);
12687
12863
  }
@@ -12695,7 +12871,7 @@ app15.get("/:attachmentId", requireAdminSession, async (c) => {
12695
12871
  console.log(`[admin-attachment] op=miss attachmentId=${shortId2} session=${shortSession} status=401 reason=no-account`);
12696
12872
  return new Response("Unauthorized", { status: 401 });
12697
12873
  }
12698
- if (!ATTACHMENT_ID_RE2.test(attachmentId)) {
12874
+ if (!ATTACHMENT_ID_RE3.test(attachmentId)) {
12699
12875
  console.log(`[admin-attachment] op=miss attachmentId=${shortId2} session=${shortSession} status=404 reason=bad-id`);
12700
12876
  return new Response("Not found", { status: 404 });
12701
12877
  }
@@ -12737,10 +12913,16 @@ app15.get("/:attachmentId", requireAdminSession, async (c) => {
12737
12913
  }
12738
12914
  const filePath = resolve17(dir, dataFile);
12739
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
+ }
12740
12921
  return new Response(new Uint8Array(buffer), {
12741
12922
  headers: {
12742
12923
  "Content-Type": meta.mimeType,
12743
- "Content-Disposition": `inline; filename="${meta.filename}"`,
12924
+ "Content-Disposition": `${inlineSafe ? "inline" : "attachment"}; filename="${safeName}"`,
12925
+ "X-Content-Type-Options": "nosniff",
12744
12926
  "Cache-Control": "private, max-age=3600"
12745
12927
  }
12746
12928
  });
@@ -12749,7 +12931,7 @@ var attachment_default = app15;
12749
12931
 
12750
12932
  // server/routes/admin/agents.ts
12751
12933
  import { resolve as resolve18 } from "path";
12752
- 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";
12753
12935
  var app16 = new Hono();
12754
12936
  app16.get("/", (c) => {
12755
12937
  const account = resolveAccount();
@@ -12758,14 +12940,14 @@ app16.get("/", (c) => {
12758
12940
  if (!existsSync22(agentsDir)) return c.json({ agents: [] });
12759
12941
  const agents = [];
12760
12942
  try {
12761
- const entries = readdirSync16(agentsDir, { withFileTypes: true });
12943
+ const entries = readdirSync17(agentsDir, { withFileTypes: true });
12762
12944
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
12763
12945
  if (!entry.isDirectory()) continue;
12764
12946
  if (entry.name === "admin") continue;
12765
12947
  const configPath2 = resolve18(agentsDir, entry.name, "config.json");
12766
12948
  if (!existsSync22(configPath2)) continue;
12767
12949
  try {
12768
- const config = JSON.parse(readFileSync26(configPath2, "utf-8"));
12950
+ const config = JSON.parse(readFileSync27(configPath2, "utf-8"));
12769
12951
  agents.push({
12770
12952
  slug: entry.name,
12771
12953
  displayName: config.displayName ?? entry.name,
@@ -13524,7 +13706,7 @@ app17.put("/:id/label", requireAdminSession, async (c) => {
13524
13706
  var sessions_default = app17;
13525
13707
 
13526
13708
  // app/lib/claude-agent/spawn-context.ts
13527
- 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";
13528
13710
  import { dirname as dirname9, resolve as resolve19, join as join27 } from "path";
13529
13711
  async function resolveOwnerProfileBlock(accountId, userId) {
13530
13712
  if (!userId) return { ok: false, reason: "missing-user-id" };
@@ -13573,7 +13755,7 @@ function extractPluginToolDescriptions(pluginDir) {
13573
13755
  for (const path2 of candidates) {
13574
13756
  if (!existsSync24(path2)) continue;
13575
13757
  try {
13576
- raw = readFileSync27(path2, "utf-8");
13758
+ raw = readFileSync28(path2, "utf-8");
13577
13759
  break;
13578
13760
  } catch {
13579
13761
  }
@@ -13624,7 +13806,7 @@ function listPluginDirs() {
13624
13806
  const out = /* @__PURE__ */ new Map();
13625
13807
  const platformPlugins = resolve19(PLATFORM_ROOT, "plugins");
13626
13808
  if (existsSync24(platformPlugins)) {
13627
- for (const name of readdirSync17(platformPlugins)) {
13809
+ for (const name of readdirSync18(platformPlugins)) {
13628
13810
  const dir = join27(platformPlugins, name);
13629
13811
  try {
13630
13812
  if (statSync11(dir).isDirectory()) out.set(name, dir);
@@ -13634,11 +13816,11 @@ function listPluginDirs() {
13634
13816
  }
13635
13817
  const premiumRoot = resolve19(dirname9(PLATFORM_ROOT), "premium-plugins");
13636
13818
  if (existsSync24(premiumRoot)) {
13637
- for (const bundle of readdirSync17(premiumRoot)) {
13819
+ for (const bundle of readdirSync18(premiumRoot)) {
13638
13820
  const bundlePlugins = join27(premiumRoot, bundle, "plugins");
13639
13821
  if (!existsSync24(bundlePlugins)) continue;
13640
13822
  try {
13641
- for (const name of readdirSync17(bundlePlugins)) {
13823
+ for (const name of readdirSync18(bundlePlugins)) {
13642
13824
  const dir = join27(bundlePlugins, name);
13643
13825
  try {
13644
13826
  if (statSync11(dir).isDirectory()) out.set(name, dir);
@@ -13655,7 +13837,7 @@ function readEnabledPlugins(accountId) {
13655
13837
  const accountFile = resolve19(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
13656
13838
  if (!existsSync24(accountFile)) return /* @__PURE__ */ new Set();
13657
13839
  try {
13658
- const parsed = JSON.parse(readFileSync27(accountFile, "utf-8"));
13840
+ const parsed = JSON.parse(readFileSync28(accountFile, "utf-8"));
13659
13841
  return new Set(
13660
13842
  Array.isArray(parsed.enabledPlugins) ? parsed.enabledPlugins.filter((p) => typeof p === "string") : []
13661
13843
  );
@@ -13667,7 +13849,7 @@ function readFrontmatter(path2) {
13667
13849
  if (!existsSync24(path2)) return null;
13668
13850
  let raw;
13669
13851
  try {
13670
- raw = readFileSync27(path2, "utf-8");
13852
+ raw = readFileSync28(path2, "utf-8");
13671
13853
  } catch {
13672
13854
  return null;
13673
13855
  }
@@ -13729,7 +13911,7 @@ function computeSpecialistDomains(accountId) {
13729
13911
  if (!existsSync24(specialistsDir)) return [];
13730
13912
  let entries;
13731
13913
  try {
13732
- entries = readdirSync17(specialistsDir);
13914
+ entries = readdirSync18(specialistsDir);
13733
13915
  } catch {
13734
13916
  return [];
13735
13917
  }
@@ -13775,7 +13957,7 @@ function computeDormantPlugins(accountId) {
13775
13957
  if (!existsSync24(accountFile)) return [];
13776
13958
  let enabled;
13777
13959
  try {
13778
- const raw = readFileSync27(accountFile, "utf-8");
13960
+ const raw = readFileSync28(accountFile, "utf-8");
13779
13961
  const parsed = JSON.parse(raw);
13780
13962
  enabled = new Set(
13781
13963
  Array.isArray(parsed.enabledPlugins) ? parsed.enabledPlugins.filter((p) => typeof p === "string") : []
@@ -13790,7 +13972,7 @@ function computeDormantPlugins(accountId) {
13790
13972
  if (!existsSync24(pluginsDir)) return [];
13791
13973
  let entries;
13792
13974
  try {
13793
- entries = readdirSync17(pluginsDir);
13975
+ entries = readdirSync18(pluginsDir);
13794
13976
  } catch {
13795
13977
  return [];
13796
13978
  }
@@ -13801,7 +13983,7 @@ function computeDormantPlugins(accountId) {
13801
13983
  if (!existsSync24(manifestPath)) continue;
13802
13984
  let manifest;
13803
13985
  try {
13804
- manifest = readFileSync27(manifestPath, "utf-8");
13986
+ manifest = readFileSync28(manifestPath, "utf-8");
13805
13987
  } catch {
13806
13988
  continue;
13807
13989
  }
@@ -13815,13 +13997,13 @@ function computeDormantPlugins(accountId) {
13815
13997
  }
13816
13998
 
13817
13999
  // server/routes/admin/claude-sessions.ts
13818
- import { existsSync as existsSync25, readFileSync as readFileSync28 } from "fs";
14000
+ import { existsSync as existsSync25, readFileSync as readFileSync29 } from "fs";
13819
14001
  import { resolve as resolve20 } from "path";
13820
14002
  function readTunnelState(brandConfigDir) {
13821
14003
  const statePath = `${process.env.HOME ?? ""}/${brandConfigDir}/cloudflared/tunnel.state`;
13822
14004
  if (!existsSync25(statePath)) return null;
13823
14005
  try {
13824
- const parsed = JSON.parse(readFileSync28(statePath, "utf-8"));
14006
+ const parsed = JSON.parse(readFileSync29(statePath, "utf-8"));
13825
14007
  const tunnelId = typeof parsed.tunnelId === "string" ? parsed.tunnelId : null;
13826
14008
  const tunnelName = typeof parsed.tunnelName === "string" ? parsed.tunnelName : null;
13827
14009
  const domain = typeof parsed.domain === "string" ? parsed.domain : null;
@@ -13835,7 +14017,7 @@ function resolveTunnelUrl() {
13835
14017
  const platformRoot3 = process.env.MAXY_PLATFORM_ROOT ?? process.env.PLATFORM_ROOT ?? resolve20(process.cwd(), "..");
13836
14018
  let brand;
13837
14019
  try {
13838
- brand = JSON.parse(readFileSync28(resolve20(platformRoot3, "config", "brand.json"), "utf-8"));
14020
+ brand = JSON.parse(readFileSync29(resolve20(platformRoot3, "config", "brand.json"), "utf-8"));
13839
14021
  } catch {
13840
14022
  return null;
13841
14023
  }
@@ -14078,7 +14260,7 @@ var events_default = app20;
14078
14260
 
14079
14261
  // server/routes/admin/files.ts
14080
14262
  import { createReadStream as createReadStream2, createWriteStream as createWriteStream2, existsSync as existsSync26 } from "fs";
14081
- 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";
14082
14264
  import { realpathSync as realpathSync5 } from "fs";
14083
14265
  import { randomUUID as randomUUID9 } from "crypto";
14084
14266
  import { basename as basename9, dirname as dirname10, join as join29, relative as relative4, resolve as resolve22, sep as sep6 } from "path";
@@ -14956,19 +15138,55 @@ async function readAccountNames() {
14956
15138
  }
14957
15139
  return map;
14958
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
+ }
14959
15158
  async function enrich(absolute, entry, accountNames) {
14960
15159
  if (entry.kind === "directory" && UUID_RE3.test(entry.name)) {
14961
- 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);
14962
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
+ }
14963
15182
  entry.displayName = meta.filename;
14964
- entry.mimeType = meta.mimeType;
14965
- return;
15183
+ return "kept-as-dir";
14966
15184
  }
14967
15185
  const accountName = accountNames.get(entry.name);
14968
15186
  if (accountName) {
14969
15187
  entry.displayName = accountName;
14970
15188
  }
14971
- return;
15189
+ return null;
14972
15190
  }
14973
15191
  if (entry.kind === "file") {
14974
15192
  const dot = entry.name.lastIndexOf(".");
@@ -14981,6 +15199,7 @@ async function enrich(absolute, entry, accountNames) {
14981
15199
  }
14982
15200
  }
14983
15201
  }
15202
+ return null;
14984
15203
  }
14985
15204
  function buildDisplayPath(relPath, accountNames) {
14986
15205
  if (relPath === "." || relPath === "") return [];
@@ -15136,8 +15355,18 @@ app21.get("/", requireAdminSession, async (c) => {
15136
15355
  }
15137
15356
  }
15138
15357
  const accountNames = await readAccountNames();
15139
- await Promise.all(entries.map((e) => enrich(absolute, e, accountNames)));
15358
+ const classes = await Promise.all(entries.map((e) => enrich(absolute, e, accountNames)));
15140
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
+ }
15141
15370
  const displayPath = buildDisplayPath(relPath, accountNames);
15142
15371
  console.error(`[data] file-list path="${relPath}" entries=${entries.length}`);
15143
15372
  return c.json({ path: relPath, displayPath, entries });
@@ -15226,8 +15455,16 @@ app21.get("/download", requireAdminSession, async (c) => {
15226
15455
  });
15227
15456
  }
15228
15457
  console.error(`[data] file-download root="${root}" path="${relPath}" size=${info.size}`);
15229
- const safeQuotedName = filename.replace(/[\r\n"\\]/g, "_");
15230
- 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);
15231
15468
  return new Response(webStream, {
15232
15469
  status: 200,
15233
15470
  headers: {
@@ -15322,7 +15559,13 @@ app21.get("/download-zip", requireAdminSession, async (c) => {
15322
15559
  if (over) return over;
15323
15560
  }
15324
15561
  } else if (info.isFile()) {
15325
- 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);
15326
15569
  if (over) return over;
15327
15570
  } else {
15328
15571
  return c.json({ error: "Path is not a file or directory" }, 400);
@@ -15742,7 +15985,9 @@ app21.delete("/", requireAdminSession, async (c) => {
15742
15985
  }
15743
15986
  const dot = base.lastIndexOf(".");
15744
15987
  const stem = dot === -1 ? base : base.slice(0, dot);
15745
- 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;
15746
15991
  await unlink2(absolute);
15747
15992
  if (sidecarPath) {
15748
15993
  try {
@@ -15750,6 +15995,12 @@ app21.delete("/", requireAdminSession, async (c) => {
15750
15995
  } catch {
15751
15996
  }
15752
15997
  }
15998
+ if (isAttachmentContainer) {
15999
+ await unlink2(join29(containerDir, `${stem}${EXTRACTED_TEXT_SUFFIX}`)).catch(() => {
16000
+ });
16001
+ await rmdir(containerDir).catch(() => {
16002
+ });
16003
+ }
15753
16004
  console.error(`[data] file-delete path="${relPath}" bytes=${info.size}`);
15754
16005
  try {
15755
16006
  await dropFileIndex(accountId, relPath);
@@ -18706,14 +18957,14 @@ app36.get("/", async (c) => {
18706
18957
  var system_stats_default = app36;
18707
18958
 
18708
18959
  // server/routes/admin/health.ts
18709
- import { existsSync as existsSync28, readFileSync as readFileSync29 } from "fs";
18960
+ import { existsSync as existsSync28, readFileSync as readFileSync30 } from "fs";
18710
18961
  import { resolve as resolve24, join as join30 } from "path";
18711
18962
  var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT ?? resolve24(process.cwd(), "..");
18712
18963
  var brandHostname = "maxy";
18713
18964
  var brandJsonPath = join30(PLATFORM_ROOT6, "config", "brand.json");
18714
18965
  if (existsSync28(brandJsonPath)) {
18715
18966
  try {
18716
- const brand = JSON.parse(readFileSync29(brandJsonPath, "utf-8"));
18967
+ const brand = JSON.parse(readFileSync30(brandJsonPath, "utf-8"));
18717
18968
  if (brand.hostname) brandHostname = brand.hostname;
18718
18969
  } catch {
18719
18970
  }
@@ -18723,7 +18974,7 @@ var PROCESS_STARTED_AT = (/* @__PURE__ */ new Date()).toISOString();
18723
18974
  var PROBE_TIMEOUT_MS = 1e3;
18724
18975
  function readVersion() {
18725
18976
  if (!existsSync28(VERSION_FILE)) return "unknown";
18726
- return readFileSync29(VERSION_FILE, "utf-8").trim() || "unknown";
18977
+ return readFileSync30(VERSION_FILE, "utf-8").trim() || "unknown";
18727
18978
  }
18728
18979
  async function probeConversationDb() {
18729
18980
  let session;
@@ -19599,7 +19850,7 @@ function countVEvents(ics) {
19599
19850
  }
19600
19851
 
19601
19852
  // server/lib/calendar-availability.ts
19602
- import { readFileSync as readFileSync30 } from "fs";
19853
+ import { readFileSync as readFileSync31 } from "fs";
19603
19854
  import { join as join31 } from "path";
19604
19855
  var AVAILABILITY_FILENAME = "calendar-availability.json";
19605
19856
  var REQUIRED_KEYS = [
@@ -19612,7 +19863,7 @@ function readAvailabilityConfig(accountDir) {
19612
19863
  const path2 = join31(accountDir, AVAILABILITY_FILENAME);
19613
19864
  let raw;
19614
19865
  try {
19615
- raw = readFileSync30(path2, "utf-8");
19866
+ raw = readFileSync31(path2, "utf-8");
19616
19867
  } catch {
19617
19868
  throw new Error(`availability not configured: ${path2} not found`);
19618
19869
  }
@@ -20589,7 +20840,7 @@ app54.route("/request-magic-link", request_magic_link_default);
20589
20840
  var access_default = app54;
20590
20841
 
20591
20842
  // server/routes/sites.ts
20592
- 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";
20593
20844
  import { resolve as resolve26 } from "path";
20594
20845
  var SAFE_SEG_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
20595
20846
  var MIME = {
@@ -20693,7 +20944,7 @@ app55.get("/:rel{.*}", (c) => {
20693
20944
  }
20694
20945
  let body;
20695
20946
  try {
20696
- body = readFileSync31(realPath);
20947
+ body = readFileSync32(realPath);
20697
20948
  } catch (err) {
20698
20949
  const code = err?.code;
20699
20950
  if (code === "EISDIR") {
@@ -20729,7 +20980,7 @@ var sites_default = app55;
20729
20980
 
20730
20981
  // app/lib/visitor-token.ts
20731
20982
  import { createHmac, randomBytes, timingSafeEqual } from "crypto";
20732
- 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";
20733
20984
  import { dirname as dirname11 } from "path";
20734
20985
  var TOKEN_PREFIX = "v1.";
20735
20986
  var SECRET_BYTES = 32;
@@ -20738,7 +20989,7 @@ var cachedSecret = null;
20738
20989
  function getSecret() {
20739
20990
  if (cachedSecret) return cachedSecret;
20740
20991
  try {
20741
- const hex2 = readFileSync32(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
20992
+ const hex2 = readFileSync33(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
20742
20993
  if (hex2.length === SECRET_BYTES * 2) {
20743
20994
  cachedSecret = Buffer.from(hex2, "hex");
20744
20995
  return cachedSecret;
@@ -20752,7 +21003,7 @@ function getSecret() {
20752
21003
  console.log(`[visitor-token] secret minted path=${VISITOR_TOKEN_SECRET_FILE}`);
20753
21004
  } catch {
20754
21005
  }
20755
- const hex = readFileSync32(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
21006
+ const hex = readFileSync33(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
20756
21007
  cachedSecret = Buffer.from(hex, "hex");
20757
21008
  return cachedSecret;
20758
21009
  }
@@ -20805,7 +21056,7 @@ var VISITOR_COOKIE_NAME = "mxy_v";
20805
21056
  var VISITOR_COOKIE_MAX_AGE_SECONDS = Math.floor(DEFAULT_TTL_MS / 1e3);
20806
21057
 
20807
21058
  // app/lib/brand-config.ts
20808
- import { existsSync as existsSync31, readFileSync as readFileSync33 } from "fs";
21059
+ import { existsSync as existsSync31, readFileSync as readFileSync34 } from "fs";
20809
21060
  import { join as join33 } from "path";
20810
21061
  var cached = null;
20811
21062
  var cachedAttempted = false;
@@ -20817,7 +21068,7 @@ function readBrandConfig() {
20817
21068
  const brandPath = join33(platformRoot3, "config", "brand.json");
20818
21069
  if (!existsSync31(brandPath)) return null;
20819
21070
  try {
20820
- cached = JSON.parse(readFileSync33(brandPath, "utf-8"));
21071
+ cached = JSON.parse(readFileSync34(brandPath, "utf-8"));
20821
21072
  return cached;
20822
21073
  } catch {
20823
21074
  return null;
@@ -24552,7 +24803,7 @@ function broadcastAdminShutdown(reason) {
24552
24803
 
24553
24804
  // ../lib/entitlement/src/index.ts
24554
24805
  import { createPublicKey, createHash as createHash6, verify as cryptoVerify } from "crypto";
24555
- 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";
24556
24807
  import { resolve as resolve32 } from "path";
24557
24808
 
24558
24809
  // ../lib/entitlement/src/canonicalize.ts
@@ -24616,7 +24867,7 @@ function resolveEntitlement(brand, account) {
24616
24867
  function verifyAndResolve(brand, entitlementPath, account) {
24617
24868
  let pubkeyPem;
24618
24869
  try {
24619
- pubkeyPem = readFileSync34(pubkeyPath(brand), "utf-8");
24870
+ pubkeyPem = readFileSync35(pubkeyPath(brand), "utf-8");
24620
24871
  } catch (err) {
24621
24872
  return logResolved(anonymousFallback("pubkey-missing"), {
24622
24873
  reason: "pubkey-missing"
@@ -24630,7 +24881,7 @@ function verifyAndResolve(brand, entitlementPath, account) {
24630
24881
  }
24631
24882
  let envelope;
24632
24883
  try {
24633
- envelope = JSON.parse(readFileSync34(entitlementPath, "utf-8"));
24884
+ envelope = JSON.parse(readFileSync35(entitlementPath, "utf-8"));
24634
24885
  } catch {
24635
24886
  return logResolved(anonymousFallback("malformed"), { reason: "malformed" });
24636
24887
  }
@@ -24798,7 +25049,7 @@ if (BRAND_JSON_PATH && !existsSync35(BRAND_JSON_PATH)) {
24798
25049
  }
24799
25050
  if (BRAND_JSON_PATH && existsSync35(BRAND_JSON_PATH)) {
24800
25051
  try {
24801
- const parsed = JSON.parse(readFileSync35(BRAND_JSON_PATH, "utf-8"));
25052
+ const parsed = JSON.parse(readFileSync36(BRAND_JSON_PATH, "utf-8"));
24802
25053
  BRAND = { ...BRAND, ...parsed };
24803
25054
  } catch (err) {
24804
25055
  console.error(`[brand] Failed to parse brand.json: ${err.message}`);
@@ -24834,7 +25085,7 @@ var ALIAS_DOMAINS_PATH = join36(homedir4(), BRAND.configDir, "alias-domains.json
24834
25085
  function loadAliasDomains() {
24835
25086
  try {
24836
25087
  if (!existsSync35(ALIAS_DOMAINS_PATH)) return null;
24837
- const parsed = JSON.parse(readFileSync35(ALIAS_DOMAINS_PATH, "utf-8"));
25088
+ const parsed = JSON.parse(readFileSync36(ALIAS_DOMAINS_PATH, "utf-8"));
24838
25089
  if (!Array.isArray(parsed)) {
24839
25090
  console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
24840
25091
  return null;
@@ -25523,7 +25774,7 @@ app61.get("/agent-assets/:slug/:filename", (c) => {
25523
25774
  const ext = "." + filename.split(".").pop()?.toLowerCase();
25524
25775
  const contentType = IMAGE_MIME[ext] || "application/octet-stream";
25525
25776
  console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
25526
- const body = readFileSync35(filePath);
25777
+ const body = readFileSync36(filePath);
25527
25778
  return c.body(body, 200, {
25528
25779
  "Content-Type": contentType,
25529
25780
  "Cache-Control": "public, max-age=3600"
@@ -25553,7 +25804,7 @@ app61.get("/generated/:filename", (c) => {
25553
25804
  const ext = "." + filename.split(".").pop()?.toLowerCase();
25554
25805
  const contentType = IMAGE_MIME[ext] || "application/octet-stream";
25555
25806
  console.log(`[generated] serve file=${filename} status=200`);
25556
- const body = readFileSync35(filePath);
25807
+ const body = readFileSync36(filePath);
25557
25808
  return c.body(body, 200, {
25558
25809
  "Content-Type": contentType,
25559
25810
  "Cache-Control": "public, max-age=86400"
@@ -25568,7 +25819,7 @@ var brandLogoPath = "/brand/maxy-monochrome.png";
25568
25819
  var brandIconPath = "/brand/maxy-monochrome.png";
25569
25820
  if (BRAND_JSON_PATH && existsSync35(BRAND_JSON_PATH)) {
25570
25821
  try {
25571
- const fullBrand = JSON.parse(readFileSync35(BRAND_JSON_PATH, "utf-8"));
25822
+ const fullBrand = JSON.parse(readFileSync36(BRAND_JSON_PATH, "utf-8"));
25572
25823
  if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
25573
25824
  brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
25574
25825
  } catch {
@@ -25599,7 +25850,7 @@ var brandAppIconsExist = [brandAppIcon192Path, brandAppIcon512Path, brandMaskabl
25599
25850
  );
25600
25851
  var SW_SOURCE = (() => {
25601
25852
  try {
25602
- return readFileSync35(resolve33(process.cwd(), "public", "sw.js"), "utf-8");
25853
+ return readFileSync36(resolve33(process.cwd(), "public", "sw.js"), "utf-8");
25603
25854
  } catch {
25604
25855
  return null;
25605
25856
  }
@@ -25609,7 +25860,7 @@ function readInstalledVersion() {
25609
25860
  if (!PLATFORM_ROOT8) return "unknown";
25610
25861
  const versionFile = join36(PLATFORM_ROOT8, "config", `.${BRAND.hostname}-version`);
25611
25862
  if (!existsSync35(versionFile)) return "unknown";
25612
- const content = readFileSync35(versionFile, "utf-8").trim();
25863
+ const content = readFileSync36(versionFile, "utf-8").trim();
25613
25864
  return content || "unknown";
25614
25865
  } catch {
25615
25866
  return "unknown";
@@ -25650,7 +25901,7 @@ var clientErrorReporterScript = `<script>
25650
25901
  function cachedHtml(file) {
25651
25902
  let html = htmlCache.get(file);
25652
25903
  if (!html) {
25653
- html = readFileSync35(resolve33(process.cwd(), "public", file), "utf-8");
25904
+ html = readFileSync36(resolve33(process.cwd(), "public", file), "utf-8");
25654
25905
  const productNameEsc = escapeHtml(BRAND.productName);
25655
25906
  html = html.replace(/<title>([^<]*)<\/title>/, (_match, inner) => `<title>${escapeHtml(inner).replace(/Maxy/g, productNameEsc)}</title>`);
25656
25907
  html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
@@ -25673,7 +25924,7 @@ function loadBrandingCache(agentSlug) {
25673
25924
  if (!accountId) return null;
25674
25925
  const cachePath = join36(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
25675
25926
  if (!existsSync35(cachePath)) return null;
25676
- return JSON.parse(readFileSync35(cachePath, "utf-8"));
25927
+ return JSON.parse(readFileSync36(cachePath, "utf-8"));
25677
25928
  } catch {
25678
25929
  return null;
25679
25930
  }
@@ -25768,7 +26019,7 @@ app61.use("/vnc-popout.html", logViewerFetch);
25768
26019
  app61.get("/vnc-popout.html", (c) => {
25769
26020
  let html = htmlCache.get("vnc-popout.html");
25770
26021
  if (!html) {
25771
- html = readFileSync35(resolve33(process.cwd(), "public", "vnc-popout.html"), "utf-8");
26022
+ html = readFileSync36(resolve33(process.cwd(), "public", "vnc-popout.html"), "utf-8");
25772
26023
  const name = escapeHtml(BRAND.productName);
25773
26024
  html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
25774
26025
  html = html.replace("</head>", ` ${brandScript}
@@ -25921,6 +26172,73 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
25921
26172
  const loop = setInterval(runReconcile, RECONCILE_INTERVAL_MS);
25922
26173
  loop.unref();
25923
26174
  }
26175
+ {
26176
+ const publishPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join36(__dirname, "..");
26177
+ const publishScript = resolve33(publishPlatformRoot, "plugins/scheduling/mcp/dist/scripts/publish-availability.js");
26178
+ const PUBLISH_INTERVAL_MS = 3e5;
26179
+ const currentAccount = () => {
26180
+ try {
26181
+ return resolveAccount();
26182
+ } catch (err) {
26183
+ console.error(`[calendar-availability] op=publish result=error reason=account-resolve-failed err="${err.message}"`);
26184
+ return null;
26185
+ }
26186
+ };
26187
+ const spawnPublish = (account) => {
26188
+ if (!existsSync35(publishScript)) return;
26189
+ try {
26190
+ const child = spawn3(process.execPath, [publishScript], {
26191
+ env: {
26192
+ ...process.env,
26193
+ PLATFORM_ROOT: publishPlatformRoot,
26194
+ PUBLISH_ACCOUNT_ID: account.accountId,
26195
+ PUBLISH_ACCOUNT_DIR: account.accountDir
26196
+ },
26197
+ stdio: "inherit"
26198
+ });
26199
+ child.unref();
26200
+ child.on("error", (err) => console.error(`[calendar-availability] op=publish result=error reason=spawn-failed err="${err.message}"`));
26201
+ } catch (err) {
26202
+ console.error(`[calendar-availability] op=publish result=error reason=spawn-failed err="${err.message}"`);
26203
+ }
26204
+ };
26205
+ const auditSnapshotAge = (account) => {
26206
+ const availPath = resolve33(account.accountDir, "calendar-availability.json");
26207
+ let hasBookingSite = false;
26208
+ try {
26209
+ if (existsSync35(availPath)) {
26210
+ const cfg = JSON.parse(readFileSync36(availPath, "utf-8"));
26211
+ hasBookingSite = typeof cfg.bookingDbName === "string" && cfg.bookingDbName.length > 0;
26212
+ }
26213
+ } catch {
26214
+ }
26215
+ if (!hasBookingSite) return;
26216
+ const statePath = resolve33(account.accountDir, "state", "booking-availability", "last-publish.json");
26217
+ let lastSuccessAt = null;
26218
+ if (existsSync35(statePath)) {
26219
+ try {
26220
+ const rec = JSON.parse(readFileSync36(statePath, "utf-8"));
26221
+ lastSuccessAt = rec.lastSuccessAt ?? null;
26222
+ } catch {
26223
+ console.error(`[calendar-availability] op=audit accountId=${account.accountId} snapshotAgeMs=na reason=bad-state-file`);
26224
+ return;
26225
+ }
26226
+ }
26227
+ const parsed = lastSuccessAt ? Date.parse(lastSuccessAt) : NaN;
26228
+ const ageMs = Number.isNaN(parsed) ? null : Date.now() - parsed;
26229
+ console.error(`[calendar-availability] op=audit accountId=${account.accountId} snapshotAgeMs=${ageMs ?? "na"}`);
26230
+ };
26231
+ const publishTick = () => {
26232
+ const account = currentAccount();
26233
+ if (!account) return;
26234
+ spawnPublish(account);
26235
+ auditSnapshotAge(account);
26236
+ };
26237
+ const firstPublish = setTimeout(publishTick, 2e4);
26238
+ firstPublish.unref();
26239
+ const publishLoop = setInterval(publishTick, PUBLISH_INTERVAL_MS);
26240
+ publishLoop.unref();
26241
+ }
25924
26242
  startTimeEntryCensus(() => getSession());
25925
26243
  {
25926
26244
  const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join36(__dirname, "..");
@@ -26042,7 +26360,7 @@ async function runAdminUserReconcileTick() {
26042
26360
  console.error("[adminuser-self-heal] skip reason=no-users-file");
26043
26361
  return;
26044
26362
  }
26045
- const usersRaw = readFileSync35(USERS_FILE, "utf-8").trim();
26363
+ const usersRaw = readFileSync36(USERS_FILE, "utf-8").trim();
26046
26364
  if (!usersRaw) {
26047
26365
  console.error("[adminuser-self-heal] skip reason=empty-users-file");
26048
26366
  return;
@@ -26072,7 +26390,7 @@ if (typeof adminUserReconcileTimer.unref === "function") adminUserReconcileTimer
26072
26390
  var USERS_RECONCILE_INTERVAL_MS = 60 * 60 * 1e3;
26073
26391
  function countUsersRows() {
26074
26392
  if (!existsSync35(USERS_FILE)) return 0;
26075
- const raw = readFileSync35(USERS_FILE, "utf-8").trim();
26393
+ const raw = readFileSync36(USERS_FILE, "utf-8").trim();
26076
26394
  if (!raw) return 0;
26077
26395
  const users = JSON.parse(raw);
26078
26396
  return users.filter((u) => typeof u.userId === "string").length;