@rubytech/create-realagent 1.0.674 → 1.0.676
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/payload/server/server.js
CHANGED
|
@@ -1,28 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
1
|
+
import {
|
|
2
|
+
BIN_DIR,
|
|
3
|
+
CLAUDE_CREDENTIALS_FILE,
|
|
4
|
+
LOG_DIR,
|
|
5
|
+
MAXY_DIR,
|
|
6
|
+
TELEGRAM_ADMIN_WEBHOOK_SECRET_FILE,
|
|
7
|
+
TELEGRAM_WEBHOOK_SECRET_FILE,
|
|
8
|
+
USERS_FILE,
|
|
9
|
+
__commonJS,
|
|
10
|
+
__toESM,
|
|
11
|
+
canAccessAdmin,
|
|
12
|
+
checkRateLimit,
|
|
13
|
+
clearRateLimit,
|
|
14
|
+
createRemoteSession,
|
|
15
|
+
hashPassword,
|
|
16
|
+
invalidateRemoteSession,
|
|
17
|
+
isPasswordValid,
|
|
18
|
+
isRemoteAuthConfigured,
|
|
19
|
+
newCorrId,
|
|
20
|
+
recordFailedAttempt,
|
|
21
|
+
renderLoginPage,
|
|
22
|
+
resolveClientIp,
|
|
23
|
+
sanitizeClientCorrId,
|
|
24
|
+
setRemotePassword,
|
|
25
|
+
validatePasswordStrength,
|
|
26
|
+
verifyPassword,
|
|
27
|
+
verifyRemotePassword,
|
|
28
|
+
vncLog
|
|
29
|
+
} from "./chunk-5YIXIF6C.js";
|
|
26
30
|
|
|
27
31
|
// ../lib/models/dist/index.js
|
|
28
32
|
var require_dist = __commonJS({
|
|
@@ -2526,7 +2530,7 @@ var responseViaResponseObject = async (res, outgoing, options = {}) => {
|
|
|
2526
2530
|
});
|
|
2527
2531
|
if (!chunk) {
|
|
2528
2532
|
if (i === 1) {
|
|
2529
|
-
await new Promise((
|
|
2533
|
+
await new Promise((resolve29) => setTimeout(resolve29));
|
|
2530
2534
|
maxReadCount = 3;
|
|
2531
2535
|
continue;
|
|
2532
2536
|
}
|
|
@@ -2892,690 +2896,40 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
2892
2896
|
};
|
|
2893
2897
|
|
|
2894
2898
|
// server/index.ts
|
|
2895
|
-
import { readFileSync as
|
|
2896
|
-
import {
|
|
2897
|
-
import {
|
|
2898
|
-
import { homedir as homedir5 } from "os";
|
|
2899
|
-
|
|
2900
|
-
// app/lib/vnc-logger.ts
|
|
2901
|
-
import { appendFileSync, mkdirSync } from "fs";
|
|
2902
|
-
import { resolve as resolve2 } from "path";
|
|
2899
|
+
import { readFileSync as readFileSync24, existsSync as existsSync23, watchFile } from "fs";
|
|
2900
|
+
import { resolve as resolve28, join as join12, basename as basename7 } from "path";
|
|
2901
|
+
import { homedir as homedir4 } from "os";
|
|
2903
2902
|
|
|
2904
|
-
//
|
|
2905
|
-
import {
|
|
2906
|
-
import { resolve, join as join2 } from "path";
|
|
2907
|
-
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
2908
|
-
var configDirName = ".maxy";
|
|
2909
|
-
var platformRoot = process.env.MAXY_PLATFORM_ROOT;
|
|
2910
|
-
if (platformRoot) {
|
|
2911
|
-
const brandPath = join2(platformRoot, "config", "brand.json");
|
|
2912
|
-
if (existsSync2(brandPath)) {
|
|
2913
|
-
try {
|
|
2914
|
-
const brand = JSON.parse(readFileSync(brandPath, "utf-8"));
|
|
2915
|
-
if (brand.configDir) configDirName = brand.configDir;
|
|
2916
|
-
} catch {
|
|
2917
|
-
}
|
|
2918
|
-
}
|
|
2919
|
-
}
|
|
2920
|
-
var MAXY_DIR = resolve(homedir(), configDirName);
|
|
2921
|
-
var PLATFORM_ROOT = process.env.MAXY_PLATFORM_ROOT ?? resolve(process.cwd(), "..");
|
|
2922
|
-
var USERS_FILE = resolve(PLATFORM_ROOT, "config", "users.json");
|
|
2923
|
-
var LOG_DIR = resolve(MAXY_DIR, "logs");
|
|
2924
|
-
var BIN_DIR = resolve(MAXY_DIR, "bin");
|
|
2925
|
-
var REMOTE_PASSWORD_FILE = resolve(MAXY_DIR, ".remote-password");
|
|
2926
|
-
var TELEGRAM_WEBHOOK_SECRET_FILE = resolve(MAXY_DIR, ".telegram-webhook-secret");
|
|
2927
|
-
var TELEGRAM_ADMIN_WEBHOOK_SECRET_FILE = resolve(MAXY_DIR, ".telegram-admin-webhook-secret");
|
|
2928
|
-
var CLAUDE_CREDENTIALS_FILE = resolve(homedir(), ".claude", ".credentials.json");
|
|
2903
|
+
// server/ws-proxy-terminal.ts
|
|
2904
|
+
import { createConnection } from "net";
|
|
2929
2905
|
|
|
2930
|
-
// app/lib/
|
|
2931
|
-
|
|
2906
|
+
// app/lib/terminal-logger.ts
|
|
2907
|
+
import { appendFileSync, mkdirSync } from "fs";
|
|
2908
|
+
import { resolve } from "path";
|
|
2909
|
+
var TERMINAL_LOG_FILE = resolve(LOG_DIR, "terminal.log");
|
|
2932
2910
|
try {
|
|
2933
2911
|
mkdirSync(LOG_DIR, { recursive: true });
|
|
2934
2912
|
} catch (err) {
|
|
2935
|
-
console.error(`[
|
|
2913
|
+
console.error(`[terminal-log-fail] mkdir ${LOG_DIR} failed: ${err.message}`);
|
|
2936
2914
|
}
|
|
2937
|
-
function
|
|
2915
|
+
function terminalLog(phase, fields = {}) {
|
|
2938
2916
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
2939
2917
|
const kv = Object.entries(fields).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(" ");
|
|
2940
|
-
const line = kv.length > 0 ? `[${ts}] [
|
|
2941
|
-
` : `[${ts}] [
|
|
2918
|
+
const line = kv.length > 0 ? `[${ts}] [terminal-${phase}] ${kv}
|
|
2919
|
+
` : `[${ts}] [terminal-${phase}]
|
|
2942
2920
|
`;
|
|
2943
2921
|
try {
|
|
2944
|
-
appendFileSync(
|
|
2922
|
+
appendFileSync(TERMINAL_LOG_FILE, line);
|
|
2945
2923
|
} catch (err) {
|
|
2946
|
-
console.error(`[
|
|
2947
|
-
}
|
|
2948
|
-
}
|
|
2949
|
-
var corrCounter = 0;
|
|
2950
|
-
function newCorrId() {
|
|
2951
|
-
corrCounter = corrCounter + 1 & 4294967295;
|
|
2952
|
-
const counterHex = corrCounter.toString(16).padStart(8, "0");
|
|
2953
|
-
const randomHex = Math.floor(Math.random() * 16777215).toString(16).padStart(6, "0");
|
|
2954
|
-
return `${counterHex}-${randomHex}`;
|
|
2955
|
-
}
|
|
2956
|
-
function sanitizeClientCorrId(raw2) {
|
|
2957
|
-
if (!raw2) return null;
|
|
2958
|
-
if (raw2.length > 32) return null;
|
|
2959
|
-
if (!/^[A-Za-z0-9_-]+$/.test(raw2)) return null;
|
|
2960
|
-
return raw2;
|
|
2961
|
-
}
|
|
2962
|
-
|
|
2963
|
-
// app/lib/remote-auth.ts
|
|
2964
|
-
import { scrypt, randomBytes, timingSafeEqual } from "crypto";
|
|
2965
|
-
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
2966
|
-
|
|
2967
|
-
// app/lib/password-strength.ts
|
|
2968
|
-
function validatePasswordStrength(password) {
|
|
2969
|
-
return [
|
|
2970
|
-
{ key: "length", label: "At least 8 characters", met: password.length >= 8 },
|
|
2971
|
-
{ key: "number", label: "Contains a number", met: /\d/.test(password) },
|
|
2972
|
-
{ key: "special", label: "Contains a special character", met: /[^A-Za-z0-9]/.test(password) },
|
|
2973
|
-
{ key: "whitespace", label: "No spaces", met: password.length > 0 && !/\s/.test(password) }
|
|
2974
|
-
];
|
|
2975
|
-
}
|
|
2976
|
-
function isPasswordValid(password) {
|
|
2977
|
-
return validatePasswordStrength(password).every((r) => r.met);
|
|
2978
|
-
}
|
|
2979
|
-
|
|
2980
|
-
// app/lib/remote-auth.ts
|
|
2981
|
-
var SCRYPT_N = 16384;
|
|
2982
|
-
var SCRYPT_R = 8;
|
|
2983
|
-
var SCRYPT_P = 1;
|
|
2984
|
-
var SCRYPT_KEYLEN = 64;
|
|
2985
|
-
function scryptAsync(password, salt) {
|
|
2986
|
-
return new Promise((resolve31, reject) => {
|
|
2987
|
-
scrypt(password, salt, SCRYPT_KEYLEN, { N: SCRYPT_N, r: SCRYPT_R, p: SCRYPT_P }, (err, key) => {
|
|
2988
|
-
if (err) reject(err);
|
|
2989
|
-
else resolve31(key);
|
|
2990
|
-
});
|
|
2991
|
-
});
|
|
2992
|
-
}
|
|
2993
|
-
async function hashPassword(password) {
|
|
2994
|
-
const salt = randomBytes(32);
|
|
2995
|
-
const hash = await scryptAsync(password, salt);
|
|
2996
|
-
return `${salt.toString("hex")}:${hash.toString("hex")}`;
|
|
2997
|
-
}
|
|
2998
|
-
async function verifyPassword(password, stored) {
|
|
2999
|
-
const colonIndex = stored.indexOf(":");
|
|
3000
|
-
if (colonIndex === -1) return false;
|
|
3001
|
-
const saltHex = stored.slice(0, colonIndex);
|
|
3002
|
-
const hashHex = stored.slice(colonIndex + 1);
|
|
3003
|
-
if (!saltHex || !hashHex) return false;
|
|
3004
|
-
try {
|
|
3005
|
-
const salt = Buffer.from(saltHex, "hex");
|
|
3006
|
-
const storedHash = Buffer.from(hashHex, "hex");
|
|
3007
|
-
if (storedHash.length !== SCRYPT_KEYLEN) return false;
|
|
3008
|
-
const computed = await scryptAsync(password, salt);
|
|
3009
|
-
return timingSafeEqual(computed, storedHash);
|
|
3010
|
-
} catch {
|
|
3011
|
-
return false;
|
|
3012
|
-
}
|
|
3013
|
-
}
|
|
3014
|
-
function isRemoteAuthConfigured() {
|
|
3015
|
-
if (!existsSync3(REMOTE_PASSWORD_FILE)) return false;
|
|
3016
|
-
const content = readFileSync2(REMOTE_PASSWORD_FILE, "utf-8").trim();
|
|
3017
|
-
return content.length > 0 && content.includes(":");
|
|
3018
|
-
}
|
|
3019
|
-
function readPasswordHash() {
|
|
3020
|
-
try {
|
|
3021
|
-
if (!existsSync3(REMOTE_PASSWORD_FILE)) return null;
|
|
3022
|
-
const content = readFileSync2(REMOTE_PASSWORD_FILE, "utf-8").trim();
|
|
3023
|
-
if (!content || !content.includes(":")) return null;
|
|
3024
|
-
return content;
|
|
3025
|
-
} catch {
|
|
3026
|
-
return null;
|
|
3027
|
-
}
|
|
3028
|
-
}
|
|
3029
|
-
async function setRemotePassword(password) {
|
|
3030
|
-
const hash = await hashPassword(password);
|
|
3031
|
-
writeFileSync(REMOTE_PASSWORD_FILE, hash, "utf-8");
|
|
3032
|
-
}
|
|
3033
|
-
async function verifyRemotePassword(password) {
|
|
3034
|
-
const stored = readPasswordHash();
|
|
3035
|
-
if (!stored) return false;
|
|
3036
|
-
return verifyPassword(password, stored);
|
|
3037
|
-
}
|
|
3038
|
-
var SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
3039
|
-
var remoteSessions = /* @__PURE__ */ new Map();
|
|
3040
|
-
function pruneExpiredSessions() {
|
|
3041
|
-
const now = Date.now();
|
|
3042
|
-
for (const [token, session] of remoteSessions) {
|
|
3043
|
-
if (now - session.createdAt > SESSION_TTL_MS) {
|
|
3044
|
-
remoteSessions.delete(token);
|
|
3045
|
-
}
|
|
3046
|
-
}
|
|
3047
|
-
}
|
|
3048
|
-
function createRemoteSession() {
|
|
3049
|
-
const token = randomBytes(32).toString("hex");
|
|
3050
|
-
remoteSessions.set(token, { createdAt: Date.now() });
|
|
3051
|
-
return token;
|
|
3052
|
-
}
|
|
3053
|
-
function validateRemoteSession(token) {
|
|
3054
|
-
if (!token) return false;
|
|
3055
|
-
pruneExpiredSessions();
|
|
3056
|
-
const session = remoteSessions.get(token);
|
|
3057
|
-
if (!session) return false;
|
|
3058
|
-
if (Date.now() - session.createdAt > SESSION_TTL_MS) {
|
|
3059
|
-
remoteSessions.delete(token);
|
|
3060
|
-
return false;
|
|
3061
|
-
}
|
|
3062
|
-
return true;
|
|
3063
|
-
}
|
|
3064
|
-
function invalidateRemoteSession(token) {
|
|
3065
|
-
remoteSessions.delete(token);
|
|
3066
|
-
}
|
|
3067
|
-
var MAX_ATTEMPTS = 5;
|
|
3068
|
-
var LOCKOUT_MS = 15 * 60 * 1e3;
|
|
3069
|
-
var rateLimitMap = /* @__PURE__ */ new Map();
|
|
3070
|
-
function checkRateLimit(ip) {
|
|
3071
|
-
const rec = rateLimitMap.get(ip);
|
|
3072
|
-
if (!rec) return null;
|
|
3073
|
-
if (rec.lockedUntil > Date.now()) {
|
|
3074
|
-
const remaining = Math.ceil((rec.lockedUntil - Date.now()) / 1e3);
|
|
3075
|
-
return `Too many attempts. Try again in ${remaining}s`;
|
|
3076
|
-
}
|
|
3077
|
-
if (rec.lockedUntil > 0) {
|
|
3078
|
-
rateLimitMap.delete(ip);
|
|
3079
|
-
}
|
|
3080
|
-
return null;
|
|
3081
|
-
}
|
|
3082
|
-
function recordFailedAttempt(ip) {
|
|
3083
|
-
const rec = rateLimitMap.get(ip) ?? { count: 0, lockedUntil: 0 };
|
|
3084
|
-
rec.count++;
|
|
3085
|
-
if (rec.count >= MAX_ATTEMPTS) {
|
|
3086
|
-
rec.lockedUntil = Date.now() + LOCKOUT_MS;
|
|
3087
|
-
rec.count = 0;
|
|
3088
|
-
}
|
|
3089
|
-
rateLimitMap.set(ip, rec);
|
|
3090
|
-
}
|
|
3091
|
-
function clearRateLimit(ip) {
|
|
3092
|
-
rateLimitMap.delete(ip);
|
|
3093
|
-
}
|
|
3094
|
-
setInterval(() => {
|
|
3095
|
-
const now = Date.now();
|
|
3096
|
-
for (const [ip, rec] of rateLimitMap) {
|
|
3097
|
-
if (rec.lockedUntil > 0 && rec.lockedUntil <= now) {
|
|
3098
|
-
rateLimitMap.delete(ip);
|
|
3099
|
-
}
|
|
3100
|
-
}
|
|
3101
|
-
}, 15 * 60 * 1e3).unref();
|
|
3102
|
-
function normalizeIp(ip) {
|
|
3103
|
-
if (!ip) return void 0;
|
|
3104
|
-
const trimmed = ip.trim();
|
|
3105
|
-
if (trimmed.startsWith("::ffff:")) return trimmed.slice(7);
|
|
3106
|
-
return trimmed;
|
|
3107
|
-
}
|
|
3108
|
-
function isLoopback(ip) {
|
|
3109
|
-
return ip === "127.0.0.1" || ip.startsWith("127.") || ip === "::1";
|
|
3110
|
-
}
|
|
3111
|
-
function isPrivateIp(ip) {
|
|
3112
|
-
const parts = ip.split(".");
|
|
3113
|
-
if (parts.length !== 4) return false;
|
|
3114
|
-
const a = parseInt(parts[0], 10);
|
|
3115
|
-
const b = parseInt(parts[1], 10);
|
|
3116
|
-
if (Number.isNaN(a) || Number.isNaN(b)) return false;
|
|
3117
|
-
if (a === 10) return true;
|
|
3118
|
-
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
3119
|
-
if (a === 192 && b === 168) return true;
|
|
3120
|
-
if (a === 169 && b === 254) return true;
|
|
3121
|
-
return false;
|
|
3122
|
-
}
|
|
3123
|
-
function resolveClientIp(remoteAddress, xForwardedFor) {
|
|
3124
|
-
const remote = normalizeIp(remoteAddress);
|
|
3125
|
-
if (!remote) return void 0;
|
|
3126
|
-
if (isLoopback(remote) && xForwardedFor) {
|
|
3127
|
-
const firstIp = normalizeIp(xForwardedFor.split(",")[0]?.trim());
|
|
3128
|
-
if (firstIp) {
|
|
3129
|
-
if (isLoopback(firstIp) || isPrivateIp(firstIp)) return "loopback";
|
|
3130
|
-
return firstIp;
|
|
3131
|
-
}
|
|
3132
|
-
}
|
|
3133
|
-
return remote;
|
|
3134
|
-
}
|
|
3135
|
-
function isExternalIp(ip) {
|
|
3136
|
-
if (!ip) return false;
|
|
3137
|
-
const normalized = normalizeIp(ip);
|
|
3138
|
-
if (!normalized) return false;
|
|
3139
|
-
if (isLoopback(normalized)) return false;
|
|
3140
|
-
if (isPrivateIp(normalized)) return false;
|
|
3141
|
-
return true;
|
|
3142
|
-
}
|
|
3143
|
-
function renderLoginPage(opts) {
|
|
3144
|
-
const error = opts?.error ?? "";
|
|
3145
|
-
const lockout = opts?.lockoutSeconds ?? 0;
|
|
3146
|
-
const redirect = opts?.redirect ?? "/";
|
|
3147
|
-
const mode = opts?.mode ?? "login";
|
|
3148
|
-
const changeError = opts?.changeError ?? "";
|
|
3149
|
-
const success = opts?.success ?? "";
|
|
3150
|
-
const setupError = opts?.setupError ?? "";
|
|
3151
|
-
const primaryColor = opts?.primaryColor ?? "#7C8C72";
|
|
3152
|
-
const primaryHoverColor = opts?.primaryHoverColor ?? "#6A7A62";
|
|
3153
|
-
const primarySubtle = opts?.primarySubtle ?? "rgba(124,140,114,0.08)";
|
|
3154
|
-
const productName = opts?.productName ?? "Maxy";
|
|
3155
|
-
const logoPath = opts?.logoPath ?? "/brand/maxy-monochrome.png";
|
|
3156
|
-
const faviconPath = opts?.faviconPath ?? "/favicon.ico";
|
|
3157
|
-
const displayFont = opts?.displayFont ?? "'Cormorant', Georgia, serif";
|
|
3158
|
-
const bodyFont = opts?.bodyFont ?? "'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
|
|
3159
|
-
const logoContainsName = opts?.logoContainsName ?? false;
|
|
3160
|
-
const errorHtml = error ? `<p class="msg msg--error">${escapeHtml(error)}</p>` : "";
|
|
3161
|
-
const changeErrorHtml = changeError ? `<p class="msg msg--error">${escapeHtml(changeError)}</p>` : "";
|
|
3162
|
-
const successHtml = success ? `<p class="msg msg--success">${escapeHtml(success)}</p>` : "";
|
|
3163
|
-
const lockoutHtml = lockout > 0 ? `<p class="msg msg--error">Too many attempts. Try again in <span id="countdown">${lockout}</span>s</p>
|
|
3164
|
-
<script>
|
|
3165
|
-
(function() {
|
|
3166
|
-
var s = ${lockout};
|
|
3167
|
-
var el = document.getElementById('countdown');
|
|
3168
|
-
var iv = setInterval(function() { s--; el.textContent = s; if (s <= 0) { clearInterval(iv); location.reload(); } }, 1000);
|
|
3169
|
-
})();
|
|
3170
|
-
</script>` : "";
|
|
3171
|
-
const setupErrorHtml = setupError ? `<p class="msg msg--error">${escapeHtml(setupError)}</p>` : "";
|
|
3172
|
-
const formDisabled = lockout > 0 ? "disabled" : "";
|
|
3173
|
-
const loginDisplay = mode === "login" ? "block" : "none";
|
|
3174
|
-
const changeDisplay = mode === "change" ? "block" : "none";
|
|
3175
|
-
const setupDisplay = mode === "setup" ? "block" : "none";
|
|
3176
|
-
const successDisplay = mode === "success" ? "block" : "none";
|
|
3177
|
-
const subtitleText = mode === "setup" ? "Set your remote password" : "Remote access";
|
|
3178
|
-
const displayFontName = displayFont.match(/^'([^']+)'/)?.[1] ?? "";
|
|
3179
|
-
const bodyFontName = bodyFont.match(/^'([^']+)'/)?.[1] ?? "";
|
|
3180
|
-
const googleFontsLink = displayFontName || bodyFontName ? [
|
|
3181
|
-
'<link rel="preconnect" href="https://fonts.googleapis.com">',
|
|
3182
|
-
'<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>',
|
|
3183
|
-
'<link href="https://fonts.googleapis.com/css2?' + [
|
|
3184
|
-
displayFontName ? `family=${displayFontName.replace(/ /g, "+")}:wght@300;400` : "",
|
|
3185
|
-
bodyFontName ? `family=${bodyFontName.replace(/ /g, "+")}:wght@400;500` : ""
|
|
3186
|
-
].filter(Boolean).join("&") + '&display=swap" rel="stylesheet">'
|
|
3187
|
-
].join("\n ") : "";
|
|
3188
|
-
return `<!DOCTYPE html>
|
|
3189
|
-
<html lang="en">
|
|
3190
|
-
<head>
|
|
3191
|
-
<meta charset="utf-8">
|
|
3192
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
3193
|
-
<title>Sign in \u2014 ${escapeHtml(productName)}</title>
|
|
3194
|
-
<link rel="icon" href="${escapeHtml(faviconPath)}">
|
|
3195
|
-
${googleFontsLink}
|
|
3196
|
-
<style>
|
|
3197
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
3198
|
-
body {
|
|
3199
|
-
font-family: ${bodyFont};
|
|
3200
|
-
background: #FAFAF8;
|
|
3201
|
-
color: #1A1A1A;
|
|
3202
|
-
min-height: 100vh;
|
|
3203
|
-
display: flex;
|
|
3204
|
-
align-items: center;
|
|
3205
|
-
justify-content: center;
|
|
3206
|
-
}
|
|
3207
|
-
.page {
|
|
3208
|
-
display: flex;
|
|
3209
|
-
flex-direction: column;
|
|
3210
|
-
align-items: center;
|
|
3211
|
-
gap: 24px;
|
|
3212
|
-
max-width: 420px;
|
|
3213
|
-
width: 100%;
|
|
3214
|
-
padding: 40px 20px;
|
|
3215
|
-
}
|
|
3216
|
-
.logo-img {
|
|
3217
|
-
width: 110px;
|
|
3218
|
-
height: 110px;
|
|
3219
|
-
margin: -11px;
|
|
3220
|
-
object-fit: contain;
|
|
3221
|
-
}
|
|
3222
|
-
.title {
|
|
3223
|
-
font-family: ${displayFont};
|
|
3224
|
-
font-weight: 300;
|
|
3225
|
-
font-size: 28px;
|
|
3226
|
-
color: #1A1A1A;
|
|
3227
|
-
text-align: center;
|
|
3228
|
-
letter-spacing: -0.01em;
|
|
3229
|
-
line-height: 1.3;
|
|
3230
|
-
}
|
|
3231
|
-
.subtitle {
|
|
3232
|
-
font-size: 15px;
|
|
3233
|
-
color: #6B6B6B;
|
|
3234
|
-
text-align: center;
|
|
3235
|
-
margin-top: -8px;
|
|
3236
|
-
}
|
|
3237
|
-
.form-wrap {
|
|
3238
|
-
width: 100%;
|
|
3239
|
-
max-width: 300px;
|
|
3240
|
-
}
|
|
3241
|
-
.field { margin-bottom: 12px; }
|
|
3242
|
-
.field-label {
|
|
3243
|
-
display: block;
|
|
3244
|
-
font-size: 13px;
|
|
3245
|
-
font-weight: 500;
|
|
3246
|
-
color: #6B6B6B;
|
|
3247
|
-
margin-bottom: 6px;
|
|
3248
|
-
}
|
|
3249
|
-
.field-input {
|
|
3250
|
-
width: 100%;
|
|
3251
|
-
padding: 10px 12px;
|
|
3252
|
-
border: 1px solid rgba(0,0,0,0.1);
|
|
3253
|
-
border-radius: 8px;
|
|
3254
|
-
font-size: 15px;
|
|
3255
|
-
font-family: inherit;
|
|
3256
|
-
outline: none;
|
|
3257
|
-
background: #fff;
|
|
3258
|
-
transition: border-color 0.15s, box-shadow 0.15s;
|
|
3259
|
-
}
|
|
3260
|
-
.field-input:focus {
|
|
3261
|
-
border-color: ${primaryColor};
|
|
3262
|
-
box-shadow: 0 0 0 3px ${primarySubtle};
|
|
3263
|
-
}
|
|
3264
|
-
.options {
|
|
3265
|
-
display: flex;
|
|
3266
|
-
align-items: center;
|
|
3267
|
-
justify-content: space-between;
|
|
3268
|
-
gap: 12px;
|
|
3269
|
-
margin-top: 8px;
|
|
3270
|
-
}
|
|
3271
|
-
|
|
3272
|
-
/* Checkbox \u2014 matches Maxy shared checkbox (asterisk-in-square) */
|
|
3273
|
-
.check {
|
|
3274
|
-
position: relative;
|
|
3275
|
-
display: flex;
|
|
3276
|
-
align-items: center;
|
|
3277
|
-
gap: 8px;
|
|
3278
|
-
cursor: pointer;
|
|
3279
|
-
user-select: none;
|
|
3280
|
-
}
|
|
3281
|
-
.check input { position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none; }
|
|
3282
|
-
.check-box {
|
|
3283
|
-
width: 14px;
|
|
3284
|
-
height: 14px;
|
|
3285
|
-
border: 1px solid rgba(0,0,0,0.1);
|
|
3286
|
-
border-radius: 2px;
|
|
3287
|
-
display: flex;
|
|
3288
|
-
align-items: center;
|
|
3289
|
-
justify-content: center;
|
|
3290
|
-
font-size: 10px;
|
|
3291
|
-
color: transparent;
|
|
3292
|
-
transition: border-color 0.15s, color 0.15s;
|
|
3293
|
-
flex-shrink: 0;
|
|
3294
|
-
}
|
|
3295
|
-
.check input:checked + .check-box {
|
|
3296
|
-
border-color: ${primaryColor};
|
|
3297
|
-
color: ${primaryColor};
|
|
3298
|
-
}
|
|
3299
|
-
.check-label {
|
|
3300
|
-
font-size: 13px;
|
|
3301
|
-
color: #6B6B6B;
|
|
3302
|
-
}
|
|
3303
|
-
|
|
3304
|
-
/* Ghost button \u2014 matches Maxy ghost variant */
|
|
3305
|
-
.ghost {
|
|
3306
|
-
background: none;
|
|
3307
|
-
border: none;
|
|
3308
|
-
font-family: inherit;
|
|
3309
|
-
font-size: 13px;
|
|
3310
|
-
color: #6B6B6B;
|
|
3311
|
-
cursor: pointer;
|
|
3312
|
-
padding: 4px 0;
|
|
3313
|
-
transition: color 0.15s;
|
|
3314
|
-
}
|
|
3315
|
-
.ghost:hover { color: #1A1A1A; }
|
|
3316
|
-
|
|
3317
|
-
/* Primary button */
|
|
3318
|
-
.btn {
|
|
3319
|
-
width: 100%;
|
|
3320
|
-
padding: 12px;
|
|
3321
|
-
margin-top: 16px;
|
|
3322
|
-
background: ${primaryColor};
|
|
3323
|
-
color: #fff;
|
|
3324
|
-
border: none;
|
|
3325
|
-
border-radius: 10px;
|
|
3326
|
-
font-size: 15px;
|
|
3327
|
-
font-weight: 500;
|
|
3328
|
-
font-family: inherit;
|
|
3329
|
-
cursor: pointer;
|
|
3330
|
-
transition: background 0.15s;
|
|
3331
|
-
}
|
|
3332
|
-
.btn:hover:not(:disabled) { background: ${primaryHoverColor}; }
|
|
3333
|
-
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
3334
|
-
|
|
3335
|
-
/* Messages */
|
|
3336
|
-
.msg {
|
|
3337
|
-
font-size: 13px;
|
|
3338
|
-
text-align: center;
|
|
3339
|
-
margin-top: 8px;
|
|
3340
|
-
}
|
|
3341
|
-
.msg--error { color: #c44; }
|
|
3342
|
-
.msg--success { color: ${primaryColor}; }
|
|
3343
|
-
|
|
3344
|
-
/* Strength checklist (setup mode) */
|
|
3345
|
-
.strength-checklist { margin: 8px 0; }
|
|
3346
|
-
.strength-item {
|
|
3347
|
-
font-size: 13px;
|
|
3348
|
-
color: #6B6B6B;
|
|
3349
|
-
padding: 2px 0;
|
|
3350
|
-
transition: color 0.15s;
|
|
3351
|
-
}
|
|
3352
|
-
.strength-icon {
|
|
3353
|
-
display: inline-block;
|
|
3354
|
-
width: 16px;
|
|
3355
|
-
text-align: center;
|
|
3356
|
-
}
|
|
3357
|
-
|
|
3358
|
-
@media (max-width: 440px) {
|
|
3359
|
-
.page { padding: 24px 16px; }
|
|
3360
|
-
}
|
|
3361
|
-
</style>
|
|
3362
|
-
</head>
|
|
3363
|
-
<body>
|
|
3364
|
-
<div class="page">
|
|
3365
|
-
<img src="${escapeHtml(logoPath)}" alt="${escapeHtml(productName)}" class="logo-img">
|
|
3366
|
-
<h1 class="title"${logoContainsName ? ' style="display:none"' : ""}>${escapeHtml(productName)}</h1>
|
|
3367
|
-
<p class="subtitle">${escapeHtml(subtitleText)}</p>
|
|
3368
|
-
|
|
3369
|
-
${successHtml}
|
|
3370
|
-
|
|
3371
|
-
<!-- Login -->
|
|
3372
|
-
<div id="login-section" class="form-wrap" style="display:${loginDisplay}">
|
|
3373
|
-
<form method="POST" action="/__remote-auth/login">
|
|
3374
|
-
<input type="hidden" name="redirect" value="${escapeHtml(redirect)}">
|
|
3375
|
-
<div class="field">
|
|
3376
|
-
<label class="field-label" for="password">Password</label>
|
|
3377
|
-
<input class="field-input" type="password" id="password" name="password" required autofocus ${formDisabled}>
|
|
3378
|
-
</div>
|
|
3379
|
-
<div class="options">
|
|
3380
|
-
<label class="check">
|
|
3381
|
-
<input type="checkbox" id="show-login-pw">
|
|
3382
|
-
<span class="check-box">\u2731</span>
|
|
3383
|
-
<span class="check-label">Show password</span>
|
|
3384
|
-
</label>
|
|
3385
|
-
<button type="button" class="ghost" onclick="toggleMode('change')">Change password</button>
|
|
3386
|
-
</div>
|
|
3387
|
-
<button type="submit" class="btn" ${formDisabled}>Sign in</button>
|
|
3388
|
-
</form>
|
|
3389
|
-
${errorHtml}
|
|
3390
|
-
${lockoutHtml}
|
|
3391
|
-
</div>
|
|
3392
|
-
|
|
3393
|
-
<!-- Change password -->
|
|
3394
|
-
<div id="change-section" class="form-wrap" style="display:${changeDisplay}">
|
|
3395
|
-
<form method="POST" action="/__remote-auth/change-password">
|
|
3396
|
-
<input type="hidden" name="redirect" value="${escapeHtml(redirect)}">
|
|
3397
|
-
<div class="field">
|
|
3398
|
-
<label class="field-label" for="current_password">Current password</label>
|
|
3399
|
-
<input class="field-input" type="password" id="current_password" name="current_password" required ${formDisabled}>
|
|
3400
|
-
</div>
|
|
3401
|
-
<div class="field">
|
|
3402
|
-
<label class="field-label" for="new_password">New password</label>
|
|
3403
|
-
<input class="field-input" type="password" id="new_password" name="new_password" required ${formDisabled}>
|
|
3404
|
-
</div>
|
|
3405
|
-
<div class="field">
|
|
3406
|
-
<label class="field-label" for="confirm_password">Confirm new password</label>
|
|
3407
|
-
<input class="field-input" type="password" id="confirm_password" name="confirm_password" required ${formDisabled}>
|
|
3408
|
-
</div>
|
|
3409
|
-
<div class="options">
|
|
3410
|
-
<label class="check">
|
|
3411
|
-
<input type="checkbox" id="show-change-pw">
|
|
3412
|
-
<span class="check-box">\u2731</span>
|
|
3413
|
-
<span class="check-label">Show passwords</span>
|
|
3414
|
-
</label>
|
|
3415
|
-
<button type="button" class="ghost" onclick="toggleMode('login')">Back to sign in</button>
|
|
3416
|
-
</div>
|
|
3417
|
-
<button type="submit" class="btn" ${formDisabled}>Change password</button>
|
|
3418
|
-
</form>
|
|
3419
|
-
${changeErrorHtml}
|
|
3420
|
-
</div>
|
|
3421
|
-
|
|
3422
|
-
<!-- Setup (initial password) -->
|
|
3423
|
-
<div id="setup-section" class="form-wrap" style="display:${setupDisplay}">
|
|
3424
|
-
<form method="POST" action="/__remote-auth/set-initial-password">
|
|
3425
|
-
<div class="field">
|
|
3426
|
-
<label class="field-label" for="setup_password">Password</label>
|
|
3427
|
-
<input class="field-input" type="password" id="setup_password" name="password" required autofocus>
|
|
3428
|
-
</div>
|
|
3429
|
-
<div class="field">
|
|
3430
|
-
<label class="field-label" for="setup_confirm">Confirm password</label>
|
|
3431
|
-
<input class="field-input" type="password" id="setup_confirm" name="confirm_password" required>
|
|
3432
|
-
</div>
|
|
3433
|
-
<div id="strength-checklist" class="strength-checklist">
|
|
3434
|
-
<div class="strength-item" id="req-length"><span class="strength-icon">\u25CB</span> At least 8 characters</div>
|
|
3435
|
-
<div class="strength-item" id="req-number"><span class="strength-icon">\u25CB</span> Contains a number</div>
|
|
3436
|
-
<div class="strength-item" id="req-special"><span class="strength-icon">\u25CB</span> Contains a special character</div>
|
|
3437
|
-
<div class="strength-item" id="req-spaces"><span class="strength-icon">\u25CB</span> No spaces</div>
|
|
3438
|
-
</div>
|
|
3439
|
-
<div class="options">
|
|
3440
|
-
<label class="check">
|
|
3441
|
-
<input type="checkbox" id="show-setup-pw">
|
|
3442
|
-
<span class="check-box">\u2731</span>
|
|
3443
|
-
<span class="check-label">Show passwords</span>
|
|
3444
|
-
</label>
|
|
3445
|
-
</div>
|
|
3446
|
-
<button type="submit" class="btn" id="setup-submit" disabled>Set password</button>
|
|
3447
|
-
</form>
|
|
3448
|
-
${setupErrorHtml}
|
|
3449
|
-
</div>
|
|
3450
|
-
|
|
3451
|
-
<!-- Success (after initial password set) -->
|
|
3452
|
-
<div id="success-section" class="form-wrap" style="display:${successDisplay}">
|
|
3453
|
-
<p class="msg msg--success" style="font-size:15px;margin-bottom:4px;">\u2713 Password set</p>
|
|
3454
|
-
<p style="font-size:14px;color:#6B6B6B;text-align:center;">You can close this tab and return to the onboarding tab.</p>
|
|
3455
|
-
<script>
|
|
3456
|
-
(function() {
|
|
3457
|
-
try {
|
|
3458
|
-
var ch = new BroadcastChannel('platform-onboarding');
|
|
3459
|
-
setTimeout(function() {
|
|
3460
|
-
ch.postMessage({ type: 'remote-password-set' });
|
|
3461
|
-
ch.close();
|
|
3462
|
-
}, 2500);
|
|
3463
|
-
} catch(e) {}
|
|
3464
|
-
})();
|
|
3465
|
-
</script>
|
|
3466
|
-
</div>
|
|
3467
|
-
</div>
|
|
3468
|
-
|
|
3469
|
-
<script>
|
|
3470
|
-
/* Toggle between login and change-password modes */
|
|
3471
|
-
function toggleMode(mode) {
|
|
3472
|
-
document.getElementById('login-section').style.display = mode === 'login' ? 'block' : 'none';
|
|
3473
|
-
document.getElementById('change-section').style.display = mode === 'change' ? 'block' : 'none';
|
|
3474
|
-
/* Focus first input in the active section */
|
|
3475
|
-
var id = mode === 'login' ? 'password' : 'current_password';
|
|
3476
|
-
var el = document.getElementById(id);
|
|
3477
|
-
if (el) el.focus();
|
|
3478
|
-
}
|
|
3479
|
-
|
|
3480
|
-
/* Show/hide password toggles */
|
|
3481
|
-
function bindShowHide(checkboxId, inputIds) {
|
|
3482
|
-
var cb = document.getElementById(checkboxId);
|
|
3483
|
-
if (!cb) return;
|
|
3484
|
-
cb.addEventListener('change', function() {
|
|
3485
|
-
var type = cb.checked ? 'text' : 'password';
|
|
3486
|
-
inputIds.forEach(function(id) {
|
|
3487
|
-
var el = document.getElementById(id);
|
|
3488
|
-
if (el) el.type = type;
|
|
3489
|
-
});
|
|
3490
|
-
});
|
|
3491
|
-
}
|
|
3492
|
-
bindShowHide('show-login-pw', ['password']);
|
|
3493
|
-
bindShowHide('show-change-pw', ['current_password', 'new_password', 'confirm_password']);
|
|
3494
|
-
bindShowHide('show-setup-pw', ['setup_password', 'setup_confirm']);
|
|
3495
|
-
|
|
3496
|
-
/* Explicit Enter-to-submit for the login password field */
|
|
3497
|
-
var loginPw = document.getElementById('password');
|
|
3498
|
-
if (loginPw) {
|
|
3499
|
-
loginPw.addEventListener('keydown', function(e) {
|
|
3500
|
-
if (e.key === 'Enter') {
|
|
3501
|
-
e.preventDefault();
|
|
3502
|
-
this.closest('form').requestSubmit();
|
|
3503
|
-
}
|
|
3504
|
-
});
|
|
3505
|
-
}
|
|
3506
|
-
|
|
3507
|
-
/* Live password strength validation (setup mode) */
|
|
3508
|
-
(function() {
|
|
3509
|
-
var pw = document.getElementById('setup_password');
|
|
3510
|
-
var confirm = document.getElementById('setup_confirm');
|
|
3511
|
-
var btn = document.getElementById('setup-submit');
|
|
3512
|
-
if (!pw || !confirm || !btn) return;
|
|
3513
|
-
|
|
3514
|
-
var rules = [
|
|
3515
|
-
{ id: 'req-length', test: function(p) { return p.length >= 8; } },
|
|
3516
|
-
{ id: 'req-number', test: function(p) { return /\\d/.test(p); } },
|
|
3517
|
-
{ id: 'req-special', test: function(p) { return /[^A-Za-z0-9]/.test(p); } },
|
|
3518
|
-
{ id: 'req-spaces', test: function(p) { return p.length > 0 && !/\\s/.test(p); } }
|
|
3519
|
-
];
|
|
3520
|
-
|
|
3521
|
-
function check() {
|
|
3522
|
-
var p = pw.value;
|
|
3523
|
-
var allMet = true;
|
|
3524
|
-
rules.forEach(function(r) {
|
|
3525
|
-
var el = document.getElementById(r.id);
|
|
3526
|
-
var met = r.test(p);
|
|
3527
|
-
if (!met) allMet = false;
|
|
3528
|
-
el.querySelector('.strength-icon').textContent = met ? '\\u2713' : '\\u25CB';
|
|
3529
|
-
el.style.color = met ? '${primaryColor}' : '#6B6B6B';
|
|
3530
|
-
});
|
|
3531
|
-
var match = p.length > 0 && p === confirm.value;
|
|
3532
|
-
btn.disabled = !(allMet && match);
|
|
3533
|
-
}
|
|
3534
|
-
|
|
3535
|
-
pw.addEventListener('input', check);
|
|
3536
|
-
confirm.addEventListener('input', check);
|
|
3537
|
-
})();
|
|
3538
|
-
</script>
|
|
3539
|
-
</body>
|
|
3540
|
-
</html>`;
|
|
3541
|
-
}
|
|
3542
|
-
function escapeHtml(str) {
|
|
3543
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
3544
|
-
}
|
|
3545
|
-
|
|
3546
|
-
// app/lib/auth-gate.ts
|
|
3547
|
-
function canAccessAdmin(input) {
|
|
3548
|
-
if (input.isPublicHost(input.host)) {
|
|
3549
|
-
return { allow: false, reason: "public-host" };
|
|
3550
|
-
}
|
|
3551
|
-
const clientIp = resolveClientIp(input.remoteAddress, input.xForwardedFor);
|
|
3552
|
-
if (!isExternalIp(clientIp)) {
|
|
3553
|
-
return { allow: true };
|
|
3554
|
-
}
|
|
3555
|
-
if (!isRemoteAuthConfigured()) {
|
|
3556
|
-
return { allow: false, reason: "remote-unconfigured" };
|
|
3557
|
-
}
|
|
3558
|
-
const token = parseCookieValue(input.cookieHeader, "__remote_session");
|
|
3559
|
-
if (token && validateRemoteSession(token)) {
|
|
3560
|
-
return { allow: true };
|
|
3561
|
-
}
|
|
3562
|
-
return { allow: false, reason: "unauthorized" };
|
|
3563
|
-
}
|
|
3564
|
-
function parseCookieValue(cookieHeader, name) {
|
|
3565
|
-
if (!cookieHeader) return null;
|
|
3566
|
-
const match2 = cookieHeader.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
|
|
3567
|
-
if (!match2) return null;
|
|
3568
|
-
try {
|
|
3569
|
-
return decodeURIComponent(match2[1]);
|
|
3570
|
-
} catch {
|
|
3571
|
-
return null;
|
|
2924
|
+
console.error(`[terminal-log-fail] ${err.message} \u2014 dropped: ${line.slice(0, 300).trim()}`);
|
|
3572
2925
|
}
|
|
3573
2926
|
}
|
|
3574
2927
|
|
|
3575
|
-
// server/ws-proxy.ts
|
|
3576
|
-
|
|
3577
|
-
var WS_PATH = "/websockify";
|
|
2928
|
+
// server/ws-proxy-terminal.ts
|
|
2929
|
+
var WS_PATH = "/admin/terminal/ws";
|
|
3578
2930
|
var UPSTREAM_TIMEOUT_MS = 5e3;
|
|
2931
|
+
var FLOW_TICK_MS = 5e3;
|
|
2932
|
+
var FLOW_IDLE_EMIT_MS = 3e4;
|
|
3579
2933
|
var HOP_BY_HOP = /* @__PURE__ */ new Set([
|
|
3580
2934
|
"connection",
|
|
3581
2935
|
"keep-alive",
|
|
@@ -3586,9 +2940,9 @@ var HOP_BY_HOP = /* @__PURE__ */ new Set([
|
|
|
3586
2940
|
"transfer-encoding",
|
|
3587
2941
|
"upgrade"
|
|
3588
2942
|
]);
|
|
3589
|
-
function
|
|
2943
|
+
function attachTerminalWsProxy(server, opts) {
|
|
3590
2944
|
const upstreamHost = opts.upstreamHost ?? "127.0.0.1";
|
|
3591
|
-
const upstreamPort = opts.upstreamPort ??
|
|
2945
|
+
const upstreamPort = opts.upstreamPort ?? 7681;
|
|
3592
2946
|
server.on("upgrade", (req, clientSocket, head) => {
|
|
3593
2947
|
try {
|
|
3594
2948
|
handleUpgrade(req, clientSocket, head, {
|
|
@@ -3597,7 +2951,7 @@ function attachVncWsProxy(server, opts) {
|
|
|
3597
2951
|
upstreamPort
|
|
3598
2952
|
});
|
|
3599
2953
|
} catch (err) {
|
|
3600
|
-
|
|
2954
|
+
terminalLog("ws-upgrade", {
|
|
3601
2955
|
decision: "rejected",
|
|
3602
2956
|
reason: "handler-exception",
|
|
3603
2957
|
err: err.message
|
|
@@ -3630,7 +2984,7 @@ function handleUpgrade(req, clientSocket, head, opts) {
|
|
|
3630
2984
|
});
|
|
3631
2985
|
if (!decision.allow) {
|
|
3632
2986
|
const status = decision.reason === "public-host" ? 404 : 401;
|
|
3633
|
-
|
|
2987
|
+
terminalLog("ws-upgrade", {
|
|
3634
2988
|
corrId,
|
|
3635
2989
|
clientCorrId: clientCorrId ?? null,
|
|
3636
2990
|
decision: "rejected",
|
|
@@ -3645,7 +2999,7 @@ function handleUpgrade(req, clientSocket, head, opts) {
|
|
|
3645
2999
|
}
|
|
3646
3000
|
const originHost = parseOriginHost(originHeader);
|
|
3647
3001
|
if (!originHost) {
|
|
3648
|
-
|
|
3002
|
+
terminalLog("ws-upgrade", {
|
|
3649
3003
|
corrId,
|
|
3650
3004
|
clientCorrId: clientCorrId ?? null,
|
|
3651
3005
|
decision: "rejected",
|
|
@@ -3658,7 +3012,7 @@ function handleUpgrade(req, clientSocket, head, opts) {
|
|
|
3658
3012
|
return;
|
|
3659
3013
|
}
|
|
3660
3014
|
if (originHost !== hostHeader) {
|
|
3661
|
-
|
|
3015
|
+
terminalLog("ws-upgrade", {
|
|
3662
3016
|
corrId,
|
|
3663
3017
|
clientCorrId: clientCorrId ?? null,
|
|
3664
3018
|
decision: "rejected",
|
|
@@ -3671,7 +3025,7 @@ function handleUpgrade(req, clientSocket, head, opts) {
|
|
|
3671
3025
|
return;
|
|
3672
3026
|
}
|
|
3673
3027
|
if (opts.isPublicHost(originHost)) {
|
|
3674
|
-
|
|
3028
|
+
terminalLog("ws-upgrade", {
|
|
3675
3029
|
corrId,
|
|
3676
3030
|
clientCorrId: clientCorrId ?? null,
|
|
3677
3031
|
decision: "rejected",
|
|
@@ -3683,7 +3037,7 @@ function handleUpgrade(req, clientSocket, head, opts) {
|
|
|
3683
3037
|
writeStatusAndDestroy(clientSocket, 403, "Forbidden");
|
|
3684
3038
|
return;
|
|
3685
3039
|
}
|
|
3686
|
-
|
|
3040
|
+
terminalLog("ws-upgrade", {
|
|
3687
3041
|
corrId,
|
|
3688
3042
|
clientCorrId: clientCorrId ?? null,
|
|
3689
3043
|
decision: "accepted",
|
|
@@ -3700,287 +3054,6 @@ function handleUpgrade(req, clientSocket, head, opts) {
|
|
|
3700
3054
|
let bytesUpstreamToClient = 0;
|
|
3701
3055
|
let closedBy = null;
|
|
3702
3056
|
let proxyOpened = false;
|
|
3703
|
-
const finish = (side, reason) => {
|
|
3704
|
-
if (closedBy) return;
|
|
3705
|
-
closedBy = side;
|
|
3706
|
-
if (proxyOpened) {
|
|
3707
|
-
vncLog("proxy-close", {
|
|
3708
|
-
corrId,
|
|
3709
|
-
closedBy: side,
|
|
3710
|
-
reason,
|
|
3711
|
-
clientBytes: bytesClientToUpstream,
|
|
3712
|
-
upstreamBytes: bytesUpstreamToClient
|
|
3713
|
-
});
|
|
3714
|
-
}
|
|
3715
|
-
clientSocket.destroy();
|
|
3716
|
-
upstream.destroy();
|
|
3717
|
-
};
|
|
3718
|
-
upstream.once("connect", () => {
|
|
3719
|
-
upstream.setTimeout(0);
|
|
3720
|
-
proxyOpened = true;
|
|
3721
|
-
vncLog("proxy-open", {
|
|
3722
|
-
corrId,
|
|
3723
|
-
upstream: `${opts.upstreamHost}:${opts.upstreamPort}`
|
|
3724
|
-
});
|
|
3725
|
-
const lines = [];
|
|
3726
|
-
lines.push(`${req.method ?? "GET"} ${WS_PATH} HTTP/${req.httpVersion}`);
|
|
3727
|
-
lines.push(`host: ${opts.upstreamHost}:${opts.upstreamPort}`);
|
|
3728
|
-
for (const [name, value] of Object.entries(req.headers)) {
|
|
3729
|
-
if (name === "host") continue;
|
|
3730
|
-
if (HOP_BY_HOP.has(name)) continue;
|
|
3731
|
-
if (value == null) continue;
|
|
3732
|
-
if (Array.isArray(value)) {
|
|
3733
|
-
for (const v of value) lines.push(`${name}: ${v}`);
|
|
3734
|
-
} else {
|
|
3735
|
-
lines.push(`${name}: ${value}`);
|
|
3736
|
-
}
|
|
3737
|
-
}
|
|
3738
|
-
const upgradeHeader = headerString(req.headers.upgrade);
|
|
3739
|
-
const connectionHeader = headerString(req.headers.connection);
|
|
3740
|
-
if (upgradeHeader) lines.push(`upgrade: ${upgradeHeader}`);
|
|
3741
|
-
if (connectionHeader) lines.push(`connection: ${connectionHeader}`);
|
|
3742
|
-
upstream.write(lines.join("\r\n") + "\r\n\r\n");
|
|
3743
|
-
if (head && head.length > 0) upstream.write(head);
|
|
3744
|
-
clientSocket.on("data", (chunk) => {
|
|
3745
|
-
bytesClientToUpstream += chunk.length;
|
|
3746
|
-
});
|
|
3747
|
-
upstream.on("data", (chunk) => {
|
|
3748
|
-
bytesUpstreamToClient += chunk.length;
|
|
3749
|
-
});
|
|
3750
|
-
clientSocket.pipe(upstream);
|
|
3751
|
-
upstream.pipe(clientSocket);
|
|
3752
|
-
clientSocket.once("close", (hadError) => {
|
|
3753
|
-
finish("client", hadError ? "error" : "normal");
|
|
3754
|
-
});
|
|
3755
|
-
upstream.once("close", (hadError) => {
|
|
3756
|
-
finish("upstream", hadError ? "error" : "normal");
|
|
3757
|
-
});
|
|
3758
|
-
clientSocket.once("error", (err) => {
|
|
3759
|
-
vncLog("proxy-error", { corrId, side: "client", err: err.message });
|
|
3760
|
-
finish("client", "error");
|
|
3761
|
-
});
|
|
3762
|
-
upstream.once("error", (err) => {
|
|
3763
|
-
vncLog("proxy-error", { corrId, side: "upstream", err: err.message });
|
|
3764
|
-
finish("upstream", "error");
|
|
3765
|
-
});
|
|
3766
|
-
});
|
|
3767
|
-
upstream.once("timeout", () => {
|
|
3768
|
-
if (proxyOpened) return;
|
|
3769
|
-
vncLog("proxy-error", {
|
|
3770
|
-
corrId,
|
|
3771
|
-
side: "upstream-connect",
|
|
3772
|
-
err: "timeout",
|
|
3773
|
-
timeout_ms: UPSTREAM_TIMEOUT_MS
|
|
3774
|
-
});
|
|
3775
|
-
writeStatusAndDestroy(clientSocket, 504, "Gateway Timeout");
|
|
3776
|
-
upstream.destroy();
|
|
3777
|
-
});
|
|
3778
|
-
upstream.once("error", (err) => {
|
|
3779
|
-
if (proxyOpened) return;
|
|
3780
|
-
vncLog("proxy-error", {
|
|
3781
|
-
corrId,
|
|
3782
|
-
side: "upstream-connect",
|
|
3783
|
-
err: err.message
|
|
3784
|
-
});
|
|
3785
|
-
writeStatusAndDestroy(clientSocket, 502, "Bad Gateway");
|
|
3786
|
-
upstream.destroy();
|
|
3787
|
-
});
|
|
3788
|
-
}
|
|
3789
|
-
function parseQueryParam(query, key) {
|
|
3790
|
-
if (!query) return null;
|
|
3791
|
-
for (const pair of query.split("&")) {
|
|
3792
|
-
const eq = pair.indexOf("=");
|
|
3793
|
-
const k = eq === -1 ? pair : pair.slice(0, eq);
|
|
3794
|
-
if (k !== key) continue;
|
|
3795
|
-
const v = eq === -1 ? "" : pair.slice(eq + 1);
|
|
3796
|
-
try {
|
|
3797
|
-
return decodeURIComponent(v);
|
|
3798
|
-
} catch {
|
|
3799
|
-
return null;
|
|
3800
|
-
}
|
|
3801
|
-
}
|
|
3802
|
-
return null;
|
|
3803
|
-
}
|
|
3804
|
-
function headerString(value) {
|
|
3805
|
-
if (value == null) return void 0;
|
|
3806
|
-
return Array.isArray(value) ? value[0] : value;
|
|
3807
|
-
}
|
|
3808
|
-
function parseOriginHost(origin) {
|
|
3809
|
-
if (!origin) return null;
|
|
3810
|
-
try {
|
|
3811
|
-
return new URL(origin).hostname;
|
|
3812
|
-
} catch {
|
|
3813
|
-
return null;
|
|
3814
|
-
}
|
|
3815
|
-
}
|
|
3816
|
-
function writeStatusAndDestroy(socket, status, statusText) {
|
|
3817
|
-
try {
|
|
3818
|
-
socket.write(
|
|
3819
|
-
`HTTP/1.1 ${status} ${statusText}\r
|
|
3820
|
-
Connection: close\r
|
|
3821
|
-
Content-Length: 0\r
|
|
3822
|
-
\r
|
|
3823
|
-
`
|
|
3824
|
-
);
|
|
3825
|
-
} catch {
|
|
3826
|
-
}
|
|
3827
|
-
socket.destroy();
|
|
3828
|
-
}
|
|
3829
|
-
|
|
3830
|
-
// server/ws-proxy-terminal.ts
|
|
3831
|
-
import { createConnection as createConnection2 } from "net";
|
|
3832
|
-
|
|
3833
|
-
// app/lib/terminal-logger.ts
|
|
3834
|
-
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
3835
|
-
import { resolve as resolve3 } from "path";
|
|
3836
|
-
var TERMINAL_LOG_FILE = resolve3(LOG_DIR, "terminal.log");
|
|
3837
|
-
try {
|
|
3838
|
-
mkdirSync2(LOG_DIR, { recursive: true });
|
|
3839
|
-
} catch (err) {
|
|
3840
|
-
console.error(`[terminal-log-fail] mkdir ${LOG_DIR} failed: ${err.message}`);
|
|
3841
|
-
}
|
|
3842
|
-
function terminalLog(phase, fields = {}) {
|
|
3843
|
-
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
3844
|
-
const kv = Object.entries(fields).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(" ");
|
|
3845
|
-
const line = kv.length > 0 ? `[${ts}] [terminal-${phase}] ${kv}
|
|
3846
|
-
` : `[${ts}] [terminal-${phase}]
|
|
3847
|
-
`;
|
|
3848
|
-
try {
|
|
3849
|
-
appendFileSync2(TERMINAL_LOG_FILE, line);
|
|
3850
|
-
} catch (err) {
|
|
3851
|
-
console.error(`[terminal-log-fail] ${err.message} \u2014 dropped: ${line.slice(0, 300).trim()}`);
|
|
3852
|
-
}
|
|
3853
|
-
}
|
|
3854
|
-
|
|
3855
|
-
// server/ws-proxy-terminal.ts
|
|
3856
|
-
var WS_PATH2 = "/admin/terminal/ws";
|
|
3857
|
-
var UPSTREAM_TIMEOUT_MS2 = 5e3;
|
|
3858
|
-
var FLOW_TICK_MS = 5e3;
|
|
3859
|
-
var FLOW_IDLE_EMIT_MS = 3e4;
|
|
3860
|
-
var HOP_BY_HOP2 = /* @__PURE__ */ new Set([
|
|
3861
|
-
"connection",
|
|
3862
|
-
"keep-alive",
|
|
3863
|
-
"proxy-authenticate",
|
|
3864
|
-
"proxy-authorization",
|
|
3865
|
-
"te",
|
|
3866
|
-
"trailer",
|
|
3867
|
-
"transfer-encoding",
|
|
3868
|
-
"upgrade"
|
|
3869
|
-
]);
|
|
3870
|
-
function attachTerminalWsProxy(server, opts) {
|
|
3871
|
-
const upstreamHost = opts.upstreamHost ?? "127.0.0.1";
|
|
3872
|
-
const upstreamPort = opts.upstreamPort ?? 7681;
|
|
3873
|
-
server.on("upgrade", (req, clientSocket, head) => {
|
|
3874
|
-
try {
|
|
3875
|
-
handleUpgrade2(req, clientSocket, head, {
|
|
3876
|
-
isPublicHost: opts.isPublicHost,
|
|
3877
|
-
upstreamHost,
|
|
3878
|
-
upstreamPort
|
|
3879
|
-
});
|
|
3880
|
-
} catch (err) {
|
|
3881
|
-
terminalLog("ws-upgrade", {
|
|
3882
|
-
decision: "rejected",
|
|
3883
|
-
reason: "handler-exception",
|
|
3884
|
-
err: err.message
|
|
3885
|
-
});
|
|
3886
|
-
clientSocket.destroy();
|
|
3887
|
-
}
|
|
3888
|
-
});
|
|
3889
|
-
}
|
|
3890
|
-
function handleUpgrade2(req, clientSocket, head, opts) {
|
|
3891
|
-
const url = req.url ?? "";
|
|
3892
|
-
const qsIndex = url.indexOf("?");
|
|
3893
|
-
const pathname = qsIndex === -1 ? url : url.slice(0, qsIndex);
|
|
3894
|
-
if (pathname !== WS_PATH2) {
|
|
3895
|
-
return;
|
|
3896
|
-
}
|
|
3897
|
-
const corrId = newCorrId();
|
|
3898
|
-
const query = qsIndex === -1 ? "" : url.slice(qsIndex + 1);
|
|
3899
|
-
const rawClientCorrId = parseQueryParam2(query, "corrId");
|
|
3900
|
-
const clientCorrId = sanitizeClientCorrId(rawClientCorrId);
|
|
3901
|
-
const hostHeader = (req.headers.host ?? "").split(":")[0];
|
|
3902
|
-
const originHeader = headerString2(req.headers.origin);
|
|
3903
|
-
const remote = req.socket.remoteAddress;
|
|
3904
|
-
const xff = headerString2(req.headers["x-forwarded-for"]);
|
|
3905
|
-
const decision = canAccessAdmin({
|
|
3906
|
-
host: hostHeader,
|
|
3907
|
-
remoteAddress: remote,
|
|
3908
|
-
xForwardedFor: xff,
|
|
3909
|
-
cookieHeader: headerString2(req.headers.cookie),
|
|
3910
|
-
isPublicHost: opts.isPublicHost
|
|
3911
|
-
});
|
|
3912
|
-
if (!decision.allow) {
|
|
3913
|
-
const status = decision.reason === "public-host" ? 404 : 401;
|
|
3914
|
-
terminalLog("ws-upgrade", {
|
|
3915
|
-
corrId,
|
|
3916
|
-
clientCorrId: clientCorrId ?? null,
|
|
3917
|
-
decision: "rejected",
|
|
3918
|
-
reason: decision.reason,
|
|
3919
|
-
ip: remote,
|
|
3920
|
-
xff: xff ?? null,
|
|
3921
|
-
origin: originHeader ?? null,
|
|
3922
|
-
host: hostHeader
|
|
3923
|
-
});
|
|
3924
|
-
writeStatusAndDestroy2(clientSocket, status, decision.reason === "public-host" ? "Not Found" : "Unauthorized");
|
|
3925
|
-
return;
|
|
3926
|
-
}
|
|
3927
|
-
const originHost = parseOriginHost2(originHeader);
|
|
3928
|
-
if (!originHost) {
|
|
3929
|
-
terminalLog("ws-upgrade", {
|
|
3930
|
-
corrId,
|
|
3931
|
-
clientCorrId: clientCorrId ?? null,
|
|
3932
|
-
decision: "rejected",
|
|
3933
|
-
reason: "origin-missing-or-invalid",
|
|
3934
|
-
origin: originHeader ?? null,
|
|
3935
|
-
host: hostHeader,
|
|
3936
|
-
ip: remote
|
|
3937
|
-
});
|
|
3938
|
-
writeStatusAndDestroy2(clientSocket, 403, "Forbidden");
|
|
3939
|
-
return;
|
|
3940
|
-
}
|
|
3941
|
-
if (originHost !== hostHeader) {
|
|
3942
|
-
terminalLog("ws-upgrade", {
|
|
3943
|
-
corrId,
|
|
3944
|
-
clientCorrId: clientCorrId ?? null,
|
|
3945
|
-
decision: "rejected",
|
|
3946
|
-
reason: "origin-mismatch",
|
|
3947
|
-
origin_host: originHost,
|
|
3948
|
-
host: hostHeader,
|
|
3949
|
-
ip: remote
|
|
3950
|
-
});
|
|
3951
|
-
writeStatusAndDestroy2(clientSocket, 403, "Forbidden");
|
|
3952
|
-
return;
|
|
3953
|
-
}
|
|
3954
|
-
if (opts.isPublicHost(originHost)) {
|
|
3955
|
-
terminalLog("ws-upgrade", {
|
|
3956
|
-
corrId,
|
|
3957
|
-
clientCorrId: clientCorrId ?? null,
|
|
3958
|
-
decision: "rejected",
|
|
3959
|
-
reason: "origin-public-host",
|
|
3960
|
-
origin_host: originHost,
|
|
3961
|
-
host: hostHeader,
|
|
3962
|
-
ip: remote
|
|
3963
|
-
});
|
|
3964
|
-
writeStatusAndDestroy2(clientSocket, 403, "Forbidden");
|
|
3965
|
-
return;
|
|
3966
|
-
}
|
|
3967
|
-
terminalLog("ws-upgrade", {
|
|
3968
|
-
corrId,
|
|
3969
|
-
clientCorrId: clientCorrId ?? null,
|
|
3970
|
-
decision: "accepted",
|
|
3971
|
-
ip: remote,
|
|
3972
|
-
xff: xff ?? null,
|
|
3973
|
-
origin: originHeader ?? null,
|
|
3974
|
-
host: hostHeader,
|
|
3975
|
-
sec_ws_version: headerString2(req.headers["sec-websocket-version"]) ?? null,
|
|
3976
|
-
sec_ws_protocol: headerString2(req.headers["sec-websocket-protocol"]) ?? null
|
|
3977
|
-
});
|
|
3978
|
-
const upstream = createConnection2({ host: opts.upstreamHost, port: opts.upstreamPort });
|
|
3979
|
-
upstream.setTimeout(UPSTREAM_TIMEOUT_MS2);
|
|
3980
|
-
let bytesClientToUpstream = 0;
|
|
3981
|
-
let bytesUpstreamToClient = 0;
|
|
3982
|
-
let closedBy = null;
|
|
3983
|
-
let proxyOpened = false;
|
|
3984
3057
|
let lastUpstreamByteTs = 0;
|
|
3985
3058
|
let flowInterval = null;
|
|
3986
3059
|
let lastEmittedAt = 0;
|
|
@@ -4012,11 +3085,11 @@ function handleUpgrade2(req, clientSocket, head, opts) {
|
|
|
4012
3085
|
upstream: `${opts.upstreamHost}:${opts.upstreamPort}`
|
|
4013
3086
|
});
|
|
4014
3087
|
const lines = [];
|
|
4015
|
-
lines.push(`${req.method ?? "GET"} ${
|
|
3088
|
+
lines.push(`${req.method ?? "GET"} ${WS_PATH} HTTP/${req.httpVersion}`);
|
|
4016
3089
|
lines.push(`host: ${opts.upstreamHost}:${opts.upstreamPort}`);
|
|
4017
3090
|
for (const [name, value] of Object.entries(req.headers)) {
|
|
4018
3091
|
if (name === "host") continue;
|
|
4019
|
-
if (
|
|
3092
|
+
if (HOP_BY_HOP.has(name)) continue;
|
|
4020
3093
|
if (value == null) continue;
|
|
4021
3094
|
if (Array.isArray(value)) {
|
|
4022
3095
|
for (const v of value) lines.push(`${name}: ${v}`);
|
|
@@ -4024,8 +3097,8 @@ function handleUpgrade2(req, clientSocket, head, opts) {
|
|
|
4024
3097
|
lines.push(`${name}: ${value}`);
|
|
4025
3098
|
}
|
|
4026
3099
|
}
|
|
4027
|
-
const upgradeHeader =
|
|
4028
|
-
const connectionHeader =
|
|
3100
|
+
const upgradeHeader = headerString(req.headers.upgrade);
|
|
3101
|
+
const connectionHeader = headerString(req.headers.connection);
|
|
4029
3102
|
if (upgradeHeader) lines.push(`upgrade: ${upgradeHeader}`);
|
|
4030
3103
|
if (connectionHeader) lines.push(`connection: ${connectionHeader}`);
|
|
4031
3104
|
upstream.write(lines.join("\r\n") + "\r\n\r\n");
|
|
@@ -4078,9 +3151,9 @@ function handleUpgrade2(req, clientSocket, head, opts) {
|
|
|
4078
3151
|
corrId,
|
|
4079
3152
|
side: "upstream-connect",
|
|
4080
3153
|
err: "timeout",
|
|
4081
|
-
timeout_ms:
|
|
3154
|
+
timeout_ms: UPSTREAM_TIMEOUT_MS
|
|
4082
3155
|
});
|
|
4083
|
-
|
|
3156
|
+
writeStatusAndDestroy(clientSocket, 504, "Gateway Timeout");
|
|
4084
3157
|
upstream.destroy();
|
|
4085
3158
|
});
|
|
4086
3159
|
upstream.once("error", (err) => {
|
|
@@ -4090,11 +3163,11 @@ function handleUpgrade2(req, clientSocket, head, opts) {
|
|
|
4090
3163
|
side: "upstream-connect",
|
|
4091
3164
|
err: err.message
|
|
4092
3165
|
});
|
|
4093
|
-
|
|
3166
|
+
writeStatusAndDestroy(clientSocket, 502, "Bad Gateway");
|
|
4094
3167
|
upstream.destroy();
|
|
4095
3168
|
});
|
|
4096
3169
|
}
|
|
4097
|
-
function
|
|
3170
|
+
function parseQueryParam(query, key) {
|
|
4098
3171
|
if (!query) return null;
|
|
4099
3172
|
for (const pair of query.split("&")) {
|
|
4100
3173
|
const eq = pair.indexOf("=");
|
|
@@ -4109,11 +3182,11 @@ function parseQueryParam2(query, key) {
|
|
|
4109
3182
|
}
|
|
4110
3183
|
return null;
|
|
4111
3184
|
}
|
|
4112
|
-
function
|
|
3185
|
+
function headerString(value) {
|
|
4113
3186
|
if (value == null) return void 0;
|
|
4114
3187
|
return Array.isArray(value) ? value[0] : value;
|
|
4115
3188
|
}
|
|
4116
|
-
function
|
|
3189
|
+
function parseOriginHost(origin) {
|
|
4117
3190
|
if (!origin) return null;
|
|
4118
3191
|
try {
|
|
4119
3192
|
return new URL(origin).hostname;
|
|
@@ -4121,7 +3194,7 @@ function parseOriginHost2(origin) {
|
|
|
4121
3194
|
return null;
|
|
4122
3195
|
}
|
|
4123
3196
|
}
|
|
4124
|
-
function
|
|
3197
|
+
function writeStatusAndDestroy(socket, status, statusText) {
|
|
4125
3198
|
try {
|
|
4126
3199
|
socket.write(
|
|
4127
3200
|
`HTTP/1.1 ${status} ${statusText}\r
|
|
@@ -4142,9 +3215,9 @@ var AGENT_SLUG_PATTERN = /^\/([a-z][a-z0-9-]{2,49})$/;
|
|
|
4142
3215
|
import Anthropic3 from "@anthropic-ai/sdk";
|
|
4143
3216
|
import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
|
|
4144
3217
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
4145
|
-
import { resolve as
|
|
3218
|
+
import { resolve as resolve6, join as join3 } from "path";
|
|
4146
3219
|
import { platform as osPlatform } from "os";
|
|
4147
|
-
import { readFileSync as
|
|
3220
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, readdirSync as readdirSync2, existsSync as existsSync5, mkdirSync as mkdirSync5, createWriteStream, statSync as statSync3, unlinkSync as unlinkSync3, cpSync, rmSync as rmSync2, appendFileSync as appendFileSync2, openSync as openSync2, readSync as readSync2, closeSync as closeSync2 } from "fs";
|
|
4148
3221
|
import { lookup as dnsLookup } from "dns/promises";
|
|
4149
3222
|
import { createConnection as netConnect } from "net";
|
|
4150
3223
|
import { StringDecoder } from "string_decoder";
|
|
@@ -4152,34 +3225,34 @@ import { StringDecoder } from "string_decoder";
|
|
|
4152
3225
|
// ../lib/anthropic-key/src/index.ts
|
|
4153
3226
|
var import_dist = __toESM(require_dist());
|
|
4154
3227
|
import {
|
|
4155
|
-
existsSync as
|
|
4156
|
-
mkdirSync as
|
|
4157
|
-
readFileSync
|
|
3228
|
+
existsSync as existsSync2,
|
|
3229
|
+
mkdirSync as mkdirSync2,
|
|
3230
|
+
readFileSync,
|
|
4158
3231
|
unlinkSync,
|
|
4159
|
-
writeFileSync
|
|
3232
|
+
writeFileSync
|
|
4160
3233
|
} from "fs";
|
|
4161
|
-
import { resolve as
|
|
4162
|
-
import { homedir
|
|
3234
|
+
import { resolve as resolve2, join as join2, dirname } from "path";
|
|
3235
|
+
import { homedir } from "os";
|
|
4163
3236
|
var cachedKeyFilePath = null;
|
|
4164
3237
|
function resolveKeyFilePath() {
|
|
4165
3238
|
if (cachedKeyFilePath) return cachedKeyFilePath;
|
|
4166
|
-
let
|
|
4167
|
-
const
|
|
4168
|
-
if (
|
|
4169
|
-
const brandPath =
|
|
4170
|
-
if (!
|
|
3239
|
+
let configDirName = ".maxy";
|
|
3240
|
+
const platformRoot2 = process.env.PLATFORM_ROOT ?? process.env.MAXY_PLATFORM_ROOT;
|
|
3241
|
+
if (platformRoot2) {
|
|
3242
|
+
const brandPath = join2(platformRoot2, "config", "brand.json");
|
|
3243
|
+
if (!existsSync2(brandPath)) {
|
|
4171
3244
|
throw new Error(
|
|
4172
3245
|
`brand.json not found at ${brandPath} \u2014 platform not properly installed`
|
|
4173
3246
|
);
|
|
4174
3247
|
}
|
|
4175
3248
|
try {
|
|
4176
|
-
const brand = JSON.parse(
|
|
3249
|
+
const brand = JSON.parse(readFileSync(brandPath, "utf-8"));
|
|
4177
3250
|
if (!brand.configDir) {
|
|
4178
3251
|
throw new Error(
|
|
4179
3252
|
`brand.json at ${brandPath} is missing the configDir field`
|
|
4180
3253
|
);
|
|
4181
3254
|
}
|
|
4182
|
-
|
|
3255
|
+
configDirName = brand.configDir;
|
|
4183
3256
|
} catch (err) {
|
|
4184
3257
|
if (err instanceof SyntaxError) {
|
|
4185
3258
|
throw new Error(
|
|
@@ -4189,7 +3262,7 @@ function resolveKeyFilePath() {
|
|
|
4189
3262
|
throw err;
|
|
4190
3263
|
}
|
|
4191
3264
|
}
|
|
4192
|
-
cachedKeyFilePath =
|
|
3265
|
+
cachedKeyFilePath = resolve2(homedir(), configDirName, ".anthropic-api-key");
|
|
4193
3266
|
return cachedKeyFilePath;
|
|
4194
3267
|
}
|
|
4195
3268
|
function readKey() {
|
|
@@ -4211,7 +3284,7 @@ function readKey() {
|
|
|
4211
3284
|
);
|
|
4212
3285
|
}
|
|
4213
3286
|
try {
|
|
4214
|
-
const raw2 =
|
|
3287
|
+
const raw2 = readFileSync(keyFilePath2, "utf-8").trim();
|
|
4215
3288
|
if (!raw2) {
|
|
4216
3289
|
console.error(
|
|
4217
3290
|
`[anthropic-key] key file exists but is empty: ${keyFilePath2}`
|
|
@@ -4325,14 +3398,14 @@ function contextWindow(model) {
|
|
|
4325
3398
|
}
|
|
4326
3399
|
|
|
4327
3400
|
// app/lib/claude-auth.ts
|
|
4328
|
-
import { readFileSync as
|
|
3401
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
4329
3402
|
var TOKEN_ENDPOINT = "https://platform.claude.com/v1/oauth/token";
|
|
4330
3403
|
var CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
4331
3404
|
var EXPIRING_THRESHOLD_MS = 5 * 60 * 1e3;
|
|
4332
3405
|
function readCredentials() {
|
|
4333
3406
|
let raw2;
|
|
4334
3407
|
try {
|
|
4335
|
-
raw2 =
|
|
3408
|
+
raw2 = readFileSync2(CLAUDE_CREDENTIALS_FILE, "utf-8");
|
|
4336
3409
|
} catch {
|
|
4337
3410
|
return null;
|
|
4338
3411
|
}
|
|
@@ -4360,7 +3433,7 @@ function writeOAuthCredentials(tokens) {
|
|
|
4360
3433
|
try {
|
|
4361
3434
|
let fileData = {};
|
|
4362
3435
|
try {
|
|
4363
|
-
const rawFile =
|
|
3436
|
+
const rawFile = readFileSync2(CLAUDE_CREDENTIALS_FILE, "utf-8");
|
|
4364
3437
|
fileData = JSON.parse(rawFile);
|
|
4365
3438
|
} catch {
|
|
4366
3439
|
}
|
|
@@ -4371,7 +3444,7 @@ function writeOAuthCredentials(tokens) {
|
|
|
4371
3444
|
...tokens.refreshToken != null ? { refreshToken: tokens.refreshToken } : {},
|
|
4372
3445
|
expiresAt: newExpiresAt
|
|
4373
3446
|
};
|
|
4374
|
-
|
|
3447
|
+
writeFileSync2(CLAUDE_CREDENTIALS_FILE, JSON.stringify(fileData, null, 2), "utf-8");
|
|
4375
3448
|
return newExpiresAt;
|
|
4376
3449
|
} catch (err) {
|
|
4377
3450
|
console.error(`[claude-auth] credential write failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -4475,11 +3548,11 @@ async function ensureAuth() {
|
|
|
4475
3548
|
|
|
4476
3549
|
// app/lib/vnc.ts
|
|
4477
3550
|
import { spawnSync, execFileSync } from "child_process";
|
|
4478
|
-
import { createConnection as
|
|
4479
|
-
import { mkdirSync as
|
|
4480
|
-
import { resolve as
|
|
4481
|
-
var
|
|
4482
|
-
var VNC_SCRIPT =
|
|
3551
|
+
import { createConnection as createConnection2 } from "net";
|
|
3552
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
3553
|
+
import { resolve as resolve3 } from "path";
|
|
3554
|
+
var PLATFORM_ROOT = process.env.MAXY_PLATFORM_ROOT ?? resolve3(process.cwd(), "..");
|
|
3555
|
+
var VNC_SCRIPT = resolve3(PLATFORM_ROOT, "scripts/vnc.sh");
|
|
4483
3556
|
var displayMode = process.env.DISPLAY_MODE ?? "virtual";
|
|
4484
3557
|
if (displayMode === "native") {
|
|
4485
3558
|
console.log(`[vnc] DISPLAY_MODE=native \u2014 local requests use desktop display, remote requests use VNC`);
|
|
@@ -4488,8 +3561,8 @@ function resolveBrowserTransport(req, remoteAddress) {
|
|
|
4488
3561
|
if (displayMode !== "native") return "vnc";
|
|
4489
3562
|
const xff = req.headers.get("x-forwarded-for") ?? void 0;
|
|
4490
3563
|
const clientIp = resolveClientIp(remoteAddress ?? "127.0.0.1", xff);
|
|
4491
|
-
const
|
|
4492
|
-
const transport =
|
|
3564
|
+
const isLoopback = clientIp === "127.0.0.1" || clientIp === "::1" || clientIp === "loopback";
|
|
3565
|
+
const transport = isLoopback && !xff ? "native" : "vnc";
|
|
4493
3566
|
if (remoteAddress && remoteAddress !== "127.0.0.1" && remoteAddress !== "::1") {
|
|
4494
3567
|
const oldClientIp = resolveClientIp("127.0.0.1", xff);
|
|
4495
3568
|
const oldIsLoopback = oldClientIp === "127.0.0.1" || oldClientIp === "::1" || oldClientIp === "loopback";
|
|
@@ -4537,7 +3610,7 @@ function discoverNativeDisplay() {
|
|
|
4537
3610
|
const leaderPid = leaderResult.stdout?.trim();
|
|
4538
3611
|
if (leaderPid) {
|
|
4539
3612
|
try {
|
|
4540
|
-
const environ =
|
|
3613
|
+
const environ = readFileSync3(`/proc/${leaderPid}/environ`, "utf8");
|
|
4541
3614
|
const match2 = environ.split("\0").find((e) => e.startsWith("WAYLAND_DISPLAY="));
|
|
4542
3615
|
if (match2) waylandDisplay = match2.split("=")[1];
|
|
4543
3616
|
} catch {
|
|
@@ -4571,7 +3644,7 @@ async function waitForPort(port2, timeoutMs = 12e3) {
|
|
|
4571
3644
|
const deadline = Date.now() + timeoutMs;
|
|
4572
3645
|
while (Date.now() < deadline) {
|
|
4573
3646
|
const ready = await new Promise((res) => {
|
|
4574
|
-
const socket =
|
|
3647
|
+
const socket = createConnection2(port2, "127.0.0.1");
|
|
4575
3648
|
socket.setTimeout(500);
|
|
4576
3649
|
socket.once("connect", () => {
|
|
4577
3650
|
socket.destroy();
|
|
@@ -4592,10 +3665,10 @@ async function waitForPort(port2, timeoutMs = 12e3) {
|
|
|
4592
3665
|
return false;
|
|
4593
3666
|
}
|
|
4594
3667
|
function ensureLogDir() {
|
|
4595
|
-
|
|
3668
|
+
mkdirSync3(LOG_DIR, { recursive: true });
|
|
4596
3669
|
}
|
|
4597
3670
|
function logPath(name) {
|
|
4598
|
-
return
|
|
3671
|
+
return resolve3(LOG_DIR, `${name}.log`);
|
|
4599
3672
|
}
|
|
4600
3673
|
async function ensureVnc() {
|
|
4601
3674
|
const up = await waitForPort(5900, 1e3);
|
|
@@ -4804,9 +3877,9 @@ async function ensureTerminalUpgrade(transport = "vnc") {
|
|
|
4804
3877
|
return { ok: true };
|
|
4805
3878
|
}
|
|
4806
3879
|
function writeChromiumWrapper() {
|
|
4807
|
-
|
|
4808
|
-
const wrapperPath =
|
|
4809
|
-
|
|
3880
|
+
mkdirSync3(BIN_DIR, { recursive: true });
|
|
3881
|
+
const wrapperPath = resolve3(BIN_DIR, "chromium");
|
|
3882
|
+
writeFileSync3(wrapperPath, `#!/bin/bash
|
|
4810
3883
|
LOG="${LOG_DIR}/chromium.log"
|
|
4811
3884
|
echo "==== [$(date)] chromium wrapper ====" >> "$LOG"
|
|
4812
3885
|
echo " DISPLAY=$DISPLAY WAYLAND=$WAYLAND_DISPLAY XDG_SESSION_TYPE=$XDG_SESSION_TYPE" >> "$LOG"
|
|
@@ -4890,15 +3963,15 @@ function buildX11Env(chromiumWrapperPath, transport = "vnc") {
|
|
|
4890
3963
|
import neo4j from "neo4j-driver";
|
|
4891
3964
|
import { randomUUID } from "crypto";
|
|
4892
3965
|
import { spawn } from "child_process";
|
|
4893
|
-
import { readFileSync as
|
|
4894
|
-
import { resolve as
|
|
4895
|
-
var
|
|
3966
|
+
import { readFileSync as readFileSync4, readdirSync, existsSync as existsSync3, openSync, readSync, closeSync, statSync as statSync2, rmSync } from "fs";
|
|
3967
|
+
import { resolve as resolve4 } from "path";
|
|
3968
|
+
var PLATFORM_ROOT2 = process.env.MAXY_PLATFORM_ROOT ?? resolve4(process.cwd(), "..");
|
|
4896
3969
|
var driver = null;
|
|
4897
3970
|
function readPassword() {
|
|
4898
3971
|
if (process.env.NEO4J_PASSWORD) return process.env.NEO4J_PASSWORD;
|
|
4899
|
-
const passwordFile =
|
|
3972
|
+
const passwordFile = resolve4(PLATFORM_ROOT2, "config/.neo4j-password");
|
|
4900
3973
|
try {
|
|
4901
|
-
return
|
|
3974
|
+
return readFileSync4(passwordFile, "utf-8").trim();
|
|
4902
3975
|
} catch {
|
|
4903
3976
|
throw new Error(
|
|
4904
3977
|
`Neo4j password not found. Expected at ${passwordFile} or in NEO4J_PASSWORD env var.`
|
|
@@ -5361,8 +4434,8 @@ async function persistMessage(conversationId, role, content, accountId, tokens,
|
|
|
5361
4434
|
const prev = persistMessageLocks.get(conversationId);
|
|
5362
4435
|
const waited = prev !== void 0;
|
|
5363
4436
|
let release;
|
|
5364
|
-
const mine = new Promise((
|
|
5365
|
-
release =
|
|
4437
|
+
const mine = new Promise((resolve29) => {
|
|
4438
|
+
release = resolve29;
|
|
5366
4439
|
});
|
|
5367
4440
|
const chained = (prev ?? Promise.resolve()).then(() => mine);
|
|
5368
4441
|
persistMessageLocks.set(conversationId, chained);
|
|
@@ -5621,7 +4694,7 @@ ${userContent}`;
|
|
|
5621
4694
|
"dontAsk",
|
|
5622
4695
|
prompt
|
|
5623
4696
|
];
|
|
5624
|
-
return new Promise((
|
|
4697
|
+
return new Promise((resolve29) => {
|
|
5625
4698
|
let stdout = "";
|
|
5626
4699
|
let stderr = "";
|
|
5627
4700
|
const spawnFn = _spawnOverride ?? spawn;
|
|
@@ -5639,35 +4712,35 @@ ${userContent}`;
|
|
|
5639
4712
|
const timer = setTimeout(() => {
|
|
5640
4713
|
proc.kill("SIGTERM");
|
|
5641
4714
|
console.error("[persist] autoLabel: haiku subprocess timed out");
|
|
5642
|
-
|
|
4715
|
+
resolve29(null);
|
|
5643
4716
|
}, SESSION_LABEL_TIMEOUT_MS);
|
|
5644
4717
|
proc.on("error", (err) => {
|
|
5645
4718
|
clearTimeout(timer);
|
|
5646
4719
|
console.error(`[persist] autoLabel: subprocess error \u2014 ${err.message}`);
|
|
5647
|
-
|
|
4720
|
+
resolve29(null);
|
|
5648
4721
|
});
|
|
5649
4722
|
proc.on("close", (code) => {
|
|
5650
4723
|
clearTimeout(timer);
|
|
5651
4724
|
if (code !== 0) {
|
|
5652
4725
|
console.error(`[persist] autoLabel: subprocess exited code=${code}${stderr ? ` stderr=${stderr.trim().slice(0, 200)}` : ""}`);
|
|
5653
|
-
|
|
4726
|
+
resolve29(null);
|
|
5654
4727
|
return;
|
|
5655
4728
|
}
|
|
5656
4729
|
const text = stdout.trim();
|
|
5657
4730
|
if (!text) {
|
|
5658
4731
|
console.error("[persist] autoLabel: haiku returned empty response");
|
|
5659
|
-
|
|
4732
|
+
resolve29(null);
|
|
5660
4733
|
return;
|
|
5661
4734
|
}
|
|
5662
4735
|
if (text === "SKIP") {
|
|
5663
4736
|
console.error("[persist] autoLabel: haiku returned SKIP \u2014 messages too vague");
|
|
5664
|
-
|
|
4737
|
+
resolve29(null);
|
|
5665
4738
|
return;
|
|
5666
4739
|
}
|
|
5667
4740
|
const words = text.split(/\s+/).slice(0, SESSION_LABEL_MAX_WORDS);
|
|
5668
4741
|
const label = words.join(" ");
|
|
5669
4742
|
console.error(`[persist] autoLabel: haiku response="${label}"`);
|
|
5670
|
-
|
|
4743
|
+
resolve29(label);
|
|
5671
4744
|
});
|
|
5672
4745
|
});
|
|
5673
4746
|
}
|
|
@@ -5939,10 +5012,10 @@ var MAX_RECENT_TOOL_FAILURES = 3;
|
|
|
5939
5012
|
var RECENT_FAILURES_TAIL_BYTES = 10 * 1024;
|
|
5940
5013
|
function readRecentToolFailures(accountId, conversationId) {
|
|
5941
5014
|
try {
|
|
5942
|
-
const
|
|
5943
|
-
const logDir =
|
|
5944
|
-
const logPath2 =
|
|
5945
|
-
if (!
|
|
5015
|
+
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? resolve4(process.cwd(), "..");
|
|
5016
|
+
const logDir = resolve4(platformRoot2, "..", "data/accounts", accountId, "logs");
|
|
5017
|
+
const logPath2 = resolve4(logDir, `claude-agent-stream-${conversationId}.log`);
|
|
5018
|
+
if (!existsSync3(logPath2)) {
|
|
5946
5019
|
console.error(`[review-tail-skip] path=${logPath2} reason=file-missing \u2014 first turn of conversation, subprocess not yet spawned, or log rotated`);
|
|
5947
5020
|
return [];
|
|
5948
5021
|
}
|
|
@@ -6098,13 +5171,13 @@ ${taskLines.join("\n")}`);
|
|
|
6098
5171
|
let pendingCount = 0;
|
|
6099
5172
|
let pendingLines = [];
|
|
6100
5173
|
try {
|
|
6101
|
-
const
|
|
6102
|
-
const pendingDir =
|
|
6103
|
-
if (
|
|
5174
|
+
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? resolve4(process.cwd(), "..");
|
|
5175
|
+
const pendingDir = resolve4(platformRoot2, "..", "data/accounts", accountId, "pending-actions");
|
|
5176
|
+
if (existsSync3(pendingDir)) {
|
|
6104
5177
|
const files = readdirSync(pendingDir).filter((f) => f.endsWith(".json") && !f.startsWith("."));
|
|
6105
5178
|
for (const file of files) {
|
|
6106
5179
|
try {
|
|
6107
|
-
const raw2 =
|
|
5180
|
+
const raw2 = readFileSync4(resolve4(pendingDir, file), "utf-8");
|
|
6108
5181
|
const action = JSON.parse(raw2);
|
|
6109
5182
|
if (action.state === "pending") {
|
|
6110
5183
|
const inputSummary = JSON.stringify(action.hookPayload?.tool_input ?? {}).slice(0, 150);
|
|
@@ -6171,12 +5244,12 @@ ${sections.join("\n\n")}
|
|
|
6171
5244
|
}
|
|
6172
5245
|
}
|
|
6173
5246
|
async function consumeStep7FlagUI(session, accountId) {
|
|
6174
|
-
const accountDir =
|
|
6175
|
-
const flagPath =
|
|
6176
|
-
if (!
|
|
5247
|
+
const accountDir = resolve4(PLATFORM_ROOT2, "..", "data/accounts", accountId);
|
|
5248
|
+
const flagPath = resolve4(accountDir, "onboarding", "step7-complete");
|
|
5249
|
+
if (!existsSync3(flagPath)) return false;
|
|
6177
5250
|
let completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6178
5251
|
try {
|
|
6179
|
-
const raw2 =
|
|
5252
|
+
const raw2 = readFileSync4(flagPath, "utf-8").trim();
|
|
6180
5253
|
if (raw2) {
|
|
6181
5254
|
const parsed = JSON.parse(raw2);
|
|
6182
5255
|
if (typeof parsed.completedAt === "string") {
|
|
@@ -6614,8 +5687,8 @@ ${items}
|
|
|
6614
5687
|
}
|
|
6615
5688
|
|
|
6616
5689
|
// app/lib/adherence-ledger.ts
|
|
6617
|
-
import { existsSync as
|
|
6618
|
-
import { resolve as
|
|
5690
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync5, renameSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
5691
|
+
import { resolve as resolve5, dirname as dirname2 } from "path";
|
|
6619
5692
|
import lockfile from "proper-lockfile";
|
|
6620
5693
|
var LOG_TAG = "[adherence-ledger]";
|
|
6621
5694
|
var ADHERENCE_BLOCK_THRESHOLD = 5;
|
|
@@ -6630,7 +5703,7 @@ function nowIso() {
|
|
|
6630
5703
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
6631
5704
|
}
|
|
6632
5705
|
function ledgerPath(accountDir, agentName) {
|
|
6633
|
-
return
|
|
5706
|
+
return resolve5(accountDir, "agents", agentName, "adherence-ledger.json");
|
|
6634
5707
|
}
|
|
6635
5708
|
function lockPath(accountDir, agentName) {
|
|
6636
5709
|
return `${ledgerPath(accountDir, agentName)}.lock`;
|
|
@@ -6655,8 +5728,8 @@ function pruneViolations(violations) {
|
|
|
6655
5728
|
});
|
|
6656
5729
|
}
|
|
6657
5730
|
function readUnlocked(path2, accountId, agentName) {
|
|
6658
|
-
if (!
|
|
6659
|
-
const content =
|
|
5731
|
+
if (!existsSync4(path2)) return emptyLedger(accountId, agentName);
|
|
5732
|
+
const content = readFileSync5(path2, "utf-8");
|
|
6660
5733
|
if (!content.trim()) return emptyLedger(accountId, agentName);
|
|
6661
5734
|
const data = JSON.parse(content);
|
|
6662
5735
|
if (typeof data !== "object" || data === null) {
|
|
@@ -6675,10 +5748,10 @@ function writeAtomic(path2, data) {
|
|
|
6675
5748
|
}
|
|
6676
5749
|
data.updated_at = nowIso();
|
|
6677
5750
|
const dir = dirname2(path2);
|
|
6678
|
-
|
|
5751
|
+
mkdirSync4(dir, { recursive: true });
|
|
6679
5752
|
const tmp = `${path2}.tmp-${process.pid}-${Date.now()}`;
|
|
6680
5753
|
try {
|
|
6681
|
-
|
|
5754
|
+
writeFileSync4(tmp, JSON.stringify(data, null, 2), "utf-8");
|
|
6682
5755
|
renameSync(tmp, path2);
|
|
6683
5756
|
} catch (err) {
|
|
6684
5757
|
try {
|
|
@@ -6691,9 +5764,9 @@ function writeAtomic(path2, data) {
|
|
|
6691
5764
|
async function withLock(accountDir, agentName, fn) {
|
|
6692
5765
|
const path2 = ledgerPath(accountDir, agentName);
|
|
6693
5766
|
const lock = lockPath(accountDir, agentName);
|
|
6694
|
-
|
|
6695
|
-
if (!
|
|
6696
|
-
|
|
5767
|
+
mkdirSync4(dirname2(lock), { recursive: true });
|
|
5768
|
+
if (!existsSync4(lock)) {
|
|
5769
|
+
writeFileSync4(lock, "", "utf-8");
|
|
6697
5770
|
}
|
|
6698
5771
|
const release = await lockfile.lock(lock, {
|
|
6699
5772
|
realpath: false,
|
|
@@ -7179,20 +6252,20 @@ function agentLogStream(name, accountDir, conversationId) {
|
|
|
7179
6252
|
if (!conversationId) {
|
|
7180
6253
|
throw new Error(`agentLogStream: conversationId is required (name=${name}) \u2014 use preConversationLogStream for pre-session events`);
|
|
7181
6254
|
}
|
|
7182
|
-
const logDir =
|
|
7183
|
-
|
|
6255
|
+
const logDir = resolve6(accountDir, "logs");
|
|
6256
|
+
mkdirSync5(logDir, { recursive: true });
|
|
7184
6257
|
purgeOldLogs(logDir, `${name}-`);
|
|
7185
|
-
const logPath2 =
|
|
6258
|
+
const logPath2 = resolve6(logDir, `${name}-${conversationId}.log`);
|
|
7186
6259
|
const stream = createWriteStream(logPath2, { flags: "a" });
|
|
7187
6260
|
registerStreamLog(stream, { path: logPath2, conversationId, name });
|
|
7188
6261
|
return stream;
|
|
7189
6262
|
}
|
|
7190
6263
|
function preConversationLogStream(name, accountDir) {
|
|
7191
|
-
const logDir =
|
|
7192
|
-
|
|
6264
|
+
const logDir = resolve6(accountDir, "logs");
|
|
6265
|
+
mkdirSync5(logDir, { recursive: true });
|
|
7193
6266
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
7194
6267
|
purgeOldLogs(logDir, `preconversation-${name}-`);
|
|
7195
|
-
const logPath2 =
|
|
6268
|
+
const logPath2 = resolve6(logDir, `preconversation-${name}-${date}.log`);
|
|
7196
6269
|
const stream = createWriteStream(logPath2, { flags: "a" });
|
|
7197
6270
|
registerStreamLog(stream, { path: logPath2, conversationId: null, name: `preconversation-${name}` });
|
|
7198
6271
|
return stream;
|
|
@@ -7211,7 +6284,7 @@ function sigtermFlushStreamLogs(reason, source) {
|
|
|
7211
6284
|
const line = `[${ts}] [server-sigterm] reason=${reason}${convPart} name=${entry.name} source=${source}
|
|
7212
6285
|
`;
|
|
7213
6286
|
try {
|
|
7214
|
-
|
|
6287
|
+
appendFileSync2(entry.path, line);
|
|
7215
6288
|
} catch (err) {
|
|
7216
6289
|
const msg = err instanceof Error ? err.message : String(err);
|
|
7217
6290
|
console.error(`[server-sigterm-flush-err] path=${entry.path} reason=${msg}`);
|
|
@@ -7230,7 +6303,7 @@ function purgeOldLogs(logDir, prefix) {
|
|
|
7230
6303
|
}
|
|
7231
6304
|
for (const file of entries) {
|
|
7232
6305
|
if (!file.startsWith(prefix)) continue;
|
|
7233
|
-
const filePath =
|
|
6306
|
+
const filePath = resolve6(logDir, file);
|
|
7234
6307
|
try {
|
|
7235
6308
|
if (statSync3(filePath).mtimeMs < cutoff) unlinkSync3(filePath);
|
|
7236
6309
|
} catch (err) {
|
|
@@ -7299,7 +6372,7 @@ function sampleProcState(pid) {
|
|
|
7299
6372
|
let sockets2 = 0;
|
|
7300
6373
|
for (const tcpFile of ["/proc/" + pid + "/net/tcp", "/proc/" + pid + "/net/tcp6"]) {
|
|
7301
6374
|
try {
|
|
7302
|
-
const raw2 =
|
|
6375
|
+
const raw2 = readFileSync6(tcpFile, "utf-8");
|
|
7303
6376
|
const lines2 = raw2.split("\n");
|
|
7304
6377
|
for (let i = 1; i < lines2.length; i++) {
|
|
7305
6378
|
const line = lines2[i].trim();
|
|
@@ -7315,7 +6388,7 @@ function sampleProcState(pid) {
|
|
|
7315
6388
|
}
|
|
7316
6389
|
let rssMb = 0;
|
|
7317
6390
|
try {
|
|
7318
|
-
const statm =
|
|
6391
|
+
const statm = readFileSync6(`/proc/${pid}/statm`, "utf-8").trim().split(/\s+/);
|
|
7319
6392
|
const rssPages = parseInt(statm[1] ?? "0", 10);
|
|
7320
6393
|
if (Number.isFinite(rssPages)) rssMb = Math.round(rssPages * 4096 / (1024 * 1024));
|
|
7321
6394
|
} catch {
|
|
@@ -7348,21 +6421,21 @@ function sampleProcState(pid) {
|
|
|
7348
6421
|
return `proc_err=${JSON.stringify(msg.slice(0, 60))}`;
|
|
7349
6422
|
}
|
|
7350
6423
|
}
|
|
7351
|
-
var
|
|
7352
|
-
var ACCOUNTS_DIR =
|
|
7353
|
-
if (!
|
|
6424
|
+
var PLATFORM_ROOT3 = process.env.MAXY_PLATFORM_ROOT ?? resolve6(process.cwd(), "..");
|
|
6425
|
+
var ACCOUNTS_DIR = resolve6(PLATFORM_ROOT3, "..", "data/accounts");
|
|
6426
|
+
if (!existsSync5(PLATFORM_ROOT3)) {
|
|
7354
6427
|
throw new Error(
|
|
7355
|
-
`PLATFORM_ROOT does not exist: ${
|
|
6428
|
+
`PLATFORM_ROOT does not exist: ${PLATFORM_ROOT3}
|
|
7356
6429
|
Set the MAXY_PLATFORM_ROOT environment variable to the absolute path of the platform directory.`
|
|
7357
6430
|
);
|
|
7358
6431
|
}
|
|
7359
6432
|
function resolveAccount() {
|
|
7360
|
-
if (!
|
|
7361
|
-
const usersFilePath =
|
|
6433
|
+
if (!existsSync5(ACCOUNTS_DIR)) return null;
|
|
6434
|
+
const usersFilePath = resolve6(PLATFORM_ROOT3, "config", "users.json");
|
|
7362
6435
|
let usersJsonUserId = null;
|
|
7363
|
-
if (
|
|
6436
|
+
if (existsSync5(usersFilePath)) {
|
|
7364
6437
|
try {
|
|
7365
|
-
const raw2 =
|
|
6438
|
+
const raw2 = readFileSync6(usersFilePath, "utf-8").trim();
|
|
7366
6439
|
if (raw2) {
|
|
7367
6440
|
const users = JSON.parse(raw2);
|
|
7368
6441
|
if (users.length > 0) {
|
|
@@ -7376,9 +6449,9 @@ function resolveAccount() {
|
|
|
7376
6449
|
let fallback = null;
|
|
7377
6450
|
for (const entry of entries) {
|
|
7378
6451
|
if (!entry.isDirectory()) continue;
|
|
7379
|
-
const configPath2 =
|
|
7380
|
-
if (!
|
|
7381
|
-
const raw2 =
|
|
6452
|
+
const configPath2 = resolve6(ACCOUNTS_DIR, entry.name, "account.json");
|
|
6453
|
+
if (!existsSync5(configPath2)) continue;
|
|
6454
|
+
const raw2 = readFileSync6(configPath2, "utf-8");
|
|
7382
6455
|
let config;
|
|
7383
6456
|
try {
|
|
7384
6457
|
config = JSON.parse(raw2);
|
|
@@ -7393,7 +6466,7 @@ function resolveAccount() {
|
|
|
7393
6466
|
}
|
|
7394
6467
|
const result = {
|
|
7395
6468
|
accountId: config.accountId,
|
|
7396
|
-
accountDir:
|
|
6469
|
+
accountDir: resolve6(ACCOUNTS_DIR, entry.name),
|
|
7397
6470
|
config
|
|
7398
6471
|
};
|
|
7399
6472
|
if (usersJsonUserId && config.admins?.some((a) => a.userId === usersJsonUserId)) {
|
|
@@ -7411,9 +6484,9 @@ function resolveAccount() {
|
|
|
7411
6484
|
return fallback;
|
|
7412
6485
|
}
|
|
7413
6486
|
function readAgentFile(accountDir, agentName, filename) {
|
|
7414
|
-
const filePath =
|
|
7415
|
-
if (!
|
|
7416
|
-
return
|
|
6487
|
+
const filePath = resolve6(accountDir, "agents", agentName, filename);
|
|
6488
|
+
if (!existsSync5(filePath)) return null;
|
|
6489
|
+
return readFileSync6(filePath, "utf-8");
|
|
7417
6490
|
}
|
|
7418
6491
|
function readIdentity(accountDir, agentName) {
|
|
7419
6492
|
return readAgentFile(accountDir, agentName, "IDENTITY.md");
|
|
@@ -7448,14 +6521,14 @@ function validateAgentSlug(slug) {
|
|
|
7448
6521
|
return true;
|
|
7449
6522
|
}
|
|
7450
6523
|
function resolveDefaultAgentSlug(accountDir) {
|
|
7451
|
-
const configPath2 =
|
|
7452
|
-
if (!
|
|
6524
|
+
const configPath2 = resolve6(accountDir, "account.json");
|
|
6525
|
+
if (!existsSync5(configPath2)) {
|
|
7453
6526
|
console.error("[agent-resolve] account.json not found \u2014 cannot resolve defaultAgent");
|
|
7454
6527
|
return null;
|
|
7455
6528
|
}
|
|
7456
6529
|
let config;
|
|
7457
6530
|
try {
|
|
7458
|
-
config = JSON.parse(
|
|
6531
|
+
config = JSON.parse(readFileSync6(configPath2, "utf-8"));
|
|
7459
6532
|
} catch (err) {
|
|
7460
6533
|
console.error("[agent-resolve] failed to read account.json:", err);
|
|
7461
6534
|
return null;
|
|
@@ -7464,8 +6537,8 @@ function resolveDefaultAgentSlug(accountDir) {
|
|
|
7464
6537
|
console.error("[agent-resolve] defaultAgent not configured in account.json \u2014 set it via the connect-whatsapp skill");
|
|
7465
6538
|
return null;
|
|
7466
6539
|
}
|
|
7467
|
-
const agentConfigPath =
|
|
7468
|
-
if (!
|
|
6540
|
+
const agentConfigPath = resolve6(accountDir, "agents", config.defaultAgent, "config.json");
|
|
6541
|
+
if (!existsSync5(agentConfigPath)) {
|
|
7469
6542
|
console.error(`[agent-resolve] defaultAgent="${config.defaultAgent}" has no config.json at ${agentConfigPath}`);
|
|
7470
6543
|
return null;
|
|
7471
6544
|
}
|
|
@@ -7537,23 +6610,23 @@ function resolveAgentConfig(accountDir, agentName) {
|
|
|
7537
6610
|
}
|
|
7538
6611
|
let knowledge = null;
|
|
7539
6612
|
let knowledgeBaked = false;
|
|
7540
|
-
const agentDir =
|
|
7541
|
-
const knowledgePath =
|
|
7542
|
-
const summaryPath =
|
|
7543
|
-
const hasKnowledge =
|
|
7544
|
-
const hasSummary =
|
|
6613
|
+
const agentDir = resolve6(accountDir, "agents", agentName);
|
|
6614
|
+
const knowledgePath = resolve6(agentDir, "KNOWLEDGE.md");
|
|
6615
|
+
const summaryPath = resolve6(agentDir, "KNOWLEDGE-SUMMARY.md");
|
|
6616
|
+
const hasKnowledge = existsSync5(knowledgePath);
|
|
6617
|
+
const hasSummary = existsSync5(summaryPath);
|
|
7545
6618
|
if (hasKnowledge && hasSummary) {
|
|
7546
6619
|
const knowledgeMtime = statSync3(knowledgePath).mtimeMs;
|
|
7547
6620
|
const summaryMtime = statSync3(summaryPath).mtimeMs;
|
|
7548
6621
|
if (summaryMtime >= knowledgeMtime) {
|
|
7549
|
-
knowledge =
|
|
6622
|
+
knowledge = readFileSync6(summaryPath, "utf-8");
|
|
7550
6623
|
} else {
|
|
7551
6624
|
console.warn(`[agent-config] ${agentName}: KNOWLEDGE-SUMMARY.md is stale (KNOWLEDGE.md is newer) \u2014 using full knowledge`);
|
|
7552
|
-
knowledge =
|
|
6625
|
+
knowledge = readFileSync6(knowledgePath, "utf-8");
|
|
7553
6626
|
}
|
|
7554
6627
|
knowledgeBaked = true;
|
|
7555
6628
|
} else if (hasKnowledge) {
|
|
7556
|
-
knowledge =
|
|
6629
|
+
knowledge = readFileSync6(knowledgePath, "utf-8");
|
|
7557
6630
|
knowledgeBaked = true;
|
|
7558
6631
|
}
|
|
7559
6632
|
let budget = null;
|
|
@@ -7575,11 +6648,11 @@ function resolveAgentConfig(accountDir, agentName) {
|
|
|
7575
6648
|
return { model, plugins, status, displayName, image, imageShape, showAgentName, knowledge, knowledgeBaked, liveMemory, knowledgeKeywords, budget, accessMode };
|
|
7576
6649
|
}
|
|
7577
6650
|
function parsePluginFrontmatter(pluginDir) {
|
|
7578
|
-
const pluginPath =
|
|
7579
|
-
if (!
|
|
6651
|
+
const pluginPath = resolve6(PLATFORM_ROOT3, "plugins", pluginDir, "PLUGIN.md");
|
|
6652
|
+
if (!existsSync5(pluginPath)) return null;
|
|
7580
6653
|
let raw2;
|
|
7581
6654
|
try {
|
|
7582
|
-
raw2 =
|
|
6655
|
+
raw2 = readFileSync6(pluginPath, "utf-8");
|
|
7583
6656
|
} catch {
|
|
7584
6657
|
console.warn(`[plugins] cannot read ${pluginPath}`);
|
|
7585
6658
|
return null;
|
|
@@ -7638,24 +6711,24 @@ function parsePluginFrontmatter(pluginDir) {
|
|
|
7638
6711
|
function autoDeliverPremiumPlugins(purchasedPlugins) {
|
|
7639
6712
|
if (!purchasedPlugins || purchasedPlugins.length === 0) return;
|
|
7640
6713
|
const TAG19 = "[premium-auto-deliver]";
|
|
7641
|
-
const stagingRoot =
|
|
7642
|
-
const pluginsDir =
|
|
7643
|
-
if (!
|
|
6714
|
+
const stagingRoot = resolve6(PLATFORM_ROOT3, "../premium-plugins");
|
|
6715
|
+
const pluginsDir = resolve6(PLATFORM_ROOT3, "plugins");
|
|
6716
|
+
if (!existsSync5(stagingRoot)) {
|
|
7644
6717
|
console.log(`${TAG19} no staging directory \u2014 skipping`);
|
|
7645
6718
|
return;
|
|
7646
6719
|
}
|
|
7647
6720
|
for (const pluginName of purchasedPlugins) {
|
|
7648
|
-
const stagingDir =
|
|
7649
|
-
if (!
|
|
6721
|
+
const stagingDir = resolve6(stagingRoot, pluginName);
|
|
6722
|
+
if (!existsSync5(stagingDir)) {
|
|
7650
6723
|
console.log(`${TAG19} ${pluginName}: not in staging \u2014 skipping`);
|
|
7651
6724
|
continue;
|
|
7652
6725
|
}
|
|
7653
|
-
const bundlePath =
|
|
7654
|
-
const isBundle =
|
|
6726
|
+
const bundlePath = join3(stagingDir, "BUNDLE.md");
|
|
6727
|
+
const isBundle = existsSync5(bundlePath);
|
|
7655
6728
|
if (isBundle) {
|
|
7656
6729
|
let bundleRaw;
|
|
7657
6730
|
try {
|
|
7658
|
-
bundleRaw =
|
|
6731
|
+
bundleRaw = readFileSync6(bundlePath, "utf-8");
|
|
7659
6732
|
} catch (err) {
|
|
7660
6733
|
console.log(`${TAG19} ${pluginName}: cannot read BUNDLE.md \u2014 ${err instanceof Error ? err.message : String(err)}`);
|
|
7661
6734
|
continue;
|
|
@@ -7686,13 +6759,13 @@ function autoDeliverPremiumPlugins(purchasedPlugins) {
|
|
|
7686
6759
|
let delivered = 0;
|
|
7687
6760
|
let skipped = 0;
|
|
7688
6761
|
for (const sub of subPlugins) {
|
|
7689
|
-
const target =
|
|
7690
|
-
if (
|
|
6762
|
+
const target = resolve6(pluginsDir, sub);
|
|
6763
|
+
if (existsSync5(resolve6(target, "PLUGIN.md"))) {
|
|
7691
6764
|
skipped++;
|
|
7692
6765
|
continue;
|
|
7693
6766
|
}
|
|
7694
|
-
const source =
|
|
7695
|
-
if (!
|
|
6767
|
+
const source = resolve6(stagingDir, "plugins", sub);
|
|
6768
|
+
if (!existsSync5(source)) {
|
|
7696
6769
|
console.log(`${TAG19} ${pluginName}/${sub}: source missing in staging \u2014 skipping`);
|
|
7697
6770
|
continue;
|
|
7698
6771
|
}
|
|
@@ -7705,8 +6778,8 @@ function autoDeliverPremiumPlugins(purchasedPlugins) {
|
|
|
7705
6778
|
}
|
|
7706
6779
|
console.log(`${TAG19} ${pluginName} (bundle): ${delivered} delivered, ${skipped} already present`);
|
|
7707
6780
|
} else {
|
|
7708
|
-
const target =
|
|
7709
|
-
if (
|
|
6781
|
+
const target = resolve6(pluginsDir, pluginName);
|
|
6782
|
+
if (existsSync5(resolve6(target, "PLUGIN.md"))) {
|
|
7710
6783
|
console.log(`${TAG19} ${pluginName}: already present \u2014 skipping`);
|
|
7711
6784
|
continue;
|
|
7712
6785
|
}
|
|
@@ -7746,21 +6819,21 @@ function migratePluginRenames(accountDir, config) {
|
|
|
7746
6819
|
return name;
|
|
7747
6820
|
});
|
|
7748
6821
|
if (!changed) return;
|
|
7749
|
-
const configPath2 =
|
|
6822
|
+
const configPath2 = resolve6(accountDir, "account.json");
|
|
7750
6823
|
try {
|
|
7751
|
-
const raw2 =
|
|
6824
|
+
const raw2 = readFileSync6(configPath2, "utf-8");
|
|
7752
6825
|
const parsed = JSON.parse(raw2);
|
|
7753
6826
|
parsed.enabledPlugins = migrated;
|
|
7754
|
-
|
|
6827
|
+
writeFileSync5(configPath2, JSON.stringify(parsed, null, 2) + "\n");
|
|
7755
6828
|
config.enabledPlugins = migrated;
|
|
7756
6829
|
console.log(`${TAG19} account.json updated (${migrated.length} plugins)`);
|
|
7757
6830
|
} catch (err) {
|
|
7758
6831
|
console.error(`${TAG19} failed to update account.json \u2014 ${err instanceof Error ? err.message : String(err)}`);
|
|
7759
6832
|
}
|
|
7760
|
-
const pluginsDir =
|
|
6833
|
+
const pluginsDir = resolve6(PLATFORM_ROOT3, "plugins");
|
|
7761
6834
|
for (const oldName of Object.keys(PLUGIN_RENAMES)) {
|
|
7762
|
-
const orphan =
|
|
7763
|
-
if (
|
|
6835
|
+
const orphan = resolve6(pluginsDir, oldName);
|
|
6836
|
+
if (existsSync5(orphan)) {
|
|
7764
6837
|
try {
|
|
7765
6838
|
rmSync2(orphan, { recursive: true });
|
|
7766
6839
|
console.log(`${TAG19} removed orphan: ${oldName}`);
|
|
@@ -7773,22 +6846,22 @@ function migratePluginRenames(accountDir, config) {
|
|
|
7773
6846
|
function autoDeliverBundleAgents(accountDir, purchasedPlugins) {
|
|
7774
6847
|
if (!purchasedPlugins || purchasedPlugins.length === 0) return;
|
|
7775
6848
|
const TAG19 = "[bundle-agent-deliver]";
|
|
7776
|
-
const stagingRoot =
|
|
7777
|
-
const specialistsDir =
|
|
7778
|
-
if (!
|
|
7779
|
-
if (!
|
|
7780
|
-
|
|
6849
|
+
const stagingRoot = resolve6(PLATFORM_ROOT3, "../premium-plugins");
|
|
6850
|
+
const specialistsDir = resolve6(accountDir, "specialists", "agents");
|
|
6851
|
+
if (!existsSync5(stagingRoot)) return;
|
|
6852
|
+
if (!existsSync5(specialistsDir)) {
|
|
6853
|
+
mkdirSync5(specialistsDir, { recursive: true });
|
|
7781
6854
|
}
|
|
7782
|
-
const agentsmdPath =
|
|
6855
|
+
const agentsmdPath = resolve6(accountDir, "agents", "admin", "AGENTS.md");
|
|
7783
6856
|
let agentsmd = "";
|
|
7784
6857
|
try {
|
|
7785
|
-
agentsmd =
|
|
6858
|
+
agentsmd = existsSync5(agentsmdPath) ? readFileSync6(agentsmdPath, "utf-8") : "";
|
|
7786
6859
|
} catch {
|
|
7787
6860
|
}
|
|
7788
6861
|
let delivered = 0;
|
|
7789
6862
|
for (const pluginName of purchasedPlugins) {
|
|
7790
|
-
const bundleAgentsDir =
|
|
7791
|
-
if (!
|
|
6863
|
+
const bundleAgentsDir = resolve6(stagingRoot, pluginName, "agents");
|
|
6864
|
+
if (!existsSync5(bundleAgentsDir)) continue;
|
|
7792
6865
|
let entries;
|
|
7793
6866
|
try {
|
|
7794
6867
|
entries = readdirSync2(bundleAgentsDir).filter((f) => f.endsWith(".md"));
|
|
@@ -7796,9 +6869,9 @@ function autoDeliverBundleAgents(accountDir, purchasedPlugins) {
|
|
|
7796
6869
|
continue;
|
|
7797
6870
|
}
|
|
7798
6871
|
for (const filename of entries) {
|
|
7799
|
-
const target =
|
|
7800
|
-
if (
|
|
7801
|
-
const source =
|
|
6872
|
+
const target = resolve6(specialistsDir, filename);
|
|
6873
|
+
if (existsSync5(target)) continue;
|
|
6874
|
+
const source = resolve6(bundleAgentsDir, filename);
|
|
7802
6875
|
try {
|
|
7803
6876
|
cpSync(source, target);
|
|
7804
6877
|
} catch (err) {
|
|
@@ -7806,7 +6879,7 @@ function autoDeliverBundleAgents(accountDir, purchasedPlugins) {
|
|
|
7806
6879
|
continue;
|
|
7807
6880
|
}
|
|
7808
6881
|
try {
|
|
7809
|
-
const content =
|
|
6882
|
+
const content = readFileSync6(target, "utf-8");
|
|
7810
6883
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
7811
6884
|
if (fmMatch) {
|
|
7812
6885
|
const nameMatch = fmMatch[1].match(/^name:\s*(.+)/m);
|
|
@@ -7830,7 +6903,7 @@ function autoDeliverBundleAgents(accountDir, purchasedPlugins) {
|
|
|
7830
6903
|
}
|
|
7831
6904
|
if (delivered > 0) {
|
|
7832
6905
|
try {
|
|
7833
|
-
|
|
6906
|
+
writeFileSync5(agentsmdPath, agentsmd);
|
|
7834
6907
|
console.log(`${TAG19} AGENTS.md updated (${delivered} agents added)`);
|
|
7835
6908
|
} catch (err) {
|
|
7836
6909
|
console.error(`${TAG19} AGENTS.md update failed \u2014 ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -7838,11 +6911,11 @@ function autoDeliverBundleAgents(accountDir, purchasedPlugins) {
|
|
|
7838
6911
|
}
|
|
7839
6912
|
}
|
|
7840
6913
|
function assemblePublicPluginContent(pluginDir) {
|
|
7841
|
-
const pluginRoot =
|
|
7842
|
-
const pluginPath =
|
|
6914
|
+
const pluginRoot = resolve6(PLATFORM_ROOT3, "plugins", pluginDir);
|
|
6915
|
+
const pluginPath = resolve6(pluginRoot, "PLUGIN.md");
|
|
7843
6916
|
let raw2;
|
|
7844
6917
|
try {
|
|
7845
|
-
raw2 =
|
|
6918
|
+
raw2 = readFileSync6(pluginPath, "utf-8");
|
|
7846
6919
|
} catch {
|
|
7847
6920
|
return null;
|
|
7848
6921
|
}
|
|
@@ -7851,7 +6924,7 @@ function assemblePublicPluginContent(pluginDir) {
|
|
|
7851
6924
|
const parts = [pluginBody];
|
|
7852
6925
|
let skillCount = 0;
|
|
7853
6926
|
let refCount = 0;
|
|
7854
|
-
const skillsDir =
|
|
6927
|
+
const skillsDir = resolve6(pluginRoot, "skills");
|
|
7855
6928
|
let skillDirs;
|
|
7856
6929
|
try {
|
|
7857
6930
|
skillDirs = readdirSync2(skillsDir).sort();
|
|
@@ -7859,11 +6932,11 @@ function assemblePublicPluginContent(pluginDir) {
|
|
|
7859
6932
|
return { body: pluginBody, skillCount: 0, refCount: 0 };
|
|
7860
6933
|
}
|
|
7861
6934
|
for (const skillName of skillDirs) {
|
|
7862
|
-
const skillDir =
|
|
7863
|
-
const skillMdPath =
|
|
6935
|
+
const skillDir = resolve6(skillsDir, skillName);
|
|
6936
|
+
const skillMdPath = resolve6(skillDir, "SKILL.md");
|
|
7864
6937
|
let skillRaw;
|
|
7865
6938
|
try {
|
|
7866
|
-
skillRaw =
|
|
6939
|
+
skillRaw = readFileSync6(skillMdPath, "utf-8");
|
|
7867
6940
|
} catch {
|
|
7868
6941
|
continue;
|
|
7869
6942
|
}
|
|
@@ -7903,7 +6976,7 @@ function assemblePublicPluginContent(pluginDir) {
|
|
|
7903
6976
|
parts.push(`
|
|
7904
6977
|
<!-- skill: ${skillName} -->`);
|
|
7905
6978
|
parts.push(skillBody);
|
|
7906
|
-
const refsDir =
|
|
6979
|
+
const refsDir = resolve6(skillDir, "references");
|
|
7907
6980
|
let refFiles;
|
|
7908
6981
|
try {
|
|
7909
6982
|
refFiles = readdirSync2(refsDir).filter((f) => f.endsWith(".md")).filter((f) => !publicExcludeReferences.includes(f)).sort();
|
|
@@ -7914,7 +6987,7 @@ function assemblePublicPluginContent(pluginDir) {
|
|
|
7914
6987
|
}
|
|
7915
6988
|
for (const refFile of refFiles) {
|
|
7916
6989
|
try {
|
|
7917
|
-
const refContent =
|
|
6990
|
+
const refContent = readFileSync6(resolve6(refsDir, refFile), "utf-8").trim();
|
|
7918
6991
|
if (refContent) {
|
|
7919
6992
|
parts.push(`
|
|
7920
6993
|
<!-- reference: ${refFile} -->`);
|
|
@@ -7932,7 +7005,7 @@ function assemblePublicPluginContent(pluginDir) {
|
|
|
7932
7005
|
return { body: parts.join("\n"), skillCount, refCount };
|
|
7933
7006
|
}
|
|
7934
7007
|
function loadEmbeddedPlugins(agentType, selectedPlugins, enabledPlugins) {
|
|
7935
|
-
const pluginsDir =
|
|
7008
|
+
const pluginsDir = resolve6(PLATFORM_ROOT3, "plugins");
|
|
7936
7009
|
let dirs;
|
|
7937
7010
|
try {
|
|
7938
7011
|
dirs = readdirSync2(pluginsDir);
|
|
@@ -7985,10 +7058,10 @@ function loadEmbeddedPlugins(agentType, selectedPlugins, enabledPlugins) {
|
|
|
7985
7058
|
console.log(`[plugins] loaded ${dir} for public (${assembled.body.length} chars, ${assembled.skillCount} skills, ${assembled.refCount} refs)`);
|
|
7986
7059
|
}
|
|
7987
7060
|
} else {
|
|
7988
|
-
const pluginPath =
|
|
7061
|
+
const pluginPath = resolve6(pluginsDir, dir, "PLUGIN.md");
|
|
7989
7062
|
let raw2;
|
|
7990
7063
|
try {
|
|
7991
|
-
raw2 =
|
|
7064
|
+
raw2 = readFileSync6(pluginPath, "utf-8");
|
|
7992
7065
|
} catch (err) {
|
|
7993
7066
|
console.warn(`[plugins] ${dir}: failed to read PLUGIN.md for ${agentType} embed: ${String(err)}`);
|
|
7994
7067
|
continue;
|
|
@@ -8012,14 +7085,14 @@ var mcpToolsCache = /* @__PURE__ */ new Map();
|
|
|
8012
7085
|
function fetchMcpToolsList(pluginDir) {
|
|
8013
7086
|
const cached = mcpToolsCache.get(pluginDir);
|
|
8014
7087
|
if (cached) return Promise.resolve(cached);
|
|
8015
|
-
const serverPath =
|
|
8016
|
-
if (!
|
|
7088
|
+
const serverPath = resolve6(PLATFORM_ROOT3, "plugins", pluginDir, "mcp/dist/index.js");
|
|
7089
|
+
if (!existsSync5(serverPath)) return Promise.resolve([]);
|
|
8017
7090
|
const startMs = Date.now();
|
|
8018
7091
|
return new Promise((resolvePromise) => {
|
|
8019
7092
|
const proc = spawn2(process.execPath, [serverPath], {
|
|
8020
7093
|
env: {
|
|
8021
7094
|
...process.env,
|
|
8022
|
-
PLATFORM_ROOT:
|
|
7095
|
+
PLATFORM_ROOT: PLATFORM_ROOT3,
|
|
8023
7096
|
ACCOUNT_ID: "__toolslist__",
|
|
8024
7097
|
PLATFORM_PORT: process.env.PORT ?? "19200"
|
|
8025
7098
|
}
|
|
@@ -8125,7 +7198,7 @@ var SPECIALIST_PLUGIN_DOMAINS = {
|
|
|
8125
7198
|
// agent, so it retains a full manifest entry for routing clarity.
|
|
8126
7199
|
};
|
|
8127
7200
|
async function buildPluginManifest(enabledPlugins) {
|
|
8128
|
-
const pluginsDir =
|
|
7201
|
+
const pluginsDir = resolve6(PLATFORM_ROOT3, "plugins");
|
|
8129
7202
|
let dirs;
|
|
8130
7203
|
try {
|
|
8131
7204
|
dirs = readdirSync2(pluginsDir);
|
|
@@ -8212,16 +7285,16 @@ ${specialist}: ${plugins.join(", ")}`);
|
|
|
8212
7285
|
for (let j = 0; j < adminPlugins.length; j++) {
|
|
8213
7286
|
const { dir, parsed } = adminPlugins[j];
|
|
8214
7287
|
const mcpTools = adminMcpResults[j];
|
|
8215
|
-
const pluginRoot =
|
|
7288
|
+
const pluginRoot = resolve6(pluginsDir, dir);
|
|
8216
7289
|
const skills = [];
|
|
8217
7290
|
const references = [];
|
|
8218
7291
|
const scanDir = (base, prefix, target) => {
|
|
8219
|
-
const scanPath =
|
|
8220
|
-
if (!
|
|
7292
|
+
const scanPath = resolve6(pluginRoot, base);
|
|
7293
|
+
if (!existsSync5(scanPath)) return;
|
|
8221
7294
|
try {
|
|
8222
7295
|
const walk = (current, rel) => {
|
|
8223
7296
|
for (const entry of readdirSync2(current)) {
|
|
8224
|
-
const full =
|
|
7297
|
+
const full = resolve6(current, entry);
|
|
8225
7298
|
try {
|
|
8226
7299
|
const stat5 = statSync3(full);
|
|
8227
7300
|
if (stat5.isDirectory()) {
|
|
@@ -8249,8 +7322,8 @@ ${specialist}: ${plugins.join(", ")}`);
|
|
|
8249
7322
|
toolLines.push(desc ? ` ${tool.name} \u2014 ${desc}` : ` ${tool.name}`);
|
|
8250
7323
|
}
|
|
8251
7324
|
} else if (parsed.tools.length > 0) {
|
|
8252
|
-
const serverPath =
|
|
8253
|
-
if (
|
|
7325
|
+
const serverPath = resolve6(PLATFORM_ROOT3, "plugins", dir, "mcp/dist/index.js");
|
|
7326
|
+
if (existsSync5(serverPath)) {
|
|
8254
7327
|
fallbackSourced++;
|
|
8255
7328
|
console.error(`[plugin-manifest] ${dir}: tools/list empty \u2014 fallback to frontmatter (${parsed.tools.length} tools)`);
|
|
8256
7329
|
}
|
|
@@ -8325,16 +7398,16 @@ function getDefaultAccountId() {
|
|
|
8325
7398
|
return resolveAccount()?.accountId ?? null;
|
|
8326
7399
|
}
|
|
8327
7400
|
function resolveUserAccounts(userId) {
|
|
8328
|
-
if (!
|
|
7401
|
+
if (!existsSync5(ACCOUNTS_DIR)) return [];
|
|
8329
7402
|
const results = [];
|
|
8330
7403
|
const entries = readdirSync2(ACCOUNTS_DIR, { withFileTypes: true });
|
|
8331
7404
|
for (const entry of entries) {
|
|
8332
7405
|
if (!entry.isDirectory()) continue;
|
|
8333
|
-
const configPath2 =
|
|
8334
|
-
if (!
|
|
7406
|
+
const configPath2 = resolve6(ACCOUNTS_DIR, entry.name, "account.json");
|
|
7407
|
+
if (!existsSync5(configPath2)) continue;
|
|
8335
7408
|
let config;
|
|
8336
7409
|
try {
|
|
8337
|
-
config = JSON.parse(
|
|
7410
|
+
config = JSON.parse(readFileSync6(configPath2, "utf-8"));
|
|
8338
7411
|
} catch {
|
|
8339
7412
|
console.error(`[session] account.json corrupt at ${configPath2} \u2014 skipping`);
|
|
8340
7413
|
continue;
|
|
@@ -8343,7 +7416,7 @@ function resolveUserAccounts(userId) {
|
|
|
8343
7416
|
if (adminEntry) {
|
|
8344
7417
|
results.push({
|
|
8345
7418
|
accountId: config.accountId,
|
|
8346
|
-
accountDir:
|
|
7419
|
+
accountDir: resolve6(ACCOUNTS_DIR, entry.name),
|
|
8347
7420
|
config,
|
|
8348
7421
|
role: adminEntry.role
|
|
8349
7422
|
});
|
|
@@ -8560,8 +7633,8 @@ function consumeStalledSubagents(sessionKey) {
|
|
|
8560
7633
|
return stalls && stalls.length > 0 ? stalls : void 0;
|
|
8561
7634
|
}
|
|
8562
7635
|
function streamLogPathFor(accountId, conversationId) {
|
|
8563
|
-
const logDir =
|
|
8564
|
-
const streamLogPath =
|
|
7636
|
+
const logDir = resolve6(ACCOUNTS_DIR, accountId, "logs");
|
|
7637
|
+
const streamLogPath = resolve6(logDir, `claude-agent-stream-${conversationId}.log`);
|
|
8565
7638
|
return { logDir, streamLogPath };
|
|
8566
7639
|
}
|
|
8567
7640
|
function buildSpawnEnv(accountId, accountDir, conversationId) {
|
|
@@ -8571,7 +7644,7 @@ function buildSpawnEnv(accountId, accountDir, conversationId) {
|
|
|
8571
7644
|
const { logDir, streamLogPath } = streamLogPathFor(accountId, conversationId);
|
|
8572
7645
|
return {
|
|
8573
7646
|
...process.env,
|
|
8574
|
-
PLATFORM_ROOT:
|
|
7647
|
+
PLATFORM_ROOT: PLATFORM_ROOT3,
|
|
8575
7648
|
ACCOUNT_DIR: accountDir,
|
|
8576
7649
|
ACCOUNT_ID: accountId,
|
|
8577
7650
|
LOG_DIR: logDir,
|
|
@@ -8582,8 +7655,8 @@ var cachedBrandHostname = null;
|
|
|
8582
7655
|
function readBrandHostname() {
|
|
8583
7656
|
if (cachedBrandHostname !== null) return cachedBrandHostname;
|
|
8584
7657
|
try {
|
|
8585
|
-
const brandPath =
|
|
8586
|
-
const parsed = JSON.parse(
|
|
7658
|
+
const brandPath = resolve6(PLATFORM_ROOT3, "config", "brand.json");
|
|
7659
|
+
const parsed = JSON.parse(readFileSync6(brandPath, "utf-8"));
|
|
8587
7660
|
cachedBrandHostname = typeof parsed.hostname === "string" && parsed.hostname.length > 0 ? parsed.hostname : "maxy";
|
|
8588
7661
|
} catch {
|
|
8589
7662
|
cachedBrandHostname = "maxy";
|
|
@@ -8612,7 +7685,7 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
8612
7685
|
const { logDir: LOG_DIR2, streamLogPath: STREAM_LOG_PATH } = streamLogPathFor(accountId, conversationId);
|
|
8613
7686
|
const baseEnv = {
|
|
8614
7687
|
ACCOUNT_ID: accountId,
|
|
8615
|
-
PLATFORM_ROOT:
|
|
7688
|
+
PLATFORM_ROOT: PLATFORM_ROOT3,
|
|
8616
7689
|
LOG_DIR: LOG_DIR2,
|
|
8617
7690
|
STREAM_LOG_PATH,
|
|
8618
7691
|
NEO4J_URI: requireNeo4jUri()
|
|
@@ -8620,37 +7693,37 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
8620
7693
|
const servers = {
|
|
8621
7694
|
"memory": {
|
|
8622
7695
|
command: "node",
|
|
8623
|
-
args: [
|
|
7696
|
+
args: [resolve6(PLATFORM_ROOT3, "plugins/memory/mcp/dist/index.js")],
|
|
8624
7697
|
env: { ...baseEnv, ...userId ? { USER_ID: userId } : {} }
|
|
8625
7698
|
},
|
|
8626
7699
|
"contacts": {
|
|
8627
7700
|
command: "node",
|
|
8628
|
-
args: [
|
|
7701
|
+
args: [resolve6(PLATFORM_ROOT3, "plugins/contacts/mcp/dist/index.js")],
|
|
8629
7702
|
env: { ...baseEnv }
|
|
8630
7703
|
},
|
|
8631
7704
|
"whatsapp": {
|
|
8632
7705
|
command: "node",
|
|
8633
|
-
args: [
|
|
7706
|
+
args: [resolve6(PLATFORM_ROOT3, "plugins/whatsapp/mcp/dist/index.js")],
|
|
8634
7707
|
env: { ...baseEnv, PLATFORM_PORT: process.env.PORT ?? "19200" }
|
|
8635
7708
|
},
|
|
8636
7709
|
"admin": {
|
|
8637
7710
|
command: "node",
|
|
8638
|
-
args: [
|
|
7711
|
+
args: [resolve6(PLATFORM_ROOT3, "plugins/admin/mcp/dist/index.js")],
|
|
8639
7712
|
env: { ...baseEnv, PLATFORM_PORT: process.env.PORT ?? "19200", ...userId ? { USER_ID: userId } : {} }
|
|
8640
7713
|
},
|
|
8641
7714
|
"scheduling": {
|
|
8642
7715
|
command: "node",
|
|
8643
|
-
args: [
|
|
7716
|
+
args: [resolve6(PLATFORM_ROOT3, "plugins/scheduling/mcp/dist/index.js")],
|
|
8644
7717
|
env: { ...baseEnv }
|
|
8645
7718
|
},
|
|
8646
7719
|
"tasks": {
|
|
8647
7720
|
command: "node",
|
|
8648
|
-
args: [
|
|
7721
|
+
args: [resolve6(PLATFORM_ROOT3, "plugins/tasks/mcp/dist/index.js")],
|
|
8649
7722
|
env: { ...baseEnv }
|
|
8650
7723
|
},
|
|
8651
7724
|
"email": {
|
|
8652
7725
|
command: "node",
|
|
8653
|
-
args: [
|
|
7726
|
+
args: [resolve6(PLATFORM_ROOT3, "plugins/email/mcp/dist/index.js")],
|
|
8654
7727
|
env: { ...baseEnv }
|
|
8655
7728
|
},
|
|
8656
7729
|
// Workflows MCP — persistent admin-session server for list/get/update/delete/
|
|
@@ -8661,7 +7734,7 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
8661
7734
|
// ToolSearches fruitlessly before degrading to a task-create stand-in (Task 571).
|
|
8662
7735
|
"workflows": {
|
|
8663
7736
|
command: "node",
|
|
8664
|
-
args: [
|
|
7737
|
+
args: [resolve6(PLATFORM_ROOT3, "plugins/workflows/mcp/dist/index.js")],
|
|
8665
7738
|
env: { ...baseEnv }
|
|
8666
7739
|
},
|
|
8667
7740
|
// Playwright MCP server — browser automation for browser-specialist.
|
|
@@ -8683,7 +7756,7 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
8683
7756
|
// MAXY-PRD.md:627, not in any application-layer filter).
|
|
8684
7757
|
"graph": {
|
|
8685
7758
|
command: "node",
|
|
8686
|
-
args: [
|
|
7759
|
+
args: [resolve6(PLATFORM_ROOT3, "lib/graph-mcp/dist/index.js")],
|
|
8687
7760
|
env: {
|
|
8688
7761
|
...baseEnv,
|
|
8689
7762
|
BRAND: readBrandHostname(),
|
|
@@ -8699,7 +7772,7 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
8699
7772
|
if (tgBotToken) {
|
|
8700
7773
|
servers["telegram"] = {
|
|
8701
7774
|
command: "node",
|
|
8702
|
-
args: [
|
|
7775
|
+
args: [resolve6(PLATFORM_ROOT3, "plugins/telegram/mcp/dist/index.js")],
|
|
8703
7776
|
env: { ...baseEnv, TELEGRAM_BOT_TOKEN: tgBotToken }
|
|
8704
7777
|
};
|
|
8705
7778
|
} else {
|
|
@@ -8707,11 +7780,11 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
8707
7780
|
}
|
|
8708
7781
|
servers["cloudflare"] = {
|
|
8709
7782
|
command: "node",
|
|
8710
|
-
args: [
|
|
7783
|
+
args: [resolve6(PLATFORM_ROOT3, "plugins/cloudflare/mcp/dist/index.js")],
|
|
8711
7784
|
env: { ...baseEnv, PLATFORM_PORT: process.env.PORT ?? "19200" }
|
|
8712
7785
|
};
|
|
8713
7786
|
if (Array.isArray(enabledPlugins) && enabledPlugins.length > 0) {
|
|
8714
|
-
const pluginsDir =
|
|
7787
|
+
const pluginsDir = resolve6(PLATFORM_ROOT3, "plugins");
|
|
8715
7788
|
let dirs;
|
|
8716
7789
|
try {
|
|
8717
7790
|
dirs = readdirSync2(pluginsDir);
|
|
@@ -8734,8 +7807,8 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
8734
7807
|
continue;
|
|
8735
7808
|
}
|
|
8736
7809
|
}
|
|
8737
|
-
const mcpEntry =
|
|
8738
|
-
if (!
|
|
7810
|
+
const mcpEntry = resolve6(PLATFORM_ROOT3, "plugins", dir, "mcp/dist/index.js");
|
|
7811
|
+
if (!existsSync5(mcpEntry)) continue;
|
|
8739
7812
|
servers[dir] = {
|
|
8740
7813
|
command: "node",
|
|
8741
7814
|
args: [mcpEntry],
|
|
@@ -8870,7 +7943,7 @@ var ADMIN_CORE_TOOLS = [
|
|
|
8870
7943
|
function getAdminAllowedTools(enabledPlugins) {
|
|
8871
7944
|
const tools = [...ADMIN_CORE_TOOLS];
|
|
8872
7945
|
if (Array.isArray(enabledPlugins) && enabledPlugins.length > 0) {
|
|
8873
|
-
const pluginsDir =
|
|
7946
|
+
const pluginsDir = resolve6(PLATFORM_ROOT3, "plugins");
|
|
8874
7947
|
let dirs;
|
|
8875
7948
|
try {
|
|
8876
7949
|
dirs = readdirSync2(pluginsDir);
|
|
@@ -8978,18 +8051,18 @@ ${message.slice(0, QUERY_CLASSIFIER_MSG_CAP)}`
|
|
|
8978
8051
|
}
|
|
8979
8052
|
}
|
|
8980
8053
|
async function fetchMemoryContext(accountId, query, sessionKey, options) {
|
|
8981
|
-
const serverPath =
|
|
8982
|
-
if (!
|
|
8054
|
+
const serverPath = resolve6(PLATFORM_ROOT3, "plugins/memory/mcp/dist/index.js");
|
|
8055
|
+
if (!existsSync5(serverPath)) {
|
|
8983
8056
|
console.error(`[fetchMemoryContext] MCP server not found: ${serverPath}`);
|
|
8984
8057
|
return null;
|
|
8985
8058
|
}
|
|
8986
8059
|
const startMs = Date.now();
|
|
8987
|
-
return new Promise((
|
|
8060
|
+
return new Promise((resolve29) => {
|
|
8988
8061
|
const proc = spawn2(process.execPath, [serverPath], {
|
|
8989
8062
|
env: {
|
|
8990
8063
|
...process.env,
|
|
8991
8064
|
ACCOUNT_ID: accountId,
|
|
8992
|
-
PLATFORM_ROOT:
|
|
8065
|
+
PLATFORM_ROOT: PLATFORM_ROOT3,
|
|
8993
8066
|
READ_ONLY: "true",
|
|
8994
8067
|
ALLOWED_SCOPES: "public,shared",
|
|
8995
8068
|
...sessionKey ? { SESSION_ID: sessionKey } : {},
|
|
@@ -9013,7 +8086,7 @@ async function fetchMemoryContext(accountId, query, sessionKey, options) {
|
|
|
9013
8086
|
} else {
|
|
9014
8087
|
console.error(`[fetchMemoryContext] failed: ${reason} (${elapsed}ms)${stderrBuf ? ` stderr: ${stderrBuf.slice(0, 500)}` : ""}`);
|
|
9015
8088
|
}
|
|
9016
|
-
|
|
8089
|
+
resolve29(value);
|
|
9017
8090
|
};
|
|
9018
8091
|
proc.stdout.on("data", (chunk) => {
|
|
9019
8092
|
buffer += chunk.toString();
|
|
@@ -9076,8 +8149,8 @@ async function fetchMemoryContext(accountId, query, sessionKey, options) {
|
|
|
9076
8149
|
}
|
|
9077
8150
|
async function compactTrimmedMessages(accountId, trimmedMessages) {
|
|
9078
8151
|
if (trimmedMessages.length === 0) return true;
|
|
9079
|
-
const serverPath =
|
|
9080
|
-
if (!
|
|
8152
|
+
const serverPath = resolve6(PLATFORM_ROOT3, "plugins/memory/mcp/dist/index.js");
|
|
8153
|
+
if (!existsSync5(serverPath)) return false;
|
|
9081
8154
|
const briefing = trimmedMessages.map((m) => `[${m.role.toUpperCase()}] ${m.content}`).join("\n\n");
|
|
9082
8155
|
return new Promise((resolvePromise) => {
|
|
9083
8156
|
const proc = spawn2(process.execPath, [serverPath], {
|
|
@@ -9394,8 +8467,8 @@ Then respond with only: [COMPACTED]`;
|
|
|
9394
8467
|
var COMPACTION_TIMEOUT_MS = 45e3;
|
|
9395
8468
|
async function* runCompactionTurn(accountDir, accountId, systemPrompt, resumeSessionId, adminModel, conversationId, enabledPlugins) {
|
|
9396
8469
|
const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, conversationId, void 0, enabledPlugins) });
|
|
9397
|
-
const specialistsDir =
|
|
9398
|
-
if (!
|
|
8470
|
+
const specialistsDir = resolve6(accountDir, "specialists");
|
|
8471
|
+
if (!existsSync5(specialistsDir)) agentLogStream("claude-agent-compaction-stream", accountDir, conversationId).write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
|
|
9399
8472
|
`);
|
|
9400
8473
|
const args = [
|
|
9401
8474
|
"--print",
|
|
@@ -9668,7 +8741,7 @@ async function* parseClaudeStream(proc, streamLog, adminModel, conversationId, a
|
|
|
9668
8741
|
const { logDir } = streamLogPathFor(accountId, conversationId);
|
|
9669
8742
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
9670
8743
|
for (const s of failed) {
|
|
9671
|
-
const stderrPath =
|
|
8744
|
+
const stderrPath = resolve6(logDir, `mcp-${s.name}-stderr-${date}.log`);
|
|
9672
8745
|
let tail = "(no stderr file)";
|
|
9673
8746
|
try {
|
|
9674
8747
|
const stats = statSync3(stderrPath);
|
|
@@ -10310,8 +9383,8 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
|
|
|
10310
9383
|
}
|
|
10311
9384
|
const ccUserId = sessionKey ? getUserIdForSession(sessionKey) : void 0;
|
|
10312
9385
|
const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, spawnConvId, ccUserId, enabledPlugins) });
|
|
10313
|
-
const specialistsDir =
|
|
10314
|
-
if (!
|
|
9386
|
+
const specialistsDir = resolve6(accountDir, "specialists");
|
|
9387
|
+
if (!existsSync5(specialistsDir)) agentLogStream("claude-agent-stream", accountDir, spawnConvId).write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
|
|
10315
9388
|
`);
|
|
10316
9389
|
const args = [
|
|
10317
9390
|
"--print",
|
|
@@ -10661,8 +9734,8 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
|
|
|
10661
9734
|
`);
|
|
10662
9735
|
const managedUserId = getUserIdForSession(sessionKey);
|
|
10663
9736
|
const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, managedConvId, managedUserId, enabledPlugins) });
|
|
10664
|
-
const specialistsDir =
|
|
10665
|
-
if (!
|
|
9737
|
+
const specialistsDir = resolve6(accountDir, "specialists");
|
|
9738
|
+
if (!existsSync5(specialistsDir)) streamLog.write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
|
|
10666
9739
|
`);
|
|
10667
9740
|
const fullMessage = attachments.length > 0 ? message + buildAttachmentMetaText(attachments) : message;
|
|
10668
9741
|
const args = [
|
|
@@ -11355,10 +10428,10 @@ ${sessionContext}`;
|
|
|
11355
10428
|
console.log(`[onboarding-inject] accountId=${accountId.slice(0, 8)}\u2026 error=neo4j-unreachable injected=false`);
|
|
11356
10429
|
} else if (onboardingStep < 8) {
|
|
11357
10430
|
const GENERIC_FALLBACK = "At every session start, call `onboarding-get`. If `currentStep` is less than 8, load the onboarding skill via `plugin-read` (find its path in the manifest under `admin`) and follow it \u2014 before any business setup. If `onboarding-get` fails (Neo4j unreachable), tell the user and skip onboarding for this session \u2014 it resumes automatically when the graph is available.";
|
|
11358
|
-
const skillPath =
|
|
10431
|
+
const skillPath = resolve6(PLATFORM_ROOT3, "plugins/admin/skills/onboarding/SKILL.md");
|
|
11359
10432
|
let skillContent = "";
|
|
11360
10433
|
try {
|
|
11361
|
-
skillContent =
|
|
10434
|
+
skillContent = readFileSync6(skillPath, "utf-8");
|
|
11362
10435
|
} catch (err) {
|
|
11363
10436
|
console.error(`[onboarding-inject] accountId=${accountId.slice(0, 8)}\u2026 error=skill-read-failed path=${skillPath} reason=${err instanceof Error ? err.message : String(err)}`);
|
|
11364
10437
|
}
|
|
@@ -11408,9 +10481,9 @@ ${body}`;
|
|
|
11408
10481
|
|
|
11409
10482
|
${manifest}`;
|
|
11410
10483
|
}
|
|
11411
|
-
const graphRefPath =
|
|
10484
|
+
const graphRefPath = resolve6(PLATFORM_ROOT3, "plugins/memory/references/graph-primitives.md");
|
|
11412
10485
|
try {
|
|
11413
|
-
const graphRef =
|
|
10486
|
+
const graphRef = readFileSync6(graphRefPath, "utf-8");
|
|
11414
10487
|
baseSystemPrompt += `
|
|
11415
10488
|
|
|
11416
10489
|
${graphRef}`;
|
|
@@ -11625,8 +10698,8 @@ var clientIpMiddleware = async (c, next) => {
|
|
|
11625
10698
|
};
|
|
11626
10699
|
|
|
11627
10700
|
// server/routes/health.ts
|
|
11628
|
-
import { existsSync as
|
|
11629
|
-
import { createConnection as
|
|
10701
|
+
import { existsSync as existsSync10, readFileSync as readFileSync11 } from "fs";
|
|
10702
|
+
import { createConnection as createConnection3 } from "net";
|
|
11630
10703
|
|
|
11631
10704
|
// app/lib/network.ts
|
|
11632
10705
|
import { networkInterfaces } from "os";
|
|
@@ -11650,8 +10723,8 @@ function getLanIp() {
|
|
|
11650
10723
|
import { basename as basename2 } from "path";
|
|
11651
10724
|
|
|
11652
10725
|
// app/lib/review-detector/rules.ts
|
|
11653
|
-
import { readFileSync as
|
|
11654
|
-
import { resolve as
|
|
10726
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync6, statSync as statSync4, mkdirSync as mkdirSync6, renameSync as renameSync2 } from "fs";
|
|
10727
|
+
import { resolve as resolve7, dirname as dirname3 } from "path";
|
|
11655
10728
|
var DEFAULT_SCAN_INTERVAL_MS = 5e3;
|
|
11656
10729
|
var RATE_LIMIT_PATTERN = "rate[- ]?limit(?:ed| reached| hit)|(?:HTTP|status)[^a-z]{0,3}429|too many requests";
|
|
11657
10730
|
var RATE_LIMIT_PATTERN_V1 = "\\b429\\b|rate.?limit|too.?many.?requests";
|
|
@@ -12080,12 +11153,12 @@ function defaultRules() {
|
|
|
12080
11153
|
];
|
|
12081
11154
|
}
|
|
12082
11155
|
function rulesFilePath(configDir2) {
|
|
12083
|
-
return
|
|
11156
|
+
return resolve7(configDir2, "review-rules.json");
|
|
12084
11157
|
}
|
|
12085
11158
|
function ensureRulesFile(configDir2) {
|
|
12086
11159
|
const path2 = rulesFilePath(configDir2);
|
|
12087
|
-
if (
|
|
12088
|
-
|
|
11160
|
+
if (existsSync6(path2)) return { created: false, path: path2 };
|
|
11161
|
+
mkdirSync6(dirname3(path2), { recursive: true });
|
|
12089
11162
|
const body = {
|
|
12090
11163
|
scanIntervalMs: DEFAULT_SCAN_INTERVAL_MS,
|
|
12091
11164
|
rules: defaultRules()
|
|
@@ -12095,10 +11168,10 @@ function ensureRulesFile(configDir2) {
|
|
|
12095
11168
|
}
|
|
12096
11169
|
function loadRules(configDir2) {
|
|
12097
11170
|
const path2 = rulesFilePath(configDir2);
|
|
12098
|
-
if (!
|
|
11171
|
+
if (!existsSync6(path2)) {
|
|
12099
11172
|
throw new Error(`rules file missing at ${path2}`);
|
|
12100
11173
|
}
|
|
12101
|
-
const raw2 =
|
|
11174
|
+
const raw2 = readFileSync7(path2, "utf-8");
|
|
12102
11175
|
let parsed;
|
|
12103
11176
|
try {
|
|
12104
11177
|
parsed = JSON.parse(raw2);
|
|
@@ -12121,7 +11194,7 @@ function saveRules(configDir2, file) {
|
|
|
12121
11194
|
}
|
|
12122
11195
|
function atomicWriteJson(path2, body) {
|
|
12123
11196
|
const tmp = `${path2}.tmp.${process.pid}.${Date.now()}`;
|
|
12124
|
-
|
|
11197
|
+
writeFileSync6(tmp, JSON.stringify(body, null, 2) + "\n", "utf-8");
|
|
12125
11198
|
renameSync2(tmp, path2);
|
|
12126
11199
|
}
|
|
12127
11200
|
function validateRulesFile(input, sourceLabel) {
|
|
@@ -12263,16 +11336,16 @@ function validateRule(input, label, seenIds) {
|
|
|
12263
11336
|
}
|
|
12264
11337
|
|
|
12265
11338
|
// app/lib/review-detector/sources.ts
|
|
12266
|
-
import { existsSync as
|
|
12267
|
-
import { resolve as
|
|
11339
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3, statSync as statSync5, writeFileSync as writeFileSync7, renameSync as renameSync3, mkdirSync as mkdirSync7, openSync as openSync3, readSync as readSync3, closeSync as closeSync3, readFileSync as readFileSync8 } from "fs";
|
|
11340
|
+
import { resolve as resolve8, join as join4, basename, dirname as dirname4 } from "path";
|
|
12268
11341
|
function tailStatePath(configDir2) {
|
|
12269
|
-
return
|
|
11342
|
+
return resolve8(configDir2, "review-state.json");
|
|
12270
11343
|
}
|
|
12271
11344
|
function loadTailState(configDir2) {
|
|
12272
11345
|
const path2 = tailStatePath(configDir2);
|
|
12273
|
-
if (!
|
|
11346
|
+
if (!existsSync7(path2)) return {};
|
|
12274
11347
|
try {
|
|
12275
|
-
const raw2 =
|
|
11348
|
+
const raw2 = readFileSync8(path2, "utf-8");
|
|
12276
11349
|
const parsed = JSON.parse(raw2);
|
|
12277
11350
|
if (!parsed || typeof parsed !== "object") return {};
|
|
12278
11351
|
const clean = {};
|
|
@@ -12290,26 +11363,26 @@ function loadTailState(configDir2) {
|
|
|
12290
11363
|
}
|
|
12291
11364
|
function saveTailState(configDir2, state) {
|
|
12292
11365
|
const path2 = tailStatePath(configDir2);
|
|
12293
|
-
|
|
11366
|
+
mkdirSync7(dirname4(path2), { recursive: true });
|
|
12294
11367
|
const tmp = `${path2}.tmp.${process.pid}.${Date.now()}`;
|
|
12295
|
-
|
|
11368
|
+
writeFileSync7(tmp, JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
12296
11369
|
renameSync3(tmp, path2);
|
|
12297
11370
|
}
|
|
12298
11371
|
function discoverSourceFiles(configDir2, accountLogDir2, logicalSource) {
|
|
12299
11372
|
if (logicalSource === "server") {
|
|
12300
|
-
const p =
|
|
12301
|
-
return
|
|
11373
|
+
const p = resolve8(configDir2, "logs", "server.log");
|
|
11374
|
+
return existsSync7(p) ? [{ logicalSource: "server", filepath: p }] : [];
|
|
12302
11375
|
}
|
|
12303
11376
|
if (logicalSource === "vnc") {
|
|
12304
|
-
const p =
|
|
12305
|
-
return
|
|
11377
|
+
const p = resolve8(configDir2, "logs", "vnc-boot.log");
|
|
11378
|
+
return existsSync7(p) ? [{ logicalSource: "vnc", filepath: p }] : [];
|
|
12306
11379
|
}
|
|
12307
11380
|
if (logicalSource === "cloudflared") {
|
|
12308
11381
|
const files2 = [];
|
|
12309
|
-
const daemon =
|
|
12310
|
-
if (
|
|
12311
|
-
const login =
|
|
12312
|
-
if (
|
|
11382
|
+
const daemon = resolve8(configDir2, "logs", "cloudflared.log");
|
|
11383
|
+
if (existsSync7(daemon)) files2.push({ logicalSource: "cloudflared", filepath: daemon });
|
|
11384
|
+
const login = resolve8(configDir2, "logs", "cloudflared-login.log");
|
|
11385
|
+
if (existsSync7(login)) files2.push({ logicalSource: "cloudflared", filepath: login });
|
|
12313
11386
|
return files2;
|
|
12314
11387
|
}
|
|
12315
11388
|
const prefix = {
|
|
@@ -12319,7 +11392,7 @@ function discoverSourceFiles(configDir2, accountLogDir2, logicalSource) {
|
|
|
12319
11392
|
public: "public-agent-stream-",
|
|
12320
11393
|
mcp: "mcp-"
|
|
12321
11394
|
}[logicalSource];
|
|
12322
|
-
if (!
|
|
11395
|
+
if (!existsSync7(accountLogDir2)) return [];
|
|
12323
11396
|
const files = [];
|
|
12324
11397
|
let scanned = 0;
|
|
12325
11398
|
let skippedPrefixMismatch = 0;
|
|
@@ -12329,7 +11402,7 @@ function discoverSourceFiles(configDir2, accountLogDir2, logicalSource) {
|
|
|
12329
11402
|
const matchesPrefix = entry.startsWith(prefix);
|
|
12330
11403
|
const isLog = entry.endsWith(".log");
|
|
12331
11404
|
if (matchesPrefix && isLog) {
|
|
12332
|
-
files.push({ logicalSource, filepath:
|
|
11405
|
+
files.push({ logicalSource, filepath: join4(accountLogDir2, entry) });
|
|
12333
11406
|
} else if (!matchesPrefix) {
|
|
12334
11407
|
skippedPrefixMismatch += 1;
|
|
12335
11408
|
} else {
|
|
@@ -12361,7 +11434,7 @@ function discoverAllSources(configDir2, accountLogDir2) {
|
|
|
12361
11434
|
];
|
|
12362
11435
|
}
|
|
12363
11436
|
function readNewLines(filepath, prev) {
|
|
12364
|
-
if (!
|
|
11437
|
+
if (!existsSync7(filepath)) return null;
|
|
12365
11438
|
const stat5 = statSync5(filepath);
|
|
12366
11439
|
const size = stat5.size;
|
|
12367
11440
|
const inode = stat5.ino;
|
|
@@ -12414,12 +11487,12 @@ function readNewLines(filepath, prev) {
|
|
|
12414
11487
|
}
|
|
12415
11488
|
}
|
|
12416
11489
|
function countRecentWrites(dir, sinceMs) {
|
|
12417
|
-
if (!
|
|
11490
|
+
if (!existsSync7(dir)) return 0;
|
|
12418
11491
|
let count = 0;
|
|
12419
11492
|
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
12420
11493
|
if (!entry.isFile()) continue;
|
|
12421
11494
|
try {
|
|
12422
|
-
const st = statSync5(
|
|
11495
|
+
const st = statSync5(join4(dir, entry.name));
|
|
12423
11496
|
if (st.mtimeMs >= sinceMs) count += 1;
|
|
12424
11497
|
} catch {
|
|
12425
11498
|
}
|
|
@@ -12427,7 +11500,7 @@ function countRecentWrites(dir, sinceMs) {
|
|
|
12427
11500
|
return count;
|
|
12428
11501
|
}
|
|
12429
11502
|
function fileLastWriteMs(path2) {
|
|
12430
|
-
if (!
|
|
11503
|
+
if (!existsSync7(path2)) return null;
|
|
12431
11504
|
try {
|
|
12432
11505
|
return statSync5(path2).mtimeMs;
|
|
12433
11506
|
} catch {
|
|
@@ -12435,31 +11508,31 @@ function fileLastWriteMs(path2) {
|
|
|
12435
11508
|
}
|
|
12436
11509
|
}
|
|
12437
11510
|
function accountLogDir(accountDir) {
|
|
12438
|
-
return
|
|
11511
|
+
return resolve8(accountDir, "logs");
|
|
12439
11512
|
}
|
|
12440
11513
|
function sourceKey(file) {
|
|
12441
11514
|
return `${file.logicalSource}:${basename(file.filepath)}`;
|
|
12442
11515
|
}
|
|
12443
11516
|
|
|
12444
11517
|
// app/lib/review-detector/writer.ts
|
|
12445
|
-
import { appendFileSync as
|
|
12446
|
-
import { resolve as
|
|
11518
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync9, writeFileSync as writeFileSync8, renameSync as renameSync4, statSync as statSync6 } from "fs";
|
|
11519
|
+
import { resolve as resolve9, dirname as dirname5 } from "path";
|
|
12447
11520
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
12448
11521
|
function reviewLogPath(configDir2) {
|
|
12449
|
-
return
|
|
11522
|
+
return resolve9(configDir2, "logs", "review.log");
|
|
12450
11523
|
}
|
|
12451
11524
|
function pendingAlertsPath(configDir2) {
|
|
12452
|
-
return
|
|
11525
|
+
return resolve9(configDir2, "review-pending-alerts.jsonl");
|
|
12453
11526
|
}
|
|
12454
11527
|
function reviewLog(configDir2, event) {
|
|
12455
11528
|
const path2 = reviewLogPath(configDir2);
|
|
12456
11529
|
try {
|
|
12457
|
-
|
|
11530
|
+
mkdirSync8(dirname5(path2), { recursive: true });
|
|
12458
11531
|
const line = `${new Date(
|
|
12459
11532
|
typeof event.ts === "number" ? event.ts : Date.now()
|
|
12460
11533
|
).toISOString()} [review] ${JSON.stringify(event)}
|
|
12461
11534
|
`;
|
|
12462
|
-
|
|
11535
|
+
appendFileSync3(path2, line, "utf-8");
|
|
12463
11536
|
} catch (err) {
|
|
12464
11537
|
console.error(`[review] failed to write review log at ${path2}: ${err instanceof Error ? err.message : String(err)}`);
|
|
12465
11538
|
}
|
|
@@ -12571,17 +11644,17 @@ async function upsertReviewAlert(accountId, match2) {
|
|
|
12571
11644
|
function queueAlert(configDir2, accountId, match2) {
|
|
12572
11645
|
const path2 = pendingAlertsPath(configDir2);
|
|
12573
11646
|
try {
|
|
12574
|
-
|
|
11647
|
+
mkdirSync8(dirname5(path2), { recursive: true });
|
|
12575
11648
|
const line = JSON.stringify({ accountId, match: match2 }) + "\n";
|
|
12576
|
-
|
|
11649
|
+
appendFileSync3(path2, line, "utf-8");
|
|
12577
11650
|
} catch (err) {
|
|
12578
11651
|
console.error(`[review] failed to queue alert at ${path2}: ${err instanceof Error ? err.message : String(err)}`);
|
|
12579
11652
|
}
|
|
12580
11653
|
}
|
|
12581
11654
|
async function drainPendingAlerts(configDir2) {
|
|
12582
11655
|
const path2 = pendingAlertsPath(configDir2);
|
|
12583
|
-
if (!
|
|
12584
|
-
const raw2 =
|
|
11656
|
+
if (!existsSync8(path2)) return { drained: 0, remaining: 0 };
|
|
11657
|
+
const raw2 = readFileSync9(path2, "utf-8");
|
|
12585
11658
|
const lines = raw2.split("\n").filter((l) => l.trim().length > 0);
|
|
12586
11659
|
if (lines.length === 0) return { drained: 0, remaining: 0 };
|
|
12587
11660
|
const remaining = [];
|
|
@@ -12602,9 +11675,9 @@ async function drainPendingAlerts(configDir2) {
|
|
|
12602
11675
|
}
|
|
12603
11676
|
const tmp = `${path2}.tmp.${process.pid}.${Date.now()}`;
|
|
12604
11677
|
if (remaining.length > 0) {
|
|
12605
|
-
|
|
11678
|
+
writeFileSync8(tmp, remaining.join("\n") + "\n", "utf-8");
|
|
12606
11679
|
} else {
|
|
12607
|
-
|
|
11680
|
+
writeFileSync8(tmp, "", "utf-8");
|
|
12608
11681
|
}
|
|
12609
11682
|
renameSync4(tmp, path2);
|
|
12610
11683
|
return { drained, remaining: remaining.length };
|
|
@@ -12704,7 +11777,7 @@ async function bootDetector() {
|
|
|
12704
11777
|
}
|
|
12705
11778
|
|
|
12706
11779
|
// app/lib/review-detector/scan-loop.ts
|
|
12707
|
-
import { resolve as
|
|
11780
|
+
import { resolve as resolve10 } from "path";
|
|
12708
11781
|
|
|
12709
11782
|
// app/lib/review-detector/evaluator.ts
|
|
12710
11783
|
var SAMPLE_MAX_CHARS = 500;
|
|
@@ -12998,14 +12071,14 @@ async function runScanCycle(runtime) {
|
|
|
12998
12071
|
match2 = result.match;
|
|
12999
12072
|
}
|
|
13000
12073
|
} else if (rule.type === "file-write-storm") {
|
|
13001
|
-
const dir =
|
|
12074
|
+
const dir = resolve10(runtime.configDir, rule.watchPath ?? "");
|
|
13002
12075
|
const sinceMs = cycleStart - rule.thresholdWindowMinutes * 6e4;
|
|
13003
12076
|
const count = countRecentWrites(dir, sinceMs);
|
|
13004
12077
|
const result = evaluateFileWriteStormRule(rule, count, state, cycleStart);
|
|
13005
12078
|
state = result.state;
|
|
13006
12079
|
match2 = result.match;
|
|
13007
12080
|
} else if (rule.type === "stale-log") {
|
|
13008
|
-
const trackedPath =
|
|
12081
|
+
const trackedPath = resolve10(runtime.configDir, rule.watchPath ?? "");
|
|
13009
12082
|
const lastMs = fileLastWriteMs(trackedPath);
|
|
13010
12083
|
const result = evaluateStaleLogRule(rule, lastMs, state, cycleStart);
|
|
13011
12084
|
state = result.state;
|
|
@@ -13244,20 +12317,20 @@ var WhatsAppConfigSchema = z.object({
|
|
|
13244
12317
|
});
|
|
13245
12318
|
|
|
13246
12319
|
// app/lib/whatsapp/config-persist.ts
|
|
13247
|
-
import { readFileSync as
|
|
13248
|
-
import { resolve as
|
|
12320
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync9, existsSync as existsSync9 } from "fs";
|
|
12321
|
+
import { resolve as resolve11, join as join5 } from "path";
|
|
13249
12322
|
var TAG3 = "[whatsapp:config]";
|
|
13250
12323
|
function configPath(accountDir) {
|
|
13251
|
-
return
|
|
12324
|
+
return resolve11(accountDir, "account.json");
|
|
13252
12325
|
}
|
|
13253
12326
|
function readConfig(accountDir) {
|
|
13254
12327
|
const path2 = configPath(accountDir);
|
|
13255
|
-
if (!
|
|
13256
|
-
return JSON.parse(
|
|
12328
|
+
if (!existsSync9(path2)) throw new Error(`account.json not found at ${path2}`);
|
|
12329
|
+
return JSON.parse(readFileSync10(path2, "utf-8"));
|
|
13257
12330
|
}
|
|
13258
12331
|
function writeConfig(accountDir, config) {
|
|
13259
12332
|
const path2 = configPath(accountDir);
|
|
13260
|
-
|
|
12333
|
+
writeFileSync9(path2, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
13261
12334
|
}
|
|
13262
12335
|
function reloadManagerConfig(accountDir) {
|
|
13263
12336
|
try {
|
|
@@ -13427,8 +12500,8 @@ function setPublicAgent(accountDir, slug) {
|
|
|
13427
12500
|
if (!trimmed) {
|
|
13428
12501
|
return { ok: false, error: "Agent slug cannot be empty." };
|
|
13429
12502
|
}
|
|
13430
|
-
const agentConfigPath =
|
|
13431
|
-
if (!
|
|
12503
|
+
const agentConfigPath = join5(accountDir, "agents", trimmed, "config.json");
|
|
12504
|
+
if (!existsSync9(agentConfigPath)) {
|
|
13432
12505
|
return { ok: false, error: `Agent "${trimmed}" not found \u2014 no config.json at ${agentConfigPath}. Check the agent slug and try again.` };
|
|
13433
12506
|
}
|
|
13434
12507
|
try {
|
|
@@ -13703,7 +12776,7 @@ var credsSaveQueue = Promise.resolve();
|
|
|
13703
12776
|
async function drainCredsSaveQueue(timeoutMs = 5e3) {
|
|
13704
12777
|
console.error(`${TAG5} draining credential save queue\u2026`);
|
|
13705
12778
|
const timer = new Promise(
|
|
13706
|
-
(
|
|
12779
|
+
(resolve29) => setTimeout(() => resolve29("timeout"), timeoutMs)
|
|
13707
12780
|
);
|
|
13708
12781
|
const result = await Promise.race([
|
|
13709
12782
|
credsSaveQueue.then(() => "drained"),
|
|
@@ -13831,11 +12904,11 @@ async function createWaSocket(opts) {
|
|
|
13831
12904
|
return sock;
|
|
13832
12905
|
}
|
|
13833
12906
|
async function waitForConnection(sock) {
|
|
13834
|
-
return new Promise((
|
|
12907
|
+
return new Promise((resolve29, reject) => {
|
|
13835
12908
|
const handler = (update) => {
|
|
13836
12909
|
if (update.connection === "open") {
|
|
13837
12910
|
sock.ev.off("connection.update", handler);
|
|
13838
|
-
|
|
12911
|
+
resolve29();
|
|
13839
12912
|
}
|
|
13840
12913
|
if (update.connection === "close") {
|
|
13841
12914
|
sock.ev.off("connection.update", handler);
|
|
@@ -13949,14 +13022,14 @@ ${inspected}`;
|
|
|
13949
13022
|
return inspect2(err, INSPECT_OPTS2);
|
|
13950
13023
|
}
|
|
13951
13024
|
function withTimeout(label, promise, timeoutMs) {
|
|
13952
|
-
return new Promise((
|
|
13025
|
+
return new Promise((resolve29, reject) => {
|
|
13953
13026
|
const timer = setTimeout(() => {
|
|
13954
13027
|
reject(new Error(`${label} timed out after ${timeoutMs}ms`));
|
|
13955
13028
|
}, timeoutMs);
|
|
13956
13029
|
promise.then(
|
|
13957
13030
|
(value) => {
|
|
13958
13031
|
clearTimeout(timer);
|
|
13959
|
-
|
|
13032
|
+
resolve29(value);
|
|
13960
13033
|
},
|
|
13961
13034
|
(err) => {
|
|
13962
13035
|
clearTimeout(timer);
|
|
@@ -14468,7 +13541,7 @@ async function sendMediaMessage(sock, to, media, opts) {
|
|
|
14468
13541
|
// app/lib/whatsapp/inbound/media.ts
|
|
14469
13542
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
14470
13543
|
import { writeFile, mkdir } from "fs/promises";
|
|
14471
|
-
import { join as
|
|
13544
|
+
import { join as join6 } from "path";
|
|
14472
13545
|
import {
|
|
14473
13546
|
downloadMediaMessage,
|
|
14474
13547
|
downloadContentFromMessage,
|
|
@@ -14554,7 +13627,7 @@ async function downloadInboundMedia(msg, sock, opts) {
|
|
|
14554
13627
|
await mkdir(MEDIA_DIR, { recursive: true });
|
|
14555
13628
|
const ext = mimeToExt(mimetype ?? "application/octet-stream");
|
|
14556
13629
|
const filename = `${randomUUID5()}.${ext}`;
|
|
14557
|
-
const filePath =
|
|
13630
|
+
const filePath = join6(MEDIA_DIR, filename);
|
|
14558
13631
|
await writeFile(filePath, buffer);
|
|
14559
13632
|
const sizeKB = (buffer.length / 1024).toFixed(0);
|
|
14560
13633
|
console.error(`${TAG9} media downloaded type=${mimetype ?? "unknown"} size=${sizeKB}KB path=${filePath}`);
|
|
@@ -14894,6 +13967,12 @@ function storeMessage(storeKey, entry) {
|
|
|
14894
13967
|
console.error(`${TAG13} message store trimmed for ${storeKey}: ${trimmed} oldest messages removed`);
|
|
14895
13968
|
}
|
|
14896
13969
|
}
|
|
13970
|
+
function deriveSessionKey(input) {
|
|
13971
|
+
if (input.isOwnerMirror || input.agentType === "admin") {
|
|
13972
|
+
return `whatsapp:${input.accountId}`;
|
|
13973
|
+
}
|
|
13974
|
+
return `whatsapp:${input.accountId}:${input.senderPhone}`;
|
|
13975
|
+
}
|
|
14897
13976
|
async function init(opts) {
|
|
14898
13977
|
if (initialized) {
|
|
14899
13978
|
console.error(`${TAG13} already initialized`);
|
|
@@ -15156,11 +14235,11 @@ async function connectWithReconnect(conn) {
|
|
|
15156
14235
|
console.error(
|
|
15157
14236
|
`${TAG13} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
|
|
15158
14237
|
);
|
|
15159
|
-
await new Promise((
|
|
15160
|
-
const timer = setTimeout(
|
|
14238
|
+
await new Promise((resolve29) => {
|
|
14239
|
+
const timer = setTimeout(resolve29, delay);
|
|
15161
14240
|
conn.abortController.signal.addEventListener("abort", () => {
|
|
15162
14241
|
clearTimeout(timer);
|
|
15163
|
-
|
|
14242
|
+
resolve29();
|
|
15164
14243
|
}, { once: true });
|
|
15165
14244
|
});
|
|
15166
14245
|
}
|
|
@@ -15168,16 +14247,16 @@ async function connectWithReconnect(conn) {
|
|
|
15168
14247
|
}
|
|
15169
14248
|
}
|
|
15170
14249
|
function waitForDisconnectEvent(conn) {
|
|
15171
|
-
return new Promise((
|
|
14250
|
+
return new Promise((resolve29) => {
|
|
15172
14251
|
if (!conn.sock) {
|
|
15173
|
-
|
|
14252
|
+
resolve29();
|
|
15174
14253
|
return;
|
|
15175
14254
|
}
|
|
15176
14255
|
const sock = conn.sock;
|
|
15177
14256
|
const handler = (update) => {
|
|
15178
14257
|
if (update.connection === "close") {
|
|
15179
14258
|
sock.ev.off("connection.update", handler);
|
|
15180
|
-
|
|
14259
|
+
resolve29();
|
|
15181
14260
|
}
|
|
15182
14261
|
};
|
|
15183
14262
|
sock.ev.on("connection.update", handler);
|
|
@@ -15359,7 +14438,12 @@ async function handleInboundMessage(conn, msg) {
|
|
|
15359
14438
|
isGroup: isGroup2,
|
|
15360
14439
|
groupJid: isGroup2 ? remoteJid : void 0,
|
|
15361
14440
|
reply: reply2,
|
|
15362
|
-
sessionKey:
|
|
14441
|
+
sessionKey: deriveSessionKey({
|
|
14442
|
+
agentType: "admin",
|
|
14443
|
+
accountId: conn.accountId,
|
|
14444
|
+
senderPhone: senderPhone2,
|
|
14445
|
+
isOwnerMirror: true
|
|
14446
|
+
}),
|
|
15363
14447
|
isOwnerMirror: true
|
|
15364
14448
|
});
|
|
15365
14449
|
return;
|
|
@@ -15387,8 +14471,8 @@ async function handleInboundMessage(conn, msg) {
|
|
|
15387
14471
|
const conversationKey = isGroup ? remoteJid : senderPhone;
|
|
15388
14472
|
const debounceKey = `${conn.accountId}:${conversationKey}:${senderPhone}`;
|
|
15389
14473
|
let resolvePending;
|
|
15390
|
-
const sttPending = new Promise((
|
|
15391
|
-
resolvePending =
|
|
14474
|
+
const sttPending = new Promise((resolve29) => {
|
|
14475
|
+
resolvePending = resolve29;
|
|
15392
14476
|
});
|
|
15393
14477
|
if (conn.debouncer) conn.debouncer.registerPending(debounceKey, sttPending);
|
|
15394
14478
|
try {
|
|
@@ -15438,7 +14522,11 @@ async function handleInboundMessage(conn, msg) {
|
|
|
15438
14522
|
if (!currentSock) throw new Error("WhatsApp disconnected \u2014 cannot reply");
|
|
15439
14523
|
await sendTextMessage(currentSock, remoteJid, text, { accountId: conn.accountId });
|
|
15440
14524
|
};
|
|
15441
|
-
const sessionKey =
|
|
14525
|
+
const sessionKey = deriveSessionKey({
|
|
14526
|
+
agentType: accessResult.agentType,
|
|
14527
|
+
accountId: conn.accountId,
|
|
14528
|
+
senderPhone
|
|
14529
|
+
});
|
|
15442
14530
|
conn.lastMessageAt = Date.now();
|
|
15443
14531
|
const payload = {
|
|
15444
14532
|
accountId: conn.accountId,
|
|
@@ -15495,20 +14583,20 @@ async function probeApiKey() {
|
|
|
15495
14583
|
return result.status;
|
|
15496
14584
|
}
|
|
15497
14585
|
function checkPort(port2, timeoutMs = 500) {
|
|
15498
|
-
return new Promise((
|
|
15499
|
-
const socket =
|
|
14586
|
+
return new Promise((resolve29) => {
|
|
14587
|
+
const socket = createConnection3(port2, "127.0.0.1");
|
|
15500
14588
|
socket.setTimeout(timeoutMs);
|
|
15501
14589
|
socket.once("connect", () => {
|
|
15502
14590
|
socket.destroy();
|
|
15503
|
-
|
|
14591
|
+
resolve29(true);
|
|
15504
14592
|
});
|
|
15505
14593
|
socket.once("error", () => {
|
|
15506
14594
|
socket.destroy();
|
|
15507
|
-
|
|
14595
|
+
resolve29(false);
|
|
15508
14596
|
});
|
|
15509
14597
|
socket.once("timeout", () => {
|
|
15510
14598
|
socket.destroy();
|
|
15511
|
-
|
|
14599
|
+
resolve29(false);
|
|
15512
14600
|
});
|
|
15513
14601
|
});
|
|
15514
14602
|
}
|
|
@@ -15531,8 +14619,8 @@ app.get("/", async (c) => {
|
|
|
15531
14619
|
const browserTransport = resolveBrowserTransport(c.req.raw, c.env?.incoming?.socket?.remoteAddress);
|
|
15532
14620
|
let pinConfigured = false;
|
|
15533
14621
|
try {
|
|
15534
|
-
if (
|
|
15535
|
-
const raw2 =
|
|
14622
|
+
if (existsSync10(USERS_FILE)) {
|
|
14623
|
+
const raw2 = readFileSync11(USERS_FILE, "utf-8").trim();
|
|
15536
14624
|
if (raw2) {
|
|
15537
14625
|
const users = JSON.parse(raw2);
|
|
15538
14626
|
pinConfigured = Array.isArray(users) && users.length > 0;
|
|
@@ -15552,7 +14640,7 @@ app.get("/", async (c) => {
|
|
|
15552
14640
|
const terminalReady = await probeTerminalReady();
|
|
15553
14641
|
let apiKeyConfigured = false;
|
|
15554
14642
|
try {
|
|
15555
|
-
apiKeyConfigured =
|
|
14643
|
+
apiKeyConfigured = existsSync10(keyFilePath());
|
|
15556
14644
|
} catch {
|
|
15557
14645
|
}
|
|
15558
14646
|
let apiKeyStatus = "missing";
|
|
@@ -15621,14 +14709,14 @@ app.get("/", async (c) => {
|
|
|
15621
14709
|
var health_default = app;
|
|
15622
14710
|
|
|
15623
14711
|
// server/routes/session.ts
|
|
15624
|
-
import { resolve as
|
|
15625
|
-
import { existsSync as
|
|
14712
|
+
import { resolve as resolve12 } from "path";
|
|
14713
|
+
import { existsSync as existsSync11, writeFileSync as writeFileSync10, mkdirSync as mkdirSync9 } from "fs";
|
|
15626
14714
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
15627
14715
|
function writeBrandingCache(accountId, agentSlug, branding) {
|
|
15628
14716
|
try {
|
|
15629
|
-
const cacheDir =
|
|
15630
|
-
|
|
15631
|
-
|
|
14717
|
+
const cacheDir = resolve12(MAXY_DIR, "branding-cache", accountId);
|
|
14718
|
+
mkdirSync9(cacheDir, { recursive: true });
|
|
14719
|
+
writeFileSync10(resolve12(cacheDir, `${agentSlug}.json`), JSON.stringify(branding), "utf-8");
|
|
15632
14720
|
} catch (err) {
|
|
15633
14721
|
console.error(`[branding] cache write failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
15634
14722
|
}
|
|
@@ -15698,9 +14786,9 @@ app2.post("/", async (c) => {
|
|
|
15698
14786
|
}
|
|
15699
14787
|
let agentConfig = null;
|
|
15700
14788
|
if (account) {
|
|
15701
|
-
const agentDir =
|
|
15702
|
-
const agentConfigPath =
|
|
15703
|
-
if (!
|
|
14789
|
+
const agentDir = resolve12(account.accountDir, "agents", agentSlug);
|
|
14790
|
+
const agentConfigPath = resolve12(agentDir, "config.json");
|
|
14791
|
+
if (!existsSync11(agentDir) || !existsSync11(agentConfigPath)) {
|
|
15704
14792
|
return c.json({ error: "Agent not found" }, 404);
|
|
15705
14793
|
}
|
|
15706
14794
|
agentConfig = resolveAgentConfig(account.accountDir, agentSlug);
|
|
@@ -15946,9 +15034,9 @@ ${raw2}`;
|
|
|
15946
15034
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
15947
15035
|
import { mkdir as mkdir2, readFile, stat as stat2, writeFile as writeFile2 } from "fs/promises";
|
|
15948
15036
|
import { realpathSync } from "fs";
|
|
15949
|
-
import { resolve as
|
|
15950
|
-
var
|
|
15951
|
-
var ATTACHMENTS_ROOT =
|
|
15037
|
+
import { resolve as resolve13, extname, basename as basename3 } from "path";
|
|
15038
|
+
var PLATFORM_ROOT4 = process.env.MAXY_PLATFORM_ROOT ?? resolve13(process.cwd(), "../platform");
|
|
15039
|
+
var ATTACHMENTS_ROOT = resolve13(PLATFORM_ROOT4, "..", "data/uploads");
|
|
15952
15040
|
var SUPPORTED_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
15953
15041
|
"image/jpeg",
|
|
15954
15042
|
"image/png",
|
|
@@ -15972,11 +15060,11 @@ function assertSupportedMime(mimeType) {
|
|
|
15972
15060
|
}
|
|
15973
15061
|
async function writeAttachment(scope, filename, mimeType, sizeBytes, buffer) {
|
|
15974
15062
|
const attachmentId = randomUUID6();
|
|
15975
|
-
const dir =
|
|
15063
|
+
const dir = resolve13(ATTACHMENTS_ROOT, scope, attachmentId);
|
|
15976
15064
|
await mkdir2(dir, { recursive: true });
|
|
15977
15065
|
const ext = extname(filename) || "";
|
|
15978
|
-
const storagePath =
|
|
15979
|
-
const metaPath =
|
|
15066
|
+
const storagePath = resolve13(dir, `${attachmentId}${ext}`);
|
|
15067
|
+
const metaPath = resolve13(dir, `${attachmentId}.meta.json`);
|
|
15980
15068
|
const meta = {
|
|
15981
15069
|
attachmentId,
|
|
15982
15070
|
scope,
|
|
@@ -16051,7 +15139,7 @@ async function storeGeneratedFile(accountId, filePath) {
|
|
|
16051
15139
|
// app/lib/stt/voice-note.ts
|
|
16052
15140
|
import { writeFile as writeFile3, mkdtemp, rm } from "fs/promises";
|
|
16053
15141
|
import { tmpdir } from "os";
|
|
16054
|
-
import { join as
|
|
15142
|
+
import { join as join7 } from "path";
|
|
16055
15143
|
var TAG14 = "[voice]";
|
|
16056
15144
|
var AUDIO_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
16057
15145
|
"audio/ogg",
|
|
@@ -16089,9 +15177,9 @@ async function transcribeVoiceNote(file, source) {
|
|
|
16089
15177
|
let tempDir;
|
|
16090
15178
|
let tempPath;
|
|
16091
15179
|
try {
|
|
16092
|
-
tempDir = await mkdtemp(
|
|
15180
|
+
tempDir = await mkdtemp(join7(tmpdir(), "voice-"));
|
|
16093
15181
|
const ext = audioExtension(mimeType);
|
|
16094
|
-
tempPath =
|
|
15182
|
+
tempPath = join7(tempDir, `recording${ext}`);
|
|
16095
15183
|
const buffer = Buffer.from(await file.arrayBuffer());
|
|
16096
15184
|
await writeFile3(tempPath, buffer);
|
|
16097
15185
|
} catch (err) {
|
|
@@ -16653,16 +15741,16 @@ var group_default = app4;
|
|
|
16653
15741
|
|
|
16654
15742
|
// app/lib/access-gate.ts
|
|
16655
15743
|
import neo4j2 from "neo4j-driver";
|
|
16656
|
-
import { readFileSync as
|
|
16657
|
-
import { resolve as
|
|
15744
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
15745
|
+
import { resolve as resolve14 } from "path";
|
|
16658
15746
|
import { randomUUID as randomUUID7, randomInt } from "crypto";
|
|
16659
|
-
var
|
|
15747
|
+
var PLATFORM_ROOT5 = process.env.MAXY_PLATFORM_ROOT ?? resolve14(process.cwd(), "..");
|
|
16660
15748
|
var driver2 = null;
|
|
16661
15749
|
function readPassword2() {
|
|
16662
15750
|
if (process.env.NEO4J_PASSWORD) return process.env.NEO4J_PASSWORD;
|
|
16663
|
-
const passwordFile =
|
|
15751
|
+
const passwordFile = resolve14(PLATFORM_ROOT5, "config/.neo4j-password");
|
|
16664
15752
|
try {
|
|
16665
|
-
return
|
|
15753
|
+
return readFileSync12(passwordFile, "utf-8").trim();
|
|
16666
15754
|
} catch {
|
|
16667
15755
|
throw new Error(
|
|
16668
15756
|
`Neo4j password not found. Expected at ${passwordFile} or in NEO4J_PASSWORD env var.`
|
|
@@ -16695,13 +15783,13 @@ process.on("SIGINT", async () => {
|
|
|
16695
15783
|
driver2 = null;
|
|
16696
15784
|
}
|
|
16697
15785
|
});
|
|
16698
|
-
var
|
|
15786
|
+
var rateLimitMap = /* @__PURE__ */ new Map();
|
|
16699
15787
|
var ACCESS_MAX_ATTEMPTS = 5;
|
|
16700
15788
|
var ACCESS_LOCKOUT_MS = 15 * 60 * 1e3;
|
|
16701
15789
|
var ACCESS_WINDOW_MS = 15 * 60 * 1e3;
|
|
16702
15790
|
function checkAccessRateLimit(ip, agentSlug) {
|
|
16703
15791
|
const key = `${ip}:${agentSlug}`;
|
|
16704
|
-
const entry =
|
|
15792
|
+
const entry = rateLimitMap.get(key);
|
|
16705
15793
|
if (!entry) return null;
|
|
16706
15794
|
const now = Date.now();
|
|
16707
15795
|
if (entry.lockedUntil && now < entry.lockedUntil) {
|
|
@@ -16710,7 +15798,7 @@ function checkAccessRateLimit(ip, agentSlug) {
|
|
|
16710
15798
|
return `Too many attempts. Try again in ${remainingS}s`;
|
|
16711
15799
|
}
|
|
16712
15800
|
if (now - entry.firstAttempt > ACCESS_WINDOW_MS) {
|
|
16713
|
-
|
|
15801
|
+
rateLimitMap.delete(key);
|
|
16714
15802
|
return null;
|
|
16715
15803
|
}
|
|
16716
15804
|
return null;
|
|
@@ -16718,9 +15806,9 @@ function checkAccessRateLimit(ip, agentSlug) {
|
|
|
16718
15806
|
function recordAccessFailedAttempt(ip, agentSlug) {
|
|
16719
15807
|
const key = `${ip}:${agentSlug}`;
|
|
16720
15808
|
const now = Date.now();
|
|
16721
|
-
const entry =
|
|
15809
|
+
const entry = rateLimitMap.get(key);
|
|
16722
15810
|
if (!entry || now - entry.firstAttempt > ACCESS_WINDOW_MS) {
|
|
16723
|
-
|
|
15811
|
+
rateLimitMap.set(key, { attempts: 1, firstAttempt: now });
|
|
16724
15812
|
return;
|
|
16725
15813
|
}
|
|
16726
15814
|
entry.attempts++;
|
|
@@ -16729,7 +15817,7 @@ function recordAccessFailedAttempt(ip, agentSlug) {
|
|
|
16729
15817
|
}
|
|
16730
15818
|
}
|
|
16731
15819
|
function clearAccessRateLimit(ip, agentSlug) {
|
|
16732
|
-
|
|
15820
|
+
rateLimitMap.delete(`${ip}:${agentSlug}`);
|
|
16733
15821
|
}
|
|
16734
15822
|
var forgotPwdMap = /* @__PURE__ */ new Map();
|
|
16735
15823
|
var FORGOT_PWD_MAX = 3;
|
|
@@ -16973,19 +16061,19 @@ async function findActiveGrantByContact(contactValue, agentSlug, accountId) {
|
|
|
16973
16061
|
}
|
|
16974
16062
|
|
|
16975
16063
|
// app/lib/brevo-sms.ts
|
|
16976
|
-
import { readFileSync as
|
|
16064
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync11, mkdirSync as mkdirSync10, existsSync as existsSync12, chmodSync } from "fs";
|
|
16977
16065
|
import { dirname as dirname6 } from "path";
|
|
16978
|
-
import { resolve as
|
|
16979
|
-
var BREVO_API_KEY_FILE =
|
|
16066
|
+
import { resolve as resolve15 } from "path";
|
|
16067
|
+
var BREVO_API_KEY_FILE = resolve15(MAXY_DIR, ".brevo-api-key");
|
|
16980
16068
|
var BREVO_API_URL = "https://api.brevo.com/v3/transactionalSMS/sms";
|
|
16981
16069
|
var BREVO_TIMEOUT_MS = 1e4;
|
|
16982
16070
|
var BREVO_SENDER = "Maxy";
|
|
16983
|
-
var
|
|
16984
|
-
if (
|
|
16071
|
+
var platformRoot = process.env.MAXY_PLATFORM_ROOT;
|
|
16072
|
+
if (platformRoot) {
|
|
16985
16073
|
try {
|
|
16986
|
-
const brandPath =
|
|
16987
|
-
if (
|
|
16988
|
-
const brand = JSON.parse(
|
|
16074
|
+
const brandPath = resolve15(platformRoot, "config", "brand.json");
|
|
16075
|
+
if (existsSync12(brandPath)) {
|
|
16076
|
+
const brand = JSON.parse(readFileSync13(brandPath, "utf-8"));
|
|
16989
16077
|
if (brand.productName) BREVO_SENDER = brand.productName;
|
|
16990
16078
|
}
|
|
16991
16079
|
} catch {
|
|
@@ -16993,7 +16081,7 @@ if (platformRoot2) {
|
|
|
16993
16081
|
}
|
|
16994
16082
|
function readBrevoApiKey() {
|
|
16995
16083
|
try {
|
|
16996
|
-
const key =
|
|
16084
|
+
const key = readFileSync13(BREVO_API_KEY_FILE, "utf-8").trim();
|
|
16997
16085
|
if (!key) {
|
|
16998
16086
|
throw new Error(`Brevo API key file is empty: ${BREVO_API_KEY_FILE}`);
|
|
16999
16087
|
}
|
|
@@ -17008,7 +16096,7 @@ function readBrevoApiKey() {
|
|
|
17008
16096
|
}
|
|
17009
16097
|
}
|
|
17010
16098
|
function hasBrevoApiKey() {
|
|
17011
|
-
return
|
|
16099
|
+
return existsSync12(BREVO_API_KEY_FILE);
|
|
17012
16100
|
}
|
|
17013
16101
|
async function sendSms(recipient, content, opts) {
|
|
17014
16102
|
let apiKey;
|
|
@@ -17380,8 +16468,8 @@ app5.post("/forgot-password", async (c) => {
|
|
|
17380
16468
|
app5.post("/send-otp", async (c) => {
|
|
17381
16469
|
const socketAddr = c.env?.incoming?.socket?.remoteAddress;
|
|
17382
16470
|
const hasXff = !!c.req.header("x-forwarded-for");
|
|
17383
|
-
const
|
|
17384
|
-
if (!
|
|
16471
|
+
const isLoopback = socketAddr === "127.0.0.1" || socketAddr === "::1" || socketAddr === "::ffff:127.0.0.1";
|
|
16472
|
+
if (!isLoopback || hasXff) {
|
|
17385
16473
|
console.error(`[access-gate] send-otp rejected: socket=${socketAddr ?? "unknown"} xff=${hasXff}`);
|
|
17386
16474
|
return c.json({ error: "Forbidden" }, 403);
|
|
17387
16475
|
}
|
|
@@ -17424,8 +16512,8 @@ app5.post("/send-otp", async (c) => {
|
|
|
17424
16512
|
var access_default = app5;
|
|
17425
16513
|
|
|
17426
16514
|
// server/routes/telegram.ts
|
|
17427
|
-
import { existsSync as
|
|
17428
|
-
import { timingSafeEqual
|
|
16515
|
+
import { existsSync as existsSync13, readFileSync as readFileSync14 } from "fs";
|
|
16516
|
+
import { timingSafeEqual } from "crypto";
|
|
17429
16517
|
|
|
17430
16518
|
// app/lib/telegram/access-control.ts
|
|
17431
16519
|
function checkTelegramAccess(params) {
|
|
@@ -17461,8 +16549,8 @@ var TELEGRAM_API = "https://api.telegram.org";
|
|
|
17461
16549
|
function getWebhookSecret(botType) {
|
|
17462
16550
|
const filePath = botType === "admin" ? TELEGRAM_ADMIN_WEBHOOK_SECRET_FILE : TELEGRAM_WEBHOOK_SECRET_FILE;
|
|
17463
16551
|
try {
|
|
17464
|
-
if (!
|
|
17465
|
-
const secret =
|
|
16552
|
+
if (!existsSync13(filePath)) return null;
|
|
16553
|
+
const secret = readFileSync14(filePath, "utf-8").trim();
|
|
17466
16554
|
return secret || null;
|
|
17467
16555
|
} catch {
|
|
17468
16556
|
return null;
|
|
@@ -17472,7 +16560,7 @@ function verifyWebhookSecret(headerValue, storedSecret) {
|
|
|
17472
16560
|
const a = Buffer.from(headerValue);
|
|
17473
16561
|
const b = Buffer.from(storedSecret);
|
|
17474
16562
|
if (a.length !== b.length) return false;
|
|
17475
|
-
return
|
|
16563
|
+
return timingSafeEqual(a, b);
|
|
17476
16564
|
}
|
|
17477
16565
|
async function handleInbound(params) {
|
|
17478
16566
|
const { chatId, senderId, text, botType, botToken, accountId, agentType } = params;
|
|
@@ -17620,9 +16708,9 @@ app6.post("/webhook", async (c) => {
|
|
|
17620
16708
|
var telegram_default = app6;
|
|
17621
16709
|
|
|
17622
16710
|
// server/routes/whatsapp.ts
|
|
17623
|
-
import { join as
|
|
16711
|
+
import { join as join8, resolve as resolve16, basename as basename4 } from "path";
|
|
17624
16712
|
import { readFile as readFile2, stat as stat3 } from "fs/promises";
|
|
17625
|
-
import { realpathSync as realpathSync2, readdirSync as readdirSync4, readFileSync as
|
|
16713
|
+
import { realpathSync as realpathSync2, readdirSync as readdirSync4, readFileSync as readFileSync15, existsSync as existsSync14 } from "fs";
|
|
17626
16714
|
|
|
17627
16715
|
// app/lib/whatsapp/login.ts
|
|
17628
16716
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
@@ -17728,8 +16816,8 @@ async function startLogin(opts) {
|
|
|
17728
16816
|
resetActiveLogin(accountId);
|
|
17729
16817
|
let resolveQr = null;
|
|
17730
16818
|
let rejectQr = null;
|
|
17731
|
-
const qrPromise = new Promise((
|
|
17732
|
-
resolveQr =
|
|
16819
|
+
const qrPromise = new Promise((resolve29, reject) => {
|
|
16820
|
+
resolveQr = resolve29;
|
|
17733
16821
|
rejectQr = reject;
|
|
17734
16822
|
});
|
|
17735
16823
|
const qrTimer = setTimeout(
|
|
@@ -17945,7 +17033,7 @@ function serializeWhatsAppSchema() {
|
|
|
17945
17033
|
|
|
17946
17034
|
// server/routes/whatsapp.ts
|
|
17947
17035
|
var TAG18 = "[whatsapp:api]";
|
|
17948
|
-
var
|
|
17036
|
+
var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
17949
17037
|
var app7 = new Hono2();
|
|
17950
17038
|
app7.get("/status", (c) => {
|
|
17951
17039
|
try {
|
|
@@ -17963,7 +17051,7 @@ app7.post("/login/start", async (c) => {
|
|
|
17963
17051
|
const body = await c.req.json().catch(() => ({}));
|
|
17964
17052
|
const accountId = validateAccountId(body.accountId);
|
|
17965
17053
|
const force = body.force ?? false;
|
|
17966
|
-
const authDir =
|
|
17054
|
+
const authDir = join8(MAXY_DIR, "credentials", "whatsapp", accountId);
|
|
17967
17055
|
const result = await startLogin({ accountId, authDir, force });
|
|
17968
17056
|
console.error(`${TAG18} login/start result account=${accountId} hasQr=${!!result.qrRaw}${result.selfPhone ? ` phone=${result.selfPhone}` : ""}`);
|
|
17969
17057
|
return c.json(result);
|
|
@@ -18100,17 +17188,17 @@ app7.post("/config", async (c) => {
|
|
|
18100
17188
|
return c.json({ ok: true, slug: currentSlug });
|
|
18101
17189
|
}
|
|
18102
17190
|
case "list-public-agents": {
|
|
18103
|
-
const agentsDir =
|
|
17191
|
+
const agentsDir = resolve16(account.accountDir, "agents");
|
|
18104
17192
|
const agents = [];
|
|
18105
|
-
if (
|
|
17193
|
+
if (existsSync14(agentsDir)) {
|
|
18106
17194
|
try {
|
|
18107
17195
|
const entries = readdirSync4(agentsDir, { withFileTypes: true });
|
|
18108
17196
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
18109
17197
|
if (!entry.isDirectory() || entry.name === "admin") continue;
|
|
18110
|
-
const configPath2 =
|
|
18111
|
-
if (!
|
|
17198
|
+
const configPath2 = resolve16(agentsDir, entry.name, "config.json");
|
|
17199
|
+
if (!existsSync14(configPath2)) continue;
|
|
18112
17200
|
try {
|
|
18113
|
-
const config = JSON.parse(
|
|
17201
|
+
const config = JSON.parse(readFileSync15(configPath2, "utf-8"));
|
|
18114
17202
|
agents.push({ slug: entry.name, displayName: config.displayName ?? entry.name });
|
|
18115
17203
|
} catch {
|
|
18116
17204
|
console.error(`${TAG18} config action=list-public-agents error="failed to parse config.json for agent ${entry.name}" \u2014 skipping`);
|
|
@@ -18182,10 +17270,10 @@ app7.post("/send-document", async (c) => {
|
|
|
18182
17270
|
if (!to || !filePath) {
|
|
18183
17271
|
return c.json({ error: "Missing required fields: to, filePath" }, 400);
|
|
18184
17272
|
}
|
|
18185
|
-
if (!maxyAccountId || !
|
|
17273
|
+
if (!maxyAccountId || !PLATFORM_ROOT6) {
|
|
18186
17274
|
return c.json({ error: "Cannot validate file path: missing account or platform context" }, 400);
|
|
18187
17275
|
}
|
|
18188
|
-
const accountDir =
|
|
17276
|
+
const accountDir = resolve16(PLATFORM_ROOT6, "..", "data/accounts", maxyAccountId);
|
|
18189
17277
|
let resolvedPath;
|
|
18190
17278
|
try {
|
|
18191
17279
|
resolvedPath = realpathSync2(filePath);
|
|
@@ -18322,16 +17410,16 @@ var whatsapp_default = app7;
|
|
|
18322
17410
|
|
|
18323
17411
|
// server/routes/onboarding.ts
|
|
18324
17412
|
import { spawn as spawn3, execFileSync as execFileSync2 } from "child_process";
|
|
18325
|
-
import { openSync as openSync4, closeSync as closeSync4, writeFileSync as
|
|
18326
|
-
import { resolve as
|
|
17413
|
+
import { openSync as openSync4, closeSync as closeSync4, writeFileSync as writeFileSync12, writeSync, existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as readFileSync16, unlinkSync as unlinkSync4 } from "fs";
|
|
17414
|
+
import { resolve as resolve17, dirname as dirname7 } from "path";
|
|
18327
17415
|
import { createHash, randomUUID as randomUUID9 } from "crypto";
|
|
18328
|
-
var
|
|
17416
|
+
var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
18329
17417
|
function hashPin(pin) {
|
|
18330
17418
|
return createHash("sha256").update(pin).digest("hex");
|
|
18331
17419
|
}
|
|
18332
17420
|
function readUsersFile() {
|
|
18333
|
-
if (!
|
|
18334
|
-
const raw2 =
|
|
17421
|
+
if (!existsSync15(USERS_FILE)) return null;
|
|
17422
|
+
const raw2 = readFileSync16(USERS_FILE, "utf-8").trim();
|
|
18335
17423
|
if (!raw2) return [];
|
|
18336
17424
|
return JSON.parse(raw2);
|
|
18337
17425
|
}
|
|
@@ -18397,7 +17485,7 @@ app8.post("/claude-auth", async (c) => {
|
|
|
18397
17485
|
if (!vncReady) return c.json({ error: "VNC display failed to start" }, 500);
|
|
18398
17486
|
}
|
|
18399
17487
|
await ensureCdp(transport);
|
|
18400
|
-
|
|
17488
|
+
writeFileSync12(logPath("claude-auth"), "");
|
|
18401
17489
|
const chromiumWrapper = writeChromiumWrapper();
|
|
18402
17490
|
const x11Env = buildX11Env(chromiumWrapper, transport);
|
|
18403
17491
|
vncLog("claude-auth", { action: "start", transport });
|
|
@@ -18436,17 +17524,17 @@ app8.post("/set-pin", async (c) => {
|
|
|
18436
17524
|
}
|
|
18437
17525
|
const hash = hashPin(body.pin);
|
|
18438
17526
|
const userId = randomUUID9();
|
|
18439
|
-
|
|
18440
|
-
|
|
17527
|
+
mkdirSync11(dirname7(USERS_FILE), { recursive: true });
|
|
17528
|
+
writeFileSync12(USERS_FILE, JSON.stringify([{ userId, name: "Owner", pin: hash }]), { mode: 384 });
|
|
18441
17529
|
console.log(`[set-pin] created users.json: userId=${userId.slice(0, 8)}\u2026 hash=${hash.slice(0, 8)}\u2026`);
|
|
18442
17530
|
const account = resolveAccount();
|
|
18443
17531
|
if (account) {
|
|
18444
17532
|
try {
|
|
18445
|
-
const config = JSON.parse(
|
|
17533
|
+
const config = JSON.parse(readFileSync16(`${account.accountDir}/account.json`, "utf-8"));
|
|
18446
17534
|
if (!config.admins) config.admins = [];
|
|
18447
17535
|
if (!config.admins.some((a) => a.userId === userId)) {
|
|
18448
17536
|
config.admins.push({ userId, role: "owner" });
|
|
18449
|
-
|
|
17537
|
+
writeFileSync12(`${account.accountDir}/account.json`, JSON.stringify(config, null, 2) + "\n");
|
|
18450
17538
|
console.log(`[set-pin] added userId=${userId.slice(0, 8)}\u2026 to account.json admins`);
|
|
18451
17539
|
}
|
|
18452
17540
|
} catch (err) {
|
|
@@ -18484,7 +17572,7 @@ app8.delete("/set-pin", async (c) => {
|
|
|
18484
17572
|
unlinkSync4(USERS_FILE);
|
|
18485
17573
|
console.log(`[set-pin] cleared users.json (last entry removed): userId=${matchedUser.userId.slice(0, 8)}\u2026`);
|
|
18486
17574
|
} else {
|
|
18487
|
-
|
|
17575
|
+
writeFileSync12(USERS_FILE, JSON.stringify(remaining), { mode: 384 });
|
|
18488
17576
|
console.log(`[set-pin] removed entry from users.json: userId=${matchedUser.userId.slice(0, 8)}\u2026 remaining=${remaining.length}`);
|
|
18489
17577
|
}
|
|
18490
17578
|
return c.json({ ok: true });
|
|
@@ -18497,19 +17585,19 @@ app8.post("/skip", async (c) => {
|
|
|
18497
17585
|
}
|
|
18498
17586
|
const { accountId, accountDir } = account;
|
|
18499
17587
|
let agentName = "Maxy";
|
|
18500
|
-
const brandPath =
|
|
18501
|
-
if (brandPath &&
|
|
17588
|
+
const brandPath = PLATFORM_ROOT7 ? resolve17(PLATFORM_ROOT7, "config", "brand.json") : "";
|
|
17589
|
+
if (brandPath && existsSync15(brandPath)) {
|
|
18502
17590
|
try {
|
|
18503
|
-
const brand = JSON.parse(
|
|
17591
|
+
const brand = JSON.parse(readFileSync16(brandPath, "utf-8"));
|
|
18504
17592
|
if (brand.productName) agentName = brand.productName;
|
|
18505
17593
|
} catch (err) {
|
|
18506
17594
|
console.error(`[onboarding-skip] brand.json read failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
18507
17595
|
}
|
|
18508
17596
|
}
|
|
18509
|
-
const soulPath =
|
|
17597
|
+
const soulPath = resolve17(accountDir, "agents", "admin", "SOUL.md");
|
|
18510
17598
|
try {
|
|
18511
|
-
|
|
18512
|
-
|
|
17599
|
+
mkdirSync11(dirname7(soulPath), { recursive: true });
|
|
17600
|
+
writeFileSync12(soulPath, `You are ${agentName}, an AI operations manager.
|
|
18513
17601
|
`);
|
|
18514
17602
|
console.log(`[onboarding-skip] wrote SOUL.md: ${soulPath}`);
|
|
18515
17603
|
} catch (err) {
|
|
@@ -18547,9 +17635,9 @@ app8.post("/skip", async (c) => {
|
|
|
18547
17635
|
var onboarding_default = app8;
|
|
18548
17636
|
|
|
18549
17637
|
// server/routes/client-error.ts
|
|
18550
|
-
import { appendFileSync as
|
|
18551
|
-
import { join as
|
|
18552
|
-
var CLIENT_ERRORS_LOG =
|
|
17638
|
+
import { appendFileSync as appendFileSync4, existsSync as existsSync16, renameSync as renameSync5, statSync as statSync7 } from "fs";
|
|
17639
|
+
import { join as join9 } from "path";
|
|
17640
|
+
var CLIENT_ERRORS_LOG = join9(LOG_DIR, "client-errors.log");
|
|
18553
17641
|
var MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
18554
17642
|
var MAX_BODY_SIZE = 8 * 1024;
|
|
18555
17643
|
var MAX_STACK_LEN = 2e3;
|
|
@@ -18601,7 +17689,7 @@ function stackHead(stack) {
|
|
|
18601
17689
|
}
|
|
18602
17690
|
function rotateIfNeeded() {
|
|
18603
17691
|
try {
|
|
18604
|
-
if (!
|
|
17692
|
+
if (!existsSync16(CLIENT_ERRORS_LOG)) return;
|
|
18605
17693
|
const stats = statSync7(CLIENT_ERRORS_LOG);
|
|
18606
17694
|
if (stats.size < MAX_LOG_SIZE) return;
|
|
18607
17695
|
renameSync5(CLIENT_ERRORS_LOG, CLIENT_ERRORS_LOG + ".1");
|
|
@@ -18684,7 +17772,7 @@ app9.post("/", async (c) => {
|
|
|
18684
17772
|
tag: typeof body.tag === "string" ? truncate2(body.tag, 32) : void 0,
|
|
18685
17773
|
status: typeof body.status === "number" ? body.status : void 0
|
|
18686
17774
|
};
|
|
18687
|
-
|
|
17775
|
+
appendFileSync4(CLIENT_ERRORS_LOG, JSON.stringify(payload) + "\n", "utf-8");
|
|
18688
17776
|
} catch (err) {
|
|
18689
17777
|
console.error(`[client-error] append failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
18690
17778
|
}
|
|
@@ -18694,14 +17782,14 @@ app9.post("/", async (c) => {
|
|
|
18694
17782
|
var client_error_default = app9;
|
|
18695
17783
|
|
|
18696
17784
|
// server/routes/admin/session.ts
|
|
18697
|
-
import { readFileSync as
|
|
17785
|
+
import { readFileSync as readFileSync17, existsSync as existsSync17 } from "fs";
|
|
18698
17786
|
import { createHash as createHash2 } from "crypto";
|
|
18699
17787
|
function hashPin2(pin) {
|
|
18700
17788
|
return createHash2("sha256").update(pin).digest("hex");
|
|
18701
17789
|
}
|
|
18702
17790
|
function readUsersFile2() {
|
|
18703
|
-
if (!
|
|
18704
|
-
const raw2 =
|
|
17791
|
+
if (!existsSync17(USERS_FILE)) return null;
|
|
17792
|
+
const raw2 = readFileSync17(USERS_FILE, "utf-8").trim();
|
|
18705
17793
|
if (!raw2) return [];
|
|
18706
17794
|
return JSON.parse(raw2);
|
|
18707
17795
|
}
|
|
@@ -18831,11 +17919,11 @@ app10.post("/", async (c) => {
|
|
|
18831
17919
|
var session_default2 = app10;
|
|
18832
17920
|
|
|
18833
17921
|
// server/routes/admin/chat.ts
|
|
18834
|
-
import { resolve as
|
|
17922
|
+
import { resolve as resolve18 } from "path";
|
|
18835
17923
|
|
|
18836
17924
|
// app/lib/script-stream-tailer.ts
|
|
18837
17925
|
import * as childProcess from "child_process";
|
|
18838
|
-
import { appendFileSync as
|
|
17926
|
+
import { appendFileSync as appendFileSync5, createReadStream as createReadStream2, mkdirSync as mkdirSync12, statSync as statSync8 } from "fs";
|
|
18839
17927
|
import { dirname as dirname8 } from "path";
|
|
18840
17928
|
import { StringDecoder as StringDecoder2 } from "string_decoder";
|
|
18841
17929
|
var SCRIPT_STREAM_RE = /^\[([^\]]+)\] \[script:([a-z][a-z0-9-]*)((?::[a-z0-9:_-]+)?)\] (.*)$/;
|
|
@@ -18945,8 +18033,8 @@ function writeRouteMilestone(streamLogPath, scope, line) {
|
|
|
18945
18033
|
}
|
|
18946
18034
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
18947
18035
|
try {
|
|
18948
|
-
|
|
18949
|
-
|
|
18036
|
+
mkdirSync12(dirname8(streamLogPath), { recursive: true });
|
|
18037
|
+
appendFileSync5(streamLogPath, `[${ts}] [script:${scope}] ${line}
|
|
18950
18038
|
`);
|
|
18951
18039
|
} catch (err) {
|
|
18952
18040
|
console.error(
|
|
@@ -19276,7 +18364,7 @@ app11.post("/", requireAdminSession, async (c) => {
|
|
|
19276
18364
|
try {
|
|
19277
18365
|
registerAdminSSE(sseEntry);
|
|
19278
18366
|
if (sseConvId) {
|
|
19279
|
-
const streamLogPath =
|
|
18367
|
+
const streamLogPath = resolve18(account.accountDir, "logs", `claude-agent-stream-${sseConvId}.log`);
|
|
19280
18368
|
tailer = startScriptStreamTailer({
|
|
19281
18369
|
path: streamLogPath,
|
|
19282
18370
|
onEvent: (event) => {
|
|
@@ -19401,8 +18489,8 @@ app12.post("/", requireAdminSession, async (c) => {
|
|
|
19401
18489
|
var compact_default = app12;
|
|
19402
18490
|
|
|
19403
18491
|
// server/routes/admin/logs.ts
|
|
19404
|
-
import { existsSync as
|
|
19405
|
-
import { resolve as
|
|
18492
|
+
import { existsSync as existsSync18, readdirSync as readdirSync5, readFileSync as readFileSync18, statSync as statSync9 } from "fs";
|
|
18493
|
+
import { resolve as resolve19, basename as basename5 } from "path";
|
|
19406
18494
|
var TAIL_BYTES = 8192;
|
|
19407
18495
|
var app13 = new Hono2();
|
|
19408
18496
|
app13.get("/", async (c) => {
|
|
@@ -19411,16 +18499,16 @@ app13.get("/", async (c) => {
|
|
|
19411
18499
|
const conversationIdParam = c.req.query("conversationId");
|
|
19412
18500
|
const download = c.req.query("download") === "1";
|
|
19413
18501
|
const account = resolveAccount();
|
|
19414
|
-
const accountLogDir2 = account ?
|
|
18502
|
+
const accountLogDir2 = account ? resolve19(account.accountDir, "logs") : null;
|
|
19415
18503
|
if (fileParam) {
|
|
19416
18504
|
const safe = basename5(fileParam);
|
|
19417
18505
|
const searched = [];
|
|
19418
18506
|
for (const dir of [accountLogDir2, LOG_DIR]) {
|
|
19419
18507
|
if (!dir) continue;
|
|
19420
|
-
const filePath =
|
|
18508
|
+
const filePath = resolve19(dir, safe);
|
|
19421
18509
|
searched.push(filePath);
|
|
19422
18510
|
try {
|
|
19423
|
-
const content =
|
|
18511
|
+
const content = readFileSync18(filePath, "utf-8");
|
|
19424
18512
|
const headers = { "Content-Type": "text/plain; charset=utf-8" };
|
|
19425
18513
|
if (download) headers["Content-Disposition"] = `attachment; filename="${safe}"`;
|
|
19426
18514
|
return new Response(content, { headers });
|
|
@@ -19459,10 +18547,10 @@ app13.get("/", async (c) => {
|
|
|
19459
18547
|
const searched = [];
|
|
19460
18548
|
for (const dir of [accountLogDir2, LOG_DIR]) {
|
|
19461
18549
|
if (!dir) continue;
|
|
19462
|
-
const filePath =
|
|
18550
|
+
const filePath = resolve19(dir, fileName);
|
|
19463
18551
|
searched.push(filePath);
|
|
19464
18552
|
try {
|
|
19465
|
-
const content =
|
|
18553
|
+
const content = readFileSync18(filePath, "utf-8");
|
|
19466
18554
|
const headers = { "Content-Type": "text/plain; charset=utf-8" };
|
|
19467
18555
|
if (download) headers["Content-Disposition"] = `attachment; filename="${fileName}"`;
|
|
19468
18556
|
return new Response(content, { headers });
|
|
@@ -19477,7 +18565,7 @@ app13.get("/", async (c) => {
|
|
|
19477
18565
|
const seen = /* @__PURE__ */ new Set();
|
|
19478
18566
|
const logs = {};
|
|
19479
18567
|
for (const dir of [accountLogDir2, LOG_DIR]) {
|
|
19480
|
-
if (!dir || !
|
|
18568
|
+
if (!dir || !existsSync18(dir)) continue;
|
|
19481
18569
|
let files;
|
|
19482
18570
|
try {
|
|
19483
18571
|
files = readdirSync5(dir).filter((f) => f.endsWith(".log"));
|
|
@@ -19486,10 +18574,10 @@ app13.get("/", async (c) => {
|
|
|
19486
18574
|
console.warn(`[admin/logs] readdir-fail dir=${dir} reason=${reason}`);
|
|
19487
18575
|
continue;
|
|
19488
18576
|
}
|
|
19489
|
-
files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync9(
|
|
18577
|
+
files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync9(resolve19(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).forEach(({ name }) => {
|
|
19490
18578
|
seen.add(name);
|
|
19491
18579
|
try {
|
|
19492
|
-
const content =
|
|
18580
|
+
const content = readFileSync18(resolve19(dir, name));
|
|
19493
18581
|
const tail = content.length > TAIL_BYTES ? content.subarray(content.length - TAIL_BYTES).toString("utf-8") : content.toString("utf-8");
|
|
19494
18582
|
logs[name] = tail.trim() || "(empty)";
|
|
19495
18583
|
} catch (err) {
|
|
@@ -19529,8 +18617,8 @@ var claude_info_default = app14;
|
|
|
19529
18617
|
|
|
19530
18618
|
// server/routes/admin/attachment.ts
|
|
19531
18619
|
import { readFile as readFile3, readdir } from "fs/promises";
|
|
19532
|
-
import { existsSync as
|
|
19533
|
-
import { resolve as
|
|
18620
|
+
import { existsSync as existsSync19 } from "fs";
|
|
18621
|
+
import { resolve as resolve20 } from "path";
|
|
19534
18622
|
var app15 = new Hono2();
|
|
19535
18623
|
app15.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
19536
18624
|
const attachmentId = c.req.param("attachmentId");
|
|
@@ -19542,12 +18630,12 @@ app15.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
|
19542
18630
|
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(attachmentId)) {
|
|
19543
18631
|
return new Response("Not found", { status: 404 });
|
|
19544
18632
|
}
|
|
19545
|
-
const dir =
|
|
19546
|
-
if (!
|
|
18633
|
+
const dir = resolve20(ATTACHMENTS_ROOT, accountId, attachmentId);
|
|
18634
|
+
if (!existsSync19(dir)) {
|
|
19547
18635
|
return new Response("Not found", { status: 404 });
|
|
19548
18636
|
}
|
|
19549
|
-
const metaPath =
|
|
19550
|
-
if (!
|
|
18637
|
+
const metaPath = resolve20(dir, `${attachmentId}.meta.json`);
|
|
18638
|
+
if (!existsSync19(metaPath)) {
|
|
19551
18639
|
return new Response("Not found", { status: 404 });
|
|
19552
18640
|
}
|
|
19553
18641
|
let meta;
|
|
@@ -19561,7 +18649,7 @@ app15.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
|
19561
18649
|
if (!dataFile) {
|
|
19562
18650
|
return new Response("Not found", { status: 404 });
|
|
19563
18651
|
}
|
|
19564
|
-
const filePath =
|
|
18652
|
+
const filePath = resolve20(dir, dataFile);
|
|
19565
18653
|
const buffer = await readFile3(filePath);
|
|
19566
18654
|
return new Response(new Uint8Array(buffer), {
|
|
19567
18655
|
headers: {
|
|
@@ -19574,8 +18662,8 @@ app15.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
|
19574
18662
|
var attachment_default = app15;
|
|
19575
18663
|
|
|
19576
18664
|
// server/routes/admin/account.ts
|
|
19577
|
-
import { readFileSync as
|
|
19578
|
-
import { resolve as
|
|
18665
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync13 } from "fs";
|
|
18666
|
+
import { resolve as resolve21 } from "path";
|
|
19579
18667
|
var VALID_CONTEXT_MODES = ["managed", "claude-code"];
|
|
19580
18668
|
var app16 = new Hono2();
|
|
19581
18669
|
app16.patch("/", requireAdminSession, async (c) => {
|
|
@@ -19591,12 +18679,12 @@ app16.patch("/", requireAdminSession, async (c) => {
|
|
|
19591
18679
|
}
|
|
19592
18680
|
const account = resolveAccount();
|
|
19593
18681
|
if (!account) return c.json({ error: "No account configured" }, 500);
|
|
19594
|
-
const configPath2 =
|
|
18682
|
+
const configPath2 = resolve21(account.accountDir, "account.json");
|
|
19595
18683
|
try {
|
|
19596
|
-
const raw2 =
|
|
18684
|
+
const raw2 = readFileSync19(configPath2, "utf-8");
|
|
19597
18685
|
const config = JSON.parse(raw2);
|
|
19598
18686
|
config.contextMode = contextMode;
|
|
19599
|
-
|
|
18687
|
+
writeFileSync13(configPath2, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
19600
18688
|
console.error(`[account-update] contextMode=${contextMode}`);
|
|
19601
18689
|
return c.json({ ok: true, contextMode });
|
|
19602
18690
|
} catch (err) {
|
|
@@ -19607,24 +18695,24 @@ app16.patch("/", requireAdminSession, async (c) => {
|
|
|
19607
18695
|
var account_default = app16;
|
|
19608
18696
|
|
|
19609
18697
|
// server/routes/admin/agents.ts
|
|
19610
|
-
import { resolve as
|
|
19611
|
-
import { readdirSync as readdirSync6, readFileSync as
|
|
18698
|
+
import { resolve as resolve22 } from "path";
|
|
18699
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync20, existsSync as existsSync20, rmSync as rmSync3 } from "fs";
|
|
19612
18700
|
var app17 = new Hono2();
|
|
19613
18701
|
app17.get("/", (c) => {
|
|
19614
18702
|
const account = resolveAccount();
|
|
19615
18703
|
if (!account) return c.json({ agents: [] });
|
|
19616
|
-
const agentsDir =
|
|
19617
|
-
if (!
|
|
18704
|
+
const agentsDir = resolve22(account.accountDir, "agents");
|
|
18705
|
+
if (!existsSync20(agentsDir)) return c.json({ agents: [] });
|
|
19618
18706
|
const agents = [];
|
|
19619
18707
|
try {
|
|
19620
18708
|
const entries = readdirSync6(agentsDir, { withFileTypes: true });
|
|
19621
18709
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
19622
18710
|
if (!entry.isDirectory()) continue;
|
|
19623
18711
|
if (entry.name === "admin") continue;
|
|
19624
|
-
const configPath2 =
|
|
19625
|
-
if (!
|
|
18712
|
+
const configPath2 = resolve22(agentsDir, entry.name, "config.json");
|
|
18713
|
+
if (!existsSync20(configPath2)) continue;
|
|
19626
18714
|
try {
|
|
19627
|
-
const config = JSON.parse(
|
|
18715
|
+
const config = JSON.parse(readFileSync20(configPath2, "utf-8"));
|
|
19628
18716
|
agents.push({
|
|
19629
18717
|
slug: entry.name,
|
|
19630
18718
|
displayName: config.displayName ?? entry.name,
|
|
@@ -19650,8 +18738,8 @@ app17.delete("/:slug", (c) => {
|
|
|
19650
18738
|
if (slug.includes("/") || slug.includes("..") || slug.includes("\\")) {
|
|
19651
18739
|
return c.json({ error: "Invalid agent slug" }, 400);
|
|
19652
18740
|
}
|
|
19653
|
-
const agentDir =
|
|
19654
|
-
if (!
|
|
18741
|
+
const agentDir = resolve22(account.accountDir, "agents", slug);
|
|
18742
|
+
if (!existsSync20(agentDir)) {
|
|
19655
18743
|
return c.json({ error: "Agent not found" }, 404);
|
|
19656
18744
|
}
|
|
19657
18745
|
try {
|
|
@@ -19666,27 +18754,27 @@ app17.delete("/:slug", (c) => {
|
|
|
19666
18754
|
var agents_default = app17;
|
|
19667
18755
|
|
|
19668
18756
|
// server/routes/admin/version.ts
|
|
19669
|
-
import { existsSync as
|
|
19670
|
-
import { resolve as
|
|
19671
|
-
var
|
|
18757
|
+
import { existsSync as existsSync21, readFileSync as readFileSync21 } from "fs";
|
|
18758
|
+
import { resolve as resolve23, join as join10 } from "path";
|
|
18759
|
+
var PLATFORM_ROOT8 = process.env.MAXY_PLATFORM_ROOT ?? resolve23(process.cwd(), "..");
|
|
19672
18760
|
var brandHostname = "maxy";
|
|
19673
18761
|
var brandNpmPackage = "@rubytech/create-maxy";
|
|
19674
|
-
var brandJsonPath =
|
|
19675
|
-
if (
|
|
18762
|
+
var brandJsonPath = join10(PLATFORM_ROOT8, "config", "brand.json");
|
|
18763
|
+
if (existsSync21(brandJsonPath)) {
|
|
19676
18764
|
try {
|
|
19677
|
-
const brand = JSON.parse(
|
|
18765
|
+
const brand = JSON.parse(readFileSync21(brandJsonPath, "utf-8"));
|
|
19678
18766
|
if (brand.hostname) brandHostname = brand.hostname;
|
|
19679
18767
|
if (brand.npm?.packageName) brandNpmPackage = brand.npm.packageName;
|
|
19680
18768
|
} catch {
|
|
19681
18769
|
}
|
|
19682
18770
|
}
|
|
19683
|
-
var VERSION_FILE =
|
|
18771
|
+
var VERSION_FILE = resolve23(PLATFORM_ROOT8, `config/.${brandHostname}-version`);
|
|
19684
18772
|
var NPM_PACKAGE = brandNpmPackage;
|
|
19685
18773
|
var REGISTRY_URL = `https://registry.npmjs.org/${NPM_PACKAGE}/latest`;
|
|
19686
18774
|
var FETCH_TIMEOUT_MS = 5e3;
|
|
19687
18775
|
function readInstalled() {
|
|
19688
|
-
if (!
|
|
19689
|
-
const content =
|
|
18776
|
+
if (!existsSync21(VERSION_FILE)) return "unknown";
|
|
18777
|
+
const content = readFileSync21(VERSION_FILE, "utf-8").trim();
|
|
19690
18778
|
return content || "unknown";
|
|
19691
18779
|
}
|
|
19692
18780
|
async function fetchLatest() {
|
|
@@ -20179,9 +19267,9 @@ app23.post("/", async (c) => {
|
|
|
20179
19267
|
var events_default = app23;
|
|
20180
19268
|
|
|
20181
19269
|
// server/routes/admin/cloudflare.ts
|
|
20182
|
-
import { homedir as
|
|
20183
|
-
import { resolve as
|
|
20184
|
-
import { readFileSync as
|
|
19270
|
+
import { homedir as homedir3 } from "os";
|
|
19271
|
+
import { resolve as resolve25 } from "path";
|
|
19272
|
+
import { readFileSync as readFileSync23 } from "fs";
|
|
20185
19273
|
|
|
20186
19274
|
// app/lib/dns-label.ts
|
|
20187
19275
|
var VALID_LABEL = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
@@ -20197,14 +19285,14 @@ function isValidDomain(value) {
|
|
|
20197
19285
|
}
|
|
20198
19286
|
|
|
20199
19287
|
// app/lib/alias-domains.ts
|
|
20200
|
-
import { existsSync as
|
|
19288
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync13, readFileSync as readFileSync22, writeFileSync as writeFileSync14 } from "fs";
|
|
20201
19289
|
import { dirname as dirname9 } from "path";
|
|
20202
|
-
import { resolve as
|
|
20203
|
-
var ALIAS_DOMAINS_PATH =
|
|
19290
|
+
import { resolve as resolve24 } from "path";
|
|
19291
|
+
var ALIAS_DOMAINS_PATH = resolve24(MAXY_DIR, "alias-domains.json");
|
|
20204
19292
|
function readExisting() {
|
|
20205
|
-
if (!
|
|
19293
|
+
if (!existsSync22(ALIAS_DOMAINS_PATH)) return /* @__PURE__ */ new Set();
|
|
20206
19294
|
try {
|
|
20207
|
-
const parsed = JSON.parse(
|
|
19295
|
+
const parsed = JSON.parse(readFileSync22(ALIAS_DOMAINS_PATH, "utf-8"));
|
|
20208
19296
|
if (!Array.isArray(parsed)) return /* @__PURE__ */ new Set();
|
|
20209
19297
|
return new Set(parsed.filter((h) => typeof h === "string"));
|
|
20210
19298
|
} catch {
|
|
@@ -20215,18 +19303,18 @@ function addAliasDomain(hostname2) {
|
|
|
20215
19303
|
const existing = readExisting();
|
|
20216
19304
|
if (existing.has(hostname2)) return;
|
|
20217
19305
|
existing.add(hostname2);
|
|
20218
|
-
|
|
20219
|
-
|
|
19306
|
+
mkdirSync13(dirname9(ALIAS_DOMAINS_PATH), { recursive: true });
|
|
19307
|
+
writeFileSync14(ALIAS_DOMAINS_PATH, JSON.stringify([...existing], null, 2) + "\n", "utf-8");
|
|
20220
19308
|
}
|
|
20221
19309
|
|
|
20222
19310
|
// server/routes/admin/cloudflare.ts
|
|
20223
19311
|
var SETUP_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
20224
19312
|
var DOMAINS_TIMEOUT_MS = 40 * 1e3;
|
|
20225
19313
|
function loadBrandInfo() {
|
|
20226
|
-
const
|
|
20227
|
-
const brandPath =
|
|
19314
|
+
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? resolve25(process.cwd(), "..");
|
|
19315
|
+
const brandPath = resolve25(platformRoot2, "config", "brand.json");
|
|
20228
19316
|
try {
|
|
20229
|
-
const parsed = JSON.parse(
|
|
19317
|
+
const parsed = JSON.parse(readFileSync23(brandPath, "utf-8"));
|
|
20230
19318
|
const hostname2 = typeof parsed.hostname === "string" && parsed.hostname ? parsed.hostname : "maxy";
|
|
20231
19319
|
const configDir2 = typeof parsed.configDir === "string" && parsed.configDir ? parsed.configDir : ".maxy";
|
|
20232
19320
|
return { hostname: hostname2, configDir: configDir2 };
|
|
@@ -20329,7 +19417,7 @@ app24.get("/domains", requireAdminSession, async (c) => {
|
|
|
20329
19417
|
streamLogPath = streamLogPathFor(accountId, correlationId).streamLogPath;
|
|
20330
19418
|
log2(`phase=stream-log-resolved path=${streamLogPath}`);
|
|
20331
19419
|
const brand = loadBrandInfo();
|
|
20332
|
-
const scriptPath =
|
|
19420
|
+
const scriptPath = resolve25(homedir3(), "list-cf-domains.sh");
|
|
20333
19421
|
const result = await runFormSpawn({
|
|
20334
19422
|
scriptPath,
|
|
20335
19423
|
args: [brand.hostname],
|
|
@@ -20454,7 +19542,7 @@ app24.post("/setup", requireAdminSession, async (c) => {
|
|
|
20454
19542
|
}
|
|
20455
19543
|
streamLogPath = streamLogPathFor(accountId, correlationId).streamLogPath;
|
|
20456
19544
|
log2(`phase=stream-log-resolved path=${streamLogPath}`);
|
|
20457
|
-
const scriptPath =
|
|
19545
|
+
const scriptPath = resolve25(homedir3(), "setup-tunnel.sh");
|
|
20458
19546
|
const args = [brand.hostname, String(port2), adminFqdn];
|
|
20459
19547
|
if (publicFqdn) args.push(publicFqdn);
|
|
20460
19548
|
if (apex) args.push(apex);
|
|
@@ -20523,17 +19611,17 @@ var cloudflare_default = app24;
|
|
|
20523
19611
|
import { createReadStream as createReadStream3 } from "fs";
|
|
20524
19612
|
import { readdir as readdir2, readFile as readFile4, stat as stat4, mkdir as mkdir3, writeFile as writeFile4, unlink as unlink2 } from "fs/promises";
|
|
20525
19613
|
import { realpathSync as realpathSync4 } from "fs";
|
|
20526
|
-
import { basename as basename6, dirname as dirname10, join as
|
|
19614
|
+
import { basename as basename6, dirname as dirname10, join as join11, resolve as resolve27, sep as sep2 } from "path";
|
|
20527
19615
|
import { Readable as Readable3 } from "stream";
|
|
20528
19616
|
|
|
20529
19617
|
// app/lib/data-path.ts
|
|
20530
19618
|
import { realpathSync as realpathSync3 } from "fs";
|
|
20531
|
-
import { resolve as
|
|
20532
|
-
var
|
|
20533
|
-
var DATA_ROOT =
|
|
19619
|
+
import { resolve as resolve26, normalize, sep, relative } from "path";
|
|
19620
|
+
var PLATFORM_ROOT9 = process.env.MAXY_PLATFORM_ROOT ?? resolve26(process.cwd(), "../platform");
|
|
19621
|
+
var DATA_ROOT = resolve26(PLATFORM_ROOT9, "..", "data");
|
|
20534
19622
|
function resolveDataPath(raw2) {
|
|
20535
19623
|
const cleaned = normalize("/" + (raw2 ?? "").replace(/\\/g, "/")).replace(/^\/+/, "");
|
|
20536
|
-
const absolute =
|
|
19624
|
+
const absolute = resolve26(DATA_ROOT, cleaned);
|
|
20537
19625
|
let dataRootReal;
|
|
20538
19626
|
try {
|
|
20539
19627
|
dataRootReal = realpathSync3(DATA_ROOT);
|
|
@@ -20800,7 +19888,7 @@ async function cascadeDeleteDocument(params) {
|
|
|
20800
19888
|
var UUID_RE3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
20801
19889
|
async function readMeta(absDir, baseName) {
|
|
20802
19890
|
try {
|
|
20803
|
-
const raw2 = await readFile4(
|
|
19891
|
+
const raw2 = await readFile4(join11(absDir, `${baseName}.meta.json`), "utf8");
|
|
20804
19892
|
const parsed = JSON.parse(raw2);
|
|
20805
19893
|
if (typeof parsed?.filename === "string") {
|
|
20806
19894
|
return { filename: parsed.filename, mimeType: typeof parsed.mimeType === "string" ? parsed.mimeType : void 0 };
|
|
@@ -20811,7 +19899,7 @@ async function readMeta(absDir, baseName) {
|
|
|
20811
19899
|
}
|
|
20812
19900
|
async function readAccountNames() {
|
|
20813
19901
|
const map = /* @__PURE__ */ new Map();
|
|
20814
|
-
const accountsDir =
|
|
19902
|
+
const accountsDir = resolve27(DATA_ROOT, "accounts");
|
|
20815
19903
|
let names;
|
|
20816
19904
|
try {
|
|
20817
19905
|
names = await readdir2(accountsDir);
|
|
@@ -20820,7 +19908,7 @@ async function readAccountNames() {
|
|
|
20820
19908
|
}
|
|
20821
19909
|
for (const name of names) {
|
|
20822
19910
|
if (!UUID_RE3.test(name)) continue;
|
|
20823
|
-
const configPath2 =
|
|
19911
|
+
const configPath2 = resolve27(accountsDir, name, "account.json");
|
|
20824
19912
|
try {
|
|
20825
19913
|
const raw2 = await readFile4(configPath2, "utf8");
|
|
20826
19914
|
const parsed = JSON.parse(raw2);
|
|
@@ -20838,7 +19926,7 @@ async function readAccountNames() {
|
|
|
20838
19926
|
}
|
|
20839
19927
|
async function enrich(absolute, entry, accountNames) {
|
|
20840
19928
|
if (entry.kind === "directory" && UUID_RE3.test(entry.name)) {
|
|
20841
|
-
const meta = await readMeta(
|
|
19929
|
+
const meta = await readMeta(join11(absolute, entry.name), entry.name);
|
|
20842
19930
|
if (meta?.filename) {
|
|
20843
19931
|
entry.displayName = meta.filename;
|
|
20844
19932
|
entry.mimeType = meta.mimeType;
|
|
@@ -20897,7 +19985,7 @@ app25.get("/", requireAdminSession, async (c) => {
|
|
|
20897
19985
|
continue;
|
|
20898
19986
|
}
|
|
20899
19987
|
try {
|
|
20900
|
-
const entryPath =
|
|
19988
|
+
const entryPath = join11(absolute, name);
|
|
20901
19989
|
const s = await stat4(entryPath);
|
|
20902
19990
|
entries.push({
|
|
20903
19991
|
name,
|
|
@@ -21011,8 +20099,8 @@ app25.post("/upload", requireAdminSession, async (c) => {
|
|
|
21011
20099
|
}
|
|
21012
20100
|
const safeName = basename6(file.name).replace(/[\0/\\]/g, "_");
|
|
21013
20101
|
const finalName = `${Date.now()}-${safeName}`;
|
|
21014
|
-
const destDir =
|
|
21015
|
-
const destPath =
|
|
20102
|
+
const destDir = resolve27(DATA_ROOT, "uploads", accountId);
|
|
20103
|
+
const destPath = resolve27(destDir, finalName);
|
|
21016
20104
|
try {
|
|
21017
20105
|
await mkdir3(destDir, { recursive: true });
|
|
21018
20106
|
const dataRootReal = realpathSync4(DATA_ROOT);
|
|
@@ -21070,7 +20158,7 @@ app25.delete("/", requireAdminSession, async (c) => {
|
|
|
21070
20158
|
}
|
|
21071
20159
|
const dot = base.lastIndexOf(".");
|
|
21072
20160
|
const stem = dot === -1 ? base : base.slice(0, dot);
|
|
21073
|
-
const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ?
|
|
20161
|
+
const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ? join11(dirname10(absolute), `${stem}.meta.json`) : null;
|
|
21074
20162
|
await unlink2(absolute);
|
|
21075
20163
|
if (sidecarPath) {
|
|
21076
20164
|
try {
|
|
@@ -21216,8 +20304,7 @@ var FILTER_EXCLUDED_LABELS = Object.freeze(
|
|
|
21216
20304
|
"Message",
|
|
21217
20305
|
"UserMessage",
|
|
21218
20306
|
"AssistantMessage",
|
|
21219
|
-
"
|
|
21220
|
-
"PublicConversation",
|
|
20307
|
+
"Conversation",
|
|
21221
20308
|
"Section",
|
|
21222
20309
|
"ToolCall",
|
|
21223
20310
|
"WorkflowRun",
|
|
@@ -21947,15 +21034,15 @@ app34.route("/adherence", adherence_default);
|
|
|
21947
21034
|
var admin_default = app34;
|
|
21948
21035
|
|
|
21949
21036
|
// server/index.ts
|
|
21950
|
-
var
|
|
21951
|
-
var BRAND_JSON_PATH =
|
|
21037
|
+
var PLATFORM_ROOT10 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
21038
|
+
var BRAND_JSON_PATH = PLATFORM_ROOT10 ? join12(PLATFORM_ROOT10, "config", "brand.json") : "";
|
|
21952
21039
|
var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", domain: "getmaxy.com" };
|
|
21953
|
-
if (BRAND_JSON_PATH && !
|
|
21040
|
+
if (BRAND_JSON_PATH && !existsSync23(BRAND_JSON_PATH)) {
|
|
21954
21041
|
console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
|
|
21955
21042
|
}
|
|
21956
|
-
if (BRAND_JSON_PATH &&
|
|
21043
|
+
if (BRAND_JSON_PATH && existsSync23(BRAND_JSON_PATH)) {
|
|
21957
21044
|
try {
|
|
21958
|
-
const parsed = JSON.parse(
|
|
21045
|
+
const parsed = JSON.parse(readFileSync24(BRAND_JSON_PATH, "utf-8"));
|
|
21959
21046
|
BRAND = { ...BRAND, ...parsed };
|
|
21960
21047
|
} catch (err) {
|
|
21961
21048
|
console.error(`[brand] Failed to parse brand.json: ${err.message}`);
|
|
@@ -21974,11 +21061,11 @@ var brandLoginOpts = {
|
|
|
21974
21061
|
bodyFont: BRAND.defaultFonts?.body,
|
|
21975
21062
|
logoContainsName: !!BRAND.logoContainsName
|
|
21976
21063
|
};
|
|
21977
|
-
var ALIAS_DOMAINS_PATH2 =
|
|
21064
|
+
var ALIAS_DOMAINS_PATH2 = join12(homedir4(), BRAND.configDir, "alias-domains.json");
|
|
21978
21065
|
function loadAliasDomains() {
|
|
21979
21066
|
try {
|
|
21980
|
-
if (!
|
|
21981
|
-
const parsed = JSON.parse(
|
|
21067
|
+
if (!existsSync23(ALIAS_DOMAINS_PATH2)) return null;
|
|
21068
|
+
const parsed = JSON.parse(readFileSync24(ALIAS_DOMAINS_PATH2, "utf-8"));
|
|
21982
21069
|
if (!Array.isArray(parsed)) {
|
|
21983
21070
|
console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
|
|
21984
21071
|
return null;
|
|
@@ -22318,20 +21405,20 @@ app35.get("/agent-assets/:slug/:filename", (c) => {
|
|
|
22318
21405
|
console.error(`[agent-assets] no-account slug=${slug} file=${filename}`);
|
|
22319
21406
|
return c.text("Not found", 404);
|
|
22320
21407
|
}
|
|
22321
|
-
const filePath =
|
|
22322
|
-
const expectedDir =
|
|
21408
|
+
const filePath = resolve28(account.accountDir, "agents", slug, "assets", filename);
|
|
21409
|
+
const expectedDir = resolve28(account.accountDir, "agents", slug, "assets");
|
|
22323
21410
|
if (!filePath.startsWith(expectedDir + "/")) {
|
|
22324
21411
|
console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
|
|
22325
21412
|
return c.text("Forbidden", 403);
|
|
22326
21413
|
}
|
|
22327
|
-
if (!
|
|
21414
|
+
if (!existsSync23(filePath)) {
|
|
22328
21415
|
console.error(`[agent-assets] serve slug=${slug} file=${filename} status=404`);
|
|
22329
21416
|
return c.text("Not found", 404);
|
|
22330
21417
|
}
|
|
22331
21418
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
22332
21419
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
22333
21420
|
console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
|
|
22334
|
-
const body =
|
|
21421
|
+
const body = readFileSync24(filePath);
|
|
22335
21422
|
return c.body(body, 200, {
|
|
22336
21423
|
"Content-Type": contentType,
|
|
22337
21424
|
"Cache-Control": "public, max-age=3600"
|
|
@@ -22348,20 +21435,20 @@ app35.get("/generated/:filename", (c) => {
|
|
|
22348
21435
|
console.error(`[generated] serve file=${filename} status=404`);
|
|
22349
21436
|
return c.text("Not found", 404);
|
|
22350
21437
|
}
|
|
22351
|
-
const filePath =
|
|
22352
|
-
const expectedDir =
|
|
21438
|
+
const filePath = resolve28(account.accountDir, "generated", filename);
|
|
21439
|
+
const expectedDir = resolve28(account.accountDir, "generated");
|
|
22353
21440
|
if (!filePath.startsWith(expectedDir + "/")) {
|
|
22354
21441
|
console.error(`[generated] serve file=${filename} status=403`);
|
|
22355
21442
|
return c.text("Forbidden", 403);
|
|
22356
21443
|
}
|
|
22357
|
-
if (!
|
|
21444
|
+
if (!existsSync23(filePath)) {
|
|
22358
21445
|
console.error(`[generated] serve file=${filename} status=404`);
|
|
22359
21446
|
return c.text("Not found", 404);
|
|
22360
21447
|
}
|
|
22361
21448
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
22362
21449
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
22363
21450
|
console.log(`[generated] serve file=${filename} status=200`);
|
|
22364
|
-
const body =
|
|
21451
|
+
const body = readFileSync24(filePath);
|
|
22365
21452
|
return c.body(body, 200, {
|
|
22366
21453
|
"Content-Type": contentType,
|
|
22367
21454
|
"Cache-Control": "public, max-age=86400"
|
|
@@ -22370,9 +21457,9 @@ app35.get("/generated/:filename", (c) => {
|
|
|
22370
21457
|
var htmlCache = /* @__PURE__ */ new Map();
|
|
22371
21458
|
var brandLogoPath = "/brand/maxy-monochrome.png";
|
|
22372
21459
|
var brandIconPath = "/brand/maxy-monochrome.png";
|
|
22373
|
-
if (BRAND_JSON_PATH &&
|
|
21460
|
+
if (BRAND_JSON_PATH && existsSync23(BRAND_JSON_PATH)) {
|
|
22374
21461
|
try {
|
|
22375
|
-
const fullBrand = JSON.parse(
|
|
21462
|
+
const fullBrand = JSON.parse(readFileSync24(BRAND_JSON_PATH, "utf-8"));
|
|
22376
21463
|
if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
|
|
22377
21464
|
brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
|
|
22378
21465
|
} catch {
|
|
@@ -22388,10 +21475,10 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
|
|
|
22388
21475
|
})}</script>`;
|
|
22389
21476
|
function readInstalledVersion() {
|
|
22390
21477
|
try {
|
|
22391
|
-
if (!
|
|
22392
|
-
const versionFile =
|
|
22393
|
-
if (!
|
|
22394
|
-
const content =
|
|
21478
|
+
if (!PLATFORM_ROOT10) return "unknown";
|
|
21479
|
+
const versionFile = join12(PLATFORM_ROOT10, "config", `.${BRAND.hostname}-version`);
|
|
21480
|
+
if (!existsSync23(versionFile)) return "unknown";
|
|
21481
|
+
const content = readFileSync24(versionFile, "utf-8").trim();
|
|
22395
21482
|
return content || "unknown";
|
|
22396
21483
|
} catch {
|
|
22397
21484
|
return "unknown";
|
|
@@ -22432,9 +21519,9 @@ var clientErrorReporterScript = `<script>
|
|
|
22432
21519
|
function cachedHtml(file) {
|
|
22433
21520
|
let html = htmlCache.get(file);
|
|
22434
21521
|
if (!html) {
|
|
22435
|
-
html =
|
|
22436
|
-
html = html.replace("<title>Maxy</title>", `<title>${
|
|
22437
|
-
html = html.replace('href="/favicon.ico"', `href="${
|
|
21522
|
+
html = readFileSync24(resolve28(process.cwd(), "public", file), "utf-8");
|
|
21523
|
+
html = html.replace("<title>Maxy</title>", `<title>${escapeHtml(BRAND.productName)}</title>`);
|
|
21524
|
+
html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
|
|
22438
21525
|
const headInjection = file === "index.html" ? `${brandScript}
|
|
22439
21526
|
${versionScript}
|
|
22440
21527
|
${clientErrorReporterScript}
|
|
@@ -22447,26 +21534,26 @@ ${clientErrorReporterScript}
|
|
|
22447
21534
|
}
|
|
22448
21535
|
var brandedHtmlCache = /* @__PURE__ */ new Map();
|
|
22449
21536
|
function loadBrandingCache(agentSlug) {
|
|
22450
|
-
const configDir2 =
|
|
21537
|
+
const configDir2 = join12(homedir4(), BRAND.configDir);
|
|
22451
21538
|
try {
|
|
22452
|
-
const accountJsonPath =
|
|
22453
|
-
if (!
|
|
22454
|
-
const account = JSON.parse(
|
|
21539
|
+
const accountJsonPath = join12(configDir2, "account.json");
|
|
21540
|
+
if (!existsSync23(accountJsonPath)) return null;
|
|
21541
|
+
const account = JSON.parse(readFileSync24(accountJsonPath, "utf-8"));
|
|
22455
21542
|
const accountId = account.accountId;
|
|
22456
21543
|
if (!accountId) return null;
|
|
22457
|
-
const cachePath =
|
|
22458
|
-
if (!
|
|
22459
|
-
return JSON.parse(
|
|
21544
|
+
const cachePath = join12(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
|
|
21545
|
+
if (!existsSync23(cachePath)) return null;
|
|
21546
|
+
return JSON.parse(readFileSync24(cachePath, "utf-8"));
|
|
22460
21547
|
} catch {
|
|
22461
21548
|
return null;
|
|
22462
21549
|
}
|
|
22463
21550
|
}
|
|
22464
21551
|
function resolveDefaultSlug() {
|
|
22465
21552
|
try {
|
|
22466
|
-
const configDir2 =
|
|
22467
|
-
const accountJsonPath =
|
|
22468
|
-
if (!
|
|
22469
|
-
const account = JSON.parse(
|
|
21553
|
+
const configDir2 = join12(homedir4(), BRAND.configDir);
|
|
21554
|
+
const accountJsonPath = join12(configDir2, "account.json");
|
|
21555
|
+
if (!existsSync23(accountJsonPath)) return null;
|
|
21556
|
+
const account = JSON.parse(readFileSync24(accountJsonPath, "utf-8"));
|
|
22470
21557
|
return account.defaultAgent || null;
|
|
22471
21558
|
} catch {
|
|
22472
21559
|
return null;
|
|
@@ -22480,26 +21567,26 @@ function brandedPublicHtml(agentSlug) {
|
|
|
22480
21567
|
if (!branding) return baseHtml;
|
|
22481
21568
|
const brandHash = JSON.stringify(branding).length;
|
|
22482
21569
|
if (cached && cached.mtime === brandHash) return cached.html;
|
|
22483
|
-
const title =
|
|
22484
|
-
const description = branding.tagline ?
|
|
21570
|
+
const title = escapeHtml(branding.name);
|
|
21571
|
+
const description = branding.tagline ? escapeHtml(branding.tagline) : "";
|
|
22485
21572
|
const themeColor = branding.primaryColor || "";
|
|
22486
21573
|
const ogImage = branding.logoUrl || "";
|
|
22487
21574
|
const favicon = branding.faviconUrl || "";
|
|
22488
21575
|
const metaTags = [
|
|
22489
21576
|
` <title>${title}</title>`,
|
|
22490
|
-
favicon ? ` <link rel="icon" href="${
|
|
22491
|
-
themeColor ? ` <meta name="theme-color" content="${
|
|
21577
|
+
favicon ? ` <link rel="icon" href="${escapeHtml(favicon)}">` : ` <link rel="icon" href="${escapeHtml(brandFaviconPath)}">`,
|
|
21578
|
+
themeColor ? ` <meta name="theme-color" content="${escapeHtml(themeColor)}">` : "",
|
|
22492
21579
|
` <meta property="og:title" content="${title}">`,
|
|
22493
21580
|
description ? ` <meta property="og:description" content="${description}">` : "",
|
|
22494
|
-
ogImage ? ` <meta property="og:image" content="${
|
|
21581
|
+
ogImage ? ` <meta property="og:image" content="${escapeHtml(ogImage)}">` : "",
|
|
22495
21582
|
' <meta property="og:type" content="website">'
|
|
22496
21583
|
].filter(Boolean).join("\n");
|
|
22497
|
-
const html = baseHtml.replace(` <title>${
|
|
22498
|
-
<link rel="icon" href="${
|
|
21584
|
+
const html = baseHtml.replace(` <title>${escapeHtml(BRAND.productName)}</title>
|
|
21585
|
+
<link rel="icon" href="${escapeHtml(brandFaviconPath)}">`, metaTags);
|
|
22499
21586
|
brandedHtmlCache.set(agentSlug, { html, mtime: brandHash });
|
|
22500
21587
|
return html;
|
|
22501
21588
|
}
|
|
22502
|
-
function
|
|
21589
|
+
function escapeHtml(s) {
|
|
22503
21590
|
return s.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
22504
21591
|
}
|
|
22505
21592
|
app35.get("/", (c) => {
|
|
@@ -22539,11 +21626,11 @@ app35.use("/vnc-popout.html", logViewerFetch);
|
|
|
22539
21626
|
app35.get("/vnc-popout.html", (c) => {
|
|
22540
21627
|
let html = htmlCache.get("vnc-popout.html");
|
|
22541
21628
|
if (!html) {
|
|
22542
|
-
html =
|
|
22543
|
-
const name =
|
|
21629
|
+
html = readFileSync24(resolve28(process.cwd(), "public", "vnc-popout.html"), "utf-8");
|
|
21630
|
+
const name = escapeHtml(BRAND.productName);
|
|
22544
21631
|
html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
|
|
22545
21632
|
html = html.replace("</head>", ` ${brandScript}
|
|
22546
|
-
<link rel="icon" href="${
|
|
21633
|
+
<link rel="icon" href="${escapeHtml(brandFaviconPath)}">
|
|
22547
21634
|
</head>`);
|
|
22548
21635
|
htmlCache.set("vnc-popout.html", html);
|
|
22549
21636
|
}
|
|
@@ -22593,36 +21680,15 @@ app35.get("/:slug", async (c, next) => {
|
|
|
22593
21680
|
await next();
|
|
22594
21681
|
});
|
|
22595
21682
|
app35.use("/*", serveStatic({ root: "./public" }));
|
|
22596
|
-
var port = parseInt(process.env.PORT ?? "
|
|
22597
|
-
var hostname = process.env.HOSTNAME ?? "
|
|
21683
|
+
var port = parseInt(process.env.PORT ?? "19199", 10);
|
|
21684
|
+
var hostname = process.env.HOSTNAME ?? "127.0.0.1";
|
|
22598
21685
|
var httpServer = serve({ fetch: app35.fetch, port, hostname });
|
|
22599
|
-
attachVncWsProxy(httpServer, {
|
|
22600
|
-
isPublicHost,
|
|
22601
|
-
upstreamHost: "127.0.0.1",
|
|
22602
|
-
upstreamPort: 6080
|
|
22603
|
-
});
|
|
22604
21686
|
attachTerminalWsProxy(httpServer, {
|
|
22605
21687
|
isPublicHost,
|
|
22606
21688
|
upstreamHost: "127.0.0.1",
|
|
22607
21689
|
upstreamPort: 7681
|
|
22608
21690
|
});
|
|
22609
21691
|
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
21692
|
var SUBAPP_MANIFEST = [
|
|
22627
21693
|
{ prefix: "/api/health", file: "server/routes/health.ts", subapp: health_default },
|
|
22628
21694
|
{ prefix: "/api/session", file: "server/routes/session.ts", subapp: session_default },
|
|
@@ -22655,8 +21721,8 @@ try {
|
|
|
22655
21721
|
(async () => {
|
|
22656
21722
|
try {
|
|
22657
21723
|
let userId = "";
|
|
22658
|
-
if (
|
|
22659
|
-
const users = JSON.parse(
|
|
21724
|
+
if (existsSync23(USERS_FILE)) {
|
|
21725
|
+
const users = JSON.parse(readFileSync24(USERS_FILE, "utf-8").trim() || "[]");
|
|
22660
21726
|
userId = users[0]?.userId ?? "";
|
|
22661
21727
|
}
|
|
22662
21728
|
await backfillNullUserIdConversations(userId);
|
|
@@ -22682,7 +21748,7 @@ if (bootAccountConfig?.whatsapp) {
|
|
|
22682
21748
|
}
|
|
22683
21749
|
init({
|
|
22684
21750
|
configDir: configDirForWhatsApp,
|
|
22685
|
-
platformRoot:
|
|
21751
|
+
platformRoot: resolve28(process.env.MAXY_PLATFORM_ROOT ?? join12(__dirname, "..")),
|
|
22686
21752
|
accountConfig: bootAccountConfig,
|
|
22687
21753
|
onMessage: async (msg) => {
|
|
22688
21754
|
try {
|