@rubytech/create-realagent 1.0.673 → 1.0.676
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/plugins/docs/references/memory-guide.md +1 -1
- package/payload/platform/plugins/docs/references/platform.md +9 -5
- package/payload/platform/plugins/docs/references/troubleshooting.md +39 -27
- package/payload/platform/scripts/vnc.sh +115 -2
- package/payload/server/chunk-5YIXIF6C.js +726 -0
- package/payload/server/maxy-edge.js +420 -0
- package/payload/server/public/assets/admin-DQmUdTBa.js +352 -0
- package/payload/server/public/assets/{data-BffoLFjh.js → data-DVlvxbTt.js} +1 -1
- package/payload/server/public/assets/{file-CFwYpr7V.js → file-OY_hX2wu.js} +1 -1
- package/payload/server/public/assets/{graph-BTqqR6HN.js → graph-BDaM4Qer.js} +13 -13
- package/payload/server/public/assets/{house-DL5e4lPb.js → house-CgENfOCP.js} +1 -1
- package/payload/server/public/assets/jsx-runtime-Bu4vXoe7.css +1 -0
- package/payload/server/public/assets/{public-BJJlVuMu.js → public-Clp4VPwo.js} +1 -1
- package/payload/server/public/assets/{share-2-Bcn7Fo6t.js → share-2-RSIR3MmX.js} +1 -1
- package/payload/server/public/assets/{useVoiceRecorder-BqzlICMx.js → useVoiceRecorder-B0FI_hts.js} +1 -1
- package/payload/server/public/assets/{x-Dv8-a-IC.js → x-DKZ5NR3n.js} +1 -1
- package/payload/server/public/data.html +6 -6
- package/payload/server/public/graph.html +6 -6
- package/payload/server/public/index.html +7 -8
- package/payload/server/public/public.html +4 -4
- package/payload/server/server.js +660 -1518
- package/payload/server/public/assets/admin-CJCz3YFE.js +0 -362
- package/payload/server/public/assets/admin-kHJ-D0s7.css +0 -1
- package/payload/server/public/assets/jsx-runtime-DsAwO6-r.css +0 -1
- /package/payload/server/public/assets/{jsx-runtime-DYU20lw9.js → jsx-runtime-C_VUlXvu.js} +0 -0
package/payload/server/server.js
CHANGED
|
@@ -1,28 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
1
|
+
import {
|
|
2
|
+
BIN_DIR,
|
|
3
|
+
CLAUDE_CREDENTIALS_FILE,
|
|
4
|
+
LOG_DIR,
|
|
5
|
+
MAXY_DIR,
|
|
6
|
+
TELEGRAM_ADMIN_WEBHOOK_SECRET_FILE,
|
|
7
|
+
TELEGRAM_WEBHOOK_SECRET_FILE,
|
|
8
|
+
USERS_FILE,
|
|
9
|
+
__commonJS,
|
|
10
|
+
__toESM,
|
|
11
|
+
canAccessAdmin,
|
|
12
|
+
checkRateLimit,
|
|
13
|
+
clearRateLimit,
|
|
14
|
+
createRemoteSession,
|
|
15
|
+
hashPassword,
|
|
16
|
+
invalidateRemoteSession,
|
|
17
|
+
isPasswordValid,
|
|
18
|
+
isRemoteAuthConfigured,
|
|
19
|
+
newCorrId,
|
|
20
|
+
recordFailedAttempt,
|
|
21
|
+
renderLoginPage,
|
|
22
|
+
resolveClientIp,
|
|
23
|
+
sanitizeClientCorrId,
|
|
24
|
+
setRemotePassword,
|
|
25
|
+
validatePasswordStrength,
|
|
26
|
+
verifyPassword,
|
|
27
|
+
verifyRemotePassword,
|
|
28
|
+
vncLog
|
|
29
|
+
} from "./chunk-5YIXIF6C.js";
|
|
26
30
|
|
|
27
31
|
// ../lib/models/dist/index.js
|
|
28
32
|
var require_dist = __commonJS({
|
|
@@ -2526,7 +2530,7 @@ var responseViaResponseObject = async (res, outgoing, options = {}) => {
|
|
|
2526
2530
|
});
|
|
2527
2531
|
if (!chunk) {
|
|
2528
2532
|
if (i === 1) {
|
|
2529
|
-
await new Promise((
|
|
2533
|
+
await new Promise((resolve29) => setTimeout(resolve29));
|
|
2530
2534
|
maxReadCount = 3;
|
|
2531
2535
|
continue;
|
|
2532
2536
|
}
|
|
@@ -2892,690 +2896,40 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
2892
2896
|
};
|
|
2893
2897
|
|
|
2894
2898
|
// server/index.ts
|
|
2895
|
-
import { readFileSync as
|
|
2896
|
-
import {
|
|
2897
|
-
import {
|
|
2898
|
-
import { homedir as homedir5 } from "os";
|
|
2899
|
-
|
|
2900
|
-
// app/lib/vnc-logger.ts
|
|
2901
|
-
import { appendFileSync, mkdirSync } from "fs";
|
|
2902
|
-
import { resolve as resolve2 } from "path";
|
|
2899
|
+
import { readFileSync as readFileSync24, existsSync as existsSync23, watchFile } from "fs";
|
|
2900
|
+
import { resolve as resolve28, join as join12, basename as basename7 } from "path";
|
|
2901
|
+
import { homedir as homedir4 } from "os";
|
|
2903
2902
|
|
|
2904
|
-
//
|
|
2905
|
-
import {
|
|
2906
|
-
import { resolve, join as join2 } from "path";
|
|
2907
|
-
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
2908
|
-
var configDirName = ".maxy";
|
|
2909
|
-
var platformRoot = process.env.MAXY_PLATFORM_ROOT;
|
|
2910
|
-
if (platformRoot) {
|
|
2911
|
-
const brandPath = join2(platformRoot, "config", "brand.json");
|
|
2912
|
-
if (existsSync2(brandPath)) {
|
|
2913
|
-
try {
|
|
2914
|
-
const brand = JSON.parse(readFileSync(brandPath, "utf-8"));
|
|
2915
|
-
if (brand.configDir) configDirName = brand.configDir;
|
|
2916
|
-
} catch {
|
|
2917
|
-
}
|
|
2918
|
-
}
|
|
2919
|
-
}
|
|
2920
|
-
var MAXY_DIR = resolve(homedir(), configDirName);
|
|
2921
|
-
var PLATFORM_ROOT = process.env.MAXY_PLATFORM_ROOT ?? resolve(process.cwd(), "..");
|
|
2922
|
-
var USERS_FILE = resolve(PLATFORM_ROOT, "config", "users.json");
|
|
2923
|
-
var LOG_DIR = resolve(MAXY_DIR, "logs");
|
|
2924
|
-
var BIN_DIR = resolve(MAXY_DIR, "bin");
|
|
2925
|
-
var REMOTE_PASSWORD_FILE = resolve(MAXY_DIR, ".remote-password");
|
|
2926
|
-
var TELEGRAM_WEBHOOK_SECRET_FILE = resolve(MAXY_DIR, ".telegram-webhook-secret");
|
|
2927
|
-
var TELEGRAM_ADMIN_WEBHOOK_SECRET_FILE = resolve(MAXY_DIR, ".telegram-admin-webhook-secret");
|
|
2928
|
-
var CLAUDE_CREDENTIALS_FILE = resolve(homedir(), ".claude", ".credentials.json");
|
|
2903
|
+
// server/ws-proxy-terminal.ts
|
|
2904
|
+
import { createConnection } from "net";
|
|
2929
2905
|
|
|
2930
|
-
// app/lib/
|
|
2931
|
-
|
|
2906
|
+
// app/lib/terminal-logger.ts
|
|
2907
|
+
import { appendFileSync, mkdirSync } from "fs";
|
|
2908
|
+
import { resolve } from "path";
|
|
2909
|
+
var TERMINAL_LOG_FILE = resolve(LOG_DIR, "terminal.log");
|
|
2932
2910
|
try {
|
|
2933
2911
|
mkdirSync(LOG_DIR, { recursive: true });
|
|
2934
2912
|
} catch (err) {
|
|
2935
|
-
console.error(`[
|
|
2913
|
+
console.error(`[terminal-log-fail] mkdir ${LOG_DIR} failed: ${err.message}`);
|
|
2936
2914
|
}
|
|
2937
|
-
function
|
|
2915
|
+
function terminalLog(phase, fields = {}) {
|
|
2938
2916
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
2939
2917
|
const kv = Object.entries(fields).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(" ");
|
|
2940
|
-
const line = kv.length > 0 ? `[${ts}] [
|
|
2941
|
-
` : `[${ts}] [
|
|
2918
|
+
const line = kv.length > 0 ? `[${ts}] [terminal-${phase}] ${kv}
|
|
2919
|
+
` : `[${ts}] [terminal-${phase}]
|
|
2942
2920
|
`;
|
|
2943
2921
|
try {
|
|
2944
|
-
appendFileSync(
|
|
2922
|
+
appendFileSync(TERMINAL_LOG_FILE, line);
|
|
2945
2923
|
} catch (err) {
|
|
2946
|
-
console.error(`[
|
|
2947
|
-
}
|
|
2948
|
-
}
|
|
2949
|
-
var corrCounter = 0;
|
|
2950
|
-
function newCorrId() {
|
|
2951
|
-
corrCounter = corrCounter + 1 & 4294967295;
|
|
2952
|
-
const counterHex = corrCounter.toString(16).padStart(8, "0");
|
|
2953
|
-
const randomHex = Math.floor(Math.random() * 16777215).toString(16).padStart(6, "0");
|
|
2954
|
-
return `${counterHex}-${randomHex}`;
|
|
2955
|
-
}
|
|
2956
|
-
function sanitizeClientCorrId(raw2) {
|
|
2957
|
-
if (!raw2) return null;
|
|
2958
|
-
if (raw2.length > 32) return null;
|
|
2959
|
-
if (!/^[A-Za-z0-9_-]+$/.test(raw2)) return null;
|
|
2960
|
-
return raw2;
|
|
2961
|
-
}
|
|
2962
|
-
|
|
2963
|
-
// app/lib/remote-auth.ts
|
|
2964
|
-
import { scrypt, randomBytes, timingSafeEqual } from "crypto";
|
|
2965
|
-
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
2966
|
-
|
|
2967
|
-
// app/lib/password-strength.ts
|
|
2968
|
-
function validatePasswordStrength(password) {
|
|
2969
|
-
return [
|
|
2970
|
-
{ key: "length", label: "At least 8 characters", met: password.length >= 8 },
|
|
2971
|
-
{ key: "number", label: "Contains a number", met: /\d/.test(password) },
|
|
2972
|
-
{ key: "special", label: "Contains a special character", met: /[^A-Za-z0-9]/.test(password) },
|
|
2973
|
-
{ key: "whitespace", label: "No spaces", met: password.length > 0 && !/\s/.test(password) }
|
|
2974
|
-
];
|
|
2975
|
-
}
|
|
2976
|
-
function isPasswordValid(password) {
|
|
2977
|
-
return validatePasswordStrength(password).every((r) => r.met);
|
|
2978
|
-
}
|
|
2979
|
-
|
|
2980
|
-
// app/lib/remote-auth.ts
|
|
2981
|
-
var SCRYPT_N = 16384;
|
|
2982
|
-
var SCRYPT_R = 8;
|
|
2983
|
-
var SCRYPT_P = 1;
|
|
2984
|
-
var SCRYPT_KEYLEN = 64;
|
|
2985
|
-
function scryptAsync(password, salt) {
|
|
2986
|
-
return new Promise((resolve31, reject) => {
|
|
2987
|
-
scrypt(password, salt, SCRYPT_KEYLEN, { N: SCRYPT_N, r: SCRYPT_R, p: SCRYPT_P }, (err, key) => {
|
|
2988
|
-
if (err) reject(err);
|
|
2989
|
-
else resolve31(key);
|
|
2990
|
-
});
|
|
2991
|
-
});
|
|
2992
|
-
}
|
|
2993
|
-
async function hashPassword(password) {
|
|
2994
|
-
const salt = randomBytes(32);
|
|
2995
|
-
const hash = await scryptAsync(password, salt);
|
|
2996
|
-
return `${salt.toString("hex")}:${hash.toString("hex")}`;
|
|
2997
|
-
}
|
|
2998
|
-
async function verifyPassword(password, stored) {
|
|
2999
|
-
const colonIndex = stored.indexOf(":");
|
|
3000
|
-
if (colonIndex === -1) return false;
|
|
3001
|
-
const saltHex = stored.slice(0, colonIndex);
|
|
3002
|
-
const hashHex = stored.slice(colonIndex + 1);
|
|
3003
|
-
if (!saltHex || !hashHex) return false;
|
|
3004
|
-
try {
|
|
3005
|
-
const salt = Buffer.from(saltHex, "hex");
|
|
3006
|
-
const storedHash = Buffer.from(hashHex, "hex");
|
|
3007
|
-
if (storedHash.length !== SCRYPT_KEYLEN) return false;
|
|
3008
|
-
const computed = await scryptAsync(password, salt);
|
|
3009
|
-
return timingSafeEqual(computed, storedHash);
|
|
3010
|
-
} catch {
|
|
3011
|
-
return false;
|
|
3012
|
-
}
|
|
3013
|
-
}
|
|
3014
|
-
function isRemoteAuthConfigured() {
|
|
3015
|
-
if (!existsSync3(REMOTE_PASSWORD_FILE)) return false;
|
|
3016
|
-
const content = readFileSync2(REMOTE_PASSWORD_FILE, "utf-8").trim();
|
|
3017
|
-
return content.length > 0 && content.includes(":");
|
|
3018
|
-
}
|
|
3019
|
-
function readPasswordHash() {
|
|
3020
|
-
try {
|
|
3021
|
-
if (!existsSync3(REMOTE_PASSWORD_FILE)) return null;
|
|
3022
|
-
const content = readFileSync2(REMOTE_PASSWORD_FILE, "utf-8").trim();
|
|
3023
|
-
if (!content || !content.includes(":")) return null;
|
|
3024
|
-
return content;
|
|
3025
|
-
} catch {
|
|
3026
|
-
return null;
|
|
3027
|
-
}
|
|
3028
|
-
}
|
|
3029
|
-
async function setRemotePassword(password) {
|
|
3030
|
-
const hash = await hashPassword(password);
|
|
3031
|
-
writeFileSync(REMOTE_PASSWORD_FILE, hash, "utf-8");
|
|
3032
|
-
}
|
|
3033
|
-
async function verifyRemotePassword(password) {
|
|
3034
|
-
const stored = readPasswordHash();
|
|
3035
|
-
if (!stored) return false;
|
|
3036
|
-
return verifyPassword(password, stored);
|
|
3037
|
-
}
|
|
3038
|
-
var SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
3039
|
-
var remoteSessions = /* @__PURE__ */ new Map();
|
|
3040
|
-
function pruneExpiredSessions() {
|
|
3041
|
-
const now = Date.now();
|
|
3042
|
-
for (const [token, session] of remoteSessions) {
|
|
3043
|
-
if (now - session.createdAt > SESSION_TTL_MS) {
|
|
3044
|
-
remoteSessions.delete(token);
|
|
3045
|
-
}
|
|
3046
|
-
}
|
|
3047
|
-
}
|
|
3048
|
-
function createRemoteSession() {
|
|
3049
|
-
const token = randomBytes(32).toString("hex");
|
|
3050
|
-
remoteSessions.set(token, { createdAt: Date.now() });
|
|
3051
|
-
return token;
|
|
3052
|
-
}
|
|
3053
|
-
function validateRemoteSession(token) {
|
|
3054
|
-
if (!token) return false;
|
|
3055
|
-
pruneExpiredSessions();
|
|
3056
|
-
const session = remoteSessions.get(token);
|
|
3057
|
-
if (!session) return false;
|
|
3058
|
-
if (Date.now() - session.createdAt > SESSION_TTL_MS) {
|
|
3059
|
-
remoteSessions.delete(token);
|
|
3060
|
-
return false;
|
|
3061
|
-
}
|
|
3062
|
-
return true;
|
|
3063
|
-
}
|
|
3064
|
-
function invalidateRemoteSession(token) {
|
|
3065
|
-
remoteSessions.delete(token);
|
|
3066
|
-
}
|
|
3067
|
-
var MAX_ATTEMPTS = 5;
|
|
3068
|
-
var LOCKOUT_MS = 15 * 60 * 1e3;
|
|
3069
|
-
var rateLimitMap = /* @__PURE__ */ new Map();
|
|
3070
|
-
function checkRateLimit(ip) {
|
|
3071
|
-
const rec = rateLimitMap.get(ip);
|
|
3072
|
-
if (!rec) return null;
|
|
3073
|
-
if (rec.lockedUntil > Date.now()) {
|
|
3074
|
-
const remaining = Math.ceil((rec.lockedUntil - Date.now()) / 1e3);
|
|
3075
|
-
return `Too many attempts. Try again in ${remaining}s`;
|
|
3076
|
-
}
|
|
3077
|
-
if (rec.lockedUntil > 0) {
|
|
3078
|
-
rateLimitMap.delete(ip);
|
|
3079
|
-
}
|
|
3080
|
-
return null;
|
|
3081
|
-
}
|
|
3082
|
-
function recordFailedAttempt(ip) {
|
|
3083
|
-
const rec = rateLimitMap.get(ip) ?? { count: 0, lockedUntil: 0 };
|
|
3084
|
-
rec.count++;
|
|
3085
|
-
if (rec.count >= MAX_ATTEMPTS) {
|
|
3086
|
-
rec.lockedUntil = Date.now() + LOCKOUT_MS;
|
|
3087
|
-
rec.count = 0;
|
|
3088
|
-
}
|
|
3089
|
-
rateLimitMap.set(ip, rec);
|
|
3090
|
-
}
|
|
3091
|
-
function clearRateLimit(ip) {
|
|
3092
|
-
rateLimitMap.delete(ip);
|
|
3093
|
-
}
|
|
3094
|
-
setInterval(() => {
|
|
3095
|
-
const now = Date.now();
|
|
3096
|
-
for (const [ip, rec] of rateLimitMap) {
|
|
3097
|
-
if (rec.lockedUntil > 0 && rec.lockedUntil <= now) {
|
|
3098
|
-
rateLimitMap.delete(ip);
|
|
3099
|
-
}
|
|
3100
|
-
}
|
|
3101
|
-
}, 15 * 60 * 1e3).unref();
|
|
3102
|
-
function normalizeIp(ip) {
|
|
3103
|
-
if (!ip) return void 0;
|
|
3104
|
-
const trimmed = ip.trim();
|
|
3105
|
-
if (trimmed.startsWith("::ffff:")) return trimmed.slice(7);
|
|
3106
|
-
return trimmed;
|
|
3107
|
-
}
|
|
3108
|
-
function isLoopback(ip) {
|
|
3109
|
-
return ip === "127.0.0.1" || ip.startsWith("127.") || ip === "::1";
|
|
3110
|
-
}
|
|
3111
|
-
function isPrivateIp(ip) {
|
|
3112
|
-
const parts = ip.split(".");
|
|
3113
|
-
if (parts.length !== 4) return false;
|
|
3114
|
-
const a = parseInt(parts[0], 10);
|
|
3115
|
-
const b = parseInt(parts[1], 10);
|
|
3116
|
-
if (Number.isNaN(a) || Number.isNaN(b)) return false;
|
|
3117
|
-
if (a === 10) return true;
|
|
3118
|
-
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
3119
|
-
if (a === 192 && b === 168) return true;
|
|
3120
|
-
if (a === 169 && b === 254) return true;
|
|
3121
|
-
return false;
|
|
3122
|
-
}
|
|
3123
|
-
function resolveClientIp(remoteAddress, xForwardedFor) {
|
|
3124
|
-
const remote = normalizeIp(remoteAddress);
|
|
3125
|
-
if (!remote) return void 0;
|
|
3126
|
-
if (isLoopback(remote) && xForwardedFor) {
|
|
3127
|
-
const firstIp = normalizeIp(xForwardedFor.split(",")[0]?.trim());
|
|
3128
|
-
if (firstIp) {
|
|
3129
|
-
if (isLoopback(firstIp) || isPrivateIp(firstIp)) return "loopback";
|
|
3130
|
-
return firstIp;
|
|
3131
|
-
}
|
|
3132
|
-
}
|
|
3133
|
-
return remote;
|
|
3134
|
-
}
|
|
3135
|
-
function isExternalIp(ip) {
|
|
3136
|
-
if (!ip) return false;
|
|
3137
|
-
const normalized = normalizeIp(ip);
|
|
3138
|
-
if (!normalized) return false;
|
|
3139
|
-
if (isLoopback(normalized)) return false;
|
|
3140
|
-
if (isPrivateIp(normalized)) return false;
|
|
3141
|
-
return true;
|
|
3142
|
-
}
|
|
3143
|
-
function renderLoginPage(opts) {
|
|
3144
|
-
const error = opts?.error ?? "";
|
|
3145
|
-
const lockout = opts?.lockoutSeconds ?? 0;
|
|
3146
|
-
const redirect = opts?.redirect ?? "/";
|
|
3147
|
-
const mode = opts?.mode ?? "login";
|
|
3148
|
-
const changeError = opts?.changeError ?? "";
|
|
3149
|
-
const success = opts?.success ?? "";
|
|
3150
|
-
const setupError = opts?.setupError ?? "";
|
|
3151
|
-
const primaryColor = opts?.primaryColor ?? "#7C8C72";
|
|
3152
|
-
const primaryHoverColor = opts?.primaryHoverColor ?? "#6A7A62";
|
|
3153
|
-
const primarySubtle = opts?.primarySubtle ?? "rgba(124,140,114,0.08)";
|
|
3154
|
-
const productName = opts?.productName ?? "Maxy";
|
|
3155
|
-
const logoPath = opts?.logoPath ?? "/brand/maxy-monochrome.png";
|
|
3156
|
-
const faviconPath = opts?.faviconPath ?? "/favicon.ico";
|
|
3157
|
-
const displayFont = opts?.displayFont ?? "'Cormorant', Georgia, serif";
|
|
3158
|
-
const bodyFont = opts?.bodyFont ?? "'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
|
|
3159
|
-
const logoContainsName = opts?.logoContainsName ?? false;
|
|
3160
|
-
const errorHtml = error ? `<p class="msg msg--error">${escapeHtml(error)}</p>` : "";
|
|
3161
|
-
const changeErrorHtml = changeError ? `<p class="msg msg--error">${escapeHtml(changeError)}</p>` : "";
|
|
3162
|
-
const successHtml = success ? `<p class="msg msg--success">${escapeHtml(success)}</p>` : "";
|
|
3163
|
-
const lockoutHtml = lockout > 0 ? `<p class="msg msg--error">Too many attempts. Try again in <span id="countdown">${lockout}</span>s</p>
|
|
3164
|
-
<script>
|
|
3165
|
-
(function() {
|
|
3166
|
-
var s = ${lockout};
|
|
3167
|
-
var el = document.getElementById('countdown');
|
|
3168
|
-
var iv = setInterval(function() { s--; el.textContent = s; if (s <= 0) { clearInterval(iv); location.reload(); } }, 1000);
|
|
3169
|
-
})();
|
|
3170
|
-
</script>` : "";
|
|
3171
|
-
const setupErrorHtml = setupError ? `<p class="msg msg--error">${escapeHtml(setupError)}</p>` : "";
|
|
3172
|
-
const formDisabled = lockout > 0 ? "disabled" : "";
|
|
3173
|
-
const loginDisplay = mode === "login" ? "block" : "none";
|
|
3174
|
-
const changeDisplay = mode === "change" ? "block" : "none";
|
|
3175
|
-
const setupDisplay = mode === "setup" ? "block" : "none";
|
|
3176
|
-
const successDisplay = mode === "success" ? "block" : "none";
|
|
3177
|
-
const subtitleText = mode === "setup" ? "Set your remote password" : "Remote access";
|
|
3178
|
-
const displayFontName = displayFont.match(/^'([^']+)'/)?.[1] ?? "";
|
|
3179
|
-
const bodyFontName = bodyFont.match(/^'([^']+)'/)?.[1] ?? "";
|
|
3180
|
-
const googleFontsLink = displayFontName || bodyFontName ? [
|
|
3181
|
-
'<link rel="preconnect" href="https://fonts.googleapis.com">',
|
|
3182
|
-
'<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>',
|
|
3183
|
-
'<link href="https://fonts.googleapis.com/css2?' + [
|
|
3184
|
-
displayFontName ? `family=${displayFontName.replace(/ /g, "+")}:wght@300;400` : "",
|
|
3185
|
-
bodyFontName ? `family=${bodyFontName.replace(/ /g, "+")}:wght@400;500` : ""
|
|
3186
|
-
].filter(Boolean).join("&") + '&display=swap" rel="stylesheet">'
|
|
3187
|
-
].join("\n ") : "";
|
|
3188
|
-
return `<!DOCTYPE html>
|
|
3189
|
-
<html lang="en">
|
|
3190
|
-
<head>
|
|
3191
|
-
<meta charset="utf-8">
|
|
3192
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
3193
|
-
<title>Sign in \u2014 ${escapeHtml(productName)}</title>
|
|
3194
|
-
<link rel="icon" href="${escapeHtml(faviconPath)}">
|
|
3195
|
-
${googleFontsLink}
|
|
3196
|
-
<style>
|
|
3197
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
3198
|
-
body {
|
|
3199
|
-
font-family: ${bodyFont};
|
|
3200
|
-
background: #FAFAF8;
|
|
3201
|
-
color: #1A1A1A;
|
|
3202
|
-
min-height: 100vh;
|
|
3203
|
-
display: flex;
|
|
3204
|
-
align-items: center;
|
|
3205
|
-
justify-content: center;
|
|
3206
|
-
}
|
|
3207
|
-
.page {
|
|
3208
|
-
display: flex;
|
|
3209
|
-
flex-direction: column;
|
|
3210
|
-
align-items: center;
|
|
3211
|
-
gap: 24px;
|
|
3212
|
-
max-width: 420px;
|
|
3213
|
-
width: 100%;
|
|
3214
|
-
padding: 40px 20px;
|
|
3215
|
-
}
|
|
3216
|
-
.logo-img {
|
|
3217
|
-
width: 110px;
|
|
3218
|
-
height: 110px;
|
|
3219
|
-
margin: -11px;
|
|
3220
|
-
object-fit: contain;
|
|
3221
|
-
}
|
|
3222
|
-
.title {
|
|
3223
|
-
font-family: ${displayFont};
|
|
3224
|
-
font-weight: 300;
|
|
3225
|
-
font-size: 28px;
|
|
3226
|
-
color: #1A1A1A;
|
|
3227
|
-
text-align: center;
|
|
3228
|
-
letter-spacing: -0.01em;
|
|
3229
|
-
line-height: 1.3;
|
|
3230
|
-
}
|
|
3231
|
-
.subtitle {
|
|
3232
|
-
font-size: 15px;
|
|
3233
|
-
color: #6B6B6B;
|
|
3234
|
-
text-align: center;
|
|
3235
|
-
margin-top: -8px;
|
|
3236
|
-
}
|
|
3237
|
-
.form-wrap {
|
|
3238
|
-
width: 100%;
|
|
3239
|
-
max-width: 300px;
|
|
3240
|
-
}
|
|
3241
|
-
.field { margin-bottom: 12px; }
|
|
3242
|
-
.field-label {
|
|
3243
|
-
display: block;
|
|
3244
|
-
font-size: 13px;
|
|
3245
|
-
font-weight: 500;
|
|
3246
|
-
color: #6B6B6B;
|
|
3247
|
-
margin-bottom: 6px;
|
|
3248
|
-
}
|
|
3249
|
-
.field-input {
|
|
3250
|
-
width: 100%;
|
|
3251
|
-
padding: 10px 12px;
|
|
3252
|
-
border: 1px solid rgba(0,0,0,0.1);
|
|
3253
|
-
border-radius: 8px;
|
|
3254
|
-
font-size: 15px;
|
|
3255
|
-
font-family: inherit;
|
|
3256
|
-
outline: none;
|
|
3257
|
-
background: #fff;
|
|
3258
|
-
transition: border-color 0.15s, box-shadow 0.15s;
|
|
3259
|
-
}
|
|
3260
|
-
.field-input:focus {
|
|
3261
|
-
border-color: ${primaryColor};
|
|
3262
|
-
box-shadow: 0 0 0 3px ${primarySubtle};
|
|
3263
|
-
}
|
|
3264
|
-
.options {
|
|
3265
|
-
display: flex;
|
|
3266
|
-
align-items: center;
|
|
3267
|
-
justify-content: space-between;
|
|
3268
|
-
gap: 12px;
|
|
3269
|
-
margin-top: 8px;
|
|
3270
|
-
}
|
|
3271
|
-
|
|
3272
|
-
/* Checkbox \u2014 matches Maxy shared checkbox (asterisk-in-square) */
|
|
3273
|
-
.check {
|
|
3274
|
-
position: relative;
|
|
3275
|
-
display: flex;
|
|
3276
|
-
align-items: center;
|
|
3277
|
-
gap: 8px;
|
|
3278
|
-
cursor: pointer;
|
|
3279
|
-
user-select: none;
|
|
3280
|
-
}
|
|
3281
|
-
.check input { position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none; }
|
|
3282
|
-
.check-box {
|
|
3283
|
-
width: 14px;
|
|
3284
|
-
height: 14px;
|
|
3285
|
-
border: 1px solid rgba(0,0,0,0.1);
|
|
3286
|
-
border-radius: 2px;
|
|
3287
|
-
display: flex;
|
|
3288
|
-
align-items: center;
|
|
3289
|
-
justify-content: center;
|
|
3290
|
-
font-size: 10px;
|
|
3291
|
-
color: transparent;
|
|
3292
|
-
transition: border-color 0.15s, color 0.15s;
|
|
3293
|
-
flex-shrink: 0;
|
|
3294
|
-
}
|
|
3295
|
-
.check input:checked + .check-box {
|
|
3296
|
-
border-color: ${primaryColor};
|
|
3297
|
-
color: ${primaryColor};
|
|
3298
|
-
}
|
|
3299
|
-
.check-label {
|
|
3300
|
-
font-size: 13px;
|
|
3301
|
-
color: #6B6B6B;
|
|
3302
|
-
}
|
|
3303
|
-
|
|
3304
|
-
/* Ghost button \u2014 matches Maxy ghost variant */
|
|
3305
|
-
.ghost {
|
|
3306
|
-
background: none;
|
|
3307
|
-
border: none;
|
|
3308
|
-
font-family: inherit;
|
|
3309
|
-
font-size: 13px;
|
|
3310
|
-
color: #6B6B6B;
|
|
3311
|
-
cursor: pointer;
|
|
3312
|
-
padding: 4px 0;
|
|
3313
|
-
transition: color 0.15s;
|
|
3314
|
-
}
|
|
3315
|
-
.ghost:hover { color: #1A1A1A; }
|
|
3316
|
-
|
|
3317
|
-
/* Primary button */
|
|
3318
|
-
.btn {
|
|
3319
|
-
width: 100%;
|
|
3320
|
-
padding: 12px;
|
|
3321
|
-
margin-top: 16px;
|
|
3322
|
-
background: ${primaryColor};
|
|
3323
|
-
color: #fff;
|
|
3324
|
-
border: none;
|
|
3325
|
-
border-radius: 10px;
|
|
3326
|
-
font-size: 15px;
|
|
3327
|
-
font-weight: 500;
|
|
3328
|
-
font-family: inherit;
|
|
3329
|
-
cursor: pointer;
|
|
3330
|
-
transition: background 0.15s;
|
|
3331
|
-
}
|
|
3332
|
-
.btn:hover:not(:disabled) { background: ${primaryHoverColor}; }
|
|
3333
|
-
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
3334
|
-
|
|
3335
|
-
/* Messages */
|
|
3336
|
-
.msg {
|
|
3337
|
-
font-size: 13px;
|
|
3338
|
-
text-align: center;
|
|
3339
|
-
margin-top: 8px;
|
|
3340
|
-
}
|
|
3341
|
-
.msg--error { color: #c44; }
|
|
3342
|
-
.msg--success { color: ${primaryColor}; }
|
|
3343
|
-
|
|
3344
|
-
/* Strength checklist (setup mode) */
|
|
3345
|
-
.strength-checklist { margin: 8px 0; }
|
|
3346
|
-
.strength-item {
|
|
3347
|
-
font-size: 13px;
|
|
3348
|
-
color: #6B6B6B;
|
|
3349
|
-
padding: 2px 0;
|
|
3350
|
-
transition: color 0.15s;
|
|
3351
|
-
}
|
|
3352
|
-
.strength-icon {
|
|
3353
|
-
display: inline-block;
|
|
3354
|
-
width: 16px;
|
|
3355
|
-
text-align: center;
|
|
3356
|
-
}
|
|
3357
|
-
|
|
3358
|
-
@media (max-width: 440px) {
|
|
3359
|
-
.page { padding: 24px 16px; }
|
|
3360
|
-
}
|
|
3361
|
-
</style>
|
|
3362
|
-
</head>
|
|
3363
|
-
<body>
|
|
3364
|
-
<div class="page">
|
|
3365
|
-
<img src="${escapeHtml(logoPath)}" alt="${escapeHtml(productName)}" class="logo-img">
|
|
3366
|
-
<h1 class="title"${logoContainsName ? ' style="display:none"' : ""}>${escapeHtml(productName)}</h1>
|
|
3367
|
-
<p class="subtitle">${escapeHtml(subtitleText)}</p>
|
|
3368
|
-
|
|
3369
|
-
${successHtml}
|
|
3370
|
-
|
|
3371
|
-
<!-- Login -->
|
|
3372
|
-
<div id="login-section" class="form-wrap" style="display:${loginDisplay}">
|
|
3373
|
-
<form method="POST" action="/__remote-auth/login">
|
|
3374
|
-
<input type="hidden" name="redirect" value="${escapeHtml(redirect)}">
|
|
3375
|
-
<div class="field">
|
|
3376
|
-
<label class="field-label" for="password">Password</label>
|
|
3377
|
-
<input class="field-input" type="password" id="password" name="password" required autofocus ${formDisabled}>
|
|
3378
|
-
</div>
|
|
3379
|
-
<div class="options">
|
|
3380
|
-
<label class="check">
|
|
3381
|
-
<input type="checkbox" id="show-login-pw">
|
|
3382
|
-
<span class="check-box">\u2731</span>
|
|
3383
|
-
<span class="check-label">Show password</span>
|
|
3384
|
-
</label>
|
|
3385
|
-
<button type="button" class="ghost" onclick="toggleMode('change')">Change password</button>
|
|
3386
|
-
</div>
|
|
3387
|
-
<button type="submit" class="btn" ${formDisabled}>Sign in</button>
|
|
3388
|
-
</form>
|
|
3389
|
-
${errorHtml}
|
|
3390
|
-
${lockoutHtml}
|
|
3391
|
-
</div>
|
|
3392
|
-
|
|
3393
|
-
<!-- Change password -->
|
|
3394
|
-
<div id="change-section" class="form-wrap" style="display:${changeDisplay}">
|
|
3395
|
-
<form method="POST" action="/__remote-auth/change-password">
|
|
3396
|
-
<input type="hidden" name="redirect" value="${escapeHtml(redirect)}">
|
|
3397
|
-
<div class="field">
|
|
3398
|
-
<label class="field-label" for="current_password">Current password</label>
|
|
3399
|
-
<input class="field-input" type="password" id="current_password" name="current_password" required ${formDisabled}>
|
|
3400
|
-
</div>
|
|
3401
|
-
<div class="field">
|
|
3402
|
-
<label class="field-label" for="new_password">New password</label>
|
|
3403
|
-
<input class="field-input" type="password" id="new_password" name="new_password" required ${formDisabled}>
|
|
3404
|
-
</div>
|
|
3405
|
-
<div class="field">
|
|
3406
|
-
<label class="field-label" for="confirm_password">Confirm new password</label>
|
|
3407
|
-
<input class="field-input" type="password" id="confirm_password" name="confirm_password" required ${formDisabled}>
|
|
3408
|
-
</div>
|
|
3409
|
-
<div class="options">
|
|
3410
|
-
<label class="check">
|
|
3411
|
-
<input type="checkbox" id="show-change-pw">
|
|
3412
|
-
<span class="check-box">\u2731</span>
|
|
3413
|
-
<span class="check-label">Show passwords</span>
|
|
3414
|
-
</label>
|
|
3415
|
-
<button type="button" class="ghost" onclick="toggleMode('login')">Back to sign in</button>
|
|
3416
|
-
</div>
|
|
3417
|
-
<button type="submit" class="btn" ${formDisabled}>Change password</button>
|
|
3418
|
-
</form>
|
|
3419
|
-
${changeErrorHtml}
|
|
3420
|
-
</div>
|
|
3421
|
-
|
|
3422
|
-
<!-- Setup (initial password) -->
|
|
3423
|
-
<div id="setup-section" class="form-wrap" style="display:${setupDisplay}">
|
|
3424
|
-
<form method="POST" action="/__remote-auth/set-initial-password">
|
|
3425
|
-
<div class="field">
|
|
3426
|
-
<label class="field-label" for="setup_password">Password</label>
|
|
3427
|
-
<input class="field-input" type="password" id="setup_password" name="password" required autofocus>
|
|
3428
|
-
</div>
|
|
3429
|
-
<div class="field">
|
|
3430
|
-
<label class="field-label" for="setup_confirm">Confirm password</label>
|
|
3431
|
-
<input class="field-input" type="password" id="setup_confirm" name="confirm_password" required>
|
|
3432
|
-
</div>
|
|
3433
|
-
<div id="strength-checklist" class="strength-checklist">
|
|
3434
|
-
<div class="strength-item" id="req-length"><span class="strength-icon">\u25CB</span> At least 8 characters</div>
|
|
3435
|
-
<div class="strength-item" id="req-number"><span class="strength-icon">\u25CB</span> Contains a number</div>
|
|
3436
|
-
<div class="strength-item" id="req-special"><span class="strength-icon">\u25CB</span> Contains a special character</div>
|
|
3437
|
-
<div class="strength-item" id="req-spaces"><span class="strength-icon">\u25CB</span> No spaces</div>
|
|
3438
|
-
</div>
|
|
3439
|
-
<div class="options">
|
|
3440
|
-
<label class="check">
|
|
3441
|
-
<input type="checkbox" id="show-setup-pw">
|
|
3442
|
-
<span class="check-box">\u2731</span>
|
|
3443
|
-
<span class="check-label">Show passwords</span>
|
|
3444
|
-
</label>
|
|
3445
|
-
</div>
|
|
3446
|
-
<button type="submit" class="btn" id="setup-submit" disabled>Set password</button>
|
|
3447
|
-
</form>
|
|
3448
|
-
${setupErrorHtml}
|
|
3449
|
-
</div>
|
|
3450
|
-
|
|
3451
|
-
<!-- Success (after initial password set) -->
|
|
3452
|
-
<div id="success-section" class="form-wrap" style="display:${successDisplay}">
|
|
3453
|
-
<p class="msg msg--success" style="font-size:15px;margin-bottom:4px;">\u2713 Password set</p>
|
|
3454
|
-
<p style="font-size:14px;color:#6B6B6B;text-align:center;">You can close this tab and return to the onboarding tab.</p>
|
|
3455
|
-
<script>
|
|
3456
|
-
(function() {
|
|
3457
|
-
try {
|
|
3458
|
-
var ch = new BroadcastChannel('platform-onboarding');
|
|
3459
|
-
setTimeout(function() {
|
|
3460
|
-
ch.postMessage({ type: 'remote-password-set' });
|
|
3461
|
-
ch.close();
|
|
3462
|
-
}, 2500);
|
|
3463
|
-
} catch(e) {}
|
|
3464
|
-
})();
|
|
3465
|
-
</script>
|
|
3466
|
-
</div>
|
|
3467
|
-
</div>
|
|
3468
|
-
|
|
3469
|
-
<script>
|
|
3470
|
-
/* Toggle between login and change-password modes */
|
|
3471
|
-
function toggleMode(mode) {
|
|
3472
|
-
document.getElementById('login-section').style.display = mode === 'login' ? 'block' : 'none';
|
|
3473
|
-
document.getElementById('change-section').style.display = mode === 'change' ? 'block' : 'none';
|
|
3474
|
-
/* Focus first input in the active section */
|
|
3475
|
-
var id = mode === 'login' ? 'password' : 'current_password';
|
|
3476
|
-
var el = document.getElementById(id);
|
|
3477
|
-
if (el) el.focus();
|
|
3478
|
-
}
|
|
3479
|
-
|
|
3480
|
-
/* Show/hide password toggles */
|
|
3481
|
-
function bindShowHide(checkboxId, inputIds) {
|
|
3482
|
-
var cb = document.getElementById(checkboxId);
|
|
3483
|
-
if (!cb) return;
|
|
3484
|
-
cb.addEventListener('change', function() {
|
|
3485
|
-
var type = cb.checked ? 'text' : 'password';
|
|
3486
|
-
inputIds.forEach(function(id) {
|
|
3487
|
-
var el = document.getElementById(id);
|
|
3488
|
-
if (el) el.type = type;
|
|
3489
|
-
});
|
|
3490
|
-
});
|
|
3491
|
-
}
|
|
3492
|
-
bindShowHide('show-login-pw', ['password']);
|
|
3493
|
-
bindShowHide('show-change-pw', ['current_password', 'new_password', 'confirm_password']);
|
|
3494
|
-
bindShowHide('show-setup-pw', ['setup_password', 'setup_confirm']);
|
|
3495
|
-
|
|
3496
|
-
/* Explicit Enter-to-submit for the login password field */
|
|
3497
|
-
var loginPw = document.getElementById('password');
|
|
3498
|
-
if (loginPw) {
|
|
3499
|
-
loginPw.addEventListener('keydown', function(e) {
|
|
3500
|
-
if (e.key === 'Enter') {
|
|
3501
|
-
e.preventDefault();
|
|
3502
|
-
this.closest('form').requestSubmit();
|
|
3503
|
-
}
|
|
3504
|
-
});
|
|
3505
|
-
}
|
|
3506
|
-
|
|
3507
|
-
/* Live password strength validation (setup mode) */
|
|
3508
|
-
(function() {
|
|
3509
|
-
var pw = document.getElementById('setup_password');
|
|
3510
|
-
var confirm = document.getElementById('setup_confirm');
|
|
3511
|
-
var btn = document.getElementById('setup-submit');
|
|
3512
|
-
if (!pw || !confirm || !btn) return;
|
|
3513
|
-
|
|
3514
|
-
var rules = [
|
|
3515
|
-
{ id: 'req-length', test: function(p) { return p.length >= 8; } },
|
|
3516
|
-
{ id: 'req-number', test: function(p) { return /\\d/.test(p); } },
|
|
3517
|
-
{ id: 'req-special', test: function(p) { return /[^A-Za-z0-9]/.test(p); } },
|
|
3518
|
-
{ id: 'req-spaces', test: function(p) { return p.length > 0 && !/\\s/.test(p); } }
|
|
3519
|
-
];
|
|
3520
|
-
|
|
3521
|
-
function check() {
|
|
3522
|
-
var p = pw.value;
|
|
3523
|
-
var allMet = true;
|
|
3524
|
-
rules.forEach(function(r) {
|
|
3525
|
-
var el = document.getElementById(r.id);
|
|
3526
|
-
var met = r.test(p);
|
|
3527
|
-
if (!met) allMet = false;
|
|
3528
|
-
el.querySelector('.strength-icon').textContent = met ? '\\u2713' : '\\u25CB';
|
|
3529
|
-
el.style.color = met ? '${primaryColor}' : '#6B6B6B';
|
|
3530
|
-
});
|
|
3531
|
-
var match = p.length > 0 && p === confirm.value;
|
|
3532
|
-
btn.disabled = !(allMet && match);
|
|
3533
|
-
}
|
|
3534
|
-
|
|
3535
|
-
pw.addEventListener('input', check);
|
|
3536
|
-
confirm.addEventListener('input', check);
|
|
3537
|
-
})();
|
|
3538
|
-
</script>
|
|
3539
|
-
</body>
|
|
3540
|
-
</html>`;
|
|
3541
|
-
}
|
|
3542
|
-
function escapeHtml(str) {
|
|
3543
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
3544
|
-
}
|
|
3545
|
-
|
|
3546
|
-
// app/lib/auth-gate.ts
|
|
3547
|
-
function canAccessAdmin(input) {
|
|
3548
|
-
if (input.isPublicHost(input.host)) {
|
|
3549
|
-
return { allow: false, reason: "public-host" };
|
|
3550
|
-
}
|
|
3551
|
-
const clientIp = resolveClientIp(input.remoteAddress, input.xForwardedFor);
|
|
3552
|
-
if (!isExternalIp(clientIp)) {
|
|
3553
|
-
return { allow: true };
|
|
3554
|
-
}
|
|
3555
|
-
if (!isRemoteAuthConfigured()) {
|
|
3556
|
-
return { allow: false, reason: "remote-unconfigured" };
|
|
3557
|
-
}
|
|
3558
|
-
const token = parseCookieValue(input.cookieHeader, "__remote_session");
|
|
3559
|
-
if (token && validateRemoteSession(token)) {
|
|
3560
|
-
return { allow: true };
|
|
3561
|
-
}
|
|
3562
|
-
return { allow: false, reason: "unauthorized" };
|
|
3563
|
-
}
|
|
3564
|
-
function parseCookieValue(cookieHeader, name) {
|
|
3565
|
-
if (!cookieHeader) return null;
|
|
3566
|
-
const match2 = cookieHeader.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
|
|
3567
|
-
if (!match2) return null;
|
|
3568
|
-
try {
|
|
3569
|
-
return decodeURIComponent(match2[1]);
|
|
3570
|
-
} catch {
|
|
3571
|
-
return null;
|
|
2924
|
+
console.error(`[terminal-log-fail] ${err.message} \u2014 dropped: ${line.slice(0, 300).trim()}`);
|
|
3572
2925
|
}
|
|
3573
2926
|
}
|
|
3574
2927
|
|
|
3575
|
-
// server/ws-proxy.ts
|
|
3576
|
-
|
|
3577
|
-
var WS_PATH = "/websockify";
|
|
2928
|
+
// server/ws-proxy-terminal.ts
|
|
2929
|
+
var WS_PATH = "/admin/terminal/ws";
|
|
3578
2930
|
var UPSTREAM_TIMEOUT_MS = 5e3;
|
|
2931
|
+
var FLOW_TICK_MS = 5e3;
|
|
2932
|
+
var FLOW_IDLE_EMIT_MS = 3e4;
|
|
3579
2933
|
var HOP_BY_HOP = /* @__PURE__ */ new Set([
|
|
3580
2934
|
"connection",
|
|
3581
2935
|
"keep-alive",
|
|
@@ -3586,9 +2940,9 @@ var HOP_BY_HOP = /* @__PURE__ */ new Set([
|
|
|
3586
2940
|
"transfer-encoding",
|
|
3587
2941
|
"upgrade"
|
|
3588
2942
|
]);
|
|
3589
|
-
function
|
|
2943
|
+
function attachTerminalWsProxy(server, opts) {
|
|
3590
2944
|
const upstreamHost = opts.upstreamHost ?? "127.0.0.1";
|
|
3591
|
-
const upstreamPort = opts.upstreamPort ??
|
|
2945
|
+
const upstreamPort = opts.upstreamPort ?? 7681;
|
|
3592
2946
|
server.on("upgrade", (req, clientSocket, head) => {
|
|
3593
2947
|
try {
|
|
3594
2948
|
handleUpgrade(req, clientSocket, head, {
|
|
@@ -3597,7 +2951,7 @@ function attachVncWsProxy(server, opts) {
|
|
|
3597
2951
|
upstreamPort
|
|
3598
2952
|
});
|
|
3599
2953
|
} catch (err) {
|
|
3600
|
-
|
|
2954
|
+
terminalLog("ws-upgrade", {
|
|
3601
2955
|
decision: "rejected",
|
|
3602
2956
|
reason: "handler-exception",
|
|
3603
2957
|
err: err.message
|
|
@@ -3630,7 +2984,7 @@ function handleUpgrade(req, clientSocket, head, opts) {
|
|
|
3630
2984
|
});
|
|
3631
2985
|
if (!decision.allow) {
|
|
3632
2986
|
const status = decision.reason === "public-host" ? 404 : 401;
|
|
3633
|
-
|
|
2987
|
+
terminalLog("ws-upgrade", {
|
|
3634
2988
|
corrId,
|
|
3635
2989
|
clientCorrId: clientCorrId ?? null,
|
|
3636
2990
|
decision: "rejected",
|
|
@@ -3645,7 +2999,7 @@ function handleUpgrade(req, clientSocket, head, opts) {
|
|
|
3645
2999
|
}
|
|
3646
3000
|
const originHost = parseOriginHost(originHeader);
|
|
3647
3001
|
if (!originHost) {
|
|
3648
|
-
|
|
3002
|
+
terminalLog("ws-upgrade", {
|
|
3649
3003
|
corrId,
|
|
3650
3004
|
clientCorrId: clientCorrId ?? null,
|
|
3651
3005
|
decision: "rejected",
|
|
@@ -3658,7 +3012,7 @@ function handleUpgrade(req, clientSocket, head, opts) {
|
|
|
3658
3012
|
return;
|
|
3659
3013
|
}
|
|
3660
3014
|
if (originHost !== hostHeader) {
|
|
3661
|
-
|
|
3015
|
+
terminalLog("ws-upgrade", {
|
|
3662
3016
|
corrId,
|
|
3663
3017
|
clientCorrId: clientCorrId ?? null,
|
|
3664
3018
|
decision: "rejected",
|
|
@@ -3671,7 +3025,7 @@ function handleUpgrade(req, clientSocket, head, opts) {
|
|
|
3671
3025
|
return;
|
|
3672
3026
|
}
|
|
3673
3027
|
if (opts.isPublicHost(originHost)) {
|
|
3674
|
-
|
|
3028
|
+
terminalLog("ws-upgrade", {
|
|
3675
3029
|
corrId,
|
|
3676
3030
|
clientCorrId: clientCorrId ?? null,
|
|
3677
3031
|
decision: "rejected",
|
|
@@ -3683,7 +3037,7 @@ function handleUpgrade(req, clientSocket, head, opts) {
|
|
|
3683
3037
|
writeStatusAndDestroy(clientSocket, 403, "Forbidden");
|
|
3684
3038
|
return;
|
|
3685
3039
|
}
|
|
3686
|
-
|
|
3040
|
+
terminalLog("ws-upgrade", {
|
|
3687
3041
|
corrId,
|
|
3688
3042
|
clientCorrId: clientCorrId ?? null,
|
|
3689
3043
|
decision: "accepted",
|
|
@@ -3700,287 +3054,6 @@ function handleUpgrade(req, clientSocket, head, opts) {
|
|
|
3700
3054
|
let bytesUpstreamToClient = 0;
|
|
3701
3055
|
let closedBy = null;
|
|
3702
3056
|
let proxyOpened = false;
|
|
3703
|
-
const finish = (side, reason) => {
|
|
3704
|
-
if (closedBy) return;
|
|
3705
|
-
closedBy = side;
|
|
3706
|
-
if (proxyOpened) {
|
|
3707
|
-
vncLog("proxy-close", {
|
|
3708
|
-
corrId,
|
|
3709
|
-
closedBy: side,
|
|
3710
|
-
reason,
|
|
3711
|
-
clientBytes: bytesClientToUpstream,
|
|
3712
|
-
upstreamBytes: bytesUpstreamToClient
|
|
3713
|
-
});
|
|
3714
|
-
}
|
|
3715
|
-
clientSocket.destroy();
|
|
3716
|
-
upstream.destroy();
|
|
3717
|
-
};
|
|
3718
|
-
upstream.once("connect", () => {
|
|
3719
|
-
upstream.setTimeout(0);
|
|
3720
|
-
proxyOpened = true;
|
|
3721
|
-
vncLog("proxy-open", {
|
|
3722
|
-
corrId,
|
|
3723
|
-
upstream: `${opts.upstreamHost}:${opts.upstreamPort}`
|
|
3724
|
-
});
|
|
3725
|
-
const lines = [];
|
|
3726
|
-
lines.push(`${req.method ?? "GET"} ${WS_PATH} HTTP/${req.httpVersion}`);
|
|
3727
|
-
lines.push(`host: ${opts.upstreamHost}:${opts.upstreamPort}`);
|
|
3728
|
-
for (const [name, value] of Object.entries(req.headers)) {
|
|
3729
|
-
if (name === "host") continue;
|
|
3730
|
-
if (HOP_BY_HOP.has(name)) continue;
|
|
3731
|
-
if (value == null) continue;
|
|
3732
|
-
if (Array.isArray(value)) {
|
|
3733
|
-
for (const v of value) lines.push(`${name}: ${v}`);
|
|
3734
|
-
} else {
|
|
3735
|
-
lines.push(`${name}: ${value}`);
|
|
3736
|
-
}
|
|
3737
|
-
}
|
|
3738
|
-
const upgradeHeader = headerString(req.headers.upgrade);
|
|
3739
|
-
const connectionHeader = headerString(req.headers.connection);
|
|
3740
|
-
if (upgradeHeader) lines.push(`upgrade: ${upgradeHeader}`);
|
|
3741
|
-
if (connectionHeader) lines.push(`connection: ${connectionHeader}`);
|
|
3742
|
-
upstream.write(lines.join("\r\n") + "\r\n\r\n");
|
|
3743
|
-
if (head && head.length > 0) upstream.write(head);
|
|
3744
|
-
clientSocket.on("data", (chunk) => {
|
|
3745
|
-
bytesClientToUpstream += chunk.length;
|
|
3746
|
-
});
|
|
3747
|
-
upstream.on("data", (chunk) => {
|
|
3748
|
-
bytesUpstreamToClient += chunk.length;
|
|
3749
|
-
});
|
|
3750
|
-
clientSocket.pipe(upstream);
|
|
3751
|
-
upstream.pipe(clientSocket);
|
|
3752
|
-
clientSocket.once("close", (hadError) => {
|
|
3753
|
-
finish("client", hadError ? "error" : "normal");
|
|
3754
|
-
});
|
|
3755
|
-
upstream.once("close", (hadError) => {
|
|
3756
|
-
finish("upstream", hadError ? "error" : "normal");
|
|
3757
|
-
});
|
|
3758
|
-
clientSocket.once("error", (err) => {
|
|
3759
|
-
vncLog("proxy-error", { corrId, side: "client", err: err.message });
|
|
3760
|
-
finish("client", "error");
|
|
3761
|
-
});
|
|
3762
|
-
upstream.once("error", (err) => {
|
|
3763
|
-
vncLog("proxy-error", { corrId, side: "upstream", err: err.message });
|
|
3764
|
-
finish("upstream", "error");
|
|
3765
|
-
});
|
|
3766
|
-
});
|
|
3767
|
-
upstream.once("timeout", () => {
|
|
3768
|
-
if (proxyOpened) return;
|
|
3769
|
-
vncLog("proxy-error", {
|
|
3770
|
-
corrId,
|
|
3771
|
-
side: "upstream-connect",
|
|
3772
|
-
err: "timeout",
|
|
3773
|
-
timeout_ms: UPSTREAM_TIMEOUT_MS
|
|
3774
|
-
});
|
|
3775
|
-
writeStatusAndDestroy(clientSocket, 504, "Gateway Timeout");
|
|
3776
|
-
upstream.destroy();
|
|
3777
|
-
});
|
|
3778
|
-
upstream.once("error", (err) => {
|
|
3779
|
-
if (proxyOpened) return;
|
|
3780
|
-
vncLog("proxy-error", {
|
|
3781
|
-
corrId,
|
|
3782
|
-
side: "upstream-connect",
|
|
3783
|
-
err: err.message
|
|
3784
|
-
});
|
|
3785
|
-
writeStatusAndDestroy(clientSocket, 502, "Bad Gateway");
|
|
3786
|
-
upstream.destroy();
|
|
3787
|
-
});
|
|
3788
|
-
}
|
|
3789
|
-
function parseQueryParam(query, key) {
|
|
3790
|
-
if (!query) return null;
|
|
3791
|
-
for (const pair of query.split("&")) {
|
|
3792
|
-
const eq = pair.indexOf("=");
|
|
3793
|
-
const k = eq === -1 ? pair : pair.slice(0, eq);
|
|
3794
|
-
if (k !== key) continue;
|
|
3795
|
-
const v = eq === -1 ? "" : pair.slice(eq + 1);
|
|
3796
|
-
try {
|
|
3797
|
-
return decodeURIComponent(v);
|
|
3798
|
-
} catch {
|
|
3799
|
-
return null;
|
|
3800
|
-
}
|
|
3801
|
-
}
|
|
3802
|
-
return null;
|
|
3803
|
-
}
|
|
3804
|
-
function headerString(value) {
|
|
3805
|
-
if (value == null) return void 0;
|
|
3806
|
-
return Array.isArray(value) ? value[0] : value;
|
|
3807
|
-
}
|
|
3808
|
-
function parseOriginHost(origin) {
|
|
3809
|
-
if (!origin) return null;
|
|
3810
|
-
try {
|
|
3811
|
-
return new URL(origin).hostname;
|
|
3812
|
-
} catch {
|
|
3813
|
-
return null;
|
|
3814
|
-
}
|
|
3815
|
-
}
|
|
3816
|
-
function writeStatusAndDestroy(socket, status, statusText) {
|
|
3817
|
-
try {
|
|
3818
|
-
socket.write(
|
|
3819
|
-
`HTTP/1.1 ${status} ${statusText}\r
|
|
3820
|
-
Connection: close\r
|
|
3821
|
-
Content-Length: 0\r
|
|
3822
|
-
\r
|
|
3823
|
-
`
|
|
3824
|
-
);
|
|
3825
|
-
} catch {
|
|
3826
|
-
}
|
|
3827
|
-
socket.destroy();
|
|
3828
|
-
}
|
|
3829
|
-
|
|
3830
|
-
// server/ws-proxy-terminal.ts
|
|
3831
|
-
import { createConnection as createConnection2 } from "net";
|
|
3832
|
-
|
|
3833
|
-
// app/lib/terminal-logger.ts
|
|
3834
|
-
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
3835
|
-
import { resolve as resolve3 } from "path";
|
|
3836
|
-
var TERMINAL_LOG_FILE = resolve3(LOG_DIR, "terminal.log");
|
|
3837
|
-
try {
|
|
3838
|
-
mkdirSync2(LOG_DIR, { recursive: true });
|
|
3839
|
-
} catch (err) {
|
|
3840
|
-
console.error(`[terminal-log-fail] mkdir ${LOG_DIR} failed: ${err.message}`);
|
|
3841
|
-
}
|
|
3842
|
-
function terminalLog(phase, fields = {}) {
|
|
3843
|
-
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
3844
|
-
const kv = Object.entries(fields).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(" ");
|
|
3845
|
-
const line = kv.length > 0 ? `[${ts}] [terminal-${phase}] ${kv}
|
|
3846
|
-
` : `[${ts}] [terminal-${phase}]
|
|
3847
|
-
`;
|
|
3848
|
-
try {
|
|
3849
|
-
appendFileSync2(TERMINAL_LOG_FILE, line);
|
|
3850
|
-
} catch (err) {
|
|
3851
|
-
console.error(`[terminal-log-fail] ${err.message} \u2014 dropped: ${line.slice(0, 300).trim()}`);
|
|
3852
|
-
}
|
|
3853
|
-
}
|
|
3854
|
-
|
|
3855
|
-
// server/ws-proxy-terminal.ts
|
|
3856
|
-
var WS_PATH2 = "/admin/terminal/ws";
|
|
3857
|
-
var UPSTREAM_TIMEOUT_MS2 = 5e3;
|
|
3858
|
-
var FLOW_TICK_MS = 5e3;
|
|
3859
|
-
var FLOW_IDLE_EMIT_MS = 3e4;
|
|
3860
|
-
var HOP_BY_HOP2 = /* @__PURE__ */ new Set([
|
|
3861
|
-
"connection",
|
|
3862
|
-
"keep-alive",
|
|
3863
|
-
"proxy-authenticate",
|
|
3864
|
-
"proxy-authorization",
|
|
3865
|
-
"te",
|
|
3866
|
-
"trailer",
|
|
3867
|
-
"transfer-encoding",
|
|
3868
|
-
"upgrade"
|
|
3869
|
-
]);
|
|
3870
|
-
function attachTerminalWsProxy(server, opts) {
|
|
3871
|
-
const upstreamHost = opts.upstreamHost ?? "127.0.0.1";
|
|
3872
|
-
const upstreamPort = opts.upstreamPort ?? 7681;
|
|
3873
|
-
server.on("upgrade", (req, clientSocket, head) => {
|
|
3874
|
-
try {
|
|
3875
|
-
handleUpgrade2(req, clientSocket, head, {
|
|
3876
|
-
isPublicHost: opts.isPublicHost,
|
|
3877
|
-
upstreamHost,
|
|
3878
|
-
upstreamPort
|
|
3879
|
-
});
|
|
3880
|
-
} catch (err) {
|
|
3881
|
-
terminalLog("ws-upgrade", {
|
|
3882
|
-
decision: "rejected",
|
|
3883
|
-
reason: "handler-exception",
|
|
3884
|
-
err: err.message
|
|
3885
|
-
});
|
|
3886
|
-
clientSocket.destroy();
|
|
3887
|
-
}
|
|
3888
|
-
});
|
|
3889
|
-
}
|
|
3890
|
-
function handleUpgrade2(req, clientSocket, head, opts) {
|
|
3891
|
-
const url = req.url ?? "";
|
|
3892
|
-
const qsIndex = url.indexOf("?");
|
|
3893
|
-
const pathname = qsIndex === -1 ? url : url.slice(0, qsIndex);
|
|
3894
|
-
if (pathname !== WS_PATH2) {
|
|
3895
|
-
return;
|
|
3896
|
-
}
|
|
3897
|
-
const corrId = newCorrId();
|
|
3898
|
-
const query = qsIndex === -1 ? "" : url.slice(qsIndex + 1);
|
|
3899
|
-
const rawClientCorrId = parseQueryParam2(query, "corrId");
|
|
3900
|
-
const clientCorrId = sanitizeClientCorrId(rawClientCorrId);
|
|
3901
|
-
const hostHeader = (req.headers.host ?? "").split(":")[0];
|
|
3902
|
-
const originHeader = headerString2(req.headers.origin);
|
|
3903
|
-
const remote = req.socket.remoteAddress;
|
|
3904
|
-
const xff = headerString2(req.headers["x-forwarded-for"]);
|
|
3905
|
-
const decision = canAccessAdmin({
|
|
3906
|
-
host: hostHeader,
|
|
3907
|
-
remoteAddress: remote,
|
|
3908
|
-
xForwardedFor: xff,
|
|
3909
|
-
cookieHeader: headerString2(req.headers.cookie),
|
|
3910
|
-
isPublicHost: opts.isPublicHost
|
|
3911
|
-
});
|
|
3912
|
-
if (!decision.allow) {
|
|
3913
|
-
const status = decision.reason === "public-host" ? 404 : 401;
|
|
3914
|
-
terminalLog("ws-upgrade", {
|
|
3915
|
-
corrId,
|
|
3916
|
-
clientCorrId: clientCorrId ?? null,
|
|
3917
|
-
decision: "rejected",
|
|
3918
|
-
reason: decision.reason,
|
|
3919
|
-
ip: remote,
|
|
3920
|
-
xff: xff ?? null,
|
|
3921
|
-
origin: originHeader ?? null,
|
|
3922
|
-
host: hostHeader
|
|
3923
|
-
});
|
|
3924
|
-
writeStatusAndDestroy2(clientSocket, status, decision.reason === "public-host" ? "Not Found" : "Unauthorized");
|
|
3925
|
-
return;
|
|
3926
|
-
}
|
|
3927
|
-
const originHost = parseOriginHost2(originHeader);
|
|
3928
|
-
if (!originHost) {
|
|
3929
|
-
terminalLog("ws-upgrade", {
|
|
3930
|
-
corrId,
|
|
3931
|
-
clientCorrId: clientCorrId ?? null,
|
|
3932
|
-
decision: "rejected",
|
|
3933
|
-
reason: "origin-missing-or-invalid",
|
|
3934
|
-
origin: originHeader ?? null,
|
|
3935
|
-
host: hostHeader,
|
|
3936
|
-
ip: remote
|
|
3937
|
-
});
|
|
3938
|
-
writeStatusAndDestroy2(clientSocket, 403, "Forbidden");
|
|
3939
|
-
return;
|
|
3940
|
-
}
|
|
3941
|
-
if (originHost !== hostHeader) {
|
|
3942
|
-
terminalLog("ws-upgrade", {
|
|
3943
|
-
corrId,
|
|
3944
|
-
clientCorrId: clientCorrId ?? null,
|
|
3945
|
-
decision: "rejected",
|
|
3946
|
-
reason: "origin-mismatch",
|
|
3947
|
-
origin_host: originHost,
|
|
3948
|
-
host: hostHeader,
|
|
3949
|
-
ip: remote
|
|
3950
|
-
});
|
|
3951
|
-
writeStatusAndDestroy2(clientSocket, 403, "Forbidden");
|
|
3952
|
-
return;
|
|
3953
|
-
}
|
|
3954
|
-
if (opts.isPublicHost(originHost)) {
|
|
3955
|
-
terminalLog("ws-upgrade", {
|
|
3956
|
-
corrId,
|
|
3957
|
-
clientCorrId: clientCorrId ?? null,
|
|
3958
|
-
decision: "rejected",
|
|
3959
|
-
reason: "origin-public-host",
|
|
3960
|
-
origin_host: originHost,
|
|
3961
|
-
host: hostHeader,
|
|
3962
|
-
ip: remote
|
|
3963
|
-
});
|
|
3964
|
-
writeStatusAndDestroy2(clientSocket, 403, "Forbidden");
|
|
3965
|
-
return;
|
|
3966
|
-
}
|
|
3967
|
-
terminalLog("ws-upgrade", {
|
|
3968
|
-
corrId,
|
|
3969
|
-
clientCorrId: clientCorrId ?? null,
|
|
3970
|
-
decision: "accepted",
|
|
3971
|
-
ip: remote,
|
|
3972
|
-
xff: xff ?? null,
|
|
3973
|
-
origin: originHeader ?? null,
|
|
3974
|
-
host: hostHeader,
|
|
3975
|
-
sec_ws_version: headerString2(req.headers["sec-websocket-version"]) ?? null,
|
|
3976
|
-
sec_ws_protocol: headerString2(req.headers["sec-websocket-protocol"]) ?? null
|
|
3977
|
-
});
|
|
3978
|
-
const upstream = createConnection2({ host: opts.upstreamHost, port: opts.upstreamPort });
|
|
3979
|
-
upstream.setTimeout(UPSTREAM_TIMEOUT_MS2);
|
|
3980
|
-
let bytesClientToUpstream = 0;
|
|
3981
|
-
let bytesUpstreamToClient = 0;
|
|
3982
|
-
let closedBy = null;
|
|
3983
|
-
let proxyOpened = false;
|
|
3984
3057
|
let lastUpstreamByteTs = 0;
|
|
3985
3058
|
let flowInterval = null;
|
|
3986
3059
|
let lastEmittedAt = 0;
|
|
@@ -4012,11 +3085,11 @@ function handleUpgrade2(req, clientSocket, head, opts) {
|
|
|
4012
3085
|
upstream: `${opts.upstreamHost}:${opts.upstreamPort}`
|
|
4013
3086
|
});
|
|
4014
3087
|
const lines = [];
|
|
4015
|
-
lines.push(`${req.method ?? "GET"} ${
|
|
3088
|
+
lines.push(`${req.method ?? "GET"} ${WS_PATH} HTTP/${req.httpVersion}`);
|
|
4016
3089
|
lines.push(`host: ${opts.upstreamHost}:${opts.upstreamPort}`);
|
|
4017
3090
|
for (const [name, value] of Object.entries(req.headers)) {
|
|
4018
3091
|
if (name === "host") continue;
|
|
4019
|
-
if (
|
|
3092
|
+
if (HOP_BY_HOP.has(name)) continue;
|
|
4020
3093
|
if (value == null) continue;
|
|
4021
3094
|
if (Array.isArray(value)) {
|
|
4022
3095
|
for (const v of value) lines.push(`${name}: ${v}`);
|
|
@@ -4024,8 +3097,8 @@ function handleUpgrade2(req, clientSocket, head, opts) {
|
|
|
4024
3097
|
lines.push(`${name}: ${value}`);
|
|
4025
3098
|
}
|
|
4026
3099
|
}
|
|
4027
|
-
const upgradeHeader =
|
|
4028
|
-
const connectionHeader =
|
|
3100
|
+
const upgradeHeader = headerString(req.headers.upgrade);
|
|
3101
|
+
const connectionHeader = headerString(req.headers.connection);
|
|
4029
3102
|
if (upgradeHeader) lines.push(`upgrade: ${upgradeHeader}`);
|
|
4030
3103
|
if (connectionHeader) lines.push(`connection: ${connectionHeader}`);
|
|
4031
3104
|
upstream.write(lines.join("\r\n") + "\r\n\r\n");
|
|
@@ -4078,9 +3151,9 @@ function handleUpgrade2(req, clientSocket, head, opts) {
|
|
|
4078
3151
|
corrId,
|
|
4079
3152
|
side: "upstream-connect",
|
|
4080
3153
|
err: "timeout",
|
|
4081
|
-
timeout_ms:
|
|
3154
|
+
timeout_ms: UPSTREAM_TIMEOUT_MS
|
|
4082
3155
|
});
|
|
4083
|
-
|
|
3156
|
+
writeStatusAndDestroy(clientSocket, 504, "Gateway Timeout");
|
|
4084
3157
|
upstream.destroy();
|
|
4085
3158
|
});
|
|
4086
3159
|
upstream.once("error", (err) => {
|
|
@@ -4090,11 +3163,11 @@ function handleUpgrade2(req, clientSocket, head, opts) {
|
|
|
4090
3163
|
side: "upstream-connect",
|
|
4091
3164
|
err: err.message
|
|
4092
3165
|
});
|
|
4093
|
-
|
|
3166
|
+
writeStatusAndDestroy(clientSocket, 502, "Bad Gateway");
|
|
4094
3167
|
upstream.destroy();
|
|
4095
3168
|
});
|
|
4096
3169
|
}
|
|
4097
|
-
function
|
|
3170
|
+
function parseQueryParam(query, key) {
|
|
4098
3171
|
if (!query) return null;
|
|
4099
3172
|
for (const pair of query.split("&")) {
|
|
4100
3173
|
const eq = pair.indexOf("=");
|
|
@@ -4109,11 +3182,11 @@ function parseQueryParam2(query, key) {
|
|
|
4109
3182
|
}
|
|
4110
3183
|
return null;
|
|
4111
3184
|
}
|
|
4112
|
-
function
|
|
3185
|
+
function headerString(value) {
|
|
4113
3186
|
if (value == null) return void 0;
|
|
4114
3187
|
return Array.isArray(value) ? value[0] : value;
|
|
4115
3188
|
}
|
|
4116
|
-
function
|
|
3189
|
+
function parseOriginHost(origin) {
|
|
4117
3190
|
if (!origin) return null;
|
|
4118
3191
|
try {
|
|
4119
3192
|
return new URL(origin).hostname;
|
|
@@ -4121,7 +3194,7 @@ function parseOriginHost2(origin) {
|
|
|
4121
3194
|
return null;
|
|
4122
3195
|
}
|
|
4123
3196
|
}
|
|
4124
|
-
function
|
|
3197
|
+
function writeStatusAndDestroy(socket, status, statusText) {
|
|
4125
3198
|
try {
|
|
4126
3199
|
socket.write(
|
|
4127
3200
|
`HTTP/1.1 ${status} ${statusText}\r
|
|
@@ -4142,9 +3215,9 @@ var AGENT_SLUG_PATTERN = /^\/([a-z][a-z0-9-]{2,49})$/;
|
|
|
4142
3215
|
import Anthropic3 from "@anthropic-ai/sdk";
|
|
4143
3216
|
import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
|
|
4144
3217
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
4145
|
-
import { resolve as
|
|
3218
|
+
import { resolve as resolve6, join as join3 } from "path";
|
|
4146
3219
|
import { platform as osPlatform } from "os";
|
|
4147
|
-
import { readFileSync as
|
|
3220
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, readdirSync as readdirSync2, existsSync as existsSync5, mkdirSync as mkdirSync5, createWriteStream, statSync as statSync3, unlinkSync as unlinkSync3, cpSync, rmSync as rmSync2, appendFileSync as appendFileSync2, openSync as openSync2, readSync as readSync2, closeSync as closeSync2 } from "fs";
|
|
4148
3221
|
import { lookup as dnsLookup } from "dns/promises";
|
|
4149
3222
|
import { createConnection as netConnect } from "net";
|
|
4150
3223
|
import { StringDecoder } from "string_decoder";
|
|
@@ -4152,34 +3225,34 @@ import { StringDecoder } from "string_decoder";
|
|
|
4152
3225
|
// ../lib/anthropic-key/src/index.ts
|
|
4153
3226
|
var import_dist = __toESM(require_dist());
|
|
4154
3227
|
import {
|
|
4155
|
-
existsSync as
|
|
4156
|
-
mkdirSync as
|
|
4157
|
-
readFileSync
|
|
3228
|
+
existsSync as existsSync2,
|
|
3229
|
+
mkdirSync as mkdirSync2,
|
|
3230
|
+
readFileSync,
|
|
4158
3231
|
unlinkSync,
|
|
4159
|
-
writeFileSync
|
|
3232
|
+
writeFileSync
|
|
4160
3233
|
} from "fs";
|
|
4161
|
-
import { resolve as
|
|
4162
|
-
import { homedir
|
|
3234
|
+
import { resolve as resolve2, join as join2, dirname } from "path";
|
|
3235
|
+
import { homedir } from "os";
|
|
4163
3236
|
var cachedKeyFilePath = null;
|
|
4164
3237
|
function resolveKeyFilePath() {
|
|
4165
3238
|
if (cachedKeyFilePath) return cachedKeyFilePath;
|
|
4166
|
-
let
|
|
4167
|
-
const
|
|
4168
|
-
if (
|
|
4169
|
-
const brandPath =
|
|
4170
|
-
if (!
|
|
3239
|
+
let configDirName = ".maxy";
|
|
3240
|
+
const platformRoot2 = process.env.PLATFORM_ROOT ?? process.env.MAXY_PLATFORM_ROOT;
|
|
3241
|
+
if (platformRoot2) {
|
|
3242
|
+
const brandPath = join2(platformRoot2, "config", "brand.json");
|
|
3243
|
+
if (!existsSync2(brandPath)) {
|
|
4171
3244
|
throw new Error(
|
|
4172
3245
|
`brand.json not found at ${brandPath} \u2014 platform not properly installed`
|
|
4173
3246
|
);
|
|
4174
3247
|
}
|
|
4175
3248
|
try {
|
|
4176
|
-
const brand = JSON.parse(
|
|
3249
|
+
const brand = JSON.parse(readFileSync(brandPath, "utf-8"));
|
|
4177
3250
|
if (!brand.configDir) {
|
|
4178
3251
|
throw new Error(
|
|
4179
3252
|
`brand.json at ${brandPath} is missing the configDir field`
|
|
4180
3253
|
);
|
|
4181
3254
|
}
|
|
4182
|
-
|
|
3255
|
+
configDirName = brand.configDir;
|
|
4183
3256
|
} catch (err) {
|
|
4184
3257
|
if (err instanceof SyntaxError) {
|
|
4185
3258
|
throw new Error(
|
|
@@ -4189,7 +3262,7 @@ function resolveKeyFilePath() {
|
|
|
4189
3262
|
throw err;
|
|
4190
3263
|
}
|
|
4191
3264
|
}
|
|
4192
|
-
cachedKeyFilePath =
|
|
3265
|
+
cachedKeyFilePath = resolve2(homedir(), configDirName, ".anthropic-api-key");
|
|
4193
3266
|
return cachedKeyFilePath;
|
|
4194
3267
|
}
|
|
4195
3268
|
function readKey() {
|
|
@@ -4211,7 +3284,7 @@ function readKey() {
|
|
|
4211
3284
|
);
|
|
4212
3285
|
}
|
|
4213
3286
|
try {
|
|
4214
|
-
const raw2 =
|
|
3287
|
+
const raw2 = readFileSync(keyFilePath2, "utf-8").trim();
|
|
4215
3288
|
if (!raw2) {
|
|
4216
3289
|
console.error(
|
|
4217
3290
|
`[anthropic-key] key file exists but is empty: ${keyFilePath2}`
|
|
@@ -4325,14 +3398,14 @@ function contextWindow(model) {
|
|
|
4325
3398
|
}
|
|
4326
3399
|
|
|
4327
3400
|
// app/lib/claude-auth.ts
|
|
4328
|
-
import { readFileSync as
|
|
3401
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
4329
3402
|
var TOKEN_ENDPOINT = "https://platform.claude.com/v1/oauth/token";
|
|
4330
3403
|
var CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
4331
3404
|
var EXPIRING_THRESHOLD_MS = 5 * 60 * 1e3;
|
|
4332
3405
|
function readCredentials() {
|
|
4333
3406
|
let raw2;
|
|
4334
3407
|
try {
|
|
4335
|
-
raw2 =
|
|
3408
|
+
raw2 = readFileSync2(CLAUDE_CREDENTIALS_FILE, "utf-8");
|
|
4336
3409
|
} catch {
|
|
4337
3410
|
return null;
|
|
4338
3411
|
}
|
|
@@ -4360,7 +3433,7 @@ function writeOAuthCredentials(tokens) {
|
|
|
4360
3433
|
try {
|
|
4361
3434
|
let fileData = {};
|
|
4362
3435
|
try {
|
|
4363
|
-
const rawFile =
|
|
3436
|
+
const rawFile = readFileSync2(CLAUDE_CREDENTIALS_FILE, "utf-8");
|
|
4364
3437
|
fileData = JSON.parse(rawFile);
|
|
4365
3438
|
} catch {
|
|
4366
3439
|
}
|
|
@@ -4371,7 +3444,7 @@ function writeOAuthCredentials(tokens) {
|
|
|
4371
3444
|
...tokens.refreshToken != null ? { refreshToken: tokens.refreshToken } : {},
|
|
4372
3445
|
expiresAt: newExpiresAt
|
|
4373
3446
|
};
|
|
4374
|
-
|
|
3447
|
+
writeFileSync2(CLAUDE_CREDENTIALS_FILE, JSON.stringify(fileData, null, 2), "utf-8");
|
|
4375
3448
|
return newExpiresAt;
|
|
4376
3449
|
} catch (err) {
|
|
4377
3450
|
console.error(`[claude-auth] credential write failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -4475,11 +3548,11 @@ async function ensureAuth() {
|
|
|
4475
3548
|
|
|
4476
3549
|
// app/lib/vnc.ts
|
|
4477
3550
|
import { spawnSync, execFileSync } from "child_process";
|
|
4478
|
-
import { createConnection as
|
|
4479
|
-
import { mkdirSync as
|
|
4480
|
-
import { resolve as
|
|
4481
|
-
var
|
|
4482
|
-
var VNC_SCRIPT =
|
|
3551
|
+
import { createConnection as createConnection2 } from "net";
|
|
3552
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
3553
|
+
import { resolve as resolve3 } from "path";
|
|
3554
|
+
var PLATFORM_ROOT = process.env.MAXY_PLATFORM_ROOT ?? resolve3(process.cwd(), "..");
|
|
3555
|
+
var VNC_SCRIPT = resolve3(PLATFORM_ROOT, "scripts/vnc.sh");
|
|
4483
3556
|
var displayMode = process.env.DISPLAY_MODE ?? "virtual";
|
|
4484
3557
|
if (displayMode === "native") {
|
|
4485
3558
|
console.log(`[vnc] DISPLAY_MODE=native \u2014 local requests use desktop display, remote requests use VNC`);
|
|
@@ -4488,8 +3561,8 @@ function resolveBrowserTransport(req, remoteAddress) {
|
|
|
4488
3561
|
if (displayMode !== "native") return "vnc";
|
|
4489
3562
|
const xff = req.headers.get("x-forwarded-for") ?? void 0;
|
|
4490
3563
|
const clientIp = resolveClientIp(remoteAddress ?? "127.0.0.1", xff);
|
|
4491
|
-
const
|
|
4492
|
-
const transport =
|
|
3564
|
+
const isLoopback = clientIp === "127.0.0.1" || clientIp === "::1" || clientIp === "loopback";
|
|
3565
|
+
const transport = isLoopback && !xff ? "native" : "vnc";
|
|
4493
3566
|
if (remoteAddress && remoteAddress !== "127.0.0.1" && remoteAddress !== "::1") {
|
|
4494
3567
|
const oldClientIp = resolveClientIp("127.0.0.1", xff);
|
|
4495
3568
|
const oldIsLoopback = oldClientIp === "127.0.0.1" || oldClientIp === "::1" || oldClientIp === "loopback";
|
|
@@ -4537,7 +3610,7 @@ function discoverNativeDisplay() {
|
|
|
4537
3610
|
const leaderPid = leaderResult.stdout?.trim();
|
|
4538
3611
|
if (leaderPid) {
|
|
4539
3612
|
try {
|
|
4540
|
-
const environ =
|
|
3613
|
+
const environ = readFileSync3(`/proc/${leaderPid}/environ`, "utf8");
|
|
4541
3614
|
const match2 = environ.split("\0").find((e) => e.startsWith("WAYLAND_DISPLAY="));
|
|
4542
3615
|
if (match2) waylandDisplay = match2.split("=")[1];
|
|
4543
3616
|
} catch {
|
|
@@ -4571,7 +3644,7 @@ async function waitForPort(port2, timeoutMs = 12e3) {
|
|
|
4571
3644
|
const deadline = Date.now() + timeoutMs;
|
|
4572
3645
|
while (Date.now() < deadline) {
|
|
4573
3646
|
const ready = await new Promise((res) => {
|
|
4574
|
-
const socket =
|
|
3647
|
+
const socket = createConnection2(port2, "127.0.0.1");
|
|
4575
3648
|
socket.setTimeout(500);
|
|
4576
3649
|
socket.once("connect", () => {
|
|
4577
3650
|
socket.destroy();
|
|
@@ -4592,10 +3665,10 @@ async function waitForPort(port2, timeoutMs = 12e3) {
|
|
|
4592
3665
|
return false;
|
|
4593
3666
|
}
|
|
4594
3667
|
function ensureLogDir() {
|
|
4595
|
-
|
|
3668
|
+
mkdirSync3(LOG_DIR, { recursive: true });
|
|
4596
3669
|
}
|
|
4597
3670
|
function logPath(name) {
|
|
4598
|
-
return
|
|
3671
|
+
return resolve3(LOG_DIR, `${name}.log`);
|
|
4599
3672
|
}
|
|
4600
3673
|
async function ensureVnc() {
|
|
4601
3674
|
const up = await waitForPort(5900, 1e3);
|
|
@@ -4761,10 +3834,52 @@ function killTerminal() {
|
|
|
4761
3834
|
spawnSync("bash", [VNC_SCRIPT, "kill-terminal"], { stdio: "pipe", timeout: 5e3 });
|
|
4762
3835
|
currentTerminalDisplay = null;
|
|
4763
3836
|
}
|
|
3837
|
+
async function ensureTerminalUpgrade(transport = "vnc") {
|
|
3838
|
+
const targetSentinel = ":99";
|
|
3839
|
+
vncLog("ensure-terminal", {
|
|
3840
|
+
action: "launch-upgrade",
|
|
3841
|
+
transport,
|
|
3842
|
+
cmd: "npx -y @rubytech/create-maxy@latest"
|
|
3843
|
+
});
|
|
3844
|
+
killTerminal();
|
|
3845
|
+
await sleep(500);
|
|
3846
|
+
if (transport === "vnc") {
|
|
3847
|
+
const xAlive = await waitForPort(5900, 1e3);
|
|
3848
|
+
if (!xAlive) {
|
|
3849
|
+
console.error("[ensureTerminalUpgrade] X server down on :5900 \u2014 escalating to full VNC restart");
|
|
3850
|
+
vncLog("ensure-terminal", { action: "escalate-vnc-restart", reason: "x-down", transport, upgrade: true });
|
|
3851
|
+
const vncOk = await ensureVnc();
|
|
3852
|
+
if (!vncOk) {
|
|
3853
|
+
console.error("[ensureTerminalUpgrade] Full VNC restart failed \u2014 upgrade degraded");
|
|
3854
|
+
vncLog("ensure-terminal", { action: "launch-upgrade-failed", transport, windowPresent: false, err: "VNC failed to start after recovery attempt" });
|
|
3855
|
+
return { ok: false, error: "VNC failed to start after recovery attempt" };
|
|
3856
|
+
}
|
|
3857
|
+
}
|
|
3858
|
+
}
|
|
3859
|
+
console.error(`[ensureTerminalUpgrade] Launching upgrade terminal transport=${transport}`);
|
|
3860
|
+
const result = spawnSync("bash", [VNC_SCRIPT, "start-terminal-upgrade"], {
|
|
3861
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3862
|
+
timeout: 1e4
|
|
3863
|
+
});
|
|
3864
|
+
if (result.status !== 0) {
|
|
3865
|
+
const errorLine = extractFailureLine(result.stderr?.toString(), result.signal);
|
|
3866
|
+
vncLog("ensure-terminal", { action: "launch-upgrade-failed", transport, windowPresent: false, err: errorLine });
|
|
3867
|
+
return { ok: false, error: errorLine };
|
|
3868
|
+
}
|
|
3869
|
+
currentTerminalDisplay = targetSentinel;
|
|
3870
|
+
vncLog("ensure-terminal", {
|
|
3871
|
+
action: "launch-upgrade-complete",
|
|
3872
|
+
transport,
|
|
3873
|
+
sentinel: targetSentinel,
|
|
3874
|
+
windowPresent: true,
|
|
3875
|
+
cmd: "npx -y @rubytech/create-maxy@latest"
|
|
3876
|
+
});
|
|
3877
|
+
return { ok: true };
|
|
3878
|
+
}
|
|
4764
3879
|
function writeChromiumWrapper() {
|
|
4765
|
-
|
|
4766
|
-
const wrapperPath =
|
|
4767
|
-
|
|
3880
|
+
mkdirSync3(BIN_DIR, { recursive: true });
|
|
3881
|
+
const wrapperPath = resolve3(BIN_DIR, "chromium");
|
|
3882
|
+
writeFileSync3(wrapperPath, `#!/bin/bash
|
|
4768
3883
|
LOG="${LOG_DIR}/chromium.log"
|
|
4769
3884
|
echo "==== [$(date)] chromium wrapper ====" >> "$LOG"
|
|
4770
3885
|
echo " DISPLAY=$DISPLAY WAYLAND=$WAYLAND_DISPLAY XDG_SESSION_TYPE=$XDG_SESSION_TYPE" >> "$LOG"
|
|
@@ -4848,15 +3963,15 @@ function buildX11Env(chromiumWrapperPath, transport = "vnc") {
|
|
|
4848
3963
|
import neo4j from "neo4j-driver";
|
|
4849
3964
|
import { randomUUID } from "crypto";
|
|
4850
3965
|
import { spawn } from "child_process";
|
|
4851
|
-
import { readFileSync as
|
|
4852
|
-
import { resolve as
|
|
4853
|
-
var
|
|
3966
|
+
import { readFileSync as readFileSync4, readdirSync, existsSync as existsSync3, openSync, readSync, closeSync, statSync as statSync2, rmSync } from "fs";
|
|
3967
|
+
import { resolve as resolve4 } from "path";
|
|
3968
|
+
var PLATFORM_ROOT2 = process.env.MAXY_PLATFORM_ROOT ?? resolve4(process.cwd(), "..");
|
|
4854
3969
|
var driver = null;
|
|
4855
3970
|
function readPassword() {
|
|
4856
3971
|
if (process.env.NEO4J_PASSWORD) return process.env.NEO4J_PASSWORD;
|
|
4857
|
-
const passwordFile =
|
|
3972
|
+
const passwordFile = resolve4(PLATFORM_ROOT2, "config/.neo4j-password");
|
|
4858
3973
|
try {
|
|
4859
|
-
return
|
|
3974
|
+
return readFileSync4(passwordFile, "utf-8").trim();
|
|
4860
3975
|
} catch {
|
|
4861
3976
|
throw new Error(
|
|
4862
3977
|
`Neo4j password not found. Expected at ${passwordFile} or in NEO4J_PASSWORD env var.`
|
|
@@ -5319,8 +4434,8 @@ async function persistMessage(conversationId, role, content, accountId, tokens,
|
|
|
5319
4434
|
const prev = persistMessageLocks.get(conversationId);
|
|
5320
4435
|
const waited = prev !== void 0;
|
|
5321
4436
|
let release;
|
|
5322
|
-
const mine = new Promise((
|
|
5323
|
-
release =
|
|
4437
|
+
const mine = new Promise((resolve29) => {
|
|
4438
|
+
release = resolve29;
|
|
5324
4439
|
});
|
|
5325
4440
|
const chained = (prev ?? Promise.resolve()).then(() => mine);
|
|
5326
4441
|
persistMessageLocks.set(conversationId, chained);
|
|
@@ -5579,7 +4694,7 @@ ${userContent}`;
|
|
|
5579
4694
|
"dontAsk",
|
|
5580
4695
|
prompt
|
|
5581
4696
|
];
|
|
5582
|
-
return new Promise((
|
|
4697
|
+
return new Promise((resolve29) => {
|
|
5583
4698
|
let stdout = "";
|
|
5584
4699
|
let stderr = "";
|
|
5585
4700
|
const spawnFn = _spawnOverride ?? spawn;
|
|
@@ -5597,35 +4712,35 @@ ${userContent}`;
|
|
|
5597
4712
|
const timer = setTimeout(() => {
|
|
5598
4713
|
proc.kill("SIGTERM");
|
|
5599
4714
|
console.error("[persist] autoLabel: haiku subprocess timed out");
|
|
5600
|
-
|
|
4715
|
+
resolve29(null);
|
|
5601
4716
|
}, SESSION_LABEL_TIMEOUT_MS);
|
|
5602
4717
|
proc.on("error", (err) => {
|
|
5603
4718
|
clearTimeout(timer);
|
|
5604
4719
|
console.error(`[persist] autoLabel: subprocess error \u2014 ${err.message}`);
|
|
5605
|
-
|
|
4720
|
+
resolve29(null);
|
|
5606
4721
|
});
|
|
5607
4722
|
proc.on("close", (code) => {
|
|
5608
4723
|
clearTimeout(timer);
|
|
5609
4724
|
if (code !== 0) {
|
|
5610
4725
|
console.error(`[persist] autoLabel: subprocess exited code=${code}${stderr ? ` stderr=${stderr.trim().slice(0, 200)}` : ""}`);
|
|
5611
|
-
|
|
4726
|
+
resolve29(null);
|
|
5612
4727
|
return;
|
|
5613
4728
|
}
|
|
5614
4729
|
const text = stdout.trim();
|
|
5615
4730
|
if (!text) {
|
|
5616
4731
|
console.error("[persist] autoLabel: haiku returned empty response");
|
|
5617
|
-
|
|
4732
|
+
resolve29(null);
|
|
5618
4733
|
return;
|
|
5619
4734
|
}
|
|
5620
4735
|
if (text === "SKIP") {
|
|
5621
4736
|
console.error("[persist] autoLabel: haiku returned SKIP \u2014 messages too vague");
|
|
5622
|
-
|
|
4737
|
+
resolve29(null);
|
|
5623
4738
|
return;
|
|
5624
4739
|
}
|
|
5625
4740
|
const words = text.split(/\s+/).slice(0, SESSION_LABEL_MAX_WORDS);
|
|
5626
4741
|
const label = words.join(" ");
|
|
5627
4742
|
console.error(`[persist] autoLabel: haiku response="${label}"`);
|
|
5628
|
-
|
|
4743
|
+
resolve29(label);
|
|
5629
4744
|
});
|
|
5630
4745
|
});
|
|
5631
4746
|
}
|
|
@@ -5897,10 +5012,10 @@ var MAX_RECENT_TOOL_FAILURES = 3;
|
|
|
5897
5012
|
var RECENT_FAILURES_TAIL_BYTES = 10 * 1024;
|
|
5898
5013
|
function readRecentToolFailures(accountId, conversationId) {
|
|
5899
5014
|
try {
|
|
5900
|
-
const
|
|
5901
|
-
const logDir =
|
|
5902
|
-
const logPath2 =
|
|
5903
|
-
if (!
|
|
5015
|
+
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? resolve4(process.cwd(), "..");
|
|
5016
|
+
const logDir = resolve4(platformRoot2, "..", "data/accounts", accountId, "logs");
|
|
5017
|
+
const logPath2 = resolve4(logDir, `claude-agent-stream-${conversationId}.log`);
|
|
5018
|
+
if (!existsSync3(logPath2)) {
|
|
5904
5019
|
console.error(`[review-tail-skip] path=${logPath2} reason=file-missing \u2014 first turn of conversation, subprocess not yet spawned, or log rotated`);
|
|
5905
5020
|
return [];
|
|
5906
5021
|
}
|
|
@@ -6056,13 +5171,13 @@ ${taskLines.join("\n")}`);
|
|
|
6056
5171
|
let pendingCount = 0;
|
|
6057
5172
|
let pendingLines = [];
|
|
6058
5173
|
try {
|
|
6059
|
-
const
|
|
6060
|
-
const pendingDir =
|
|
6061
|
-
if (
|
|
5174
|
+
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? resolve4(process.cwd(), "..");
|
|
5175
|
+
const pendingDir = resolve4(platformRoot2, "..", "data/accounts", accountId, "pending-actions");
|
|
5176
|
+
if (existsSync3(pendingDir)) {
|
|
6062
5177
|
const files = readdirSync(pendingDir).filter((f) => f.endsWith(".json") && !f.startsWith("."));
|
|
6063
5178
|
for (const file of files) {
|
|
6064
5179
|
try {
|
|
6065
|
-
const raw2 =
|
|
5180
|
+
const raw2 = readFileSync4(resolve4(pendingDir, file), "utf-8");
|
|
6066
5181
|
const action = JSON.parse(raw2);
|
|
6067
5182
|
if (action.state === "pending") {
|
|
6068
5183
|
const inputSummary = JSON.stringify(action.hookPayload?.tool_input ?? {}).slice(0, 150);
|
|
@@ -6129,12 +5244,12 @@ ${sections.join("\n\n")}
|
|
|
6129
5244
|
}
|
|
6130
5245
|
}
|
|
6131
5246
|
async function consumeStep7FlagUI(session, accountId) {
|
|
6132
|
-
const accountDir =
|
|
6133
|
-
const flagPath =
|
|
6134
|
-
if (!
|
|
5247
|
+
const accountDir = resolve4(PLATFORM_ROOT2, "..", "data/accounts", accountId);
|
|
5248
|
+
const flagPath = resolve4(accountDir, "onboarding", "step7-complete");
|
|
5249
|
+
if (!existsSync3(flagPath)) return false;
|
|
6135
5250
|
let completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6136
5251
|
try {
|
|
6137
|
-
const raw2 =
|
|
5252
|
+
const raw2 = readFileSync4(flagPath, "utf-8").trim();
|
|
6138
5253
|
if (raw2) {
|
|
6139
5254
|
const parsed = JSON.parse(raw2);
|
|
6140
5255
|
if (typeof parsed.completedAt === "string") {
|
|
@@ -6572,8 +5687,8 @@ ${items}
|
|
|
6572
5687
|
}
|
|
6573
5688
|
|
|
6574
5689
|
// app/lib/adherence-ledger.ts
|
|
6575
|
-
import { existsSync as
|
|
6576
|
-
import { resolve as
|
|
5690
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync5, renameSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
5691
|
+
import { resolve as resolve5, dirname as dirname2 } from "path";
|
|
6577
5692
|
import lockfile from "proper-lockfile";
|
|
6578
5693
|
var LOG_TAG = "[adherence-ledger]";
|
|
6579
5694
|
var ADHERENCE_BLOCK_THRESHOLD = 5;
|
|
@@ -6588,7 +5703,7 @@ function nowIso() {
|
|
|
6588
5703
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
6589
5704
|
}
|
|
6590
5705
|
function ledgerPath(accountDir, agentName) {
|
|
6591
|
-
return
|
|
5706
|
+
return resolve5(accountDir, "agents", agentName, "adherence-ledger.json");
|
|
6592
5707
|
}
|
|
6593
5708
|
function lockPath(accountDir, agentName) {
|
|
6594
5709
|
return `${ledgerPath(accountDir, agentName)}.lock`;
|
|
@@ -6613,8 +5728,8 @@ function pruneViolations(violations) {
|
|
|
6613
5728
|
});
|
|
6614
5729
|
}
|
|
6615
5730
|
function readUnlocked(path2, accountId, agentName) {
|
|
6616
|
-
if (!
|
|
6617
|
-
const content =
|
|
5731
|
+
if (!existsSync4(path2)) return emptyLedger(accountId, agentName);
|
|
5732
|
+
const content = readFileSync5(path2, "utf-8");
|
|
6618
5733
|
if (!content.trim()) return emptyLedger(accountId, agentName);
|
|
6619
5734
|
const data = JSON.parse(content);
|
|
6620
5735
|
if (typeof data !== "object" || data === null) {
|
|
@@ -6633,10 +5748,10 @@ function writeAtomic(path2, data) {
|
|
|
6633
5748
|
}
|
|
6634
5749
|
data.updated_at = nowIso();
|
|
6635
5750
|
const dir = dirname2(path2);
|
|
6636
|
-
|
|
5751
|
+
mkdirSync4(dir, { recursive: true });
|
|
6637
5752
|
const tmp = `${path2}.tmp-${process.pid}-${Date.now()}`;
|
|
6638
5753
|
try {
|
|
6639
|
-
|
|
5754
|
+
writeFileSync4(tmp, JSON.stringify(data, null, 2), "utf-8");
|
|
6640
5755
|
renameSync(tmp, path2);
|
|
6641
5756
|
} catch (err) {
|
|
6642
5757
|
try {
|
|
@@ -6649,9 +5764,9 @@ function writeAtomic(path2, data) {
|
|
|
6649
5764
|
async function withLock(accountDir, agentName, fn) {
|
|
6650
5765
|
const path2 = ledgerPath(accountDir, agentName);
|
|
6651
5766
|
const lock = lockPath(accountDir, agentName);
|
|
6652
|
-
|
|
6653
|
-
if (!
|
|
6654
|
-
|
|
5767
|
+
mkdirSync4(dirname2(lock), { recursive: true });
|
|
5768
|
+
if (!existsSync4(lock)) {
|
|
5769
|
+
writeFileSync4(lock, "", "utf-8");
|
|
6655
5770
|
}
|
|
6656
5771
|
const release = await lockfile.lock(lock, {
|
|
6657
5772
|
realpath: false,
|
|
@@ -7137,20 +6252,20 @@ function agentLogStream(name, accountDir, conversationId) {
|
|
|
7137
6252
|
if (!conversationId) {
|
|
7138
6253
|
throw new Error(`agentLogStream: conversationId is required (name=${name}) \u2014 use preConversationLogStream for pre-session events`);
|
|
7139
6254
|
}
|
|
7140
|
-
const logDir =
|
|
7141
|
-
|
|
6255
|
+
const logDir = resolve6(accountDir, "logs");
|
|
6256
|
+
mkdirSync5(logDir, { recursive: true });
|
|
7142
6257
|
purgeOldLogs(logDir, `${name}-`);
|
|
7143
|
-
const logPath2 =
|
|
6258
|
+
const logPath2 = resolve6(logDir, `${name}-${conversationId}.log`);
|
|
7144
6259
|
const stream = createWriteStream(logPath2, { flags: "a" });
|
|
7145
6260
|
registerStreamLog(stream, { path: logPath2, conversationId, name });
|
|
7146
6261
|
return stream;
|
|
7147
6262
|
}
|
|
7148
6263
|
function preConversationLogStream(name, accountDir) {
|
|
7149
|
-
const logDir =
|
|
7150
|
-
|
|
6264
|
+
const logDir = resolve6(accountDir, "logs");
|
|
6265
|
+
mkdirSync5(logDir, { recursive: true });
|
|
7151
6266
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
7152
6267
|
purgeOldLogs(logDir, `preconversation-${name}-`);
|
|
7153
|
-
const logPath2 =
|
|
6268
|
+
const logPath2 = resolve6(logDir, `preconversation-${name}-${date}.log`);
|
|
7154
6269
|
const stream = createWriteStream(logPath2, { flags: "a" });
|
|
7155
6270
|
registerStreamLog(stream, { path: logPath2, conversationId: null, name: `preconversation-${name}` });
|
|
7156
6271
|
return stream;
|
|
@@ -7169,7 +6284,7 @@ function sigtermFlushStreamLogs(reason, source) {
|
|
|
7169
6284
|
const line = `[${ts}] [server-sigterm] reason=${reason}${convPart} name=${entry.name} source=${source}
|
|
7170
6285
|
`;
|
|
7171
6286
|
try {
|
|
7172
|
-
|
|
6287
|
+
appendFileSync2(entry.path, line);
|
|
7173
6288
|
} catch (err) {
|
|
7174
6289
|
const msg = err instanceof Error ? err.message : String(err);
|
|
7175
6290
|
console.error(`[server-sigterm-flush-err] path=${entry.path} reason=${msg}`);
|
|
@@ -7188,7 +6303,7 @@ function purgeOldLogs(logDir, prefix) {
|
|
|
7188
6303
|
}
|
|
7189
6304
|
for (const file of entries) {
|
|
7190
6305
|
if (!file.startsWith(prefix)) continue;
|
|
7191
|
-
const filePath =
|
|
6306
|
+
const filePath = resolve6(logDir, file);
|
|
7192
6307
|
try {
|
|
7193
6308
|
if (statSync3(filePath).mtimeMs < cutoff) unlinkSync3(filePath);
|
|
7194
6309
|
} catch (err) {
|
|
@@ -7257,7 +6372,7 @@ function sampleProcState(pid) {
|
|
|
7257
6372
|
let sockets2 = 0;
|
|
7258
6373
|
for (const tcpFile of ["/proc/" + pid + "/net/tcp", "/proc/" + pid + "/net/tcp6"]) {
|
|
7259
6374
|
try {
|
|
7260
|
-
const raw2 =
|
|
6375
|
+
const raw2 = readFileSync6(tcpFile, "utf-8");
|
|
7261
6376
|
const lines2 = raw2.split("\n");
|
|
7262
6377
|
for (let i = 1; i < lines2.length; i++) {
|
|
7263
6378
|
const line = lines2[i].trim();
|
|
@@ -7273,7 +6388,7 @@ function sampleProcState(pid) {
|
|
|
7273
6388
|
}
|
|
7274
6389
|
let rssMb = 0;
|
|
7275
6390
|
try {
|
|
7276
|
-
const statm =
|
|
6391
|
+
const statm = readFileSync6(`/proc/${pid}/statm`, "utf-8").trim().split(/\s+/);
|
|
7277
6392
|
const rssPages = parseInt(statm[1] ?? "0", 10);
|
|
7278
6393
|
if (Number.isFinite(rssPages)) rssMb = Math.round(rssPages * 4096 / (1024 * 1024));
|
|
7279
6394
|
} catch {
|
|
@@ -7306,21 +6421,21 @@ function sampleProcState(pid) {
|
|
|
7306
6421
|
return `proc_err=${JSON.stringify(msg.slice(0, 60))}`;
|
|
7307
6422
|
}
|
|
7308
6423
|
}
|
|
7309
|
-
var
|
|
7310
|
-
var ACCOUNTS_DIR =
|
|
7311
|
-
if (!
|
|
6424
|
+
var PLATFORM_ROOT3 = process.env.MAXY_PLATFORM_ROOT ?? resolve6(process.cwd(), "..");
|
|
6425
|
+
var ACCOUNTS_DIR = resolve6(PLATFORM_ROOT3, "..", "data/accounts");
|
|
6426
|
+
if (!existsSync5(PLATFORM_ROOT3)) {
|
|
7312
6427
|
throw new Error(
|
|
7313
|
-
`PLATFORM_ROOT does not exist: ${
|
|
6428
|
+
`PLATFORM_ROOT does not exist: ${PLATFORM_ROOT3}
|
|
7314
6429
|
Set the MAXY_PLATFORM_ROOT environment variable to the absolute path of the platform directory.`
|
|
7315
6430
|
);
|
|
7316
6431
|
}
|
|
7317
6432
|
function resolveAccount() {
|
|
7318
|
-
if (!
|
|
7319
|
-
const usersFilePath =
|
|
6433
|
+
if (!existsSync5(ACCOUNTS_DIR)) return null;
|
|
6434
|
+
const usersFilePath = resolve6(PLATFORM_ROOT3, "config", "users.json");
|
|
7320
6435
|
let usersJsonUserId = null;
|
|
7321
|
-
if (
|
|
6436
|
+
if (existsSync5(usersFilePath)) {
|
|
7322
6437
|
try {
|
|
7323
|
-
const raw2 =
|
|
6438
|
+
const raw2 = readFileSync6(usersFilePath, "utf-8").trim();
|
|
7324
6439
|
if (raw2) {
|
|
7325
6440
|
const users = JSON.parse(raw2);
|
|
7326
6441
|
if (users.length > 0) {
|
|
@@ -7334,9 +6449,9 @@ function resolveAccount() {
|
|
|
7334
6449
|
let fallback = null;
|
|
7335
6450
|
for (const entry of entries) {
|
|
7336
6451
|
if (!entry.isDirectory()) continue;
|
|
7337
|
-
const configPath2 =
|
|
7338
|
-
if (!
|
|
7339
|
-
const raw2 =
|
|
6452
|
+
const configPath2 = resolve6(ACCOUNTS_DIR, entry.name, "account.json");
|
|
6453
|
+
if (!existsSync5(configPath2)) continue;
|
|
6454
|
+
const raw2 = readFileSync6(configPath2, "utf-8");
|
|
7340
6455
|
let config;
|
|
7341
6456
|
try {
|
|
7342
6457
|
config = JSON.parse(raw2);
|
|
@@ -7351,7 +6466,7 @@ function resolveAccount() {
|
|
|
7351
6466
|
}
|
|
7352
6467
|
const result = {
|
|
7353
6468
|
accountId: config.accountId,
|
|
7354
|
-
accountDir:
|
|
6469
|
+
accountDir: resolve6(ACCOUNTS_DIR, entry.name),
|
|
7355
6470
|
config
|
|
7356
6471
|
};
|
|
7357
6472
|
if (usersJsonUserId && config.admins?.some((a) => a.userId === usersJsonUserId)) {
|
|
@@ -7369,9 +6484,9 @@ function resolveAccount() {
|
|
|
7369
6484
|
return fallback;
|
|
7370
6485
|
}
|
|
7371
6486
|
function readAgentFile(accountDir, agentName, filename) {
|
|
7372
|
-
const filePath =
|
|
7373
|
-
if (!
|
|
7374
|
-
return
|
|
6487
|
+
const filePath = resolve6(accountDir, "agents", agentName, filename);
|
|
6488
|
+
if (!existsSync5(filePath)) return null;
|
|
6489
|
+
return readFileSync6(filePath, "utf-8");
|
|
7375
6490
|
}
|
|
7376
6491
|
function readIdentity(accountDir, agentName) {
|
|
7377
6492
|
return readAgentFile(accountDir, agentName, "IDENTITY.md");
|
|
@@ -7406,14 +6521,14 @@ function validateAgentSlug(slug) {
|
|
|
7406
6521
|
return true;
|
|
7407
6522
|
}
|
|
7408
6523
|
function resolveDefaultAgentSlug(accountDir) {
|
|
7409
|
-
const configPath2 =
|
|
7410
|
-
if (!
|
|
6524
|
+
const configPath2 = resolve6(accountDir, "account.json");
|
|
6525
|
+
if (!existsSync5(configPath2)) {
|
|
7411
6526
|
console.error("[agent-resolve] account.json not found \u2014 cannot resolve defaultAgent");
|
|
7412
6527
|
return null;
|
|
7413
6528
|
}
|
|
7414
6529
|
let config;
|
|
7415
6530
|
try {
|
|
7416
|
-
config = JSON.parse(
|
|
6531
|
+
config = JSON.parse(readFileSync6(configPath2, "utf-8"));
|
|
7417
6532
|
} catch (err) {
|
|
7418
6533
|
console.error("[agent-resolve] failed to read account.json:", err);
|
|
7419
6534
|
return null;
|
|
@@ -7422,8 +6537,8 @@ function resolveDefaultAgentSlug(accountDir) {
|
|
|
7422
6537
|
console.error("[agent-resolve] defaultAgent not configured in account.json \u2014 set it via the connect-whatsapp skill");
|
|
7423
6538
|
return null;
|
|
7424
6539
|
}
|
|
7425
|
-
const agentConfigPath =
|
|
7426
|
-
if (!
|
|
6540
|
+
const agentConfigPath = resolve6(accountDir, "agents", config.defaultAgent, "config.json");
|
|
6541
|
+
if (!existsSync5(agentConfigPath)) {
|
|
7427
6542
|
console.error(`[agent-resolve] defaultAgent="${config.defaultAgent}" has no config.json at ${agentConfigPath}`);
|
|
7428
6543
|
return null;
|
|
7429
6544
|
}
|
|
@@ -7495,23 +6610,23 @@ function resolveAgentConfig(accountDir, agentName) {
|
|
|
7495
6610
|
}
|
|
7496
6611
|
let knowledge = null;
|
|
7497
6612
|
let knowledgeBaked = false;
|
|
7498
|
-
const agentDir =
|
|
7499
|
-
const knowledgePath =
|
|
7500
|
-
const summaryPath =
|
|
7501
|
-
const hasKnowledge =
|
|
7502
|
-
const hasSummary =
|
|
6613
|
+
const agentDir = resolve6(accountDir, "agents", agentName);
|
|
6614
|
+
const knowledgePath = resolve6(agentDir, "KNOWLEDGE.md");
|
|
6615
|
+
const summaryPath = resolve6(agentDir, "KNOWLEDGE-SUMMARY.md");
|
|
6616
|
+
const hasKnowledge = existsSync5(knowledgePath);
|
|
6617
|
+
const hasSummary = existsSync5(summaryPath);
|
|
7503
6618
|
if (hasKnowledge && hasSummary) {
|
|
7504
6619
|
const knowledgeMtime = statSync3(knowledgePath).mtimeMs;
|
|
7505
6620
|
const summaryMtime = statSync3(summaryPath).mtimeMs;
|
|
7506
6621
|
if (summaryMtime >= knowledgeMtime) {
|
|
7507
|
-
knowledge =
|
|
6622
|
+
knowledge = readFileSync6(summaryPath, "utf-8");
|
|
7508
6623
|
} else {
|
|
7509
6624
|
console.warn(`[agent-config] ${agentName}: KNOWLEDGE-SUMMARY.md is stale (KNOWLEDGE.md is newer) \u2014 using full knowledge`);
|
|
7510
|
-
knowledge =
|
|
6625
|
+
knowledge = readFileSync6(knowledgePath, "utf-8");
|
|
7511
6626
|
}
|
|
7512
6627
|
knowledgeBaked = true;
|
|
7513
6628
|
} else if (hasKnowledge) {
|
|
7514
|
-
knowledge =
|
|
6629
|
+
knowledge = readFileSync6(knowledgePath, "utf-8");
|
|
7515
6630
|
knowledgeBaked = true;
|
|
7516
6631
|
}
|
|
7517
6632
|
let budget = null;
|
|
@@ -7533,11 +6648,11 @@ function resolveAgentConfig(accountDir, agentName) {
|
|
|
7533
6648
|
return { model, plugins, status, displayName, image, imageShape, showAgentName, knowledge, knowledgeBaked, liveMemory, knowledgeKeywords, budget, accessMode };
|
|
7534
6649
|
}
|
|
7535
6650
|
function parsePluginFrontmatter(pluginDir) {
|
|
7536
|
-
const pluginPath =
|
|
7537
|
-
if (!
|
|
6651
|
+
const pluginPath = resolve6(PLATFORM_ROOT3, "plugins", pluginDir, "PLUGIN.md");
|
|
6652
|
+
if (!existsSync5(pluginPath)) return null;
|
|
7538
6653
|
let raw2;
|
|
7539
6654
|
try {
|
|
7540
|
-
raw2 =
|
|
6655
|
+
raw2 = readFileSync6(pluginPath, "utf-8");
|
|
7541
6656
|
} catch {
|
|
7542
6657
|
console.warn(`[plugins] cannot read ${pluginPath}`);
|
|
7543
6658
|
return null;
|
|
@@ -7596,24 +6711,24 @@ function parsePluginFrontmatter(pluginDir) {
|
|
|
7596
6711
|
function autoDeliverPremiumPlugins(purchasedPlugins) {
|
|
7597
6712
|
if (!purchasedPlugins || purchasedPlugins.length === 0) return;
|
|
7598
6713
|
const TAG19 = "[premium-auto-deliver]";
|
|
7599
|
-
const stagingRoot =
|
|
7600
|
-
const pluginsDir =
|
|
7601
|
-
if (!
|
|
6714
|
+
const stagingRoot = resolve6(PLATFORM_ROOT3, "../premium-plugins");
|
|
6715
|
+
const pluginsDir = resolve6(PLATFORM_ROOT3, "plugins");
|
|
6716
|
+
if (!existsSync5(stagingRoot)) {
|
|
7602
6717
|
console.log(`${TAG19} no staging directory \u2014 skipping`);
|
|
7603
6718
|
return;
|
|
7604
6719
|
}
|
|
7605
6720
|
for (const pluginName of purchasedPlugins) {
|
|
7606
|
-
const stagingDir =
|
|
7607
|
-
if (!
|
|
6721
|
+
const stagingDir = resolve6(stagingRoot, pluginName);
|
|
6722
|
+
if (!existsSync5(stagingDir)) {
|
|
7608
6723
|
console.log(`${TAG19} ${pluginName}: not in staging \u2014 skipping`);
|
|
7609
6724
|
continue;
|
|
7610
6725
|
}
|
|
7611
|
-
const bundlePath =
|
|
7612
|
-
const isBundle =
|
|
6726
|
+
const bundlePath = join3(stagingDir, "BUNDLE.md");
|
|
6727
|
+
const isBundle = existsSync5(bundlePath);
|
|
7613
6728
|
if (isBundle) {
|
|
7614
6729
|
let bundleRaw;
|
|
7615
6730
|
try {
|
|
7616
|
-
bundleRaw =
|
|
6731
|
+
bundleRaw = readFileSync6(bundlePath, "utf-8");
|
|
7617
6732
|
} catch (err) {
|
|
7618
6733
|
console.log(`${TAG19} ${pluginName}: cannot read BUNDLE.md \u2014 ${err instanceof Error ? err.message : String(err)}`);
|
|
7619
6734
|
continue;
|
|
@@ -7644,13 +6759,13 @@ function autoDeliverPremiumPlugins(purchasedPlugins) {
|
|
|
7644
6759
|
let delivered = 0;
|
|
7645
6760
|
let skipped = 0;
|
|
7646
6761
|
for (const sub of subPlugins) {
|
|
7647
|
-
const target =
|
|
7648
|
-
if (
|
|
6762
|
+
const target = resolve6(pluginsDir, sub);
|
|
6763
|
+
if (existsSync5(resolve6(target, "PLUGIN.md"))) {
|
|
7649
6764
|
skipped++;
|
|
7650
6765
|
continue;
|
|
7651
6766
|
}
|
|
7652
|
-
const source =
|
|
7653
|
-
if (!
|
|
6767
|
+
const source = resolve6(stagingDir, "plugins", sub);
|
|
6768
|
+
if (!existsSync5(source)) {
|
|
7654
6769
|
console.log(`${TAG19} ${pluginName}/${sub}: source missing in staging \u2014 skipping`);
|
|
7655
6770
|
continue;
|
|
7656
6771
|
}
|
|
@@ -7663,8 +6778,8 @@ function autoDeliverPremiumPlugins(purchasedPlugins) {
|
|
|
7663
6778
|
}
|
|
7664
6779
|
console.log(`${TAG19} ${pluginName} (bundle): ${delivered} delivered, ${skipped} already present`);
|
|
7665
6780
|
} else {
|
|
7666
|
-
const target =
|
|
7667
|
-
if (
|
|
6781
|
+
const target = resolve6(pluginsDir, pluginName);
|
|
6782
|
+
if (existsSync5(resolve6(target, "PLUGIN.md"))) {
|
|
7668
6783
|
console.log(`${TAG19} ${pluginName}: already present \u2014 skipping`);
|
|
7669
6784
|
continue;
|
|
7670
6785
|
}
|
|
@@ -7704,21 +6819,21 @@ function migratePluginRenames(accountDir, config) {
|
|
|
7704
6819
|
return name;
|
|
7705
6820
|
});
|
|
7706
6821
|
if (!changed) return;
|
|
7707
|
-
const configPath2 =
|
|
6822
|
+
const configPath2 = resolve6(accountDir, "account.json");
|
|
7708
6823
|
try {
|
|
7709
|
-
const raw2 =
|
|
6824
|
+
const raw2 = readFileSync6(configPath2, "utf-8");
|
|
7710
6825
|
const parsed = JSON.parse(raw2);
|
|
7711
6826
|
parsed.enabledPlugins = migrated;
|
|
7712
|
-
|
|
6827
|
+
writeFileSync5(configPath2, JSON.stringify(parsed, null, 2) + "\n");
|
|
7713
6828
|
config.enabledPlugins = migrated;
|
|
7714
6829
|
console.log(`${TAG19} account.json updated (${migrated.length} plugins)`);
|
|
7715
6830
|
} catch (err) {
|
|
7716
6831
|
console.error(`${TAG19} failed to update account.json \u2014 ${err instanceof Error ? err.message : String(err)}`);
|
|
7717
6832
|
}
|
|
7718
|
-
const pluginsDir =
|
|
6833
|
+
const pluginsDir = resolve6(PLATFORM_ROOT3, "plugins");
|
|
7719
6834
|
for (const oldName of Object.keys(PLUGIN_RENAMES)) {
|
|
7720
|
-
const orphan =
|
|
7721
|
-
if (
|
|
6835
|
+
const orphan = resolve6(pluginsDir, oldName);
|
|
6836
|
+
if (existsSync5(orphan)) {
|
|
7722
6837
|
try {
|
|
7723
6838
|
rmSync2(orphan, { recursive: true });
|
|
7724
6839
|
console.log(`${TAG19} removed orphan: ${oldName}`);
|
|
@@ -7731,22 +6846,22 @@ function migratePluginRenames(accountDir, config) {
|
|
|
7731
6846
|
function autoDeliverBundleAgents(accountDir, purchasedPlugins) {
|
|
7732
6847
|
if (!purchasedPlugins || purchasedPlugins.length === 0) return;
|
|
7733
6848
|
const TAG19 = "[bundle-agent-deliver]";
|
|
7734
|
-
const stagingRoot =
|
|
7735
|
-
const specialistsDir =
|
|
7736
|
-
if (!
|
|
7737
|
-
if (!
|
|
7738
|
-
|
|
6849
|
+
const stagingRoot = resolve6(PLATFORM_ROOT3, "../premium-plugins");
|
|
6850
|
+
const specialistsDir = resolve6(accountDir, "specialists", "agents");
|
|
6851
|
+
if (!existsSync5(stagingRoot)) return;
|
|
6852
|
+
if (!existsSync5(specialistsDir)) {
|
|
6853
|
+
mkdirSync5(specialistsDir, { recursive: true });
|
|
7739
6854
|
}
|
|
7740
|
-
const agentsmdPath =
|
|
6855
|
+
const agentsmdPath = resolve6(accountDir, "agents", "admin", "AGENTS.md");
|
|
7741
6856
|
let agentsmd = "";
|
|
7742
6857
|
try {
|
|
7743
|
-
agentsmd =
|
|
6858
|
+
agentsmd = existsSync5(agentsmdPath) ? readFileSync6(agentsmdPath, "utf-8") : "";
|
|
7744
6859
|
} catch {
|
|
7745
6860
|
}
|
|
7746
6861
|
let delivered = 0;
|
|
7747
6862
|
for (const pluginName of purchasedPlugins) {
|
|
7748
|
-
const bundleAgentsDir =
|
|
7749
|
-
if (!
|
|
6863
|
+
const bundleAgentsDir = resolve6(stagingRoot, pluginName, "agents");
|
|
6864
|
+
if (!existsSync5(bundleAgentsDir)) continue;
|
|
7750
6865
|
let entries;
|
|
7751
6866
|
try {
|
|
7752
6867
|
entries = readdirSync2(bundleAgentsDir).filter((f) => f.endsWith(".md"));
|
|
@@ -7754,9 +6869,9 @@ function autoDeliverBundleAgents(accountDir, purchasedPlugins) {
|
|
|
7754
6869
|
continue;
|
|
7755
6870
|
}
|
|
7756
6871
|
for (const filename of entries) {
|
|
7757
|
-
const target =
|
|
7758
|
-
if (
|
|
7759
|
-
const source =
|
|
6872
|
+
const target = resolve6(specialistsDir, filename);
|
|
6873
|
+
if (existsSync5(target)) continue;
|
|
6874
|
+
const source = resolve6(bundleAgentsDir, filename);
|
|
7760
6875
|
try {
|
|
7761
6876
|
cpSync(source, target);
|
|
7762
6877
|
} catch (err) {
|
|
@@ -7764,7 +6879,7 @@ function autoDeliverBundleAgents(accountDir, purchasedPlugins) {
|
|
|
7764
6879
|
continue;
|
|
7765
6880
|
}
|
|
7766
6881
|
try {
|
|
7767
|
-
const content =
|
|
6882
|
+
const content = readFileSync6(target, "utf-8");
|
|
7768
6883
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
7769
6884
|
if (fmMatch) {
|
|
7770
6885
|
const nameMatch = fmMatch[1].match(/^name:\s*(.+)/m);
|
|
@@ -7788,7 +6903,7 @@ function autoDeliverBundleAgents(accountDir, purchasedPlugins) {
|
|
|
7788
6903
|
}
|
|
7789
6904
|
if (delivered > 0) {
|
|
7790
6905
|
try {
|
|
7791
|
-
|
|
6906
|
+
writeFileSync5(agentsmdPath, agentsmd);
|
|
7792
6907
|
console.log(`${TAG19} AGENTS.md updated (${delivered} agents added)`);
|
|
7793
6908
|
} catch (err) {
|
|
7794
6909
|
console.error(`${TAG19} AGENTS.md update failed \u2014 ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -7796,11 +6911,11 @@ function autoDeliverBundleAgents(accountDir, purchasedPlugins) {
|
|
|
7796
6911
|
}
|
|
7797
6912
|
}
|
|
7798
6913
|
function assemblePublicPluginContent(pluginDir) {
|
|
7799
|
-
const pluginRoot =
|
|
7800
|
-
const pluginPath =
|
|
6914
|
+
const pluginRoot = resolve6(PLATFORM_ROOT3, "plugins", pluginDir);
|
|
6915
|
+
const pluginPath = resolve6(pluginRoot, "PLUGIN.md");
|
|
7801
6916
|
let raw2;
|
|
7802
6917
|
try {
|
|
7803
|
-
raw2 =
|
|
6918
|
+
raw2 = readFileSync6(pluginPath, "utf-8");
|
|
7804
6919
|
} catch {
|
|
7805
6920
|
return null;
|
|
7806
6921
|
}
|
|
@@ -7809,7 +6924,7 @@ function assemblePublicPluginContent(pluginDir) {
|
|
|
7809
6924
|
const parts = [pluginBody];
|
|
7810
6925
|
let skillCount = 0;
|
|
7811
6926
|
let refCount = 0;
|
|
7812
|
-
const skillsDir =
|
|
6927
|
+
const skillsDir = resolve6(pluginRoot, "skills");
|
|
7813
6928
|
let skillDirs;
|
|
7814
6929
|
try {
|
|
7815
6930
|
skillDirs = readdirSync2(skillsDir).sort();
|
|
@@ -7817,11 +6932,11 @@ function assemblePublicPluginContent(pluginDir) {
|
|
|
7817
6932
|
return { body: pluginBody, skillCount: 0, refCount: 0 };
|
|
7818
6933
|
}
|
|
7819
6934
|
for (const skillName of skillDirs) {
|
|
7820
|
-
const skillDir =
|
|
7821
|
-
const skillMdPath =
|
|
6935
|
+
const skillDir = resolve6(skillsDir, skillName);
|
|
6936
|
+
const skillMdPath = resolve6(skillDir, "SKILL.md");
|
|
7822
6937
|
let skillRaw;
|
|
7823
6938
|
try {
|
|
7824
|
-
skillRaw =
|
|
6939
|
+
skillRaw = readFileSync6(skillMdPath, "utf-8");
|
|
7825
6940
|
} catch {
|
|
7826
6941
|
continue;
|
|
7827
6942
|
}
|
|
@@ -7861,7 +6976,7 @@ function assemblePublicPluginContent(pluginDir) {
|
|
|
7861
6976
|
parts.push(`
|
|
7862
6977
|
<!-- skill: ${skillName} -->`);
|
|
7863
6978
|
parts.push(skillBody);
|
|
7864
|
-
const refsDir =
|
|
6979
|
+
const refsDir = resolve6(skillDir, "references");
|
|
7865
6980
|
let refFiles;
|
|
7866
6981
|
try {
|
|
7867
6982
|
refFiles = readdirSync2(refsDir).filter((f) => f.endsWith(".md")).filter((f) => !publicExcludeReferences.includes(f)).sort();
|
|
@@ -7872,7 +6987,7 @@ function assemblePublicPluginContent(pluginDir) {
|
|
|
7872
6987
|
}
|
|
7873
6988
|
for (const refFile of refFiles) {
|
|
7874
6989
|
try {
|
|
7875
|
-
const refContent =
|
|
6990
|
+
const refContent = readFileSync6(resolve6(refsDir, refFile), "utf-8").trim();
|
|
7876
6991
|
if (refContent) {
|
|
7877
6992
|
parts.push(`
|
|
7878
6993
|
<!-- reference: ${refFile} -->`);
|
|
@@ -7890,7 +7005,7 @@ function assemblePublicPluginContent(pluginDir) {
|
|
|
7890
7005
|
return { body: parts.join("\n"), skillCount, refCount };
|
|
7891
7006
|
}
|
|
7892
7007
|
function loadEmbeddedPlugins(agentType, selectedPlugins, enabledPlugins) {
|
|
7893
|
-
const pluginsDir =
|
|
7008
|
+
const pluginsDir = resolve6(PLATFORM_ROOT3, "plugins");
|
|
7894
7009
|
let dirs;
|
|
7895
7010
|
try {
|
|
7896
7011
|
dirs = readdirSync2(pluginsDir);
|
|
@@ -7943,10 +7058,10 @@ function loadEmbeddedPlugins(agentType, selectedPlugins, enabledPlugins) {
|
|
|
7943
7058
|
console.log(`[plugins] loaded ${dir} for public (${assembled.body.length} chars, ${assembled.skillCount} skills, ${assembled.refCount} refs)`);
|
|
7944
7059
|
}
|
|
7945
7060
|
} else {
|
|
7946
|
-
const pluginPath =
|
|
7061
|
+
const pluginPath = resolve6(pluginsDir, dir, "PLUGIN.md");
|
|
7947
7062
|
let raw2;
|
|
7948
7063
|
try {
|
|
7949
|
-
raw2 =
|
|
7064
|
+
raw2 = readFileSync6(pluginPath, "utf-8");
|
|
7950
7065
|
} catch (err) {
|
|
7951
7066
|
console.warn(`[plugins] ${dir}: failed to read PLUGIN.md for ${agentType} embed: ${String(err)}`);
|
|
7952
7067
|
continue;
|
|
@@ -7970,14 +7085,14 @@ var mcpToolsCache = /* @__PURE__ */ new Map();
|
|
|
7970
7085
|
function fetchMcpToolsList(pluginDir) {
|
|
7971
7086
|
const cached = mcpToolsCache.get(pluginDir);
|
|
7972
7087
|
if (cached) return Promise.resolve(cached);
|
|
7973
|
-
const serverPath =
|
|
7974
|
-
if (!
|
|
7088
|
+
const serverPath = resolve6(PLATFORM_ROOT3, "plugins", pluginDir, "mcp/dist/index.js");
|
|
7089
|
+
if (!existsSync5(serverPath)) return Promise.resolve([]);
|
|
7975
7090
|
const startMs = Date.now();
|
|
7976
7091
|
return new Promise((resolvePromise) => {
|
|
7977
7092
|
const proc = spawn2(process.execPath, [serverPath], {
|
|
7978
7093
|
env: {
|
|
7979
7094
|
...process.env,
|
|
7980
|
-
PLATFORM_ROOT:
|
|
7095
|
+
PLATFORM_ROOT: PLATFORM_ROOT3,
|
|
7981
7096
|
ACCOUNT_ID: "__toolslist__",
|
|
7982
7097
|
PLATFORM_PORT: process.env.PORT ?? "19200"
|
|
7983
7098
|
}
|
|
@@ -8083,7 +7198,7 @@ var SPECIALIST_PLUGIN_DOMAINS = {
|
|
|
8083
7198
|
// agent, so it retains a full manifest entry for routing clarity.
|
|
8084
7199
|
};
|
|
8085
7200
|
async function buildPluginManifest(enabledPlugins) {
|
|
8086
|
-
const pluginsDir =
|
|
7201
|
+
const pluginsDir = resolve6(PLATFORM_ROOT3, "plugins");
|
|
8087
7202
|
let dirs;
|
|
8088
7203
|
try {
|
|
8089
7204
|
dirs = readdirSync2(pluginsDir);
|
|
@@ -8170,16 +7285,16 @@ ${specialist}: ${plugins.join(", ")}`);
|
|
|
8170
7285
|
for (let j = 0; j < adminPlugins.length; j++) {
|
|
8171
7286
|
const { dir, parsed } = adminPlugins[j];
|
|
8172
7287
|
const mcpTools = adminMcpResults[j];
|
|
8173
|
-
const pluginRoot =
|
|
7288
|
+
const pluginRoot = resolve6(pluginsDir, dir);
|
|
8174
7289
|
const skills = [];
|
|
8175
7290
|
const references = [];
|
|
8176
7291
|
const scanDir = (base, prefix, target) => {
|
|
8177
|
-
const scanPath =
|
|
8178
|
-
if (!
|
|
7292
|
+
const scanPath = resolve6(pluginRoot, base);
|
|
7293
|
+
if (!existsSync5(scanPath)) return;
|
|
8179
7294
|
try {
|
|
8180
7295
|
const walk = (current, rel) => {
|
|
8181
7296
|
for (const entry of readdirSync2(current)) {
|
|
8182
|
-
const full =
|
|
7297
|
+
const full = resolve6(current, entry);
|
|
8183
7298
|
try {
|
|
8184
7299
|
const stat5 = statSync3(full);
|
|
8185
7300
|
if (stat5.isDirectory()) {
|
|
@@ -8207,8 +7322,8 @@ ${specialist}: ${plugins.join(", ")}`);
|
|
|
8207
7322
|
toolLines.push(desc ? ` ${tool.name} \u2014 ${desc}` : ` ${tool.name}`);
|
|
8208
7323
|
}
|
|
8209
7324
|
} else if (parsed.tools.length > 0) {
|
|
8210
|
-
const serverPath =
|
|
8211
|
-
if (
|
|
7325
|
+
const serverPath = resolve6(PLATFORM_ROOT3, "plugins", dir, "mcp/dist/index.js");
|
|
7326
|
+
if (existsSync5(serverPath)) {
|
|
8212
7327
|
fallbackSourced++;
|
|
8213
7328
|
console.error(`[plugin-manifest] ${dir}: tools/list empty \u2014 fallback to frontmatter (${parsed.tools.length} tools)`);
|
|
8214
7329
|
}
|
|
@@ -8283,16 +7398,16 @@ function getDefaultAccountId() {
|
|
|
8283
7398
|
return resolveAccount()?.accountId ?? null;
|
|
8284
7399
|
}
|
|
8285
7400
|
function resolveUserAccounts(userId) {
|
|
8286
|
-
if (!
|
|
7401
|
+
if (!existsSync5(ACCOUNTS_DIR)) return [];
|
|
8287
7402
|
const results = [];
|
|
8288
7403
|
const entries = readdirSync2(ACCOUNTS_DIR, { withFileTypes: true });
|
|
8289
7404
|
for (const entry of entries) {
|
|
8290
7405
|
if (!entry.isDirectory()) continue;
|
|
8291
|
-
const configPath2 =
|
|
8292
|
-
if (!
|
|
7406
|
+
const configPath2 = resolve6(ACCOUNTS_DIR, entry.name, "account.json");
|
|
7407
|
+
if (!existsSync5(configPath2)) continue;
|
|
8293
7408
|
let config;
|
|
8294
7409
|
try {
|
|
8295
|
-
config = JSON.parse(
|
|
7410
|
+
config = JSON.parse(readFileSync6(configPath2, "utf-8"));
|
|
8296
7411
|
} catch {
|
|
8297
7412
|
console.error(`[session] account.json corrupt at ${configPath2} \u2014 skipping`);
|
|
8298
7413
|
continue;
|
|
@@ -8301,7 +7416,7 @@ function resolveUserAccounts(userId) {
|
|
|
8301
7416
|
if (adminEntry) {
|
|
8302
7417
|
results.push({
|
|
8303
7418
|
accountId: config.accountId,
|
|
8304
|
-
accountDir:
|
|
7419
|
+
accountDir: resolve6(ACCOUNTS_DIR, entry.name),
|
|
8305
7420
|
config,
|
|
8306
7421
|
role: adminEntry.role
|
|
8307
7422
|
});
|
|
@@ -8518,8 +7633,8 @@ function consumeStalledSubagents(sessionKey) {
|
|
|
8518
7633
|
return stalls && stalls.length > 0 ? stalls : void 0;
|
|
8519
7634
|
}
|
|
8520
7635
|
function streamLogPathFor(accountId, conversationId) {
|
|
8521
|
-
const logDir =
|
|
8522
|
-
const streamLogPath =
|
|
7636
|
+
const logDir = resolve6(ACCOUNTS_DIR, accountId, "logs");
|
|
7637
|
+
const streamLogPath = resolve6(logDir, `claude-agent-stream-${conversationId}.log`);
|
|
8523
7638
|
return { logDir, streamLogPath };
|
|
8524
7639
|
}
|
|
8525
7640
|
function buildSpawnEnv(accountId, accountDir, conversationId) {
|
|
@@ -8529,7 +7644,7 @@ function buildSpawnEnv(accountId, accountDir, conversationId) {
|
|
|
8529
7644
|
const { logDir, streamLogPath } = streamLogPathFor(accountId, conversationId);
|
|
8530
7645
|
return {
|
|
8531
7646
|
...process.env,
|
|
8532
|
-
PLATFORM_ROOT:
|
|
7647
|
+
PLATFORM_ROOT: PLATFORM_ROOT3,
|
|
8533
7648
|
ACCOUNT_DIR: accountDir,
|
|
8534
7649
|
ACCOUNT_ID: accountId,
|
|
8535
7650
|
LOG_DIR: logDir,
|
|
@@ -8540,8 +7655,8 @@ var cachedBrandHostname = null;
|
|
|
8540
7655
|
function readBrandHostname() {
|
|
8541
7656
|
if (cachedBrandHostname !== null) return cachedBrandHostname;
|
|
8542
7657
|
try {
|
|
8543
|
-
const brandPath =
|
|
8544
|
-
const parsed = JSON.parse(
|
|
7658
|
+
const brandPath = resolve6(PLATFORM_ROOT3, "config", "brand.json");
|
|
7659
|
+
const parsed = JSON.parse(readFileSync6(brandPath, "utf-8"));
|
|
8545
7660
|
cachedBrandHostname = typeof parsed.hostname === "string" && parsed.hostname.length > 0 ? parsed.hostname : "maxy";
|
|
8546
7661
|
} catch {
|
|
8547
7662
|
cachedBrandHostname = "maxy";
|
|
@@ -8570,7 +7685,7 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
8570
7685
|
const { logDir: LOG_DIR2, streamLogPath: STREAM_LOG_PATH } = streamLogPathFor(accountId, conversationId);
|
|
8571
7686
|
const baseEnv = {
|
|
8572
7687
|
ACCOUNT_ID: accountId,
|
|
8573
|
-
PLATFORM_ROOT:
|
|
7688
|
+
PLATFORM_ROOT: PLATFORM_ROOT3,
|
|
8574
7689
|
LOG_DIR: LOG_DIR2,
|
|
8575
7690
|
STREAM_LOG_PATH,
|
|
8576
7691
|
NEO4J_URI: requireNeo4jUri()
|
|
@@ -8578,37 +7693,37 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
8578
7693
|
const servers = {
|
|
8579
7694
|
"memory": {
|
|
8580
7695
|
command: "node",
|
|
8581
|
-
args: [
|
|
7696
|
+
args: [resolve6(PLATFORM_ROOT3, "plugins/memory/mcp/dist/index.js")],
|
|
8582
7697
|
env: { ...baseEnv, ...userId ? { USER_ID: userId } : {} }
|
|
8583
7698
|
},
|
|
8584
7699
|
"contacts": {
|
|
8585
7700
|
command: "node",
|
|
8586
|
-
args: [
|
|
7701
|
+
args: [resolve6(PLATFORM_ROOT3, "plugins/contacts/mcp/dist/index.js")],
|
|
8587
7702
|
env: { ...baseEnv }
|
|
8588
7703
|
},
|
|
8589
7704
|
"whatsapp": {
|
|
8590
7705
|
command: "node",
|
|
8591
|
-
args: [
|
|
7706
|
+
args: [resolve6(PLATFORM_ROOT3, "plugins/whatsapp/mcp/dist/index.js")],
|
|
8592
7707
|
env: { ...baseEnv, PLATFORM_PORT: process.env.PORT ?? "19200" }
|
|
8593
7708
|
},
|
|
8594
7709
|
"admin": {
|
|
8595
7710
|
command: "node",
|
|
8596
|
-
args: [
|
|
7711
|
+
args: [resolve6(PLATFORM_ROOT3, "plugins/admin/mcp/dist/index.js")],
|
|
8597
7712
|
env: { ...baseEnv, PLATFORM_PORT: process.env.PORT ?? "19200", ...userId ? { USER_ID: userId } : {} }
|
|
8598
7713
|
},
|
|
8599
7714
|
"scheduling": {
|
|
8600
7715
|
command: "node",
|
|
8601
|
-
args: [
|
|
7716
|
+
args: [resolve6(PLATFORM_ROOT3, "plugins/scheduling/mcp/dist/index.js")],
|
|
8602
7717
|
env: { ...baseEnv }
|
|
8603
7718
|
},
|
|
8604
7719
|
"tasks": {
|
|
8605
7720
|
command: "node",
|
|
8606
|
-
args: [
|
|
7721
|
+
args: [resolve6(PLATFORM_ROOT3, "plugins/tasks/mcp/dist/index.js")],
|
|
8607
7722
|
env: { ...baseEnv }
|
|
8608
7723
|
},
|
|
8609
7724
|
"email": {
|
|
8610
7725
|
command: "node",
|
|
8611
|
-
args: [
|
|
7726
|
+
args: [resolve6(PLATFORM_ROOT3, "plugins/email/mcp/dist/index.js")],
|
|
8612
7727
|
env: { ...baseEnv }
|
|
8613
7728
|
},
|
|
8614
7729
|
// Workflows MCP — persistent admin-session server for list/get/update/delete/
|
|
@@ -8619,7 +7734,7 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
8619
7734
|
// ToolSearches fruitlessly before degrading to a task-create stand-in (Task 571).
|
|
8620
7735
|
"workflows": {
|
|
8621
7736
|
command: "node",
|
|
8622
|
-
args: [
|
|
7737
|
+
args: [resolve6(PLATFORM_ROOT3, "plugins/workflows/mcp/dist/index.js")],
|
|
8623
7738
|
env: { ...baseEnv }
|
|
8624
7739
|
},
|
|
8625
7740
|
// Playwright MCP server — browser automation for browser-specialist.
|
|
@@ -8641,7 +7756,7 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
8641
7756
|
// MAXY-PRD.md:627, not in any application-layer filter).
|
|
8642
7757
|
"graph": {
|
|
8643
7758
|
command: "node",
|
|
8644
|
-
args: [
|
|
7759
|
+
args: [resolve6(PLATFORM_ROOT3, "lib/graph-mcp/dist/index.js")],
|
|
8645
7760
|
env: {
|
|
8646
7761
|
...baseEnv,
|
|
8647
7762
|
BRAND: readBrandHostname(),
|
|
@@ -8657,7 +7772,7 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
8657
7772
|
if (tgBotToken) {
|
|
8658
7773
|
servers["telegram"] = {
|
|
8659
7774
|
command: "node",
|
|
8660
|
-
args: [
|
|
7775
|
+
args: [resolve6(PLATFORM_ROOT3, "plugins/telegram/mcp/dist/index.js")],
|
|
8661
7776
|
env: { ...baseEnv, TELEGRAM_BOT_TOKEN: tgBotToken }
|
|
8662
7777
|
};
|
|
8663
7778
|
} else {
|
|
@@ -8665,11 +7780,11 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
8665
7780
|
}
|
|
8666
7781
|
servers["cloudflare"] = {
|
|
8667
7782
|
command: "node",
|
|
8668
|
-
args: [
|
|
7783
|
+
args: [resolve6(PLATFORM_ROOT3, "plugins/cloudflare/mcp/dist/index.js")],
|
|
8669
7784
|
env: { ...baseEnv, PLATFORM_PORT: process.env.PORT ?? "19200" }
|
|
8670
7785
|
};
|
|
8671
7786
|
if (Array.isArray(enabledPlugins) && enabledPlugins.length > 0) {
|
|
8672
|
-
const pluginsDir =
|
|
7787
|
+
const pluginsDir = resolve6(PLATFORM_ROOT3, "plugins");
|
|
8673
7788
|
let dirs;
|
|
8674
7789
|
try {
|
|
8675
7790
|
dirs = readdirSync2(pluginsDir);
|
|
@@ -8692,8 +7807,8 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
8692
7807
|
continue;
|
|
8693
7808
|
}
|
|
8694
7809
|
}
|
|
8695
|
-
const mcpEntry =
|
|
8696
|
-
if (!
|
|
7810
|
+
const mcpEntry = resolve6(PLATFORM_ROOT3, "plugins", dir, "mcp/dist/index.js");
|
|
7811
|
+
if (!existsSync5(mcpEntry)) continue;
|
|
8697
7812
|
servers[dir] = {
|
|
8698
7813
|
command: "node",
|
|
8699
7814
|
args: [mcpEntry],
|
|
@@ -8828,7 +7943,7 @@ var ADMIN_CORE_TOOLS = [
|
|
|
8828
7943
|
function getAdminAllowedTools(enabledPlugins) {
|
|
8829
7944
|
const tools = [...ADMIN_CORE_TOOLS];
|
|
8830
7945
|
if (Array.isArray(enabledPlugins) && enabledPlugins.length > 0) {
|
|
8831
|
-
const pluginsDir =
|
|
7946
|
+
const pluginsDir = resolve6(PLATFORM_ROOT3, "plugins");
|
|
8832
7947
|
let dirs;
|
|
8833
7948
|
try {
|
|
8834
7949
|
dirs = readdirSync2(pluginsDir);
|
|
@@ -8936,18 +8051,18 @@ ${message.slice(0, QUERY_CLASSIFIER_MSG_CAP)}`
|
|
|
8936
8051
|
}
|
|
8937
8052
|
}
|
|
8938
8053
|
async function fetchMemoryContext(accountId, query, sessionKey, options) {
|
|
8939
|
-
const serverPath =
|
|
8940
|
-
if (!
|
|
8054
|
+
const serverPath = resolve6(PLATFORM_ROOT3, "plugins/memory/mcp/dist/index.js");
|
|
8055
|
+
if (!existsSync5(serverPath)) {
|
|
8941
8056
|
console.error(`[fetchMemoryContext] MCP server not found: ${serverPath}`);
|
|
8942
8057
|
return null;
|
|
8943
8058
|
}
|
|
8944
8059
|
const startMs = Date.now();
|
|
8945
|
-
return new Promise((
|
|
8060
|
+
return new Promise((resolve29) => {
|
|
8946
8061
|
const proc = spawn2(process.execPath, [serverPath], {
|
|
8947
8062
|
env: {
|
|
8948
8063
|
...process.env,
|
|
8949
8064
|
ACCOUNT_ID: accountId,
|
|
8950
|
-
PLATFORM_ROOT:
|
|
8065
|
+
PLATFORM_ROOT: PLATFORM_ROOT3,
|
|
8951
8066
|
READ_ONLY: "true",
|
|
8952
8067
|
ALLOWED_SCOPES: "public,shared",
|
|
8953
8068
|
...sessionKey ? { SESSION_ID: sessionKey } : {},
|
|
@@ -8971,7 +8086,7 @@ async function fetchMemoryContext(accountId, query, sessionKey, options) {
|
|
|
8971
8086
|
} else {
|
|
8972
8087
|
console.error(`[fetchMemoryContext] failed: ${reason} (${elapsed}ms)${stderrBuf ? ` stderr: ${stderrBuf.slice(0, 500)}` : ""}`);
|
|
8973
8088
|
}
|
|
8974
|
-
|
|
8089
|
+
resolve29(value);
|
|
8975
8090
|
};
|
|
8976
8091
|
proc.stdout.on("data", (chunk) => {
|
|
8977
8092
|
buffer += chunk.toString();
|
|
@@ -9034,8 +8149,8 @@ async function fetchMemoryContext(accountId, query, sessionKey, options) {
|
|
|
9034
8149
|
}
|
|
9035
8150
|
async function compactTrimmedMessages(accountId, trimmedMessages) {
|
|
9036
8151
|
if (trimmedMessages.length === 0) return true;
|
|
9037
|
-
const serverPath =
|
|
9038
|
-
if (!
|
|
8152
|
+
const serverPath = resolve6(PLATFORM_ROOT3, "plugins/memory/mcp/dist/index.js");
|
|
8153
|
+
if (!existsSync5(serverPath)) return false;
|
|
9039
8154
|
const briefing = trimmedMessages.map((m) => `[${m.role.toUpperCase()}] ${m.content}`).join("\n\n");
|
|
9040
8155
|
return new Promise((resolvePromise) => {
|
|
9041
8156
|
const proc = spawn2(process.execPath, [serverPath], {
|
|
@@ -9352,8 +8467,8 @@ Then respond with only: [COMPACTED]`;
|
|
|
9352
8467
|
var COMPACTION_TIMEOUT_MS = 45e3;
|
|
9353
8468
|
async function* runCompactionTurn(accountDir, accountId, systemPrompt, resumeSessionId, adminModel, conversationId, enabledPlugins) {
|
|
9354
8469
|
const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, conversationId, void 0, enabledPlugins) });
|
|
9355
|
-
const specialistsDir =
|
|
9356
|
-
if (!
|
|
8470
|
+
const specialistsDir = resolve6(accountDir, "specialists");
|
|
8471
|
+
if (!existsSync5(specialistsDir)) agentLogStream("claude-agent-compaction-stream", accountDir, conversationId).write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
|
|
9357
8472
|
`);
|
|
9358
8473
|
const args = [
|
|
9359
8474
|
"--print",
|
|
@@ -9626,7 +8741,7 @@ async function* parseClaudeStream(proc, streamLog, adminModel, conversationId, a
|
|
|
9626
8741
|
const { logDir } = streamLogPathFor(accountId, conversationId);
|
|
9627
8742
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
9628
8743
|
for (const s of failed) {
|
|
9629
|
-
const stderrPath =
|
|
8744
|
+
const stderrPath = resolve6(logDir, `mcp-${s.name}-stderr-${date}.log`);
|
|
9630
8745
|
let tail = "(no stderr file)";
|
|
9631
8746
|
try {
|
|
9632
8747
|
const stats = statSync3(stderrPath);
|
|
@@ -10268,8 +9383,8 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
|
|
|
10268
9383
|
}
|
|
10269
9384
|
const ccUserId = sessionKey ? getUserIdForSession(sessionKey) : void 0;
|
|
10270
9385
|
const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, spawnConvId, ccUserId, enabledPlugins) });
|
|
10271
|
-
const specialistsDir =
|
|
10272
|
-
if (!
|
|
9386
|
+
const specialistsDir = resolve6(accountDir, "specialists");
|
|
9387
|
+
if (!existsSync5(specialistsDir)) agentLogStream("claude-agent-stream", accountDir, spawnConvId).write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
|
|
10273
9388
|
`);
|
|
10274
9389
|
const args = [
|
|
10275
9390
|
"--print",
|
|
@@ -10619,8 +9734,8 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
|
|
|
10619
9734
|
`);
|
|
10620
9735
|
const managedUserId = getUserIdForSession(sessionKey);
|
|
10621
9736
|
const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, managedConvId, managedUserId, enabledPlugins) });
|
|
10622
|
-
const specialistsDir =
|
|
10623
|
-
if (!
|
|
9737
|
+
const specialistsDir = resolve6(accountDir, "specialists");
|
|
9738
|
+
if (!existsSync5(specialistsDir)) streamLog.write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
|
|
10624
9739
|
`);
|
|
10625
9740
|
const fullMessage = attachments.length > 0 ? message + buildAttachmentMetaText(attachments) : message;
|
|
10626
9741
|
const args = [
|
|
@@ -11313,10 +10428,10 @@ ${sessionContext}`;
|
|
|
11313
10428
|
console.log(`[onboarding-inject] accountId=${accountId.slice(0, 8)}\u2026 error=neo4j-unreachable injected=false`);
|
|
11314
10429
|
} else if (onboardingStep < 8) {
|
|
11315
10430
|
const GENERIC_FALLBACK = "At every session start, call `onboarding-get`. If `currentStep` is less than 8, load the onboarding skill via `plugin-read` (find its path in the manifest under `admin`) and follow it \u2014 before any business setup. If `onboarding-get` fails (Neo4j unreachable), tell the user and skip onboarding for this session \u2014 it resumes automatically when the graph is available.";
|
|
11316
|
-
const skillPath =
|
|
10431
|
+
const skillPath = resolve6(PLATFORM_ROOT3, "plugins/admin/skills/onboarding/SKILL.md");
|
|
11317
10432
|
let skillContent = "";
|
|
11318
10433
|
try {
|
|
11319
|
-
skillContent =
|
|
10434
|
+
skillContent = readFileSync6(skillPath, "utf-8");
|
|
11320
10435
|
} catch (err) {
|
|
11321
10436
|
console.error(`[onboarding-inject] accountId=${accountId.slice(0, 8)}\u2026 error=skill-read-failed path=${skillPath} reason=${err instanceof Error ? err.message : String(err)}`);
|
|
11322
10437
|
}
|
|
@@ -11366,9 +10481,9 @@ ${body}`;
|
|
|
11366
10481
|
|
|
11367
10482
|
${manifest}`;
|
|
11368
10483
|
}
|
|
11369
|
-
const graphRefPath =
|
|
10484
|
+
const graphRefPath = resolve6(PLATFORM_ROOT3, "plugins/memory/references/graph-primitives.md");
|
|
11370
10485
|
try {
|
|
11371
|
-
const graphRef =
|
|
10486
|
+
const graphRef = readFileSync6(graphRefPath, "utf-8");
|
|
11372
10487
|
baseSystemPrompt += `
|
|
11373
10488
|
|
|
11374
10489
|
${graphRef}`;
|
|
@@ -11583,8 +10698,8 @@ var clientIpMiddleware = async (c, next) => {
|
|
|
11583
10698
|
};
|
|
11584
10699
|
|
|
11585
10700
|
// server/routes/health.ts
|
|
11586
|
-
import { existsSync as
|
|
11587
|
-
import { createConnection as
|
|
10701
|
+
import { existsSync as existsSync10, readFileSync as readFileSync11 } from "fs";
|
|
10702
|
+
import { createConnection as createConnection3 } from "net";
|
|
11588
10703
|
|
|
11589
10704
|
// app/lib/network.ts
|
|
11590
10705
|
import { networkInterfaces } from "os";
|
|
@@ -11608,8 +10723,8 @@ function getLanIp() {
|
|
|
11608
10723
|
import { basename as basename2 } from "path";
|
|
11609
10724
|
|
|
11610
10725
|
// app/lib/review-detector/rules.ts
|
|
11611
|
-
import { readFileSync as
|
|
11612
|
-
import { resolve as
|
|
10726
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync6, statSync as statSync4, mkdirSync as mkdirSync6, renameSync as renameSync2 } from "fs";
|
|
10727
|
+
import { resolve as resolve7, dirname as dirname3 } from "path";
|
|
11613
10728
|
var DEFAULT_SCAN_INTERVAL_MS = 5e3;
|
|
11614
10729
|
var RATE_LIMIT_PATTERN = "rate[- ]?limit(?:ed| reached| hit)|(?:HTTP|status)[^a-z]{0,3}429|too many requests";
|
|
11615
10730
|
var RATE_LIMIT_PATTERN_V1 = "\\b429\\b|rate.?limit|too.?many.?requests";
|
|
@@ -12038,12 +11153,12 @@ function defaultRules() {
|
|
|
12038
11153
|
];
|
|
12039
11154
|
}
|
|
12040
11155
|
function rulesFilePath(configDir2) {
|
|
12041
|
-
return
|
|
11156
|
+
return resolve7(configDir2, "review-rules.json");
|
|
12042
11157
|
}
|
|
12043
11158
|
function ensureRulesFile(configDir2) {
|
|
12044
11159
|
const path2 = rulesFilePath(configDir2);
|
|
12045
|
-
if (
|
|
12046
|
-
|
|
11160
|
+
if (existsSync6(path2)) return { created: false, path: path2 };
|
|
11161
|
+
mkdirSync6(dirname3(path2), { recursive: true });
|
|
12047
11162
|
const body = {
|
|
12048
11163
|
scanIntervalMs: DEFAULT_SCAN_INTERVAL_MS,
|
|
12049
11164
|
rules: defaultRules()
|
|
@@ -12053,10 +11168,10 @@ function ensureRulesFile(configDir2) {
|
|
|
12053
11168
|
}
|
|
12054
11169
|
function loadRules(configDir2) {
|
|
12055
11170
|
const path2 = rulesFilePath(configDir2);
|
|
12056
|
-
if (!
|
|
11171
|
+
if (!existsSync6(path2)) {
|
|
12057
11172
|
throw new Error(`rules file missing at ${path2}`);
|
|
12058
11173
|
}
|
|
12059
|
-
const raw2 =
|
|
11174
|
+
const raw2 = readFileSync7(path2, "utf-8");
|
|
12060
11175
|
let parsed;
|
|
12061
11176
|
try {
|
|
12062
11177
|
parsed = JSON.parse(raw2);
|
|
@@ -12079,7 +11194,7 @@ function saveRules(configDir2, file) {
|
|
|
12079
11194
|
}
|
|
12080
11195
|
function atomicWriteJson(path2, body) {
|
|
12081
11196
|
const tmp = `${path2}.tmp.${process.pid}.${Date.now()}`;
|
|
12082
|
-
|
|
11197
|
+
writeFileSync6(tmp, JSON.stringify(body, null, 2) + "\n", "utf-8");
|
|
12083
11198
|
renameSync2(tmp, path2);
|
|
12084
11199
|
}
|
|
12085
11200
|
function validateRulesFile(input, sourceLabel) {
|
|
@@ -12221,16 +11336,16 @@ function validateRule(input, label, seenIds) {
|
|
|
12221
11336
|
}
|
|
12222
11337
|
|
|
12223
11338
|
// app/lib/review-detector/sources.ts
|
|
12224
|
-
import { existsSync as
|
|
12225
|
-
import { resolve as
|
|
11339
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3, statSync as statSync5, writeFileSync as writeFileSync7, renameSync as renameSync3, mkdirSync as mkdirSync7, openSync as openSync3, readSync as readSync3, closeSync as closeSync3, readFileSync as readFileSync8 } from "fs";
|
|
11340
|
+
import { resolve as resolve8, join as join4, basename, dirname as dirname4 } from "path";
|
|
12226
11341
|
function tailStatePath(configDir2) {
|
|
12227
|
-
return
|
|
11342
|
+
return resolve8(configDir2, "review-state.json");
|
|
12228
11343
|
}
|
|
12229
11344
|
function loadTailState(configDir2) {
|
|
12230
11345
|
const path2 = tailStatePath(configDir2);
|
|
12231
|
-
if (!
|
|
11346
|
+
if (!existsSync7(path2)) return {};
|
|
12232
11347
|
try {
|
|
12233
|
-
const raw2 =
|
|
11348
|
+
const raw2 = readFileSync8(path2, "utf-8");
|
|
12234
11349
|
const parsed = JSON.parse(raw2);
|
|
12235
11350
|
if (!parsed || typeof parsed !== "object") return {};
|
|
12236
11351
|
const clean = {};
|
|
@@ -12248,26 +11363,26 @@ function loadTailState(configDir2) {
|
|
|
12248
11363
|
}
|
|
12249
11364
|
function saveTailState(configDir2, state) {
|
|
12250
11365
|
const path2 = tailStatePath(configDir2);
|
|
12251
|
-
|
|
11366
|
+
mkdirSync7(dirname4(path2), { recursive: true });
|
|
12252
11367
|
const tmp = `${path2}.tmp.${process.pid}.${Date.now()}`;
|
|
12253
|
-
|
|
11368
|
+
writeFileSync7(tmp, JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
12254
11369
|
renameSync3(tmp, path2);
|
|
12255
11370
|
}
|
|
12256
11371
|
function discoverSourceFiles(configDir2, accountLogDir2, logicalSource) {
|
|
12257
11372
|
if (logicalSource === "server") {
|
|
12258
|
-
const p =
|
|
12259
|
-
return
|
|
11373
|
+
const p = resolve8(configDir2, "logs", "server.log");
|
|
11374
|
+
return existsSync7(p) ? [{ logicalSource: "server", filepath: p }] : [];
|
|
12260
11375
|
}
|
|
12261
11376
|
if (logicalSource === "vnc") {
|
|
12262
|
-
const p =
|
|
12263
|
-
return
|
|
11377
|
+
const p = resolve8(configDir2, "logs", "vnc-boot.log");
|
|
11378
|
+
return existsSync7(p) ? [{ logicalSource: "vnc", filepath: p }] : [];
|
|
12264
11379
|
}
|
|
12265
11380
|
if (logicalSource === "cloudflared") {
|
|
12266
11381
|
const files2 = [];
|
|
12267
|
-
const daemon =
|
|
12268
|
-
if (
|
|
12269
|
-
const login =
|
|
12270
|
-
if (
|
|
11382
|
+
const daemon = resolve8(configDir2, "logs", "cloudflared.log");
|
|
11383
|
+
if (existsSync7(daemon)) files2.push({ logicalSource: "cloudflared", filepath: daemon });
|
|
11384
|
+
const login = resolve8(configDir2, "logs", "cloudflared-login.log");
|
|
11385
|
+
if (existsSync7(login)) files2.push({ logicalSource: "cloudflared", filepath: login });
|
|
12271
11386
|
return files2;
|
|
12272
11387
|
}
|
|
12273
11388
|
const prefix = {
|
|
@@ -12277,7 +11392,7 @@ function discoverSourceFiles(configDir2, accountLogDir2, logicalSource) {
|
|
|
12277
11392
|
public: "public-agent-stream-",
|
|
12278
11393
|
mcp: "mcp-"
|
|
12279
11394
|
}[logicalSource];
|
|
12280
|
-
if (!
|
|
11395
|
+
if (!existsSync7(accountLogDir2)) return [];
|
|
12281
11396
|
const files = [];
|
|
12282
11397
|
let scanned = 0;
|
|
12283
11398
|
let skippedPrefixMismatch = 0;
|
|
@@ -12287,7 +11402,7 @@ function discoverSourceFiles(configDir2, accountLogDir2, logicalSource) {
|
|
|
12287
11402
|
const matchesPrefix = entry.startsWith(prefix);
|
|
12288
11403
|
const isLog = entry.endsWith(".log");
|
|
12289
11404
|
if (matchesPrefix && isLog) {
|
|
12290
|
-
files.push({ logicalSource, filepath:
|
|
11405
|
+
files.push({ logicalSource, filepath: join4(accountLogDir2, entry) });
|
|
12291
11406
|
} else if (!matchesPrefix) {
|
|
12292
11407
|
skippedPrefixMismatch += 1;
|
|
12293
11408
|
} else {
|
|
@@ -12319,7 +11434,7 @@ function discoverAllSources(configDir2, accountLogDir2) {
|
|
|
12319
11434
|
];
|
|
12320
11435
|
}
|
|
12321
11436
|
function readNewLines(filepath, prev) {
|
|
12322
|
-
if (!
|
|
11437
|
+
if (!existsSync7(filepath)) return null;
|
|
12323
11438
|
const stat5 = statSync5(filepath);
|
|
12324
11439
|
const size = stat5.size;
|
|
12325
11440
|
const inode = stat5.ino;
|
|
@@ -12372,12 +11487,12 @@ function readNewLines(filepath, prev) {
|
|
|
12372
11487
|
}
|
|
12373
11488
|
}
|
|
12374
11489
|
function countRecentWrites(dir, sinceMs) {
|
|
12375
|
-
if (!
|
|
11490
|
+
if (!existsSync7(dir)) return 0;
|
|
12376
11491
|
let count = 0;
|
|
12377
11492
|
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
12378
11493
|
if (!entry.isFile()) continue;
|
|
12379
11494
|
try {
|
|
12380
|
-
const st = statSync5(
|
|
11495
|
+
const st = statSync5(join4(dir, entry.name));
|
|
12381
11496
|
if (st.mtimeMs >= sinceMs) count += 1;
|
|
12382
11497
|
} catch {
|
|
12383
11498
|
}
|
|
@@ -12385,7 +11500,7 @@ function countRecentWrites(dir, sinceMs) {
|
|
|
12385
11500
|
return count;
|
|
12386
11501
|
}
|
|
12387
11502
|
function fileLastWriteMs(path2) {
|
|
12388
|
-
if (!
|
|
11503
|
+
if (!existsSync7(path2)) return null;
|
|
12389
11504
|
try {
|
|
12390
11505
|
return statSync5(path2).mtimeMs;
|
|
12391
11506
|
} catch {
|
|
@@ -12393,31 +11508,31 @@ function fileLastWriteMs(path2) {
|
|
|
12393
11508
|
}
|
|
12394
11509
|
}
|
|
12395
11510
|
function accountLogDir(accountDir) {
|
|
12396
|
-
return
|
|
11511
|
+
return resolve8(accountDir, "logs");
|
|
12397
11512
|
}
|
|
12398
11513
|
function sourceKey(file) {
|
|
12399
11514
|
return `${file.logicalSource}:${basename(file.filepath)}`;
|
|
12400
11515
|
}
|
|
12401
11516
|
|
|
12402
11517
|
// app/lib/review-detector/writer.ts
|
|
12403
|
-
import { appendFileSync as
|
|
12404
|
-
import { resolve as
|
|
11518
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync9, writeFileSync as writeFileSync8, renameSync as renameSync4, statSync as statSync6 } from "fs";
|
|
11519
|
+
import { resolve as resolve9, dirname as dirname5 } from "path";
|
|
12405
11520
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
12406
11521
|
function reviewLogPath(configDir2) {
|
|
12407
|
-
return
|
|
11522
|
+
return resolve9(configDir2, "logs", "review.log");
|
|
12408
11523
|
}
|
|
12409
11524
|
function pendingAlertsPath(configDir2) {
|
|
12410
|
-
return
|
|
11525
|
+
return resolve9(configDir2, "review-pending-alerts.jsonl");
|
|
12411
11526
|
}
|
|
12412
11527
|
function reviewLog(configDir2, event) {
|
|
12413
11528
|
const path2 = reviewLogPath(configDir2);
|
|
12414
11529
|
try {
|
|
12415
|
-
|
|
11530
|
+
mkdirSync8(dirname5(path2), { recursive: true });
|
|
12416
11531
|
const line = `${new Date(
|
|
12417
11532
|
typeof event.ts === "number" ? event.ts : Date.now()
|
|
12418
11533
|
).toISOString()} [review] ${JSON.stringify(event)}
|
|
12419
11534
|
`;
|
|
12420
|
-
|
|
11535
|
+
appendFileSync3(path2, line, "utf-8");
|
|
12421
11536
|
} catch (err) {
|
|
12422
11537
|
console.error(`[review] failed to write review log at ${path2}: ${err instanceof Error ? err.message : String(err)}`);
|
|
12423
11538
|
}
|
|
@@ -12529,17 +11644,17 @@ async function upsertReviewAlert(accountId, match2) {
|
|
|
12529
11644
|
function queueAlert(configDir2, accountId, match2) {
|
|
12530
11645
|
const path2 = pendingAlertsPath(configDir2);
|
|
12531
11646
|
try {
|
|
12532
|
-
|
|
11647
|
+
mkdirSync8(dirname5(path2), { recursive: true });
|
|
12533
11648
|
const line = JSON.stringify({ accountId, match: match2 }) + "\n";
|
|
12534
|
-
|
|
11649
|
+
appendFileSync3(path2, line, "utf-8");
|
|
12535
11650
|
} catch (err) {
|
|
12536
11651
|
console.error(`[review] failed to queue alert at ${path2}: ${err instanceof Error ? err.message : String(err)}`);
|
|
12537
11652
|
}
|
|
12538
11653
|
}
|
|
12539
11654
|
async function drainPendingAlerts(configDir2) {
|
|
12540
11655
|
const path2 = pendingAlertsPath(configDir2);
|
|
12541
|
-
if (!
|
|
12542
|
-
const raw2 =
|
|
11656
|
+
if (!existsSync8(path2)) return { drained: 0, remaining: 0 };
|
|
11657
|
+
const raw2 = readFileSync9(path2, "utf-8");
|
|
12543
11658
|
const lines = raw2.split("\n").filter((l) => l.trim().length > 0);
|
|
12544
11659
|
if (lines.length === 0) return { drained: 0, remaining: 0 };
|
|
12545
11660
|
const remaining = [];
|
|
@@ -12560,9 +11675,9 @@ async function drainPendingAlerts(configDir2) {
|
|
|
12560
11675
|
}
|
|
12561
11676
|
const tmp = `${path2}.tmp.${process.pid}.${Date.now()}`;
|
|
12562
11677
|
if (remaining.length > 0) {
|
|
12563
|
-
|
|
11678
|
+
writeFileSync8(tmp, remaining.join("\n") + "\n", "utf-8");
|
|
12564
11679
|
} else {
|
|
12565
|
-
|
|
11680
|
+
writeFileSync8(tmp, "", "utf-8");
|
|
12566
11681
|
}
|
|
12567
11682
|
renameSync4(tmp, path2);
|
|
12568
11683
|
return { drained, remaining: remaining.length };
|
|
@@ -12662,7 +11777,7 @@ async function bootDetector() {
|
|
|
12662
11777
|
}
|
|
12663
11778
|
|
|
12664
11779
|
// app/lib/review-detector/scan-loop.ts
|
|
12665
|
-
import { resolve as
|
|
11780
|
+
import { resolve as resolve10 } from "path";
|
|
12666
11781
|
|
|
12667
11782
|
// app/lib/review-detector/evaluator.ts
|
|
12668
11783
|
var SAMPLE_MAX_CHARS = 500;
|
|
@@ -12956,14 +12071,14 @@ async function runScanCycle(runtime) {
|
|
|
12956
12071
|
match2 = result.match;
|
|
12957
12072
|
}
|
|
12958
12073
|
} else if (rule.type === "file-write-storm") {
|
|
12959
|
-
const dir =
|
|
12074
|
+
const dir = resolve10(runtime.configDir, rule.watchPath ?? "");
|
|
12960
12075
|
const sinceMs = cycleStart - rule.thresholdWindowMinutes * 6e4;
|
|
12961
12076
|
const count = countRecentWrites(dir, sinceMs);
|
|
12962
12077
|
const result = evaluateFileWriteStormRule(rule, count, state, cycleStart);
|
|
12963
12078
|
state = result.state;
|
|
12964
12079
|
match2 = result.match;
|
|
12965
12080
|
} else if (rule.type === "stale-log") {
|
|
12966
|
-
const trackedPath =
|
|
12081
|
+
const trackedPath = resolve10(runtime.configDir, rule.watchPath ?? "");
|
|
12967
12082
|
const lastMs = fileLastWriteMs(trackedPath);
|
|
12968
12083
|
const result = evaluateStaleLogRule(rule, lastMs, state, cycleStart);
|
|
12969
12084
|
state = result.state;
|
|
@@ -13202,20 +12317,20 @@ var WhatsAppConfigSchema = z.object({
|
|
|
13202
12317
|
});
|
|
13203
12318
|
|
|
13204
12319
|
// app/lib/whatsapp/config-persist.ts
|
|
13205
|
-
import { readFileSync as
|
|
13206
|
-
import { resolve as
|
|
12320
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync9, existsSync as existsSync9 } from "fs";
|
|
12321
|
+
import { resolve as resolve11, join as join5 } from "path";
|
|
13207
12322
|
var TAG3 = "[whatsapp:config]";
|
|
13208
12323
|
function configPath(accountDir) {
|
|
13209
|
-
return
|
|
12324
|
+
return resolve11(accountDir, "account.json");
|
|
13210
12325
|
}
|
|
13211
12326
|
function readConfig(accountDir) {
|
|
13212
12327
|
const path2 = configPath(accountDir);
|
|
13213
|
-
if (!
|
|
13214
|
-
return JSON.parse(
|
|
12328
|
+
if (!existsSync9(path2)) throw new Error(`account.json not found at ${path2}`);
|
|
12329
|
+
return JSON.parse(readFileSync10(path2, "utf-8"));
|
|
13215
12330
|
}
|
|
13216
12331
|
function writeConfig(accountDir, config) {
|
|
13217
12332
|
const path2 = configPath(accountDir);
|
|
13218
|
-
|
|
12333
|
+
writeFileSync9(path2, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
13219
12334
|
}
|
|
13220
12335
|
function reloadManagerConfig(accountDir) {
|
|
13221
12336
|
try {
|
|
@@ -13385,8 +12500,8 @@ function setPublicAgent(accountDir, slug) {
|
|
|
13385
12500
|
if (!trimmed) {
|
|
13386
12501
|
return { ok: false, error: "Agent slug cannot be empty." };
|
|
13387
12502
|
}
|
|
13388
|
-
const agentConfigPath =
|
|
13389
|
-
if (!
|
|
12503
|
+
const agentConfigPath = join5(accountDir, "agents", trimmed, "config.json");
|
|
12504
|
+
if (!existsSync9(agentConfigPath)) {
|
|
13390
12505
|
return { ok: false, error: `Agent "${trimmed}" not found \u2014 no config.json at ${agentConfigPath}. Check the agent slug and try again.` };
|
|
13391
12506
|
}
|
|
13392
12507
|
try {
|
|
@@ -13661,7 +12776,7 @@ var credsSaveQueue = Promise.resolve();
|
|
|
13661
12776
|
async function drainCredsSaveQueue(timeoutMs = 5e3) {
|
|
13662
12777
|
console.error(`${TAG5} draining credential save queue\u2026`);
|
|
13663
12778
|
const timer = new Promise(
|
|
13664
|
-
(
|
|
12779
|
+
(resolve29) => setTimeout(() => resolve29("timeout"), timeoutMs)
|
|
13665
12780
|
);
|
|
13666
12781
|
const result = await Promise.race([
|
|
13667
12782
|
credsSaveQueue.then(() => "drained"),
|
|
@@ -13789,11 +12904,11 @@ async function createWaSocket(opts) {
|
|
|
13789
12904
|
return sock;
|
|
13790
12905
|
}
|
|
13791
12906
|
async function waitForConnection(sock) {
|
|
13792
|
-
return new Promise((
|
|
12907
|
+
return new Promise((resolve29, reject) => {
|
|
13793
12908
|
const handler = (update) => {
|
|
13794
12909
|
if (update.connection === "open") {
|
|
13795
12910
|
sock.ev.off("connection.update", handler);
|
|
13796
|
-
|
|
12911
|
+
resolve29();
|
|
13797
12912
|
}
|
|
13798
12913
|
if (update.connection === "close") {
|
|
13799
12914
|
sock.ev.off("connection.update", handler);
|
|
@@ -13907,14 +13022,14 @@ ${inspected}`;
|
|
|
13907
13022
|
return inspect2(err, INSPECT_OPTS2);
|
|
13908
13023
|
}
|
|
13909
13024
|
function withTimeout(label, promise, timeoutMs) {
|
|
13910
|
-
return new Promise((
|
|
13025
|
+
return new Promise((resolve29, reject) => {
|
|
13911
13026
|
const timer = setTimeout(() => {
|
|
13912
13027
|
reject(new Error(`${label} timed out after ${timeoutMs}ms`));
|
|
13913
13028
|
}, timeoutMs);
|
|
13914
13029
|
promise.then(
|
|
13915
13030
|
(value) => {
|
|
13916
13031
|
clearTimeout(timer);
|
|
13917
|
-
|
|
13032
|
+
resolve29(value);
|
|
13918
13033
|
},
|
|
13919
13034
|
(err) => {
|
|
13920
13035
|
clearTimeout(timer);
|
|
@@ -14426,7 +13541,7 @@ async function sendMediaMessage(sock, to, media, opts) {
|
|
|
14426
13541
|
// app/lib/whatsapp/inbound/media.ts
|
|
14427
13542
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
14428
13543
|
import { writeFile, mkdir } from "fs/promises";
|
|
14429
|
-
import { join as
|
|
13544
|
+
import { join as join6 } from "path";
|
|
14430
13545
|
import {
|
|
14431
13546
|
downloadMediaMessage,
|
|
14432
13547
|
downloadContentFromMessage,
|
|
@@ -14512,7 +13627,7 @@ async function downloadInboundMedia(msg, sock, opts) {
|
|
|
14512
13627
|
await mkdir(MEDIA_DIR, { recursive: true });
|
|
14513
13628
|
const ext = mimeToExt(mimetype ?? "application/octet-stream");
|
|
14514
13629
|
const filename = `${randomUUID5()}.${ext}`;
|
|
14515
|
-
const filePath =
|
|
13630
|
+
const filePath = join6(MEDIA_DIR, filename);
|
|
14516
13631
|
await writeFile(filePath, buffer);
|
|
14517
13632
|
const sizeKB = (buffer.length / 1024).toFixed(0);
|
|
14518
13633
|
console.error(`${TAG9} media downloaded type=${mimetype ?? "unknown"} size=${sizeKB}KB path=${filePath}`);
|
|
@@ -14852,6 +13967,12 @@ function storeMessage(storeKey, entry) {
|
|
|
14852
13967
|
console.error(`${TAG13} message store trimmed for ${storeKey}: ${trimmed} oldest messages removed`);
|
|
14853
13968
|
}
|
|
14854
13969
|
}
|
|
13970
|
+
function deriveSessionKey(input) {
|
|
13971
|
+
if (input.isOwnerMirror || input.agentType === "admin") {
|
|
13972
|
+
return `whatsapp:${input.accountId}`;
|
|
13973
|
+
}
|
|
13974
|
+
return `whatsapp:${input.accountId}:${input.senderPhone}`;
|
|
13975
|
+
}
|
|
14855
13976
|
async function init(opts) {
|
|
14856
13977
|
if (initialized) {
|
|
14857
13978
|
console.error(`${TAG13} already initialized`);
|
|
@@ -15114,11 +14235,11 @@ async function connectWithReconnect(conn) {
|
|
|
15114
14235
|
console.error(
|
|
15115
14236
|
`${TAG13} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
|
|
15116
14237
|
);
|
|
15117
|
-
await new Promise((
|
|
15118
|
-
const timer = setTimeout(
|
|
14238
|
+
await new Promise((resolve29) => {
|
|
14239
|
+
const timer = setTimeout(resolve29, delay);
|
|
15119
14240
|
conn.abortController.signal.addEventListener("abort", () => {
|
|
15120
14241
|
clearTimeout(timer);
|
|
15121
|
-
|
|
14242
|
+
resolve29();
|
|
15122
14243
|
}, { once: true });
|
|
15123
14244
|
});
|
|
15124
14245
|
}
|
|
@@ -15126,16 +14247,16 @@ async function connectWithReconnect(conn) {
|
|
|
15126
14247
|
}
|
|
15127
14248
|
}
|
|
15128
14249
|
function waitForDisconnectEvent(conn) {
|
|
15129
|
-
return new Promise((
|
|
14250
|
+
return new Promise((resolve29) => {
|
|
15130
14251
|
if (!conn.sock) {
|
|
15131
|
-
|
|
14252
|
+
resolve29();
|
|
15132
14253
|
return;
|
|
15133
14254
|
}
|
|
15134
14255
|
const sock = conn.sock;
|
|
15135
14256
|
const handler = (update) => {
|
|
15136
14257
|
if (update.connection === "close") {
|
|
15137
14258
|
sock.ev.off("connection.update", handler);
|
|
15138
|
-
|
|
14259
|
+
resolve29();
|
|
15139
14260
|
}
|
|
15140
14261
|
};
|
|
15141
14262
|
sock.ev.on("connection.update", handler);
|
|
@@ -15317,7 +14438,12 @@ async function handleInboundMessage(conn, msg) {
|
|
|
15317
14438
|
isGroup: isGroup2,
|
|
15318
14439
|
groupJid: isGroup2 ? remoteJid : void 0,
|
|
15319
14440
|
reply: reply2,
|
|
15320
|
-
sessionKey:
|
|
14441
|
+
sessionKey: deriveSessionKey({
|
|
14442
|
+
agentType: "admin",
|
|
14443
|
+
accountId: conn.accountId,
|
|
14444
|
+
senderPhone: senderPhone2,
|
|
14445
|
+
isOwnerMirror: true
|
|
14446
|
+
}),
|
|
15321
14447
|
isOwnerMirror: true
|
|
15322
14448
|
});
|
|
15323
14449
|
return;
|
|
@@ -15345,8 +14471,8 @@ async function handleInboundMessage(conn, msg) {
|
|
|
15345
14471
|
const conversationKey = isGroup ? remoteJid : senderPhone;
|
|
15346
14472
|
const debounceKey = `${conn.accountId}:${conversationKey}:${senderPhone}`;
|
|
15347
14473
|
let resolvePending;
|
|
15348
|
-
const sttPending = new Promise((
|
|
15349
|
-
resolvePending =
|
|
14474
|
+
const sttPending = new Promise((resolve29) => {
|
|
14475
|
+
resolvePending = resolve29;
|
|
15350
14476
|
});
|
|
15351
14477
|
if (conn.debouncer) conn.debouncer.registerPending(debounceKey, sttPending);
|
|
15352
14478
|
try {
|
|
@@ -15396,7 +14522,11 @@ async function handleInboundMessage(conn, msg) {
|
|
|
15396
14522
|
if (!currentSock) throw new Error("WhatsApp disconnected \u2014 cannot reply");
|
|
15397
14523
|
await sendTextMessage(currentSock, remoteJid, text, { accountId: conn.accountId });
|
|
15398
14524
|
};
|
|
15399
|
-
const sessionKey =
|
|
14525
|
+
const sessionKey = deriveSessionKey({
|
|
14526
|
+
agentType: accessResult.agentType,
|
|
14527
|
+
accountId: conn.accountId,
|
|
14528
|
+
senderPhone
|
|
14529
|
+
});
|
|
15400
14530
|
conn.lastMessageAt = Date.now();
|
|
15401
14531
|
const payload = {
|
|
15402
14532
|
accountId: conn.accountId,
|
|
@@ -15453,20 +14583,20 @@ async function probeApiKey() {
|
|
|
15453
14583
|
return result.status;
|
|
15454
14584
|
}
|
|
15455
14585
|
function checkPort(port2, timeoutMs = 500) {
|
|
15456
|
-
return new Promise((
|
|
15457
|
-
const socket =
|
|
14586
|
+
return new Promise((resolve29) => {
|
|
14587
|
+
const socket = createConnection3(port2, "127.0.0.1");
|
|
15458
14588
|
socket.setTimeout(timeoutMs);
|
|
15459
14589
|
socket.once("connect", () => {
|
|
15460
14590
|
socket.destroy();
|
|
15461
|
-
|
|
14591
|
+
resolve29(true);
|
|
15462
14592
|
});
|
|
15463
14593
|
socket.once("error", () => {
|
|
15464
14594
|
socket.destroy();
|
|
15465
|
-
|
|
14595
|
+
resolve29(false);
|
|
15466
14596
|
});
|
|
15467
14597
|
socket.once("timeout", () => {
|
|
15468
14598
|
socket.destroy();
|
|
15469
|
-
|
|
14599
|
+
resolve29(false);
|
|
15470
14600
|
});
|
|
15471
14601
|
});
|
|
15472
14602
|
}
|
|
@@ -15489,8 +14619,8 @@ app.get("/", async (c) => {
|
|
|
15489
14619
|
const browserTransport = resolveBrowserTransport(c.req.raw, c.env?.incoming?.socket?.remoteAddress);
|
|
15490
14620
|
let pinConfigured = false;
|
|
15491
14621
|
try {
|
|
15492
|
-
if (
|
|
15493
|
-
const raw2 =
|
|
14622
|
+
if (existsSync10(USERS_FILE)) {
|
|
14623
|
+
const raw2 = readFileSync11(USERS_FILE, "utf-8").trim();
|
|
15494
14624
|
if (raw2) {
|
|
15495
14625
|
const users = JSON.parse(raw2);
|
|
15496
14626
|
pinConfigured = Array.isArray(users) && users.length > 0;
|
|
@@ -15510,7 +14640,7 @@ app.get("/", async (c) => {
|
|
|
15510
14640
|
const terminalReady = await probeTerminalReady();
|
|
15511
14641
|
let apiKeyConfigured = false;
|
|
15512
14642
|
try {
|
|
15513
|
-
apiKeyConfigured =
|
|
14643
|
+
apiKeyConfigured = existsSync10(keyFilePath());
|
|
15514
14644
|
} catch {
|
|
15515
14645
|
}
|
|
15516
14646
|
let apiKeyStatus = "missing";
|
|
@@ -15579,14 +14709,14 @@ app.get("/", async (c) => {
|
|
|
15579
14709
|
var health_default = app;
|
|
15580
14710
|
|
|
15581
14711
|
// server/routes/session.ts
|
|
15582
|
-
import { resolve as
|
|
15583
|
-
import { existsSync as
|
|
14712
|
+
import { resolve as resolve12 } from "path";
|
|
14713
|
+
import { existsSync as existsSync11, writeFileSync as writeFileSync10, mkdirSync as mkdirSync9 } from "fs";
|
|
15584
14714
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
15585
14715
|
function writeBrandingCache(accountId, agentSlug, branding) {
|
|
15586
14716
|
try {
|
|
15587
|
-
const cacheDir =
|
|
15588
|
-
|
|
15589
|
-
|
|
14717
|
+
const cacheDir = resolve12(MAXY_DIR, "branding-cache", accountId);
|
|
14718
|
+
mkdirSync9(cacheDir, { recursive: true });
|
|
14719
|
+
writeFileSync10(resolve12(cacheDir, `${agentSlug}.json`), JSON.stringify(branding), "utf-8");
|
|
15590
14720
|
} catch (err) {
|
|
15591
14721
|
console.error(`[branding] cache write failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
15592
14722
|
}
|
|
@@ -15656,9 +14786,9 @@ app2.post("/", async (c) => {
|
|
|
15656
14786
|
}
|
|
15657
14787
|
let agentConfig = null;
|
|
15658
14788
|
if (account) {
|
|
15659
|
-
const agentDir =
|
|
15660
|
-
const agentConfigPath =
|
|
15661
|
-
if (!
|
|
14789
|
+
const agentDir = resolve12(account.accountDir, "agents", agentSlug);
|
|
14790
|
+
const agentConfigPath = resolve12(agentDir, "config.json");
|
|
14791
|
+
if (!existsSync11(agentDir) || !existsSync11(agentConfigPath)) {
|
|
15662
14792
|
return c.json({ error: "Agent not found" }, 404);
|
|
15663
14793
|
}
|
|
15664
14794
|
agentConfig = resolveAgentConfig(account.accountDir, agentSlug);
|
|
@@ -15904,9 +15034,9 @@ ${raw2}`;
|
|
|
15904
15034
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
15905
15035
|
import { mkdir as mkdir2, readFile, stat as stat2, writeFile as writeFile2 } from "fs/promises";
|
|
15906
15036
|
import { realpathSync } from "fs";
|
|
15907
|
-
import { resolve as
|
|
15908
|
-
var
|
|
15909
|
-
var ATTACHMENTS_ROOT =
|
|
15037
|
+
import { resolve as resolve13, extname, basename as basename3 } from "path";
|
|
15038
|
+
var PLATFORM_ROOT4 = process.env.MAXY_PLATFORM_ROOT ?? resolve13(process.cwd(), "../platform");
|
|
15039
|
+
var ATTACHMENTS_ROOT = resolve13(PLATFORM_ROOT4, "..", "data/uploads");
|
|
15910
15040
|
var SUPPORTED_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
15911
15041
|
"image/jpeg",
|
|
15912
15042
|
"image/png",
|
|
@@ -15930,11 +15060,11 @@ function assertSupportedMime(mimeType) {
|
|
|
15930
15060
|
}
|
|
15931
15061
|
async function writeAttachment(scope, filename, mimeType, sizeBytes, buffer) {
|
|
15932
15062
|
const attachmentId = randomUUID6();
|
|
15933
|
-
const dir =
|
|
15063
|
+
const dir = resolve13(ATTACHMENTS_ROOT, scope, attachmentId);
|
|
15934
15064
|
await mkdir2(dir, { recursive: true });
|
|
15935
15065
|
const ext = extname(filename) || "";
|
|
15936
|
-
const storagePath =
|
|
15937
|
-
const metaPath =
|
|
15066
|
+
const storagePath = resolve13(dir, `${attachmentId}${ext}`);
|
|
15067
|
+
const metaPath = resolve13(dir, `${attachmentId}.meta.json`);
|
|
15938
15068
|
const meta = {
|
|
15939
15069
|
attachmentId,
|
|
15940
15070
|
scope,
|
|
@@ -16009,7 +15139,7 @@ async function storeGeneratedFile(accountId, filePath) {
|
|
|
16009
15139
|
// app/lib/stt/voice-note.ts
|
|
16010
15140
|
import { writeFile as writeFile3, mkdtemp, rm } from "fs/promises";
|
|
16011
15141
|
import { tmpdir } from "os";
|
|
16012
|
-
import { join as
|
|
15142
|
+
import { join as join7 } from "path";
|
|
16013
15143
|
var TAG14 = "[voice]";
|
|
16014
15144
|
var AUDIO_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
16015
15145
|
"audio/ogg",
|
|
@@ -16047,9 +15177,9 @@ async function transcribeVoiceNote(file, source) {
|
|
|
16047
15177
|
let tempDir;
|
|
16048
15178
|
let tempPath;
|
|
16049
15179
|
try {
|
|
16050
|
-
tempDir = await mkdtemp(
|
|
15180
|
+
tempDir = await mkdtemp(join7(tmpdir(), "voice-"));
|
|
16051
15181
|
const ext = audioExtension(mimeType);
|
|
16052
|
-
tempPath =
|
|
15182
|
+
tempPath = join7(tempDir, `recording${ext}`);
|
|
16053
15183
|
const buffer = Buffer.from(await file.arrayBuffer());
|
|
16054
15184
|
await writeFile3(tempPath, buffer);
|
|
16055
15185
|
} catch (err) {
|
|
@@ -16611,16 +15741,16 @@ var group_default = app4;
|
|
|
16611
15741
|
|
|
16612
15742
|
// app/lib/access-gate.ts
|
|
16613
15743
|
import neo4j2 from "neo4j-driver";
|
|
16614
|
-
import { readFileSync as
|
|
16615
|
-
import { resolve as
|
|
15744
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
15745
|
+
import { resolve as resolve14 } from "path";
|
|
16616
15746
|
import { randomUUID as randomUUID7, randomInt } from "crypto";
|
|
16617
|
-
var
|
|
15747
|
+
var PLATFORM_ROOT5 = process.env.MAXY_PLATFORM_ROOT ?? resolve14(process.cwd(), "..");
|
|
16618
15748
|
var driver2 = null;
|
|
16619
15749
|
function readPassword2() {
|
|
16620
15750
|
if (process.env.NEO4J_PASSWORD) return process.env.NEO4J_PASSWORD;
|
|
16621
|
-
const passwordFile =
|
|
15751
|
+
const passwordFile = resolve14(PLATFORM_ROOT5, "config/.neo4j-password");
|
|
16622
15752
|
try {
|
|
16623
|
-
return
|
|
15753
|
+
return readFileSync12(passwordFile, "utf-8").trim();
|
|
16624
15754
|
} catch {
|
|
16625
15755
|
throw new Error(
|
|
16626
15756
|
`Neo4j password not found. Expected at ${passwordFile} or in NEO4J_PASSWORD env var.`
|
|
@@ -16653,13 +15783,13 @@ process.on("SIGINT", async () => {
|
|
|
16653
15783
|
driver2 = null;
|
|
16654
15784
|
}
|
|
16655
15785
|
});
|
|
16656
|
-
var
|
|
15786
|
+
var rateLimitMap = /* @__PURE__ */ new Map();
|
|
16657
15787
|
var ACCESS_MAX_ATTEMPTS = 5;
|
|
16658
15788
|
var ACCESS_LOCKOUT_MS = 15 * 60 * 1e3;
|
|
16659
15789
|
var ACCESS_WINDOW_MS = 15 * 60 * 1e3;
|
|
16660
15790
|
function checkAccessRateLimit(ip, agentSlug) {
|
|
16661
15791
|
const key = `${ip}:${agentSlug}`;
|
|
16662
|
-
const entry =
|
|
15792
|
+
const entry = rateLimitMap.get(key);
|
|
16663
15793
|
if (!entry) return null;
|
|
16664
15794
|
const now = Date.now();
|
|
16665
15795
|
if (entry.lockedUntil && now < entry.lockedUntil) {
|
|
@@ -16668,7 +15798,7 @@ function checkAccessRateLimit(ip, agentSlug) {
|
|
|
16668
15798
|
return `Too many attempts. Try again in ${remainingS}s`;
|
|
16669
15799
|
}
|
|
16670
15800
|
if (now - entry.firstAttempt > ACCESS_WINDOW_MS) {
|
|
16671
|
-
|
|
15801
|
+
rateLimitMap.delete(key);
|
|
16672
15802
|
return null;
|
|
16673
15803
|
}
|
|
16674
15804
|
return null;
|
|
@@ -16676,9 +15806,9 @@ function checkAccessRateLimit(ip, agentSlug) {
|
|
|
16676
15806
|
function recordAccessFailedAttempt(ip, agentSlug) {
|
|
16677
15807
|
const key = `${ip}:${agentSlug}`;
|
|
16678
15808
|
const now = Date.now();
|
|
16679
|
-
const entry =
|
|
15809
|
+
const entry = rateLimitMap.get(key);
|
|
16680
15810
|
if (!entry || now - entry.firstAttempt > ACCESS_WINDOW_MS) {
|
|
16681
|
-
|
|
15811
|
+
rateLimitMap.set(key, { attempts: 1, firstAttempt: now });
|
|
16682
15812
|
return;
|
|
16683
15813
|
}
|
|
16684
15814
|
entry.attempts++;
|
|
@@ -16687,7 +15817,7 @@ function recordAccessFailedAttempt(ip, agentSlug) {
|
|
|
16687
15817
|
}
|
|
16688
15818
|
}
|
|
16689
15819
|
function clearAccessRateLimit(ip, agentSlug) {
|
|
16690
|
-
|
|
15820
|
+
rateLimitMap.delete(`${ip}:${agentSlug}`);
|
|
16691
15821
|
}
|
|
16692
15822
|
var forgotPwdMap = /* @__PURE__ */ new Map();
|
|
16693
15823
|
var FORGOT_PWD_MAX = 3;
|
|
@@ -16931,19 +16061,19 @@ async function findActiveGrantByContact(contactValue, agentSlug, accountId) {
|
|
|
16931
16061
|
}
|
|
16932
16062
|
|
|
16933
16063
|
// app/lib/brevo-sms.ts
|
|
16934
|
-
import { readFileSync as
|
|
16064
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync11, mkdirSync as mkdirSync10, existsSync as existsSync12, chmodSync } from "fs";
|
|
16935
16065
|
import { dirname as dirname6 } from "path";
|
|
16936
|
-
import { resolve as
|
|
16937
|
-
var BREVO_API_KEY_FILE =
|
|
16066
|
+
import { resolve as resolve15 } from "path";
|
|
16067
|
+
var BREVO_API_KEY_FILE = resolve15(MAXY_DIR, ".brevo-api-key");
|
|
16938
16068
|
var BREVO_API_URL = "https://api.brevo.com/v3/transactionalSMS/sms";
|
|
16939
16069
|
var BREVO_TIMEOUT_MS = 1e4;
|
|
16940
16070
|
var BREVO_SENDER = "Maxy";
|
|
16941
|
-
var
|
|
16942
|
-
if (
|
|
16071
|
+
var platformRoot = process.env.MAXY_PLATFORM_ROOT;
|
|
16072
|
+
if (platformRoot) {
|
|
16943
16073
|
try {
|
|
16944
|
-
const brandPath =
|
|
16945
|
-
if (
|
|
16946
|
-
const brand = JSON.parse(
|
|
16074
|
+
const brandPath = resolve15(platformRoot, "config", "brand.json");
|
|
16075
|
+
if (existsSync12(brandPath)) {
|
|
16076
|
+
const brand = JSON.parse(readFileSync13(brandPath, "utf-8"));
|
|
16947
16077
|
if (brand.productName) BREVO_SENDER = brand.productName;
|
|
16948
16078
|
}
|
|
16949
16079
|
} catch {
|
|
@@ -16951,7 +16081,7 @@ if (platformRoot2) {
|
|
|
16951
16081
|
}
|
|
16952
16082
|
function readBrevoApiKey() {
|
|
16953
16083
|
try {
|
|
16954
|
-
const key =
|
|
16084
|
+
const key = readFileSync13(BREVO_API_KEY_FILE, "utf-8").trim();
|
|
16955
16085
|
if (!key) {
|
|
16956
16086
|
throw new Error(`Brevo API key file is empty: ${BREVO_API_KEY_FILE}`);
|
|
16957
16087
|
}
|
|
@@ -16966,7 +16096,7 @@ function readBrevoApiKey() {
|
|
|
16966
16096
|
}
|
|
16967
16097
|
}
|
|
16968
16098
|
function hasBrevoApiKey() {
|
|
16969
|
-
return
|
|
16099
|
+
return existsSync12(BREVO_API_KEY_FILE);
|
|
16970
16100
|
}
|
|
16971
16101
|
async function sendSms(recipient, content, opts) {
|
|
16972
16102
|
let apiKey;
|
|
@@ -17338,8 +16468,8 @@ app5.post("/forgot-password", async (c) => {
|
|
|
17338
16468
|
app5.post("/send-otp", async (c) => {
|
|
17339
16469
|
const socketAddr = c.env?.incoming?.socket?.remoteAddress;
|
|
17340
16470
|
const hasXff = !!c.req.header("x-forwarded-for");
|
|
17341
|
-
const
|
|
17342
|
-
if (!
|
|
16471
|
+
const isLoopback = socketAddr === "127.0.0.1" || socketAddr === "::1" || socketAddr === "::ffff:127.0.0.1";
|
|
16472
|
+
if (!isLoopback || hasXff) {
|
|
17343
16473
|
console.error(`[access-gate] send-otp rejected: socket=${socketAddr ?? "unknown"} xff=${hasXff}`);
|
|
17344
16474
|
return c.json({ error: "Forbidden" }, 403);
|
|
17345
16475
|
}
|
|
@@ -17382,8 +16512,8 @@ app5.post("/send-otp", async (c) => {
|
|
|
17382
16512
|
var access_default = app5;
|
|
17383
16513
|
|
|
17384
16514
|
// server/routes/telegram.ts
|
|
17385
|
-
import { existsSync as
|
|
17386
|
-
import { timingSafeEqual
|
|
16515
|
+
import { existsSync as existsSync13, readFileSync as readFileSync14 } from "fs";
|
|
16516
|
+
import { timingSafeEqual } from "crypto";
|
|
17387
16517
|
|
|
17388
16518
|
// app/lib/telegram/access-control.ts
|
|
17389
16519
|
function checkTelegramAccess(params) {
|
|
@@ -17419,8 +16549,8 @@ var TELEGRAM_API = "https://api.telegram.org";
|
|
|
17419
16549
|
function getWebhookSecret(botType) {
|
|
17420
16550
|
const filePath = botType === "admin" ? TELEGRAM_ADMIN_WEBHOOK_SECRET_FILE : TELEGRAM_WEBHOOK_SECRET_FILE;
|
|
17421
16551
|
try {
|
|
17422
|
-
if (!
|
|
17423
|
-
const secret =
|
|
16552
|
+
if (!existsSync13(filePath)) return null;
|
|
16553
|
+
const secret = readFileSync14(filePath, "utf-8").trim();
|
|
17424
16554
|
return secret || null;
|
|
17425
16555
|
} catch {
|
|
17426
16556
|
return null;
|
|
@@ -17430,7 +16560,7 @@ function verifyWebhookSecret(headerValue, storedSecret) {
|
|
|
17430
16560
|
const a = Buffer.from(headerValue);
|
|
17431
16561
|
const b = Buffer.from(storedSecret);
|
|
17432
16562
|
if (a.length !== b.length) return false;
|
|
17433
|
-
return
|
|
16563
|
+
return timingSafeEqual(a, b);
|
|
17434
16564
|
}
|
|
17435
16565
|
async function handleInbound(params) {
|
|
17436
16566
|
const { chatId, senderId, text, botType, botToken, accountId, agentType } = params;
|
|
@@ -17578,9 +16708,9 @@ app6.post("/webhook", async (c) => {
|
|
|
17578
16708
|
var telegram_default = app6;
|
|
17579
16709
|
|
|
17580
16710
|
// server/routes/whatsapp.ts
|
|
17581
|
-
import { join as
|
|
16711
|
+
import { join as join8, resolve as resolve16, basename as basename4 } from "path";
|
|
17582
16712
|
import { readFile as readFile2, stat as stat3 } from "fs/promises";
|
|
17583
|
-
import { realpathSync as realpathSync2, readdirSync as readdirSync4, readFileSync as
|
|
16713
|
+
import { realpathSync as realpathSync2, readdirSync as readdirSync4, readFileSync as readFileSync15, existsSync as existsSync14 } from "fs";
|
|
17584
16714
|
|
|
17585
16715
|
// app/lib/whatsapp/login.ts
|
|
17586
16716
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
@@ -17686,8 +16816,8 @@ async function startLogin(opts) {
|
|
|
17686
16816
|
resetActiveLogin(accountId);
|
|
17687
16817
|
let resolveQr = null;
|
|
17688
16818
|
let rejectQr = null;
|
|
17689
|
-
const qrPromise = new Promise((
|
|
17690
|
-
resolveQr =
|
|
16819
|
+
const qrPromise = new Promise((resolve29, reject) => {
|
|
16820
|
+
resolveQr = resolve29;
|
|
17691
16821
|
rejectQr = reject;
|
|
17692
16822
|
});
|
|
17693
16823
|
const qrTimer = setTimeout(
|
|
@@ -17903,7 +17033,7 @@ function serializeWhatsAppSchema() {
|
|
|
17903
17033
|
|
|
17904
17034
|
// server/routes/whatsapp.ts
|
|
17905
17035
|
var TAG18 = "[whatsapp:api]";
|
|
17906
|
-
var
|
|
17036
|
+
var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
17907
17037
|
var app7 = new Hono2();
|
|
17908
17038
|
app7.get("/status", (c) => {
|
|
17909
17039
|
try {
|
|
@@ -17921,7 +17051,7 @@ app7.post("/login/start", async (c) => {
|
|
|
17921
17051
|
const body = await c.req.json().catch(() => ({}));
|
|
17922
17052
|
const accountId = validateAccountId(body.accountId);
|
|
17923
17053
|
const force = body.force ?? false;
|
|
17924
|
-
const authDir =
|
|
17054
|
+
const authDir = join8(MAXY_DIR, "credentials", "whatsapp", accountId);
|
|
17925
17055
|
const result = await startLogin({ accountId, authDir, force });
|
|
17926
17056
|
console.error(`${TAG18} login/start result account=${accountId} hasQr=${!!result.qrRaw}${result.selfPhone ? ` phone=${result.selfPhone}` : ""}`);
|
|
17927
17057
|
return c.json(result);
|
|
@@ -18058,17 +17188,17 @@ app7.post("/config", async (c) => {
|
|
|
18058
17188
|
return c.json({ ok: true, slug: currentSlug });
|
|
18059
17189
|
}
|
|
18060
17190
|
case "list-public-agents": {
|
|
18061
|
-
const agentsDir =
|
|
17191
|
+
const agentsDir = resolve16(account.accountDir, "agents");
|
|
18062
17192
|
const agents = [];
|
|
18063
|
-
if (
|
|
17193
|
+
if (existsSync14(agentsDir)) {
|
|
18064
17194
|
try {
|
|
18065
17195
|
const entries = readdirSync4(agentsDir, { withFileTypes: true });
|
|
18066
17196
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
18067
17197
|
if (!entry.isDirectory() || entry.name === "admin") continue;
|
|
18068
|
-
const configPath2 =
|
|
18069
|
-
if (!
|
|
17198
|
+
const configPath2 = resolve16(agentsDir, entry.name, "config.json");
|
|
17199
|
+
if (!existsSync14(configPath2)) continue;
|
|
18070
17200
|
try {
|
|
18071
|
-
const config = JSON.parse(
|
|
17201
|
+
const config = JSON.parse(readFileSync15(configPath2, "utf-8"));
|
|
18072
17202
|
agents.push({ slug: entry.name, displayName: config.displayName ?? entry.name });
|
|
18073
17203
|
} catch {
|
|
18074
17204
|
console.error(`${TAG18} config action=list-public-agents error="failed to parse config.json for agent ${entry.name}" \u2014 skipping`);
|
|
@@ -18140,10 +17270,10 @@ app7.post("/send-document", async (c) => {
|
|
|
18140
17270
|
if (!to || !filePath) {
|
|
18141
17271
|
return c.json({ error: "Missing required fields: to, filePath" }, 400);
|
|
18142
17272
|
}
|
|
18143
|
-
if (!maxyAccountId || !
|
|
17273
|
+
if (!maxyAccountId || !PLATFORM_ROOT6) {
|
|
18144
17274
|
return c.json({ error: "Cannot validate file path: missing account or platform context" }, 400);
|
|
18145
17275
|
}
|
|
18146
|
-
const accountDir =
|
|
17276
|
+
const accountDir = resolve16(PLATFORM_ROOT6, "..", "data/accounts", maxyAccountId);
|
|
18147
17277
|
let resolvedPath;
|
|
18148
17278
|
try {
|
|
18149
17279
|
resolvedPath = realpathSync2(filePath);
|
|
@@ -18280,16 +17410,16 @@ var whatsapp_default = app7;
|
|
|
18280
17410
|
|
|
18281
17411
|
// server/routes/onboarding.ts
|
|
18282
17412
|
import { spawn as spawn3, execFileSync as execFileSync2 } from "child_process";
|
|
18283
|
-
import { openSync as openSync4, closeSync as closeSync4, writeFileSync as
|
|
18284
|
-
import { resolve as
|
|
17413
|
+
import { openSync as openSync4, closeSync as closeSync4, writeFileSync as writeFileSync12, writeSync, existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as readFileSync16, unlinkSync as unlinkSync4 } from "fs";
|
|
17414
|
+
import { resolve as resolve17, dirname as dirname7 } from "path";
|
|
18285
17415
|
import { createHash, randomUUID as randomUUID9 } from "crypto";
|
|
18286
|
-
var
|
|
17416
|
+
var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
18287
17417
|
function hashPin(pin) {
|
|
18288
17418
|
return createHash("sha256").update(pin).digest("hex");
|
|
18289
17419
|
}
|
|
18290
17420
|
function readUsersFile() {
|
|
18291
|
-
if (!
|
|
18292
|
-
const raw2 =
|
|
17421
|
+
if (!existsSync15(USERS_FILE)) return null;
|
|
17422
|
+
const raw2 = readFileSync16(USERS_FILE, "utf-8").trim();
|
|
18293
17423
|
if (!raw2) return [];
|
|
18294
17424
|
return JSON.parse(raw2);
|
|
18295
17425
|
}
|
|
@@ -18355,7 +17485,7 @@ app8.post("/claude-auth", async (c) => {
|
|
|
18355
17485
|
if (!vncReady) return c.json({ error: "VNC display failed to start" }, 500);
|
|
18356
17486
|
}
|
|
18357
17487
|
await ensureCdp(transport);
|
|
18358
|
-
|
|
17488
|
+
writeFileSync12(logPath("claude-auth"), "");
|
|
18359
17489
|
const chromiumWrapper = writeChromiumWrapper();
|
|
18360
17490
|
const x11Env = buildX11Env(chromiumWrapper, transport);
|
|
18361
17491
|
vncLog("claude-auth", { action: "start", transport });
|
|
@@ -18394,17 +17524,17 @@ app8.post("/set-pin", async (c) => {
|
|
|
18394
17524
|
}
|
|
18395
17525
|
const hash = hashPin(body.pin);
|
|
18396
17526
|
const userId = randomUUID9();
|
|
18397
|
-
|
|
18398
|
-
|
|
17527
|
+
mkdirSync11(dirname7(USERS_FILE), { recursive: true });
|
|
17528
|
+
writeFileSync12(USERS_FILE, JSON.stringify([{ userId, name: "Owner", pin: hash }]), { mode: 384 });
|
|
18399
17529
|
console.log(`[set-pin] created users.json: userId=${userId.slice(0, 8)}\u2026 hash=${hash.slice(0, 8)}\u2026`);
|
|
18400
17530
|
const account = resolveAccount();
|
|
18401
17531
|
if (account) {
|
|
18402
17532
|
try {
|
|
18403
|
-
const config = JSON.parse(
|
|
17533
|
+
const config = JSON.parse(readFileSync16(`${account.accountDir}/account.json`, "utf-8"));
|
|
18404
17534
|
if (!config.admins) config.admins = [];
|
|
18405
17535
|
if (!config.admins.some((a) => a.userId === userId)) {
|
|
18406
17536
|
config.admins.push({ userId, role: "owner" });
|
|
18407
|
-
|
|
17537
|
+
writeFileSync12(`${account.accountDir}/account.json`, JSON.stringify(config, null, 2) + "\n");
|
|
18408
17538
|
console.log(`[set-pin] added userId=${userId.slice(0, 8)}\u2026 to account.json admins`);
|
|
18409
17539
|
}
|
|
18410
17540
|
} catch (err) {
|
|
@@ -18442,7 +17572,7 @@ app8.delete("/set-pin", async (c) => {
|
|
|
18442
17572
|
unlinkSync4(USERS_FILE);
|
|
18443
17573
|
console.log(`[set-pin] cleared users.json (last entry removed): userId=${matchedUser.userId.slice(0, 8)}\u2026`);
|
|
18444
17574
|
} else {
|
|
18445
|
-
|
|
17575
|
+
writeFileSync12(USERS_FILE, JSON.stringify(remaining), { mode: 384 });
|
|
18446
17576
|
console.log(`[set-pin] removed entry from users.json: userId=${matchedUser.userId.slice(0, 8)}\u2026 remaining=${remaining.length}`);
|
|
18447
17577
|
}
|
|
18448
17578
|
return c.json({ ok: true });
|
|
@@ -18455,19 +17585,19 @@ app8.post("/skip", async (c) => {
|
|
|
18455
17585
|
}
|
|
18456
17586
|
const { accountId, accountDir } = account;
|
|
18457
17587
|
let agentName = "Maxy";
|
|
18458
|
-
const brandPath =
|
|
18459
|
-
if (brandPath &&
|
|
17588
|
+
const brandPath = PLATFORM_ROOT7 ? resolve17(PLATFORM_ROOT7, "config", "brand.json") : "";
|
|
17589
|
+
if (brandPath && existsSync15(brandPath)) {
|
|
18460
17590
|
try {
|
|
18461
|
-
const brand = JSON.parse(
|
|
17591
|
+
const brand = JSON.parse(readFileSync16(brandPath, "utf-8"));
|
|
18462
17592
|
if (brand.productName) agentName = brand.productName;
|
|
18463
17593
|
} catch (err) {
|
|
18464
17594
|
console.error(`[onboarding-skip] brand.json read failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
18465
17595
|
}
|
|
18466
17596
|
}
|
|
18467
|
-
const soulPath =
|
|
17597
|
+
const soulPath = resolve17(accountDir, "agents", "admin", "SOUL.md");
|
|
18468
17598
|
try {
|
|
18469
|
-
|
|
18470
|
-
|
|
17599
|
+
mkdirSync11(dirname7(soulPath), { recursive: true });
|
|
17600
|
+
writeFileSync12(soulPath, `You are ${agentName}, an AI operations manager.
|
|
18471
17601
|
`);
|
|
18472
17602
|
console.log(`[onboarding-skip] wrote SOUL.md: ${soulPath}`);
|
|
18473
17603
|
} catch (err) {
|
|
@@ -18505,9 +17635,9 @@ app8.post("/skip", async (c) => {
|
|
|
18505
17635
|
var onboarding_default = app8;
|
|
18506
17636
|
|
|
18507
17637
|
// server/routes/client-error.ts
|
|
18508
|
-
import { appendFileSync as
|
|
18509
|
-
import { join as
|
|
18510
|
-
var CLIENT_ERRORS_LOG =
|
|
17638
|
+
import { appendFileSync as appendFileSync4, existsSync as existsSync16, renameSync as renameSync5, statSync as statSync7 } from "fs";
|
|
17639
|
+
import { join as join9 } from "path";
|
|
17640
|
+
var CLIENT_ERRORS_LOG = join9(LOG_DIR, "client-errors.log");
|
|
18511
17641
|
var MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
18512
17642
|
var MAX_BODY_SIZE = 8 * 1024;
|
|
18513
17643
|
var MAX_STACK_LEN = 2e3;
|
|
@@ -18559,7 +17689,7 @@ function stackHead(stack) {
|
|
|
18559
17689
|
}
|
|
18560
17690
|
function rotateIfNeeded() {
|
|
18561
17691
|
try {
|
|
18562
|
-
if (!
|
|
17692
|
+
if (!existsSync16(CLIENT_ERRORS_LOG)) return;
|
|
18563
17693
|
const stats = statSync7(CLIENT_ERRORS_LOG);
|
|
18564
17694
|
if (stats.size < MAX_LOG_SIZE) return;
|
|
18565
17695
|
renameSync5(CLIENT_ERRORS_LOG, CLIENT_ERRORS_LOG + ".1");
|
|
@@ -18642,7 +17772,7 @@ app9.post("/", async (c) => {
|
|
|
18642
17772
|
tag: typeof body.tag === "string" ? truncate2(body.tag, 32) : void 0,
|
|
18643
17773
|
status: typeof body.status === "number" ? body.status : void 0
|
|
18644
17774
|
};
|
|
18645
|
-
|
|
17775
|
+
appendFileSync4(CLIENT_ERRORS_LOG, JSON.stringify(payload) + "\n", "utf-8");
|
|
18646
17776
|
} catch (err) {
|
|
18647
17777
|
console.error(`[client-error] append failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
18648
17778
|
}
|
|
@@ -18652,14 +17782,14 @@ app9.post("/", async (c) => {
|
|
|
18652
17782
|
var client_error_default = app9;
|
|
18653
17783
|
|
|
18654
17784
|
// server/routes/admin/session.ts
|
|
18655
|
-
import { readFileSync as
|
|
17785
|
+
import { readFileSync as readFileSync17, existsSync as existsSync17 } from "fs";
|
|
18656
17786
|
import { createHash as createHash2 } from "crypto";
|
|
18657
17787
|
function hashPin2(pin) {
|
|
18658
17788
|
return createHash2("sha256").update(pin).digest("hex");
|
|
18659
17789
|
}
|
|
18660
17790
|
function readUsersFile2() {
|
|
18661
|
-
if (!
|
|
18662
|
-
const raw2 =
|
|
17791
|
+
if (!existsSync17(USERS_FILE)) return null;
|
|
17792
|
+
const raw2 = readFileSync17(USERS_FILE, "utf-8").trim();
|
|
18663
17793
|
if (!raw2) return [];
|
|
18664
17794
|
return JSON.parse(raw2);
|
|
18665
17795
|
}
|
|
@@ -18789,11 +17919,11 @@ app10.post("/", async (c) => {
|
|
|
18789
17919
|
var session_default2 = app10;
|
|
18790
17920
|
|
|
18791
17921
|
// server/routes/admin/chat.ts
|
|
18792
|
-
import { resolve as
|
|
17922
|
+
import { resolve as resolve18 } from "path";
|
|
18793
17923
|
|
|
18794
17924
|
// app/lib/script-stream-tailer.ts
|
|
18795
17925
|
import * as childProcess from "child_process";
|
|
18796
|
-
import { appendFileSync as
|
|
17926
|
+
import { appendFileSync as appendFileSync5, createReadStream as createReadStream2, mkdirSync as mkdirSync12, statSync as statSync8 } from "fs";
|
|
18797
17927
|
import { dirname as dirname8 } from "path";
|
|
18798
17928
|
import { StringDecoder as StringDecoder2 } from "string_decoder";
|
|
18799
17929
|
var SCRIPT_STREAM_RE = /^\[([^\]]+)\] \[script:([a-z][a-z0-9-]*)((?::[a-z0-9:_-]+)?)\] (.*)$/;
|
|
@@ -18903,8 +18033,8 @@ function writeRouteMilestone(streamLogPath, scope, line) {
|
|
|
18903
18033
|
}
|
|
18904
18034
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
18905
18035
|
try {
|
|
18906
|
-
|
|
18907
|
-
|
|
18036
|
+
mkdirSync12(dirname8(streamLogPath), { recursive: true });
|
|
18037
|
+
appendFileSync5(streamLogPath, `[${ts}] [script:${scope}] ${line}
|
|
18908
18038
|
`);
|
|
18909
18039
|
} catch (err) {
|
|
18910
18040
|
console.error(
|
|
@@ -19234,7 +18364,7 @@ app11.post("/", requireAdminSession, async (c) => {
|
|
|
19234
18364
|
try {
|
|
19235
18365
|
registerAdminSSE(sseEntry);
|
|
19236
18366
|
if (sseConvId) {
|
|
19237
|
-
const streamLogPath =
|
|
18367
|
+
const streamLogPath = resolve18(account.accountDir, "logs", `claude-agent-stream-${sseConvId}.log`);
|
|
19238
18368
|
tailer = startScriptStreamTailer({
|
|
19239
18369
|
path: streamLogPath,
|
|
19240
18370
|
onEvent: (event) => {
|
|
@@ -19359,8 +18489,8 @@ app12.post("/", requireAdminSession, async (c) => {
|
|
|
19359
18489
|
var compact_default = app12;
|
|
19360
18490
|
|
|
19361
18491
|
// server/routes/admin/logs.ts
|
|
19362
|
-
import { existsSync as
|
|
19363
|
-
import { resolve as
|
|
18492
|
+
import { existsSync as existsSync18, readdirSync as readdirSync5, readFileSync as readFileSync18, statSync as statSync9 } from "fs";
|
|
18493
|
+
import { resolve as resolve19, basename as basename5 } from "path";
|
|
19364
18494
|
var TAIL_BYTES = 8192;
|
|
19365
18495
|
var app13 = new Hono2();
|
|
19366
18496
|
app13.get("/", async (c) => {
|
|
@@ -19369,16 +18499,16 @@ app13.get("/", async (c) => {
|
|
|
19369
18499
|
const conversationIdParam = c.req.query("conversationId");
|
|
19370
18500
|
const download = c.req.query("download") === "1";
|
|
19371
18501
|
const account = resolveAccount();
|
|
19372
|
-
const accountLogDir2 = account ?
|
|
18502
|
+
const accountLogDir2 = account ? resolve19(account.accountDir, "logs") : null;
|
|
19373
18503
|
if (fileParam) {
|
|
19374
18504
|
const safe = basename5(fileParam);
|
|
19375
18505
|
const searched = [];
|
|
19376
18506
|
for (const dir of [accountLogDir2, LOG_DIR]) {
|
|
19377
18507
|
if (!dir) continue;
|
|
19378
|
-
const filePath =
|
|
18508
|
+
const filePath = resolve19(dir, safe);
|
|
19379
18509
|
searched.push(filePath);
|
|
19380
18510
|
try {
|
|
19381
|
-
const content =
|
|
18511
|
+
const content = readFileSync18(filePath, "utf-8");
|
|
19382
18512
|
const headers = { "Content-Type": "text/plain; charset=utf-8" };
|
|
19383
18513
|
if (download) headers["Content-Disposition"] = `attachment; filename="${safe}"`;
|
|
19384
18514
|
return new Response(content, { headers });
|
|
@@ -19417,10 +18547,10 @@ app13.get("/", async (c) => {
|
|
|
19417
18547
|
const searched = [];
|
|
19418
18548
|
for (const dir of [accountLogDir2, LOG_DIR]) {
|
|
19419
18549
|
if (!dir) continue;
|
|
19420
|
-
const filePath =
|
|
18550
|
+
const filePath = resolve19(dir, fileName);
|
|
19421
18551
|
searched.push(filePath);
|
|
19422
18552
|
try {
|
|
19423
|
-
const content =
|
|
18553
|
+
const content = readFileSync18(filePath, "utf-8");
|
|
19424
18554
|
const headers = { "Content-Type": "text/plain; charset=utf-8" };
|
|
19425
18555
|
if (download) headers["Content-Disposition"] = `attachment; filename="${fileName}"`;
|
|
19426
18556
|
return new Response(content, { headers });
|
|
@@ -19435,7 +18565,7 @@ app13.get("/", async (c) => {
|
|
|
19435
18565
|
const seen = /* @__PURE__ */ new Set();
|
|
19436
18566
|
const logs = {};
|
|
19437
18567
|
for (const dir of [accountLogDir2, LOG_DIR]) {
|
|
19438
|
-
if (!dir || !
|
|
18568
|
+
if (!dir || !existsSync18(dir)) continue;
|
|
19439
18569
|
let files;
|
|
19440
18570
|
try {
|
|
19441
18571
|
files = readdirSync5(dir).filter((f) => f.endsWith(".log"));
|
|
@@ -19444,10 +18574,10 @@ app13.get("/", async (c) => {
|
|
|
19444
18574
|
console.warn(`[admin/logs] readdir-fail dir=${dir} reason=${reason}`);
|
|
19445
18575
|
continue;
|
|
19446
18576
|
}
|
|
19447
|
-
files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync9(
|
|
18577
|
+
files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync9(resolve19(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).forEach(({ name }) => {
|
|
19448
18578
|
seen.add(name);
|
|
19449
18579
|
try {
|
|
19450
|
-
const content =
|
|
18580
|
+
const content = readFileSync18(resolve19(dir, name));
|
|
19451
18581
|
const tail = content.length > TAIL_BYTES ? content.subarray(content.length - TAIL_BYTES).toString("utf-8") : content.toString("utf-8");
|
|
19452
18582
|
logs[name] = tail.trim() || "(empty)";
|
|
19453
18583
|
} catch (err) {
|
|
@@ -19487,8 +18617,8 @@ var claude_info_default = app14;
|
|
|
19487
18617
|
|
|
19488
18618
|
// server/routes/admin/attachment.ts
|
|
19489
18619
|
import { readFile as readFile3, readdir } from "fs/promises";
|
|
19490
|
-
import { existsSync as
|
|
19491
|
-
import { resolve as
|
|
18620
|
+
import { existsSync as existsSync19 } from "fs";
|
|
18621
|
+
import { resolve as resolve20 } from "path";
|
|
19492
18622
|
var app15 = new Hono2();
|
|
19493
18623
|
app15.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
19494
18624
|
const attachmentId = c.req.param("attachmentId");
|
|
@@ -19500,12 +18630,12 @@ app15.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
|
19500
18630
|
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(attachmentId)) {
|
|
19501
18631
|
return new Response("Not found", { status: 404 });
|
|
19502
18632
|
}
|
|
19503
|
-
const dir =
|
|
19504
|
-
if (!
|
|
18633
|
+
const dir = resolve20(ATTACHMENTS_ROOT, accountId, attachmentId);
|
|
18634
|
+
if (!existsSync19(dir)) {
|
|
19505
18635
|
return new Response("Not found", { status: 404 });
|
|
19506
18636
|
}
|
|
19507
|
-
const metaPath =
|
|
19508
|
-
if (!
|
|
18637
|
+
const metaPath = resolve20(dir, `${attachmentId}.meta.json`);
|
|
18638
|
+
if (!existsSync19(metaPath)) {
|
|
19509
18639
|
return new Response("Not found", { status: 404 });
|
|
19510
18640
|
}
|
|
19511
18641
|
let meta;
|
|
@@ -19519,7 +18649,7 @@ app15.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
|
19519
18649
|
if (!dataFile) {
|
|
19520
18650
|
return new Response("Not found", { status: 404 });
|
|
19521
18651
|
}
|
|
19522
|
-
const filePath =
|
|
18652
|
+
const filePath = resolve20(dir, dataFile);
|
|
19523
18653
|
const buffer = await readFile3(filePath);
|
|
19524
18654
|
return new Response(new Uint8Array(buffer), {
|
|
19525
18655
|
headers: {
|
|
@@ -19532,8 +18662,8 @@ app15.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
|
19532
18662
|
var attachment_default = app15;
|
|
19533
18663
|
|
|
19534
18664
|
// server/routes/admin/account.ts
|
|
19535
|
-
import { readFileSync as
|
|
19536
|
-
import { resolve as
|
|
18665
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync13 } from "fs";
|
|
18666
|
+
import { resolve as resolve21 } from "path";
|
|
19537
18667
|
var VALID_CONTEXT_MODES = ["managed", "claude-code"];
|
|
19538
18668
|
var app16 = new Hono2();
|
|
19539
18669
|
app16.patch("/", requireAdminSession, async (c) => {
|
|
@@ -19549,12 +18679,12 @@ app16.patch("/", requireAdminSession, async (c) => {
|
|
|
19549
18679
|
}
|
|
19550
18680
|
const account = resolveAccount();
|
|
19551
18681
|
if (!account) return c.json({ error: "No account configured" }, 500);
|
|
19552
|
-
const configPath2 =
|
|
18682
|
+
const configPath2 = resolve21(account.accountDir, "account.json");
|
|
19553
18683
|
try {
|
|
19554
|
-
const raw2 =
|
|
18684
|
+
const raw2 = readFileSync19(configPath2, "utf-8");
|
|
19555
18685
|
const config = JSON.parse(raw2);
|
|
19556
18686
|
config.contextMode = contextMode;
|
|
19557
|
-
|
|
18687
|
+
writeFileSync13(configPath2, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
19558
18688
|
console.error(`[account-update] contextMode=${contextMode}`);
|
|
19559
18689
|
return c.json({ ok: true, contextMode });
|
|
19560
18690
|
} catch (err) {
|
|
@@ -19565,24 +18695,24 @@ app16.patch("/", requireAdminSession, async (c) => {
|
|
|
19565
18695
|
var account_default = app16;
|
|
19566
18696
|
|
|
19567
18697
|
// server/routes/admin/agents.ts
|
|
19568
|
-
import { resolve as
|
|
19569
|
-
import { readdirSync as readdirSync6, readFileSync as
|
|
18698
|
+
import { resolve as resolve22 } from "path";
|
|
18699
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync20, existsSync as existsSync20, rmSync as rmSync3 } from "fs";
|
|
19570
18700
|
var app17 = new Hono2();
|
|
19571
18701
|
app17.get("/", (c) => {
|
|
19572
18702
|
const account = resolveAccount();
|
|
19573
18703
|
if (!account) return c.json({ agents: [] });
|
|
19574
|
-
const agentsDir =
|
|
19575
|
-
if (!
|
|
18704
|
+
const agentsDir = resolve22(account.accountDir, "agents");
|
|
18705
|
+
if (!existsSync20(agentsDir)) return c.json({ agents: [] });
|
|
19576
18706
|
const agents = [];
|
|
19577
18707
|
try {
|
|
19578
18708
|
const entries = readdirSync6(agentsDir, { withFileTypes: true });
|
|
19579
18709
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
19580
18710
|
if (!entry.isDirectory()) continue;
|
|
19581
18711
|
if (entry.name === "admin") continue;
|
|
19582
|
-
const configPath2 =
|
|
19583
|
-
if (!
|
|
18712
|
+
const configPath2 = resolve22(agentsDir, entry.name, "config.json");
|
|
18713
|
+
if (!existsSync20(configPath2)) continue;
|
|
19584
18714
|
try {
|
|
19585
|
-
const config = JSON.parse(
|
|
18715
|
+
const config = JSON.parse(readFileSync20(configPath2, "utf-8"));
|
|
19586
18716
|
agents.push({
|
|
19587
18717
|
slug: entry.name,
|
|
19588
18718
|
displayName: config.displayName ?? entry.name,
|
|
@@ -19608,8 +18738,8 @@ app17.delete("/:slug", (c) => {
|
|
|
19608
18738
|
if (slug.includes("/") || slug.includes("..") || slug.includes("\\")) {
|
|
19609
18739
|
return c.json({ error: "Invalid agent slug" }, 400);
|
|
19610
18740
|
}
|
|
19611
|
-
const agentDir =
|
|
19612
|
-
if (!
|
|
18741
|
+
const agentDir = resolve22(account.accountDir, "agents", slug);
|
|
18742
|
+
if (!existsSync20(agentDir)) {
|
|
19613
18743
|
return c.json({ error: "Agent not found" }, 404);
|
|
19614
18744
|
}
|
|
19615
18745
|
try {
|
|
@@ -19624,27 +18754,27 @@ app17.delete("/:slug", (c) => {
|
|
|
19624
18754
|
var agents_default = app17;
|
|
19625
18755
|
|
|
19626
18756
|
// server/routes/admin/version.ts
|
|
19627
|
-
import { existsSync as
|
|
19628
|
-
import { resolve as
|
|
19629
|
-
var
|
|
18757
|
+
import { existsSync as existsSync21, readFileSync as readFileSync21 } from "fs";
|
|
18758
|
+
import { resolve as resolve23, join as join10 } from "path";
|
|
18759
|
+
var PLATFORM_ROOT8 = process.env.MAXY_PLATFORM_ROOT ?? resolve23(process.cwd(), "..");
|
|
19630
18760
|
var brandHostname = "maxy";
|
|
19631
18761
|
var brandNpmPackage = "@rubytech/create-maxy";
|
|
19632
|
-
var brandJsonPath =
|
|
19633
|
-
if (
|
|
18762
|
+
var brandJsonPath = join10(PLATFORM_ROOT8, "config", "brand.json");
|
|
18763
|
+
if (existsSync21(brandJsonPath)) {
|
|
19634
18764
|
try {
|
|
19635
|
-
const brand = JSON.parse(
|
|
18765
|
+
const brand = JSON.parse(readFileSync21(brandJsonPath, "utf-8"));
|
|
19636
18766
|
if (brand.hostname) brandHostname = brand.hostname;
|
|
19637
18767
|
if (brand.npm?.packageName) brandNpmPackage = brand.npm.packageName;
|
|
19638
18768
|
} catch {
|
|
19639
18769
|
}
|
|
19640
18770
|
}
|
|
19641
|
-
var VERSION_FILE =
|
|
18771
|
+
var VERSION_FILE = resolve23(PLATFORM_ROOT8, `config/.${brandHostname}-version`);
|
|
19642
18772
|
var NPM_PACKAGE = brandNpmPackage;
|
|
19643
18773
|
var REGISTRY_URL = `https://registry.npmjs.org/${NPM_PACKAGE}/latest`;
|
|
19644
18774
|
var FETCH_TIMEOUT_MS = 5e3;
|
|
19645
18775
|
function readInstalled() {
|
|
19646
|
-
if (!
|
|
19647
|
-
const content =
|
|
18776
|
+
if (!existsSync21(VERSION_FILE)) return "unknown";
|
|
18777
|
+
const content = readFileSync21(VERSION_FILE, "utf-8").trim();
|
|
19648
18778
|
return content || "unknown";
|
|
19649
18779
|
}
|
|
19650
18780
|
async function fetchLatest() {
|
|
@@ -19928,6 +19058,28 @@ app21.post("/launch", async (c) => {
|
|
|
19928
19058
|
);
|
|
19929
19059
|
}
|
|
19930
19060
|
});
|
|
19061
|
+
app21.post("/launch-upgrade", async (c) => {
|
|
19062
|
+
try {
|
|
19063
|
+
const transport = resolveBrowserTransport(c.req.raw, c.env?.incoming?.socket?.remoteAddress);
|
|
19064
|
+
if (transport === "vnc") {
|
|
19065
|
+
const vncOk = await ensureVnc();
|
|
19066
|
+
if (!vncOk) {
|
|
19067
|
+
return c.json({ ok: false, error: "VNC failed to start" }, 502);
|
|
19068
|
+
}
|
|
19069
|
+
}
|
|
19070
|
+
const result = await ensureTerminalUpgrade(transport);
|
|
19071
|
+
if (!result.ok) {
|
|
19072
|
+
return c.json({ ok: false, error: result.error ?? "Upgrade terminal failed to start" }, 502);
|
|
19073
|
+
}
|
|
19074
|
+
return c.json({ ok: true, transport });
|
|
19075
|
+
} catch (err) {
|
|
19076
|
+
console.error("[admin/terminal/launch-upgrade] Failed to start upgrade terminal:", err);
|
|
19077
|
+
return c.json(
|
|
19078
|
+
{ ok: false, error: err instanceof Error ? err.message : "Unknown error" },
|
|
19079
|
+
500
|
|
19080
|
+
);
|
|
19081
|
+
}
|
|
19082
|
+
});
|
|
19931
19083
|
app21.post("/close", (c) => {
|
|
19932
19084
|
try {
|
|
19933
19085
|
killTerminal();
|
|
@@ -20115,9 +19267,9 @@ app23.post("/", async (c) => {
|
|
|
20115
19267
|
var events_default = app23;
|
|
20116
19268
|
|
|
20117
19269
|
// server/routes/admin/cloudflare.ts
|
|
20118
|
-
import { homedir as
|
|
20119
|
-
import { resolve as
|
|
20120
|
-
import { readFileSync as
|
|
19270
|
+
import { homedir as homedir3 } from "os";
|
|
19271
|
+
import { resolve as resolve25 } from "path";
|
|
19272
|
+
import { readFileSync as readFileSync23 } from "fs";
|
|
20121
19273
|
|
|
20122
19274
|
// app/lib/dns-label.ts
|
|
20123
19275
|
var VALID_LABEL = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
@@ -20133,14 +19285,14 @@ function isValidDomain(value) {
|
|
|
20133
19285
|
}
|
|
20134
19286
|
|
|
20135
19287
|
// app/lib/alias-domains.ts
|
|
20136
|
-
import { existsSync as
|
|
19288
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync13, readFileSync as readFileSync22, writeFileSync as writeFileSync14 } from "fs";
|
|
20137
19289
|
import { dirname as dirname9 } from "path";
|
|
20138
|
-
import { resolve as
|
|
20139
|
-
var ALIAS_DOMAINS_PATH =
|
|
19290
|
+
import { resolve as resolve24 } from "path";
|
|
19291
|
+
var ALIAS_DOMAINS_PATH = resolve24(MAXY_DIR, "alias-domains.json");
|
|
20140
19292
|
function readExisting() {
|
|
20141
|
-
if (!
|
|
19293
|
+
if (!existsSync22(ALIAS_DOMAINS_PATH)) return /* @__PURE__ */ new Set();
|
|
20142
19294
|
try {
|
|
20143
|
-
const parsed = JSON.parse(
|
|
19295
|
+
const parsed = JSON.parse(readFileSync22(ALIAS_DOMAINS_PATH, "utf-8"));
|
|
20144
19296
|
if (!Array.isArray(parsed)) return /* @__PURE__ */ new Set();
|
|
20145
19297
|
return new Set(parsed.filter((h) => typeof h === "string"));
|
|
20146
19298
|
} catch {
|
|
@@ -20151,18 +19303,18 @@ function addAliasDomain(hostname2) {
|
|
|
20151
19303
|
const existing = readExisting();
|
|
20152
19304
|
if (existing.has(hostname2)) return;
|
|
20153
19305
|
existing.add(hostname2);
|
|
20154
|
-
|
|
20155
|
-
|
|
19306
|
+
mkdirSync13(dirname9(ALIAS_DOMAINS_PATH), { recursive: true });
|
|
19307
|
+
writeFileSync14(ALIAS_DOMAINS_PATH, JSON.stringify([...existing], null, 2) + "\n", "utf-8");
|
|
20156
19308
|
}
|
|
20157
19309
|
|
|
20158
19310
|
// server/routes/admin/cloudflare.ts
|
|
20159
19311
|
var SETUP_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
20160
19312
|
var DOMAINS_TIMEOUT_MS = 40 * 1e3;
|
|
20161
19313
|
function loadBrandInfo() {
|
|
20162
|
-
const
|
|
20163
|
-
const brandPath =
|
|
19314
|
+
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? resolve25(process.cwd(), "..");
|
|
19315
|
+
const brandPath = resolve25(platformRoot2, "config", "brand.json");
|
|
20164
19316
|
try {
|
|
20165
|
-
const parsed = JSON.parse(
|
|
19317
|
+
const parsed = JSON.parse(readFileSync23(brandPath, "utf-8"));
|
|
20166
19318
|
const hostname2 = typeof parsed.hostname === "string" && parsed.hostname ? parsed.hostname : "maxy";
|
|
20167
19319
|
const configDir2 = typeof parsed.configDir === "string" && parsed.configDir ? parsed.configDir : ".maxy";
|
|
20168
19320
|
return { hostname: hostname2, configDir: configDir2 };
|
|
@@ -20265,7 +19417,7 @@ app24.get("/domains", requireAdminSession, async (c) => {
|
|
|
20265
19417
|
streamLogPath = streamLogPathFor(accountId, correlationId).streamLogPath;
|
|
20266
19418
|
log2(`phase=stream-log-resolved path=${streamLogPath}`);
|
|
20267
19419
|
const brand = loadBrandInfo();
|
|
20268
|
-
const scriptPath =
|
|
19420
|
+
const scriptPath = resolve25(homedir3(), "list-cf-domains.sh");
|
|
20269
19421
|
const result = await runFormSpawn({
|
|
20270
19422
|
scriptPath,
|
|
20271
19423
|
args: [brand.hostname],
|
|
@@ -20390,7 +19542,7 @@ app24.post("/setup", requireAdminSession, async (c) => {
|
|
|
20390
19542
|
}
|
|
20391
19543
|
streamLogPath = streamLogPathFor(accountId, correlationId).streamLogPath;
|
|
20392
19544
|
log2(`phase=stream-log-resolved path=${streamLogPath}`);
|
|
20393
|
-
const scriptPath =
|
|
19545
|
+
const scriptPath = resolve25(homedir3(), "setup-tunnel.sh");
|
|
20394
19546
|
const args = [brand.hostname, String(port2), adminFqdn];
|
|
20395
19547
|
if (publicFqdn) args.push(publicFqdn);
|
|
20396
19548
|
if (apex) args.push(apex);
|
|
@@ -20459,17 +19611,17 @@ var cloudflare_default = app24;
|
|
|
20459
19611
|
import { createReadStream as createReadStream3 } from "fs";
|
|
20460
19612
|
import { readdir as readdir2, readFile as readFile4, stat as stat4, mkdir as mkdir3, writeFile as writeFile4, unlink as unlink2 } from "fs/promises";
|
|
20461
19613
|
import { realpathSync as realpathSync4 } from "fs";
|
|
20462
|
-
import { basename as basename6, dirname as dirname10, join as
|
|
19614
|
+
import { basename as basename6, dirname as dirname10, join as join11, resolve as resolve27, sep as sep2 } from "path";
|
|
20463
19615
|
import { Readable as Readable3 } from "stream";
|
|
20464
19616
|
|
|
20465
19617
|
// app/lib/data-path.ts
|
|
20466
19618
|
import { realpathSync as realpathSync3 } from "fs";
|
|
20467
|
-
import { resolve as
|
|
20468
|
-
var
|
|
20469
|
-
var DATA_ROOT =
|
|
19619
|
+
import { resolve as resolve26, normalize, sep, relative } from "path";
|
|
19620
|
+
var PLATFORM_ROOT9 = process.env.MAXY_PLATFORM_ROOT ?? resolve26(process.cwd(), "../platform");
|
|
19621
|
+
var DATA_ROOT = resolve26(PLATFORM_ROOT9, "..", "data");
|
|
20470
19622
|
function resolveDataPath(raw2) {
|
|
20471
19623
|
const cleaned = normalize("/" + (raw2 ?? "").replace(/\\/g, "/")).replace(/^\/+/, "");
|
|
20472
|
-
const absolute =
|
|
19624
|
+
const absolute = resolve26(DATA_ROOT, cleaned);
|
|
20473
19625
|
let dataRootReal;
|
|
20474
19626
|
try {
|
|
20475
19627
|
dataRootReal = realpathSync3(DATA_ROOT);
|
|
@@ -20736,7 +19888,7 @@ async function cascadeDeleteDocument(params) {
|
|
|
20736
19888
|
var UUID_RE3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
20737
19889
|
async function readMeta(absDir, baseName) {
|
|
20738
19890
|
try {
|
|
20739
|
-
const raw2 = await readFile4(
|
|
19891
|
+
const raw2 = await readFile4(join11(absDir, `${baseName}.meta.json`), "utf8");
|
|
20740
19892
|
const parsed = JSON.parse(raw2);
|
|
20741
19893
|
if (typeof parsed?.filename === "string") {
|
|
20742
19894
|
return { filename: parsed.filename, mimeType: typeof parsed.mimeType === "string" ? parsed.mimeType : void 0 };
|
|
@@ -20747,7 +19899,7 @@ async function readMeta(absDir, baseName) {
|
|
|
20747
19899
|
}
|
|
20748
19900
|
async function readAccountNames() {
|
|
20749
19901
|
const map = /* @__PURE__ */ new Map();
|
|
20750
|
-
const accountsDir =
|
|
19902
|
+
const accountsDir = resolve27(DATA_ROOT, "accounts");
|
|
20751
19903
|
let names;
|
|
20752
19904
|
try {
|
|
20753
19905
|
names = await readdir2(accountsDir);
|
|
@@ -20756,7 +19908,7 @@ async function readAccountNames() {
|
|
|
20756
19908
|
}
|
|
20757
19909
|
for (const name of names) {
|
|
20758
19910
|
if (!UUID_RE3.test(name)) continue;
|
|
20759
|
-
const configPath2 =
|
|
19911
|
+
const configPath2 = resolve27(accountsDir, name, "account.json");
|
|
20760
19912
|
try {
|
|
20761
19913
|
const raw2 = await readFile4(configPath2, "utf8");
|
|
20762
19914
|
const parsed = JSON.parse(raw2);
|
|
@@ -20774,7 +19926,7 @@ async function readAccountNames() {
|
|
|
20774
19926
|
}
|
|
20775
19927
|
async function enrich(absolute, entry, accountNames) {
|
|
20776
19928
|
if (entry.kind === "directory" && UUID_RE3.test(entry.name)) {
|
|
20777
|
-
const meta = await readMeta(
|
|
19929
|
+
const meta = await readMeta(join11(absolute, entry.name), entry.name);
|
|
20778
19930
|
if (meta?.filename) {
|
|
20779
19931
|
entry.displayName = meta.filename;
|
|
20780
19932
|
entry.mimeType = meta.mimeType;
|
|
@@ -20833,7 +19985,7 @@ app25.get("/", requireAdminSession, async (c) => {
|
|
|
20833
19985
|
continue;
|
|
20834
19986
|
}
|
|
20835
19987
|
try {
|
|
20836
|
-
const entryPath =
|
|
19988
|
+
const entryPath = join11(absolute, name);
|
|
20837
19989
|
const s = await stat4(entryPath);
|
|
20838
19990
|
entries.push({
|
|
20839
19991
|
name,
|
|
@@ -20947,8 +20099,8 @@ app25.post("/upload", requireAdminSession, async (c) => {
|
|
|
20947
20099
|
}
|
|
20948
20100
|
const safeName = basename6(file.name).replace(/[\0/\\]/g, "_");
|
|
20949
20101
|
const finalName = `${Date.now()}-${safeName}`;
|
|
20950
|
-
const destDir =
|
|
20951
|
-
const destPath =
|
|
20102
|
+
const destDir = resolve27(DATA_ROOT, "uploads", accountId);
|
|
20103
|
+
const destPath = resolve27(destDir, finalName);
|
|
20952
20104
|
try {
|
|
20953
20105
|
await mkdir3(destDir, { recursive: true });
|
|
20954
20106
|
const dataRootReal = realpathSync4(DATA_ROOT);
|
|
@@ -21006,7 +20158,7 @@ app25.delete("/", requireAdminSession, async (c) => {
|
|
|
21006
20158
|
}
|
|
21007
20159
|
const dot = base.lastIndexOf(".");
|
|
21008
20160
|
const stem = dot === -1 ? base : base.slice(0, dot);
|
|
21009
|
-
const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ?
|
|
20161
|
+
const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ? join11(dirname10(absolute), `${stem}.meta.json`) : null;
|
|
21010
20162
|
await unlink2(absolute);
|
|
21011
20163
|
if (sidecarPath) {
|
|
21012
20164
|
try {
|
|
@@ -21152,8 +20304,7 @@ var FILTER_EXCLUDED_LABELS = Object.freeze(
|
|
|
21152
20304
|
"Message",
|
|
21153
20305
|
"UserMessage",
|
|
21154
20306
|
"AssistantMessage",
|
|
21155
|
-
"
|
|
21156
|
-
"PublicConversation",
|
|
20307
|
+
"Conversation",
|
|
21157
20308
|
"Section",
|
|
21158
20309
|
"ToolCall",
|
|
21159
20310
|
"WorkflowRun",
|
|
@@ -21162,6 +20313,18 @@ var FILTER_EXCLUDED_LABELS = Object.freeze(
|
|
|
21162
20313
|
"ReviewAlert"
|
|
21163
20314
|
])
|
|
21164
20315
|
);
|
|
20316
|
+
var CHILD_LABELS = Object.freeze(
|
|
20317
|
+
/* @__PURE__ */ new Set([
|
|
20318
|
+
"Message",
|
|
20319
|
+
"UserMessage",
|
|
20320
|
+
"AssistantMessage",
|
|
20321
|
+
"Section",
|
|
20322
|
+
"ToolCall",
|
|
20323
|
+
"WorkflowStep",
|
|
20324
|
+
"StepResult",
|
|
20325
|
+
"ReviewAlert"
|
|
20326
|
+
])
|
|
20327
|
+
);
|
|
21165
20328
|
function isKnownLabel(label) {
|
|
21166
20329
|
return Object.prototype.hasOwnProperty.call(GRAPH_LABEL_COLOURS, label);
|
|
21167
20330
|
}
|
|
@@ -21871,15 +21034,15 @@ app34.route("/adherence", adherence_default);
|
|
|
21871
21034
|
var admin_default = app34;
|
|
21872
21035
|
|
|
21873
21036
|
// server/index.ts
|
|
21874
|
-
var
|
|
21875
|
-
var BRAND_JSON_PATH =
|
|
21037
|
+
var PLATFORM_ROOT10 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
21038
|
+
var BRAND_JSON_PATH = PLATFORM_ROOT10 ? join12(PLATFORM_ROOT10, "config", "brand.json") : "";
|
|
21876
21039
|
var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", domain: "getmaxy.com" };
|
|
21877
|
-
if (BRAND_JSON_PATH && !
|
|
21040
|
+
if (BRAND_JSON_PATH && !existsSync23(BRAND_JSON_PATH)) {
|
|
21878
21041
|
console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
|
|
21879
21042
|
}
|
|
21880
|
-
if (BRAND_JSON_PATH &&
|
|
21043
|
+
if (BRAND_JSON_PATH && existsSync23(BRAND_JSON_PATH)) {
|
|
21881
21044
|
try {
|
|
21882
|
-
const parsed = JSON.parse(
|
|
21045
|
+
const parsed = JSON.parse(readFileSync24(BRAND_JSON_PATH, "utf-8"));
|
|
21883
21046
|
BRAND = { ...BRAND, ...parsed };
|
|
21884
21047
|
} catch (err) {
|
|
21885
21048
|
console.error(`[brand] Failed to parse brand.json: ${err.message}`);
|
|
@@ -21898,11 +21061,11 @@ var brandLoginOpts = {
|
|
|
21898
21061
|
bodyFont: BRAND.defaultFonts?.body,
|
|
21899
21062
|
logoContainsName: !!BRAND.logoContainsName
|
|
21900
21063
|
};
|
|
21901
|
-
var ALIAS_DOMAINS_PATH2 =
|
|
21064
|
+
var ALIAS_DOMAINS_PATH2 = join12(homedir4(), BRAND.configDir, "alias-domains.json");
|
|
21902
21065
|
function loadAliasDomains() {
|
|
21903
21066
|
try {
|
|
21904
|
-
if (!
|
|
21905
|
-
const parsed = JSON.parse(
|
|
21067
|
+
if (!existsSync23(ALIAS_DOMAINS_PATH2)) return null;
|
|
21068
|
+
const parsed = JSON.parse(readFileSync24(ALIAS_DOMAINS_PATH2, "utf-8"));
|
|
21906
21069
|
if (!Array.isArray(parsed)) {
|
|
21907
21070
|
console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
|
|
21908
21071
|
return null;
|
|
@@ -22242,20 +21405,20 @@ app35.get("/agent-assets/:slug/:filename", (c) => {
|
|
|
22242
21405
|
console.error(`[agent-assets] no-account slug=${slug} file=${filename}`);
|
|
22243
21406
|
return c.text("Not found", 404);
|
|
22244
21407
|
}
|
|
22245
|
-
const filePath =
|
|
22246
|
-
const expectedDir =
|
|
21408
|
+
const filePath = resolve28(account.accountDir, "agents", slug, "assets", filename);
|
|
21409
|
+
const expectedDir = resolve28(account.accountDir, "agents", slug, "assets");
|
|
22247
21410
|
if (!filePath.startsWith(expectedDir + "/")) {
|
|
22248
21411
|
console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
|
|
22249
21412
|
return c.text("Forbidden", 403);
|
|
22250
21413
|
}
|
|
22251
|
-
if (!
|
|
21414
|
+
if (!existsSync23(filePath)) {
|
|
22252
21415
|
console.error(`[agent-assets] serve slug=${slug} file=${filename} status=404`);
|
|
22253
21416
|
return c.text("Not found", 404);
|
|
22254
21417
|
}
|
|
22255
21418
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
22256
21419
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
22257
21420
|
console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
|
|
22258
|
-
const body =
|
|
21421
|
+
const body = readFileSync24(filePath);
|
|
22259
21422
|
return c.body(body, 200, {
|
|
22260
21423
|
"Content-Type": contentType,
|
|
22261
21424
|
"Cache-Control": "public, max-age=3600"
|
|
@@ -22272,20 +21435,20 @@ app35.get("/generated/:filename", (c) => {
|
|
|
22272
21435
|
console.error(`[generated] serve file=${filename} status=404`);
|
|
22273
21436
|
return c.text("Not found", 404);
|
|
22274
21437
|
}
|
|
22275
|
-
const filePath =
|
|
22276
|
-
const expectedDir =
|
|
21438
|
+
const filePath = resolve28(account.accountDir, "generated", filename);
|
|
21439
|
+
const expectedDir = resolve28(account.accountDir, "generated");
|
|
22277
21440
|
if (!filePath.startsWith(expectedDir + "/")) {
|
|
22278
21441
|
console.error(`[generated] serve file=${filename} status=403`);
|
|
22279
21442
|
return c.text("Forbidden", 403);
|
|
22280
21443
|
}
|
|
22281
|
-
if (!
|
|
21444
|
+
if (!existsSync23(filePath)) {
|
|
22282
21445
|
console.error(`[generated] serve file=${filename} status=404`);
|
|
22283
21446
|
return c.text("Not found", 404);
|
|
22284
21447
|
}
|
|
22285
21448
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
22286
21449
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
22287
21450
|
console.log(`[generated] serve file=${filename} status=200`);
|
|
22288
|
-
const body =
|
|
21451
|
+
const body = readFileSync24(filePath);
|
|
22289
21452
|
return c.body(body, 200, {
|
|
22290
21453
|
"Content-Type": contentType,
|
|
22291
21454
|
"Cache-Control": "public, max-age=86400"
|
|
@@ -22294,9 +21457,9 @@ app35.get("/generated/:filename", (c) => {
|
|
|
22294
21457
|
var htmlCache = /* @__PURE__ */ new Map();
|
|
22295
21458
|
var brandLogoPath = "/brand/maxy-monochrome.png";
|
|
22296
21459
|
var brandIconPath = "/brand/maxy-monochrome.png";
|
|
22297
|
-
if (BRAND_JSON_PATH &&
|
|
21460
|
+
if (BRAND_JSON_PATH && existsSync23(BRAND_JSON_PATH)) {
|
|
22298
21461
|
try {
|
|
22299
|
-
const fullBrand = JSON.parse(
|
|
21462
|
+
const fullBrand = JSON.parse(readFileSync24(BRAND_JSON_PATH, "utf-8"));
|
|
22300
21463
|
if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
|
|
22301
21464
|
brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
|
|
22302
21465
|
} catch {
|
|
@@ -22312,10 +21475,10 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
|
|
|
22312
21475
|
})}</script>`;
|
|
22313
21476
|
function readInstalledVersion() {
|
|
22314
21477
|
try {
|
|
22315
|
-
if (!
|
|
22316
|
-
const versionFile =
|
|
22317
|
-
if (!
|
|
22318
|
-
const content =
|
|
21478
|
+
if (!PLATFORM_ROOT10) return "unknown";
|
|
21479
|
+
const versionFile = join12(PLATFORM_ROOT10, "config", `.${BRAND.hostname}-version`);
|
|
21480
|
+
if (!existsSync23(versionFile)) return "unknown";
|
|
21481
|
+
const content = readFileSync24(versionFile, "utf-8").trim();
|
|
22319
21482
|
return content || "unknown";
|
|
22320
21483
|
} catch {
|
|
22321
21484
|
return "unknown";
|
|
@@ -22356,9 +21519,9 @@ var clientErrorReporterScript = `<script>
|
|
|
22356
21519
|
function cachedHtml(file) {
|
|
22357
21520
|
let html = htmlCache.get(file);
|
|
22358
21521
|
if (!html) {
|
|
22359
|
-
html =
|
|
22360
|
-
html = html.replace("<title>Maxy</title>", `<title>${
|
|
22361
|
-
html = html.replace('href="/favicon.ico"', `href="${
|
|
21522
|
+
html = readFileSync24(resolve28(process.cwd(), "public", file), "utf-8");
|
|
21523
|
+
html = html.replace("<title>Maxy</title>", `<title>${escapeHtml(BRAND.productName)}</title>`);
|
|
21524
|
+
html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
|
|
22362
21525
|
const headInjection = file === "index.html" ? `${brandScript}
|
|
22363
21526
|
${versionScript}
|
|
22364
21527
|
${clientErrorReporterScript}
|
|
@@ -22371,26 +21534,26 @@ ${clientErrorReporterScript}
|
|
|
22371
21534
|
}
|
|
22372
21535
|
var brandedHtmlCache = /* @__PURE__ */ new Map();
|
|
22373
21536
|
function loadBrandingCache(agentSlug) {
|
|
22374
|
-
const configDir2 =
|
|
21537
|
+
const configDir2 = join12(homedir4(), BRAND.configDir);
|
|
22375
21538
|
try {
|
|
22376
|
-
const accountJsonPath =
|
|
22377
|
-
if (!
|
|
22378
|
-
const account = JSON.parse(
|
|
21539
|
+
const accountJsonPath = join12(configDir2, "account.json");
|
|
21540
|
+
if (!existsSync23(accountJsonPath)) return null;
|
|
21541
|
+
const account = JSON.parse(readFileSync24(accountJsonPath, "utf-8"));
|
|
22379
21542
|
const accountId = account.accountId;
|
|
22380
21543
|
if (!accountId) return null;
|
|
22381
|
-
const cachePath =
|
|
22382
|
-
if (!
|
|
22383
|
-
return JSON.parse(
|
|
21544
|
+
const cachePath = join12(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
|
|
21545
|
+
if (!existsSync23(cachePath)) return null;
|
|
21546
|
+
return JSON.parse(readFileSync24(cachePath, "utf-8"));
|
|
22384
21547
|
} catch {
|
|
22385
21548
|
return null;
|
|
22386
21549
|
}
|
|
22387
21550
|
}
|
|
22388
21551
|
function resolveDefaultSlug() {
|
|
22389
21552
|
try {
|
|
22390
|
-
const configDir2 =
|
|
22391
|
-
const accountJsonPath =
|
|
22392
|
-
if (!
|
|
22393
|
-
const account = JSON.parse(
|
|
21553
|
+
const configDir2 = join12(homedir4(), BRAND.configDir);
|
|
21554
|
+
const accountJsonPath = join12(configDir2, "account.json");
|
|
21555
|
+
if (!existsSync23(accountJsonPath)) return null;
|
|
21556
|
+
const account = JSON.parse(readFileSync24(accountJsonPath, "utf-8"));
|
|
22394
21557
|
return account.defaultAgent || null;
|
|
22395
21558
|
} catch {
|
|
22396
21559
|
return null;
|
|
@@ -22404,26 +21567,26 @@ function brandedPublicHtml(agentSlug) {
|
|
|
22404
21567
|
if (!branding) return baseHtml;
|
|
22405
21568
|
const brandHash = JSON.stringify(branding).length;
|
|
22406
21569
|
if (cached && cached.mtime === brandHash) return cached.html;
|
|
22407
|
-
const title =
|
|
22408
|
-
const description = branding.tagline ?
|
|
21570
|
+
const title = escapeHtml(branding.name);
|
|
21571
|
+
const description = branding.tagline ? escapeHtml(branding.tagline) : "";
|
|
22409
21572
|
const themeColor = branding.primaryColor || "";
|
|
22410
21573
|
const ogImage = branding.logoUrl || "";
|
|
22411
21574
|
const favicon = branding.faviconUrl || "";
|
|
22412
21575
|
const metaTags = [
|
|
22413
21576
|
` <title>${title}</title>`,
|
|
22414
|
-
favicon ? ` <link rel="icon" href="${
|
|
22415
|
-
themeColor ? ` <meta name="theme-color" content="${
|
|
21577
|
+
favicon ? ` <link rel="icon" href="${escapeHtml(favicon)}">` : ` <link rel="icon" href="${escapeHtml(brandFaviconPath)}">`,
|
|
21578
|
+
themeColor ? ` <meta name="theme-color" content="${escapeHtml(themeColor)}">` : "",
|
|
22416
21579
|
` <meta property="og:title" content="${title}">`,
|
|
22417
21580
|
description ? ` <meta property="og:description" content="${description}">` : "",
|
|
22418
|
-
ogImage ? ` <meta property="og:image" content="${
|
|
21581
|
+
ogImage ? ` <meta property="og:image" content="${escapeHtml(ogImage)}">` : "",
|
|
22419
21582
|
' <meta property="og:type" content="website">'
|
|
22420
21583
|
].filter(Boolean).join("\n");
|
|
22421
|
-
const html = baseHtml.replace(` <title>${
|
|
22422
|
-
<link rel="icon" href="${
|
|
21584
|
+
const html = baseHtml.replace(` <title>${escapeHtml(BRAND.productName)}</title>
|
|
21585
|
+
<link rel="icon" href="${escapeHtml(brandFaviconPath)}">`, metaTags);
|
|
22423
21586
|
brandedHtmlCache.set(agentSlug, { html, mtime: brandHash });
|
|
22424
21587
|
return html;
|
|
22425
21588
|
}
|
|
22426
|
-
function
|
|
21589
|
+
function escapeHtml(s) {
|
|
22427
21590
|
return s.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
22428
21591
|
}
|
|
22429
21592
|
app35.get("/", (c) => {
|
|
@@ -22463,11 +21626,11 @@ app35.use("/vnc-popout.html", logViewerFetch);
|
|
|
22463
21626
|
app35.get("/vnc-popout.html", (c) => {
|
|
22464
21627
|
let html = htmlCache.get("vnc-popout.html");
|
|
22465
21628
|
if (!html) {
|
|
22466
|
-
html =
|
|
22467
|
-
const name =
|
|
21629
|
+
html = readFileSync24(resolve28(process.cwd(), "public", "vnc-popout.html"), "utf-8");
|
|
21630
|
+
const name = escapeHtml(BRAND.productName);
|
|
22468
21631
|
html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
|
|
22469
21632
|
html = html.replace("</head>", ` ${brandScript}
|
|
22470
|
-
<link rel="icon" href="${
|
|
21633
|
+
<link rel="icon" href="${escapeHtml(brandFaviconPath)}">
|
|
22471
21634
|
</head>`);
|
|
22472
21635
|
htmlCache.set("vnc-popout.html", html);
|
|
22473
21636
|
}
|
|
@@ -22517,36 +21680,15 @@ app35.get("/:slug", async (c, next) => {
|
|
|
22517
21680
|
await next();
|
|
22518
21681
|
});
|
|
22519
21682
|
app35.use("/*", serveStatic({ root: "./public" }));
|
|
22520
|
-
var port = parseInt(process.env.PORT ?? "
|
|
22521
|
-
var hostname = process.env.HOSTNAME ?? "
|
|
21683
|
+
var port = parseInt(process.env.PORT ?? "19199", 10);
|
|
21684
|
+
var hostname = process.env.HOSTNAME ?? "127.0.0.1";
|
|
22522
21685
|
var httpServer = serve({ fetch: app35.fetch, port, hostname });
|
|
22523
|
-
attachVncWsProxy(httpServer, {
|
|
22524
|
-
isPublicHost,
|
|
22525
|
-
upstreamHost: "127.0.0.1",
|
|
22526
|
-
upstreamPort: 6080
|
|
22527
|
-
});
|
|
22528
21686
|
attachTerminalWsProxy(httpServer, {
|
|
22529
21687
|
isPublicHost,
|
|
22530
21688
|
upstreamHost: "127.0.0.1",
|
|
22531
21689
|
upstreamPort: 7681
|
|
22532
21690
|
});
|
|
22533
21691
|
console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
22534
|
-
setTimeout(() => {
|
|
22535
|
-
const socket = createConnection5(7681, "127.0.0.1");
|
|
22536
|
-
socket.setTimeout(500);
|
|
22537
|
-
socket.once("connect", () => {
|
|
22538
|
-
socket.destroy();
|
|
22539
|
-
console.log("[ttyd] upstream ready on 127.0.0.1:7681");
|
|
22540
|
-
});
|
|
22541
|
-
socket.once("error", (err) => {
|
|
22542
|
-
socket.destroy();
|
|
22543
|
-
console.error(`[ttyd] upstream NOT reachable on 127.0.0.1:7681 \u2014 admin terminal will be unavailable. Check 'sudo systemctl --user status maxy-ttyd' and /usr/local/bin/ttyd. (${err.message})`);
|
|
22544
|
-
});
|
|
22545
|
-
socket.once("timeout", () => {
|
|
22546
|
-
socket.destroy();
|
|
22547
|
-
console.error("[ttyd] upstream NOT reachable on 127.0.0.1:7681 (timeout) \u2014 admin terminal will be unavailable. Check 'sudo systemctl --user status maxy-ttyd'.");
|
|
22548
|
-
});
|
|
22549
|
-
}, 5e3);
|
|
22550
21692
|
var SUBAPP_MANIFEST = [
|
|
22551
21693
|
{ prefix: "/api/health", file: "server/routes/health.ts", subapp: health_default },
|
|
22552
21694
|
{ prefix: "/api/session", file: "server/routes/session.ts", subapp: session_default },
|
|
@@ -22579,8 +21721,8 @@ try {
|
|
|
22579
21721
|
(async () => {
|
|
22580
21722
|
try {
|
|
22581
21723
|
let userId = "";
|
|
22582
|
-
if (
|
|
22583
|
-
const users = JSON.parse(
|
|
21724
|
+
if (existsSync23(USERS_FILE)) {
|
|
21725
|
+
const users = JSON.parse(readFileSync24(USERS_FILE, "utf-8").trim() || "[]");
|
|
22584
21726
|
userId = users[0]?.userId ?? "";
|
|
22585
21727
|
}
|
|
22586
21728
|
await backfillNullUserIdConversations(userId);
|
|
@@ -22606,7 +21748,7 @@ if (bootAccountConfig?.whatsapp) {
|
|
|
22606
21748
|
}
|
|
22607
21749
|
init({
|
|
22608
21750
|
configDir: configDirForWhatsApp,
|
|
22609
|
-
platformRoot:
|
|
21751
|
+
platformRoot: resolve28(process.env.MAXY_PLATFORM_ROOT ?? join12(__dirname, "..")),
|
|
22610
21752
|
accountConfig: bootAccountConfig,
|
|
22611
21753
|
onMessage: async (msg) => {
|
|
22612
21754
|
try {
|