@rubytech/create-realagent 1.0.674 → 1.0.677
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/dist/index.js +77 -166
- package/dist/uninstall.js +24 -0
- package/package.json +1 -1
- package/payload/platform/plugins/docs/references/deployment.md +2 -3
- package/payload/platform/plugins/docs/references/platform.md +1 -1
- package/payload/platform/plugins/docs/references/troubleshooting.md +2 -2
- package/payload/platform/scripts/vnc.sh +34 -13
- package/payload/platform/templates/systemd/maxy-edge.service +33 -0
- package/payload/server/chunk-5YIXIF6C.js +726 -0
- package/payload/server/maxy-edge.js +422 -0
- package/payload/server/public/assets/{graph-BH7epmF8.js → graph-BDaM4Qer.js} +1 -1
- package/payload/server/public/graph.html +1 -1
- package/payload/server/server.js +560 -1812
- package/payload/platform/templates/dotfiles/.tmux.conf +0 -1
- package/payload/platform/templates/systemd/maxy-ttyd.service +0 -25
package/payload/server/server.js
CHANGED
|
@@ -1,28 +1,31 @@
|
|
|
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
|
+
recordFailedAttempt,
|
|
20
|
+
renderLoginPage,
|
|
21
|
+
resolveClientIp,
|
|
22
|
+
sanitizeClientCorrId,
|
|
23
|
+
setRemotePassword,
|
|
24
|
+
validatePasswordStrength,
|
|
25
|
+
verifyPassword,
|
|
26
|
+
verifyRemotePassword,
|
|
27
|
+
vncLog
|
|
28
|
+
} from "./chunk-5YIXIF6C.js";
|
|
26
29
|
|
|
27
30
|
// ../lib/models/dist/index.js
|
|
28
31
|
var require_dist = __commonJS({
|
|
@@ -2526,7 +2529,7 @@ var responseViaResponseObject = async (res, outgoing, options = {}) => {
|
|
|
2526
2529
|
});
|
|
2527
2530
|
if (!chunk) {
|
|
2528
2531
|
if (i === 1) {
|
|
2529
|
-
await new Promise((
|
|
2532
|
+
await new Promise((resolve28) => setTimeout(resolve28));
|
|
2530
2533
|
maxReadCount = 3;
|
|
2531
2534
|
continue;
|
|
2532
2535
|
}
|
|
@@ -2892,1248 +2895,9 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
2892
2895
|
};
|
|
2893
2896
|
|
|
2894
2897
|
// 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";
|
|
2903
|
-
|
|
2904
|
-
// app/lib/paths.ts
|
|
2905
|
-
import { homedir } from "os";
|
|
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");
|
|
2929
|
-
|
|
2930
|
-
// app/lib/vnc-logger.ts
|
|
2931
|
-
var VNC_LOG_FILE = resolve2(LOG_DIR, "vnc-boot.log");
|
|
2932
|
-
try {
|
|
2933
|
-
mkdirSync(LOG_DIR, { recursive: true });
|
|
2934
|
-
} catch (err) {
|
|
2935
|
-
console.error(`[vnc-log-fail] mkdir ${LOG_DIR} failed: ${err.message}`);
|
|
2936
|
-
}
|
|
2937
|
-
function vncLog(phase, fields = {}) {
|
|
2938
|
-
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
2939
|
-
const kv = Object.entries(fields).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(" ");
|
|
2940
|
-
const line = kv.length > 0 ? `[${ts}] [${phase}] ${kv}
|
|
2941
|
-
` : `[${ts}] [${phase}]
|
|
2942
|
-
`;
|
|
2943
|
-
try {
|
|
2944
|
-
appendFileSync(VNC_LOG_FILE, line);
|
|
2945
|
-
} catch (err) {
|
|
2946
|
-
console.error(`[vnc-log-fail] ${err.message} \u2014 dropped: ${line.slice(0, 300).trim()}`);
|
|
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;
|
|
3572
|
-
}
|
|
3573
|
-
}
|
|
3574
|
-
|
|
3575
|
-
// server/ws-proxy.ts
|
|
3576
|
-
import { createConnection } from "net";
|
|
3577
|
-
var WS_PATH = "/websockify";
|
|
3578
|
-
var UPSTREAM_TIMEOUT_MS = 5e3;
|
|
3579
|
-
var HOP_BY_HOP = /* @__PURE__ */ new Set([
|
|
3580
|
-
"connection",
|
|
3581
|
-
"keep-alive",
|
|
3582
|
-
"proxy-authenticate",
|
|
3583
|
-
"proxy-authorization",
|
|
3584
|
-
"te",
|
|
3585
|
-
"trailer",
|
|
3586
|
-
"transfer-encoding",
|
|
3587
|
-
"upgrade"
|
|
3588
|
-
]);
|
|
3589
|
-
function attachVncWsProxy(server, opts) {
|
|
3590
|
-
const upstreamHost = opts.upstreamHost ?? "127.0.0.1";
|
|
3591
|
-
const upstreamPort = opts.upstreamPort ?? 6080;
|
|
3592
|
-
server.on("upgrade", (req, clientSocket, head) => {
|
|
3593
|
-
try {
|
|
3594
|
-
handleUpgrade(req, clientSocket, head, {
|
|
3595
|
-
isPublicHost: opts.isPublicHost,
|
|
3596
|
-
upstreamHost,
|
|
3597
|
-
upstreamPort
|
|
3598
|
-
});
|
|
3599
|
-
} catch (err) {
|
|
3600
|
-
vncLog("ws-upgrade", {
|
|
3601
|
-
decision: "rejected",
|
|
3602
|
-
reason: "handler-exception",
|
|
3603
|
-
err: err.message
|
|
3604
|
-
});
|
|
3605
|
-
clientSocket.destroy();
|
|
3606
|
-
}
|
|
3607
|
-
});
|
|
3608
|
-
}
|
|
3609
|
-
function handleUpgrade(req, clientSocket, head, opts) {
|
|
3610
|
-
const url = req.url ?? "";
|
|
3611
|
-
const qsIndex = url.indexOf("?");
|
|
3612
|
-
const pathname = qsIndex === -1 ? url : url.slice(0, qsIndex);
|
|
3613
|
-
if (pathname !== WS_PATH) {
|
|
3614
|
-
return;
|
|
3615
|
-
}
|
|
3616
|
-
const corrId = newCorrId();
|
|
3617
|
-
const query = qsIndex === -1 ? "" : url.slice(qsIndex + 1);
|
|
3618
|
-
const rawClientCorrId = parseQueryParam(query, "corrId");
|
|
3619
|
-
const clientCorrId = sanitizeClientCorrId(rawClientCorrId);
|
|
3620
|
-
const hostHeader = (req.headers.host ?? "").split(":")[0];
|
|
3621
|
-
const originHeader = headerString(req.headers.origin);
|
|
3622
|
-
const remote = req.socket.remoteAddress;
|
|
3623
|
-
const xff = headerString(req.headers["x-forwarded-for"]);
|
|
3624
|
-
const decision = canAccessAdmin({
|
|
3625
|
-
host: hostHeader,
|
|
3626
|
-
remoteAddress: remote,
|
|
3627
|
-
xForwardedFor: xff,
|
|
3628
|
-
cookieHeader: headerString(req.headers.cookie),
|
|
3629
|
-
isPublicHost: opts.isPublicHost
|
|
3630
|
-
});
|
|
3631
|
-
if (!decision.allow) {
|
|
3632
|
-
const status = decision.reason === "public-host" ? 404 : 401;
|
|
3633
|
-
vncLog("ws-upgrade", {
|
|
3634
|
-
corrId,
|
|
3635
|
-
clientCorrId: clientCorrId ?? null,
|
|
3636
|
-
decision: "rejected",
|
|
3637
|
-
reason: decision.reason,
|
|
3638
|
-
ip: remote,
|
|
3639
|
-
xff: xff ?? null,
|
|
3640
|
-
origin: originHeader ?? null,
|
|
3641
|
-
host: hostHeader
|
|
3642
|
-
});
|
|
3643
|
-
writeStatusAndDestroy(clientSocket, status, decision.reason === "public-host" ? "Not Found" : "Unauthorized");
|
|
3644
|
-
return;
|
|
3645
|
-
}
|
|
3646
|
-
const originHost = parseOriginHost(originHeader);
|
|
3647
|
-
if (!originHost) {
|
|
3648
|
-
vncLog("ws-upgrade", {
|
|
3649
|
-
corrId,
|
|
3650
|
-
clientCorrId: clientCorrId ?? null,
|
|
3651
|
-
decision: "rejected",
|
|
3652
|
-
reason: "origin-missing-or-invalid",
|
|
3653
|
-
origin: originHeader ?? null,
|
|
3654
|
-
host: hostHeader,
|
|
3655
|
-
ip: remote
|
|
3656
|
-
});
|
|
3657
|
-
writeStatusAndDestroy(clientSocket, 403, "Forbidden");
|
|
3658
|
-
return;
|
|
3659
|
-
}
|
|
3660
|
-
if (originHost !== hostHeader) {
|
|
3661
|
-
vncLog("ws-upgrade", {
|
|
3662
|
-
corrId,
|
|
3663
|
-
clientCorrId: clientCorrId ?? null,
|
|
3664
|
-
decision: "rejected",
|
|
3665
|
-
reason: "origin-mismatch",
|
|
3666
|
-
origin_host: originHost,
|
|
3667
|
-
host: hostHeader,
|
|
3668
|
-
ip: remote
|
|
3669
|
-
});
|
|
3670
|
-
writeStatusAndDestroy(clientSocket, 403, "Forbidden");
|
|
3671
|
-
return;
|
|
3672
|
-
}
|
|
3673
|
-
if (opts.isPublicHost(originHost)) {
|
|
3674
|
-
vncLog("ws-upgrade", {
|
|
3675
|
-
corrId,
|
|
3676
|
-
clientCorrId: clientCorrId ?? null,
|
|
3677
|
-
decision: "rejected",
|
|
3678
|
-
reason: "origin-public-host",
|
|
3679
|
-
origin_host: originHost,
|
|
3680
|
-
host: hostHeader,
|
|
3681
|
-
ip: remote
|
|
3682
|
-
});
|
|
3683
|
-
writeStatusAndDestroy(clientSocket, 403, "Forbidden");
|
|
3684
|
-
return;
|
|
3685
|
-
}
|
|
3686
|
-
vncLog("ws-upgrade", {
|
|
3687
|
-
corrId,
|
|
3688
|
-
clientCorrId: clientCorrId ?? null,
|
|
3689
|
-
decision: "accepted",
|
|
3690
|
-
ip: remote,
|
|
3691
|
-
xff: xff ?? null,
|
|
3692
|
-
origin: originHeader ?? null,
|
|
3693
|
-
host: hostHeader,
|
|
3694
|
-
sec_ws_version: headerString(req.headers["sec-websocket-version"]) ?? null,
|
|
3695
|
-
sec_ws_protocol: headerString(req.headers["sec-websocket-protocol"]) ?? null
|
|
3696
|
-
});
|
|
3697
|
-
const upstream = createConnection({ host: opts.upstreamHost, port: opts.upstreamPort });
|
|
3698
|
-
upstream.setTimeout(UPSTREAM_TIMEOUT_MS);
|
|
3699
|
-
let bytesClientToUpstream = 0;
|
|
3700
|
-
let bytesUpstreamToClient = 0;
|
|
3701
|
-
let closedBy = null;
|
|
3702
|
-
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
|
-
let lastUpstreamByteTs = 0;
|
|
3985
|
-
let flowInterval = null;
|
|
3986
|
-
let lastEmittedAt = 0;
|
|
3987
|
-
let lastEmittedUpstreamBytes = 0;
|
|
3988
|
-
const finish = (side, reason) => {
|
|
3989
|
-
if (closedBy) return;
|
|
3990
|
-
closedBy = side;
|
|
3991
|
-
if (flowInterval) {
|
|
3992
|
-
clearInterval(flowInterval);
|
|
3993
|
-
flowInterval = null;
|
|
3994
|
-
}
|
|
3995
|
-
if (proxyOpened) {
|
|
3996
|
-
terminalLog("proxy-close", {
|
|
3997
|
-
corrId,
|
|
3998
|
-
closedBy: side,
|
|
3999
|
-
reason,
|
|
4000
|
-
clientBytes: bytesClientToUpstream,
|
|
4001
|
-
upstreamBytes: bytesUpstreamToClient
|
|
4002
|
-
});
|
|
4003
|
-
}
|
|
4004
|
-
clientSocket.destroy();
|
|
4005
|
-
upstream.destroy();
|
|
4006
|
-
};
|
|
4007
|
-
upstream.once("connect", () => {
|
|
4008
|
-
upstream.setTimeout(0);
|
|
4009
|
-
proxyOpened = true;
|
|
4010
|
-
terminalLog("proxy-open", {
|
|
4011
|
-
corrId,
|
|
4012
|
-
upstream: `${opts.upstreamHost}:${opts.upstreamPort}`
|
|
4013
|
-
});
|
|
4014
|
-
const lines = [];
|
|
4015
|
-
lines.push(`${req.method ?? "GET"} ${WS_PATH2} HTTP/${req.httpVersion}`);
|
|
4016
|
-
lines.push(`host: ${opts.upstreamHost}:${opts.upstreamPort}`);
|
|
4017
|
-
for (const [name, value] of Object.entries(req.headers)) {
|
|
4018
|
-
if (name === "host") continue;
|
|
4019
|
-
if (HOP_BY_HOP2.has(name)) continue;
|
|
4020
|
-
if (value == null) continue;
|
|
4021
|
-
if (Array.isArray(value)) {
|
|
4022
|
-
for (const v of value) lines.push(`${name}: ${v}`);
|
|
4023
|
-
} else {
|
|
4024
|
-
lines.push(`${name}: ${value}`);
|
|
4025
|
-
}
|
|
4026
|
-
}
|
|
4027
|
-
const upgradeHeader = headerString2(req.headers.upgrade);
|
|
4028
|
-
const connectionHeader = headerString2(req.headers.connection);
|
|
4029
|
-
if (upgradeHeader) lines.push(`upgrade: ${upgradeHeader}`);
|
|
4030
|
-
if (connectionHeader) lines.push(`connection: ${connectionHeader}`);
|
|
4031
|
-
upstream.write(lines.join("\r\n") + "\r\n\r\n");
|
|
4032
|
-
if (head && head.length > 0) upstream.write(head);
|
|
4033
|
-
clientSocket.on("data", (chunk) => {
|
|
4034
|
-
bytesClientToUpstream += chunk.length;
|
|
4035
|
-
});
|
|
4036
|
-
upstream.on("data", (chunk) => {
|
|
4037
|
-
bytesUpstreamToClient += chunk.length;
|
|
4038
|
-
lastUpstreamByteTs = Date.now();
|
|
4039
|
-
});
|
|
4040
|
-
clientSocket.pipe(upstream);
|
|
4041
|
-
upstream.pipe(clientSocket);
|
|
4042
|
-
lastEmittedAt = Date.now();
|
|
4043
|
-
lastEmittedUpstreamBytes = 0;
|
|
4044
|
-
flowInterval = setInterval(() => {
|
|
4045
|
-
const now = Date.now();
|
|
4046
|
-
const idleMs = lastUpstreamByteTs === 0 ? now - lastEmittedAt : now - lastUpstreamByteTs;
|
|
4047
|
-
const upstreamMoved = bytesUpstreamToClient !== lastEmittedUpstreamBytes;
|
|
4048
|
-
const sinceLastEmit = now - lastEmittedAt;
|
|
4049
|
-
if (upstreamMoved || sinceLastEmit >= FLOW_IDLE_EMIT_MS) {
|
|
4050
|
-
terminalLog("proxy-flow", {
|
|
4051
|
-
corrId,
|
|
4052
|
-
clientBytes: bytesClientToUpstream,
|
|
4053
|
-
upstreamBytes: bytesUpstreamToClient,
|
|
4054
|
-
idleMs
|
|
4055
|
-
});
|
|
4056
|
-
lastEmittedAt = now;
|
|
4057
|
-
lastEmittedUpstreamBytes = bytesUpstreamToClient;
|
|
4058
|
-
}
|
|
4059
|
-
}, FLOW_TICK_MS);
|
|
4060
|
-
clientSocket.once("close", (hadError) => {
|
|
4061
|
-
finish("client", hadError ? "error" : "normal");
|
|
4062
|
-
});
|
|
4063
|
-
upstream.once("close", (hadError) => {
|
|
4064
|
-
finish("upstream", hadError ? "error" : "normal");
|
|
4065
|
-
});
|
|
4066
|
-
clientSocket.once("error", (err) => {
|
|
4067
|
-
terminalLog("proxy-error", { corrId, side: "client", err: err.message });
|
|
4068
|
-
finish("client", "error");
|
|
4069
|
-
});
|
|
4070
|
-
upstream.once("error", (err) => {
|
|
4071
|
-
terminalLog("proxy-error", { corrId, side: "upstream", err: err.message });
|
|
4072
|
-
finish("upstream", "error");
|
|
4073
|
-
});
|
|
4074
|
-
});
|
|
4075
|
-
upstream.once("timeout", () => {
|
|
4076
|
-
if (proxyOpened) return;
|
|
4077
|
-
terminalLog("proxy-error", {
|
|
4078
|
-
corrId,
|
|
4079
|
-
side: "upstream-connect",
|
|
4080
|
-
err: "timeout",
|
|
4081
|
-
timeout_ms: UPSTREAM_TIMEOUT_MS2
|
|
4082
|
-
});
|
|
4083
|
-
writeStatusAndDestroy2(clientSocket, 504, "Gateway Timeout");
|
|
4084
|
-
upstream.destroy();
|
|
4085
|
-
});
|
|
4086
|
-
upstream.once("error", (err) => {
|
|
4087
|
-
if (proxyOpened) return;
|
|
4088
|
-
terminalLog("proxy-error", {
|
|
4089
|
-
corrId,
|
|
4090
|
-
side: "upstream-connect",
|
|
4091
|
-
err: err.message
|
|
4092
|
-
});
|
|
4093
|
-
writeStatusAndDestroy2(clientSocket, 502, "Bad Gateway");
|
|
4094
|
-
upstream.destroy();
|
|
4095
|
-
});
|
|
4096
|
-
}
|
|
4097
|
-
function parseQueryParam2(query, key) {
|
|
4098
|
-
if (!query) return null;
|
|
4099
|
-
for (const pair of query.split("&")) {
|
|
4100
|
-
const eq = pair.indexOf("=");
|
|
4101
|
-
const k = eq === -1 ? pair : pair.slice(0, eq);
|
|
4102
|
-
if (k !== key) continue;
|
|
4103
|
-
const v = eq === -1 ? "" : pair.slice(eq + 1);
|
|
4104
|
-
try {
|
|
4105
|
-
return decodeURIComponent(v);
|
|
4106
|
-
} catch {
|
|
4107
|
-
return null;
|
|
4108
|
-
}
|
|
4109
|
-
}
|
|
4110
|
-
return null;
|
|
4111
|
-
}
|
|
4112
|
-
function headerString2(value) {
|
|
4113
|
-
if (value == null) return void 0;
|
|
4114
|
-
return Array.isArray(value) ? value[0] : value;
|
|
4115
|
-
}
|
|
4116
|
-
function parseOriginHost2(origin) {
|
|
4117
|
-
if (!origin) return null;
|
|
4118
|
-
try {
|
|
4119
|
-
return new URL(origin).hostname;
|
|
4120
|
-
} catch {
|
|
4121
|
-
return null;
|
|
4122
|
-
}
|
|
4123
|
-
}
|
|
4124
|
-
function writeStatusAndDestroy2(socket, status, statusText) {
|
|
4125
|
-
try {
|
|
4126
|
-
socket.write(
|
|
4127
|
-
`HTTP/1.1 ${status} ${statusText}\r
|
|
4128
|
-
Connection: close\r
|
|
4129
|
-
Content-Length: 0\r
|
|
4130
|
-
\r
|
|
4131
|
-
`
|
|
4132
|
-
);
|
|
4133
|
-
} catch {
|
|
4134
|
-
}
|
|
4135
|
-
socket.destroy();
|
|
4136
|
-
}
|
|
2898
|
+
import { readFileSync as readFileSync24, existsSync as existsSync23, watchFile } from "fs";
|
|
2899
|
+
import { resolve as resolve27, join as join12, basename as basename7 } from "path";
|
|
2900
|
+
import { homedir as homedir4 } from "os";
|
|
4137
2901
|
|
|
4138
2902
|
// app/lib/agent-slug-pattern.ts
|
|
4139
2903
|
var AGENT_SLUG_PATTERN = /^\/([a-z][a-z0-9-]{2,49})$/;
|
|
@@ -4142,9 +2906,9 @@ var AGENT_SLUG_PATTERN = /^\/([a-z][a-z0-9-]{2,49})$/;
|
|
|
4142
2906
|
import Anthropic3 from "@anthropic-ai/sdk";
|
|
4143
2907
|
import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
|
|
4144
2908
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
4145
|
-
import { resolve as
|
|
2909
|
+
import { resolve as resolve5, join as join3 } from "path";
|
|
4146
2910
|
import { platform as osPlatform } from "os";
|
|
4147
|
-
import { readFileSync as
|
|
2911
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, readdirSync as readdirSync2, existsSync as existsSync5, mkdirSync as mkdirSync4, createWriteStream, statSync as statSync3, unlinkSync as unlinkSync3, cpSync, rmSync as rmSync2, appendFileSync, openSync as openSync2, readSync as readSync2, closeSync as closeSync2 } from "fs";
|
|
4148
2912
|
import { lookup as dnsLookup } from "dns/promises";
|
|
4149
2913
|
import { createConnection as netConnect } from "net";
|
|
4150
2914
|
import { StringDecoder } from "string_decoder";
|
|
@@ -4152,34 +2916,34 @@ import { StringDecoder } from "string_decoder";
|
|
|
4152
2916
|
// ../lib/anthropic-key/src/index.ts
|
|
4153
2917
|
var import_dist = __toESM(require_dist());
|
|
4154
2918
|
import {
|
|
4155
|
-
existsSync as
|
|
4156
|
-
mkdirSync
|
|
4157
|
-
readFileSync
|
|
2919
|
+
existsSync as existsSync2,
|
|
2920
|
+
mkdirSync,
|
|
2921
|
+
readFileSync,
|
|
4158
2922
|
unlinkSync,
|
|
4159
|
-
writeFileSync
|
|
2923
|
+
writeFileSync
|
|
4160
2924
|
} from "fs";
|
|
4161
|
-
import { resolve
|
|
4162
|
-
import { homedir
|
|
2925
|
+
import { resolve, join as join2, dirname } from "path";
|
|
2926
|
+
import { homedir } from "os";
|
|
4163
2927
|
var cachedKeyFilePath = null;
|
|
4164
2928
|
function resolveKeyFilePath() {
|
|
4165
2929
|
if (cachedKeyFilePath) return cachedKeyFilePath;
|
|
4166
|
-
let
|
|
4167
|
-
const
|
|
4168
|
-
if (
|
|
4169
|
-
const brandPath =
|
|
4170
|
-
if (!
|
|
2930
|
+
let configDirName = ".maxy";
|
|
2931
|
+
const platformRoot2 = process.env.PLATFORM_ROOT ?? process.env.MAXY_PLATFORM_ROOT;
|
|
2932
|
+
if (platformRoot2) {
|
|
2933
|
+
const brandPath = join2(platformRoot2, "config", "brand.json");
|
|
2934
|
+
if (!existsSync2(brandPath)) {
|
|
4171
2935
|
throw new Error(
|
|
4172
2936
|
`brand.json not found at ${brandPath} \u2014 platform not properly installed`
|
|
4173
2937
|
);
|
|
4174
2938
|
}
|
|
4175
2939
|
try {
|
|
4176
|
-
const brand = JSON.parse(
|
|
2940
|
+
const brand = JSON.parse(readFileSync(brandPath, "utf-8"));
|
|
4177
2941
|
if (!brand.configDir) {
|
|
4178
2942
|
throw new Error(
|
|
4179
2943
|
`brand.json at ${brandPath} is missing the configDir field`
|
|
4180
2944
|
);
|
|
4181
2945
|
}
|
|
4182
|
-
|
|
2946
|
+
configDirName = brand.configDir;
|
|
4183
2947
|
} catch (err) {
|
|
4184
2948
|
if (err instanceof SyntaxError) {
|
|
4185
2949
|
throw new Error(
|
|
@@ -4189,7 +2953,7 @@ function resolveKeyFilePath() {
|
|
|
4189
2953
|
throw err;
|
|
4190
2954
|
}
|
|
4191
2955
|
}
|
|
4192
|
-
cachedKeyFilePath =
|
|
2956
|
+
cachedKeyFilePath = resolve(homedir(), configDirName, ".anthropic-api-key");
|
|
4193
2957
|
return cachedKeyFilePath;
|
|
4194
2958
|
}
|
|
4195
2959
|
function readKey() {
|
|
@@ -4211,7 +2975,7 @@ function readKey() {
|
|
|
4211
2975
|
);
|
|
4212
2976
|
}
|
|
4213
2977
|
try {
|
|
4214
|
-
const raw2 =
|
|
2978
|
+
const raw2 = readFileSync(keyFilePath2, "utf-8").trim();
|
|
4215
2979
|
if (!raw2) {
|
|
4216
2980
|
console.error(
|
|
4217
2981
|
`[anthropic-key] key file exists but is empty: ${keyFilePath2}`
|
|
@@ -4325,14 +3089,14 @@ function contextWindow(model) {
|
|
|
4325
3089
|
}
|
|
4326
3090
|
|
|
4327
3091
|
// app/lib/claude-auth.ts
|
|
4328
|
-
import { readFileSync as
|
|
3092
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
4329
3093
|
var TOKEN_ENDPOINT = "https://platform.claude.com/v1/oauth/token";
|
|
4330
3094
|
var CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
4331
3095
|
var EXPIRING_THRESHOLD_MS = 5 * 60 * 1e3;
|
|
4332
3096
|
function readCredentials() {
|
|
4333
3097
|
let raw2;
|
|
4334
3098
|
try {
|
|
4335
|
-
raw2 =
|
|
3099
|
+
raw2 = readFileSync2(CLAUDE_CREDENTIALS_FILE, "utf-8");
|
|
4336
3100
|
} catch {
|
|
4337
3101
|
return null;
|
|
4338
3102
|
}
|
|
@@ -4360,7 +3124,7 @@ function writeOAuthCredentials(tokens) {
|
|
|
4360
3124
|
try {
|
|
4361
3125
|
let fileData = {};
|
|
4362
3126
|
try {
|
|
4363
|
-
const rawFile =
|
|
3127
|
+
const rawFile = readFileSync2(CLAUDE_CREDENTIALS_FILE, "utf-8");
|
|
4364
3128
|
fileData = JSON.parse(rawFile);
|
|
4365
3129
|
} catch {
|
|
4366
3130
|
}
|
|
@@ -4371,7 +3135,7 @@ function writeOAuthCredentials(tokens) {
|
|
|
4371
3135
|
...tokens.refreshToken != null ? { refreshToken: tokens.refreshToken } : {},
|
|
4372
3136
|
expiresAt: newExpiresAt
|
|
4373
3137
|
};
|
|
4374
|
-
|
|
3138
|
+
writeFileSync2(CLAUDE_CREDENTIALS_FILE, JSON.stringify(fileData, null, 2), "utf-8");
|
|
4375
3139
|
return newExpiresAt;
|
|
4376
3140
|
} catch (err) {
|
|
4377
3141
|
console.error(`[claude-auth] credential write failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -4475,11 +3239,11 @@ async function ensureAuth() {
|
|
|
4475
3239
|
|
|
4476
3240
|
// app/lib/vnc.ts
|
|
4477
3241
|
import { spawnSync, execFileSync } from "child_process";
|
|
4478
|
-
import { createConnection
|
|
4479
|
-
import { mkdirSync as
|
|
4480
|
-
import { resolve as
|
|
4481
|
-
var
|
|
4482
|
-
var VNC_SCRIPT =
|
|
3242
|
+
import { createConnection } from "net";
|
|
3243
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
3244
|
+
import { resolve as resolve2 } from "path";
|
|
3245
|
+
var PLATFORM_ROOT = process.env.MAXY_PLATFORM_ROOT ?? resolve2(process.cwd(), "..");
|
|
3246
|
+
var VNC_SCRIPT = resolve2(PLATFORM_ROOT, "scripts/vnc.sh");
|
|
4483
3247
|
var displayMode = process.env.DISPLAY_MODE ?? "virtual";
|
|
4484
3248
|
if (displayMode === "native") {
|
|
4485
3249
|
console.log(`[vnc] DISPLAY_MODE=native \u2014 local requests use desktop display, remote requests use VNC`);
|
|
@@ -4488,8 +3252,8 @@ function resolveBrowserTransport(req, remoteAddress) {
|
|
|
4488
3252
|
if (displayMode !== "native") return "vnc";
|
|
4489
3253
|
const xff = req.headers.get("x-forwarded-for") ?? void 0;
|
|
4490
3254
|
const clientIp = resolveClientIp(remoteAddress ?? "127.0.0.1", xff);
|
|
4491
|
-
const
|
|
4492
|
-
const transport =
|
|
3255
|
+
const isLoopback = clientIp === "127.0.0.1" || clientIp === "::1" || clientIp === "loopback";
|
|
3256
|
+
const transport = isLoopback && !xff ? "native" : "vnc";
|
|
4493
3257
|
if (remoteAddress && remoteAddress !== "127.0.0.1" && remoteAddress !== "::1") {
|
|
4494
3258
|
const oldClientIp = resolveClientIp("127.0.0.1", xff);
|
|
4495
3259
|
const oldIsLoopback = oldClientIp === "127.0.0.1" || oldClientIp === "::1" || oldClientIp === "loopback";
|
|
@@ -4537,7 +3301,7 @@ function discoverNativeDisplay() {
|
|
|
4537
3301
|
const leaderPid = leaderResult.stdout?.trim();
|
|
4538
3302
|
if (leaderPid) {
|
|
4539
3303
|
try {
|
|
4540
|
-
const environ =
|
|
3304
|
+
const environ = readFileSync3(`/proc/${leaderPid}/environ`, "utf8");
|
|
4541
3305
|
const match2 = environ.split("\0").find((e) => e.startsWith("WAYLAND_DISPLAY="));
|
|
4542
3306
|
if (match2) waylandDisplay = match2.split("=")[1];
|
|
4543
3307
|
} catch {
|
|
@@ -4571,7 +3335,7 @@ async function waitForPort(port2, timeoutMs = 12e3) {
|
|
|
4571
3335
|
const deadline = Date.now() + timeoutMs;
|
|
4572
3336
|
while (Date.now() < deadline) {
|
|
4573
3337
|
const ready = await new Promise((res) => {
|
|
4574
|
-
const socket =
|
|
3338
|
+
const socket = createConnection(port2, "127.0.0.1");
|
|
4575
3339
|
socket.setTimeout(500);
|
|
4576
3340
|
socket.once("connect", () => {
|
|
4577
3341
|
socket.destroy();
|
|
@@ -4592,10 +3356,10 @@ async function waitForPort(port2, timeoutMs = 12e3) {
|
|
|
4592
3356
|
return false;
|
|
4593
3357
|
}
|
|
4594
3358
|
function ensureLogDir() {
|
|
4595
|
-
|
|
3359
|
+
mkdirSync2(LOG_DIR, { recursive: true });
|
|
4596
3360
|
}
|
|
4597
3361
|
function logPath(name) {
|
|
4598
|
-
return
|
|
3362
|
+
return resolve2(LOG_DIR, `${name}.log`);
|
|
4599
3363
|
}
|
|
4600
3364
|
async function ensureVnc() {
|
|
4601
3365
|
const up = await waitForPort(5900, 1e3);
|
|
@@ -4804,9 +3568,9 @@ async function ensureTerminalUpgrade(transport = "vnc") {
|
|
|
4804
3568
|
return { ok: true };
|
|
4805
3569
|
}
|
|
4806
3570
|
function writeChromiumWrapper() {
|
|
4807
|
-
|
|
4808
|
-
const wrapperPath =
|
|
4809
|
-
|
|
3571
|
+
mkdirSync2(BIN_DIR, { recursive: true });
|
|
3572
|
+
const wrapperPath = resolve2(BIN_DIR, "chromium");
|
|
3573
|
+
writeFileSync3(wrapperPath, `#!/bin/bash
|
|
4810
3574
|
LOG="${LOG_DIR}/chromium.log"
|
|
4811
3575
|
echo "==== [$(date)] chromium wrapper ====" >> "$LOG"
|
|
4812
3576
|
echo " DISPLAY=$DISPLAY WAYLAND=$WAYLAND_DISPLAY XDG_SESSION_TYPE=$XDG_SESSION_TYPE" >> "$LOG"
|
|
@@ -4890,15 +3654,15 @@ function buildX11Env(chromiumWrapperPath, transport = "vnc") {
|
|
|
4890
3654
|
import neo4j from "neo4j-driver";
|
|
4891
3655
|
import { randomUUID } from "crypto";
|
|
4892
3656
|
import { spawn } from "child_process";
|
|
4893
|
-
import { readFileSync as
|
|
4894
|
-
import { resolve as
|
|
4895
|
-
var
|
|
3657
|
+
import { readFileSync as readFileSync4, readdirSync, existsSync as existsSync3, openSync, readSync, closeSync, statSync as statSync2, rmSync } from "fs";
|
|
3658
|
+
import { resolve as resolve3 } from "path";
|
|
3659
|
+
var PLATFORM_ROOT2 = process.env.MAXY_PLATFORM_ROOT ?? resolve3(process.cwd(), "..");
|
|
4896
3660
|
var driver = null;
|
|
4897
3661
|
function readPassword() {
|
|
4898
3662
|
if (process.env.NEO4J_PASSWORD) return process.env.NEO4J_PASSWORD;
|
|
4899
|
-
const passwordFile =
|
|
3663
|
+
const passwordFile = resolve3(PLATFORM_ROOT2, "config/.neo4j-password");
|
|
4900
3664
|
try {
|
|
4901
|
-
return
|
|
3665
|
+
return readFileSync4(passwordFile, "utf-8").trim();
|
|
4902
3666
|
} catch {
|
|
4903
3667
|
throw new Error(
|
|
4904
3668
|
`Neo4j password not found. Expected at ${passwordFile} or in NEO4J_PASSWORD env var.`
|
|
@@ -5361,8 +4125,8 @@ async function persistMessage(conversationId, role, content, accountId, tokens,
|
|
|
5361
4125
|
const prev = persistMessageLocks.get(conversationId);
|
|
5362
4126
|
const waited = prev !== void 0;
|
|
5363
4127
|
let release;
|
|
5364
|
-
const mine = new Promise((
|
|
5365
|
-
release =
|
|
4128
|
+
const mine = new Promise((resolve28) => {
|
|
4129
|
+
release = resolve28;
|
|
5366
4130
|
});
|
|
5367
4131
|
const chained = (prev ?? Promise.resolve()).then(() => mine);
|
|
5368
4132
|
persistMessageLocks.set(conversationId, chained);
|
|
@@ -5621,7 +4385,7 @@ ${userContent}`;
|
|
|
5621
4385
|
"dontAsk",
|
|
5622
4386
|
prompt
|
|
5623
4387
|
];
|
|
5624
|
-
return new Promise((
|
|
4388
|
+
return new Promise((resolve28) => {
|
|
5625
4389
|
let stdout = "";
|
|
5626
4390
|
let stderr = "";
|
|
5627
4391
|
const spawnFn = _spawnOverride ?? spawn;
|
|
@@ -5639,35 +4403,35 @@ ${userContent}`;
|
|
|
5639
4403
|
const timer = setTimeout(() => {
|
|
5640
4404
|
proc.kill("SIGTERM");
|
|
5641
4405
|
console.error("[persist] autoLabel: haiku subprocess timed out");
|
|
5642
|
-
|
|
4406
|
+
resolve28(null);
|
|
5643
4407
|
}, SESSION_LABEL_TIMEOUT_MS);
|
|
5644
4408
|
proc.on("error", (err) => {
|
|
5645
4409
|
clearTimeout(timer);
|
|
5646
4410
|
console.error(`[persist] autoLabel: subprocess error \u2014 ${err.message}`);
|
|
5647
|
-
|
|
4411
|
+
resolve28(null);
|
|
5648
4412
|
});
|
|
5649
4413
|
proc.on("close", (code) => {
|
|
5650
4414
|
clearTimeout(timer);
|
|
5651
4415
|
if (code !== 0) {
|
|
5652
4416
|
console.error(`[persist] autoLabel: subprocess exited code=${code}${stderr ? ` stderr=${stderr.trim().slice(0, 200)}` : ""}`);
|
|
5653
|
-
|
|
4417
|
+
resolve28(null);
|
|
5654
4418
|
return;
|
|
5655
4419
|
}
|
|
5656
4420
|
const text = stdout.trim();
|
|
5657
4421
|
if (!text) {
|
|
5658
4422
|
console.error("[persist] autoLabel: haiku returned empty response");
|
|
5659
|
-
|
|
4423
|
+
resolve28(null);
|
|
5660
4424
|
return;
|
|
5661
4425
|
}
|
|
5662
4426
|
if (text === "SKIP") {
|
|
5663
4427
|
console.error("[persist] autoLabel: haiku returned SKIP \u2014 messages too vague");
|
|
5664
|
-
|
|
4428
|
+
resolve28(null);
|
|
5665
4429
|
return;
|
|
5666
4430
|
}
|
|
5667
4431
|
const words = text.split(/\s+/).slice(0, SESSION_LABEL_MAX_WORDS);
|
|
5668
4432
|
const label = words.join(" ");
|
|
5669
4433
|
console.error(`[persist] autoLabel: haiku response="${label}"`);
|
|
5670
|
-
|
|
4434
|
+
resolve28(label);
|
|
5671
4435
|
});
|
|
5672
4436
|
});
|
|
5673
4437
|
}
|
|
@@ -5939,10 +4703,10 @@ var MAX_RECENT_TOOL_FAILURES = 3;
|
|
|
5939
4703
|
var RECENT_FAILURES_TAIL_BYTES = 10 * 1024;
|
|
5940
4704
|
function readRecentToolFailures(accountId, conversationId) {
|
|
5941
4705
|
try {
|
|
5942
|
-
const
|
|
5943
|
-
const logDir =
|
|
5944
|
-
const logPath2 =
|
|
5945
|
-
if (!
|
|
4706
|
+
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? resolve3(process.cwd(), "..");
|
|
4707
|
+
const logDir = resolve3(platformRoot2, "..", "data/accounts", accountId, "logs");
|
|
4708
|
+
const logPath2 = resolve3(logDir, `claude-agent-stream-${conversationId}.log`);
|
|
4709
|
+
if (!existsSync3(logPath2)) {
|
|
5946
4710
|
console.error(`[review-tail-skip] path=${logPath2} reason=file-missing \u2014 first turn of conversation, subprocess not yet spawned, or log rotated`);
|
|
5947
4711
|
return [];
|
|
5948
4712
|
}
|
|
@@ -6098,13 +4862,13 @@ ${taskLines.join("\n")}`);
|
|
|
6098
4862
|
let pendingCount = 0;
|
|
6099
4863
|
let pendingLines = [];
|
|
6100
4864
|
try {
|
|
6101
|
-
const
|
|
6102
|
-
const pendingDir =
|
|
6103
|
-
if (
|
|
4865
|
+
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? resolve3(process.cwd(), "..");
|
|
4866
|
+
const pendingDir = resolve3(platformRoot2, "..", "data/accounts", accountId, "pending-actions");
|
|
4867
|
+
if (existsSync3(pendingDir)) {
|
|
6104
4868
|
const files = readdirSync(pendingDir).filter((f) => f.endsWith(".json") && !f.startsWith("."));
|
|
6105
4869
|
for (const file of files) {
|
|
6106
4870
|
try {
|
|
6107
|
-
const raw2 =
|
|
4871
|
+
const raw2 = readFileSync4(resolve3(pendingDir, file), "utf-8");
|
|
6108
4872
|
const action = JSON.parse(raw2);
|
|
6109
4873
|
if (action.state === "pending") {
|
|
6110
4874
|
const inputSummary = JSON.stringify(action.hookPayload?.tool_input ?? {}).slice(0, 150);
|
|
@@ -6171,12 +4935,12 @@ ${sections.join("\n\n")}
|
|
|
6171
4935
|
}
|
|
6172
4936
|
}
|
|
6173
4937
|
async function consumeStep7FlagUI(session, accountId) {
|
|
6174
|
-
const accountDir =
|
|
6175
|
-
const flagPath =
|
|
6176
|
-
if (!
|
|
4938
|
+
const accountDir = resolve3(PLATFORM_ROOT2, "..", "data/accounts", accountId);
|
|
4939
|
+
const flagPath = resolve3(accountDir, "onboarding", "step7-complete");
|
|
4940
|
+
if (!existsSync3(flagPath)) return false;
|
|
6177
4941
|
let completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6178
4942
|
try {
|
|
6179
|
-
const raw2 =
|
|
4943
|
+
const raw2 = readFileSync4(flagPath, "utf-8").trim();
|
|
6180
4944
|
if (raw2) {
|
|
6181
4945
|
const parsed = JSON.parse(raw2);
|
|
6182
4946
|
if (typeof parsed.completedAt === "string") {
|
|
@@ -6614,8 +5378,8 @@ ${items}
|
|
|
6614
5378
|
}
|
|
6615
5379
|
|
|
6616
5380
|
// app/lib/adherence-ledger.ts
|
|
6617
|
-
import { existsSync as
|
|
6618
|
-
import { resolve as
|
|
5381
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, renameSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
5382
|
+
import { resolve as resolve4, dirname as dirname2 } from "path";
|
|
6619
5383
|
import lockfile from "proper-lockfile";
|
|
6620
5384
|
var LOG_TAG = "[adherence-ledger]";
|
|
6621
5385
|
var ADHERENCE_BLOCK_THRESHOLD = 5;
|
|
@@ -6630,7 +5394,7 @@ function nowIso() {
|
|
|
6630
5394
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
6631
5395
|
}
|
|
6632
5396
|
function ledgerPath(accountDir, agentName) {
|
|
6633
|
-
return
|
|
5397
|
+
return resolve4(accountDir, "agents", agentName, "adherence-ledger.json");
|
|
6634
5398
|
}
|
|
6635
5399
|
function lockPath(accountDir, agentName) {
|
|
6636
5400
|
return `${ledgerPath(accountDir, agentName)}.lock`;
|
|
@@ -6655,8 +5419,8 @@ function pruneViolations(violations) {
|
|
|
6655
5419
|
});
|
|
6656
5420
|
}
|
|
6657
5421
|
function readUnlocked(path2, accountId, agentName) {
|
|
6658
|
-
if (!
|
|
6659
|
-
const content =
|
|
5422
|
+
if (!existsSync4(path2)) return emptyLedger(accountId, agentName);
|
|
5423
|
+
const content = readFileSync5(path2, "utf-8");
|
|
6660
5424
|
if (!content.trim()) return emptyLedger(accountId, agentName);
|
|
6661
5425
|
const data = JSON.parse(content);
|
|
6662
5426
|
if (typeof data !== "object" || data === null) {
|
|
@@ -6675,10 +5439,10 @@ function writeAtomic(path2, data) {
|
|
|
6675
5439
|
}
|
|
6676
5440
|
data.updated_at = nowIso();
|
|
6677
5441
|
const dir = dirname2(path2);
|
|
6678
|
-
|
|
5442
|
+
mkdirSync3(dir, { recursive: true });
|
|
6679
5443
|
const tmp = `${path2}.tmp-${process.pid}-${Date.now()}`;
|
|
6680
5444
|
try {
|
|
6681
|
-
|
|
5445
|
+
writeFileSync4(tmp, JSON.stringify(data, null, 2), "utf-8");
|
|
6682
5446
|
renameSync(tmp, path2);
|
|
6683
5447
|
} catch (err) {
|
|
6684
5448
|
try {
|
|
@@ -6691,9 +5455,9 @@ function writeAtomic(path2, data) {
|
|
|
6691
5455
|
async function withLock(accountDir, agentName, fn) {
|
|
6692
5456
|
const path2 = ledgerPath(accountDir, agentName);
|
|
6693
5457
|
const lock = lockPath(accountDir, agentName);
|
|
6694
|
-
|
|
6695
|
-
if (!
|
|
6696
|
-
|
|
5458
|
+
mkdirSync3(dirname2(lock), { recursive: true });
|
|
5459
|
+
if (!existsSync4(lock)) {
|
|
5460
|
+
writeFileSync4(lock, "", "utf-8");
|
|
6697
5461
|
}
|
|
6698
5462
|
const release = await lockfile.lock(lock, {
|
|
6699
5463
|
realpath: false,
|
|
@@ -7179,20 +5943,20 @@ function agentLogStream(name, accountDir, conversationId) {
|
|
|
7179
5943
|
if (!conversationId) {
|
|
7180
5944
|
throw new Error(`agentLogStream: conversationId is required (name=${name}) \u2014 use preConversationLogStream for pre-session events`);
|
|
7181
5945
|
}
|
|
7182
|
-
const logDir =
|
|
7183
|
-
|
|
5946
|
+
const logDir = resolve5(accountDir, "logs");
|
|
5947
|
+
mkdirSync4(logDir, { recursive: true });
|
|
7184
5948
|
purgeOldLogs(logDir, `${name}-`);
|
|
7185
|
-
const logPath2 =
|
|
5949
|
+
const logPath2 = resolve5(logDir, `${name}-${conversationId}.log`);
|
|
7186
5950
|
const stream = createWriteStream(logPath2, { flags: "a" });
|
|
7187
5951
|
registerStreamLog(stream, { path: logPath2, conversationId, name });
|
|
7188
5952
|
return stream;
|
|
7189
5953
|
}
|
|
7190
5954
|
function preConversationLogStream(name, accountDir) {
|
|
7191
|
-
const logDir =
|
|
7192
|
-
|
|
5955
|
+
const logDir = resolve5(accountDir, "logs");
|
|
5956
|
+
mkdirSync4(logDir, { recursive: true });
|
|
7193
5957
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
7194
5958
|
purgeOldLogs(logDir, `preconversation-${name}-`);
|
|
7195
|
-
const logPath2 =
|
|
5959
|
+
const logPath2 = resolve5(logDir, `preconversation-${name}-${date}.log`);
|
|
7196
5960
|
const stream = createWriteStream(logPath2, { flags: "a" });
|
|
7197
5961
|
registerStreamLog(stream, { path: logPath2, conversationId: null, name: `preconversation-${name}` });
|
|
7198
5962
|
return stream;
|
|
@@ -7211,7 +5975,7 @@ function sigtermFlushStreamLogs(reason, source) {
|
|
|
7211
5975
|
const line = `[${ts}] [server-sigterm] reason=${reason}${convPart} name=${entry.name} source=${source}
|
|
7212
5976
|
`;
|
|
7213
5977
|
try {
|
|
7214
|
-
|
|
5978
|
+
appendFileSync(entry.path, line);
|
|
7215
5979
|
} catch (err) {
|
|
7216
5980
|
const msg = err instanceof Error ? err.message : String(err);
|
|
7217
5981
|
console.error(`[server-sigterm-flush-err] path=${entry.path} reason=${msg}`);
|
|
@@ -7230,7 +5994,7 @@ function purgeOldLogs(logDir, prefix) {
|
|
|
7230
5994
|
}
|
|
7231
5995
|
for (const file of entries) {
|
|
7232
5996
|
if (!file.startsWith(prefix)) continue;
|
|
7233
|
-
const filePath =
|
|
5997
|
+
const filePath = resolve5(logDir, file);
|
|
7234
5998
|
try {
|
|
7235
5999
|
if (statSync3(filePath).mtimeMs < cutoff) unlinkSync3(filePath);
|
|
7236
6000
|
} catch (err) {
|
|
@@ -7299,7 +6063,7 @@ function sampleProcState(pid) {
|
|
|
7299
6063
|
let sockets2 = 0;
|
|
7300
6064
|
for (const tcpFile of ["/proc/" + pid + "/net/tcp", "/proc/" + pid + "/net/tcp6"]) {
|
|
7301
6065
|
try {
|
|
7302
|
-
const raw2 =
|
|
6066
|
+
const raw2 = readFileSync6(tcpFile, "utf-8");
|
|
7303
6067
|
const lines2 = raw2.split("\n");
|
|
7304
6068
|
for (let i = 1; i < lines2.length; i++) {
|
|
7305
6069
|
const line = lines2[i].trim();
|
|
@@ -7315,7 +6079,7 @@ function sampleProcState(pid) {
|
|
|
7315
6079
|
}
|
|
7316
6080
|
let rssMb = 0;
|
|
7317
6081
|
try {
|
|
7318
|
-
const statm =
|
|
6082
|
+
const statm = readFileSync6(`/proc/${pid}/statm`, "utf-8").trim().split(/\s+/);
|
|
7319
6083
|
const rssPages = parseInt(statm[1] ?? "0", 10);
|
|
7320
6084
|
if (Number.isFinite(rssPages)) rssMb = Math.round(rssPages * 4096 / (1024 * 1024));
|
|
7321
6085
|
} catch {
|
|
@@ -7348,21 +6112,21 @@ function sampleProcState(pid) {
|
|
|
7348
6112
|
return `proc_err=${JSON.stringify(msg.slice(0, 60))}`;
|
|
7349
6113
|
}
|
|
7350
6114
|
}
|
|
7351
|
-
var
|
|
7352
|
-
var ACCOUNTS_DIR =
|
|
7353
|
-
if (!
|
|
6115
|
+
var PLATFORM_ROOT3 = process.env.MAXY_PLATFORM_ROOT ?? resolve5(process.cwd(), "..");
|
|
6116
|
+
var ACCOUNTS_DIR = resolve5(PLATFORM_ROOT3, "..", "data/accounts");
|
|
6117
|
+
if (!existsSync5(PLATFORM_ROOT3)) {
|
|
7354
6118
|
throw new Error(
|
|
7355
|
-
`PLATFORM_ROOT does not exist: ${
|
|
6119
|
+
`PLATFORM_ROOT does not exist: ${PLATFORM_ROOT3}
|
|
7356
6120
|
Set the MAXY_PLATFORM_ROOT environment variable to the absolute path of the platform directory.`
|
|
7357
6121
|
);
|
|
7358
6122
|
}
|
|
7359
6123
|
function resolveAccount() {
|
|
7360
|
-
if (!
|
|
7361
|
-
const usersFilePath =
|
|
6124
|
+
if (!existsSync5(ACCOUNTS_DIR)) return null;
|
|
6125
|
+
const usersFilePath = resolve5(PLATFORM_ROOT3, "config", "users.json");
|
|
7362
6126
|
let usersJsonUserId = null;
|
|
7363
|
-
if (
|
|
6127
|
+
if (existsSync5(usersFilePath)) {
|
|
7364
6128
|
try {
|
|
7365
|
-
const raw2 =
|
|
6129
|
+
const raw2 = readFileSync6(usersFilePath, "utf-8").trim();
|
|
7366
6130
|
if (raw2) {
|
|
7367
6131
|
const users = JSON.parse(raw2);
|
|
7368
6132
|
if (users.length > 0) {
|
|
@@ -7376,9 +6140,9 @@ function resolveAccount() {
|
|
|
7376
6140
|
let fallback = null;
|
|
7377
6141
|
for (const entry of entries) {
|
|
7378
6142
|
if (!entry.isDirectory()) continue;
|
|
7379
|
-
const configPath2 =
|
|
7380
|
-
if (!
|
|
7381
|
-
const raw2 =
|
|
6143
|
+
const configPath2 = resolve5(ACCOUNTS_DIR, entry.name, "account.json");
|
|
6144
|
+
if (!existsSync5(configPath2)) continue;
|
|
6145
|
+
const raw2 = readFileSync6(configPath2, "utf-8");
|
|
7382
6146
|
let config;
|
|
7383
6147
|
try {
|
|
7384
6148
|
config = JSON.parse(raw2);
|
|
@@ -7393,7 +6157,7 @@ function resolveAccount() {
|
|
|
7393
6157
|
}
|
|
7394
6158
|
const result = {
|
|
7395
6159
|
accountId: config.accountId,
|
|
7396
|
-
accountDir:
|
|
6160
|
+
accountDir: resolve5(ACCOUNTS_DIR, entry.name),
|
|
7397
6161
|
config
|
|
7398
6162
|
};
|
|
7399
6163
|
if (usersJsonUserId && config.admins?.some((a) => a.userId === usersJsonUserId)) {
|
|
@@ -7411,9 +6175,9 @@ function resolveAccount() {
|
|
|
7411
6175
|
return fallback;
|
|
7412
6176
|
}
|
|
7413
6177
|
function readAgentFile(accountDir, agentName, filename) {
|
|
7414
|
-
const filePath =
|
|
7415
|
-
if (!
|
|
7416
|
-
return
|
|
6178
|
+
const filePath = resolve5(accountDir, "agents", agentName, filename);
|
|
6179
|
+
if (!existsSync5(filePath)) return null;
|
|
6180
|
+
return readFileSync6(filePath, "utf-8");
|
|
7417
6181
|
}
|
|
7418
6182
|
function readIdentity(accountDir, agentName) {
|
|
7419
6183
|
return readAgentFile(accountDir, agentName, "IDENTITY.md");
|
|
@@ -7448,14 +6212,14 @@ function validateAgentSlug(slug) {
|
|
|
7448
6212
|
return true;
|
|
7449
6213
|
}
|
|
7450
6214
|
function resolveDefaultAgentSlug(accountDir) {
|
|
7451
|
-
const configPath2 =
|
|
7452
|
-
if (!
|
|
6215
|
+
const configPath2 = resolve5(accountDir, "account.json");
|
|
6216
|
+
if (!existsSync5(configPath2)) {
|
|
7453
6217
|
console.error("[agent-resolve] account.json not found \u2014 cannot resolve defaultAgent");
|
|
7454
6218
|
return null;
|
|
7455
6219
|
}
|
|
7456
6220
|
let config;
|
|
7457
6221
|
try {
|
|
7458
|
-
config = JSON.parse(
|
|
6222
|
+
config = JSON.parse(readFileSync6(configPath2, "utf-8"));
|
|
7459
6223
|
} catch (err) {
|
|
7460
6224
|
console.error("[agent-resolve] failed to read account.json:", err);
|
|
7461
6225
|
return null;
|
|
@@ -7464,8 +6228,8 @@ function resolveDefaultAgentSlug(accountDir) {
|
|
|
7464
6228
|
console.error("[agent-resolve] defaultAgent not configured in account.json \u2014 set it via the connect-whatsapp skill");
|
|
7465
6229
|
return null;
|
|
7466
6230
|
}
|
|
7467
|
-
const agentConfigPath =
|
|
7468
|
-
if (!
|
|
6231
|
+
const agentConfigPath = resolve5(accountDir, "agents", config.defaultAgent, "config.json");
|
|
6232
|
+
if (!existsSync5(agentConfigPath)) {
|
|
7469
6233
|
console.error(`[agent-resolve] defaultAgent="${config.defaultAgent}" has no config.json at ${agentConfigPath}`);
|
|
7470
6234
|
return null;
|
|
7471
6235
|
}
|
|
@@ -7537,23 +6301,23 @@ function resolveAgentConfig(accountDir, agentName) {
|
|
|
7537
6301
|
}
|
|
7538
6302
|
let knowledge = null;
|
|
7539
6303
|
let knowledgeBaked = false;
|
|
7540
|
-
const agentDir =
|
|
7541
|
-
const knowledgePath =
|
|
7542
|
-
const summaryPath =
|
|
7543
|
-
const hasKnowledge =
|
|
7544
|
-
const hasSummary =
|
|
6304
|
+
const agentDir = resolve5(accountDir, "agents", agentName);
|
|
6305
|
+
const knowledgePath = resolve5(agentDir, "KNOWLEDGE.md");
|
|
6306
|
+
const summaryPath = resolve5(agentDir, "KNOWLEDGE-SUMMARY.md");
|
|
6307
|
+
const hasKnowledge = existsSync5(knowledgePath);
|
|
6308
|
+
const hasSummary = existsSync5(summaryPath);
|
|
7545
6309
|
if (hasKnowledge && hasSummary) {
|
|
7546
6310
|
const knowledgeMtime = statSync3(knowledgePath).mtimeMs;
|
|
7547
6311
|
const summaryMtime = statSync3(summaryPath).mtimeMs;
|
|
7548
6312
|
if (summaryMtime >= knowledgeMtime) {
|
|
7549
|
-
knowledge =
|
|
6313
|
+
knowledge = readFileSync6(summaryPath, "utf-8");
|
|
7550
6314
|
} else {
|
|
7551
6315
|
console.warn(`[agent-config] ${agentName}: KNOWLEDGE-SUMMARY.md is stale (KNOWLEDGE.md is newer) \u2014 using full knowledge`);
|
|
7552
|
-
knowledge =
|
|
6316
|
+
knowledge = readFileSync6(knowledgePath, "utf-8");
|
|
7553
6317
|
}
|
|
7554
6318
|
knowledgeBaked = true;
|
|
7555
6319
|
} else if (hasKnowledge) {
|
|
7556
|
-
knowledge =
|
|
6320
|
+
knowledge = readFileSync6(knowledgePath, "utf-8");
|
|
7557
6321
|
knowledgeBaked = true;
|
|
7558
6322
|
}
|
|
7559
6323
|
let budget = null;
|
|
@@ -7575,11 +6339,11 @@ function resolveAgentConfig(accountDir, agentName) {
|
|
|
7575
6339
|
return { model, plugins, status, displayName, image, imageShape, showAgentName, knowledge, knowledgeBaked, liveMemory, knowledgeKeywords, budget, accessMode };
|
|
7576
6340
|
}
|
|
7577
6341
|
function parsePluginFrontmatter(pluginDir) {
|
|
7578
|
-
const pluginPath =
|
|
7579
|
-
if (!
|
|
6342
|
+
const pluginPath = resolve5(PLATFORM_ROOT3, "plugins", pluginDir, "PLUGIN.md");
|
|
6343
|
+
if (!existsSync5(pluginPath)) return null;
|
|
7580
6344
|
let raw2;
|
|
7581
6345
|
try {
|
|
7582
|
-
raw2 =
|
|
6346
|
+
raw2 = readFileSync6(pluginPath, "utf-8");
|
|
7583
6347
|
} catch {
|
|
7584
6348
|
console.warn(`[plugins] cannot read ${pluginPath}`);
|
|
7585
6349
|
return null;
|
|
@@ -7638,24 +6402,24 @@ function parsePluginFrontmatter(pluginDir) {
|
|
|
7638
6402
|
function autoDeliverPremiumPlugins(purchasedPlugins) {
|
|
7639
6403
|
if (!purchasedPlugins || purchasedPlugins.length === 0) return;
|
|
7640
6404
|
const TAG19 = "[premium-auto-deliver]";
|
|
7641
|
-
const stagingRoot =
|
|
7642
|
-
const pluginsDir =
|
|
7643
|
-
if (!
|
|
6405
|
+
const stagingRoot = resolve5(PLATFORM_ROOT3, "../premium-plugins");
|
|
6406
|
+
const pluginsDir = resolve5(PLATFORM_ROOT3, "plugins");
|
|
6407
|
+
if (!existsSync5(stagingRoot)) {
|
|
7644
6408
|
console.log(`${TAG19} no staging directory \u2014 skipping`);
|
|
7645
6409
|
return;
|
|
7646
6410
|
}
|
|
7647
6411
|
for (const pluginName of purchasedPlugins) {
|
|
7648
|
-
const stagingDir =
|
|
7649
|
-
if (!
|
|
6412
|
+
const stagingDir = resolve5(stagingRoot, pluginName);
|
|
6413
|
+
if (!existsSync5(stagingDir)) {
|
|
7650
6414
|
console.log(`${TAG19} ${pluginName}: not in staging \u2014 skipping`);
|
|
7651
6415
|
continue;
|
|
7652
6416
|
}
|
|
7653
|
-
const bundlePath =
|
|
7654
|
-
const isBundle =
|
|
6417
|
+
const bundlePath = join3(stagingDir, "BUNDLE.md");
|
|
6418
|
+
const isBundle = existsSync5(bundlePath);
|
|
7655
6419
|
if (isBundle) {
|
|
7656
6420
|
let bundleRaw;
|
|
7657
6421
|
try {
|
|
7658
|
-
bundleRaw =
|
|
6422
|
+
bundleRaw = readFileSync6(bundlePath, "utf-8");
|
|
7659
6423
|
} catch (err) {
|
|
7660
6424
|
console.log(`${TAG19} ${pluginName}: cannot read BUNDLE.md \u2014 ${err instanceof Error ? err.message : String(err)}`);
|
|
7661
6425
|
continue;
|
|
@@ -7686,13 +6450,13 @@ function autoDeliverPremiumPlugins(purchasedPlugins) {
|
|
|
7686
6450
|
let delivered = 0;
|
|
7687
6451
|
let skipped = 0;
|
|
7688
6452
|
for (const sub of subPlugins) {
|
|
7689
|
-
const target =
|
|
7690
|
-
if (
|
|
6453
|
+
const target = resolve5(pluginsDir, sub);
|
|
6454
|
+
if (existsSync5(resolve5(target, "PLUGIN.md"))) {
|
|
7691
6455
|
skipped++;
|
|
7692
6456
|
continue;
|
|
7693
6457
|
}
|
|
7694
|
-
const source =
|
|
7695
|
-
if (!
|
|
6458
|
+
const source = resolve5(stagingDir, "plugins", sub);
|
|
6459
|
+
if (!existsSync5(source)) {
|
|
7696
6460
|
console.log(`${TAG19} ${pluginName}/${sub}: source missing in staging \u2014 skipping`);
|
|
7697
6461
|
continue;
|
|
7698
6462
|
}
|
|
@@ -7705,8 +6469,8 @@ function autoDeliverPremiumPlugins(purchasedPlugins) {
|
|
|
7705
6469
|
}
|
|
7706
6470
|
console.log(`${TAG19} ${pluginName} (bundle): ${delivered} delivered, ${skipped} already present`);
|
|
7707
6471
|
} else {
|
|
7708
|
-
const target =
|
|
7709
|
-
if (
|
|
6472
|
+
const target = resolve5(pluginsDir, pluginName);
|
|
6473
|
+
if (existsSync5(resolve5(target, "PLUGIN.md"))) {
|
|
7710
6474
|
console.log(`${TAG19} ${pluginName}: already present \u2014 skipping`);
|
|
7711
6475
|
continue;
|
|
7712
6476
|
}
|
|
@@ -7746,21 +6510,21 @@ function migratePluginRenames(accountDir, config) {
|
|
|
7746
6510
|
return name;
|
|
7747
6511
|
});
|
|
7748
6512
|
if (!changed) return;
|
|
7749
|
-
const configPath2 =
|
|
6513
|
+
const configPath2 = resolve5(accountDir, "account.json");
|
|
7750
6514
|
try {
|
|
7751
|
-
const raw2 =
|
|
6515
|
+
const raw2 = readFileSync6(configPath2, "utf-8");
|
|
7752
6516
|
const parsed = JSON.parse(raw2);
|
|
7753
6517
|
parsed.enabledPlugins = migrated;
|
|
7754
|
-
|
|
6518
|
+
writeFileSync5(configPath2, JSON.stringify(parsed, null, 2) + "\n");
|
|
7755
6519
|
config.enabledPlugins = migrated;
|
|
7756
6520
|
console.log(`${TAG19} account.json updated (${migrated.length} plugins)`);
|
|
7757
6521
|
} catch (err) {
|
|
7758
6522
|
console.error(`${TAG19} failed to update account.json \u2014 ${err instanceof Error ? err.message : String(err)}`);
|
|
7759
6523
|
}
|
|
7760
|
-
const pluginsDir =
|
|
6524
|
+
const pluginsDir = resolve5(PLATFORM_ROOT3, "plugins");
|
|
7761
6525
|
for (const oldName of Object.keys(PLUGIN_RENAMES)) {
|
|
7762
|
-
const orphan =
|
|
7763
|
-
if (
|
|
6526
|
+
const orphan = resolve5(pluginsDir, oldName);
|
|
6527
|
+
if (existsSync5(orphan)) {
|
|
7764
6528
|
try {
|
|
7765
6529
|
rmSync2(orphan, { recursive: true });
|
|
7766
6530
|
console.log(`${TAG19} removed orphan: ${oldName}`);
|
|
@@ -7773,22 +6537,22 @@ function migratePluginRenames(accountDir, config) {
|
|
|
7773
6537
|
function autoDeliverBundleAgents(accountDir, purchasedPlugins) {
|
|
7774
6538
|
if (!purchasedPlugins || purchasedPlugins.length === 0) return;
|
|
7775
6539
|
const TAG19 = "[bundle-agent-deliver]";
|
|
7776
|
-
const stagingRoot =
|
|
7777
|
-
const specialistsDir =
|
|
7778
|
-
if (!
|
|
7779
|
-
if (!
|
|
7780
|
-
|
|
6540
|
+
const stagingRoot = resolve5(PLATFORM_ROOT3, "../premium-plugins");
|
|
6541
|
+
const specialistsDir = resolve5(accountDir, "specialists", "agents");
|
|
6542
|
+
if (!existsSync5(stagingRoot)) return;
|
|
6543
|
+
if (!existsSync5(specialistsDir)) {
|
|
6544
|
+
mkdirSync4(specialistsDir, { recursive: true });
|
|
7781
6545
|
}
|
|
7782
|
-
const agentsmdPath =
|
|
6546
|
+
const agentsmdPath = resolve5(accountDir, "agents", "admin", "AGENTS.md");
|
|
7783
6547
|
let agentsmd = "";
|
|
7784
6548
|
try {
|
|
7785
|
-
agentsmd =
|
|
6549
|
+
agentsmd = existsSync5(agentsmdPath) ? readFileSync6(agentsmdPath, "utf-8") : "";
|
|
7786
6550
|
} catch {
|
|
7787
6551
|
}
|
|
7788
6552
|
let delivered = 0;
|
|
7789
6553
|
for (const pluginName of purchasedPlugins) {
|
|
7790
|
-
const bundleAgentsDir =
|
|
7791
|
-
if (!
|
|
6554
|
+
const bundleAgentsDir = resolve5(stagingRoot, pluginName, "agents");
|
|
6555
|
+
if (!existsSync5(bundleAgentsDir)) continue;
|
|
7792
6556
|
let entries;
|
|
7793
6557
|
try {
|
|
7794
6558
|
entries = readdirSync2(bundleAgentsDir).filter((f) => f.endsWith(".md"));
|
|
@@ -7796,9 +6560,9 @@ function autoDeliverBundleAgents(accountDir, purchasedPlugins) {
|
|
|
7796
6560
|
continue;
|
|
7797
6561
|
}
|
|
7798
6562
|
for (const filename of entries) {
|
|
7799
|
-
const target =
|
|
7800
|
-
if (
|
|
7801
|
-
const source =
|
|
6563
|
+
const target = resolve5(specialistsDir, filename);
|
|
6564
|
+
if (existsSync5(target)) continue;
|
|
6565
|
+
const source = resolve5(bundleAgentsDir, filename);
|
|
7802
6566
|
try {
|
|
7803
6567
|
cpSync(source, target);
|
|
7804
6568
|
} catch (err) {
|
|
@@ -7806,7 +6570,7 @@ function autoDeliverBundleAgents(accountDir, purchasedPlugins) {
|
|
|
7806
6570
|
continue;
|
|
7807
6571
|
}
|
|
7808
6572
|
try {
|
|
7809
|
-
const content =
|
|
6573
|
+
const content = readFileSync6(target, "utf-8");
|
|
7810
6574
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
7811
6575
|
if (fmMatch) {
|
|
7812
6576
|
const nameMatch = fmMatch[1].match(/^name:\s*(.+)/m);
|
|
@@ -7830,7 +6594,7 @@ function autoDeliverBundleAgents(accountDir, purchasedPlugins) {
|
|
|
7830
6594
|
}
|
|
7831
6595
|
if (delivered > 0) {
|
|
7832
6596
|
try {
|
|
7833
|
-
|
|
6597
|
+
writeFileSync5(agentsmdPath, agentsmd);
|
|
7834
6598
|
console.log(`${TAG19} AGENTS.md updated (${delivered} agents added)`);
|
|
7835
6599
|
} catch (err) {
|
|
7836
6600
|
console.error(`${TAG19} AGENTS.md update failed \u2014 ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -7838,11 +6602,11 @@ function autoDeliverBundleAgents(accountDir, purchasedPlugins) {
|
|
|
7838
6602
|
}
|
|
7839
6603
|
}
|
|
7840
6604
|
function assemblePublicPluginContent(pluginDir) {
|
|
7841
|
-
const pluginRoot =
|
|
7842
|
-
const pluginPath =
|
|
6605
|
+
const pluginRoot = resolve5(PLATFORM_ROOT3, "plugins", pluginDir);
|
|
6606
|
+
const pluginPath = resolve5(pluginRoot, "PLUGIN.md");
|
|
7843
6607
|
let raw2;
|
|
7844
6608
|
try {
|
|
7845
|
-
raw2 =
|
|
6609
|
+
raw2 = readFileSync6(pluginPath, "utf-8");
|
|
7846
6610
|
} catch {
|
|
7847
6611
|
return null;
|
|
7848
6612
|
}
|
|
@@ -7851,7 +6615,7 @@ function assemblePublicPluginContent(pluginDir) {
|
|
|
7851
6615
|
const parts = [pluginBody];
|
|
7852
6616
|
let skillCount = 0;
|
|
7853
6617
|
let refCount = 0;
|
|
7854
|
-
const skillsDir =
|
|
6618
|
+
const skillsDir = resolve5(pluginRoot, "skills");
|
|
7855
6619
|
let skillDirs;
|
|
7856
6620
|
try {
|
|
7857
6621
|
skillDirs = readdirSync2(skillsDir).sort();
|
|
@@ -7859,11 +6623,11 @@ function assemblePublicPluginContent(pluginDir) {
|
|
|
7859
6623
|
return { body: pluginBody, skillCount: 0, refCount: 0 };
|
|
7860
6624
|
}
|
|
7861
6625
|
for (const skillName of skillDirs) {
|
|
7862
|
-
const skillDir =
|
|
7863
|
-
const skillMdPath =
|
|
6626
|
+
const skillDir = resolve5(skillsDir, skillName);
|
|
6627
|
+
const skillMdPath = resolve5(skillDir, "SKILL.md");
|
|
7864
6628
|
let skillRaw;
|
|
7865
6629
|
try {
|
|
7866
|
-
skillRaw =
|
|
6630
|
+
skillRaw = readFileSync6(skillMdPath, "utf-8");
|
|
7867
6631
|
} catch {
|
|
7868
6632
|
continue;
|
|
7869
6633
|
}
|
|
@@ -7903,7 +6667,7 @@ function assemblePublicPluginContent(pluginDir) {
|
|
|
7903
6667
|
parts.push(`
|
|
7904
6668
|
<!-- skill: ${skillName} -->`);
|
|
7905
6669
|
parts.push(skillBody);
|
|
7906
|
-
const refsDir =
|
|
6670
|
+
const refsDir = resolve5(skillDir, "references");
|
|
7907
6671
|
let refFiles;
|
|
7908
6672
|
try {
|
|
7909
6673
|
refFiles = readdirSync2(refsDir).filter((f) => f.endsWith(".md")).filter((f) => !publicExcludeReferences.includes(f)).sort();
|
|
@@ -7914,7 +6678,7 @@ function assemblePublicPluginContent(pluginDir) {
|
|
|
7914
6678
|
}
|
|
7915
6679
|
for (const refFile of refFiles) {
|
|
7916
6680
|
try {
|
|
7917
|
-
const refContent =
|
|
6681
|
+
const refContent = readFileSync6(resolve5(refsDir, refFile), "utf-8").trim();
|
|
7918
6682
|
if (refContent) {
|
|
7919
6683
|
parts.push(`
|
|
7920
6684
|
<!-- reference: ${refFile} -->`);
|
|
@@ -7932,7 +6696,7 @@ function assemblePublicPluginContent(pluginDir) {
|
|
|
7932
6696
|
return { body: parts.join("\n"), skillCount, refCount };
|
|
7933
6697
|
}
|
|
7934
6698
|
function loadEmbeddedPlugins(agentType, selectedPlugins, enabledPlugins) {
|
|
7935
|
-
const pluginsDir =
|
|
6699
|
+
const pluginsDir = resolve5(PLATFORM_ROOT3, "plugins");
|
|
7936
6700
|
let dirs;
|
|
7937
6701
|
try {
|
|
7938
6702
|
dirs = readdirSync2(pluginsDir);
|
|
@@ -7985,10 +6749,10 @@ function loadEmbeddedPlugins(agentType, selectedPlugins, enabledPlugins) {
|
|
|
7985
6749
|
console.log(`[plugins] loaded ${dir} for public (${assembled.body.length} chars, ${assembled.skillCount} skills, ${assembled.refCount} refs)`);
|
|
7986
6750
|
}
|
|
7987
6751
|
} else {
|
|
7988
|
-
const pluginPath =
|
|
6752
|
+
const pluginPath = resolve5(pluginsDir, dir, "PLUGIN.md");
|
|
7989
6753
|
let raw2;
|
|
7990
6754
|
try {
|
|
7991
|
-
raw2 =
|
|
6755
|
+
raw2 = readFileSync6(pluginPath, "utf-8");
|
|
7992
6756
|
} catch (err) {
|
|
7993
6757
|
console.warn(`[plugins] ${dir}: failed to read PLUGIN.md for ${agentType} embed: ${String(err)}`);
|
|
7994
6758
|
continue;
|
|
@@ -8012,14 +6776,14 @@ var mcpToolsCache = /* @__PURE__ */ new Map();
|
|
|
8012
6776
|
function fetchMcpToolsList(pluginDir) {
|
|
8013
6777
|
const cached = mcpToolsCache.get(pluginDir);
|
|
8014
6778
|
if (cached) return Promise.resolve(cached);
|
|
8015
|
-
const serverPath =
|
|
8016
|
-
if (!
|
|
6779
|
+
const serverPath = resolve5(PLATFORM_ROOT3, "plugins", pluginDir, "mcp/dist/index.js");
|
|
6780
|
+
if (!existsSync5(serverPath)) return Promise.resolve([]);
|
|
8017
6781
|
const startMs = Date.now();
|
|
8018
6782
|
return new Promise((resolvePromise) => {
|
|
8019
6783
|
const proc = spawn2(process.execPath, [serverPath], {
|
|
8020
6784
|
env: {
|
|
8021
6785
|
...process.env,
|
|
8022
|
-
PLATFORM_ROOT:
|
|
6786
|
+
PLATFORM_ROOT: PLATFORM_ROOT3,
|
|
8023
6787
|
ACCOUNT_ID: "__toolslist__",
|
|
8024
6788
|
PLATFORM_PORT: process.env.PORT ?? "19200"
|
|
8025
6789
|
}
|
|
@@ -8125,7 +6889,7 @@ var SPECIALIST_PLUGIN_DOMAINS = {
|
|
|
8125
6889
|
// agent, so it retains a full manifest entry for routing clarity.
|
|
8126
6890
|
};
|
|
8127
6891
|
async function buildPluginManifest(enabledPlugins) {
|
|
8128
|
-
const pluginsDir =
|
|
6892
|
+
const pluginsDir = resolve5(PLATFORM_ROOT3, "plugins");
|
|
8129
6893
|
let dirs;
|
|
8130
6894
|
try {
|
|
8131
6895
|
dirs = readdirSync2(pluginsDir);
|
|
@@ -8212,16 +6976,16 @@ ${specialist}: ${plugins.join(", ")}`);
|
|
|
8212
6976
|
for (let j = 0; j < adminPlugins.length; j++) {
|
|
8213
6977
|
const { dir, parsed } = adminPlugins[j];
|
|
8214
6978
|
const mcpTools = adminMcpResults[j];
|
|
8215
|
-
const pluginRoot =
|
|
6979
|
+
const pluginRoot = resolve5(pluginsDir, dir);
|
|
8216
6980
|
const skills = [];
|
|
8217
6981
|
const references = [];
|
|
8218
6982
|
const scanDir = (base, prefix, target) => {
|
|
8219
|
-
const scanPath =
|
|
8220
|
-
if (!
|
|
6983
|
+
const scanPath = resolve5(pluginRoot, base);
|
|
6984
|
+
if (!existsSync5(scanPath)) return;
|
|
8221
6985
|
try {
|
|
8222
6986
|
const walk = (current, rel) => {
|
|
8223
6987
|
for (const entry of readdirSync2(current)) {
|
|
8224
|
-
const full =
|
|
6988
|
+
const full = resolve5(current, entry);
|
|
8225
6989
|
try {
|
|
8226
6990
|
const stat5 = statSync3(full);
|
|
8227
6991
|
if (stat5.isDirectory()) {
|
|
@@ -8249,8 +7013,8 @@ ${specialist}: ${plugins.join(", ")}`);
|
|
|
8249
7013
|
toolLines.push(desc ? ` ${tool.name} \u2014 ${desc}` : ` ${tool.name}`);
|
|
8250
7014
|
}
|
|
8251
7015
|
} else if (parsed.tools.length > 0) {
|
|
8252
|
-
const serverPath =
|
|
8253
|
-
if (
|
|
7016
|
+
const serverPath = resolve5(PLATFORM_ROOT3, "plugins", dir, "mcp/dist/index.js");
|
|
7017
|
+
if (existsSync5(serverPath)) {
|
|
8254
7018
|
fallbackSourced++;
|
|
8255
7019
|
console.error(`[plugin-manifest] ${dir}: tools/list empty \u2014 fallback to frontmatter (${parsed.tools.length} tools)`);
|
|
8256
7020
|
}
|
|
@@ -8325,16 +7089,16 @@ function getDefaultAccountId() {
|
|
|
8325
7089
|
return resolveAccount()?.accountId ?? null;
|
|
8326
7090
|
}
|
|
8327
7091
|
function resolveUserAccounts(userId) {
|
|
8328
|
-
if (!
|
|
7092
|
+
if (!existsSync5(ACCOUNTS_DIR)) return [];
|
|
8329
7093
|
const results = [];
|
|
8330
7094
|
const entries = readdirSync2(ACCOUNTS_DIR, { withFileTypes: true });
|
|
8331
7095
|
for (const entry of entries) {
|
|
8332
7096
|
if (!entry.isDirectory()) continue;
|
|
8333
|
-
const configPath2 =
|
|
8334
|
-
if (!
|
|
7097
|
+
const configPath2 = resolve5(ACCOUNTS_DIR, entry.name, "account.json");
|
|
7098
|
+
if (!existsSync5(configPath2)) continue;
|
|
8335
7099
|
let config;
|
|
8336
7100
|
try {
|
|
8337
|
-
config = JSON.parse(
|
|
7101
|
+
config = JSON.parse(readFileSync6(configPath2, "utf-8"));
|
|
8338
7102
|
} catch {
|
|
8339
7103
|
console.error(`[session] account.json corrupt at ${configPath2} \u2014 skipping`);
|
|
8340
7104
|
continue;
|
|
@@ -8343,7 +7107,7 @@ function resolveUserAccounts(userId) {
|
|
|
8343
7107
|
if (adminEntry) {
|
|
8344
7108
|
results.push({
|
|
8345
7109
|
accountId: config.accountId,
|
|
8346
|
-
accountDir:
|
|
7110
|
+
accountDir: resolve5(ACCOUNTS_DIR, entry.name),
|
|
8347
7111
|
config,
|
|
8348
7112
|
role: adminEntry.role
|
|
8349
7113
|
});
|
|
@@ -8560,8 +7324,8 @@ function consumeStalledSubagents(sessionKey) {
|
|
|
8560
7324
|
return stalls && stalls.length > 0 ? stalls : void 0;
|
|
8561
7325
|
}
|
|
8562
7326
|
function streamLogPathFor(accountId, conversationId) {
|
|
8563
|
-
const logDir =
|
|
8564
|
-
const streamLogPath =
|
|
7327
|
+
const logDir = resolve5(ACCOUNTS_DIR, accountId, "logs");
|
|
7328
|
+
const streamLogPath = resolve5(logDir, `claude-agent-stream-${conversationId}.log`);
|
|
8565
7329
|
return { logDir, streamLogPath };
|
|
8566
7330
|
}
|
|
8567
7331
|
function buildSpawnEnv(accountId, accountDir, conversationId) {
|
|
@@ -8571,7 +7335,7 @@ function buildSpawnEnv(accountId, accountDir, conversationId) {
|
|
|
8571
7335
|
const { logDir, streamLogPath } = streamLogPathFor(accountId, conversationId);
|
|
8572
7336
|
return {
|
|
8573
7337
|
...process.env,
|
|
8574
|
-
PLATFORM_ROOT:
|
|
7338
|
+
PLATFORM_ROOT: PLATFORM_ROOT3,
|
|
8575
7339
|
ACCOUNT_DIR: accountDir,
|
|
8576
7340
|
ACCOUNT_ID: accountId,
|
|
8577
7341
|
LOG_DIR: logDir,
|
|
@@ -8582,8 +7346,8 @@ var cachedBrandHostname = null;
|
|
|
8582
7346
|
function readBrandHostname() {
|
|
8583
7347
|
if (cachedBrandHostname !== null) return cachedBrandHostname;
|
|
8584
7348
|
try {
|
|
8585
|
-
const brandPath =
|
|
8586
|
-
const parsed = JSON.parse(
|
|
7349
|
+
const brandPath = resolve5(PLATFORM_ROOT3, "config", "brand.json");
|
|
7350
|
+
const parsed = JSON.parse(readFileSync6(brandPath, "utf-8"));
|
|
8587
7351
|
cachedBrandHostname = typeof parsed.hostname === "string" && parsed.hostname.length > 0 ? parsed.hostname : "maxy";
|
|
8588
7352
|
} catch {
|
|
8589
7353
|
cachedBrandHostname = "maxy";
|
|
@@ -8612,7 +7376,7 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
8612
7376
|
const { logDir: LOG_DIR2, streamLogPath: STREAM_LOG_PATH } = streamLogPathFor(accountId, conversationId);
|
|
8613
7377
|
const baseEnv = {
|
|
8614
7378
|
ACCOUNT_ID: accountId,
|
|
8615
|
-
PLATFORM_ROOT:
|
|
7379
|
+
PLATFORM_ROOT: PLATFORM_ROOT3,
|
|
8616
7380
|
LOG_DIR: LOG_DIR2,
|
|
8617
7381
|
STREAM_LOG_PATH,
|
|
8618
7382
|
NEO4J_URI: requireNeo4jUri()
|
|
@@ -8620,37 +7384,37 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
8620
7384
|
const servers = {
|
|
8621
7385
|
"memory": {
|
|
8622
7386
|
command: "node",
|
|
8623
|
-
args: [
|
|
7387
|
+
args: [resolve5(PLATFORM_ROOT3, "plugins/memory/mcp/dist/index.js")],
|
|
8624
7388
|
env: { ...baseEnv, ...userId ? { USER_ID: userId } : {} }
|
|
8625
7389
|
},
|
|
8626
7390
|
"contacts": {
|
|
8627
7391
|
command: "node",
|
|
8628
|
-
args: [
|
|
7392
|
+
args: [resolve5(PLATFORM_ROOT3, "plugins/contacts/mcp/dist/index.js")],
|
|
8629
7393
|
env: { ...baseEnv }
|
|
8630
7394
|
},
|
|
8631
7395
|
"whatsapp": {
|
|
8632
7396
|
command: "node",
|
|
8633
|
-
args: [
|
|
7397
|
+
args: [resolve5(PLATFORM_ROOT3, "plugins/whatsapp/mcp/dist/index.js")],
|
|
8634
7398
|
env: { ...baseEnv, PLATFORM_PORT: process.env.PORT ?? "19200" }
|
|
8635
7399
|
},
|
|
8636
7400
|
"admin": {
|
|
8637
7401
|
command: "node",
|
|
8638
|
-
args: [
|
|
7402
|
+
args: [resolve5(PLATFORM_ROOT3, "plugins/admin/mcp/dist/index.js")],
|
|
8639
7403
|
env: { ...baseEnv, PLATFORM_PORT: process.env.PORT ?? "19200", ...userId ? { USER_ID: userId } : {} }
|
|
8640
7404
|
},
|
|
8641
7405
|
"scheduling": {
|
|
8642
7406
|
command: "node",
|
|
8643
|
-
args: [
|
|
7407
|
+
args: [resolve5(PLATFORM_ROOT3, "plugins/scheduling/mcp/dist/index.js")],
|
|
8644
7408
|
env: { ...baseEnv }
|
|
8645
7409
|
},
|
|
8646
7410
|
"tasks": {
|
|
8647
7411
|
command: "node",
|
|
8648
|
-
args: [
|
|
7412
|
+
args: [resolve5(PLATFORM_ROOT3, "plugins/tasks/mcp/dist/index.js")],
|
|
8649
7413
|
env: { ...baseEnv }
|
|
8650
7414
|
},
|
|
8651
7415
|
"email": {
|
|
8652
7416
|
command: "node",
|
|
8653
|
-
args: [
|
|
7417
|
+
args: [resolve5(PLATFORM_ROOT3, "plugins/email/mcp/dist/index.js")],
|
|
8654
7418
|
env: { ...baseEnv }
|
|
8655
7419
|
},
|
|
8656
7420
|
// Workflows MCP — persistent admin-session server for list/get/update/delete/
|
|
@@ -8661,7 +7425,7 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
8661
7425
|
// ToolSearches fruitlessly before degrading to a task-create stand-in (Task 571).
|
|
8662
7426
|
"workflows": {
|
|
8663
7427
|
command: "node",
|
|
8664
|
-
args: [
|
|
7428
|
+
args: [resolve5(PLATFORM_ROOT3, "plugins/workflows/mcp/dist/index.js")],
|
|
8665
7429
|
env: { ...baseEnv }
|
|
8666
7430
|
},
|
|
8667
7431
|
// Playwright MCP server — browser automation for browser-specialist.
|
|
@@ -8683,7 +7447,7 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
8683
7447
|
// MAXY-PRD.md:627, not in any application-layer filter).
|
|
8684
7448
|
"graph": {
|
|
8685
7449
|
command: "node",
|
|
8686
|
-
args: [
|
|
7450
|
+
args: [resolve5(PLATFORM_ROOT3, "lib/graph-mcp/dist/index.js")],
|
|
8687
7451
|
env: {
|
|
8688
7452
|
...baseEnv,
|
|
8689
7453
|
BRAND: readBrandHostname(),
|
|
@@ -8699,7 +7463,7 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
8699
7463
|
if (tgBotToken) {
|
|
8700
7464
|
servers["telegram"] = {
|
|
8701
7465
|
command: "node",
|
|
8702
|
-
args: [
|
|
7466
|
+
args: [resolve5(PLATFORM_ROOT3, "plugins/telegram/mcp/dist/index.js")],
|
|
8703
7467
|
env: { ...baseEnv, TELEGRAM_BOT_TOKEN: tgBotToken }
|
|
8704
7468
|
};
|
|
8705
7469
|
} else {
|
|
@@ -8707,11 +7471,11 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
8707
7471
|
}
|
|
8708
7472
|
servers["cloudflare"] = {
|
|
8709
7473
|
command: "node",
|
|
8710
|
-
args: [
|
|
7474
|
+
args: [resolve5(PLATFORM_ROOT3, "plugins/cloudflare/mcp/dist/index.js")],
|
|
8711
7475
|
env: { ...baseEnv, PLATFORM_PORT: process.env.PORT ?? "19200" }
|
|
8712
7476
|
};
|
|
8713
7477
|
if (Array.isArray(enabledPlugins) && enabledPlugins.length > 0) {
|
|
8714
|
-
const pluginsDir =
|
|
7478
|
+
const pluginsDir = resolve5(PLATFORM_ROOT3, "plugins");
|
|
8715
7479
|
let dirs;
|
|
8716
7480
|
try {
|
|
8717
7481
|
dirs = readdirSync2(pluginsDir);
|
|
@@ -8734,8 +7498,8 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
8734
7498
|
continue;
|
|
8735
7499
|
}
|
|
8736
7500
|
}
|
|
8737
|
-
const mcpEntry =
|
|
8738
|
-
if (!
|
|
7501
|
+
const mcpEntry = resolve5(PLATFORM_ROOT3, "plugins", dir, "mcp/dist/index.js");
|
|
7502
|
+
if (!existsSync5(mcpEntry)) continue;
|
|
8739
7503
|
servers[dir] = {
|
|
8740
7504
|
command: "node",
|
|
8741
7505
|
args: [mcpEntry],
|
|
@@ -8870,7 +7634,7 @@ var ADMIN_CORE_TOOLS = [
|
|
|
8870
7634
|
function getAdminAllowedTools(enabledPlugins) {
|
|
8871
7635
|
const tools = [...ADMIN_CORE_TOOLS];
|
|
8872
7636
|
if (Array.isArray(enabledPlugins) && enabledPlugins.length > 0) {
|
|
8873
|
-
const pluginsDir =
|
|
7637
|
+
const pluginsDir = resolve5(PLATFORM_ROOT3, "plugins");
|
|
8874
7638
|
let dirs;
|
|
8875
7639
|
try {
|
|
8876
7640
|
dirs = readdirSync2(pluginsDir);
|
|
@@ -8978,18 +7742,18 @@ ${message.slice(0, QUERY_CLASSIFIER_MSG_CAP)}`
|
|
|
8978
7742
|
}
|
|
8979
7743
|
}
|
|
8980
7744
|
async function fetchMemoryContext(accountId, query, sessionKey, options) {
|
|
8981
|
-
const serverPath =
|
|
8982
|
-
if (!
|
|
7745
|
+
const serverPath = resolve5(PLATFORM_ROOT3, "plugins/memory/mcp/dist/index.js");
|
|
7746
|
+
if (!existsSync5(serverPath)) {
|
|
8983
7747
|
console.error(`[fetchMemoryContext] MCP server not found: ${serverPath}`);
|
|
8984
7748
|
return null;
|
|
8985
7749
|
}
|
|
8986
7750
|
const startMs = Date.now();
|
|
8987
|
-
return new Promise((
|
|
7751
|
+
return new Promise((resolve28) => {
|
|
8988
7752
|
const proc = spawn2(process.execPath, [serverPath], {
|
|
8989
7753
|
env: {
|
|
8990
7754
|
...process.env,
|
|
8991
7755
|
ACCOUNT_ID: accountId,
|
|
8992
|
-
PLATFORM_ROOT:
|
|
7756
|
+
PLATFORM_ROOT: PLATFORM_ROOT3,
|
|
8993
7757
|
READ_ONLY: "true",
|
|
8994
7758
|
ALLOWED_SCOPES: "public,shared",
|
|
8995
7759
|
...sessionKey ? { SESSION_ID: sessionKey } : {},
|
|
@@ -9013,7 +7777,7 @@ async function fetchMemoryContext(accountId, query, sessionKey, options) {
|
|
|
9013
7777
|
} else {
|
|
9014
7778
|
console.error(`[fetchMemoryContext] failed: ${reason} (${elapsed}ms)${stderrBuf ? ` stderr: ${stderrBuf.slice(0, 500)}` : ""}`);
|
|
9015
7779
|
}
|
|
9016
|
-
|
|
7780
|
+
resolve28(value);
|
|
9017
7781
|
};
|
|
9018
7782
|
proc.stdout.on("data", (chunk) => {
|
|
9019
7783
|
buffer += chunk.toString();
|
|
@@ -9076,8 +7840,8 @@ async function fetchMemoryContext(accountId, query, sessionKey, options) {
|
|
|
9076
7840
|
}
|
|
9077
7841
|
async function compactTrimmedMessages(accountId, trimmedMessages) {
|
|
9078
7842
|
if (trimmedMessages.length === 0) return true;
|
|
9079
|
-
const serverPath =
|
|
9080
|
-
if (!
|
|
7843
|
+
const serverPath = resolve5(PLATFORM_ROOT3, "plugins/memory/mcp/dist/index.js");
|
|
7844
|
+
if (!existsSync5(serverPath)) return false;
|
|
9081
7845
|
const briefing = trimmedMessages.map((m) => `[${m.role.toUpperCase()}] ${m.content}`).join("\n\n");
|
|
9082
7846
|
return new Promise((resolvePromise) => {
|
|
9083
7847
|
const proc = spawn2(process.execPath, [serverPath], {
|
|
@@ -9394,8 +8158,8 @@ Then respond with only: [COMPACTED]`;
|
|
|
9394
8158
|
var COMPACTION_TIMEOUT_MS = 45e3;
|
|
9395
8159
|
async function* runCompactionTurn(accountDir, accountId, systemPrompt, resumeSessionId, adminModel, conversationId, enabledPlugins) {
|
|
9396
8160
|
const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, conversationId, void 0, enabledPlugins) });
|
|
9397
|
-
const specialistsDir =
|
|
9398
|
-
if (!
|
|
8161
|
+
const specialistsDir = resolve5(accountDir, "specialists");
|
|
8162
|
+
if (!existsSync5(specialistsDir)) agentLogStream("claude-agent-compaction-stream", accountDir, conversationId).write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
|
|
9399
8163
|
`);
|
|
9400
8164
|
const args = [
|
|
9401
8165
|
"--print",
|
|
@@ -9668,7 +8432,7 @@ async function* parseClaudeStream(proc, streamLog, adminModel, conversationId, a
|
|
|
9668
8432
|
const { logDir } = streamLogPathFor(accountId, conversationId);
|
|
9669
8433
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
9670
8434
|
for (const s of failed) {
|
|
9671
|
-
const stderrPath =
|
|
8435
|
+
const stderrPath = resolve5(logDir, `mcp-${s.name}-stderr-${date}.log`);
|
|
9672
8436
|
let tail = "(no stderr file)";
|
|
9673
8437
|
try {
|
|
9674
8438
|
const stats = statSync3(stderrPath);
|
|
@@ -10310,8 +9074,8 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
|
|
|
10310
9074
|
}
|
|
10311
9075
|
const ccUserId = sessionKey ? getUserIdForSession(sessionKey) : void 0;
|
|
10312
9076
|
const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, spawnConvId, ccUserId, enabledPlugins) });
|
|
10313
|
-
const specialistsDir =
|
|
10314
|
-
if (!
|
|
9077
|
+
const specialistsDir = resolve5(accountDir, "specialists");
|
|
9078
|
+
if (!existsSync5(specialistsDir)) agentLogStream("claude-agent-stream", accountDir, spawnConvId).write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
|
|
10315
9079
|
`);
|
|
10316
9080
|
const args = [
|
|
10317
9081
|
"--print",
|
|
@@ -10661,8 +9425,8 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
|
|
|
10661
9425
|
`);
|
|
10662
9426
|
const managedUserId = getUserIdForSession(sessionKey);
|
|
10663
9427
|
const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, managedConvId, managedUserId, enabledPlugins) });
|
|
10664
|
-
const specialistsDir =
|
|
10665
|
-
if (!
|
|
9428
|
+
const specialistsDir = resolve5(accountDir, "specialists");
|
|
9429
|
+
if (!existsSync5(specialistsDir)) streamLog.write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
|
|
10666
9430
|
`);
|
|
10667
9431
|
const fullMessage = attachments.length > 0 ? message + buildAttachmentMetaText(attachments) : message;
|
|
10668
9432
|
const args = [
|
|
@@ -11355,10 +10119,10 @@ ${sessionContext}`;
|
|
|
11355
10119
|
console.log(`[onboarding-inject] accountId=${accountId.slice(0, 8)}\u2026 error=neo4j-unreachable injected=false`);
|
|
11356
10120
|
} else if (onboardingStep < 8) {
|
|
11357
10121
|
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.";
|
|
11358
|
-
const skillPath =
|
|
10122
|
+
const skillPath = resolve5(PLATFORM_ROOT3, "plugins/admin/skills/onboarding/SKILL.md");
|
|
11359
10123
|
let skillContent = "";
|
|
11360
10124
|
try {
|
|
11361
|
-
skillContent =
|
|
10125
|
+
skillContent = readFileSync6(skillPath, "utf-8");
|
|
11362
10126
|
} catch (err) {
|
|
11363
10127
|
console.error(`[onboarding-inject] accountId=${accountId.slice(0, 8)}\u2026 error=skill-read-failed path=${skillPath} reason=${err instanceof Error ? err.message : String(err)}`);
|
|
11364
10128
|
}
|
|
@@ -11408,9 +10172,9 @@ ${body}`;
|
|
|
11408
10172
|
|
|
11409
10173
|
${manifest}`;
|
|
11410
10174
|
}
|
|
11411
|
-
const graphRefPath =
|
|
10175
|
+
const graphRefPath = resolve5(PLATFORM_ROOT3, "plugins/memory/references/graph-primitives.md");
|
|
11412
10176
|
try {
|
|
11413
|
-
const graphRef =
|
|
10177
|
+
const graphRef = readFileSync6(graphRefPath, "utf-8");
|
|
11414
10178
|
baseSystemPrompt += `
|
|
11415
10179
|
|
|
11416
10180
|
${graphRef}`;
|
|
@@ -11625,8 +10389,8 @@ var clientIpMiddleware = async (c, next) => {
|
|
|
11625
10389
|
};
|
|
11626
10390
|
|
|
11627
10391
|
// server/routes/health.ts
|
|
11628
|
-
import { existsSync as
|
|
11629
|
-
import { createConnection as
|
|
10392
|
+
import { existsSync as existsSync10, readFileSync as readFileSync11 } from "fs";
|
|
10393
|
+
import { createConnection as createConnection2 } from "net";
|
|
11630
10394
|
|
|
11631
10395
|
// app/lib/network.ts
|
|
11632
10396
|
import { networkInterfaces } from "os";
|
|
@@ -11650,8 +10414,8 @@ function getLanIp() {
|
|
|
11650
10414
|
import { basename as basename2 } from "path";
|
|
11651
10415
|
|
|
11652
10416
|
// app/lib/review-detector/rules.ts
|
|
11653
|
-
import { readFileSync as
|
|
11654
|
-
import { resolve as
|
|
10417
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync6, statSync as statSync4, mkdirSync as mkdirSync5, renameSync as renameSync2 } from "fs";
|
|
10418
|
+
import { resolve as resolve6, dirname as dirname3 } from "path";
|
|
11655
10419
|
var DEFAULT_SCAN_INTERVAL_MS = 5e3;
|
|
11656
10420
|
var RATE_LIMIT_PATTERN = "rate[- ]?limit(?:ed| reached| hit)|(?:HTTP|status)[^a-z]{0,3}429|too many requests";
|
|
11657
10421
|
var RATE_LIMIT_PATTERN_V1 = "\\b429\\b|rate.?limit|too.?many.?requests";
|
|
@@ -12080,12 +10844,12 @@ function defaultRules() {
|
|
|
12080
10844
|
];
|
|
12081
10845
|
}
|
|
12082
10846
|
function rulesFilePath(configDir2) {
|
|
12083
|
-
return
|
|
10847
|
+
return resolve6(configDir2, "review-rules.json");
|
|
12084
10848
|
}
|
|
12085
10849
|
function ensureRulesFile(configDir2) {
|
|
12086
10850
|
const path2 = rulesFilePath(configDir2);
|
|
12087
|
-
if (
|
|
12088
|
-
|
|
10851
|
+
if (existsSync6(path2)) return { created: false, path: path2 };
|
|
10852
|
+
mkdirSync5(dirname3(path2), { recursive: true });
|
|
12089
10853
|
const body = {
|
|
12090
10854
|
scanIntervalMs: DEFAULT_SCAN_INTERVAL_MS,
|
|
12091
10855
|
rules: defaultRules()
|
|
@@ -12095,10 +10859,10 @@ function ensureRulesFile(configDir2) {
|
|
|
12095
10859
|
}
|
|
12096
10860
|
function loadRules(configDir2) {
|
|
12097
10861
|
const path2 = rulesFilePath(configDir2);
|
|
12098
|
-
if (!
|
|
10862
|
+
if (!existsSync6(path2)) {
|
|
12099
10863
|
throw new Error(`rules file missing at ${path2}`);
|
|
12100
10864
|
}
|
|
12101
|
-
const raw2 =
|
|
10865
|
+
const raw2 = readFileSync7(path2, "utf-8");
|
|
12102
10866
|
let parsed;
|
|
12103
10867
|
try {
|
|
12104
10868
|
parsed = JSON.parse(raw2);
|
|
@@ -12121,7 +10885,7 @@ function saveRules(configDir2, file) {
|
|
|
12121
10885
|
}
|
|
12122
10886
|
function atomicWriteJson(path2, body) {
|
|
12123
10887
|
const tmp = `${path2}.tmp.${process.pid}.${Date.now()}`;
|
|
12124
|
-
|
|
10888
|
+
writeFileSync6(tmp, JSON.stringify(body, null, 2) + "\n", "utf-8");
|
|
12125
10889
|
renameSync2(tmp, path2);
|
|
12126
10890
|
}
|
|
12127
10891
|
function validateRulesFile(input, sourceLabel) {
|
|
@@ -12263,16 +11027,16 @@ function validateRule(input, label, seenIds) {
|
|
|
12263
11027
|
}
|
|
12264
11028
|
|
|
12265
11029
|
// app/lib/review-detector/sources.ts
|
|
12266
|
-
import { existsSync as
|
|
12267
|
-
import { resolve as
|
|
11030
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3, statSync as statSync5, writeFileSync as writeFileSync7, renameSync as renameSync3, mkdirSync as mkdirSync6, openSync as openSync3, readSync as readSync3, closeSync as closeSync3, readFileSync as readFileSync8 } from "fs";
|
|
11031
|
+
import { resolve as resolve7, join as join4, basename, dirname as dirname4 } from "path";
|
|
12268
11032
|
function tailStatePath(configDir2) {
|
|
12269
|
-
return
|
|
11033
|
+
return resolve7(configDir2, "review-state.json");
|
|
12270
11034
|
}
|
|
12271
11035
|
function loadTailState(configDir2) {
|
|
12272
11036
|
const path2 = tailStatePath(configDir2);
|
|
12273
|
-
if (!
|
|
11037
|
+
if (!existsSync7(path2)) return {};
|
|
12274
11038
|
try {
|
|
12275
|
-
const raw2 =
|
|
11039
|
+
const raw2 = readFileSync8(path2, "utf-8");
|
|
12276
11040
|
const parsed = JSON.parse(raw2);
|
|
12277
11041
|
if (!parsed || typeof parsed !== "object") return {};
|
|
12278
11042
|
const clean = {};
|
|
@@ -12290,26 +11054,26 @@ function loadTailState(configDir2) {
|
|
|
12290
11054
|
}
|
|
12291
11055
|
function saveTailState(configDir2, state) {
|
|
12292
11056
|
const path2 = tailStatePath(configDir2);
|
|
12293
|
-
|
|
11057
|
+
mkdirSync6(dirname4(path2), { recursive: true });
|
|
12294
11058
|
const tmp = `${path2}.tmp.${process.pid}.${Date.now()}`;
|
|
12295
|
-
|
|
11059
|
+
writeFileSync7(tmp, JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
12296
11060
|
renameSync3(tmp, path2);
|
|
12297
11061
|
}
|
|
12298
11062
|
function discoverSourceFiles(configDir2, accountLogDir2, logicalSource) {
|
|
12299
11063
|
if (logicalSource === "server") {
|
|
12300
|
-
const p =
|
|
12301
|
-
return
|
|
11064
|
+
const p = resolve7(configDir2, "logs", "server.log");
|
|
11065
|
+
return existsSync7(p) ? [{ logicalSource: "server", filepath: p }] : [];
|
|
12302
11066
|
}
|
|
12303
11067
|
if (logicalSource === "vnc") {
|
|
12304
|
-
const p =
|
|
12305
|
-
return
|
|
11068
|
+
const p = resolve7(configDir2, "logs", "vnc-boot.log");
|
|
11069
|
+
return existsSync7(p) ? [{ logicalSource: "vnc", filepath: p }] : [];
|
|
12306
11070
|
}
|
|
12307
11071
|
if (logicalSource === "cloudflared") {
|
|
12308
11072
|
const files2 = [];
|
|
12309
|
-
const daemon =
|
|
12310
|
-
if (
|
|
12311
|
-
const login =
|
|
12312
|
-
if (
|
|
11073
|
+
const daemon = resolve7(configDir2, "logs", "cloudflared.log");
|
|
11074
|
+
if (existsSync7(daemon)) files2.push({ logicalSource: "cloudflared", filepath: daemon });
|
|
11075
|
+
const login = resolve7(configDir2, "logs", "cloudflared-login.log");
|
|
11076
|
+
if (existsSync7(login)) files2.push({ logicalSource: "cloudflared", filepath: login });
|
|
12313
11077
|
return files2;
|
|
12314
11078
|
}
|
|
12315
11079
|
const prefix = {
|
|
@@ -12319,7 +11083,7 @@ function discoverSourceFiles(configDir2, accountLogDir2, logicalSource) {
|
|
|
12319
11083
|
public: "public-agent-stream-",
|
|
12320
11084
|
mcp: "mcp-"
|
|
12321
11085
|
}[logicalSource];
|
|
12322
|
-
if (!
|
|
11086
|
+
if (!existsSync7(accountLogDir2)) return [];
|
|
12323
11087
|
const files = [];
|
|
12324
11088
|
let scanned = 0;
|
|
12325
11089
|
let skippedPrefixMismatch = 0;
|
|
@@ -12329,7 +11093,7 @@ function discoverSourceFiles(configDir2, accountLogDir2, logicalSource) {
|
|
|
12329
11093
|
const matchesPrefix = entry.startsWith(prefix);
|
|
12330
11094
|
const isLog = entry.endsWith(".log");
|
|
12331
11095
|
if (matchesPrefix && isLog) {
|
|
12332
|
-
files.push({ logicalSource, filepath:
|
|
11096
|
+
files.push({ logicalSource, filepath: join4(accountLogDir2, entry) });
|
|
12333
11097
|
} else if (!matchesPrefix) {
|
|
12334
11098
|
skippedPrefixMismatch += 1;
|
|
12335
11099
|
} else {
|
|
@@ -12361,7 +11125,7 @@ function discoverAllSources(configDir2, accountLogDir2) {
|
|
|
12361
11125
|
];
|
|
12362
11126
|
}
|
|
12363
11127
|
function readNewLines(filepath, prev) {
|
|
12364
|
-
if (!
|
|
11128
|
+
if (!existsSync7(filepath)) return null;
|
|
12365
11129
|
const stat5 = statSync5(filepath);
|
|
12366
11130
|
const size = stat5.size;
|
|
12367
11131
|
const inode = stat5.ino;
|
|
@@ -12414,12 +11178,12 @@ function readNewLines(filepath, prev) {
|
|
|
12414
11178
|
}
|
|
12415
11179
|
}
|
|
12416
11180
|
function countRecentWrites(dir, sinceMs) {
|
|
12417
|
-
if (!
|
|
11181
|
+
if (!existsSync7(dir)) return 0;
|
|
12418
11182
|
let count = 0;
|
|
12419
11183
|
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
12420
11184
|
if (!entry.isFile()) continue;
|
|
12421
11185
|
try {
|
|
12422
|
-
const st = statSync5(
|
|
11186
|
+
const st = statSync5(join4(dir, entry.name));
|
|
12423
11187
|
if (st.mtimeMs >= sinceMs) count += 1;
|
|
12424
11188
|
} catch {
|
|
12425
11189
|
}
|
|
@@ -12427,7 +11191,7 @@ function countRecentWrites(dir, sinceMs) {
|
|
|
12427
11191
|
return count;
|
|
12428
11192
|
}
|
|
12429
11193
|
function fileLastWriteMs(path2) {
|
|
12430
|
-
if (!
|
|
11194
|
+
if (!existsSync7(path2)) return null;
|
|
12431
11195
|
try {
|
|
12432
11196
|
return statSync5(path2).mtimeMs;
|
|
12433
11197
|
} catch {
|
|
@@ -12435,31 +11199,31 @@ function fileLastWriteMs(path2) {
|
|
|
12435
11199
|
}
|
|
12436
11200
|
}
|
|
12437
11201
|
function accountLogDir(accountDir) {
|
|
12438
|
-
return
|
|
11202
|
+
return resolve7(accountDir, "logs");
|
|
12439
11203
|
}
|
|
12440
11204
|
function sourceKey(file) {
|
|
12441
11205
|
return `${file.logicalSource}:${basename(file.filepath)}`;
|
|
12442
11206
|
}
|
|
12443
11207
|
|
|
12444
11208
|
// app/lib/review-detector/writer.ts
|
|
12445
|
-
import { appendFileSync as
|
|
12446
|
-
import { resolve as
|
|
11209
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync8, renameSync as renameSync4, statSync as statSync6 } from "fs";
|
|
11210
|
+
import { resolve as resolve8, dirname as dirname5 } from "path";
|
|
12447
11211
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
12448
11212
|
function reviewLogPath(configDir2) {
|
|
12449
|
-
return
|
|
11213
|
+
return resolve8(configDir2, "logs", "review.log");
|
|
12450
11214
|
}
|
|
12451
11215
|
function pendingAlertsPath(configDir2) {
|
|
12452
|
-
return
|
|
11216
|
+
return resolve8(configDir2, "review-pending-alerts.jsonl");
|
|
12453
11217
|
}
|
|
12454
11218
|
function reviewLog(configDir2, event) {
|
|
12455
11219
|
const path2 = reviewLogPath(configDir2);
|
|
12456
11220
|
try {
|
|
12457
|
-
|
|
11221
|
+
mkdirSync7(dirname5(path2), { recursive: true });
|
|
12458
11222
|
const line = `${new Date(
|
|
12459
11223
|
typeof event.ts === "number" ? event.ts : Date.now()
|
|
12460
11224
|
).toISOString()} [review] ${JSON.stringify(event)}
|
|
12461
11225
|
`;
|
|
12462
|
-
|
|
11226
|
+
appendFileSync2(path2, line, "utf-8");
|
|
12463
11227
|
} catch (err) {
|
|
12464
11228
|
console.error(`[review] failed to write review log at ${path2}: ${err instanceof Error ? err.message : String(err)}`);
|
|
12465
11229
|
}
|
|
@@ -12571,17 +11335,17 @@ async function upsertReviewAlert(accountId, match2) {
|
|
|
12571
11335
|
function queueAlert(configDir2, accountId, match2) {
|
|
12572
11336
|
const path2 = pendingAlertsPath(configDir2);
|
|
12573
11337
|
try {
|
|
12574
|
-
|
|
11338
|
+
mkdirSync7(dirname5(path2), { recursive: true });
|
|
12575
11339
|
const line = JSON.stringify({ accountId, match: match2 }) + "\n";
|
|
12576
|
-
|
|
11340
|
+
appendFileSync2(path2, line, "utf-8");
|
|
12577
11341
|
} catch (err) {
|
|
12578
11342
|
console.error(`[review] failed to queue alert at ${path2}: ${err instanceof Error ? err.message : String(err)}`);
|
|
12579
11343
|
}
|
|
12580
11344
|
}
|
|
12581
11345
|
async function drainPendingAlerts(configDir2) {
|
|
12582
11346
|
const path2 = pendingAlertsPath(configDir2);
|
|
12583
|
-
if (!
|
|
12584
|
-
const raw2 =
|
|
11347
|
+
if (!existsSync8(path2)) return { drained: 0, remaining: 0 };
|
|
11348
|
+
const raw2 = readFileSync9(path2, "utf-8");
|
|
12585
11349
|
const lines = raw2.split("\n").filter((l) => l.trim().length > 0);
|
|
12586
11350
|
if (lines.length === 0) return { drained: 0, remaining: 0 };
|
|
12587
11351
|
const remaining = [];
|
|
@@ -12602,9 +11366,9 @@ async function drainPendingAlerts(configDir2) {
|
|
|
12602
11366
|
}
|
|
12603
11367
|
const tmp = `${path2}.tmp.${process.pid}.${Date.now()}`;
|
|
12604
11368
|
if (remaining.length > 0) {
|
|
12605
|
-
|
|
11369
|
+
writeFileSync8(tmp, remaining.join("\n") + "\n", "utf-8");
|
|
12606
11370
|
} else {
|
|
12607
|
-
|
|
11371
|
+
writeFileSync8(tmp, "", "utf-8");
|
|
12608
11372
|
}
|
|
12609
11373
|
renameSync4(tmp, path2);
|
|
12610
11374
|
return { drained, remaining: remaining.length };
|
|
@@ -12704,7 +11468,7 @@ async function bootDetector() {
|
|
|
12704
11468
|
}
|
|
12705
11469
|
|
|
12706
11470
|
// app/lib/review-detector/scan-loop.ts
|
|
12707
|
-
import { resolve as
|
|
11471
|
+
import { resolve as resolve9 } from "path";
|
|
12708
11472
|
|
|
12709
11473
|
// app/lib/review-detector/evaluator.ts
|
|
12710
11474
|
var SAMPLE_MAX_CHARS = 500;
|
|
@@ -12998,14 +11762,14 @@ async function runScanCycle(runtime) {
|
|
|
12998
11762
|
match2 = result.match;
|
|
12999
11763
|
}
|
|
13000
11764
|
} else if (rule.type === "file-write-storm") {
|
|
13001
|
-
const dir =
|
|
11765
|
+
const dir = resolve9(runtime.configDir, rule.watchPath ?? "");
|
|
13002
11766
|
const sinceMs = cycleStart - rule.thresholdWindowMinutes * 6e4;
|
|
13003
11767
|
const count = countRecentWrites(dir, sinceMs);
|
|
13004
11768
|
const result = evaluateFileWriteStormRule(rule, count, state, cycleStart);
|
|
13005
11769
|
state = result.state;
|
|
13006
11770
|
match2 = result.match;
|
|
13007
11771
|
} else if (rule.type === "stale-log") {
|
|
13008
|
-
const trackedPath =
|
|
11772
|
+
const trackedPath = resolve9(runtime.configDir, rule.watchPath ?? "");
|
|
13009
11773
|
const lastMs = fileLastWriteMs(trackedPath);
|
|
13010
11774
|
const result = evaluateStaleLogRule(rule, lastMs, state, cycleStart);
|
|
13011
11775
|
state = result.state;
|
|
@@ -13244,20 +12008,20 @@ var WhatsAppConfigSchema = z.object({
|
|
|
13244
12008
|
});
|
|
13245
12009
|
|
|
13246
12010
|
// app/lib/whatsapp/config-persist.ts
|
|
13247
|
-
import { readFileSync as
|
|
13248
|
-
import { resolve as
|
|
12011
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync9, existsSync as existsSync9 } from "fs";
|
|
12012
|
+
import { resolve as resolve10, join as join5 } from "path";
|
|
13249
12013
|
var TAG3 = "[whatsapp:config]";
|
|
13250
12014
|
function configPath(accountDir) {
|
|
13251
|
-
return
|
|
12015
|
+
return resolve10(accountDir, "account.json");
|
|
13252
12016
|
}
|
|
13253
12017
|
function readConfig(accountDir) {
|
|
13254
12018
|
const path2 = configPath(accountDir);
|
|
13255
|
-
if (!
|
|
13256
|
-
return JSON.parse(
|
|
12019
|
+
if (!existsSync9(path2)) throw new Error(`account.json not found at ${path2}`);
|
|
12020
|
+
return JSON.parse(readFileSync10(path2, "utf-8"));
|
|
13257
12021
|
}
|
|
13258
12022
|
function writeConfig(accountDir, config) {
|
|
13259
12023
|
const path2 = configPath(accountDir);
|
|
13260
|
-
|
|
12024
|
+
writeFileSync9(path2, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
13261
12025
|
}
|
|
13262
12026
|
function reloadManagerConfig(accountDir) {
|
|
13263
12027
|
try {
|
|
@@ -13427,8 +12191,8 @@ function setPublicAgent(accountDir, slug) {
|
|
|
13427
12191
|
if (!trimmed) {
|
|
13428
12192
|
return { ok: false, error: "Agent slug cannot be empty." };
|
|
13429
12193
|
}
|
|
13430
|
-
const agentConfigPath =
|
|
13431
|
-
if (!
|
|
12194
|
+
const agentConfigPath = join5(accountDir, "agents", trimmed, "config.json");
|
|
12195
|
+
if (!existsSync9(agentConfigPath)) {
|
|
13432
12196
|
return { ok: false, error: `Agent "${trimmed}" not found \u2014 no config.json at ${agentConfigPath}. Check the agent slug and try again.` };
|
|
13433
12197
|
}
|
|
13434
12198
|
try {
|
|
@@ -13703,7 +12467,7 @@ var credsSaveQueue = Promise.resolve();
|
|
|
13703
12467
|
async function drainCredsSaveQueue(timeoutMs = 5e3) {
|
|
13704
12468
|
console.error(`${TAG5} draining credential save queue\u2026`);
|
|
13705
12469
|
const timer = new Promise(
|
|
13706
|
-
(
|
|
12470
|
+
(resolve28) => setTimeout(() => resolve28("timeout"), timeoutMs)
|
|
13707
12471
|
);
|
|
13708
12472
|
const result = await Promise.race([
|
|
13709
12473
|
credsSaveQueue.then(() => "drained"),
|
|
@@ -13831,11 +12595,11 @@ async function createWaSocket(opts) {
|
|
|
13831
12595
|
return sock;
|
|
13832
12596
|
}
|
|
13833
12597
|
async function waitForConnection(sock) {
|
|
13834
|
-
return new Promise((
|
|
12598
|
+
return new Promise((resolve28, reject) => {
|
|
13835
12599
|
const handler = (update) => {
|
|
13836
12600
|
if (update.connection === "open") {
|
|
13837
12601
|
sock.ev.off("connection.update", handler);
|
|
13838
|
-
|
|
12602
|
+
resolve28();
|
|
13839
12603
|
}
|
|
13840
12604
|
if (update.connection === "close") {
|
|
13841
12605
|
sock.ev.off("connection.update", handler);
|
|
@@ -13949,14 +12713,14 @@ ${inspected}`;
|
|
|
13949
12713
|
return inspect2(err, INSPECT_OPTS2);
|
|
13950
12714
|
}
|
|
13951
12715
|
function withTimeout(label, promise, timeoutMs) {
|
|
13952
|
-
return new Promise((
|
|
12716
|
+
return new Promise((resolve28, reject) => {
|
|
13953
12717
|
const timer = setTimeout(() => {
|
|
13954
12718
|
reject(new Error(`${label} timed out after ${timeoutMs}ms`));
|
|
13955
12719
|
}, timeoutMs);
|
|
13956
12720
|
promise.then(
|
|
13957
12721
|
(value) => {
|
|
13958
12722
|
clearTimeout(timer);
|
|
13959
|
-
|
|
12723
|
+
resolve28(value);
|
|
13960
12724
|
},
|
|
13961
12725
|
(err) => {
|
|
13962
12726
|
clearTimeout(timer);
|
|
@@ -14468,7 +13232,7 @@ async function sendMediaMessage(sock, to, media, opts) {
|
|
|
14468
13232
|
// app/lib/whatsapp/inbound/media.ts
|
|
14469
13233
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
14470
13234
|
import { writeFile, mkdir } from "fs/promises";
|
|
14471
|
-
import { join as
|
|
13235
|
+
import { join as join6 } from "path";
|
|
14472
13236
|
import {
|
|
14473
13237
|
downloadMediaMessage,
|
|
14474
13238
|
downloadContentFromMessage,
|
|
@@ -14554,7 +13318,7 @@ async function downloadInboundMedia(msg, sock, opts) {
|
|
|
14554
13318
|
await mkdir(MEDIA_DIR, { recursive: true });
|
|
14555
13319
|
const ext = mimeToExt(mimetype ?? "application/octet-stream");
|
|
14556
13320
|
const filename = `${randomUUID5()}.${ext}`;
|
|
14557
|
-
const filePath =
|
|
13321
|
+
const filePath = join6(MEDIA_DIR, filename);
|
|
14558
13322
|
await writeFile(filePath, buffer);
|
|
14559
13323
|
const sizeKB = (buffer.length / 1024).toFixed(0);
|
|
14560
13324
|
console.error(`${TAG9} media downloaded type=${mimetype ?? "unknown"} size=${sizeKB}KB path=${filePath}`);
|
|
@@ -14894,6 +13658,20 @@ function storeMessage(storeKey, entry) {
|
|
|
14894
13658
|
console.error(`${TAG13} message store trimmed for ${storeKey}: ${trimmed} oldest messages removed`);
|
|
14895
13659
|
}
|
|
14896
13660
|
}
|
|
13661
|
+
function deriveSessionKey(input) {
|
|
13662
|
+
if (input.isOwnerMirror || input.agentType === "admin") {
|
|
13663
|
+
return `whatsapp:${input.accountId}`;
|
|
13664
|
+
}
|
|
13665
|
+
if (input.isGroup) {
|
|
13666
|
+
if (!input.groupJid) {
|
|
13667
|
+
throw new Error(
|
|
13668
|
+
`deriveSessionKey: isGroup=true requires groupJid (accountId=${input.accountId}, senderPhone=${input.senderPhone})`
|
|
13669
|
+
);
|
|
13670
|
+
}
|
|
13671
|
+
return `whatsapp:${input.accountId}:group:${input.groupJid}`;
|
|
13672
|
+
}
|
|
13673
|
+
return `whatsapp:${input.accountId}:${input.senderPhone}`;
|
|
13674
|
+
}
|
|
14897
13675
|
async function init(opts) {
|
|
14898
13676
|
if (initialized) {
|
|
14899
13677
|
console.error(`${TAG13} already initialized`);
|
|
@@ -15156,11 +13934,11 @@ async function connectWithReconnect(conn) {
|
|
|
15156
13934
|
console.error(
|
|
15157
13935
|
`${TAG13} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
|
|
15158
13936
|
);
|
|
15159
|
-
await new Promise((
|
|
15160
|
-
const timer = setTimeout(
|
|
13937
|
+
await new Promise((resolve28) => {
|
|
13938
|
+
const timer = setTimeout(resolve28, delay);
|
|
15161
13939
|
conn.abortController.signal.addEventListener("abort", () => {
|
|
15162
13940
|
clearTimeout(timer);
|
|
15163
|
-
|
|
13941
|
+
resolve28();
|
|
15164
13942
|
}, { once: true });
|
|
15165
13943
|
});
|
|
15166
13944
|
}
|
|
@@ -15168,16 +13946,16 @@ async function connectWithReconnect(conn) {
|
|
|
15168
13946
|
}
|
|
15169
13947
|
}
|
|
15170
13948
|
function waitForDisconnectEvent(conn) {
|
|
15171
|
-
return new Promise((
|
|
13949
|
+
return new Promise((resolve28) => {
|
|
15172
13950
|
if (!conn.sock) {
|
|
15173
|
-
|
|
13951
|
+
resolve28();
|
|
15174
13952
|
return;
|
|
15175
13953
|
}
|
|
15176
13954
|
const sock = conn.sock;
|
|
15177
13955
|
const handler = (update) => {
|
|
15178
13956
|
if (update.connection === "close") {
|
|
15179
13957
|
sock.ev.off("connection.update", handler);
|
|
15180
|
-
|
|
13958
|
+
resolve28();
|
|
15181
13959
|
}
|
|
15182
13960
|
};
|
|
15183
13961
|
sock.ev.on("connection.update", handler);
|
|
@@ -15359,7 +14137,14 @@ async function handleInboundMessage(conn, msg) {
|
|
|
15359
14137
|
isGroup: isGroup2,
|
|
15360
14138
|
groupJid: isGroup2 ? remoteJid : void 0,
|
|
15361
14139
|
reply: reply2,
|
|
15362
|
-
sessionKey:
|
|
14140
|
+
sessionKey: deriveSessionKey({
|
|
14141
|
+
agentType: "admin",
|
|
14142
|
+
accountId: conn.accountId,
|
|
14143
|
+
senderPhone: senderPhone2,
|
|
14144
|
+
isGroup: isGroup2,
|
|
14145
|
+
groupJid: isGroup2 ? remoteJid : void 0,
|
|
14146
|
+
isOwnerMirror: true
|
|
14147
|
+
}),
|
|
15363
14148
|
isOwnerMirror: true
|
|
15364
14149
|
});
|
|
15365
14150
|
return;
|
|
@@ -15387,8 +14172,8 @@ async function handleInboundMessage(conn, msg) {
|
|
|
15387
14172
|
const conversationKey = isGroup ? remoteJid : senderPhone;
|
|
15388
14173
|
const debounceKey = `${conn.accountId}:${conversationKey}:${senderPhone}`;
|
|
15389
14174
|
let resolvePending;
|
|
15390
|
-
const sttPending = new Promise((
|
|
15391
|
-
resolvePending =
|
|
14175
|
+
const sttPending = new Promise((resolve28) => {
|
|
14176
|
+
resolvePending = resolve28;
|
|
15392
14177
|
});
|
|
15393
14178
|
if (conn.debouncer) conn.debouncer.registerPending(debounceKey, sttPending);
|
|
15394
14179
|
try {
|
|
@@ -15438,7 +14223,13 @@ async function handleInboundMessage(conn, msg) {
|
|
|
15438
14223
|
if (!currentSock) throw new Error("WhatsApp disconnected \u2014 cannot reply");
|
|
15439
14224
|
await sendTextMessage(currentSock, remoteJid, text, { accountId: conn.accountId });
|
|
15440
14225
|
};
|
|
15441
|
-
const sessionKey =
|
|
14226
|
+
const sessionKey = deriveSessionKey({
|
|
14227
|
+
agentType: accessResult.agentType,
|
|
14228
|
+
accountId: conn.accountId,
|
|
14229
|
+
senderPhone,
|
|
14230
|
+
isGroup,
|
|
14231
|
+
groupJid: isGroup ? remoteJid : void 0
|
|
14232
|
+
});
|
|
15442
14233
|
conn.lastMessageAt = Date.now();
|
|
15443
14234
|
const payload = {
|
|
15444
14235
|
accountId: conn.accountId,
|
|
@@ -15495,44 +14286,30 @@ async function probeApiKey() {
|
|
|
15495
14286
|
return result.status;
|
|
15496
14287
|
}
|
|
15497
14288
|
function checkPort(port2, timeoutMs = 500) {
|
|
15498
|
-
return new Promise((
|
|
15499
|
-
const socket =
|
|
14289
|
+
return new Promise((resolve28) => {
|
|
14290
|
+
const socket = createConnection2(port2, "127.0.0.1");
|
|
15500
14291
|
socket.setTimeout(timeoutMs);
|
|
15501
14292
|
socket.once("connect", () => {
|
|
15502
14293
|
socket.destroy();
|
|
15503
|
-
|
|
14294
|
+
resolve28(true);
|
|
15504
14295
|
});
|
|
15505
14296
|
socket.once("error", () => {
|
|
15506
14297
|
socket.destroy();
|
|
15507
|
-
|
|
14298
|
+
resolve28(false);
|
|
15508
14299
|
});
|
|
15509
14300
|
socket.once("timeout", () => {
|
|
15510
14301
|
socket.destroy();
|
|
15511
|
-
|
|
14302
|
+
resolve28(false);
|
|
15512
14303
|
});
|
|
15513
14304
|
});
|
|
15514
14305
|
}
|
|
15515
|
-
var TTYD_PROBE_CACHE_MS = 2e3;
|
|
15516
|
-
var TTYD_PROBE_TIMEOUT_MS = 200;
|
|
15517
|
-
var cachedTtydReady = null;
|
|
15518
|
-
async function probeTerminalReady() {
|
|
15519
|
-
if (cachedTtydReady && Date.now() - cachedTtydReady.checkedAt < TTYD_PROBE_CACHE_MS) {
|
|
15520
|
-
return cachedTtydReady.ok;
|
|
15521
|
-
}
|
|
15522
|
-
const ok = await checkPort(7681, TTYD_PROBE_TIMEOUT_MS);
|
|
15523
|
-
if (!cachedTtydReady || cachedTtydReady.ok !== ok) {
|
|
15524
|
-
console.log(`[health] terminal_ready: ${cachedTtydReady?.ok ?? "unknown"} \u2192 ${ok}`);
|
|
15525
|
-
}
|
|
15526
|
-
cachedTtydReady = { ok, checkedAt: Date.now() };
|
|
15527
|
-
return ok;
|
|
15528
|
-
}
|
|
15529
14306
|
var app = new Hono2();
|
|
15530
14307
|
app.get("/", async (c) => {
|
|
15531
14308
|
const browserTransport = resolveBrowserTransport(c.req.raw, c.env?.incoming?.socket?.remoteAddress);
|
|
15532
14309
|
let pinConfigured = false;
|
|
15533
14310
|
try {
|
|
15534
|
-
if (
|
|
15535
|
-
const raw2 =
|
|
14311
|
+
if (existsSync10(USERS_FILE)) {
|
|
14312
|
+
const raw2 = readFileSync11(USERS_FILE, "utf-8").trim();
|
|
15536
14313
|
if (raw2) {
|
|
15537
14314
|
const users = JSON.parse(raw2);
|
|
15538
14315
|
pinConfigured = Array.isArray(users) && users.length > 0;
|
|
@@ -15549,10 +14326,9 @@ app.get("/", async (c) => {
|
|
|
15549
14326
|
}
|
|
15550
14327
|
const claudeAuthenticated = authHealth.status === "ok" || authHealth.status === "expiring";
|
|
15551
14328
|
const vncRunning = await checkPort(6080);
|
|
15552
|
-
const terminalReady = await probeTerminalReady();
|
|
15553
14329
|
let apiKeyConfigured = false;
|
|
15554
14330
|
try {
|
|
15555
|
-
apiKeyConfigured =
|
|
14331
|
+
apiKeyConfigured = existsSync10(keyFilePath());
|
|
15556
14332
|
} catch {
|
|
15557
14333
|
}
|
|
15558
14334
|
let apiKeyStatus = "missing";
|
|
@@ -15593,7 +14369,6 @@ app.get("/", async (c) => {
|
|
|
15593
14369
|
claude_authenticated: claudeAuthenticated,
|
|
15594
14370
|
...onboardingComplete !== void 0 && { onboarding_complete: onboardingComplete },
|
|
15595
14371
|
vnc_running: vncRunning,
|
|
15596
|
-
terminal_ready: terminalReady,
|
|
15597
14372
|
browser_transport: browserTransport,
|
|
15598
14373
|
auth_status: authHealth.status,
|
|
15599
14374
|
auth_expires_at: authHealth.expiresAt ?? null,
|
|
@@ -15621,14 +14396,14 @@ app.get("/", async (c) => {
|
|
|
15621
14396
|
var health_default = app;
|
|
15622
14397
|
|
|
15623
14398
|
// server/routes/session.ts
|
|
15624
|
-
import { resolve as
|
|
15625
|
-
import { existsSync as
|
|
14399
|
+
import { resolve as resolve11 } from "path";
|
|
14400
|
+
import { existsSync as existsSync11, writeFileSync as writeFileSync10, mkdirSync as mkdirSync8 } from "fs";
|
|
15626
14401
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
15627
14402
|
function writeBrandingCache(accountId, agentSlug, branding) {
|
|
15628
14403
|
try {
|
|
15629
|
-
const cacheDir =
|
|
15630
|
-
|
|
15631
|
-
|
|
14404
|
+
const cacheDir = resolve11(MAXY_DIR, "branding-cache", accountId);
|
|
14405
|
+
mkdirSync8(cacheDir, { recursive: true });
|
|
14406
|
+
writeFileSync10(resolve11(cacheDir, `${agentSlug}.json`), JSON.stringify(branding), "utf-8");
|
|
15632
14407
|
} catch (err) {
|
|
15633
14408
|
console.error(`[branding] cache write failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
15634
14409
|
}
|
|
@@ -15698,9 +14473,9 @@ app2.post("/", async (c) => {
|
|
|
15698
14473
|
}
|
|
15699
14474
|
let agentConfig = null;
|
|
15700
14475
|
if (account) {
|
|
15701
|
-
const agentDir =
|
|
15702
|
-
const agentConfigPath =
|
|
15703
|
-
if (!
|
|
14476
|
+
const agentDir = resolve11(account.accountDir, "agents", agentSlug);
|
|
14477
|
+
const agentConfigPath = resolve11(agentDir, "config.json");
|
|
14478
|
+
if (!existsSync11(agentDir) || !existsSync11(agentConfigPath)) {
|
|
15704
14479
|
return c.json({ error: "Agent not found" }, 404);
|
|
15705
14480
|
}
|
|
15706
14481
|
agentConfig = resolveAgentConfig(account.accountDir, agentSlug);
|
|
@@ -15946,9 +14721,9 @@ ${raw2}`;
|
|
|
15946
14721
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
15947
14722
|
import { mkdir as mkdir2, readFile, stat as stat2, writeFile as writeFile2 } from "fs/promises";
|
|
15948
14723
|
import { realpathSync } from "fs";
|
|
15949
|
-
import { resolve as
|
|
15950
|
-
var
|
|
15951
|
-
var ATTACHMENTS_ROOT =
|
|
14724
|
+
import { resolve as resolve12, extname, basename as basename3 } from "path";
|
|
14725
|
+
var PLATFORM_ROOT4 = process.env.MAXY_PLATFORM_ROOT ?? resolve12(process.cwd(), "../platform");
|
|
14726
|
+
var ATTACHMENTS_ROOT = resolve12(PLATFORM_ROOT4, "..", "data/uploads");
|
|
15952
14727
|
var SUPPORTED_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
15953
14728
|
"image/jpeg",
|
|
15954
14729
|
"image/png",
|
|
@@ -15972,11 +14747,11 @@ function assertSupportedMime(mimeType) {
|
|
|
15972
14747
|
}
|
|
15973
14748
|
async function writeAttachment(scope, filename, mimeType, sizeBytes, buffer) {
|
|
15974
14749
|
const attachmentId = randomUUID6();
|
|
15975
|
-
const dir =
|
|
14750
|
+
const dir = resolve12(ATTACHMENTS_ROOT, scope, attachmentId);
|
|
15976
14751
|
await mkdir2(dir, { recursive: true });
|
|
15977
14752
|
const ext = extname(filename) || "";
|
|
15978
|
-
const storagePath =
|
|
15979
|
-
const metaPath =
|
|
14753
|
+
const storagePath = resolve12(dir, `${attachmentId}${ext}`);
|
|
14754
|
+
const metaPath = resolve12(dir, `${attachmentId}.meta.json`);
|
|
15980
14755
|
const meta = {
|
|
15981
14756
|
attachmentId,
|
|
15982
14757
|
scope,
|
|
@@ -16051,7 +14826,7 @@ async function storeGeneratedFile(accountId, filePath) {
|
|
|
16051
14826
|
// app/lib/stt/voice-note.ts
|
|
16052
14827
|
import { writeFile as writeFile3, mkdtemp, rm } from "fs/promises";
|
|
16053
14828
|
import { tmpdir } from "os";
|
|
16054
|
-
import { join as
|
|
14829
|
+
import { join as join7 } from "path";
|
|
16055
14830
|
var TAG14 = "[voice]";
|
|
16056
14831
|
var AUDIO_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
16057
14832
|
"audio/ogg",
|
|
@@ -16089,9 +14864,9 @@ async function transcribeVoiceNote(file, source) {
|
|
|
16089
14864
|
let tempDir;
|
|
16090
14865
|
let tempPath;
|
|
16091
14866
|
try {
|
|
16092
|
-
tempDir = await mkdtemp(
|
|
14867
|
+
tempDir = await mkdtemp(join7(tmpdir(), "voice-"));
|
|
16093
14868
|
const ext = audioExtension(mimeType);
|
|
16094
|
-
tempPath =
|
|
14869
|
+
tempPath = join7(tempDir, `recording${ext}`);
|
|
16095
14870
|
const buffer = Buffer.from(await file.arrayBuffer());
|
|
16096
14871
|
await writeFile3(tempPath, buffer);
|
|
16097
14872
|
} catch (err) {
|
|
@@ -16653,16 +15428,16 @@ var group_default = app4;
|
|
|
16653
15428
|
|
|
16654
15429
|
// app/lib/access-gate.ts
|
|
16655
15430
|
import neo4j2 from "neo4j-driver";
|
|
16656
|
-
import { readFileSync as
|
|
16657
|
-
import { resolve as
|
|
15431
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
15432
|
+
import { resolve as resolve13 } from "path";
|
|
16658
15433
|
import { randomUUID as randomUUID7, randomInt } from "crypto";
|
|
16659
|
-
var
|
|
15434
|
+
var PLATFORM_ROOT5 = process.env.MAXY_PLATFORM_ROOT ?? resolve13(process.cwd(), "..");
|
|
16660
15435
|
var driver2 = null;
|
|
16661
15436
|
function readPassword2() {
|
|
16662
15437
|
if (process.env.NEO4J_PASSWORD) return process.env.NEO4J_PASSWORD;
|
|
16663
|
-
const passwordFile =
|
|
15438
|
+
const passwordFile = resolve13(PLATFORM_ROOT5, "config/.neo4j-password");
|
|
16664
15439
|
try {
|
|
16665
|
-
return
|
|
15440
|
+
return readFileSync12(passwordFile, "utf-8").trim();
|
|
16666
15441
|
} catch {
|
|
16667
15442
|
throw new Error(
|
|
16668
15443
|
`Neo4j password not found. Expected at ${passwordFile} or in NEO4J_PASSWORD env var.`
|
|
@@ -16695,13 +15470,13 @@ process.on("SIGINT", async () => {
|
|
|
16695
15470
|
driver2 = null;
|
|
16696
15471
|
}
|
|
16697
15472
|
});
|
|
16698
|
-
var
|
|
15473
|
+
var rateLimitMap = /* @__PURE__ */ new Map();
|
|
16699
15474
|
var ACCESS_MAX_ATTEMPTS = 5;
|
|
16700
15475
|
var ACCESS_LOCKOUT_MS = 15 * 60 * 1e3;
|
|
16701
15476
|
var ACCESS_WINDOW_MS = 15 * 60 * 1e3;
|
|
16702
15477
|
function checkAccessRateLimit(ip, agentSlug) {
|
|
16703
15478
|
const key = `${ip}:${agentSlug}`;
|
|
16704
|
-
const entry =
|
|
15479
|
+
const entry = rateLimitMap.get(key);
|
|
16705
15480
|
if (!entry) return null;
|
|
16706
15481
|
const now = Date.now();
|
|
16707
15482
|
if (entry.lockedUntil && now < entry.lockedUntil) {
|
|
@@ -16710,7 +15485,7 @@ function checkAccessRateLimit(ip, agentSlug) {
|
|
|
16710
15485
|
return `Too many attempts. Try again in ${remainingS}s`;
|
|
16711
15486
|
}
|
|
16712
15487
|
if (now - entry.firstAttempt > ACCESS_WINDOW_MS) {
|
|
16713
|
-
|
|
15488
|
+
rateLimitMap.delete(key);
|
|
16714
15489
|
return null;
|
|
16715
15490
|
}
|
|
16716
15491
|
return null;
|
|
@@ -16718,9 +15493,9 @@ function checkAccessRateLimit(ip, agentSlug) {
|
|
|
16718
15493
|
function recordAccessFailedAttempt(ip, agentSlug) {
|
|
16719
15494
|
const key = `${ip}:${agentSlug}`;
|
|
16720
15495
|
const now = Date.now();
|
|
16721
|
-
const entry =
|
|
15496
|
+
const entry = rateLimitMap.get(key);
|
|
16722
15497
|
if (!entry || now - entry.firstAttempt > ACCESS_WINDOW_MS) {
|
|
16723
|
-
|
|
15498
|
+
rateLimitMap.set(key, { attempts: 1, firstAttempt: now });
|
|
16724
15499
|
return;
|
|
16725
15500
|
}
|
|
16726
15501
|
entry.attempts++;
|
|
@@ -16729,7 +15504,7 @@ function recordAccessFailedAttempt(ip, agentSlug) {
|
|
|
16729
15504
|
}
|
|
16730
15505
|
}
|
|
16731
15506
|
function clearAccessRateLimit(ip, agentSlug) {
|
|
16732
|
-
|
|
15507
|
+
rateLimitMap.delete(`${ip}:${agentSlug}`);
|
|
16733
15508
|
}
|
|
16734
15509
|
var forgotPwdMap = /* @__PURE__ */ new Map();
|
|
16735
15510
|
var FORGOT_PWD_MAX = 3;
|
|
@@ -16973,19 +15748,19 @@ async function findActiveGrantByContact(contactValue, agentSlug, accountId) {
|
|
|
16973
15748
|
}
|
|
16974
15749
|
|
|
16975
15750
|
// app/lib/brevo-sms.ts
|
|
16976
|
-
import { readFileSync as
|
|
15751
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync11, mkdirSync as mkdirSync9, existsSync as existsSync12, chmodSync } from "fs";
|
|
16977
15752
|
import { dirname as dirname6 } from "path";
|
|
16978
|
-
import { resolve as
|
|
16979
|
-
var BREVO_API_KEY_FILE =
|
|
15753
|
+
import { resolve as resolve14 } from "path";
|
|
15754
|
+
var BREVO_API_KEY_FILE = resolve14(MAXY_DIR, ".brevo-api-key");
|
|
16980
15755
|
var BREVO_API_URL = "https://api.brevo.com/v3/transactionalSMS/sms";
|
|
16981
15756
|
var BREVO_TIMEOUT_MS = 1e4;
|
|
16982
15757
|
var BREVO_SENDER = "Maxy";
|
|
16983
|
-
var
|
|
16984
|
-
if (
|
|
15758
|
+
var platformRoot = process.env.MAXY_PLATFORM_ROOT;
|
|
15759
|
+
if (platformRoot) {
|
|
16985
15760
|
try {
|
|
16986
|
-
const brandPath =
|
|
16987
|
-
if (
|
|
16988
|
-
const brand = JSON.parse(
|
|
15761
|
+
const brandPath = resolve14(platformRoot, "config", "brand.json");
|
|
15762
|
+
if (existsSync12(brandPath)) {
|
|
15763
|
+
const brand = JSON.parse(readFileSync13(brandPath, "utf-8"));
|
|
16989
15764
|
if (brand.productName) BREVO_SENDER = brand.productName;
|
|
16990
15765
|
}
|
|
16991
15766
|
} catch {
|
|
@@ -16993,7 +15768,7 @@ if (platformRoot2) {
|
|
|
16993
15768
|
}
|
|
16994
15769
|
function readBrevoApiKey() {
|
|
16995
15770
|
try {
|
|
16996
|
-
const key =
|
|
15771
|
+
const key = readFileSync13(BREVO_API_KEY_FILE, "utf-8").trim();
|
|
16997
15772
|
if (!key) {
|
|
16998
15773
|
throw new Error(`Brevo API key file is empty: ${BREVO_API_KEY_FILE}`);
|
|
16999
15774
|
}
|
|
@@ -17008,7 +15783,7 @@ function readBrevoApiKey() {
|
|
|
17008
15783
|
}
|
|
17009
15784
|
}
|
|
17010
15785
|
function hasBrevoApiKey() {
|
|
17011
|
-
return
|
|
15786
|
+
return existsSync12(BREVO_API_KEY_FILE);
|
|
17012
15787
|
}
|
|
17013
15788
|
async function sendSms(recipient, content, opts) {
|
|
17014
15789
|
let apiKey;
|
|
@@ -17380,8 +16155,8 @@ app5.post("/forgot-password", async (c) => {
|
|
|
17380
16155
|
app5.post("/send-otp", async (c) => {
|
|
17381
16156
|
const socketAddr = c.env?.incoming?.socket?.remoteAddress;
|
|
17382
16157
|
const hasXff = !!c.req.header("x-forwarded-for");
|
|
17383
|
-
const
|
|
17384
|
-
if (!
|
|
16158
|
+
const isLoopback = socketAddr === "127.0.0.1" || socketAddr === "::1" || socketAddr === "::ffff:127.0.0.1";
|
|
16159
|
+
if (!isLoopback || hasXff) {
|
|
17385
16160
|
console.error(`[access-gate] send-otp rejected: socket=${socketAddr ?? "unknown"} xff=${hasXff}`);
|
|
17386
16161
|
return c.json({ error: "Forbidden" }, 403);
|
|
17387
16162
|
}
|
|
@@ -17424,8 +16199,8 @@ app5.post("/send-otp", async (c) => {
|
|
|
17424
16199
|
var access_default = app5;
|
|
17425
16200
|
|
|
17426
16201
|
// server/routes/telegram.ts
|
|
17427
|
-
import { existsSync as
|
|
17428
|
-
import { timingSafeEqual
|
|
16202
|
+
import { existsSync as existsSync13, readFileSync as readFileSync14 } from "fs";
|
|
16203
|
+
import { timingSafeEqual } from "crypto";
|
|
17429
16204
|
|
|
17430
16205
|
// app/lib/telegram/access-control.ts
|
|
17431
16206
|
function checkTelegramAccess(params) {
|
|
@@ -17461,8 +16236,8 @@ var TELEGRAM_API = "https://api.telegram.org";
|
|
|
17461
16236
|
function getWebhookSecret(botType) {
|
|
17462
16237
|
const filePath = botType === "admin" ? TELEGRAM_ADMIN_WEBHOOK_SECRET_FILE : TELEGRAM_WEBHOOK_SECRET_FILE;
|
|
17463
16238
|
try {
|
|
17464
|
-
if (!
|
|
17465
|
-
const secret =
|
|
16239
|
+
if (!existsSync13(filePath)) return null;
|
|
16240
|
+
const secret = readFileSync14(filePath, "utf-8").trim();
|
|
17466
16241
|
return secret || null;
|
|
17467
16242
|
} catch {
|
|
17468
16243
|
return null;
|
|
@@ -17472,7 +16247,7 @@ function verifyWebhookSecret(headerValue, storedSecret) {
|
|
|
17472
16247
|
const a = Buffer.from(headerValue);
|
|
17473
16248
|
const b = Buffer.from(storedSecret);
|
|
17474
16249
|
if (a.length !== b.length) return false;
|
|
17475
|
-
return
|
|
16250
|
+
return timingSafeEqual(a, b);
|
|
17476
16251
|
}
|
|
17477
16252
|
async function handleInbound(params) {
|
|
17478
16253
|
const { chatId, senderId, text, botType, botToken, accountId, agentType } = params;
|
|
@@ -17620,9 +16395,9 @@ app6.post("/webhook", async (c) => {
|
|
|
17620
16395
|
var telegram_default = app6;
|
|
17621
16396
|
|
|
17622
16397
|
// server/routes/whatsapp.ts
|
|
17623
|
-
import { join as
|
|
16398
|
+
import { join as join8, resolve as resolve15, basename as basename4 } from "path";
|
|
17624
16399
|
import { readFile as readFile2, stat as stat3 } from "fs/promises";
|
|
17625
|
-
import { realpathSync as realpathSync2, readdirSync as readdirSync4, readFileSync as
|
|
16400
|
+
import { realpathSync as realpathSync2, readdirSync as readdirSync4, readFileSync as readFileSync15, existsSync as existsSync14 } from "fs";
|
|
17626
16401
|
|
|
17627
16402
|
// app/lib/whatsapp/login.ts
|
|
17628
16403
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
@@ -17728,8 +16503,8 @@ async function startLogin(opts) {
|
|
|
17728
16503
|
resetActiveLogin(accountId);
|
|
17729
16504
|
let resolveQr = null;
|
|
17730
16505
|
let rejectQr = null;
|
|
17731
|
-
const qrPromise = new Promise((
|
|
17732
|
-
resolveQr =
|
|
16506
|
+
const qrPromise = new Promise((resolve28, reject) => {
|
|
16507
|
+
resolveQr = resolve28;
|
|
17733
16508
|
rejectQr = reject;
|
|
17734
16509
|
});
|
|
17735
16510
|
const qrTimer = setTimeout(
|
|
@@ -17945,7 +16720,7 @@ function serializeWhatsAppSchema() {
|
|
|
17945
16720
|
|
|
17946
16721
|
// server/routes/whatsapp.ts
|
|
17947
16722
|
var TAG18 = "[whatsapp:api]";
|
|
17948
|
-
var
|
|
16723
|
+
var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
17949
16724
|
var app7 = new Hono2();
|
|
17950
16725
|
app7.get("/status", (c) => {
|
|
17951
16726
|
try {
|
|
@@ -17963,7 +16738,7 @@ app7.post("/login/start", async (c) => {
|
|
|
17963
16738
|
const body = await c.req.json().catch(() => ({}));
|
|
17964
16739
|
const accountId = validateAccountId(body.accountId);
|
|
17965
16740
|
const force = body.force ?? false;
|
|
17966
|
-
const authDir =
|
|
16741
|
+
const authDir = join8(MAXY_DIR, "credentials", "whatsapp", accountId);
|
|
17967
16742
|
const result = await startLogin({ accountId, authDir, force });
|
|
17968
16743
|
console.error(`${TAG18} login/start result account=${accountId} hasQr=${!!result.qrRaw}${result.selfPhone ? ` phone=${result.selfPhone}` : ""}`);
|
|
17969
16744
|
return c.json(result);
|
|
@@ -18100,17 +16875,17 @@ app7.post("/config", async (c) => {
|
|
|
18100
16875
|
return c.json({ ok: true, slug: currentSlug });
|
|
18101
16876
|
}
|
|
18102
16877
|
case "list-public-agents": {
|
|
18103
|
-
const agentsDir =
|
|
16878
|
+
const agentsDir = resolve15(account.accountDir, "agents");
|
|
18104
16879
|
const agents = [];
|
|
18105
|
-
if (
|
|
16880
|
+
if (existsSync14(agentsDir)) {
|
|
18106
16881
|
try {
|
|
18107
16882
|
const entries = readdirSync4(agentsDir, { withFileTypes: true });
|
|
18108
16883
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
18109
16884
|
if (!entry.isDirectory() || entry.name === "admin") continue;
|
|
18110
|
-
const configPath2 =
|
|
18111
|
-
if (!
|
|
16885
|
+
const configPath2 = resolve15(agentsDir, entry.name, "config.json");
|
|
16886
|
+
if (!existsSync14(configPath2)) continue;
|
|
18112
16887
|
try {
|
|
18113
|
-
const config = JSON.parse(
|
|
16888
|
+
const config = JSON.parse(readFileSync15(configPath2, "utf-8"));
|
|
18114
16889
|
agents.push({ slug: entry.name, displayName: config.displayName ?? entry.name });
|
|
18115
16890
|
} catch {
|
|
18116
16891
|
console.error(`${TAG18} config action=list-public-agents error="failed to parse config.json for agent ${entry.name}" \u2014 skipping`);
|
|
@@ -18182,10 +16957,10 @@ app7.post("/send-document", async (c) => {
|
|
|
18182
16957
|
if (!to || !filePath) {
|
|
18183
16958
|
return c.json({ error: "Missing required fields: to, filePath" }, 400);
|
|
18184
16959
|
}
|
|
18185
|
-
if (!maxyAccountId || !
|
|
16960
|
+
if (!maxyAccountId || !PLATFORM_ROOT6) {
|
|
18186
16961
|
return c.json({ error: "Cannot validate file path: missing account or platform context" }, 400);
|
|
18187
16962
|
}
|
|
18188
|
-
const accountDir =
|
|
16963
|
+
const accountDir = resolve15(PLATFORM_ROOT6, "..", "data/accounts", maxyAccountId);
|
|
18189
16964
|
let resolvedPath;
|
|
18190
16965
|
try {
|
|
18191
16966
|
resolvedPath = realpathSync2(filePath);
|
|
@@ -18322,16 +17097,16 @@ var whatsapp_default = app7;
|
|
|
18322
17097
|
|
|
18323
17098
|
// server/routes/onboarding.ts
|
|
18324
17099
|
import { spawn as spawn3, execFileSync as execFileSync2 } from "child_process";
|
|
18325
|
-
import { openSync as openSync4, closeSync as closeSync4, writeFileSync as
|
|
18326
|
-
import { resolve as
|
|
17100
|
+
import { openSync as openSync4, closeSync as closeSync4, writeFileSync as writeFileSync12, writeSync, existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync16, unlinkSync as unlinkSync4 } from "fs";
|
|
17101
|
+
import { resolve as resolve16, dirname as dirname7 } from "path";
|
|
18327
17102
|
import { createHash, randomUUID as randomUUID9 } from "crypto";
|
|
18328
|
-
var
|
|
17103
|
+
var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
18329
17104
|
function hashPin(pin) {
|
|
18330
17105
|
return createHash("sha256").update(pin).digest("hex");
|
|
18331
17106
|
}
|
|
18332
17107
|
function readUsersFile() {
|
|
18333
|
-
if (!
|
|
18334
|
-
const raw2 =
|
|
17108
|
+
if (!existsSync15(USERS_FILE)) return null;
|
|
17109
|
+
const raw2 = readFileSync16(USERS_FILE, "utf-8").trim();
|
|
18335
17110
|
if (!raw2) return [];
|
|
18336
17111
|
return JSON.parse(raw2);
|
|
18337
17112
|
}
|
|
@@ -18397,7 +17172,7 @@ app8.post("/claude-auth", async (c) => {
|
|
|
18397
17172
|
if (!vncReady) return c.json({ error: "VNC display failed to start" }, 500);
|
|
18398
17173
|
}
|
|
18399
17174
|
await ensureCdp(transport);
|
|
18400
|
-
|
|
17175
|
+
writeFileSync12(logPath("claude-auth"), "");
|
|
18401
17176
|
const chromiumWrapper = writeChromiumWrapper();
|
|
18402
17177
|
const x11Env = buildX11Env(chromiumWrapper, transport);
|
|
18403
17178
|
vncLog("claude-auth", { action: "start", transport });
|
|
@@ -18436,17 +17211,17 @@ app8.post("/set-pin", async (c) => {
|
|
|
18436
17211
|
}
|
|
18437
17212
|
const hash = hashPin(body.pin);
|
|
18438
17213
|
const userId = randomUUID9();
|
|
18439
|
-
|
|
18440
|
-
|
|
17214
|
+
mkdirSync10(dirname7(USERS_FILE), { recursive: true });
|
|
17215
|
+
writeFileSync12(USERS_FILE, JSON.stringify([{ userId, name: "Owner", pin: hash }]), { mode: 384 });
|
|
18441
17216
|
console.log(`[set-pin] created users.json: userId=${userId.slice(0, 8)}\u2026 hash=${hash.slice(0, 8)}\u2026`);
|
|
18442
17217
|
const account = resolveAccount();
|
|
18443
17218
|
if (account) {
|
|
18444
17219
|
try {
|
|
18445
|
-
const config = JSON.parse(
|
|
17220
|
+
const config = JSON.parse(readFileSync16(`${account.accountDir}/account.json`, "utf-8"));
|
|
18446
17221
|
if (!config.admins) config.admins = [];
|
|
18447
17222
|
if (!config.admins.some((a) => a.userId === userId)) {
|
|
18448
17223
|
config.admins.push({ userId, role: "owner" });
|
|
18449
|
-
|
|
17224
|
+
writeFileSync12(`${account.accountDir}/account.json`, JSON.stringify(config, null, 2) + "\n");
|
|
18450
17225
|
console.log(`[set-pin] added userId=${userId.slice(0, 8)}\u2026 to account.json admins`);
|
|
18451
17226
|
}
|
|
18452
17227
|
} catch (err) {
|
|
@@ -18484,7 +17259,7 @@ app8.delete("/set-pin", async (c) => {
|
|
|
18484
17259
|
unlinkSync4(USERS_FILE);
|
|
18485
17260
|
console.log(`[set-pin] cleared users.json (last entry removed): userId=${matchedUser.userId.slice(0, 8)}\u2026`);
|
|
18486
17261
|
} else {
|
|
18487
|
-
|
|
17262
|
+
writeFileSync12(USERS_FILE, JSON.stringify(remaining), { mode: 384 });
|
|
18488
17263
|
console.log(`[set-pin] removed entry from users.json: userId=${matchedUser.userId.slice(0, 8)}\u2026 remaining=${remaining.length}`);
|
|
18489
17264
|
}
|
|
18490
17265
|
return c.json({ ok: true });
|
|
@@ -18497,19 +17272,19 @@ app8.post("/skip", async (c) => {
|
|
|
18497
17272
|
}
|
|
18498
17273
|
const { accountId, accountDir } = account;
|
|
18499
17274
|
let agentName = "Maxy";
|
|
18500
|
-
const brandPath =
|
|
18501
|
-
if (brandPath &&
|
|
17275
|
+
const brandPath = PLATFORM_ROOT7 ? resolve16(PLATFORM_ROOT7, "config", "brand.json") : "";
|
|
17276
|
+
if (brandPath && existsSync15(brandPath)) {
|
|
18502
17277
|
try {
|
|
18503
|
-
const brand = JSON.parse(
|
|
17278
|
+
const brand = JSON.parse(readFileSync16(brandPath, "utf-8"));
|
|
18504
17279
|
if (brand.productName) agentName = brand.productName;
|
|
18505
17280
|
} catch (err) {
|
|
18506
17281
|
console.error(`[onboarding-skip] brand.json read failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
18507
17282
|
}
|
|
18508
17283
|
}
|
|
18509
|
-
const soulPath =
|
|
17284
|
+
const soulPath = resolve16(accountDir, "agents", "admin", "SOUL.md");
|
|
18510
17285
|
try {
|
|
18511
|
-
|
|
18512
|
-
|
|
17286
|
+
mkdirSync10(dirname7(soulPath), { recursive: true });
|
|
17287
|
+
writeFileSync12(soulPath, `You are ${agentName}, an AI operations manager.
|
|
18513
17288
|
`);
|
|
18514
17289
|
console.log(`[onboarding-skip] wrote SOUL.md: ${soulPath}`);
|
|
18515
17290
|
} catch (err) {
|
|
@@ -18547,9 +17322,9 @@ app8.post("/skip", async (c) => {
|
|
|
18547
17322
|
var onboarding_default = app8;
|
|
18548
17323
|
|
|
18549
17324
|
// server/routes/client-error.ts
|
|
18550
|
-
import { appendFileSync as
|
|
18551
|
-
import { join as
|
|
18552
|
-
var CLIENT_ERRORS_LOG =
|
|
17325
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync16, renameSync as renameSync5, statSync as statSync7 } from "fs";
|
|
17326
|
+
import { join as join9 } from "path";
|
|
17327
|
+
var CLIENT_ERRORS_LOG = join9(LOG_DIR, "client-errors.log");
|
|
18553
17328
|
var MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
18554
17329
|
var MAX_BODY_SIZE = 8 * 1024;
|
|
18555
17330
|
var MAX_STACK_LEN = 2e3;
|
|
@@ -18601,7 +17376,7 @@ function stackHead(stack) {
|
|
|
18601
17376
|
}
|
|
18602
17377
|
function rotateIfNeeded() {
|
|
18603
17378
|
try {
|
|
18604
|
-
if (!
|
|
17379
|
+
if (!existsSync16(CLIENT_ERRORS_LOG)) return;
|
|
18605
17380
|
const stats = statSync7(CLIENT_ERRORS_LOG);
|
|
18606
17381
|
if (stats.size < MAX_LOG_SIZE) return;
|
|
18607
17382
|
renameSync5(CLIENT_ERRORS_LOG, CLIENT_ERRORS_LOG + ".1");
|
|
@@ -18684,7 +17459,7 @@ app9.post("/", async (c) => {
|
|
|
18684
17459
|
tag: typeof body.tag === "string" ? truncate2(body.tag, 32) : void 0,
|
|
18685
17460
|
status: typeof body.status === "number" ? body.status : void 0
|
|
18686
17461
|
};
|
|
18687
|
-
|
|
17462
|
+
appendFileSync3(CLIENT_ERRORS_LOG, JSON.stringify(payload) + "\n", "utf-8");
|
|
18688
17463
|
} catch (err) {
|
|
18689
17464
|
console.error(`[client-error] append failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
18690
17465
|
}
|
|
@@ -18694,14 +17469,14 @@ app9.post("/", async (c) => {
|
|
|
18694
17469
|
var client_error_default = app9;
|
|
18695
17470
|
|
|
18696
17471
|
// server/routes/admin/session.ts
|
|
18697
|
-
import { readFileSync as
|
|
17472
|
+
import { readFileSync as readFileSync17, existsSync as existsSync17 } from "fs";
|
|
18698
17473
|
import { createHash as createHash2 } from "crypto";
|
|
18699
17474
|
function hashPin2(pin) {
|
|
18700
17475
|
return createHash2("sha256").update(pin).digest("hex");
|
|
18701
17476
|
}
|
|
18702
17477
|
function readUsersFile2() {
|
|
18703
|
-
if (!
|
|
18704
|
-
const raw2 =
|
|
17478
|
+
if (!existsSync17(USERS_FILE)) return null;
|
|
17479
|
+
const raw2 = readFileSync17(USERS_FILE, "utf-8").trim();
|
|
18705
17480
|
if (!raw2) return [];
|
|
18706
17481
|
return JSON.parse(raw2);
|
|
18707
17482
|
}
|
|
@@ -18831,11 +17606,11 @@ app10.post("/", async (c) => {
|
|
|
18831
17606
|
var session_default2 = app10;
|
|
18832
17607
|
|
|
18833
17608
|
// server/routes/admin/chat.ts
|
|
18834
|
-
import { resolve as
|
|
17609
|
+
import { resolve as resolve17 } from "path";
|
|
18835
17610
|
|
|
18836
17611
|
// app/lib/script-stream-tailer.ts
|
|
18837
17612
|
import * as childProcess from "child_process";
|
|
18838
|
-
import { appendFileSync as
|
|
17613
|
+
import { appendFileSync as appendFileSync4, createReadStream as createReadStream2, mkdirSync as mkdirSync11, statSync as statSync8 } from "fs";
|
|
18839
17614
|
import { dirname as dirname8 } from "path";
|
|
18840
17615
|
import { StringDecoder as StringDecoder2 } from "string_decoder";
|
|
18841
17616
|
var SCRIPT_STREAM_RE = /^\[([^\]]+)\] \[script:([a-z][a-z0-9-]*)((?::[a-z0-9:_-]+)?)\] (.*)$/;
|
|
@@ -18945,8 +17720,8 @@ function writeRouteMilestone(streamLogPath, scope, line) {
|
|
|
18945
17720
|
}
|
|
18946
17721
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
18947
17722
|
try {
|
|
18948
|
-
|
|
18949
|
-
|
|
17723
|
+
mkdirSync11(dirname8(streamLogPath), { recursive: true });
|
|
17724
|
+
appendFileSync4(streamLogPath, `[${ts}] [script:${scope}] ${line}
|
|
18950
17725
|
`);
|
|
18951
17726
|
} catch (err) {
|
|
18952
17727
|
console.error(
|
|
@@ -19276,7 +18051,7 @@ app11.post("/", requireAdminSession, async (c) => {
|
|
|
19276
18051
|
try {
|
|
19277
18052
|
registerAdminSSE(sseEntry);
|
|
19278
18053
|
if (sseConvId) {
|
|
19279
|
-
const streamLogPath =
|
|
18054
|
+
const streamLogPath = resolve17(account.accountDir, "logs", `claude-agent-stream-${sseConvId}.log`);
|
|
19280
18055
|
tailer = startScriptStreamTailer({
|
|
19281
18056
|
path: streamLogPath,
|
|
19282
18057
|
onEvent: (event) => {
|
|
@@ -19401,8 +18176,8 @@ app12.post("/", requireAdminSession, async (c) => {
|
|
|
19401
18176
|
var compact_default = app12;
|
|
19402
18177
|
|
|
19403
18178
|
// server/routes/admin/logs.ts
|
|
19404
|
-
import { existsSync as
|
|
19405
|
-
import { resolve as
|
|
18179
|
+
import { existsSync as existsSync18, readdirSync as readdirSync5, readFileSync as readFileSync18, statSync as statSync9 } from "fs";
|
|
18180
|
+
import { resolve as resolve18, basename as basename5 } from "path";
|
|
19406
18181
|
var TAIL_BYTES = 8192;
|
|
19407
18182
|
var app13 = new Hono2();
|
|
19408
18183
|
app13.get("/", async (c) => {
|
|
@@ -19411,16 +18186,16 @@ app13.get("/", async (c) => {
|
|
|
19411
18186
|
const conversationIdParam = c.req.query("conversationId");
|
|
19412
18187
|
const download = c.req.query("download") === "1";
|
|
19413
18188
|
const account = resolveAccount();
|
|
19414
|
-
const accountLogDir2 = account ?
|
|
18189
|
+
const accountLogDir2 = account ? resolve18(account.accountDir, "logs") : null;
|
|
19415
18190
|
if (fileParam) {
|
|
19416
18191
|
const safe = basename5(fileParam);
|
|
19417
18192
|
const searched = [];
|
|
19418
18193
|
for (const dir of [accountLogDir2, LOG_DIR]) {
|
|
19419
18194
|
if (!dir) continue;
|
|
19420
|
-
const filePath =
|
|
18195
|
+
const filePath = resolve18(dir, safe);
|
|
19421
18196
|
searched.push(filePath);
|
|
19422
18197
|
try {
|
|
19423
|
-
const content =
|
|
18198
|
+
const content = readFileSync18(filePath, "utf-8");
|
|
19424
18199
|
const headers = { "Content-Type": "text/plain; charset=utf-8" };
|
|
19425
18200
|
if (download) headers["Content-Disposition"] = `attachment; filename="${safe}"`;
|
|
19426
18201
|
return new Response(content, { headers });
|
|
@@ -19459,10 +18234,10 @@ app13.get("/", async (c) => {
|
|
|
19459
18234
|
const searched = [];
|
|
19460
18235
|
for (const dir of [accountLogDir2, LOG_DIR]) {
|
|
19461
18236
|
if (!dir) continue;
|
|
19462
|
-
const filePath =
|
|
18237
|
+
const filePath = resolve18(dir, fileName);
|
|
19463
18238
|
searched.push(filePath);
|
|
19464
18239
|
try {
|
|
19465
|
-
const content =
|
|
18240
|
+
const content = readFileSync18(filePath, "utf-8");
|
|
19466
18241
|
const headers = { "Content-Type": "text/plain; charset=utf-8" };
|
|
19467
18242
|
if (download) headers["Content-Disposition"] = `attachment; filename="${fileName}"`;
|
|
19468
18243
|
return new Response(content, { headers });
|
|
@@ -19477,7 +18252,7 @@ app13.get("/", async (c) => {
|
|
|
19477
18252
|
const seen = /* @__PURE__ */ new Set();
|
|
19478
18253
|
const logs = {};
|
|
19479
18254
|
for (const dir of [accountLogDir2, LOG_DIR]) {
|
|
19480
|
-
if (!dir || !
|
|
18255
|
+
if (!dir || !existsSync18(dir)) continue;
|
|
19481
18256
|
let files;
|
|
19482
18257
|
try {
|
|
19483
18258
|
files = readdirSync5(dir).filter((f) => f.endsWith(".log"));
|
|
@@ -19486,10 +18261,10 @@ app13.get("/", async (c) => {
|
|
|
19486
18261
|
console.warn(`[admin/logs] readdir-fail dir=${dir} reason=${reason}`);
|
|
19487
18262
|
continue;
|
|
19488
18263
|
}
|
|
19489
|
-
files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync9(
|
|
18264
|
+
files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync9(resolve18(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).forEach(({ name }) => {
|
|
19490
18265
|
seen.add(name);
|
|
19491
18266
|
try {
|
|
19492
|
-
const content =
|
|
18267
|
+
const content = readFileSync18(resolve18(dir, name));
|
|
19493
18268
|
const tail = content.length > TAIL_BYTES ? content.subarray(content.length - TAIL_BYTES).toString("utf-8") : content.toString("utf-8");
|
|
19494
18269
|
logs[name] = tail.trim() || "(empty)";
|
|
19495
18270
|
} catch (err) {
|
|
@@ -19529,8 +18304,8 @@ var claude_info_default = app14;
|
|
|
19529
18304
|
|
|
19530
18305
|
// server/routes/admin/attachment.ts
|
|
19531
18306
|
import { readFile as readFile3, readdir } from "fs/promises";
|
|
19532
|
-
import { existsSync as
|
|
19533
|
-
import { resolve as
|
|
18307
|
+
import { existsSync as existsSync19 } from "fs";
|
|
18308
|
+
import { resolve as resolve19 } from "path";
|
|
19534
18309
|
var app15 = new Hono2();
|
|
19535
18310
|
app15.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
19536
18311
|
const attachmentId = c.req.param("attachmentId");
|
|
@@ -19542,12 +18317,12 @@ app15.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
|
19542
18317
|
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(attachmentId)) {
|
|
19543
18318
|
return new Response("Not found", { status: 404 });
|
|
19544
18319
|
}
|
|
19545
|
-
const dir =
|
|
19546
|
-
if (!
|
|
18320
|
+
const dir = resolve19(ATTACHMENTS_ROOT, accountId, attachmentId);
|
|
18321
|
+
if (!existsSync19(dir)) {
|
|
19547
18322
|
return new Response("Not found", { status: 404 });
|
|
19548
18323
|
}
|
|
19549
|
-
const metaPath =
|
|
19550
|
-
if (!
|
|
18324
|
+
const metaPath = resolve19(dir, `${attachmentId}.meta.json`);
|
|
18325
|
+
if (!existsSync19(metaPath)) {
|
|
19551
18326
|
return new Response("Not found", { status: 404 });
|
|
19552
18327
|
}
|
|
19553
18328
|
let meta;
|
|
@@ -19561,7 +18336,7 @@ app15.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
|
19561
18336
|
if (!dataFile) {
|
|
19562
18337
|
return new Response("Not found", { status: 404 });
|
|
19563
18338
|
}
|
|
19564
|
-
const filePath =
|
|
18339
|
+
const filePath = resolve19(dir, dataFile);
|
|
19565
18340
|
const buffer = await readFile3(filePath);
|
|
19566
18341
|
return new Response(new Uint8Array(buffer), {
|
|
19567
18342
|
headers: {
|
|
@@ -19574,8 +18349,8 @@ app15.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
|
19574
18349
|
var attachment_default = app15;
|
|
19575
18350
|
|
|
19576
18351
|
// server/routes/admin/account.ts
|
|
19577
|
-
import { readFileSync as
|
|
19578
|
-
import { resolve as
|
|
18352
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync13 } from "fs";
|
|
18353
|
+
import { resolve as resolve20 } from "path";
|
|
19579
18354
|
var VALID_CONTEXT_MODES = ["managed", "claude-code"];
|
|
19580
18355
|
var app16 = new Hono2();
|
|
19581
18356
|
app16.patch("/", requireAdminSession, async (c) => {
|
|
@@ -19591,12 +18366,12 @@ app16.patch("/", requireAdminSession, async (c) => {
|
|
|
19591
18366
|
}
|
|
19592
18367
|
const account = resolveAccount();
|
|
19593
18368
|
if (!account) return c.json({ error: "No account configured" }, 500);
|
|
19594
|
-
const configPath2 =
|
|
18369
|
+
const configPath2 = resolve20(account.accountDir, "account.json");
|
|
19595
18370
|
try {
|
|
19596
|
-
const raw2 =
|
|
18371
|
+
const raw2 = readFileSync19(configPath2, "utf-8");
|
|
19597
18372
|
const config = JSON.parse(raw2);
|
|
19598
18373
|
config.contextMode = contextMode;
|
|
19599
|
-
|
|
18374
|
+
writeFileSync13(configPath2, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
19600
18375
|
console.error(`[account-update] contextMode=${contextMode}`);
|
|
19601
18376
|
return c.json({ ok: true, contextMode });
|
|
19602
18377
|
} catch (err) {
|
|
@@ -19607,24 +18382,24 @@ app16.patch("/", requireAdminSession, async (c) => {
|
|
|
19607
18382
|
var account_default = app16;
|
|
19608
18383
|
|
|
19609
18384
|
// server/routes/admin/agents.ts
|
|
19610
|
-
import { resolve as
|
|
19611
|
-
import { readdirSync as readdirSync6, readFileSync as
|
|
18385
|
+
import { resolve as resolve21 } from "path";
|
|
18386
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync20, existsSync as existsSync20, rmSync as rmSync3 } from "fs";
|
|
19612
18387
|
var app17 = new Hono2();
|
|
19613
18388
|
app17.get("/", (c) => {
|
|
19614
18389
|
const account = resolveAccount();
|
|
19615
18390
|
if (!account) return c.json({ agents: [] });
|
|
19616
|
-
const agentsDir =
|
|
19617
|
-
if (!
|
|
18391
|
+
const agentsDir = resolve21(account.accountDir, "agents");
|
|
18392
|
+
if (!existsSync20(agentsDir)) return c.json({ agents: [] });
|
|
19618
18393
|
const agents = [];
|
|
19619
18394
|
try {
|
|
19620
18395
|
const entries = readdirSync6(agentsDir, { withFileTypes: true });
|
|
19621
18396
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
19622
18397
|
if (!entry.isDirectory()) continue;
|
|
19623
18398
|
if (entry.name === "admin") continue;
|
|
19624
|
-
const configPath2 =
|
|
19625
|
-
if (!
|
|
18399
|
+
const configPath2 = resolve21(agentsDir, entry.name, "config.json");
|
|
18400
|
+
if (!existsSync20(configPath2)) continue;
|
|
19626
18401
|
try {
|
|
19627
|
-
const config = JSON.parse(
|
|
18402
|
+
const config = JSON.parse(readFileSync20(configPath2, "utf-8"));
|
|
19628
18403
|
agents.push({
|
|
19629
18404
|
slug: entry.name,
|
|
19630
18405
|
displayName: config.displayName ?? entry.name,
|
|
@@ -19650,8 +18425,8 @@ app17.delete("/:slug", (c) => {
|
|
|
19650
18425
|
if (slug.includes("/") || slug.includes("..") || slug.includes("\\")) {
|
|
19651
18426
|
return c.json({ error: "Invalid agent slug" }, 400);
|
|
19652
18427
|
}
|
|
19653
|
-
const agentDir =
|
|
19654
|
-
if (!
|
|
18428
|
+
const agentDir = resolve21(account.accountDir, "agents", slug);
|
|
18429
|
+
if (!existsSync20(agentDir)) {
|
|
19655
18430
|
return c.json({ error: "Agent not found" }, 404);
|
|
19656
18431
|
}
|
|
19657
18432
|
try {
|
|
@@ -19666,27 +18441,27 @@ app17.delete("/:slug", (c) => {
|
|
|
19666
18441
|
var agents_default = app17;
|
|
19667
18442
|
|
|
19668
18443
|
// server/routes/admin/version.ts
|
|
19669
|
-
import { existsSync as
|
|
19670
|
-
import { resolve as
|
|
19671
|
-
var
|
|
18444
|
+
import { existsSync as existsSync21, readFileSync as readFileSync21 } from "fs";
|
|
18445
|
+
import { resolve as resolve22, join as join10 } from "path";
|
|
18446
|
+
var PLATFORM_ROOT8 = process.env.MAXY_PLATFORM_ROOT ?? resolve22(process.cwd(), "..");
|
|
19672
18447
|
var brandHostname = "maxy";
|
|
19673
18448
|
var brandNpmPackage = "@rubytech/create-maxy";
|
|
19674
|
-
var brandJsonPath =
|
|
19675
|
-
if (
|
|
18449
|
+
var brandJsonPath = join10(PLATFORM_ROOT8, "config", "brand.json");
|
|
18450
|
+
if (existsSync21(brandJsonPath)) {
|
|
19676
18451
|
try {
|
|
19677
|
-
const brand = JSON.parse(
|
|
18452
|
+
const brand = JSON.parse(readFileSync21(brandJsonPath, "utf-8"));
|
|
19678
18453
|
if (brand.hostname) brandHostname = brand.hostname;
|
|
19679
18454
|
if (brand.npm?.packageName) brandNpmPackage = brand.npm.packageName;
|
|
19680
18455
|
} catch {
|
|
19681
18456
|
}
|
|
19682
18457
|
}
|
|
19683
|
-
var VERSION_FILE =
|
|
18458
|
+
var VERSION_FILE = resolve22(PLATFORM_ROOT8, `config/.${brandHostname}-version`);
|
|
19684
18459
|
var NPM_PACKAGE = brandNpmPackage;
|
|
19685
18460
|
var REGISTRY_URL = `https://registry.npmjs.org/${NPM_PACKAGE}/latest`;
|
|
19686
18461
|
var FETCH_TIMEOUT_MS = 5e3;
|
|
19687
18462
|
function readInstalled() {
|
|
19688
|
-
if (!
|
|
19689
|
-
const content =
|
|
18463
|
+
if (!existsSync21(VERSION_FILE)) return "unknown";
|
|
18464
|
+
const content = readFileSync21(VERSION_FILE, "utf-8").trim();
|
|
19690
18465
|
return content || "unknown";
|
|
19691
18466
|
}
|
|
19692
18467
|
async function fetchLatest() {
|
|
@@ -20179,9 +18954,9 @@ app23.post("/", async (c) => {
|
|
|
20179
18954
|
var events_default = app23;
|
|
20180
18955
|
|
|
20181
18956
|
// server/routes/admin/cloudflare.ts
|
|
20182
|
-
import { homedir as
|
|
20183
|
-
import { resolve as
|
|
20184
|
-
import { readFileSync as
|
|
18957
|
+
import { homedir as homedir3 } from "os";
|
|
18958
|
+
import { resolve as resolve24 } from "path";
|
|
18959
|
+
import { readFileSync as readFileSync23 } from "fs";
|
|
20185
18960
|
|
|
20186
18961
|
// app/lib/dns-label.ts
|
|
20187
18962
|
var VALID_LABEL = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
@@ -20197,14 +18972,14 @@ function isValidDomain(value) {
|
|
|
20197
18972
|
}
|
|
20198
18973
|
|
|
20199
18974
|
// app/lib/alias-domains.ts
|
|
20200
|
-
import { existsSync as
|
|
18975
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync12, readFileSync as readFileSync22, writeFileSync as writeFileSync14 } from "fs";
|
|
20201
18976
|
import { dirname as dirname9 } from "path";
|
|
20202
|
-
import { resolve as
|
|
20203
|
-
var ALIAS_DOMAINS_PATH =
|
|
18977
|
+
import { resolve as resolve23 } from "path";
|
|
18978
|
+
var ALIAS_DOMAINS_PATH = resolve23(MAXY_DIR, "alias-domains.json");
|
|
20204
18979
|
function readExisting() {
|
|
20205
|
-
if (!
|
|
18980
|
+
if (!existsSync22(ALIAS_DOMAINS_PATH)) return /* @__PURE__ */ new Set();
|
|
20206
18981
|
try {
|
|
20207
|
-
const parsed = JSON.parse(
|
|
18982
|
+
const parsed = JSON.parse(readFileSync22(ALIAS_DOMAINS_PATH, "utf-8"));
|
|
20208
18983
|
if (!Array.isArray(parsed)) return /* @__PURE__ */ new Set();
|
|
20209
18984
|
return new Set(parsed.filter((h) => typeof h === "string"));
|
|
20210
18985
|
} catch {
|
|
@@ -20215,18 +18990,18 @@ function addAliasDomain(hostname2) {
|
|
|
20215
18990
|
const existing = readExisting();
|
|
20216
18991
|
if (existing.has(hostname2)) return;
|
|
20217
18992
|
existing.add(hostname2);
|
|
20218
|
-
|
|
20219
|
-
|
|
18993
|
+
mkdirSync12(dirname9(ALIAS_DOMAINS_PATH), { recursive: true });
|
|
18994
|
+
writeFileSync14(ALIAS_DOMAINS_PATH, JSON.stringify([...existing], null, 2) + "\n", "utf-8");
|
|
20220
18995
|
}
|
|
20221
18996
|
|
|
20222
18997
|
// server/routes/admin/cloudflare.ts
|
|
20223
18998
|
var SETUP_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
20224
18999
|
var DOMAINS_TIMEOUT_MS = 40 * 1e3;
|
|
20225
19000
|
function loadBrandInfo() {
|
|
20226
|
-
const
|
|
20227
|
-
const brandPath =
|
|
19001
|
+
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? resolve24(process.cwd(), "..");
|
|
19002
|
+
const brandPath = resolve24(platformRoot2, "config", "brand.json");
|
|
20228
19003
|
try {
|
|
20229
|
-
const parsed = JSON.parse(
|
|
19004
|
+
const parsed = JSON.parse(readFileSync23(brandPath, "utf-8"));
|
|
20230
19005
|
const hostname2 = typeof parsed.hostname === "string" && parsed.hostname ? parsed.hostname : "maxy";
|
|
20231
19006
|
const configDir2 = typeof parsed.configDir === "string" && parsed.configDir ? parsed.configDir : ".maxy";
|
|
20232
19007
|
return { hostname: hostname2, configDir: configDir2 };
|
|
@@ -20329,7 +19104,7 @@ app24.get("/domains", requireAdminSession, async (c) => {
|
|
|
20329
19104
|
streamLogPath = streamLogPathFor(accountId, correlationId).streamLogPath;
|
|
20330
19105
|
log2(`phase=stream-log-resolved path=${streamLogPath}`);
|
|
20331
19106
|
const brand = loadBrandInfo();
|
|
20332
|
-
const scriptPath =
|
|
19107
|
+
const scriptPath = resolve24(homedir3(), "list-cf-domains.sh");
|
|
20333
19108
|
const result = await runFormSpawn({
|
|
20334
19109
|
scriptPath,
|
|
20335
19110
|
args: [brand.hostname],
|
|
@@ -20454,7 +19229,7 @@ app24.post("/setup", requireAdminSession, async (c) => {
|
|
|
20454
19229
|
}
|
|
20455
19230
|
streamLogPath = streamLogPathFor(accountId, correlationId).streamLogPath;
|
|
20456
19231
|
log2(`phase=stream-log-resolved path=${streamLogPath}`);
|
|
20457
|
-
const scriptPath =
|
|
19232
|
+
const scriptPath = resolve24(homedir3(), "setup-tunnel.sh");
|
|
20458
19233
|
const args = [brand.hostname, String(port2), adminFqdn];
|
|
20459
19234
|
if (publicFqdn) args.push(publicFqdn);
|
|
20460
19235
|
if (apex) args.push(apex);
|
|
@@ -20523,17 +19298,17 @@ var cloudflare_default = app24;
|
|
|
20523
19298
|
import { createReadStream as createReadStream3 } from "fs";
|
|
20524
19299
|
import { readdir as readdir2, readFile as readFile4, stat as stat4, mkdir as mkdir3, writeFile as writeFile4, unlink as unlink2 } from "fs/promises";
|
|
20525
19300
|
import { realpathSync as realpathSync4 } from "fs";
|
|
20526
|
-
import { basename as basename6, dirname as dirname10, join as
|
|
19301
|
+
import { basename as basename6, dirname as dirname10, join as join11, resolve as resolve26, sep as sep2 } from "path";
|
|
20527
19302
|
import { Readable as Readable3 } from "stream";
|
|
20528
19303
|
|
|
20529
19304
|
// app/lib/data-path.ts
|
|
20530
19305
|
import { realpathSync as realpathSync3 } from "fs";
|
|
20531
|
-
import { resolve as
|
|
20532
|
-
var
|
|
20533
|
-
var DATA_ROOT =
|
|
19306
|
+
import { resolve as resolve25, normalize, sep, relative } from "path";
|
|
19307
|
+
var PLATFORM_ROOT9 = process.env.MAXY_PLATFORM_ROOT ?? resolve25(process.cwd(), "../platform");
|
|
19308
|
+
var DATA_ROOT = resolve25(PLATFORM_ROOT9, "..", "data");
|
|
20534
19309
|
function resolveDataPath(raw2) {
|
|
20535
19310
|
const cleaned = normalize("/" + (raw2 ?? "").replace(/\\/g, "/")).replace(/^\/+/, "");
|
|
20536
|
-
const absolute =
|
|
19311
|
+
const absolute = resolve25(DATA_ROOT, cleaned);
|
|
20537
19312
|
let dataRootReal;
|
|
20538
19313
|
try {
|
|
20539
19314
|
dataRootReal = realpathSync3(DATA_ROOT);
|
|
@@ -20800,7 +19575,7 @@ async function cascadeDeleteDocument(params) {
|
|
|
20800
19575
|
var UUID_RE3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
20801
19576
|
async function readMeta(absDir, baseName) {
|
|
20802
19577
|
try {
|
|
20803
|
-
const raw2 = await readFile4(
|
|
19578
|
+
const raw2 = await readFile4(join11(absDir, `${baseName}.meta.json`), "utf8");
|
|
20804
19579
|
const parsed = JSON.parse(raw2);
|
|
20805
19580
|
if (typeof parsed?.filename === "string") {
|
|
20806
19581
|
return { filename: parsed.filename, mimeType: typeof parsed.mimeType === "string" ? parsed.mimeType : void 0 };
|
|
@@ -20811,7 +19586,7 @@ async function readMeta(absDir, baseName) {
|
|
|
20811
19586
|
}
|
|
20812
19587
|
async function readAccountNames() {
|
|
20813
19588
|
const map = /* @__PURE__ */ new Map();
|
|
20814
|
-
const accountsDir =
|
|
19589
|
+
const accountsDir = resolve26(DATA_ROOT, "accounts");
|
|
20815
19590
|
let names;
|
|
20816
19591
|
try {
|
|
20817
19592
|
names = await readdir2(accountsDir);
|
|
@@ -20820,7 +19595,7 @@ async function readAccountNames() {
|
|
|
20820
19595
|
}
|
|
20821
19596
|
for (const name of names) {
|
|
20822
19597
|
if (!UUID_RE3.test(name)) continue;
|
|
20823
|
-
const configPath2 =
|
|
19598
|
+
const configPath2 = resolve26(accountsDir, name, "account.json");
|
|
20824
19599
|
try {
|
|
20825
19600
|
const raw2 = await readFile4(configPath2, "utf8");
|
|
20826
19601
|
const parsed = JSON.parse(raw2);
|
|
@@ -20838,7 +19613,7 @@ async function readAccountNames() {
|
|
|
20838
19613
|
}
|
|
20839
19614
|
async function enrich(absolute, entry, accountNames) {
|
|
20840
19615
|
if (entry.kind === "directory" && UUID_RE3.test(entry.name)) {
|
|
20841
|
-
const meta = await readMeta(
|
|
19616
|
+
const meta = await readMeta(join11(absolute, entry.name), entry.name);
|
|
20842
19617
|
if (meta?.filename) {
|
|
20843
19618
|
entry.displayName = meta.filename;
|
|
20844
19619
|
entry.mimeType = meta.mimeType;
|
|
@@ -20897,7 +19672,7 @@ app25.get("/", requireAdminSession, async (c) => {
|
|
|
20897
19672
|
continue;
|
|
20898
19673
|
}
|
|
20899
19674
|
try {
|
|
20900
|
-
const entryPath =
|
|
19675
|
+
const entryPath = join11(absolute, name);
|
|
20901
19676
|
const s = await stat4(entryPath);
|
|
20902
19677
|
entries.push({
|
|
20903
19678
|
name,
|
|
@@ -21011,8 +19786,8 @@ app25.post("/upload", requireAdminSession, async (c) => {
|
|
|
21011
19786
|
}
|
|
21012
19787
|
const safeName = basename6(file.name).replace(/[\0/\\]/g, "_");
|
|
21013
19788
|
const finalName = `${Date.now()}-${safeName}`;
|
|
21014
|
-
const destDir =
|
|
21015
|
-
const destPath =
|
|
19789
|
+
const destDir = resolve26(DATA_ROOT, "uploads", accountId);
|
|
19790
|
+
const destPath = resolve26(destDir, finalName);
|
|
21016
19791
|
try {
|
|
21017
19792
|
await mkdir3(destDir, { recursive: true });
|
|
21018
19793
|
const dataRootReal = realpathSync4(DATA_ROOT);
|
|
@@ -21070,7 +19845,7 @@ app25.delete("/", requireAdminSession, async (c) => {
|
|
|
21070
19845
|
}
|
|
21071
19846
|
const dot = base.lastIndexOf(".");
|
|
21072
19847
|
const stem = dot === -1 ? base : base.slice(0, dot);
|
|
21073
|
-
const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ?
|
|
19848
|
+
const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ? join11(dirname10(absolute), `${stem}.meta.json`) : null;
|
|
21074
19849
|
await unlink2(absolute);
|
|
21075
19850
|
if (sidecarPath) {
|
|
21076
19851
|
try {
|
|
@@ -21216,8 +19991,7 @@ var FILTER_EXCLUDED_LABELS = Object.freeze(
|
|
|
21216
19991
|
"Message",
|
|
21217
19992
|
"UserMessage",
|
|
21218
19993
|
"AssistantMessage",
|
|
21219
|
-
"
|
|
21220
|
-
"PublicConversation",
|
|
19994
|
+
"Conversation",
|
|
21221
19995
|
"Section",
|
|
21222
19996
|
"ToolCall",
|
|
21223
19997
|
"WorkflowRun",
|
|
@@ -21947,15 +20721,15 @@ app34.route("/adherence", adherence_default);
|
|
|
21947
20721
|
var admin_default = app34;
|
|
21948
20722
|
|
|
21949
20723
|
// server/index.ts
|
|
21950
|
-
var
|
|
21951
|
-
var BRAND_JSON_PATH =
|
|
20724
|
+
var PLATFORM_ROOT10 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
20725
|
+
var BRAND_JSON_PATH = PLATFORM_ROOT10 ? join12(PLATFORM_ROOT10, "config", "brand.json") : "";
|
|
21952
20726
|
var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", domain: "getmaxy.com" };
|
|
21953
|
-
if (BRAND_JSON_PATH && !
|
|
20727
|
+
if (BRAND_JSON_PATH && !existsSync23(BRAND_JSON_PATH)) {
|
|
21954
20728
|
console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
|
|
21955
20729
|
}
|
|
21956
|
-
if (BRAND_JSON_PATH &&
|
|
20730
|
+
if (BRAND_JSON_PATH && existsSync23(BRAND_JSON_PATH)) {
|
|
21957
20731
|
try {
|
|
21958
|
-
const parsed = JSON.parse(
|
|
20732
|
+
const parsed = JSON.parse(readFileSync24(BRAND_JSON_PATH, "utf-8"));
|
|
21959
20733
|
BRAND = { ...BRAND, ...parsed };
|
|
21960
20734
|
} catch (err) {
|
|
21961
20735
|
console.error(`[brand] Failed to parse brand.json: ${err.message}`);
|
|
@@ -21974,11 +20748,11 @@ var brandLoginOpts = {
|
|
|
21974
20748
|
bodyFont: BRAND.defaultFonts?.body,
|
|
21975
20749
|
logoContainsName: !!BRAND.logoContainsName
|
|
21976
20750
|
};
|
|
21977
|
-
var ALIAS_DOMAINS_PATH2 =
|
|
20751
|
+
var ALIAS_DOMAINS_PATH2 = join12(homedir4(), BRAND.configDir, "alias-domains.json");
|
|
21978
20752
|
function loadAliasDomains() {
|
|
21979
20753
|
try {
|
|
21980
|
-
if (!
|
|
21981
|
-
const parsed = JSON.parse(
|
|
20754
|
+
if (!existsSync23(ALIAS_DOMAINS_PATH2)) return null;
|
|
20755
|
+
const parsed = JSON.parse(readFileSync24(ALIAS_DOMAINS_PATH2, "utf-8"));
|
|
21982
20756
|
if (!Array.isArray(parsed)) {
|
|
21983
20757
|
console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
|
|
21984
20758
|
return null;
|
|
@@ -22318,20 +21092,20 @@ app35.get("/agent-assets/:slug/:filename", (c) => {
|
|
|
22318
21092
|
console.error(`[agent-assets] no-account slug=${slug} file=${filename}`);
|
|
22319
21093
|
return c.text("Not found", 404);
|
|
22320
21094
|
}
|
|
22321
|
-
const filePath =
|
|
22322
|
-
const expectedDir =
|
|
21095
|
+
const filePath = resolve27(account.accountDir, "agents", slug, "assets", filename);
|
|
21096
|
+
const expectedDir = resolve27(account.accountDir, "agents", slug, "assets");
|
|
22323
21097
|
if (!filePath.startsWith(expectedDir + "/")) {
|
|
22324
21098
|
console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
|
|
22325
21099
|
return c.text("Forbidden", 403);
|
|
22326
21100
|
}
|
|
22327
|
-
if (!
|
|
21101
|
+
if (!existsSync23(filePath)) {
|
|
22328
21102
|
console.error(`[agent-assets] serve slug=${slug} file=${filename} status=404`);
|
|
22329
21103
|
return c.text("Not found", 404);
|
|
22330
21104
|
}
|
|
22331
21105
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
22332
21106
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
22333
21107
|
console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
|
|
22334
|
-
const body =
|
|
21108
|
+
const body = readFileSync24(filePath);
|
|
22335
21109
|
return c.body(body, 200, {
|
|
22336
21110
|
"Content-Type": contentType,
|
|
22337
21111
|
"Cache-Control": "public, max-age=3600"
|
|
@@ -22348,20 +21122,20 @@ app35.get("/generated/:filename", (c) => {
|
|
|
22348
21122
|
console.error(`[generated] serve file=${filename} status=404`);
|
|
22349
21123
|
return c.text("Not found", 404);
|
|
22350
21124
|
}
|
|
22351
|
-
const filePath =
|
|
22352
|
-
const expectedDir =
|
|
21125
|
+
const filePath = resolve27(account.accountDir, "generated", filename);
|
|
21126
|
+
const expectedDir = resolve27(account.accountDir, "generated");
|
|
22353
21127
|
if (!filePath.startsWith(expectedDir + "/")) {
|
|
22354
21128
|
console.error(`[generated] serve file=${filename} status=403`);
|
|
22355
21129
|
return c.text("Forbidden", 403);
|
|
22356
21130
|
}
|
|
22357
|
-
if (!
|
|
21131
|
+
if (!existsSync23(filePath)) {
|
|
22358
21132
|
console.error(`[generated] serve file=${filename} status=404`);
|
|
22359
21133
|
return c.text("Not found", 404);
|
|
22360
21134
|
}
|
|
22361
21135
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
22362
21136
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
22363
21137
|
console.log(`[generated] serve file=${filename} status=200`);
|
|
22364
|
-
const body =
|
|
21138
|
+
const body = readFileSync24(filePath);
|
|
22365
21139
|
return c.body(body, 200, {
|
|
22366
21140
|
"Content-Type": contentType,
|
|
22367
21141
|
"Cache-Control": "public, max-age=86400"
|
|
@@ -22370,9 +21144,9 @@ app35.get("/generated/:filename", (c) => {
|
|
|
22370
21144
|
var htmlCache = /* @__PURE__ */ new Map();
|
|
22371
21145
|
var brandLogoPath = "/brand/maxy-monochrome.png";
|
|
22372
21146
|
var brandIconPath = "/brand/maxy-monochrome.png";
|
|
22373
|
-
if (BRAND_JSON_PATH &&
|
|
21147
|
+
if (BRAND_JSON_PATH && existsSync23(BRAND_JSON_PATH)) {
|
|
22374
21148
|
try {
|
|
22375
|
-
const fullBrand = JSON.parse(
|
|
21149
|
+
const fullBrand = JSON.parse(readFileSync24(BRAND_JSON_PATH, "utf-8"));
|
|
22376
21150
|
if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
|
|
22377
21151
|
brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
|
|
22378
21152
|
} catch {
|
|
@@ -22388,10 +21162,10 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
|
|
|
22388
21162
|
})}</script>`;
|
|
22389
21163
|
function readInstalledVersion() {
|
|
22390
21164
|
try {
|
|
22391
|
-
if (!
|
|
22392
|
-
const versionFile =
|
|
22393
|
-
if (!
|
|
22394
|
-
const content =
|
|
21165
|
+
if (!PLATFORM_ROOT10) return "unknown";
|
|
21166
|
+
const versionFile = join12(PLATFORM_ROOT10, "config", `.${BRAND.hostname}-version`);
|
|
21167
|
+
if (!existsSync23(versionFile)) return "unknown";
|
|
21168
|
+
const content = readFileSync24(versionFile, "utf-8").trim();
|
|
22395
21169
|
return content || "unknown";
|
|
22396
21170
|
} catch {
|
|
22397
21171
|
return "unknown";
|
|
@@ -22432,9 +21206,9 @@ var clientErrorReporterScript = `<script>
|
|
|
22432
21206
|
function cachedHtml(file) {
|
|
22433
21207
|
let html = htmlCache.get(file);
|
|
22434
21208
|
if (!html) {
|
|
22435
|
-
html =
|
|
22436
|
-
html = html.replace("<title>Maxy</title>", `<title>${
|
|
22437
|
-
html = html.replace('href="/favicon.ico"', `href="${
|
|
21209
|
+
html = readFileSync24(resolve27(process.cwd(), "public", file), "utf-8");
|
|
21210
|
+
html = html.replace("<title>Maxy</title>", `<title>${escapeHtml(BRAND.productName)}</title>`);
|
|
21211
|
+
html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
|
|
22438
21212
|
const headInjection = file === "index.html" ? `${brandScript}
|
|
22439
21213
|
${versionScript}
|
|
22440
21214
|
${clientErrorReporterScript}
|
|
@@ -22447,26 +21221,26 @@ ${clientErrorReporterScript}
|
|
|
22447
21221
|
}
|
|
22448
21222
|
var brandedHtmlCache = /* @__PURE__ */ new Map();
|
|
22449
21223
|
function loadBrandingCache(agentSlug) {
|
|
22450
|
-
const configDir2 =
|
|
21224
|
+
const configDir2 = join12(homedir4(), BRAND.configDir);
|
|
22451
21225
|
try {
|
|
22452
|
-
const accountJsonPath =
|
|
22453
|
-
if (!
|
|
22454
|
-
const account = JSON.parse(
|
|
21226
|
+
const accountJsonPath = join12(configDir2, "account.json");
|
|
21227
|
+
if (!existsSync23(accountJsonPath)) return null;
|
|
21228
|
+
const account = JSON.parse(readFileSync24(accountJsonPath, "utf-8"));
|
|
22455
21229
|
const accountId = account.accountId;
|
|
22456
21230
|
if (!accountId) return null;
|
|
22457
|
-
const cachePath =
|
|
22458
|
-
if (!
|
|
22459
|
-
return JSON.parse(
|
|
21231
|
+
const cachePath = join12(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
|
|
21232
|
+
if (!existsSync23(cachePath)) return null;
|
|
21233
|
+
return JSON.parse(readFileSync24(cachePath, "utf-8"));
|
|
22460
21234
|
} catch {
|
|
22461
21235
|
return null;
|
|
22462
21236
|
}
|
|
22463
21237
|
}
|
|
22464
21238
|
function resolveDefaultSlug() {
|
|
22465
21239
|
try {
|
|
22466
|
-
const configDir2 =
|
|
22467
|
-
const accountJsonPath =
|
|
22468
|
-
if (!
|
|
22469
|
-
const account = JSON.parse(
|
|
21240
|
+
const configDir2 = join12(homedir4(), BRAND.configDir);
|
|
21241
|
+
const accountJsonPath = join12(configDir2, "account.json");
|
|
21242
|
+
if (!existsSync23(accountJsonPath)) return null;
|
|
21243
|
+
const account = JSON.parse(readFileSync24(accountJsonPath, "utf-8"));
|
|
22470
21244
|
return account.defaultAgent || null;
|
|
22471
21245
|
} catch {
|
|
22472
21246
|
return null;
|
|
@@ -22480,26 +21254,26 @@ function brandedPublicHtml(agentSlug) {
|
|
|
22480
21254
|
if (!branding) return baseHtml;
|
|
22481
21255
|
const brandHash = JSON.stringify(branding).length;
|
|
22482
21256
|
if (cached && cached.mtime === brandHash) return cached.html;
|
|
22483
|
-
const title =
|
|
22484
|
-
const description = branding.tagline ?
|
|
21257
|
+
const title = escapeHtml(branding.name);
|
|
21258
|
+
const description = branding.tagline ? escapeHtml(branding.tagline) : "";
|
|
22485
21259
|
const themeColor = branding.primaryColor || "";
|
|
22486
21260
|
const ogImage = branding.logoUrl || "";
|
|
22487
21261
|
const favicon = branding.faviconUrl || "";
|
|
22488
21262
|
const metaTags = [
|
|
22489
21263
|
` <title>${title}</title>`,
|
|
22490
|
-
favicon ? ` <link rel="icon" href="${
|
|
22491
|
-
themeColor ? ` <meta name="theme-color" content="${
|
|
21264
|
+
favicon ? ` <link rel="icon" href="${escapeHtml(favicon)}">` : ` <link rel="icon" href="${escapeHtml(brandFaviconPath)}">`,
|
|
21265
|
+
themeColor ? ` <meta name="theme-color" content="${escapeHtml(themeColor)}">` : "",
|
|
22492
21266
|
` <meta property="og:title" content="${title}">`,
|
|
22493
21267
|
description ? ` <meta property="og:description" content="${description}">` : "",
|
|
22494
|
-
ogImage ? ` <meta property="og:image" content="${
|
|
21268
|
+
ogImage ? ` <meta property="og:image" content="${escapeHtml(ogImage)}">` : "",
|
|
22495
21269
|
' <meta property="og:type" content="website">'
|
|
22496
21270
|
].filter(Boolean).join("\n");
|
|
22497
|
-
const html = baseHtml.replace(` <title>${
|
|
22498
|
-
<link rel="icon" href="${
|
|
21271
|
+
const html = baseHtml.replace(` <title>${escapeHtml(BRAND.productName)}</title>
|
|
21272
|
+
<link rel="icon" href="${escapeHtml(brandFaviconPath)}">`, metaTags);
|
|
22499
21273
|
brandedHtmlCache.set(agentSlug, { html, mtime: brandHash });
|
|
22500
21274
|
return html;
|
|
22501
21275
|
}
|
|
22502
|
-
function
|
|
21276
|
+
function escapeHtml(s) {
|
|
22503
21277
|
return s.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
22504
21278
|
}
|
|
22505
21279
|
app35.get("/", (c) => {
|
|
@@ -22539,11 +21313,11 @@ app35.use("/vnc-popout.html", logViewerFetch);
|
|
|
22539
21313
|
app35.get("/vnc-popout.html", (c) => {
|
|
22540
21314
|
let html = htmlCache.get("vnc-popout.html");
|
|
22541
21315
|
if (!html) {
|
|
22542
|
-
html =
|
|
22543
|
-
const name =
|
|
21316
|
+
html = readFileSync24(resolve27(process.cwd(), "public", "vnc-popout.html"), "utf-8");
|
|
21317
|
+
const name = escapeHtml(BRAND.productName);
|
|
22544
21318
|
html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
|
|
22545
21319
|
html = html.replace("</head>", ` ${brandScript}
|
|
22546
|
-
<link rel="icon" href="${
|
|
21320
|
+
<link rel="icon" href="${escapeHtml(brandFaviconPath)}">
|
|
22547
21321
|
</head>`);
|
|
22548
21322
|
htmlCache.set("vnc-popout.html", html);
|
|
22549
21323
|
}
|
|
@@ -22593,36 +21367,10 @@ app35.get("/:slug", async (c, next) => {
|
|
|
22593
21367
|
await next();
|
|
22594
21368
|
});
|
|
22595
21369
|
app35.use("/*", serveStatic({ root: "./public" }));
|
|
22596
|
-
var port = parseInt(process.env.PORT ?? "
|
|
22597
|
-
var hostname = process.env.HOSTNAME ?? "
|
|
21370
|
+
var port = parseInt(process.env.PORT ?? "19199", 10);
|
|
21371
|
+
var hostname = process.env.HOSTNAME ?? "127.0.0.1";
|
|
22598
21372
|
var httpServer = serve({ fetch: app35.fetch, port, hostname });
|
|
22599
|
-
attachVncWsProxy(httpServer, {
|
|
22600
|
-
isPublicHost,
|
|
22601
|
-
upstreamHost: "127.0.0.1",
|
|
22602
|
-
upstreamPort: 6080
|
|
22603
|
-
});
|
|
22604
|
-
attachTerminalWsProxy(httpServer, {
|
|
22605
|
-
isPublicHost,
|
|
22606
|
-
upstreamHost: "127.0.0.1",
|
|
22607
|
-
upstreamPort: 7681
|
|
22608
|
-
});
|
|
22609
21373
|
console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
22610
|
-
setTimeout(() => {
|
|
22611
|
-
const socket = createConnection5(7681, "127.0.0.1");
|
|
22612
|
-
socket.setTimeout(500);
|
|
22613
|
-
socket.once("connect", () => {
|
|
22614
|
-
socket.destroy();
|
|
22615
|
-
console.log("[ttyd] upstream ready on 127.0.0.1:7681");
|
|
22616
|
-
});
|
|
22617
|
-
socket.once("error", (err) => {
|
|
22618
|
-
socket.destroy();
|
|
22619
|
-
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})`);
|
|
22620
|
-
});
|
|
22621
|
-
socket.once("timeout", () => {
|
|
22622
|
-
socket.destroy();
|
|
22623
|
-
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'.");
|
|
22624
|
-
});
|
|
22625
|
-
}, 5e3);
|
|
22626
21374
|
var SUBAPP_MANIFEST = [
|
|
22627
21375
|
{ prefix: "/api/health", file: "server/routes/health.ts", subapp: health_default },
|
|
22628
21376
|
{ prefix: "/api/session", file: "server/routes/session.ts", subapp: session_default },
|
|
@@ -22655,8 +21403,8 @@ try {
|
|
|
22655
21403
|
(async () => {
|
|
22656
21404
|
try {
|
|
22657
21405
|
let userId = "";
|
|
22658
|
-
if (
|
|
22659
|
-
const users = JSON.parse(
|
|
21406
|
+
if (existsSync23(USERS_FILE)) {
|
|
21407
|
+
const users = JSON.parse(readFileSync24(USERS_FILE, "utf-8").trim() || "[]");
|
|
22660
21408
|
userId = users[0]?.userId ?? "";
|
|
22661
21409
|
}
|
|
22662
21410
|
await backfillNullUserIdConversations(userId);
|
|
@@ -22682,7 +21430,7 @@ if (bootAccountConfig?.whatsapp) {
|
|
|
22682
21430
|
}
|
|
22683
21431
|
init({
|
|
22684
21432
|
configDir: configDirForWhatsApp,
|
|
22685
|
-
platformRoot:
|
|
21433
|
+
platformRoot: resolve27(process.env.MAXY_PLATFORM_ROOT ?? join12(__dirname, "..")),
|
|
22686
21434
|
accountConfig: bootAccountConfig,
|
|
22687
21435
|
onMessage: async (msg) => {
|
|
22688
21436
|
try {
|