@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.
- package/package.json +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.js +19 -1
- package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/targets.d.ts +9 -0
- package/payload/platform/services/whatsapp-channel/dist/targets.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/targets.js +13 -0
- package/payload/platform/services/whatsapp-channel/dist/targets.js.map +1 -1
- package/payload/server/public/assets/{AdminLoginScreens-CcW3pAAZ.js → AdminLoginScreens-BM45lL5G.js} +1 -1
- package/payload/server/public/assets/{admin-Co3hOTNp.js → admin-B8Eb9Wbj.js} +1 -1
- package/payload/server/public/assets/{audio-attachment-mime-CwYrV1pg.js → audio-attachment-mime-Cx5d3Jls.js} +1 -1
- package/payload/server/public/assets/{chat-rxbGimwf.js → chat-LEEUAJOG.js} +1 -1
- package/payload/server/public/assets/data-CKojMQSz.js +1 -0
- package/payload/server/public/assets/{operator-Djv3MdcL.js → operator-OwY_sWTH.js} +1 -1
- package/payload/server/public/assets/{page-D1EAVC_9.js → page-B7nCws9K.js} +1 -1
- package/payload/server/public/assets/{page-Ci27UrJz.js → page-C3L6-Ufk.js} +1 -1
- package/payload/server/public/assets/{public-C6k-fPfx.js → public-CSLXSSy5.js} +1 -1
- package/payload/server/public/chat.html +3 -3
- package/payload/server/public/data.html +3 -3
- package/payload/server/public/index.html +4 -4
- package/payload/server/public/operator.html +4 -4
- package/payload/server/public/public.html +3 -3
- package/payload/server/server.js +684 -577
- package/payload/server/public/assets/data-D0YFYP1i.js +0 -1
package/payload/server/server.js
CHANGED
|
@@ -1383,9 +1383,9 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
1383
1383
|
};
|
|
1384
1384
|
|
|
1385
1385
|
// server/index.ts
|
|
1386
|
-
import { readFileSync as readFileSync35, existsSync as
|
|
1386
|
+
import { readFileSync as readFileSync35, existsSync as existsSync34, watchFile } from "fs";
|
|
1387
1387
|
import { spawn as spawn3 } from "child_process";
|
|
1388
|
-
import { createHash as
|
|
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
|
|
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
|
|
3785
|
-
import { writeFile, mkdir } from "fs/promises";
|
|
3786
|
-
import { join as
|
|
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(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/&/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
|
|
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
|
-
|
|
4240
|
+
buffer,
|
|
3878
4241
|
mimetype: mimetype ?? "application/octet-stream",
|
|
3879
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
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
|
-
|
|
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
|
|
5055
|
-
var
|
|
5056
|
-
var VNC_SCRIPT =
|
|
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
|
|
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 =
|
|
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="${
|
|
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(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/&/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
|
-
|
|
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
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
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
|
-
//
|
|
5604
|
-
function
|
|
5605
|
-
return
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
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
|
-
|
|
5653
|
-
|
|
5654
|
-
const
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
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
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
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
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
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
|
|
5743
|
-
|
|
5744
|
-
const
|
|
5745
|
-
const
|
|
5746
|
-
const
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
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 (!
|
|
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
|
|
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
|
|
8335
|
-
import { existsSync as
|
|
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
|
|
8425
|
+
return createHash3("sha256").update(pin).digest("hex");
|
|
8339
8426
|
}
|
|
8340
8427
|
function readUsersFile(usersFilePath) {
|
|
8341
|
-
if (!
|
|
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=${
|
|
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 || !
|
|
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
|
|
9218
|
-
|
|
9219
|
-
|
|
9220
|
-
|
|
9221
|
-
|
|
9222
|
-
|
|
9223
|
-
|
|
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,
|
|
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
|
|
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
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
|
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 (!
|
|
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
|
|
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 (
|
|
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 (!
|
|
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
|
|
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) || !
|
|
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 (!
|
|
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
|
|
11000
|
-
import { createHash as
|
|
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
|
|
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 (
|
|
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 (!
|
|
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 (
|
|
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 (
|
|
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 (!
|
|
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
|
|
11412
|
+
return createHash4("sha256").update(pin).digest("hex");
|
|
11314
11413
|
}
|
|
11315
11414
|
function readUsersFile2() {
|
|
11316
|
-
if (!
|
|
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 =
|
|
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 (
|
|
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 (
|
|
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
|
|
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 (!
|
|
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
|
|
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
|
|
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 (
|
|
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 (!
|
|
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
|
|
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 =
|
|
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 (
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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 (
|
|
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 (
|
|
13322
|
+
if (existsSync23(premiumRoot)) {
|
|
13224
13323
|
for (const bundle of readdirSync16(premiumRoot)) {
|
|
13225
13324
|
const bundlePlugins = join26(premiumRoot, bundle, "plugins");
|
|
13226
|
-
if (!
|
|
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 (!
|
|
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 (!
|
|
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 =
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
|
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 = !
|
|
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 = !
|
|
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
|
|
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
|
|
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
|
|
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 (!
|
|
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 (
|
|
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 (
|
|
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
|
|
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 (
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
|
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 =
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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
|
|
24051
|
-
import { existsSync as
|
|
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 (!
|
|
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 =
|
|
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 && !
|
|
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 &&
|
|
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 (!
|
|
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 =
|
|
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 (!
|
|
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 (!
|
|
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 &&
|
|
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) =>
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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);
|