@rubytech/create-maxy-code 0.1.431 → 0.1.432

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 (24) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -1
  3. package/payload/platform/services/whatsapp-channel/dist/server.js +19 -1
  4. package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
  5. package/payload/platform/services/whatsapp-channel/dist/targets.d.ts +9 -0
  6. package/payload/platform/services/whatsapp-channel/dist/targets.d.ts.map +1 -1
  7. package/payload/platform/services/whatsapp-channel/dist/targets.js +13 -0
  8. package/payload/platform/services/whatsapp-channel/dist/targets.js.map +1 -1
  9. package/payload/server/public/assets/{AdminLoginScreens-CcW3pAAZ.js → AdminLoginScreens-BM45lL5G.js} +1 -1
  10. package/payload/server/public/assets/{admin-Co3hOTNp.js → admin-B8Eb9Wbj.js} +1 -1
  11. package/payload/server/public/assets/{audio-attachment-mime-CwYrV1pg.js → audio-attachment-mime-Cx5d3Jls.js} +1 -1
  12. package/payload/server/public/assets/{chat-rxbGimwf.js → chat-LEEUAJOG.js} +1 -1
  13. package/payload/server/public/assets/data-CKojMQSz.js +1 -0
  14. package/payload/server/public/assets/{operator-Djv3MdcL.js → operator-OwY_sWTH.js} +1 -1
  15. package/payload/server/public/assets/{page-D1EAVC_9.js → page-B7nCws9K.js} +1 -1
  16. package/payload/server/public/assets/{page-Ci27UrJz.js → page-C3L6-Ufk.js} +1 -1
  17. package/payload/server/public/assets/{public-C6k-fPfx.js → public-CSLXSSy5.js} +1 -1
  18. package/payload/server/public/chat.html +3 -3
  19. package/payload/server/public/data.html +3 -3
  20. package/payload/server/public/index.html +4 -4
  21. package/payload/server/public/operator.html +4 -4
  22. package/payload/server/public/public.html +3 -3
  23. package/payload/server/server.js +684 -577
  24. package/payload/server/public/assets/data-D0YFYP1i.js +0 -1
@@ -1383,9 +1383,9 @@ var serveStatic = (options = { root: "" }) => {
1383
1383
  };
1384
1384
 
1385
1385
  // server/index.ts
1386
- import { readFileSync as readFileSync35, existsSync as existsSync33, watchFile } from "fs";
1386
+ import { readFileSync as readFileSync35, existsSync as existsSync34, watchFile } from "fs";
1387
1387
  import { spawn as spawn3 } from "child_process";
1388
- import { createHash as createHash6 } from "crypto";
1388
+ import { createHash as createHash7 } from "crypto";
1389
1389
  import { resolve as resolve33, join as join35, basename as basename13 } from "path";
1390
1390
  import { homedir as homedir3 } from "os";
1391
1391
  import { monitorEventLoopDelay } from "perf_hooks";
@@ -1763,7 +1763,7 @@ async function ensureAuth() {
1763
1763
  }
1764
1764
 
1765
1765
  // server/routes/health.ts
1766
- import { existsSync as existsSync4, readFileSync as readFileSync9 } from "fs";
1766
+ import { existsSync as existsSync5, readFileSync as readFileSync9 } from "fs";
1767
1767
  import { createConnection as createConnection2 } from "net";
1768
1768
 
1769
1769
  // app/lib/network.ts
@@ -3494,7 +3494,7 @@ function readAllConversations(platformAccountId) {
3494
3494
  var TAG7 = "[whatsapp-persist]";
3495
3495
  var sessionWriteLocks = /* @__PURE__ */ new Map();
3496
3496
  async function persistWhatsAppMessage(input) {
3497
- if (!input.body || !input.msgKeyId) return null;
3497
+ if (!input.body && !input.attachmentId || !input.msgKeyId) return null;
3498
3498
  const messageId = `whatsapp-live:${input.accountId}:${input.remoteJid}:${input.msgKeyId}`;
3499
3499
  const canonicalRemoteJid = input.canonicalRemoteJid;
3500
3500
  const keyResolved = canonicalRemoteJid !== input.remoteJid ? "lid2pn" : "none";
@@ -3564,6 +3564,7 @@ async function persistWhatsAppMessage(input) {
3564
3564
  quotedId: input.quoted?.id ?? null,
3565
3565
  quotedSender: input.quoted?.sender ?? null,
3566
3566
  scope,
3567
+ ...input.attachmentId ? { attachmentId: input.attachmentId } : {},
3567
3568
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
3568
3569
  };
3569
3570
  const altDedupeRemoteJids = canonicalRemoteJid !== input.remoteJid ? [input.remoteJid] : [];
@@ -3781,16 +3782,385 @@ function isCustomerTurnSuppressed(role, managedService) {
3781
3782
  }
3782
3783
 
3783
3784
  // app/lib/whatsapp/inbound/media.ts
3784
- import { randomUUID as randomUUID3 } from "crypto";
3785
- import { writeFile, mkdir } from "fs/promises";
3786
- import { join as join5 } from "path";
3785
+ import { randomUUID as randomUUID4, createHash } from "crypto";
3786
+ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
3787
+ import { join as join6 } from "path";
3787
3788
  import {
3788
3789
  downloadMediaMessage,
3789
3790
  downloadContentFromMessage,
3790
3791
  normalizeMessageContent as normalizeMessageContent2
3791
3792
  } from "@whiskeysockets/baileys";
3793
+
3794
+ // app/lib/attachments.ts
3795
+ import { randomUUID as randomUUID3 } from "crypto";
3796
+ import { mkdir, writeFile } from "fs/promises";
3797
+ import { realpathSync as realpathSync2, existsSync as existsSync4 } from "fs";
3798
+ import { resolve as resolve6, extname } from "path";
3799
+
3800
+ // app/lib/data-path.ts
3801
+ import { realpathSync } from "fs";
3802
+ import { resolve as resolve5, normalize, sep as sep2, relative, basename, join as join5 } from "path";
3803
+ var PLATFORM_ROOT3 = process.env.MAXY_PLATFORM_ROOT ?? resolve5(process.cwd(), "../platform");
3804
+ var DATA_ROOT = resolve5(PLATFORM_ROOT3, "..", "data");
3805
+ var CLAUDE_UPLOADS_ROOT = process.env.CLAUDE_CONFIG_DIR ? resolve5(process.env.CLAUDE_CONFIG_DIR, "uploads") : null;
3806
+ var ACCOUNT_PARTITION_DIRS = /* @__PURE__ */ new Set(["accounts"]);
3807
+ var ACCOUNT_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
3808
+ function crossesForeignAccountPartition(relPath, accountId) {
3809
+ const segs = relPath.split("/").filter(Boolean);
3810
+ if (segs.length < 2) return false;
3811
+ if (!ACCOUNT_PARTITION_DIRS.has(segs[0])) return false;
3812
+ const owner = segs[1];
3813
+ if (!ACCOUNT_UUID_RE.test(owner)) return false;
3814
+ return owner !== accountId;
3815
+ }
3816
+ function isWithinOwnAccountPartition(relPath, accountId) {
3817
+ const segs = relPath.split("/").filter(Boolean);
3818
+ return segs.length >= 2 && segs[0] === "accounts" && segs[1] === accountId;
3819
+ }
3820
+ function resolveUnderRoot(raw, rootAbs, rootLabel) {
3821
+ const cleaned = normalize("/" + (raw ?? "").replace(/\\/g, "/")).replace(/^\/+/, "");
3822
+ const absolute = resolve5(rootAbs, cleaned);
3823
+ let rootReal;
3824
+ try {
3825
+ rootReal = realpathSync(rootAbs);
3826
+ } catch (err) {
3827
+ return {
3828
+ ok: false,
3829
+ status: 500,
3830
+ error: `${rootLabel} not accessible: ${err instanceof Error ? err.message : String(err)}`
3831
+ };
3832
+ }
3833
+ let resolvedReal;
3834
+ try {
3835
+ resolvedReal = realpathSync(absolute);
3836
+ } catch (err) {
3837
+ const code = err.code;
3838
+ if (code === "ENOENT") {
3839
+ if (absolute !== rootReal && !absolute.startsWith(rootReal + sep2)) {
3840
+ return { ok: false, status: 403, error: `Path escapes ${rootLabel}`, resolved: absolute };
3841
+ }
3842
+ return { ok: false, status: 404, error: "Not found" };
3843
+ }
3844
+ return {
3845
+ ok: false,
3846
+ status: 500,
3847
+ error: err instanceof Error ? err.message : String(err)
3848
+ };
3849
+ }
3850
+ if (resolvedReal !== rootReal && !resolvedReal.startsWith(rootReal + sep2)) {
3851
+ return { ok: false, status: 403, error: `Path escapes ${rootLabel}`, resolved: resolvedReal };
3852
+ }
3853
+ const relPath = relative(rootReal, resolvedReal) || ".";
3854
+ return { ok: true, absolute: resolvedReal, dataRootReal: rootReal, relative: relPath };
3855
+ }
3856
+ function resolveDataPath(raw) {
3857
+ return resolveUnderRoot(raw, DATA_ROOT, "DATA_ROOT");
3858
+ }
3859
+ function resolveClaudeUploadsPath(raw) {
3860
+ if (!CLAUDE_UPLOADS_ROOT) {
3861
+ return { ok: false, status: 404, error: "Not found" };
3862
+ }
3863
+ return resolveUnderRoot(raw, CLAUDE_UPLOADS_ROOT, "CLAUDE_UPLOADS_ROOT");
3864
+ }
3865
+ function scratchpadTmpBase() {
3866
+ return process.env.MAXY_SCRATCHPAD_BASE ?? "/tmp";
3867
+ }
3868
+ function sessionScratchpadRoot(projectDir, sessionId) {
3869
+ const uid = typeof process.geteuid === "function" ? process.geteuid() : 0;
3870
+ return join5(scratchpadTmpBase(), `claude-${uid}`, basename(projectDir), sessionId, "scratchpad");
3871
+ }
3872
+ function resolveScratchpadPath(raw, scratchpadRootAbs) {
3873
+ return resolveUnderRoot(raw, scratchpadRootAbs, "scratchpad");
3874
+ }
3875
+
3876
+ // app/lib/doc-to-text.ts
3877
+ import { unzipSync, strFromU8 } from "fflate";
3878
+ var DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
3879
+ var ODT = "application/vnd.oasis.opendocument.text";
3880
+ var CONVERTIBLE_DOC_MIME = /* @__PURE__ */ new Set([
3881
+ DOCX,
3882
+ ODT,
3883
+ "text/rtf",
3884
+ "application/rtf"
3885
+ ]);
3886
+ function bareMime(mimeType) {
3887
+ return mimeType.split(";")[0].trim().toLowerCase();
3888
+ }
3889
+ function unescapeXml(s) {
3890
+ return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, "&");
3891
+ }
3892
+ function unzipEntry(buffer, entry) {
3893
+ const files = unzipSync(new Uint8Array(buffer));
3894
+ const bytes = files[entry];
3895
+ if (!bytes) throw new Error(`zip entry not found: ${entry}`);
3896
+ return strFromU8(bytes);
3897
+ }
3898
+ function docxToText(buffer) {
3899
+ const xml = unzipEntry(buffer, "word/document.xml");
3900
+ const paras = [];
3901
+ for (const p of xml.split("</w:p>")) {
3902
+ const withBreaks = p.replace(/<w:tab\b[^>]*\/?>/g, " ").replace(/<w:br\b[^>]*\/?>/g, "\n");
3903
+ let line = "";
3904
+ for (const m of withBreaks.matchAll(/<w:t\b[^>]*>([\s\S]*?)<\/w:t>/g)) {
3905
+ line += unescapeXml(m[1]);
3906
+ }
3907
+ if (line.length > 0) paras.push(line);
3908
+ }
3909
+ return paras.join("\n");
3910
+ }
3911
+ function odtToText(buffer) {
3912
+ const xml = unzipEntry(buffer, "content.xml");
3913
+ const body = xml.replace(/^[\s\S]*?<office:text\b[^>]*>/, "").replace(/<\/office:text>[\s\S]*$/, "");
3914
+ const withBreaks = body.replace(/<text:line-break\b[^>]*\/?>/g, "\n").replace(/<text:tab\b[^>]*\/?>/g, " ").replace(/<\/text:(?:p|h)>/g, "\n");
3915
+ const stripped = withBreaks.replace(/<[^>]+>/g, "");
3916
+ return unescapeXml(stripped).replace(/\n{3,}/g, "\n\n").trim();
3917
+ }
3918
+ var RTF_DROP_DEST = /^\\(?:\*|fonttbl|colortbl|stylesheet|listtable|info|pict)\b/;
3919
+ function stripRtfDestinations(s) {
3920
+ let out = "";
3921
+ for (let i = 0; i < s.length; i++) {
3922
+ const ch = s[i];
3923
+ if (ch === "\\" && i + 1 < s.length) {
3924
+ out += ch + s[i + 1];
3925
+ i++;
3926
+ continue;
3927
+ }
3928
+ if (ch === "{" && RTF_DROP_DEST.test(s.slice(i + 1))) {
3929
+ let depth = 1;
3930
+ let j = i + 1;
3931
+ for (; j < s.length && depth > 0; j++) {
3932
+ if (s[j] === "\\") {
3933
+ j++;
3934
+ continue;
3935
+ }
3936
+ if (s[j] === "{") depth++;
3937
+ else if (s[j] === "}") depth--;
3938
+ }
3939
+ i = j - 1;
3940
+ continue;
3941
+ }
3942
+ out += ch;
3943
+ }
3944
+ return out;
3945
+ }
3946
+ function rtfToText(rtf) {
3947
+ let s = stripRtfDestinations(rtf);
3948
+ s = s.replace(/\\par[d]?\b/g, "\n").replace(/\\line\b/g, "\n").replace(/\\tab\b/g, " ");
3949
+ s = s.replace(/\\'([0-9a-fA-F]{2})/g, (_m, h) => String.fromCharCode(parseInt(h, 16)));
3950
+ s = s.replace(/\\[a-zA-Z]+-?\d* ?/g, "");
3951
+ s = s.replace(/\\([{}\\])/g, "$1").replace(/[{}]/g, "");
3952
+ return s.replace(/[ \t]{2,}/g, " ").replace(/\n{3,}/g, "\n\n").trim();
3953
+ }
3954
+ function docToText(buffer, mimeType) {
3955
+ const mime = bareMime(mimeType);
3956
+ if (mime === DOCX) return docxToText(buffer);
3957
+ if (mime === ODT) return odtToText(buffer);
3958
+ if (mime === "text/rtf" || mime === "application/rtf") return rtfToText(buffer.toString("latin1"));
3959
+ return null;
3960
+ }
3961
+
3962
+ // app/lib/attachments.ts
3963
+ function uploadsDirFor(accountId, subPath, attachmentId) {
3964
+ return subPath ? resolve6(DATA_ROOT, "accounts", accountId, "uploads", subPath, attachmentId) : resolve6(DATA_ROOT, "accounts", accountId, "uploads", attachmentId);
3965
+ }
3966
+ var SUPPORTED_MIME_TYPES = /* @__PURE__ */ new Set([
3967
+ "image/jpeg",
3968
+ "image/png",
3969
+ "image/gif",
3970
+ "image/webp",
3971
+ "application/pdf",
3972
+ "text/plain",
3973
+ "text/markdown",
3974
+ "text/csv",
3975
+ "text/html",
3976
+ "text/calendar",
3977
+ "application/zip",
3978
+ "application/x-zip-compressed",
3979
+ // Task 1347 — Word/OpenDocument/RTF text documents. The bytes are converted to
3980
+ // a readable-text sibling at ingestion (writeAttachment → doc-to-text.ts)
3981
+ // because the on-device Read tool cannot parse the binary containers. rtf is
3982
+ // reported by browsers as either text/rtf or application/rtf.
3983
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
3984
+ "application/vnd.oasis.opendocument.text",
3985
+ "text/rtf",
3986
+ "application/rtf",
3987
+ // Task 1240 — voice recordings persist as playable clips in the admin bubble.
3988
+ // These match AUDIO_MIME_TYPES in app/lib/stt/audio-mime.ts exactly.
3989
+ "audio/ogg",
3990
+ "audio/opus",
3991
+ "audio/webm",
3992
+ "audio/mp4",
3993
+ "audio/x-m4a",
3994
+ "audio/mpeg",
3995
+ "audio/wav",
3996
+ "video/mp4"
3997
+ ]);
3998
+ var MAX_FILE_SIZE_BYTES = 50 * 1024 * 1024;
3999
+ var MAX_FILES_PER_MESSAGE = 5;
4000
+ var MAX_DATA_UPLOAD_BYTES = 2 * 1024 * 1024 * 1024;
4001
+ var MAX_ZIP_UNCOMPRESSED_BYTES = 100 * 1024 * 1024;
4002
+ var EXTRACTED_TEXT_SUFFIX = ".extracted.txt";
4003
+ function assertSupportedMime(mimeType) {
4004
+ const bare = mimeType.split(";")[0].trim().toLowerCase();
4005
+ if (!SUPPORTED_MIME_TYPES.has(bare)) {
4006
+ throw new Error(
4007
+ `Unsupported file type: "${mimeType}". Supported types: ${[...SUPPORTED_MIME_TYPES].join(", ")}.`
4008
+ );
4009
+ }
4010
+ }
4011
+ function isSupportedMime(mimeType) {
4012
+ return SUPPORTED_MIME_TYPES.has(mimeType.split(";")[0].trim().toLowerCase());
4013
+ }
4014
+ async function storeMediaBuffer(accountId, attachmentId, filename, mimeType, buffer) {
4015
+ assertSupportedMime(mimeType);
4016
+ if (buffer.byteLength > MAX_FILE_SIZE_BYTES) {
4017
+ throw new Error(
4018
+ `Media exceeds the 50 MB limit (${(buffer.byteLength / 1024 / 1024).toFixed(1)} MB).`
4019
+ );
4020
+ }
4021
+ return writeAttachment(accountId, "", filename, mimeType, buffer.byteLength, buffer, attachmentId, true);
4022
+ }
4023
+ function attachmentExists(accountId, attachmentId) {
4024
+ return existsSync4(resolve6(uploadsDirFor(accountId, "", attachmentId), `${attachmentId}.meta.json`));
4025
+ }
4026
+ async function writeAttachment(accountId, subPath, filename, mimeType, sizeBytes, buffer, explicitId, skipDocToText = false) {
4027
+ const attachmentId = explicitId ?? randomUUID3();
4028
+ const dir = uploadsDirFor(accountId, subPath, attachmentId);
4029
+ await mkdir(dir, { recursive: true });
4030
+ const ext = extname(filename) || "";
4031
+ const storagePath = resolve6(dir, `${attachmentId}${ext}`);
4032
+ const metaPath = resolve6(dir, `${attachmentId}.meta.json`);
4033
+ const meta = {
4034
+ attachmentId,
4035
+ scope: subPath || accountId,
4036
+ filename,
4037
+ mimeType,
4038
+ sizeBytes,
4039
+ storedAt: (/* @__PURE__ */ new Date()).toISOString()
4040
+ };
4041
+ await writeFile(storagePath, buffer);
4042
+ await writeFile(metaPath, JSON.stringify(meta, null, 2));
4043
+ let readableTextPath;
4044
+ if (!skipDocToText && CONVERTIBLE_DOC_MIME.has(mimeType.split(";")[0].trim().toLowerCase())) {
4045
+ const text = docToText(buffer, mimeType);
4046
+ if (text) {
4047
+ readableTextPath = resolve6(dir, `${attachmentId}${EXTRACTED_TEXT_SUFFIX}`);
4048
+ await writeFile(readableTextPath, text);
4049
+ }
4050
+ }
4051
+ return { attachmentId, filename, storagePath, metaPath, mimeType, sizeBytes, readableTextPath };
4052
+ }
4053
+ async function storeAttachment(accountId, file) {
4054
+ assertSupportedMime(file.type);
4055
+ if (file.size > MAX_FILE_SIZE_BYTES) {
4056
+ throw new Error(
4057
+ `File "${file.name}" exceeds the 50 MB limit (${(file.size / 1024 / 1024).toFixed(1)} MB).`
4058
+ );
4059
+ }
4060
+ const buffer = Buffer.from(await file.arrayBuffer());
4061
+ return writeAttachment(accountId, "", file.name, file.type, file.size, buffer);
4062
+ }
4063
+ async function storePublicAttachment(accountId, sessionId, file, buffer) {
4064
+ assertSupportedMime(file.type);
4065
+ if (file.size > MAX_FILE_SIZE_BYTES) {
4066
+ throw new Error(
4067
+ `File "${file.name}" exceeds the 50 MB limit (${(file.size / 1024 / 1024).toFixed(1)} MB).`
4068
+ );
4069
+ }
4070
+ return writeAttachment(accountId, `public/${sessionId}`, file.name, file.type, file.size, buffer);
4071
+ }
4072
+ var MIME_BY_EXT = {
4073
+ ".pdf": "application/pdf",
4074
+ ".png": "image/png",
4075
+ ".jpg": "image/jpeg",
4076
+ ".jpeg": "image/jpeg",
4077
+ ".gif": "image/gif",
4078
+ ".webp": "image/webp",
4079
+ ".svg": "image/svg+xml",
4080
+ ".html": "text/html",
4081
+ ".htm": "text/html",
4082
+ ".css": "text/css",
4083
+ ".js": "application/javascript",
4084
+ ".json": "application/json",
4085
+ ".csv": "text/csv",
4086
+ ".txt": "text/plain",
4087
+ ".md": "text/markdown",
4088
+ ".xml": "text/xml",
4089
+ ".zip": "application/zip",
4090
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
4091
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
4092
+ ".odt": "application/vnd.oasis.opendocument.text",
4093
+ ".rtf": "text/rtf",
4094
+ ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
4095
+ ".ics": "text/calendar"
4096
+ };
4097
+ function detectMimeType(filePath) {
4098
+ const ext = extname(filePath).toLowerCase();
4099
+ return MIME_BY_EXT[ext] ?? "application/octet-stream";
4100
+ }
4101
+ function validateFilePathInAccount(filePath, accountDir) {
4102
+ const resolved = realpathSync2(filePath);
4103
+ const accountResolved = realpathSync2(accountDir);
4104
+ if (!resolved.startsWith(accountResolved + "/")) {
4105
+ throw new Error(`File path is outside the account directory`);
4106
+ }
4107
+ return resolved;
4108
+ }
4109
+ async function storeComponentArtefact(accountId, attachmentId, mimeType, content, filename) {
4110
+ const buffer = Buffer.from(content, "utf-8");
4111
+ if (buffer.byteLength > MAX_FILE_SIZE_BYTES) {
4112
+ throw new Error(
4113
+ `Component artefact exceeds the 50 MB limit (${(buffer.byteLength / 1024 / 1024).toFixed(1)} MB).`
4114
+ );
4115
+ }
4116
+ const dir = uploadsDirFor(accountId, "", attachmentId);
4117
+ await mkdir(dir, { recursive: true });
4118
+ const ext = extname(filename) || (mimeType === "text/html" ? ".html" : ".md");
4119
+ const storagePath = resolve6(dir, `${attachmentId}${ext}`);
4120
+ const metaPath = resolve6(dir, `${attachmentId}.meta.json`);
4121
+ const meta = {
4122
+ attachmentId,
4123
+ scope: accountId,
4124
+ filename,
4125
+ mimeType,
4126
+ sizeBytes: buffer.byteLength,
4127
+ storedAt: (/* @__PURE__ */ new Date()).toISOString()
4128
+ };
4129
+ await writeFile(storagePath, buffer);
4130
+ await writeFile(metaPath, JSON.stringify(meta, null, 2));
4131
+ return { attachmentId, filename, storagePath, metaPath, mimeType, sizeBytes: buffer.byteLength };
4132
+ }
4133
+
4134
+ // app/lib/whatsapp/inbound/media.ts
3792
4135
  var TAG9 = "[whatsapp:media]";
3793
4136
  var MEDIA_DIR = "/tmp/maxy-media";
4137
+ function deterministicAttachmentId(seed) {
4138
+ const h = createHash("sha256").update(seed).digest("hex");
4139
+ return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20, 32)}`;
4140
+ }
4141
+ var CACHE_RETAIN_MS = 5e3;
4142
+ var inflightDownloads = /* @__PURE__ */ new Map();
4143
+ function downloadInboundMediaBufferCached(msg, sock, opts) {
4144
+ const key = msg.key.id ? `${msg.key.remoteJid ?? ""}:${msg.key.id}` : null;
4145
+ if (!key) return downloadInboundMediaBuffer(msg, sock, opts);
4146
+ const existing = inflightDownloads.get(key);
4147
+ if (existing) return existing;
4148
+ const p = downloadInboundMediaBuffer(msg, sock, opts);
4149
+ inflightDownloads.set(key, p);
4150
+ void p.then(
4151
+ (res) => {
4152
+ if (res === void 0) {
4153
+ inflightDownloads.delete(key);
4154
+ } else {
4155
+ const t = setTimeout(() => inflightDownloads.delete(key), CACHE_RETAIN_MS);
4156
+ t.unref?.();
4157
+ }
4158
+ },
4159
+ () => inflightDownloads.delete(key)
4160
+ // downloadInboundMediaBuffer never rejects, but be safe
4161
+ );
4162
+ return p;
4163
+ }
3794
4164
  function mimeToExt(mimetype) {
3795
4165
  const map = {
3796
4166
  "image/jpeg": "jpg",
@@ -3823,7 +4193,7 @@ async function streamToBuffer(stream2) {
3823
4193
  }
3824
4194
  return Buffer.concat(chunks);
3825
4195
  }
3826
- async function downloadInboundMedia(msg, sock, opts) {
4196
+ async function downloadInboundMediaBuffer(msg, sock, opts) {
3827
4197
  const rawMessage = msg.message;
3828
4198
  if (!rawMessage) return void 0;
3829
4199
  const content = normalizeMessageContent2(rawMessage);
@@ -3866,23 +4236,45 @@ async function downloadInboundMedia(msg, sock, opts) {
3866
4236
  console.error(`${TAG9} media too large type=${mimetype ?? "unknown"} size=${sizeMB}MB limit=${limitMB}MB`);
3867
4237
  return void 0;
3868
4238
  }
3869
- await mkdir(MEDIA_DIR, { recursive: true });
3870
- const ext = mimeToExt(mimetype ?? "application/octet-stream");
3871
- const filename = `${randomUUID3()}.${ext}`;
3872
- const filePath = join5(MEDIA_DIR, filename);
3873
- await writeFile(filePath, buffer);
3874
- const sizeKB = (buffer.length / 1024).toFixed(0);
3875
- console.error(`${TAG9} media downloaded type=${mimetype ?? "unknown"} size=${sizeKB}KB path=${filePath}`);
3876
4239
  return {
3877
- path: filePath,
4240
+ buffer,
3878
4241
  mimetype: mimetype ?? "application/octet-stream",
3879
- size: buffer.length
4242
+ filename: inner.documentMessage?.fileName ?? void 0
3880
4243
  };
3881
4244
  } catch (err) {
3882
4245
  console.error(`${TAG9} media download failed type=${mimetype ?? "unknown"} error=${String(err)}`);
3883
4246
  return void 0;
3884
4247
  }
3885
4248
  }
4249
+ async function downloadInboundMedia(msg, sock, opts) {
4250
+ const res = await downloadInboundMediaBufferCached(msg, sock, opts);
4251
+ if (!res) return void 0;
4252
+ await mkdir2(MEDIA_DIR, { recursive: true });
4253
+ const ext = mimeToExt(res.mimetype);
4254
+ const filePath = join6(MEDIA_DIR, `${randomUUID4()}.${ext}`);
4255
+ await writeFile2(filePath, res.buffer);
4256
+ const sizeKB = (res.buffer.length / 1024).toFixed(0);
4257
+ console.error(`${TAG9} media downloaded type=${res.mimetype} size=${sizeKB}KB path=${filePath}`);
4258
+ return { path: filePath, mimetype: res.mimetype, size: res.buffer.length };
4259
+ }
4260
+ async function storeInboundMediaServable(msg, sock, accountId, seed, opts) {
4261
+ const attachmentId = deterministicAttachmentId(seed);
4262
+ try {
4263
+ if (attachmentExists(accountId, attachmentId)) return attachmentId;
4264
+ const res = await downloadInboundMediaBufferCached(msg, sock, opts);
4265
+ if (!res) {
4266
+ console.error(`${TAG9} servable-store skip reason=no-buffer id=${attachmentId}`);
4267
+ return null;
4268
+ }
4269
+ const filename = res.filename ?? `${attachmentId}.${mimeToExt(res.mimetype)}`;
4270
+ await storeMediaBuffer(accountId, attachmentId, filename, res.mimetype, res.buffer);
4271
+ console.error(`${TAG9} servable-store ok id=${attachmentId} mime=${res.mimetype} bytes=${res.buffer.length}`);
4272
+ return attachmentId;
4273
+ } catch (err) {
4274
+ console.error(`${TAG9} servable-store FAIL id=${attachmentId} reason=${String(err).slice(0, 120)}`);
4275
+ return null;
4276
+ }
4277
+ }
3886
4278
 
3887
4279
  // app/lib/whatsapp/inbound/debounce.ts
3888
4280
  var TAG10 = "[whatsapp:debounce]";
@@ -4713,21 +5105,24 @@ function monitorInbound(conn) {
4713
5105
  if (!remoteJid || remoteJid === "status@broadcast") continue;
4714
5106
  if (!msg.message) continue;
4715
5107
  const extracted = extractMessage(msg);
4716
- if (extracted.text) {
5108
+ const mediaServable = !!(extracted.mediaType && extracted.mimetype && isSupportedMime(extracted.mimetype));
5109
+ if (extracted.text || mediaServable) {
4717
5110
  const isGroup = isGroupJid(remoteJid);
4718
5111
  const senderJid = isGroup ? msg.key.participant ?? remoteJid : remoteJid;
4719
5112
  const senderPhone = await resolveJidToE164(senderJid, conn.lidMapping) ?? senderJid;
4720
5113
  const ts = msg.messageTimestamp ? Number(msg.messageTimestamp) : Math.floor(Date.now() / 1e3);
4721
5114
  const storeKey = messageStoreKey(conn.platformAccountId, remoteJid);
4722
- storeMessage(storeKey, {
4723
- id: msg.key.id ?? `${remoteJid}-${ts}`,
4724
- sender: senderPhone,
4725
- senderName: msg.pushName ?? void 0,
4726
- body: extracted.text,
4727
- timestamp: ts,
4728
- fromMe: Boolean(msg.key.fromMe),
4729
- quoted: extracted.quotedMessage ? { id: extracted.quotedMessage.id, body: extracted.quotedMessage.text, sender: extracted.quotedMessage.sender } : void 0
4730
- });
5115
+ if (extracted.text) {
5116
+ storeMessage(storeKey, {
5117
+ id: msg.key.id ?? `${remoteJid}-${ts}`,
5118
+ sender: senderPhone,
5119
+ senderName: msg.pushName ?? void 0,
5120
+ body: extracted.text,
5121
+ timestamp: ts,
5122
+ fromMe: Boolean(msg.key.fromMe),
5123
+ quoted: extracted.quotedMessage ? { id: extracted.quotedMessage.id, body: extracted.quotedMessage.text, sender: extracted.quotedMessage.sender } : void 0
5124
+ });
5125
+ }
4731
5126
  const entries = messageStore.get(storeKey);
4732
5127
  console.error(
4733
5128
  `${TAG13} stored message ${msg.key.id ?? "?"} for ${remoteJid} (type: ${upsert.type}, store size: ${entries?.length ?? 0}/${MESSAGE_STORE_MAX}) account=${conn.platformAccountId}`
@@ -4765,6 +5160,17 @@ function monitorInbound(conn) {
4765
5160
  remoteJid,
4766
5161
  conn.lidMapping
4767
5162
  );
5163
+ let mediaAttachmentId;
5164
+ if (mediaServable && !fromMe && conn.sock && upsert.type === "notify") {
5165
+ const maxMb = whatsAppConfig.mediaMaxMb ?? 50;
5166
+ mediaAttachmentId = await storeInboundMediaServable(
5167
+ msg,
5168
+ conn.sock,
5169
+ conn.platformAccountId,
5170
+ `whatsapp-live:${conn.accountId}:${canonicalRemoteJid}:${msg.key.id}`,
5171
+ { maxBytes: maxMb * 1024 * 1024 }
5172
+ ) ?? void 0;
5173
+ }
4768
5174
  await persistWhatsAppMessage({
4769
5175
  accountId: conn.accountId,
4770
5176
  platformAccountId: conn.platformAccountId,
@@ -4776,7 +5182,13 @@ function monitorInbound(conn) {
4776
5182
  fromMe,
4777
5183
  senderPhone,
4778
5184
  selfPhone: conn.selfPhone,
4779
- body: extracted.text,
5185
+ // A stored media message persists the human caption (or empty) plus
5186
+ // the attachment id — the reader shows the inline media, never the
5187
+ // synthetic `[document: …]` / `[sticker]` line. If the store failed
5188
+ // (no id), fall back to the extracted text so a document/sticker
5189
+ // still shows its placeholder rather than a blank bubble.
5190
+ body: mediaAttachmentId ? extracted.caption ?? "" : extracted.text,
5191
+ attachmentId: mediaAttachmentId,
4780
5192
  timestamp: ts,
4781
5193
  pushName: msg.pushName ?? void 0,
4782
5194
  quoted: extracted.quotedMessage ? {
@@ -5051,9 +5463,9 @@ async function handleInboundMessage(conn, msg) {
5051
5463
  import { spawnSync, execFileSync } from "child_process";
5052
5464
  import { createConnection } from "net";
5053
5465
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
5054
- import { resolve as resolve5 } from "path";
5055
- var PLATFORM_ROOT3 = process.env.MAXY_PLATFORM_ROOT ?? resolve5(process.cwd(), "..");
5056
- var VNC_SCRIPT = resolve5(PLATFORM_ROOT3, "scripts/vnc.sh");
5466
+ import { resolve as resolve7 } from "path";
5467
+ var PLATFORM_ROOT4 = process.env.MAXY_PLATFORM_ROOT ?? resolve7(process.cwd(), "..");
5468
+ var VNC_SCRIPT = resolve7(PLATFORM_ROOT4, "scripts/vnc.sh");
5057
5469
  var displayMode = process.env.DISPLAY_MODE ?? "virtual";
5058
5470
  if (displayMode === "native") {
5059
5471
  console.log(`[vnc] DISPLAY_MODE=native \u2014 local requests use desktop display, remote requests use VNC`);
@@ -5166,7 +5578,7 @@ function ensureLogDir() {
5166
5578
  mkdirSync2(LOG_DIR, { recursive: true });
5167
5579
  }
5168
5580
  function logPath(name) {
5169
- return resolve5(LOG_DIR, `${name}.log`);
5581
+ return resolve7(LOG_DIR, `${name}.log`);
5170
5582
  }
5171
5583
  async function ensureVnc() {
5172
5584
  if (process.platform !== "linux") {
@@ -5253,7 +5665,7 @@ function killChromium() {
5253
5665
  }
5254
5666
  function writeChromiumWrapper() {
5255
5667
  mkdirSync2(BIN_DIR, { recursive: true });
5256
- const wrapperPath = resolve5(BIN_DIR, "chromium");
5668
+ const wrapperPath = resolve7(BIN_DIR, "chromium");
5257
5669
  writeFileSync3(wrapperPath, `#!/bin/bash
5258
5670
  LOG="${LOG_DIR}/chromium.log"
5259
5671
  echo "==== [$(date)] chromium wrapper ====" >> "$LOG"
@@ -5275,7 +5687,7 @@ fi
5275
5687
  # replaced). Refusing the legacy probe loop closes the silent-fallback path
5276
5688
  # where /snap/bin/chromium would re-introduce the AppArmor SingletonLock
5277
5689
  # denial that triggered.
5278
- PATH_FILE="${PLATFORM_ROOT3}/config/chromium-binary.path"
5690
+ PATH_FILE="${PLATFORM_ROOT4}/config/chromium-binary.path"
5279
5691
  if [ ! -r "$PATH_FILE" ]; then
5280
5692
  echo " ERROR: $PATH_FILE missing \u2014 re-run installer" >> "$LOG"
5281
5693
  exit 1
@@ -5330,432 +5742,107 @@ function buildX11Env(chromiumWrapperPath, transport = "vnc") {
5330
5742
  };
5331
5743
  if (nativeInfo.sessionType === "wayland") {
5332
5744
  env.WAYLAND_DISPLAY = nativeInfo.waylandDisplay;
5333
- } else {
5334
- env.WAYLAND_DISPLAY = "";
5335
- }
5336
- vncLog("build-x11-env", {
5337
- transport,
5338
- session_type: nativeInfo.sessionType,
5339
- display: nativeInfo.display,
5340
- wayland_display: env.WAYLAND_DISPLAY || void 0
5341
- });
5342
- return env;
5343
- }
5344
- return {
5345
- ...process.env,
5346
- DISPLAY: VNC_DISPLAY,
5347
- WAYLAND_DISPLAY: "",
5348
- XDG_SESSION_TYPE: "x11",
5349
- BROWSER: chromiumWrapperPath,
5350
- PATH: `${BIN_DIR}:${process.env.PATH ?? "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}`
5351
- };
5352
- }
5353
-
5354
- // server/routes/health.ts
5355
- function checkPort(port2, timeoutMs = 500) {
5356
- return new Promise((resolve34) => {
5357
- const socket = createConnection2(port2, "127.0.0.1");
5358
- socket.setTimeout(timeoutMs);
5359
- socket.once("connect", () => {
5360
- socket.destroy();
5361
- resolve34(true);
5362
- });
5363
- socket.once("error", () => {
5364
- socket.destroy();
5365
- resolve34(false);
5366
- });
5367
- socket.once("timeout", () => {
5368
- socket.destroy();
5369
- resolve34(false);
5370
- });
5371
- });
5372
- }
5373
- var app = new Hono();
5374
- app.get("/", async (c) => {
5375
- const browserTransport = resolveBrowserTransport(c.req.raw, c.env?.incoming?.socket?.remoteAddress);
5376
- let pinConfigured = false;
5377
- try {
5378
- if (existsSync4(USERS_FILE)) {
5379
- const raw = readFileSync9(USERS_FILE, "utf-8").trim();
5380
- if (raw) {
5381
- const users = JSON.parse(raw);
5382
- pinConfigured = Array.isArray(users) && users.length > 0;
5383
- }
5384
- }
5385
- } catch (err) {
5386
- console.error(`[health] users.json corrupt \u2014 reporting pin_configured=false: ${err instanceof Error ? err.message : String(err)}`);
5387
- }
5388
- let authHealth;
5389
- try {
5390
- authHealth = await ensureAuth();
5391
- } catch {
5392
- authHealth = { status: "missing" };
5393
- }
5394
- const claudeAuthenticated = authHealth.status === "ok" || authHealth.status === "expiring";
5395
- const vncRunning = await checkPort(WEBSOCKIFY_PORT);
5396
- let whatsappAccounts = [];
5397
- try {
5398
- whatsappAccounts = getStatus().map((a) => ({
5399
- accountId: a.accountId,
5400
- accountName: a.accountName ?? null,
5401
- connected: a.connected,
5402
- selfPhone: a.selfPhone ?? null,
5403
- reconnectAttempts: a.reconnectAttempts,
5404
- lastError: a.lastError ?? null,
5405
- sessionStuckReason: a.sessionStuckReason ?? null,
5406
- terminalReason: a.terminalReason ?? null
5407
- }));
5408
- } catch (err) {
5409
- console.error(`[health] failed to read WhatsApp status: ${err instanceof Error ? err.message : String(err)}`);
5410
- }
5411
- const whatsappAnyConnected = whatsappAccounts.some((a) => a.connected);
5412
- const whatsappAnyStuck = whatsappAccounts.some((a) => Boolean(a.sessionStuckReason));
5413
- const whatsappAnyTerminal = whatsappAccounts.some((a) => Boolean(a.terminalReason));
5414
- const account = resolveAccount();
5415
- const missingPlugins = findMissingPlugins(account?.config.enabledPlugins);
5416
- return c.json({
5417
- pin_configured: pinConfigured,
5418
- claude_authenticated: claudeAuthenticated,
5419
- vnc_running: vncRunning,
5420
- browser_transport: browserTransport,
5421
- auth_status: authHealth.status,
5422
- auth_expires_at: authHealth.expiresAt ?? null,
5423
- server_ip: getLanIp(),
5424
- missingPlugins,
5425
- whatsapp: {
5426
- any_connected: whatsappAnyConnected,
5427
- any_stuck: whatsappAnyStuck,
5428
- any_terminal: whatsappAnyTerminal,
5429
- accounts: whatsappAccounts
5430
- }
5431
- });
5432
- });
5433
- var health_default = app;
5434
-
5435
- // app/lib/attachments.ts
5436
- import { randomUUID as randomUUID4 } from "crypto";
5437
- import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
5438
- import { realpathSync as realpathSync2 } from "fs";
5439
- import { resolve as resolve7, extname } from "path";
5440
-
5441
- // app/lib/data-path.ts
5442
- import { realpathSync } from "fs";
5443
- import { resolve as resolve6, normalize, sep as sep2, relative, basename, join as join6 } from "path";
5444
- var PLATFORM_ROOT4 = process.env.MAXY_PLATFORM_ROOT ?? resolve6(process.cwd(), "../platform");
5445
- var DATA_ROOT = resolve6(PLATFORM_ROOT4, "..", "data");
5446
- var CLAUDE_UPLOADS_ROOT = process.env.CLAUDE_CONFIG_DIR ? resolve6(process.env.CLAUDE_CONFIG_DIR, "uploads") : null;
5447
- var ACCOUNT_PARTITION_DIRS = /* @__PURE__ */ new Set(["accounts"]);
5448
- var ACCOUNT_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
5449
- function crossesForeignAccountPartition(relPath, accountId) {
5450
- const segs = relPath.split("/").filter(Boolean);
5451
- if (segs.length < 2) return false;
5452
- if (!ACCOUNT_PARTITION_DIRS.has(segs[0])) return false;
5453
- const owner = segs[1];
5454
- if (!ACCOUNT_UUID_RE.test(owner)) return false;
5455
- return owner !== accountId;
5456
- }
5457
- function isWithinOwnAccountPartition(relPath, accountId) {
5458
- const segs = relPath.split("/").filter(Boolean);
5459
- return segs.length >= 2 && segs[0] === "accounts" && segs[1] === accountId;
5460
- }
5461
- function resolveUnderRoot(raw, rootAbs, rootLabel) {
5462
- const cleaned = normalize("/" + (raw ?? "").replace(/\\/g, "/")).replace(/^\/+/, "");
5463
- const absolute = resolve6(rootAbs, cleaned);
5464
- let rootReal;
5465
- try {
5466
- rootReal = realpathSync(rootAbs);
5467
- } catch (err) {
5468
- return {
5469
- ok: false,
5470
- status: 500,
5471
- error: `${rootLabel} not accessible: ${err instanceof Error ? err.message : String(err)}`
5472
- };
5473
- }
5474
- let resolvedReal;
5475
- try {
5476
- resolvedReal = realpathSync(absolute);
5477
- } catch (err) {
5478
- const code = err.code;
5479
- if (code === "ENOENT") {
5480
- if (absolute !== rootReal && !absolute.startsWith(rootReal + sep2)) {
5481
- return { ok: false, status: 403, error: `Path escapes ${rootLabel}`, resolved: absolute };
5482
- }
5483
- return { ok: false, status: 404, error: "Not found" };
5484
- }
5485
- return {
5486
- ok: false,
5487
- status: 500,
5488
- error: err instanceof Error ? err.message : String(err)
5489
- };
5490
- }
5491
- if (resolvedReal !== rootReal && !resolvedReal.startsWith(rootReal + sep2)) {
5492
- return { ok: false, status: 403, error: `Path escapes ${rootLabel}`, resolved: resolvedReal };
5493
- }
5494
- const relPath = relative(rootReal, resolvedReal) || ".";
5495
- return { ok: true, absolute: resolvedReal, dataRootReal: rootReal, relative: relPath };
5496
- }
5497
- function resolveDataPath(raw) {
5498
- return resolveUnderRoot(raw, DATA_ROOT, "DATA_ROOT");
5499
- }
5500
- function resolveClaudeUploadsPath(raw) {
5501
- if (!CLAUDE_UPLOADS_ROOT) {
5502
- return { ok: false, status: 404, error: "Not found" };
5503
- }
5504
- return resolveUnderRoot(raw, CLAUDE_UPLOADS_ROOT, "CLAUDE_UPLOADS_ROOT");
5505
- }
5506
- function scratchpadTmpBase() {
5507
- return process.env.MAXY_SCRATCHPAD_BASE ?? "/tmp";
5508
- }
5509
- function sessionScratchpadRoot(projectDir, sessionId) {
5510
- const uid = typeof process.geteuid === "function" ? process.geteuid() : 0;
5511
- return join6(scratchpadTmpBase(), `claude-${uid}`, basename(projectDir), sessionId, "scratchpad");
5512
- }
5513
- function resolveScratchpadPath(raw, scratchpadRootAbs) {
5514
- return resolveUnderRoot(raw, scratchpadRootAbs, "scratchpad");
5515
- }
5516
-
5517
- // app/lib/doc-to-text.ts
5518
- import { unzipSync, strFromU8 } from "fflate";
5519
- var DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
5520
- var ODT = "application/vnd.oasis.opendocument.text";
5521
- var CONVERTIBLE_DOC_MIME = /* @__PURE__ */ new Set([
5522
- DOCX,
5523
- ODT,
5524
- "text/rtf",
5525
- "application/rtf"
5526
- ]);
5527
- function bareMime(mimeType) {
5528
- return mimeType.split(";")[0].trim().toLowerCase();
5529
- }
5530
- function unescapeXml(s) {
5531
- return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, "&");
5532
- }
5533
- function unzipEntry(buffer, entry) {
5534
- const files = unzipSync(new Uint8Array(buffer));
5535
- const bytes = files[entry];
5536
- if (!bytes) throw new Error(`zip entry not found: ${entry}`);
5537
- return strFromU8(bytes);
5538
- }
5539
- function docxToText(buffer) {
5540
- const xml = unzipEntry(buffer, "word/document.xml");
5541
- const paras = [];
5542
- for (const p of xml.split("</w:p>")) {
5543
- const withBreaks = p.replace(/<w:tab\b[^>]*\/?>/g, " ").replace(/<w:br\b[^>]*\/?>/g, "\n");
5544
- let line = "";
5545
- for (const m of withBreaks.matchAll(/<w:t\b[^>]*>([\s\S]*?)<\/w:t>/g)) {
5546
- line += unescapeXml(m[1]);
5547
- }
5548
- if (line.length > 0) paras.push(line);
5549
- }
5550
- return paras.join("\n");
5551
- }
5552
- function odtToText(buffer) {
5553
- const xml = unzipEntry(buffer, "content.xml");
5554
- const body = xml.replace(/^[\s\S]*?<office:text\b[^>]*>/, "").replace(/<\/office:text>[\s\S]*$/, "");
5555
- const withBreaks = body.replace(/<text:line-break\b[^>]*\/?>/g, "\n").replace(/<text:tab\b[^>]*\/?>/g, " ").replace(/<\/text:(?:p|h)>/g, "\n");
5556
- const stripped = withBreaks.replace(/<[^>]+>/g, "");
5557
- return unescapeXml(stripped).replace(/\n{3,}/g, "\n\n").trim();
5558
- }
5559
- var RTF_DROP_DEST = /^\\(?:\*|fonttbl|colortbl|stylesheet|listtable|info|pict)\b/;
5560
- function stripRtfDestinations(s) {
5561
- let out = "";
5562
- for (let i = 0; i < s.length; i++) {
5563
- const ch = s[i];
5564
- if (ch === "\\" && i + 1 < s.length) {
5565
- out += ch + s[i + 1];
5566
- i++;
5567
- continue;
5568
- }
5569
- if (ch === "{" && RTF_DROP_DEST.test(s.slice(i + 1))) {
5570
- let depth = 1;
5571
- let j = i + 1;
5572
- for (; j < s.length && depth > 0; j++) {
5573
- if (s[j] === "\\") {
5574
- j++;
5575
- continue;
5576
- }
5577
- if (s[j] === "{") depth++;
5578
- else if (s[j] === "}") depth--;
5579
- }
5580
- i = j - 1;
5581
- continue;
5745
+ } else {
5746
+ env.WAYLAND_DISPLAY = "";
5582
5747
  }
5583
- out += ch;
5748
+ vncLog("build-x11-env", {
5749
+ transport,
5750
+ session_type: nativeInfo.sessionType,
5751
+ display: nativeInfo.display,
5752
+ wayland_display: env.WAYLAND_DISPLAY || void 0
5753
+ });
5754
+ return env;
5584
5755
  }
5585
- return out;
5586
- }
5587
- function rtfToText(rtf) {
5588
- let s = stripRtfDestinations(rtf);
5589
- s = s.replace(/\\par[d]?\b/g, "\n").replace(/\\line\b/g, "\n").replace(/\\tab\b/g, " ");
5590
- s = s.replace(/\\'([0-9a-fA-F]{2})/g, (_m, h) => String.fromCharCode(parseInt(h, 16)));
5591
- s = s.replace(/\\[a-zA-Z]+-?\d* ?/g, "");
5592
- s = s.replace(/\\([{}\\])/g, "$1").replace(/[{}]/g, "");
5593
- return s.replace(/[ \t]{2,}/g, " ").replace(/\n{3,}/g, "\n\n").trim();
5594
- }
5595
- function docToText(buffer, mimeType) {
5596
- const mime = bareMime(mimeType);
5597
- if (mime === DOCX) return docxToText(buffer);
5598
- if (mime === ODT) return odtToText(buffer);
5599
- if (mime === "text/rtf" || mime === "application/rtf") return rtfToText(buffer.toString("latin1"));
5600
- return null;
5756
+ return {
5757
+ ...process.env,
5758
+ DISPLAY: VNC_DISPLAY,
5759
+ WAYLAND_DISPLAY: "",
5760
+ XDG_SESSION_TYPE: "x11",
5761
+ BROWSER: chromiumWrapperPath,
5762
+ PATH: `${BIN_DIR}:${process.env.PATH ?? "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}`
5763
+ };
5601
5764
  }
5602
5765
 
5603
- // app/lib/attachments.ts
5604
- function uploadsDirFor(accountId, subPath, attachmentId) {
5605
- return subPath ? resolve7(DATA_ROOT, "accounts", accountId, "uploads", subPath, attachmentId) : resolve7(DATA_ROOT, "accounts", accountId, "uploads", attachmentId);
5606
- }
5607
- var SUPPORTED_MIME_TYPES = /* @__PURE__ */ new Set([
5608
- "image/jpeg",
5609
- "image/png",
5610
- "image/gif",
5611
- "image/webp",
5612
- "application/pdf",
5613
- "text/plain",
5614
- "text/markdown",
5615
- "text/csv",
5616
- "text/html",
5617
- "text/calendar",
5618
- "application/zip",
5619
- "application/x-zip-compressed",
5620
- // Task 1347 — Word/OpenDocument/RTF text documents. The bytes are converted to
5621
- // a readable-text sibling at ingestion (writeAttachment → doc-to-text.ts)
5622
- // because the on-device Read tool cannot parse the binary containers. rtf is
5623
- // reported by browsers as either text/rtf or application/rtf.
5624
- "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
5625
- "application/vnd.oasis.opendocument.text",
5626
- "text/rtf",
5627
- "application/rtf",
5628
- // Task 1240 — voice recordings persist as playable clips in the admin bubble.
5629
- // These match AUDIO_MIME_TYPES in app/lib/stt/audio-mime.ts exactly.
5630
- "audio/ogg",
5631
- "audio/opus",
5632
- "audio/webm",
5633
- "audio/mp4",
5634
- "audio/x-m4a",
5635
- "audio/mpeg",
5636
- "audio/wav",
5637
- "video/mp4"
5638
- ]);
5639
- var MAX_FILE_SIZE_BYTES = 50 * 1024 * 1024;
5640
- var MAX_FILES_PER_MESSAGE = 5;
5641
- var MAX_DATA_UPLOAD_BYTES = 2 * 1024 * 1024 * 1024;
5642
- var MAX_ZIP_UNCOMPRESSED_BYTES = 100 * 1024 * 1024;
5643
- var EXTRACTED_TEXT_SUFFIX = ".extracted.txt";
5644
- function assertSupportedMime(mimeType) {
5645
- const bare = mimeType.split(";")[0].trim().toLowerCase();
5646
- if (!SUPPORTED_MIME_TYPES.has(bare)) {
5647
- throw new Error(
5648
- `Unsupported file type: "${mimeType}". Supported types: ${[...SUPPORTED_MIME_TYPES].join(", ")}.`
5649
- );
5650
- }
5766
+ // server/routes/health.ts
5767
+ function checkPort(port2, timeoutMs = 500) {
5768
+ return new Promise((resolve34) => {
5769
+ const socket = createConnection2(port2, "127.0.0.1");
5770
+ socket.setTimeout(timeoutMs);
5771
+ socket.once("connect", () => {
5772
+ socket.destroy();
5773
+ resolve34(true);
5774
+ });
5775
+ socket.once("error", () => {
5776
+ socket.destroy();
5777
+ resolve34(false);
5778
+ });
5779
+ socket.once("timeout", () => {
5780
+ socket.destroy();
5781
+ resolve34(false);
5782
+ });
5783
+ });
5651
5784
  }
5652
- async function writeAttachment(accountId, subPath, filename, mimeType, sizeBytes, buffer) {
5653
- const attachmentId = randomUUID4();
5654
- const dir = uploadsDirFor(accountId, subPath, attachmentId);
5655
- await mkdir2(dir, { recursive: true });
5656
- const ext = extname(filename) || "";
5657
- const storagePath = resolve7(dir, `${attachmentId}${ext}`);
5658
- const metaPath = resolve7(dir, `${attachmentId}.meta.json`);
5659
- const meta = {
5660
- attachmentId,
5661
- scope: subPath || accountId,
5662
- filename,
5663
- mimeType,
5664
- sizeBytes,
5665
- storedAt: (/* @__PURE__ */ new Date()).toISOString()
5666
- };
5667
- await writeFile2(storagePath, buffer);
5668
- await writeFile2(metaPath, JSON.stringify(meta, null, 2));
5669
- let readableTextPath;
5670
- if (CONVERTIBLE_DOC_MIME.has(mimeType.split(";")[0].trim().toLowerCase())) {
5671
- const text = docToText(buffer, mimeType);
5672
- if (text) {
5673
- readableTextPath = resolve7(dir, `${attachmentId}${EXTRACTED_TEXT_SUFFIX}`);
5674
- await writeFile2(readableTextPath, text);
5785
+ var app = new Hono();
5786
+ app.get("/", async (c) => {
5787
+ const browserTransport = resolveBrowserTransport(c.req.raw, c.env?.incoming?.socket?.remoteAddress);
5788
+ let pinConfigured = false;
5789
+ try {
5790
+ if (existsSync5(USERS_FILE)) {
5791
+ const raw = readFileSync9(USERS_FILE, "utf-8").trim();
5792
+ if (raw) {
5793
+ const users = JSON.parse(raw);
5794
+ pinConfigured = Array.isArray(users) && users.length > 0;
5795
+ }
5675
5796
  }
5797
+ } catch (err) {
5798
+ console.error(`[health] users.json corrupt \u2014 reporting pin_configured=false: ${err instanceof Error ? err.message : String(err)}`);
5676
5799
  }
5677
- return { attachmentId, filename, storagePath, metaPath, mimeType, sizeBytes, readableTextPath };
5678
- }
5679
- async function storeAttachment(accountId, file) {
5680
- assertSupportedMime(file.type);
5681
- if (file.size > MAX_FILE_SIZE_BYTES) {
5682
- throw new Error(
5683
- `File "${file.name}" exceeds the 50 MB limit (${(file.size / 1024 / 1024).toFixed(1)} MB).`
5684
- );
5685
- }
5686
- const buffer = Buffer.from(await file.arrayBuffer());
5687
- return writeAttachment(accountId, "", file.name, file.type, file.size, buffer);
5688
- }
5689
- async function storePublicAttachment(accountId, sessionId, file, buffer) {
5690
- assertSupportedMime(file.type);
5691
- if (file.size > MAX_FILE_SIZE_BYTES) {
5692
- throw new Error(
5693
- `File "${file.name}" exceeds the 50 MB limit (${(file.size / 1024 / 1024).toFixed(1)} MB).`
5694
- );
5695
- }
5696
- return writeAttachment(accountId, `public/${sessionId}`, file.name, file.type, file.size, buffer);
5697
- }
5698
- var MIME_BY_EXT = {
5699
- ".pdf": "application/pdf",
5700
- ".png": "image/png",
5701
- ".jpg": "image/jpeg",
5702
- ".jpeg": "image/jpeg",
5703
- ".gif": "image/gif",
5704
- ".webp": "image/webp",
5705
- ".svg": "image/svg+xml",
5706
- ".html": "text/html",
5707
- ".htm": "text/html",
5708
- ".css": "text/css",
5709
- ".js": "application/javascript",
5710
- ".json": "application/json",
5711
- ".csv": "text/csv",
5712
- ".txt": "text/plain",
5713
- ".md": "text/markdown",
5714
- ".xml": "text/xml",
5715
- ".zip": "application/zip",
5716
- ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
5717
- ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
5718
- ".odt": "application/vnd.oasis.opendocument.text",
5719
- ".rtf": "text/rtf",
5720
- ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
5721
- ".ics": "text/calendar"
5722
- };
5723
- function detectMimeType(filePath) {
5724
- const ext = extname(filePath).toLowerCase();
5725
- return MIME_BY_EXT[ext] ?? "application/octet-stream";
5726
- }
5727
- function validateFilePathInAccount(filePath, accountDir) {
5728
- const resolved = realpathSync2(filePath);
5729
- const accountResolved = realpathSync2(accountDir);
5730
- if (!resolved.startsWith(accountResolved + "/")) {
5731
- throw new Error(`File path is outside the account directory`);
5800
+ let authHealth;
5801
+ try {
5802
+ authHealth = await ensureAuth();
5803
+ } catch {
5804
+ authHealth = { status: "missing" };
5732
5805
  }
5733
- return resolved;
5734
- }
5735
- async function storeComponentArtefact(accountId, attachmentId, mimeType, content, filename) {
5736
- const buffer = Buffer.from(content, "utf-8");
5737
- if (buffer.byteLength > MAX_FILE_SIZE_BYTES) {
5738
- throw new Error(
5739
- `Component artefact exceeds the 50 MB limit (${(buffer.byteLength / 1024 / 1024).toFixed(1)} MB).`
5740
- );
5806
+ const claudeAuthenticated = authHealth.status === "ok" || authHealth.status === "expiring";
5807
+ const vncRunning = await checkPort(WEBSOCKIFY_PORT);
5808
+ let whatsappAccounts = [];
5809
+ try {
5810
+ whatsappAccounts = getStatus().map((a) => ({
5811
+ accountId: a.accountId,
5812
+ accountName: a.accountName ?? null,
5813
+ connected: a.connected,
5814
+ selfPhone: a.selfPhone ?? null,
5815
+ reconnectAttempts: a.reconnectAttempts,
5816
+ lastError: a.lastError ?? null,
5817
+ sessionStuckReason: a.sessionStuckReason ?? null,
5818
+ terminalReason: a.terminalReason ?? null
5819
+ }));
5820
+ } catch (err) {
5821
+ console.error(`[health] failed to read WhatsApp status: ${err instanceof Error ? err.message : String(err)}`);
5741
5822
  }
5742
- const dir = uploadsDirFor(accountId, "", attachmentId);
5743
- await mkdir2(dir, { recursive: true });
5744
- const ext = extname(filename) || (mimeType === "text/html" ? ".html" : ".md");
5745
- const storagePath = resolve7(dir, `${attachmentId}${ext}`);
5746
- const metaPath = resolve7(dir, `${attachmentId}.meta.json`);
5747
- const meta = {
5748
- attachmentId,
5749
- scope: accountId,
5750
- filename,
5751
- mimeType,
5752
- sizeBytes: buffer.byteLength,
5753
- storedAt: (/* @__PURE__ */ new Date()).toISOString()
5754
- };
5755
- await writeFile2(storagePath, buffer);
5756
- await writeFile2(metaPath, JSON.stringify(meta, null, 2));
5757
- return { attachmentId, filename, storagePath, metaPath, mimeType, sizeBytes: buffer.byteLength };
5758
- }
5823
+ const whatsappAnyConnected = whatsappAccounts.some((a) => a.connected);
5824
+ const whatsappAnyStuck = whatsappAccounts.some((a) => Boolean(a.sessionStuckReason));
5825
+ const whatsappAnyTerminal = whatsappAccounts.some((a) => Boolean(a.terminalReason));
5826
+ const account = resolveAccount();
5827
+ const missingPlugins = findMissingPlugins(account?.config.enabledPlugins);
5828
+ return c.json({
5829
+ pin_configured: pinConfigured,
5830
+ claude_authenticated: claudeAuthenticated,
5831
+ vnc_running: vncRunning,
5832
+ browser_transport: browserTransport,
5833
+ auth_status: authHealth.status,
5834
+ auth_expires_at: authHealth.expiresAt ?? null,
5835
+ server_ip: getLanIp(),
5836
+ missingPlugins,
5837
+ whatsapp: {
5838
+ any_connected: whatsappAnyConnected,
5839
+ any_stuck: whatsappAnyStuck,
5840
+ any_terminal: whatsappAnyTerminal,
5841
+ accounts: whatsappAccounts
5842
+ }
5843
+ });
5844
+ });
5845
+ var health_default = app;
5759
5846
 
5760
5847
  // app/lib/stt/voice-note.ts
5761
5848
  import { writeFile as writeFile3, mkdtemp, rm } from "fs/promises";
@@ -6214,10 +6301,10 @@ ${result.result.text}` : result.result.text;
6214
6301
  }
6215
6302
 
6216
6303
  // app/lib/channel-pty-bridge/admin-session-id.ts
6217
- import { createHash } from "crypto";
6304
+ import { createHash as createHash2 } from "crypto";
6218
6305
  function adminSessionIdFor(accountId, senderId, personId) {
6219
6306
  const subject = personId && personId.length > 0 ? `person:${personId}` : senderId;
6220
- const b = createHash("sha256").update(`${accountId}:admin:${subject}`).digest().subarray(0, 16);
6307
+ const b = createHash2("sha256").update(`${accountId}:admin:${subject}`).digest().subarray(0, 16);
6221
6308
  const v = Buffer.from(b);
6222
6309
  v[6] = v[6] & 15 | 64;
6223
6310
  v[8] = v[8] & 63 | 128;
@@ -6373,13 +6460,13 @@ async function sendTelegramText(botToken, chatId, text) {
6373
6460
 
6374
6461
  // server/routes/whatsapp.ts
6375
6462
  import { join as join10, resolve as resolve10 } from "path";
6376
- import { readdirSync as readdirSync4, readFileSync as readFileSync11, existsSync as existsSync6 } from "fs";
6463
+ import { readdirSync as readdirSync4, readFileSync as readFileSync11, existsSync as existsSync7 } from "fs";
6377
6464
 
6378
6465
  // app/lib/whatsapp/login.ts
6379
6466
  import { randomUUID as randomUUID6 } from "crypto";
6380
6467
 
6381
6468
  // app/lib/whatsapp/config-persist.ts
6382
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync4, existsSync as existsSync5 } from "fs";
6469
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync4, existsSync as existsSync6 } from "fs";
6383
6470
  import { resolve as resolve8, join as join8, dirname as dirname2, basename as basename2 } from "path";
6384
6471
  var TAG17 = "[whatsapp:config]";
6385
6472
  function configPath(accountDir) {
@@ -6387,7 +6474,7 @@ function configPath(accountDir) {
6387
6474
  }
6388
6475
  function readConfig(accountDir) {
6389
6476
  const path2 = configPath(accountDir);
6390
- if (!existsSync5(path2)) throw new Error(`account.json not found at ${path2}`);
6477
+ if (!existsSync6(path2)) throw new Error(`account.json not found at ${path2}`);
6391
6478
  return JSON.parse(readFileSync10(path2, "utf-8"));
6392
6479
  }
6393
6480
  function writeConfig(accountDir, config) {
@@ -6686,7 +6773,7 @@ function setPublicAgent(accountDir, slug) {
6686
6773
  return { ok: false, error: "Agent slug cannot be empty." };
6687
6774
  }
6688
6775
  const agentConfigPath = join8(accountDir, "agents", trimmed, "config.json");
6689
- if (!existsSync5(agentConfigPath)) {
6776
+ if (!existsSync6(agentConfigPath)) {
6690
6777
  return { ok: false, error: `Agent "${trimmed}" not found \u2014 no config.json at ${agentConfigPath}. Check the agent slug and try again.` };
6691
6778
  }
6692
6779
  try {
@@ -7648,13 +7735,13 @@ app2.post("/config", async (c) => {
7648
7735
  case "list-public-agents": {
7649
7736
  const agentsDir = resolve10(account.accountDir, "agents");
7650
7737
  const agents = [];
7651
- if (existsSync6(agentsDir)) {
7738
+ if (existsSync7(agentsDir)) {
7652
7739
  try {
7653
7740
  const entries = readdirSync4(agentsDir, { withFileTypes: true });
7654
7741
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
7655
7742
  if (!entry.isDirectory() || entry.name === "admin") continue;
7656
7743
  const configPath2 = resolve10(agentsDir, entry.name, "config.json");
7657
- if (!existsSync6(configPath2)) continue;
7744
+ if (!existsSync7(configPath2)) continue;
7658
7745
  try {
7659
7746
  const config = JSON.parse(readFileSync11(configPath2, "utf-8"));
7660
7747
  agents.push({ slug: entry.name, displayName: config.displayName ?? entry.name });
@@ -7971,7 +8058,7 @@ app2.get("/group-info", async (c) => {
7971
8058
  var whatsapp_default = app2;
7972
8059
 
7973
8060
  // server/routes/whatsapp-reader.ts
7974
- import { readFileSync as readFileSync15, watch, statSync as statSync5, openSync, readSync, closeSync, existsSync as existsSync8, readdirSync as readdirSync8, realpathSync as realpathSync4 } from "fs";
8061
+ import { readFileSync as readFileSync15, watch, statSync as statSync5, openSync, readSync, closeSync, existsSync as existsSync9, readdirSync as readdirSync8, realpathSync as realpathSync4 } from "fs";
7975
8062
  import { basename as basename5, dirname as dirname4, isAbsolute, join as join14, relative as relative2, resolve as resolve12, sep as sep3 } from "path";
7976
8063
 
7977
8064
  // server/routes/admin/sidebar-sessions.ts
@@ -8331,14 +8418,14 @@ app3.get("/", requireAdminSession, async (c) => {
8331
8418
  var sidebar_sessions_default = app3;
8332
8419
 
8333
8420
  // app/lib/admin-identity/pin-validator.ts
8334
- import { createHash as createHash2 } from "crypto";
8335
- import { existsSync as existsSync7, readFileSync as readFileSync13, readdirSync as readdirSync6, statSync as statSync4 } from "fs";
8421
+ import { createHash as createHash3 } from "crypto";
8422
+ import { existsSync as existsSync8, readFileSync as readFileSync13, readdirSync as readdirSync6, statSync as statSync4 } from "fs";
8336
8423
  import { join as join12 } from "path";
8337
8424
  function hashPin(pin) {
8338
- return createHash2("sha256").update(pin).digest("hex");
8425
+ return createHash3("sha256").update(pin).digest("hex");
8339
8426
  }
8340
8427
  function readUsersFile(usersFilePath) {
8341
- if (!existsSync7(usersFilePath)) return null;
8428
+ if (!existsSync8(usersFilePath)) return null;
8342
8429
  const raw = readFileSync13(usersFilePath, "utf-8").trim();
8343
8430
  if (!raw) return [];
8344
8431
  return JSON.parse(raw);
@@ -9049,7 +9136,7 @@ data: ${JSON.stringify(turn)}
9049
9136
  const sessionKey = c.req.query("session_key") ?? "";
9050
9137
  const subagentsDir = resolve12(join14(projectDir, sessionId, "subagents"));
9051
9138
  console.log(
9052
- `[webchat-activity] op=watch-subagents key=${sessionKey} dir=${subagentsDir} exists=${existsSync8(subagentsDir)}`
9139
+ `[webchat-activity] op=watch-subagents key=${sessionKey} dir=${subagentsDir} exists=${existsSync9(subagentsDir)}`
9053
9140
  );
9054
9141
  const actx = makeActivityCtx({
9055
9142
  subagentsDir,
@@ -9156,7 +9243,7 @@ app4.get("/directives", requireAdminSession, (c) => {
9156
9243
  const sessionId = c.req.query("sessionId") ?? "";
9157
9244
  if (!SESSION_ID_RE2.test(sessionId)) return c.json({ error: "bad session reference" }, 400);
9158
9245
  const dir = directiveStoreDir(sessionId);
9159
- if (!dir || !existsSync8(dir)) return c.json({ entries: [] });
9246
+ if (!dir || !existsSync9(dir)) return c.json({ entries: [] });
9160
9247
  let names;
9161
9248
  try {
9162
9249
  names = readdirSync8(dir).filter((n) => DIRECTIVE_NAME_RE.test(n));
@@ -9214,14 +9301,22 @@ app4.get("/store-stream", requireAdminSession, (c) => {
9214
9301
  const connId = `${remoteJid.slice(0, 12)}-${process.hrtime.bigint().toString(36).slice(-6)}`;
9215
9302
  const startedAt = Date.now();
9216
9303
  const lastEventId = c.req.header("Last-Event-ID");
9217
- const toTurn = (r) => ({
9218
- kind: "whatsapp-persisted",
9219
- text: r.body,
9220
- ts: r.dateSent,
9221
- fromMe: r.fromMe,
9222
- senderName: r.senderName,
9223
- origin: r.origin
9224
- });
9304
+ const uploadsBase = resolve12(DATA_ROOT, "accounts", accountId, "uploads");
9305
+ const toTurn = (r) => {
9306
+ const turn = {
9307
+ kind: "whatsapp-persisted",
9308
+ text: r.body,
9309
+ ts: r.dateSent,
9310
+ fromMe: r.fromMe,
9311
+ senderName: r.senderName,
9312
+ origin: r.origin
9313
+ };
9314
+ if (r.attachmentId) {
9315
+ const meta = readAttachmentMeta(join14(uploadsBase, r.attachmentId));
9316
+ if (meta) return { ...turn, attachments: [meta] };
9317
+ }
9318
+ return turn;
9319
+ };
9225
9320
  const readable = new ReadableStream({
9226
9321
  start(controller) {
9227
9322
  const send = (turn, id) => {
@@ -9405,7 +9500,7 @@ app4.post("/reply", requireAdminSession, async (c) => {
9405
9500
  return c.json({ error: `WhatsApp account "${accountId}" is registered but its socket is disconnected.` }, 503);
9406
9501
  }
9407
9502
  console.log(`${MANUAL_TAG} op=socket sendId=${sendId} accountId=${accountId} result=ok socketAccountId=${res.accountId}`);
9408
- const record = (waMessageId, body) => {
9503
+ const record = (waMessageId, body, attachmentId) => {
9409
9504
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
9410
9505
  const rec = {
9411
9506
  messageId: `whatsapp-live:${res.accountId}:${remoteJid}:${waMessageId}`,
@@ -9421,11 +9516,15 @@ app4.post("/reply", requireAdminSession, async (c) => {
9421
9516
  quotedSender: null,
9422
9517
  scope: template.scope,
9423
9518
  origin: "operator-manual",
9424
- createdAt: nowIso
9519
+ createdAt: nowIso,
9520
+ // Task 1536 — a media send carries the servable attachment reference (the
9521
+ // file already lives in the account's uploads store via storeAttachment),
9522
+ // so the store reader renders it inline instead of a `[sent …]` text line.
9523
+ ...attachmentId ? { attachmentId } : {}
9425
9524
  };
9426
9525
  appendMessage(accountId, rec);
9427
9526
  const present = readConversation(accountId, remoteJid).some((r) => r.messageId === rec.messageId);
9428
- console.log(`${MANUAL_TAG} op=recorded sendId=${sendId} waMessageId=${waMessageId} present=${present}`);
9527
+ console.log(`${MANUAL_TAG} op=recorded sendId=${sendId} waMessageId=${waMessageId} present=${present} attachment=${attachmentId ? "yes" : "no"}`);
9429
9528
  };
9430
9529
  let sent = 0;
9431
9530
  if (text) {
@@ -9463,7 +9562,7 @@ app4.post("/reply", requireAdminSession, async (c) => {
9463
9562
  mid = r.messageId;
9464
9563
  }
9465
9564
  console.log(`${MANUAL_TAG} op=sent sendId=${sendId} kind=${kind ?? "document"} idx=${i} waMessageId=${mid ?? ""}`);
9466
- if (mid) record(mid, `[sent ${stored.filename}]`);
9565
+ if (mid) record(mid, "", stored.attachmentId);
9467
9566
  sent++;
9468
9567
  }
9469
9568
  exit("delivered");
@@ -9774,7 +9873,7 @@ var public_reader_default = app5;
9774
9873
  // server/routes/webchat.ts
9775
9874
  import { basename as basename7, dirname as dirname6 } from "path";
9776
9875
  import { join as join18 } from "path";
9777
- import { existsSync as existsSync10, readdirSync as readdirSync11, readFileSync as readFileSync19, renameSync as renameSync4, statSync as statSync7, writeFileSync as writeFileSync7 } from "fs";
9876
+ import { existsSync as existsSync11, readdirSync as readdirSync11, readFileSync as readFileSync19, renameSync as renameSync4, statSync as statSync7, writeFileSync as writeFileSync7 } from "fs";
9778
9877
 
9779
9878
  // server/canonical-webchat-override.ts
9780
9879
  import { readFileSync as readFileSync17, writeFileSync as writeFileSync5, renameSync as renameSync2 } from "fs";
@@ -9861,7 +9960,7 @@ function isNewSessionEffortLevel(value) {
9861
9960
  }
9862
9961
 
9863
9962
  // ../services/claude-session-manager/src/sidecar-store.ts
9864
- import { existsSync as existsSync9, mkdirSync as mkdirSync3, readdirSync as readdirSync10, readFileSync as readFileSync18, renameSync as renameSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync6 } from "fs";
9963
+ import { existsSync as existsSync10, mkdirSync as mkdirSync3, readdirSync as readdirSync10, readFileSync as readFileSync18, renameSync as renameSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync6 } from "fs";
9865
9964
  import { join as join17 } from "path";
9866
9965
  function escapeRegExp(s) {
9867
9966
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -9882,7 +9981,7 @@ function createSidecarStore(config) {
9882
9981
  return { ok: true };
9883
9982
  } catch (error) {
9884
9983
  try {
9885
- if (existsSync9(tmp)) unlinkSync2(tmp);
9984
+ if (existsSync10(tmp)) unlinkSync2(tmp);
9886
9985
  } catch {
9887
9986
  }
9888
9987
  return { ok: false, error };
@@ -9938,7 +10037,7 @@ function createSidecarStore(config) {
9938
10037
  function clear(sessionsDir, id) {
9939
10038
  const path2 = pathFor(sessionsDir, id);
9940
10039
  try {
9941
- if (existsSync9(path2)) {
10040
+ if (existsSync10(path2)) {
9942
10041
  unlinkSync2(path2);
9943
10042
  return { ok: true, removed: true };
9944
10043
  }
@@ -10006,7 +10105,7 @@ function locateSidecar(sessionId) {
10006
10105
  }
10007
10106
  for (const slug of slugs) {
10008
10107
  const metaPath = join18(projectsRoot, slug, `${sessionId}.meta.json`);
10009
- if (existsSync10(metaPath)) {
10108
+ if (existsSync11(metaPath)) {
10010
10109
  const meta = readSidecarMeta(metaPath);
10011
10110
  return { channel: meta.channel, adminUserId: meta.adminUserId, accountId: meta.accountId };
10012
10111
  }
@@ -10601,7 +10700,7 @@ ${note}` : note;
10601
10700
  import { resolve as resolve14 } from "path";
10602
10701
 
10603
10702
  // app/lib/claude-agent/specialist-roster.ts
10604
- import { existsSync as existsSync11, readFileSync as readFileSync20, readdirSync as readdirSync12 } from "fs";
10703
+ import { existsSync as existsSync12, readFileSync as readFileSync20, readdirSync as readdirSync12 } from "fs";
10605
10704
  import { join as join19 } from "path";
10606
10705
  function field(fm, name) {
10607
10706
  const m = fm.match(new RegExp(`^${name}:\\s*(.*?)\\r?$`, "m"));
@@ -10613,7 +10712,7 @@ function field(fm, name) {
10613
10712
  return value || null;
10614
10713
  }
10615
10714
  function readSpecialistRoster(specialistsDir) {
10616
- if (!existsSync11(specialistsDir)) return { specialists: [], skipped: [] };
10715
+ if (!existsSync12(specialistsDir)) return { specialists: [], skipped: [] };
10617
10716
  const entries = readdirSync12(specialistsDir);
10618
10717
  const specialists = [];
10619
10718
  const skipped = [];
@@ -10739,7 +10838,7 @@ var webchat_greeting_default = app6;
10739
10838
  // server/routes/telegram.ts
10740
10839
  import { homedir } from "os";
10741
10840
  import { resolve as resolve15, join as join20 } from "path";
10742
- import { existsSync as existsSync12, readFileSync as readFileSync21 } from "fs";
10841
+ import { existsSync as existsSync13, readFileSync as readFileSync21 } from "fs";
10743
10842
 
10744
10843
  // app/lib/telegram/gateway/instance.ts
10745
10844
  var instance = null;
@@ -10799,7 +10898,7 @@ var TAG23 = "[telegram-inbound]";
10799
10898
  function configDirName() {
10800
10899
  const platformRoot3 = process.env.MAXY_PLATFORM_ROOT ?? resolve15(process.cwd(), "..");
10801
10900
  const brandPath = join20(platformRoot3, "config", "brand.json");
10802
- if (existsSync12(brandPath)) {
10901
+ if (existsSync13(brandPath)) {
10803
10902
  try {
10804
10903
  return JSON.parse(readFileSync21(brandPath, "utf-8")).configDir ?? ".maxy";
10805
10904
  } catch {
@@ -10820,7 +10919,7 @@ app7.post("/", async (c) => {
10820
10919
  return c.json({ ok: false }, 400);
10821
10920
  }
10822
10921
  const sp = secretPath(botType);
10823
- if (!existsSync12(sp)) {
10922
+ if (!existsSync13(sp)) {
10824
10923
  console.error(`${TAG23} op=reject reason=no-secret-file botType=${botType}`);
10825
10924
  return c.json({ ok: false }, 401);
10826
10925
  }
@@ -10888,7 +10987,7 @@ var telegram_default = app7;
10888
10987
 
10889
10988
  // server/routes/quickbooks.ts
10890
10989
  import { join as join21 } from "path";
10891
- import { existsSync as existsSync13, readFileSync as readFileSync22, writeFileSync as writeFileSync8, mkdirSync as mkdirSync4, rmSync, renameSync as renameSync5 } from "fs";
10990
+ import { existsSync as existsSync14, readFileSync as readFileSync22, writeFileSync as writeFileSync8, mkdirSync as mkdirSync4, rmSync, renameSync as renameSync5 } from "fs";
10892
10991
  var TAG24 = "[quickbooks]";
10893
10992
  var INTUIT_TOKEN_URL = "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer";
10894
10993
  var STATE_RE = /^[A-Za-z0-9_-]+$/;
@@ -10901,7 +11000,7 @@ function storePath(accountDir) {
10901
11000
  async function completeConsent(args) {
10902
11001
  const { accountDir, code, realmId } = args;
10903
11002
  const state = args.state;
10904
- if (!state || !STATE_RE.test(state) || !existsSync13(pendingPath(accountDir, state))) {
11003
+ if (!state || !STATE_RE.test(state) || !existsSync14(pendingPath(accountDir, state))) {
10905
11004
  console.error(`${TAG24} op=callback state=${state ?? ""} stateValid=false realmId=${realmId ?? ""}`);
10906
11005
  return { ok: false, status: 400, message: "Invalid or unknown authorization state.", stateValid: false };
10907
11006
  }
@@ -10928,7 +11027,7 @@ async function completeConsent(args) {
10928
11027
  if (!code || !realmId) {
10929
11028
  return { ok: false, status: 400, message: "Missing code or realmId in the callback.", stateValid: true };
10930
11029
  }
10931
- if (!existsSync13(storePath(accountDir))) {
11030
+ if (!existsSync14(storePath(accountDir))) {
10932
11031
  return { ok: false, status: 500, message: "No QuickBooks credentials are stored for this account.", stateValid: true };
10933
11032
  }
10934
11033
  const store2 = JSON.parse(readFileSync22(storePath(accountDir), "utf-8"));
@@ -10996,8 +11095,8 @@ var quickbooks_default = app8;
10996
11095
 
10997
11096
  // server/routes/onboarding.ts
10998
11097
  import { spawn, spawnSync as spawnSync2, execFileSync as execFileSync3 } from "child_process";
10999
- import { openSync as openSync3, closeSync as closeSync3, writeFileSync as writeFileSync10, writeSync, existsSync as existsSync15, readFileSync as readFileSync24, unlinkSync as unlinkSync3, renameSync as renameSync7 } from "fs";
11000
- import { createHash as createHash3, randomUUID as randomUUID7 } from "crypto";
11098
+ import { openSync as openSync3, closeSync as closeSync3, writeFileSync as writeFileSync10, writeSync, existsSync as existsSync16, readFileSync as readFileSync24, unlinkSync as unlinkSync3, renameSync as renameSync7 } from "fs";
11099
+ import { createHash as createHash4, randomUUID as randomUUID7 } from "crypto";
11001
11100
 
11002
11101
  // app/lib/claude-spawn-env.ts
11003
11102
  function claudeBin() {
@@ -11134,7 +11233,7 @@ function readHostCredsBlob(service) {
11134
11233
  }
11135
11234
 
11136
11235
  // ../lib/admins-write/src/index.ts
11137
- import { existsSync as existsSync14, readFileSync as readFileSync23, writeFileSync as writeFileSync9, renameSync as renameSync6, mkdirSync as mkdirSync5, readdirSync as readdirSync13, statSync as statSync8, appendFileSync as appendFileSync2 } from "fs";
11236
+ import { existsSync as existsSync15, readFileSync as readFileSync23, writeFileSync as writeFileSync9, renameSync as renameSync6, mkdirSync as mkdirSync5, readdirSync as readdirSync13, statSync as statSync8, appendFileSync as appendFileSync2 } from "fs";
11138
11237
  import { dirname as dirname7, join as join22 } from "path";
11139
11238
  function id8(value) {
11140
11239
  return value.slice(0, 8);
@@ -11175,7 +11274,7 @@ function writeAdminEntry(input) {
11175
11274
  const result = { usersJsonResult: "noop", accountJsonResult: "noop" };
11176
11275
  try {
11177
11276
  let users = [];
11178
- if (existsSync14(input.usersFile)) {
11277
+ if (existsSync15(input.usersFile)) {
11179
11278
  const raw = readFileSync23(input.usersFile, "utf-8").trim();
11180
11279
  if (raw) {
11181
11280
  const parsed = JSON.parse(raw);
@@ -11208,7 +11307,7 @@ function writeAdminEntry(input) {
11208
11307
  }
11209
11308
  try {
11210
11309
  const accountJsonPath = join22(input.accountDir, "account.json");
11211
- if (!existsSync14(accountJsonPath)) {
11310
+ if (!existsSync15(accountJsonPath)) {
11212
11311
  throw new Error(`account.json not found at ${accountJsonPath}`);
11213
11312
  }
11214
11313
  const config = JSON.parse(readFileSync23(accountJsonPath, "utf-8"));
@@ -11231,7 +11330,7 @@ function writeAdminEntry(input) {
11231
11330
  function computeAdminStoreDivergence(input) {
11232
11331
  const result = { divergences: 0, accountWithoutUsers: [], usersWithoutAccount: [], errors: [] };
11233
11332
  const usersUserIds = /* @__PURE__ */ new Set();
11234
- if (existsSync14(input.usersFile)) {
11333
+ if (existsSync15(input.usersFile)) {
11235
11334
  try {
11236
11335
  const raw = readFileSync23(input.usersFile, "utf-8").trim();
11237
11336
  if (raw) {
@@ -11245,7 +11344,7 @@ function computeAdminStoreDivergence(input) {
11245
11344
  }
11246
11345
  }
11247
11346
  const accountUserIds = /* @__PURE__ */ new Set();
11248
- if (existsSync14(input.accountsDir)) {
11347
+ if (existsSync15(input.accountsDir)) {
11249
11348
  let entries;
11250
11349
  try {
11251
11350
  entries = readdirSync13(input.accountsDir);
@@ -11262,7 +11361,7 @@ function computeAdminStoreDivergence(input) {
11262
11361
  continue;
11263
11362
  }
11264
11363
  const accountJsonPath = join22(accountDir, "account.json");
11265
- if (!existsSync14(accountJsonPath)) continue;
11364
+ if (!existsSync15(accountJsonPath)) continue;
11266
11365
  let admins = [];
11267
11366
  try {
11268
11367
  const config = JSON.parse(readFileSync23(accountJsonPath, "utf-8"));
@@ -11310,10 +11409,10 @@ function checkAdminAuthInvariant(input) {
11310
11409
 
11311
11410
  // server/routes/onboarding.ts
11312
11411
  function hashPin2(pin) {
11313
- return createHash3("sha256").update(pin).digest("hex");
11412
+ return createHash4("sha256").update(pin).digest("hex");
11314
11413
  }
11315
11414
  function readUsersFile2() {
11316
- if (!existsSync15(USERS_FILE)) return null;
11415
+ if (!existsSync16(USERS_FILE)) return null;
11317
11416
  const raw = readFileSync24(USERS_FILE, "utf-8").trim();
11318
11417
  if (!raw) return [];
11319
11418
  return JSON.parse(raw);
@@ -11373,7 +11472,7 @@ app9.post("/claude-auth", async (c) => {
11373
11472
  } catch (err) {
11374
11473
  logoutError = err instanceof Error ? err.message : String(err);
11375
11474
  }
11376
- const credentialsPresent = existsSync15(CLAUDE_CREDENTIALS_FILE);
11475
+ const credentialsPresent = existsSync16(CLAUDE_CREDENTIALS_FILE);
11377
11476
  const ranMs = Date.now() - start;
11378
11477
  console.log(`[onboarding] op=claude-logout credentialsPresent=${credentialsPresent} ranMs=${ranMs} logoutError=${logoutError ? JSON.stringify(logoutError.slice(0, 120)) : "none"}`);
11379
11478
  return c.json({ logged_out: !credentialsPresent });
@@ -11522,7 +11621,7 @@ app9.post("/set-pin", async (c) => {
11522
11621
  console.log(`[set-pin] wrote users.json + account.json admins: userId=${userId.slice(0, 8)}\u2026 role=owner`);
11523
11622
  if (process.platform === "linux") {
11524
11623
  let installOwner = null;
11525
- if (existsSync15(INSTALL_OWNER_FILE)) {
11624
+ if (existsSync16(INSTALL_OWNER_FILE)) {
11526
11625
  try {
11527
11626
  const raw = readFileSync24(INSTALL_OWNER_FILE, "utf-8").trim();
11528
11627
  if (raw.length > 0) installOwner = raw;
@@ -11670,7 +11769,7 @@ app9.put("/set-pin", requireAdminSession, async (c) => {
11670
11769
  console.log(`[set-pin] changed PIN in place: userId=${sessionUserId.slice(0, 8)}\u2026`);
11671
11770
  if (process.platform === "linux") {
11672
11771
  let installOwner = null;
11673
- if (existsSync15(INSTALL_OWNER_FILE)) {
11772
+ if (existsSync16(INSTALL_OWNER_FILE)) {
11674
11773
  try {
11675
11774
  const raw = readFileSync24(INSTALL_OWNER_FILE, "utf-8").trim();
11676
11775
  if (raw.length > 0) installOwner = raw;
@@ -11704,7 +11803,7 @@ ${body.newPin}
11704
11803
  var onboarding_default = app9;
11705
11804
 
11706
11805
  // server/routes/client-error.ts
11707
- import { appendFileSync as appendFileSync3, existsSync as existsSync16, renameSync as renameSync8, statSync as statSync9 } from "fs";
11806
+ import { appendFileSync as appendFileSync3, existsSync as existsSync17, renameSync as renameSync8, statSync as statSync9 } from "fs";
11708
11807
  import { join as join24 } from "path";
11709
11808
  var CLIENT_ERRORS_LOG = join24(LOG_DIR, "client-errors.log");
11710
11809
  var MAX_LOG_SIZE = 10 * 1024 * 1024;
@@ -11754,7 +11853,7 @@ function stackHead(stack) {
11754
11853
  }
11755
11854
  function rotateIfNeeded() {
11756
11855
  try {
11757
- if (!existsSync16(CLIENT_ERRORS_LOG)) return;
11856
+ if (!existsSync17(CLIENT_ERRORS_LOG)) return;
11758
11857
  const stats = statSync9(CLIENT_ERRORS_LOG);
11759
11858
  if (stats.size < MAX_LOG_SIZE) return;
11760
11859
  renameSync8(CLIENT_ERRORS_LOG, CLIENT_ERRORS_LOG + ".1");
@@ -12074,18 +12173,18 @@ app12.get("/", requireAdminSession, async (c) => {
12074
12173
  var accounts_default = app12;
12075
12174
 
12076
12175
  // server/routes/admin/logs.ts
12077
- import { existsSync as existsSync18, readdirSync as readdirSync14, readFileSync as readFileSync25, statSync as statSync10 } from "fs";
12176
+ import { existsSync as existsSync19, readdirSync as readdirSync14, readFileSync as readFileSync25, statSync as statSync10 } from "fs";
12078
12177
  import { resolve as resolve16, basename as basename8 } from "path";
12079
12178
 
12080
12179
  // app/lib/logs-read-resolve.ts
12081
- import { existsSync as existsSync17 } from "fs";
12180
+ import { existsSync as existsSync18 } from "fs";
12082
12181
  import { join as join25 } from "path";
12083
12182
  function resolveSessionLogPaths(filename, logDirs) {
12084
12183
  const tried = [filename];
12085
12184
  const hits = [];
12086
12185
  for (const dir of logDirs) {
12087
12186
  const fullPath = join25(dir, filename);
12088
- if (existsSync17(fullPath)) {
12187
+ if (existsSync18(fullPath)) {
12089
12188
  hits.push({ path: fullPath, dir });
12090
12189
  }
12091
12190
  }
@@ -12213,7 +12312,7 @@ app13.get("/", async (c) => {
12213
12312
  const seen = /* @__PURE__ */ new Set();
12214
12313
  const logs = {};
12215
12314
  for (const dir of logDirs) {
12216
- if (!existsSync18(dir)) continue;
12315
+ if (!existsSync19(dir)) continue;
12217
12316
  let files;
12218
12317
  try {
12219
12318
  files = readdirSync14(dir).filter((f) => f.endsWith(".log"));
@@ -12265,7 +12364,7 @@ var claude_info_default = app14;
12265
12364
 
12266
12365
  // server/routes/admin/attachment.ts
12267
12366
  import { readFile as readFile2, readdir } from "fs/promises";
12268
- import { existsSync as existsSync19 } from "fs";
12367
+ import { existsSync as existsSync20 } from "fs";
12269
12368
  import { resolve as resolve17 } from "path";
12270
12369
  var app15 = new Hono();
12271
12370
  var ATTACHMENT_ID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
@@ -12288,12 +12387,12 @@ app15.get("/:attachmentId", requireAdminSession, async (c) => {
12288
12387
  }
12289
12388
  let accountId = pinnedAccountId;
12290
12389
  let dir = uploadsDirFor2(accountId, attachmentId);
12291
- let found = existsSync19(dir);
12390
+ let found = existsSync20(dir);
12292
12391
  if (!found) {
12293
12392
  for (const candidate of listValidAccounts()) {
12294
12393
  if (candidate.accountId === pinnedAccountId) continue;
12295
12394
  const candidateDir = uploadsDirFor2(candidate.accountId, attachmentId);
12296
- if (existsSync19(candidateDir)) {
12395
+ if (existsSync20(candidateDir)) {
12297
12396
  accountId = candidate.accountId;
12298
12397
  dir = candidateDir;
12299
12398
  found = true;
@@ -12308,7 +12407,7 @@ app15.get("/:attachmentId", requireAdminSession, async (c) => {
12308
12407
  return new Response("Not found", { status: 404 });
12309
12408
  }
12310
12409
  const metaPath = resolve17(dir, `${attachmentId}.meta.json`);
12311
- if (!existsSync19(metaPath)) {
12410
+ if (!existsSync20(metaPath)) {
12312
12411
  return new Response("Not found", { status: 404 });
12313
12412
  }
12314
12413
  let meta;
@@ -12336,13 +12435,13 @@ var attachment_default = app15;
12336
12435
 
12337
12436
  // server/routes/admin/agents.ts
12338
12437
  import { resolve as resolve18 } from "path";
12339
- import { readdirSync as readdirSync15, readFileSync as readFileSync26, existsSync as existsSync20, rmSync as rmSync2 } from "fs";
12438
+ import { readdirSync as readdirSync15, readFileSync as readFileSync26, existsSync as existsSync21, rmSync as rmSync2 } from "fs";
12340
12439
  var app16 = new Hono();
12341
12440
  app16.get("/", (c) => {
12342
12441
  const account = resolveAccount();
12343
12442
  if (!account) return c.json({ agents: [] });
12344
12443
  const agentsDir = resolve18(account.accountDir, "agents");
12345
- if (!existsSync20(agentsDir)) return c.json({ agents: [] });
12444
+ if (!existsSync21(agentsDir)) return c.json({ agents: [] });
12346
12445
  const agents = [];
12347
12446
  try {
12348
12447
  const entries = readdirSync15(agentsDir, { withFileTypes: true });
@@ -12350,7 +12449,7 @@ app16.get("/", (c) => {
12350
12449
  if (!entry.isDirectory()) continue;
12351
12450
  if (entry.name === "admin") continue;
12352
12451
  const configPath2 = resolve18(agentsDir, entry.name, "config.json");
12353
- if (!existsSync20(configPath2)) continue;
12452
+ if (!existsSync21(configPath2)) continue;
12354
12453
  try {
12355
12454
  const config = JSON.parse(readFileSync26(configPath2, "utf-8"));
12356
12455
  agents.push({
@@ -12379,7 +12478,7 @@ app16.delete("/:slug", async (c) => {
12379
12478
  return c.json({ error: "Invalid agent slug" }, 400);
12380
12479
  }
12381
12480
  const agentDir = resolve18(account.accountDir, "agents", slug);
12382
- if (!existsSync20(agentDir)) {
12481
+ if (!existsSync21(agentDir)) {
12383
12482
  return c.json({ error: "Agent not found" }, 404);
12384
12483
  }
12385
12484
  try {
@@ -12409,7 +12508,7 @@ app16.post("/:slug/project", async (c) => {
12409
12508
  return c.json({ error: "Invalid agent slug" }, 400);
12410
12509
  }
12411
12510
  const agentDir = resolve18(account.accountDir, "agents", slug);
12412
- if (!existsSync20(agentDir)) {
12511
+ if (!existsSync21(agentDir)) {
12413
12512
  return c.json({ error: "Agent not found on disk" }, 404);
12414
12513
  }
12415
12514
  try {
@@ -12425,7 +12524,7 @@ var agents_default = app16;
12425
12524
  // server/routes/admin/sessions.ts
12426
12525
  import crypto2 from "crypto";
12427
12526
  import { resolve as resolvePath } from "path";
12428
- import { existsSync as existsSync21, mkdirSync as mkdirSync6, createWriteStream } from "fs";
12527
+ import { existsSync as existsSync22, mkdirSync as mkdirSync6, createWriteStream } from "fs";
12429
12528
 
12430
12529
  // ../lib/admin-conversation-purge/src/index.ts
12431
12530
  async function purgeAdminConversationJsonls(args) {
@@ -12585,7 +12684,7 @@ function validateAndShapeAttachments(raws, conversationAccountId, sessionId, mes
12585
12684
  let reason = null;
12586
12685
  if (!a.attachmentId || !a.filename || !a.mimeType || !a.storagePath) reason = "schema-fail";
12587
12686
  else if (a.accountId !== conversationAccountId) reason = "account-mismatch";
12588
- else if (!existsSync21(a.storagePath)) reason = "missing-file";
12687
+ else if (!existsSync22(a.storagePath)) reason = "missing-file";
12589
12688
  if (reason) {
12590
12689
  invalid++;
12591
12690
  try {
@@ -13111,7 +13210,7 @@ app17.put("/:id/label", requireAdminSession, async (c) => {
13111
13210
  var sessions_default = app17;
13112
13211
 
13113
13212
  // app/lib/claude-agent/spawn-context.ts
13114
- import { existsSync as existsSync22, readFileSync as readFileSync27, readdirSync as readdirSync16, statSync as statSync11 } from "fs";
13213
+ import { existsSync as existsSync23, readFileSync as readFileSync27, readdirSync as readdirSync16, statSync as statSync11 } from "fs";
13115
13214
  import { dirname as dirname9, resolve as resolve19, join as join26 } from "path";
13116
13215
  async function resolveOwnerProfileBlock(accountId, userId) {
13117
13216
  if (!userId) return { ok: false, reason: "missing-user-id" };
@@ -13158,7 +13257,7 @@ function extractPluginToolDescriptions(pluginDir) {
13158
13257
  ];
13159
13258
  let raw = null;
13160
13259
  for (const path2 of candidates) {
13161
- if (!existsSync22(path2)) continue;
13260
+ if (!existsSync23(path2)) continue;
13162
13261
  try {
13163
13262
  raw = readFileSync27(path2, "utf-8");
13164
13263
  break;
@@ -13210,7 +13309,7 @@ function parseSkillPathsFromFrontmatter(fm) {
13210
13309
  function listPluginDirs() {
13211
13310
  const out = /* @__PURE__ */ new Map();
13212
13311
  const platformPlugins = resolve19(PLATFORM_ROOT, "plugins");
13213
- if (existsSync22(platformPlugins)) {
13312
+ if (existsSync23(platformPlugins)) {
13214
13313
  for (const name of readdirSync16(platformPlugins)) {
13215
13314
  const dir = join26(platformPlugins, name);
13216
13315
  try {
@@ -13220,10 +13319,10 @@ function listPluginDirs() {
13220
13319
  }
13221
13320
  }
13222
13321
  const premiumRoot = resolve19(dirname9(PLATFORM_ROOT), "premium-plugins");
13223
- if (existsSync22(premiumRoot)) {
13322
+ if (existsSync23(premiumRoot)) {
13224
13323
  for (const bundle of readdirSync16(premiumRoot)) {
13225
13324
  const bundlePlugins = join26(premiumRoot, bundle, "plugins");
13226
- if (!existsSync22(bundlePlugins)) continue;
13325
+ if (!existsSync23(bundlePlugins)) continue;
13227
13326
  try {
13228
13327
  for (const name of readdirSync16(bundlePlugins)) {
13229
13328
  const dir = join26(bundlePlugins, name);
@@ -13240,7 +13339,7 @@ function listPluginDirs() {
13240
13339
  }
13241
13340
  function readEnabledPlugins(accountId) {
13242
13341
  const accountFile = resolve19(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
13243
- if (!existsSync22(accountFile)) return /* @__PURE__ */ new Set();
13342
+ if (!existsSync23(accountFile)) return /* @__PURE__ */ new Set();
13244
13343
  try {
13245
13344
  const parsed = JSON.parse(readFileSync27(accountFile, "utf-8"));
13246
13345
  return new Set(
@@ -13251,7 +13350,7 @@ function readEnabledPlugins(accountId) {
13251
13350
  }
13252
13351
  }
13253
13352
  function readFrontmatter(path2) {
13254
- if (!existsSync22(path2)) return null;
13353
+ if (!existsSync23(path2)) return null;
13255
13354
  let raw;
13256
13355
  try {
13257
13356
  raw = readFileSync27(path2, "utf-8");
@@ -13286,7 +13385,7 @@ ${fm}
13286
13385
  `plugin-manifest-tool-coverage plugin=${name} tools=${tools.length} with-desc=${withDesc}`
13287
13386
  );
13288
13387
  if (toolNames.length > 0 && descMap.size === 0) {
13289
- const hasSource = existsSync22(join26(dir, "mcp/dist/index.js")) || existsSync22(join26(dir, "mcp/src/index.ts"));
13388
+ const hasSource = existsSync23(join26(dir, "mcp/dist/index.js")) || existsSync23(join26(dir, "mcp/src/index.ts"));
13290
13389
  if (hasSource) {
13291
13390
  console.warn(
13292
13391
  `[spawn-context] WARN plugin=${name} mcp source present but tool-description regex matched zero entries \u2014 SDK upgrade may have changed the registration shape`
@@ -13313,7 +13412,7 @@ ${skillFm}
13313
13412
  }
13314
13413
  function computeSpecialistDomains(accountId) {
13315
13414
  const specialistsDir = resolve19(PLATFORM_ROOT, "..", "data/accounts", accountId, "specialists", "agents");
13316
- if (!existsSync22(specialistsDir)) return [];
13415
+ if (!existsSync23(specialistsDir)) return [];
13317
13416
  let entries;
13318
13417
  try {
13319
13418
  entries = readdirSync16(specialistsDir);
@@ -13359,7 +13458,7 @@ ${fm}
13359
13458
  }
13360
13459
  function computeDormantPlugins(accountId) {
13361
13460
  const accountFile = resolve19(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
13362
- if (!existsSync22(accountFile)) return [];
13461
+ if (!existsSync23(accountFile)) return [];
13363
13462
  let enabled;
13364
13463
  try {
13365
13464
  const raw = readFileSync27(accountFile, "utf-8");
@@ -13374,7 +13473,7 @@ function computeDormantPlugins(accountId) {
13374
13473
  return [];
13375
13474
  }
13376
13475
  const pluginsDir = resolve19(PLATFORM_ROOT, "plugins");
13377
- if (!existsSync22(pluginsDir)) return [];
13476
+ if (!existsSync23(pluginsDir)) return [];
13378
13477
  let entries;
13379
13478
  try {
13380
13479
  entries = readdirSync16(pluginsDir);
@@ -13385,7 +13484,7 @@ function computeDormantPlugins(accountId) {
13385
13484
  for (const name of entries) {
13386
13485
  if (enabled.has(name)) continue;
13387
13486
  const manifestPath = resolve19(pluginsDir, name, "PLUGIN.md");
13388
- if (!existsSync22(manifestPath)) continue;
13487
+ if (!existsSync23(manifestPath)) continue;
13389
13488
  let manifest;
13390
13489
  try {
13391
13490
  manifest = readFileSync27(manifestPath, "utf-8");
@@ -13402,11 +13501,11 @@ function computeDormantPlugins(accountId) {
13402
13501
  }
13403
13502
 
13404
13503
  // server/routes/admin/claude-sessions.ts
13405
- import { existsSync as existsSync23, readFileSync as readFileSync28 } from "fs";
13504
+ import { existsSync as existsSync24, readFileSync as readFileSync28 } from "fs";
13406
13505
  import { resolve as resolve20 } from "path";
13407
13506
  function readTunnelState(brandConfigDir) {
13408
13507
  const statePath = `${process.env.HOME ?? ""}/${brandConfigDir}/cloudflared/tunnel.state`;
13409
- if (!existsSync23(statePath)) return null;
13508
+ if (!existsSync24(statePath)) return null;
13410
13509
  try {
13411
13510
  const parsed = JSON.parse(readFileSync28(statePath, "utf-8"));
13412
13511
  const tunnelId = typeof parsed.tunnelId === "string" ? parsed.tunnelId : null;
@@ -13664,7 +13763,7 @@ app20.post("/", async (c) => {
13664
13763
  var events_default = app20;
13665
13764
 
13666
13765
  // server/routes/admin/files.ts
13667
- import { createReadStream as createReadStream2, createWriteStream as createWriteStream2, existsSync as existsSync24 } from "fs";
13766
+ import { createReadStream as createReadStream2, createWriteStream as createWriteStream2, existsSync as existsSync25 } from "fs";
13668
13767
  import { readdir as readdir3, readFile as readFile4, stat as stat4, mkdir as mkdir3, unlink as unlink2, rename } from "fs/promises";
13669
13768
  import { realpathSync as realpathSync5 } from "fs";
13670
13769
  import { randomUUID as randomUUID9 } from "crypto";
@@ -15027,7 +15126,7 @@ app21.post("/upload", requireAdminSession, async (c) => {
15027
15126
  });
15028
15127
  await unlink2(tmpPath).catch(() => {
15029
15128
  });
15030
- const tempRemoved = !existsSync24(tmpPath);
15129
+ const tempRemoved = !existsSync25(tmpPath);
15031
15130
  console.error(`[data-upload] op=cleanup uid=${uid} tempRemoved=${tempRemoved}`);
15032
15131
  };
15033
15132
  const reader = body.getReader();
@@ -15101,7 +15200,7 @@ async function discardChunkUpload(uploadId, state) {
15101
15200
  });
15102
15201
  await unlink2(state.tmpPath).catch(() => {
15103
15202
  });
15104
- const tempRemoved = !existsSync24(state.tmpPath);
15203
+ const tempRemoved = !existsSync25(state.tmpPath);
15105
15204
  console.error(`[data-upload] op=cleanup uid=${uploadId} tempRemoved=${tempRemoved}`);
15106
15205
  }
15107
15206
  async function writeChunkBody(body, out, alreadyReceived, ceiling) {
@@ -15511,7 +15610,7 @@ function applyBacklinkBoost(hits) {
15511
15610
  }
15512
15611
 
15513
15612
  // ../lib/graph-search/src/dedup.ts
15514
- import { createHash as createHash4 } from "crypto";
15613
+ import { createHash as createHash5 } from "crypto";
15515
15614
  var DEFAULT_LAYERS = [
15516
15615
  "nodeId",
15517
15616
  "slug",
@@ -15538,7 +15637,7 @@ function readContentHash(props) {
15538
15637
  const content = props["content"];
15539
15638
  const source = typeof compiled === "string" && compiled.length > 0 ? compiled : typeof content === "string" && content.length > 0 ? content : null;
15540
15639
  if (source === null) return null;
15541
- return createHash4("sha256").update(source).digest("hex").slice(0, 16);
15640
+ return createHash5("sha256").update(source).digest("hex").slice(0, 16);
15542
15641
  }
15543
15642
  function dedup(hits, layers = DEFAULT_LAYERS) {
15544
15643
  const sorted = [...hits].sort((a, b) => b.score - a.score);
@@ -17558,7 +17657,7 @@ var graph_default_view_default = app27;
17558
17657
  // server/routes/admin/sidebar-artefacts.ts
17559
17658
  import { readdir as readdir4, stat as stat5 } from "fs/promises";
17560
17659
  import { resolve as resolve23, relative as relative5, isAbsolute as isAbsolute2, sep as sep7, basename as basename11 } from "path";
17561
- import { existsSync as existsSync25 } from "fs";
17660
+ import { existsSync as existsSync26 } from "fs";
17562
17661
  var LIMIT = 50;
17563
17662
  var ADMIN_AGENT_FILES = ["IDENTITY.md", "SOUL.md", "KNOWLEDGE.md"];
17564
17663
  function toDataRootRelPath(absPath) {
@@ -17665,7 +17764,7 @@ async function fetchAgentTemplateRows(accountDir) {
17665
17764
  async function unionSpecialistFilenames(overrideDir, bundledDir) {
17666
17765
  const names = /* @__PURE__ */ new Set();
17667
17766
  for (const dir of [overrideDir, bundledDir]) {
17668
- if (!existsSync25(dir)) continue;
17767
+ if (!existsSync26(dir)) continue;
17669
17768
  try {
17670
17769
  const entries = await readdir4(dir);
17671
17770
  for (const entry of entries) {
@@ -17680,7 +17779,7 @@ async function unionSpecialistFilenames(overrideDir, bundledDir) {
17680
17779
  }
17681
17780
  async function readAgentTemplateRow(inp) {
17682
17781
  let chosenPath = null;
17683
- if (existsSync25(inp.overridePath)) {
17782
+ if (existsSync26(inp.overridePath)) {
17684
17783
  try {
17685
17784
  validateFilePathInAccount(inp.overridePath, inp.overrideRoot);
17686
17785
  chosenPath = inp.overridePath;
@@ -17691,7 +17790,7 @@ async function readAgentTemplateRow(inp) {
17691
17790
  );
17692
17791
  return null;
17693
17792
  }
17694
- } else if (existsSync25(inp.bundledPath)) {
17793
+ } else if (existsSync26(inp.bundledPath)) {
17695
17794
  if (!isWithin(inp.bundledPath, inp.bundledRoot)) {
17696
17795
  console.error(
17697
17796
  `[admin/sidebar-artefacts] agent-template-read-failed agent=${inp.displayName} kind=${inp.logName} error="bundled path outside PLATFORM_ROOT"`
@@ -18294,12 +18393,12 @@ app36.get("/", async (c) => {
18294
18393
  var system_stats_default = app36;
18295
18394
 
18296
18395
  // server/routes/admin/health.ts
18297
- import { existsSync as existsSync26, readFileSync as readFileSync29 } from "fs";
18396
+ import { existsSync as existsSync27, readFileSync as readFileSync29 } from "fs";
18298
18397
  import { resolve as resolve24, join as join29 } from "path";
18299
18398
  var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT ?? resolve24(process.cwd(), "..");
18300
18399
  var brandHostname = "maxy";
18301
18400
  var brandJsonPath = join29(PLATFORM_ROOT6, "config", "brand.json");
18302
- if (existsSync26(brandJsonPath)) {
18401
+ if (existsSync27(brandJsonPath)) {
18303
18402
  try {
18304
18403
  const brand = JSON.parse(readFileSync29(brandJsonPath, "utf-8"));
18305
18404
  if (brand.hostname) brandHostname = brand.hostname;
@@ -18310,7 +18409,7 @@ var VERSION_FILE = resolve24(PLATFORM_ROOT6, `config/.${brandHostname}-version`)
18310
18409
  var PROCESS_STARTED_AT = (/* @__PURE__ */ new Date()).toISOString();
18311
18410
  var PROBE_TIMEOUT_MS = 1e3;
18312
18411
  function readVersion() {
18313
- if (!existsSync26(VERSION_FILE)) return "unknown";
18412
+ if (!existsSync27(VERSION_FILE)) return "unknown";
18314
18413
  return readFileSync29(VERSION_FILE, "utf-8").trim() || "unknown";
18315
18414
  }
18316
18415
  async function probeConversationDb() {
@@ -19087,7 +19186,7 @@ app44.post("/launch", requireAdminSession, async (c) => {
19087
19186
  var browser_default = app44;
19088
19187
 
19089
19188
  // server/routes/admin/calendar.ts
19090
- import { existsSync as existsSync27 } from "fs";
19189
+ import { existsSync as existsSync28 } from "fs";
19091
19190
  import { join as join31 } from "path";
19092
19191
 
19093
19192
  // server/lib/calendar-ics.ts
@@ -19559,7 +19658,7 @@ app45.get("/booking-link", requireAdminSession, (c) => {
19559
19658
  console.error('[calendar] op=booking-link-fail err="no account configured"');
19560
19659
  return c.json({ bookingDomain: null });
19561
19660
  }
19562
- if (!existsSync27(join31(account.accountDir, AVAILABILITY_FILENAME))) {
19661
+ if (!existsSync28(join31(account.accountDir, AVAILABILITY_FILENAME))) {
19563
19662
  console.log("[calendar] op=booking-link bookingDomain=none source=availability-config");
19564
19663
  return c.json({ bookingDomain: null });
19565
19664
  }
@@ -20107,7 +20206,7 @@ app53.route("/request-magic-link", request_magic_link_default);
20107
20206
  var access_default = app53;
20108
20207
 
20109
20208
  // server/routes/sites.ts
20110
- import { existsSync as existsSync28, readFileSync as readFileSync31, realpathSync as realpathSync6, statSync as statSync12 } from "fs";
20209
+ import { existsSync as existsSync29, readFileSync as readFileSync31, realpathSync as realpathSync6, statSync as statSync12 } from "fs";
20111
20210
  import { resolve as resolve26 } from "path";
20112
20211
  var SAFE_SEG_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
20113
20212
  var MIME = {
@@ -20173,7 +20272,7 @@ app54.get("/:rel{.*}", (c) => {
20173
20272
  }
20174
20273
  let stat8;
20175
20274
  try {
20176
- stat8 = existsSync28(filePath) ? statSync12(filePath) : null;
20275
+ stat8 = existsSync29(filePath) ? statSync12(filePath) : null;
20177
20276
  } catch {
20178
20277
  stat8 = null;
20179
20278
  }
@@ -20192,7 +20291,7 @@ app54.get("/:rel{.*}", (c) => {
20192
20291
  console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
20193
20292
  return c.text("Forbidden", 403);
20194
20293
  }
20195
- if (!existsSync28(filePath)) {
20294
+ if (!existsSync29(filePath)) {
20196
20295
  console.error(`[sites] not-found path=${reqPath} status=404`);
20197
20296
  return c.text("Not found", 404);
20198
20297
  }
@@ -20323,7 +20422,7 @@ var VISITOR_COOKIE_NAME = "mxy_v";
20323
20422
  var VISITOR_COOKIE_MAX_AGE_SECONDS = Math.floor(DEFAULT_TTL_MS / 1e3);
20324
20423
 
20325
20424
  // app/lib/brand-config.ts
20326
- import { existsSync as existsSync29, readFileSync as readFileSync33 } from "fs";
20425
+ import { existsSync as existsSync30, readFileSync as readFileSync33 } from "fs";
20327
20426
  import { join as join32 } from "path";
20328
20427
  var cached = null;
20329
20428
  var cachedAttempted = false;
@@ -20333,7 +20432,7 @@ function readBrandConfig() {
20333
20432
  const platformRoot3 = process.env.MAXY_PLATFORM_ROOT;
20334
20433
  if (!platformRoot3) return null;
20335
20434
  const brandPath = join32(platformRoot3, "config", "brand.json");
20336
- if (!existsSync29(brandPath)) return null;
20435
+ if (!existsSync30(brandPath)) return null;
20337
20436
  try {
20338
20437
  cached = JSON.parse(readFileSync33(brandPath, "utf-8"));
20339
20438
  return cached;
@@ -20824,7 +20923,7 @@ var visitor_event_default = app57;
20824
20923
 
20825
20924
  // server/routes/session.ts
20826
20925
  import { resolve as resolve27 } from "path";
20827
- import { existsSync as existsSync30, writeFileSync as writeFileSync12, mkdirSync as mkdirSync8 } from "fs";
20926
+ import { existsSync as existsSync31, writeFileSync as writeFileSync12, mkdirSync as mkdirSync8 } from "fs";
20828
20927
  var UUID_RE4 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
20829
20928
  function writeBrandingCache(accountId, agentSlug, branding) {
20830
20929
  try {
@@ -20908,7 +21007,7 @@ app58.post("/", async (c) => {
20908
21007
  let agentConfig = null;
20909
21008
  if (account) {
20910
21009
  const agentDir = resolve27(account.accountDir, "agents", agentSlug);
20911
- if (!existsSync30(agentDir) || !existsSync30(resolve27(agentDir, "config.json"))) {
21010
+ if (!existsSync31(agentDir) || !existsSync31(resolve27(agentDir, "config.json"))) {
20912
21011
  return c.json({ error: "Agent not found" }, 404);
20913
21012
  }
20914
21013
  agentConfig = resolveAgentConfig(account.accountDir, agentSlug);
@@ -21203,9 +21302,17 @@ function startTimeEntryCensus(openSession, opts = {}) {
21203
21302
  const intervalMs = opts.intervalMs ?? DEFAULT_INTERVAL_MS;
21204
21303
  const horizonMs = opts.horizonMs ?? DEFAULT_HORIZON_MS;
21205
21304
  const tick = async () => {
21206
- const session = openSession();
21305
+ let session;
21306
+ try {
21307
+ session = openSession();
21308
+ } catch (err) {
21309
+ console.error(`${TAG39} open-session-failed error="${err instanceof Error ? err.message : String(err)}"`);
21310
+ return;
21311
+ }
21207
21312
  try {
21208
21313
  await runTimeEntryCensusOnce(session, Date.now(), horizonMs);
21314
+ } catch (err) {
21315
+ console.error(`${TAG39} tick-failed error="${err instanceof Error ? err.message : String(err)}"`);
21209
21316
  } finally {
21210
21317
  try {
21211
21318
  await session.close();
@@ -21568,7 +21675,7 @@ async function migrateUploads(opts = {}) {
21568
21675
 
21569
21676
  // app/lib/migrate-admin-webchat-sidecars.ts
21570
21677
  import { readdir as readdir6, readFile as readFile6, rename as rename3, writeFile as writeFile4, unlink as unlink3 } from "fs/promises";
21571
- import { existsSync as existsSync31 } from "fs";
21678
+ import { existsSync as existsSync32 } from "fs";
21572
21679
  import { join as join33 } from "path";
21573
21680
  var ADMIN_ROLE2 = "admin";
21574
21681
  var WEBCHAT_CHANNEL2 = "webchat";
@@ -21598,7 +21705,7 @@ async function writeRaw(path2, obj) {
21598
21705
  await rename3(tmp, path2);
21599
21706
  } catch (err) {
21600
21707
  try {
21601
- if (existsSync31(tmp)) await unlink3(tmp);
21708
+ if (existsSync32(tmp)) await unlink3(tmp);
21602
21709
  } catch {
21603
21710
  }
21604
21711
  throw err;
@@ -24047,8 +24154,8 @@ function broadcastAdminShutdown(reason) {
24047
24154
  }
24048
24155
 
24049
24156
  // ../lib/entitlement/src/index.ts
24050
- import { createPublicKey, createHash as createHash5, verify as cryptoVerify } from "crypto";
24051
- import { existsSync as existsSync32, readFileSync as readFileSync34, statSync as statSync13 } from "fs";
24157
+ import { createPublicKey, createHash as createHash6, verify as cryptoVerify } from "crypto";
24158
+ import { existsSync as existsSync33, readFileSync as readFileSync34, statSync as statSync13 } from "fs";
24052
24159
  import { resolve as resolve32 } from "path";
24053
24160
 
24054
24161
  // ../lib/entitlement/src/canonicalize.ts
@@ -24097,7 +24204,7 @@ function resolveEntitlement(brand, account) {
24097
24204
  return logResolved(implicitTrust(account), null);
24098
24205
  }
24099
24206
  const entitlementPath = resolve32(brand.configDir, "entitlement.json");
24100
- if (!existsSync32(entitlementPath)) {
24207
+ if (!existsSync33(entitlementPath)) {
24101
24208
  return logResolved(anonymousFallback("missing"), { reason: "missing" });
24102
24209
  }
24103
24210
  const stat8 = statSync13(entitlementPath);
@@ -24118,7 +24225,7 @@ function verifyAndResolve(brand, entitlementPath, account) {
24118
24225
  reason: "pubkey-missing"
24119
24226
  });
24120
24227
  }
24121
- const pubkeyHash = createHash5("sha256").update(pubkeyPem).digest("hex");
24228
+ const pubkeyHash = createHash6("sha256").update(pubkeyPem).digest("hex");
24122
24229
  if (pubkeyHash !== PUBKEY_SHA256) {
24123
24230
  return logResolved(anonymousFallback("pubkey-tamper"), {
24124
24231
  reason: "pubkey-tamper"
@@ -24289,10 +24396,10 @@ function clientFrom(c) {
24289
24396
  var PLATFORM_ROOT8 = process.env.MAXY_PLATFORM_ROOT || "";
24290
24397
  var BRAND_JSON_PATH = PLATFORM_ROOT8 ? join35(PLATFORM_ROOT8, "config", "brand.json") : "";
24291
24398
  var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", marketingUrl: "https://getmaxy.com" };
24292
- if (BRAND_JSON_PATH && !existsSync33(BRAND_JSON_PATH)) {
24399
+ if (BRAND_JSON_PATH && !existsSync34(BRAND_JSON_PATH)) {
24293
24400
  console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
24294
24401
  }
24295
- if (BRAND_JSON_PATH && existsSync33(BRAND_JSON_PATH)) {
24402
+ if (BRAND_JSON_PATH && existsSync34(BRAND_JSON_PATH)) {
24296
24403
  try {
24297
24404
  const parsed = JSON.parse(readFileSync35(BRAND_JSON_PATH, "utf-8"));
24298
24405
  BRAND = { ...BRAND, ...parsed };
@@ -24329,7 +24436,7 @@ var brandLoginOpts = {
24329
24436
  var ALIAS_DOMAINS_PATH = join35(homedir3(), BRAND.configDir, "alias-domains.json");
24330
24437
  function loadAliasDomains() {
24331
24438
  try {
24332
- if (!existsSync33(ALIAS_DOMAINS_PATH)) return null;
24439
+ if (!existsSync34(ALIAS_DOMAINS_PATH)) return null;
24333
24440
  const parsed = JSON.parse(readFileSync35(ALIAS_DOMAINS_PATH, "utf-8"));
24334
24441
  if (!Array.isArray(parsed)) {
24335
24442
  console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
@@ -24414,7 +24521,7 @@ var waGateway = new WaGateway({
24414
24521
  },
24415
24522
  ensureChannelSession: async ({ accountId, effectiveAccountId, senderId, role, personId, gatewayUrl, serverPath, passive, text, msgId, sessionIdOverride }) => {
24416
24523
  if (passive) {
24417
- const phoneHash = createHash6("sha256").update(senderId).digest("hex").slice(0, 12);
24524
+ const phoneHash = createHash7("sha256").update(senderId).digest("hex").slice(0, 12);
24418
24525
  const prompt = (text ?? "").trim();
24419
24526
  if (prompt.length === 0) {
24420
24527
  console.error(`[wa-passive] op=dropped account=${effectiveAccountId} phoneHash=${phoneHash} msgId=${msgId ?? "none"} reason=empty-text`);
@@ -25011,7 +25118,7 @@ app60.get("/agent-assets/:slug/:filename", (c) => {
25011
25118
  console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
25012
25119
  return c.text("Forbidden", 403);
25013
25120
  }
25014
- if (!existsSync33(filePath)) {
25121
+ if (!existsSync34(filePath)) {
25015
25122
  console.error(`[agent-assets] serve slug=${slug} file=${filename} status=404`);
25016
25123
  return c.text("Not found", 404);
25017
25124
  }
@@ -25041,7 +25148,7 @@ app60.get("/generated/:filename", (c) => {
25041
25148
  console.error(`[generated] serve file=${filename} status=403`);
25042
25149
  return c.text("Forbidden", 403);
25043
25150
  }
25044
- if (!existsSync33(filePath)) {
25151
+ if (!existsSync34(filePath)) {
25045
25152
  console.error(`[generated] serve file=${filename} status=404`);
25046
25153
  return c.text("Not found", 404);
25047
25154
  }
@@ -25061,7 +25168,7 @@ app60.route("/v", visitor_consent_default);
25061
25168
  var htmlCache = /* @__PURE__ */ new Map();
25062
25169
  var brandLogoPath = "/brand/maxy-monochrome.png";
25063
25170
  var brandIconPath = "/brand/maxy-monochrome.png";
25064
- if (BRAND_JSON_PATH && existsSync33(BRAND_JSON_PATH)) {
25171
+ if (BRAND_JSON_PATH && existsSync34(BRAND_JSON_PATH)) {
25065
25172
  try {
25066
25173
  const fullBrand = JSON.parse(readFileSync35(BRAND_JSON_PATH, "utf-8"));
25067
25174
  if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
@@ -25090,7 +25197,7 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
25090
25197
  var brandThemeColor = BRAND.defaultColors?.primary ?? "#000000";
25091
25198
  var brandBackgroundColor = BRAND.defaultColors?.background ?? "#ffffff";
25092
25199
  var brandAppIconsExist = [brandAppIcon192Path, brandAppIcon512Path, brandMaskableIconPath].every(
25093
- (p) => existsSync33(resolve33(process.cwd(), "public", p.replace(/^\//, "")))
25200
+ (p) => existsSync34(resolve33(process.cwd(), "public", p.replace(/^\//, "")))
25094
25201
  );
25095
25202
  var SW_SOURCE = (() => {
25096
25203
  try {
@@ -25103,7 +25210,7 @@ function readInstalledVersion() {
25103
25210
  try {
25104
25211
  if (!PLATFORM_ROOT8) return "unknown";
25105
25212
  const versionFile = join35(PLATFORM_ROOT8, "config", `.${BRAND.hostname}-version`);
25106
- if (!existsSync33(versionFile)) return "unknown";
25213
+ if (!existsSync34(versionFile)) return "unknown";
25107
25214
  const content = readFileSync35(versionFile, "utf-8").trim();
25108
25215
  return content || "unknown";
25109
25216
  } catch {
@@ -25167,7 +25274,7 @@ function loadBrandingCache(agentSlug) {
25167
25274
  const accountId = getDefaultAccountId();
25168
25275
  if (!accountId) return null;
25169
25276
  const cachePath = join35(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
25170
- if (!existsSync33(cachePath)) return null;
25277
+ if (!existsSync34(cachePath)) return null;
25171
25278
  return JSON.parse(readFileSync35(cachePath, "utf-8"));
25172
25279
  } catch {
25173
25280
  return null;
@@ -25394,7 +25501,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
25394
25501
  const reconcileScript = resolve33(reconcilePlatformRoot, "plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js");
25395
25502
  const RECONCILE_INTERVAL_MS = 12e4;
25396
25503
  const runReconcile = () => {
25397
- if (!existsSync33(reconcileScript)) return;
25504
+ if (!existsSync34(reconcileScript)) return;
25398
25505
  try {
25399
25506
  const child = spawn3(process.execPath, [reconcileScript], {
25400
25507
  env: { ...process.env, PLATFORM_ROOT: reconcilePlatformRoot },
@@ -25418,7 +25525,7 @@ startTimeEntryCensus(() => getSession());
25418
25525
  const auditLogDir = process.env.LOG_DIR ?? LOG_DIR;
25419
25526
  const CONNECTOR_AUDIT_INTERVAL_MS = 3e5;
25420
25527
  const runConnectorAudit = () => {
25421
- if (!existsSync33(auditScript)) return;
25528
+ if (!existsSync34(auditScript)) return;
25422
25529
  try {
25423
25530
  const child = spawn3(process.execPath, [auditScript], {
25424
25531
  env: { ...process.env, PLATFORM_ROOT: auditPlatformRoot, LOG_DIR: auditLogDir },
@@ -25528,7 +25635,7 @@ try {
25528
25635
  var ADMINUSER_RECONCILE_INTERVAL_MS = 60 * 60 * 1e3;
25529
25636
  async function runAdminUserReconcileTick() {
25530
25637
  try {
25531
- if (!existsSync33(USERS_FILE)) {
25638
+ if (!existsSync34(USERS_FILE)) {
25532
25639
  console.error("[adminuser-self-heal] skip reason=no-users-file");
25533
25640
  return;
25534
25641
  }
@@ -25561,7 +25668,7 @@ var adminUserReconcileTimer = setInterval(() => {
25561
25668
  if (typeof adminUserReconcileTimer.unref === "function") adminUserReconcileTimer.unref();
25562
25669
  var USERS_RECONCILE_INTERVAL_MS = 60 * 60 * 1e3;
25563
25670
  function countUsersRows() {
25564
- if (!existsSync33(USERS_FILE)) return 0;
25671
+ if (!existsSync34(USERS_FILE)) return 0;
25565
25672
  const raw = readFileSync35(USERS_FILE, "utf-8").trim();
25566
25673
  if (!raw) return 0;
25567
25674
  const users = JSON.parse(raw);