@rubytech/create-maxy 1.0.676 → 1.0.678
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +77 -166
- package/dist/uninstall.js +24 -0
- package/package.json +1 -1
- package/payload/platform/plugins/docs/references/deployment.md +2 -3
- package/payload/platform/plugins/docs/references/internals.md +4 -0
- package/payload/platform/plugins/docs/references/platform.md +1 -1
- package/payload/platform/plugins/docs/references/troubleshooting.md +2 -2
- package/payload/platform/scripts/vnc.sh +34 -13
- package/payload/platform/templates/systemd/maxy-edge.service +33 -0
- package/payload/server/chunk-5YIXIF6C.js +726 -0
- package/payload/server/maxy-edge.js +422 -0
- package/payload/server/public/assets/{admin-DQmUdTBa.js → admin-BBL1no_g.js} +1 -1
- package/payload/server/public/assets/{data-DVlvxbTt.js → data-DUSyrydY.js} +1 -1
- package/payload/server/public/assets/{file-OY_hX2wu.js → file-CDJ6dUV3.js} +1 -1
- package/payload/server/public/assets/graph-CWcYp5bE.js +50 -0
- package/payload/server/public/assets/{house-CgENfOCP.js → house-CNP_bwvT.js} +1 -1
- package/payload/server/public/assets/{jsx-runtime-Bu4vXoe7.css → jsx-runtime-BFFQvkdQ.css} +1 -1
- package/payload/server/public/assets/{public-Clp4VPwo.js → public-sHoAccvb.js} +1 -1
- package/payload/server/public/assets/{share-2-RSIR3MmX.js → share-2-DBcb9j6E.js} +1 -1
- package/payload/server/public/assets/{useVoiceRecorder-B0FI_hts.js → useVoiceRecorder-CtSgpc95.js} +1 -1
- package/payload/server/public/assets/{x-DKZ5NR3n.js → x-CTVJaC_u.js} +1 -1
- package/payload/server/public/data.html +6 -6
- package/payload/server/public/graph.html +6 -6
- package/payload/server/public/index.html +7 -7
- package/payload/server/public/public.html +4 -4
- package/payload/server/server.js +704 -1874
- package/payload/platform/templates/dotfiles/.tmux.conf +0 -1
- package/payload/platform/templates/systemd/maxy-ttyd.service +0 -25
- package/payload/server/public/assets/graph-BDaM4Qer.js +0 -49
- /package/payload/server/public/assets/{jsx-runtime-C_VUlXvu.js → jsx-runtime-BVKWELH6.js} +0 -0
package/payload/server/server.js
CHANGED
|
@@ -1,28 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
1
|
+
import {
|
|
2
|
+
BIN_DIR,
|
|
3
|
+
CLAUDE_CREDENTIALS_FILE,
|
|
4
|
+
LOG_DIR,
|
|
5
|
+
MAXY_DIR,
|
|
6
|
+
TELEGRAM_ADMIN_WEBHOOK_SECRET_FILE,
|
|
7
|
+
TELEGRAM_WEBHOOK_SECRET_FILE,
|
|
8
|
+
USERS_FILE,
|
|
9
|
+
__commonJS,
|
|
10
|
+
__toESM,
|
|
11
|
+
canAccessAdmin,
|
|
12
|
+
checkRateLimit,
|
|
13
|
+
clearRateLimit,
|
|
14
|
+
createRemoteSession,
|
|
15
|
+
hashPassword,
|
|
16
|
+
invalidateRemoteSession,
|
|
17
|
+
isPasswordValid,
|
|
18
|
+
isRemoteAuthConfigured,
|
|
19
|
+
recordFailedAttempt,
|
|
20
|
+
renderLoginPage,
|
|
21
|
+
resolveClientIp,
|
|
22
|
+
sanitizeClientCorrId,
|
|
23
|
+
setRemotePassword,
|
|
24
|
+
validatePasswordStrength,
|
|
25
|
+
verifyPassword,
|
|
26
|
+
verifyRemotePassword,
|
|
27
|
+
vncLog
|
|
28
|
+
} from "./chunk-5YIXIF6C.js";
|
|
26
29
|
|
|
27
30
|
// ../lib/models/dist/index.js
|
|
28
31
|
var require_dist = __commonJS({
|
|
@@ -2526,7 +2529,7 @@ var responseViaResponseObject = async (res, outgoing, options = {}) => {
|
|
|
2526
2529
|
});
|
|
2527
2530
|
if (!chunk) {
|
|
2528
2531
|
if (i === 1) {
|
|
2529
|
-
await new Promise((
|
|
2532
|
+
await new Promise((resolve28) => setTimeout(resolve28));
|
|
2530
2533
|
maxReadCount = 3;
|
|
2531
2534
|
continue;
|
|
2532
2535
|
}
|
|
@@ -2892,1248 +2895,9 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
2892
2895
|
};
|
|
2893
2896
|
|
|
2894
2897
|
// server/index.ts
|
|
2895
|
-
import { readFileSync as
|
|
2896
|
-
import {
|
|
2897
|
-
import {
|
|
2898
|
-
import { homedir as homedir5 } from "os";
|
|
2899
|
-
|
|
2900
|
-
// app/lib/vnc-logger.ts
|
|
2901
|
-
import { appendFileSync, mkdirSync } from "fs";
|
|
2902
|
-
import { resolve as resolve2 } from "path";
|
|
2903
|
-
|
|
2904
|
-
// app/lib/paths.ts
|
|
2905
|
-
import { homedir } from "os";
|
|
2906
|
-
import { resolve, join as join2 } from "path";
|
|
2907
|
-
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
2908
|
-
var configDirName = ".maxy";
|
|
2909
|
-
var platformRoot = process.env.MAXY_PLATFORM_ROOT;
|
|
2910
|
-
if (platformRoot) {
|
|
2911
|
-
const brandPath = join2(platformRoot, "config", "brand.json");
|
|
2912
|
-
if (existsSync2(brandPath)) {
|
|
2913
|
-
try {
|
|
2914
|
-
const brand = JSON.parse(readFileSync(brandPath, "utf-8"));
|
|
2915
|
-
if (brand.configDir) configDirName = brand.configDir;
|
|
2916
|
-
} catch {
|
|
2917
|
-
}
|
|
2918
|
-
}
|
|
2919
|
-
}
|
|
2920
|
-
var MAXY_DIR = resolve(homedir(), configDirName);
|
|
2921
|
-
var PLATFORM_ROOT = process.env.MAXY_PLATFORM_ROOT ?? resolve(process.cwd(), "..");
|
|
2922
|
-
var USERS_FILE = resolve(PLATFORM_ROOT, "config", "users.json");
|
|
2923
|
-
var LOG_DIR = resolve(MAXY_DIR, "logs");
|
|
2924
|
-
var BIN_DIR = resolve(MAXY_DIR, "bin");
|
|
2925
|
-
var REMOTE_PASSWORD_FILE = resolve(MAXY_DIR, ".remote-password");
|
|
2926
|
-
var TELEGRAM_WEBHOOK_SECRET_FILE = resolve(MAXY_DIR, ".telegram-webhook-secret");
|
|
2927
|
-
var TELEGRAM_ADMIN_WEBHOOK_SECRET_FILE = resolve(MAXY_DIR, ".telegram-admin-webhook-secret");
|
|
2928
|
-
var CLAUDE_CREDENTIALS_FILE = resolve(homedir(), ".claude", ".credentials.json");
|
|
2929
|
-
|
|
2930
|
-
// app/lib/vnc-logger.ts
|
|
2931
|
-
var VNC_LOG_FILE = resolve2(LOG_DIR, "vnc-boot.log");
|
|
2932
|
-
try {
|
|
2933
|
-
mkdirSync(LOG_DIR, { recursive: true });
|
|
2934
|
-
} catch (err) {
|
|
2935
|
-
console.error(`[vnc-log-fail] mkdir ${LOG_DIR} failed: ${err.message}`);
|
|
2936
|
-
}
|
|
2937
|
-
function vncLog(phase, fields = {}) {
|
|
2938
|
-
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
2939
|
-
const kv = Object.entries(fields).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(" ");
|
|
2940
|
-
const line = kv.length > 0 ? `[${ts}] [${phase}] ${kv}
|
|
2941
|
-
` : `[${ts}] [${phase}]
|
|
2942
|
-
`;
|
|
2943
|
-
try {
|
|
2944
|
-
appendFileSync(VNC_LOG_FILE, line);
|
|
2945
|
-
} catch (err) {
|
|
2946
|
-
console.error(`[vnc-log-fail] ${err.message} \u2014 dropped: ${line.slice(0, 300).trim()}`);
|
|
2947
|
-
}
|
|
2948
|
-
}
|
|
2949
|
-
var corrCounter = 0;
|
|
2950
|
-
function newCorrId() {
|
|
2951
|
-
corrCounter = corrCounter + 1 & 4294967295;
|
|
2952
|
-
const counterHex = corrCounter.toString(16).padStart(8, "0");
|
|
2953
|
-
const randomHex = Math.floor(Math.random() * 16777215).toString(16).padStart(6, "0");
|
|
2954
|
-
return `${counterHex}-${randomHex}`;
|
|
2955
|
-
}
|
|
2956
|
-
function sanitizeClientCorrId(raw2) {
|
|
2957
|
-
if (!raw2) return null;
|
|
2958
|
-
if (raw2.length > 32) return null;
|
|
2959
|
-
if (!/^[A-Za-z0-9_-]+$/.test(raw2)) return null;
|
|
2960
|
-
return raw2;
|
|
2961
|
-
}
|
|
2962
|
-
|
|
2963
|
-
// app/lib/remote-auth.ts
|
|
2964
|
-
import { scrypt, randomBytes, timingSafeEqual } from "crypto";
|
|
2965
|
-
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
2966
|
-
|
|
2967
|
-
// app/lib/password-strength.ts
|
|
2968
|
-
function validatePasswordStrength(password) {
|
|
2969
|
-
return [
|
|
2970
|
-
{ key: "length", label: "At least 8 characters", met: password.length >= 8 },
|
|
2971
|
-
{ key: "number", label: "Contains a number", met: /\d/.test(password) },
|
|
2972
|
-
{ key: "special", label: "Contains a special character", met: /[^A-Za-z0-9]/.test(password) },
|
|
2973
|
-
{ key: "whitespace", label: "No spaces", met: password.length > 0 && !/\s/.test(password) }
|
|
2974
|
-
];
|
|
2975
|
-
}
|
|
2976
|
-
function isPasswordValid(password) {
|
|
2977
|
-
return validatePasswordStrength(password).every((r) => r.met);
|
|
2978
|
-
}
|
|
2979
|
-
|
|
2980
|
-
// app/lib/remote-auth.ts
|
|
2981
|
-
var SCRYPT_N = 16384;
|
|
2982
|
-
var SCRYPT_R = 8;
|
|
2983
|
-
var SCRYPT_P = 1;
|
|
2984
|
-
var SCRYPT_KEYLEN = 64;
|
|
2985
|
-
function scryptAsync(password, salt) {
|
|
2986
|
-
return new Promise((resolve31, reject) => {
|
|
2987
|
-
scrypt(password, salt, SCRYPT_KEYLEN, { N: SCRYPT_N, r: SCRYPT_R, p: SCRYPT_P }, (err, key) => {
|
|
2988
|
-
if (err) reject(err);
|
|
2989
|
-
else resolve31(key);
|
|
2990
|
-
});
|
|
2991
|
-
});
|
|
2992
|
-
}
|
|
2993
|
-
async function hashPassword(password) {
|
|
2994
|
-
const salt = randomBytes(32);
|
|
2995
|
-
const hash = await scryptAsync(password, salt);
|
|
2996
|
-
return `${salt.toString("hex")}:${hash.toString("hex")}`;
|
|
2997
|
-
}
|
|
2998
|
-
async function verifyPassword(password, stored) {
|
|
2999
|
-
const colonIndex = stored.indexOf(":");
|
|
3000
|
-
if (colonIndex === -1) return false;
|
|
3001
|
-
const saltHex = stored.slice(0, colonIndex);
|
|
3002
|
-
const hashHex = stored.slice(colonIndex + 1);
|
|
3003
|
-
if (!saltHex || !hashHex) return false;
|
|
3004
|
-
try {
|
|
3005
|
-
const salt = Buffer.from(saltHex, "hex");
|
|
3006
|
-
const storedHash = Buffer.from(hashHex, "hex");
|
|
3007
|
-
if (storedHash.length !== SCRYPT_KEYLEN) return false;
|
|
3008
|
-
const computed = await scryptAsync(password, salt);
|
|
3009
|
-
return timingSafeEqual(computed, storedHash);
|
|
3010
|
-
} catch {
|
|
3011
|
-
return false;
|
|
3012
|
-
}
|
|
3013
|
-
}
|
|
3014
|
-
function isRemoteAuthConfigured() {
|
|
3015
|
-
if (!existsSync3(REMOTE_PASSWORD_FILE)) return false;
|
|
3016
|
-
const content = readFileSync2(REMOTE_PASSWORD_FILE, "utf-8").trim();
|
|
3017
|
-
return content.length > 0 && content.includes(":");
|
|
3018
|
-
}
|
|
3019
|
-
function readPasswordHash() {
|
|
3020
|
-
try {
|
|
3021
|
-
if (!existsSync3(REMOTE_PASSWORD_FILE)) return null;
|
|
3022
|
-
const content = readFileSync2(REMOTE_PASSWORD_FILE, "utf-8").trim();
|
|
3023
|
-
if (!content || !content.includes(":")) return null;
|
|
3024
|
-
return content;
|
|
3025
|
-
} catch {
|
|
3026
|
-
return null;
|
|
3027
|
-
}
|
|
3028
|
-
}
|
|
3029
|
-
async function setRemotePassword(password) {
|
|
3030
|
-
const hash = await hashPassword(password);
|
|
3031
|
-
writeFileSync(REMOTE_PASSWORD_FILE, hash, "utf-8");
|
|
3032
|
-
}
|
|
3033
|
-
async function verifyRemotePassword(password) {
|
|
3034
|
-
const stored = readPasswordHash();
|
|
3035
|
-
if (!stored) return false;
|
|
3036
|
-
return verifyPassword(password, stored);
|
|
3037
|
-
}
|
|
3038
|
-
var SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
3039
|
-
var remoteSessions = /* @__PURE__ */ new Map();
|
|
3040
|
-
function pruneExpiredSessions() {
|
|
3041
|
-
const now = Date.now();
|
|
3042
|
-
for (const [token, session] of remoteSessions) {
|
|
3043
|
-
if (now - session.createdAt > SESSION_TTL_MS) {
|
|
3044
|
-
remoteSessions.delete(token);
|
|
3045
|
-
}
|
|
3046
|
-
}
|
|
3047
|
-
}
|
|
3048
|
-
function createRemoteSession() {
|
|
3049
|
-
const token = randomBytes(32).toString("hex");
|
|
3050
|
-
remoteSessions.set(token, { createdAt: Date.now() });
|
|
3051
|
-
return token;
|
|
3052
|
-
}
|
|
3053
|
-
function validateRemoteSession(token) {
|
|
3054
|
-
if (!token) return false;
|
|
3055
|
-
pruneExpiredSessions();
|
|
3056
|
-
const session = remoteSessions.get(token);
|
|
3057
|
-
if (!session) return false;
|
|
3058
|
-
if (Date.now() - session.createdAt > SESSION_TTL_MS) {
|
|
3059
|
-
remoteSessions.delete(token);
|
|
3060
|
-
return false;
|
|
3061
|
-
}
|
|
3062
|
-
return true;
|
|
3063
|
-
}
|
|
3064
|
-
function invalidateRemoteSession(token) {
|
|
3065
|
-
remoteSessions.delete(token);
|
|
3066
|
-
}
|
|
3067
|
-
var MAX_ATTEMPTS = 5;
|
|
3068
|
-
var LOCKOUT_MS = 15 * 60 * 1e3;
|
|
3069
|
-
var rateLimitMap = /* @__PURE__ */ new Map();
|
|
3070
|
-
function checkRateLimit(ip) {
|
|
3071
|
-
const rec = rateLimitMap.get(ip);
|
|
3072
|
-
if (!rec) return null;
|
|
3073
|
-
if (rec.lockedUntil > Date.now()) {
|
|
3074
|
-
const remaining = Math.ceil((rec.lockedUntil - Date.now()) / 1e3);
|
|
3075
|
-
return `Too many attempts. Try again in ${remaining}s`;
|
|
3076
|
-
}
|
|
3077
|
-
if (rec.lockedUntil > 0) {
|
|
3078
|
-
rateLimitMap.delete(ip);
|
|
3079
|
-
}
|
|
3080
|
-
return null;
|
|
3081
|
-
}
|
|
3082
|
-
function recordFailedAttempt(ip) {
|
|
3083
|
-
const rec = rateLimitMap.get(ip) ?? { count: 0, lockedUntil: 0 };
|
|
3084
|
-
rec.count++;
|
|
3085
|
-
if (rec.count >= MAX_ATTEMPTS) {
|
|
3086
|
-
rec.lockedUntil = Date.now() + LOCKOUT_MS;
|
|
3087
|
-
rec.count = 0;
|
|
3088
|
-
}
|
|
3089
|
-
rateLimitMap.set(ip, rec);
|
|
3090
|
-
}
|
|
3091
|
-
function clearRateLimit(ip) {
|
|
3092
|
-
rateLimitMap.delete(ip);
|
|
3093
|
-
}
|
|
3094
|
-
setInterval(() => {
|
|
3095
|
-
const now = Date.now();
|
|
3096
|
-
for (const [ip, rec] of rateLimitMap) {
|
|
3097
|
-
if (rec.lockedUntil > 0 && rec.lockedUntil <= now) {
|
|
3098
|
-
rateLimitMap.delete(ip);
|
|
3099
|
-
}
|
|
3100
|
-
}
|
|
3101
|
-
}, 15 * 60 * 1e3).unref();
|
|
3102
|
-
function normalizeIp(ip) {
|
|
3103
|
-
if (!ip) return void 0;
|
|
3104
|
-
const trimmed = ip.trim();
|
|
3105
|
-
if (trimmed.startsWith("::ffff:")) return trimmed.slice(7);
|
|
3106
|
-
return trimmed;
|
|
3107
|
-
}
|
|
3108
|
-
function isLoopback(ip) {
|
|
3109
|
-
return ip === "127.0.0.1" || ip.startsWith("127.") || ip === "::1";
|
|
3110
|
-
}
|
|
3111
|
-
function isPrivateIp(ip) {
|
|
3112
|
-
const parts = ip.split(".");
|
|
3113
|
-
if (parts.length !== 4) return false;
|
|
3114
|
-
const a = parseInt(parts[0], 10);
|
|
3115
|
-
const b = parseInt(parts[1], 10);
|
|
3116
|
-
if (Number.isNaN(a) || Number.isNaN(b)) return false;
|
|
3117
|
-
if (a === 10) return true;
|
|
3118
|
-
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
3119
|
-
if (a === 192 && b === 168) return true;
|
|
3120
|
-
if (a === 169 && b === 254) return true;
|
|
3121
|
-
return false;
|
|
3122
|
-
}
|
|
3123
|
-
function resolveClientIp(remoteAddress, xForwardedFor) {
|
|
3124
|
-
const remote = normalizeIp(remoteAddress);
|
|
3125
|
-
if (!remote) return void 0;
|
|
3126
|
-
if (isLoopback(remote) && xForwardedFor) {
|
|
3127
|
-
const firstIp = normalizeIp(xForwardedFor.split(",")[0]?.trim());
|
|
3128
|
-
if (firstIp) {
|
|
3129
|
-
if (isLoopback(firstIp) || isPrivateIp(firstIp)) return "loopback";
|
|
3130
|
-
return firstIp;
|
|
3131
|
-
}
|
|
3132
|
-
}
|
|
3133
|
-
return remote;
|
|
3134
|
-
}
|
|
3135
|
-
function isExternalIp(ip) {
|
|
3136
|
-
if (!ip) return false;
|
|
3137
|
-
const normalized = normalizeIp(ip);
|
|
3138
|
-
if (!normalized) return false;
|
|
3139
|
-
if (isLoopback(normalized)) return false;
|
|
3140
|
-
if (isPrivateIp(normalized)) return false;
|
|
3141
|
-
return true;
|
|
3142
|
-
}
|
|
3143
|
-
function renderLoginPage(opts) {
|
|
3144
|
-
const error = opts?.error ?? "";
|
|
3145
|
-
const lockout = opts?.lockoutSeconds ?? 0;
|
|
3146
|
-
const redirect = opts?.redirect ?? "/";
|
|
3147
|
-
const mode = opts?.mode ?? "login";
|
|
3148
|
-
const changeError = opts?.changeError ?? "";
|
|
3149
|
-
const success = opts?.success ?? "";
|
|
3150
|
-
const setupError = opts?.setupError ?? "";
|
|
3151
|
-
const primaryColor = opts?.primaryColor ?? "#7C8C72";
|
|
3152
|
-
const primaryHoverColor = opts?.primaryHoverColor ?? "#6A7A62";
|
|
3153
|
-
const primarySubtle = opts?.primarySubtle ?? "rgba(124,140,114,0.08)";
|
|
3154
|
-
const productName = opts?.productName ?? "Maxy";
|
|
3155
|
-
const logoPath = opts?.logoPath ?? "/brand/maxy-monochrome.png";
|
|
3156
|
-
const faviconPath = opts?.faviconPath ?? "/favicon.ico";
|
|
3157
|
-
const displayFont = opts?.displayFont ?? "'Cormorant', Georgia, serif";
|
|
3158
|
-
const bodyFont = opts?.bodyFont ?? "'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
|
|
3159
|
-
const logoContainsName = opts?.logoContainsName ?? false;
|
|
3160
|
-
const errorHtml = error ? `<p class="msg msg--error">${escapeHtml(error)}</p>` : "";
|
|
3161
|
-
const changeErrorHtml = changeError ? `<p class="msg msg--error">${escapeHtml(changeError)}</p>` : "";
|
|
3162
|
-
const successHtml = success ? `<p class="msg msg--success">${escapeHtml(success)}</p>` : "";
|
|
3163
|
-
const lockoutHtml = lockout > 0 ? `<p class="msg msg--error">Too many attempts. Try again in <span id="countdown">${lockout}</span>s</p>
|
|
3164
|
-
<script>
|
|
3165
|
-
(function() {
|
|
3166
|
-
var s = ${lockout};
|
|
3167
|
-
var el = document.getElementById('countdown');
|
|
3168
|
-
var iv = setInterval(function() { s--; el.textContent = s; if (s <= 0) { clearInterval(iv); location.reload(); } }, 1000);
|
|
3169
|
-
})();
|
|
3170
|
-
</script>` : "";
|
|
3171
|
-
const setupErrorHtml = setupError ? `<p class="msg msg--error">${escapeHtml(setupError)}</p>` : "";
|
|
3172
|
-
const formDisabled = lockout > 0 ? "disabled" : "";
|
|
3173
|
-
const loginDisplay = mode === "login" ? "block" : "none";
|
|
3174
|
-
const changeDisplay = mode === "change" ? "block" : "none";
|
|
3175
|
-
const setupDisplay = mode === "setup" ? "block" : "none";
|
|
3176
|
-
const successDisplay = mode === "success" ? "block" : "none";
|
|
3177
|
-
const subtitleText = mode === "setup" ? "Set your remote password" : "Remote access";
|
|
3178
|
-
const displayFontName = displayFont.match(/^'([^']+)'/)?.[1] ?? "";
|
|
3179
|
-
const bodyFontName = bodyFont.match(/^'([^']+)'/)?.[1] ?? "";
|
|
3180
|
-
const googleFontsLink = displayFontName || bodyFontName ? [
|
|
3181
|
-
'<link rel="preconnect" href="https://fonts.googleapis.com">',
|
|
3182
|
-
'<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>',
|
|
3183
|
-
'<link href="https://fonts.googleapis.com/css2?' + [
|
|
3184
|
-
displayFontName ? `family=${displayFontName.replace(/ /g, "+")}:wght@300;400` : "",
|
|
3185
|
-
bodyFontName ? `family=${bodyFontName.replace(/ /g, "+")}:wght@400;500` : ""
|
|
3186
|
-
].filter(Boolean).join("&") + '&display=swap" rel="stylesheet">'
|
|
3187
|
-
].join("\n ") : "";
|
|
3188
|
-
return `<!DOCTYPE html>
|
|
3189
|
-
<html lang="en">
|
|
3190
|
-
<head>
|
|
3191
|
-
<meta charset="utf-8">
|
|
3192
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
3193
|
-
<title>Sign in \u2014 ${escapeHtml(productName)}</title>
|
|
3194
|
-
<link rel="icon" href="${escapeHtml(faviconPath)}">
|
|
3195
|
-
${googleFontsLink}
|
|
3196
|
-
<style>
|
|
3197
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
3198
|
-
body {
|
|
3199
|
-
font-family: ${bodyFont};
|
|
3200
|
-
background: #FAFAF8;
|
|
3201
|
-
color: #1A1A1A;
|
|
3202
|
-
min-height: 100vh;
|
|
3203
|
-
display: flex;
|
|
3204
|
-
align-items: center;
|
|
3205
|
-
justify-content: center;
|
|
3206
|
-
}
|
|
3207
|
-
.page {
|
|
3208
|
-
display: flex;
|
|
3209
|
-
flex-direction: column;
|
|
3210
|
-
align-items: center;
|
|
3211
|
-
gap: 24px;
|
|
3212
|
-
max-width: 420px;
|
|
3213
|
-
width: 100%;
|
|
3214
|
-
padding: 40px 20px;
|
|
3215
|
-
}
|
|
3216
|
-
.logo-img {
|
|
3217
|
-
width: 110px;
|
|
3218
|
-
height: 110px;
|
|
3219
|
-
margin: -11px;
|
|
3220
|
-
object-fit: contain;
|
|
3221
|
-
}
|
|
3222
|
-
.title {
|
|
3223
|
-
font-family: ${displayFont};
|
|
3224
|
-
font-weight: 300;
|
|
3225
|
-
font-size: 28px;
|
|
3226
|
-
color: #1A1A1A;
|
|
3227
|
-
text-align: center;
|
|
3228
|
-
letter-spacing: -0.01em;
|
|
3229
|
-
line-height: 1.3;
|
|
3230
|
-
}
|
|
3231
|
-
.subtitle {
|
|
3232
|
-
font-size: 15px;
|
|
3233
|
-
color: #6B6B6B;
|
|
3234
|
-
text-align: center;
|
|
3235
|
-
margin-top: -8px;
|
|
3236
|
-
}
|
|
3237
|
-
.form-wrap {
|
|
3238
|
-
width: 100%;
|
|
3239
|
-
max-width: 300px;
|
|
3240
|
-
}
|
|
3241
|
-
.field { margin-bottom: 12px; }
|
|
3242
|
-
.field-label {
|
|
3243
|
-
display: block;
|
|
3244
|
-
font-size: 13px;
|
|
3245
|
-
font-weight: 500;
|
|
3246
|
-
color: #6B6B6B;
|
|
3247
|
-
margin-bottom: 6px;
|
|
3248
|
-
}
|
|
3249
|
-
.field-input {
|
|
3250
|
-
width: 100%;
|
|
3251
|
-
padding: 10px 12px;
|
|
3252
|
-
border: 1px solid rgba(0,0,0,0.1);
|
|
3253
|
-
border-radius: 8px;
|
|
3254
|
-
font-size: 15px;
|
|
3255
|
-
font-family: inherit;
|
|
3256
|
-
outline: none;
|
|
3257
|
-
background: #fff;
|
|
3258
|
-
transition: border-color 0.15s, box-shadow 0.15s;
|
|
3259
|
-
}
|
|
3260
|
-
.field-input:focus {
|
|
3261
|
-
border-color: ${primaryColor};
|
|
3262
|
-
box-shadow: 0 0 0 3px ${primarySubtle};
|
|
3263
|
-
}
|
|
3264
|
-
.options {
|
|
3265
|
-
display: flex;
|
|
3266
|
-
align-items: center;
|
|
3267
|
-
justify-content: space-between;
|
|
3268
|
-
gap: 12px;
|
|
3269
|
-
margin-top: 8px;
|
|
3270
|
-
}
|
|
3271
|
-
|
|
3272
|
-
/* Checkbox \u2014 matches Maxy shared checkbox (asterisk-in-square) */
|
|
3273
|
-
.check {
|
|
3274
|
-
position: relative;
|
|
3275
|
-
display: flex;
|
|
3276
|
-
align-items: center;
|
|
3277
|
-
gap: 8px;
|
|
3278
|
-
cursor: pointer;
|
|
3279
|
-
user-select: none;
|
|
3280
|
-
}
|
|
3281
|
-
.check input { position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none; }
|
|
3282
|
-
.check-box {
|
|
3283
|
-
width: 14px;
|
|
3284
|
-
height: 14px;
|
|
3285
|
-
border: 1px solid rgba(0,0,0,0.1);
|
|
3286
|
-
border-radius: 2px;
|
|
3287
|
-
display: flex;
|
|
3288
|
-
align-items: center;
|
|
3289
|
-
justify-content: center;
|
|
3290
|
-
font-size: 10px;
|
|
3291
|
-
color: transparent;
|
|
3292
|
-
transition: border-color 0.15s, color 0.15s;
|
|
3293
|
-
flex-shrink: 0;
|
|
3294
|
-
}
|
|
3295
|
-
.check input:checked + .check-box {
|
|
3296
|
-
border-color: ${primaryColor};
|
|
3297
|
-
color: ${primaryColor};
|
|
3298
|
-
}
|
|
3299
|
-
.check-label {
|
|
3300
|
-
font-size: 13px;
|
|
3301
|
-
color: #6B6B6B;
|
|
3302
|
-
}
|
|
3303
|
-
|
|
3304
|
-
/* Ghost button \u2014 matches Maxy ghost variant */
|
|
3305
|
-
.ghost {
|
|
3306
|
-
background: none;
|
|
3307
|
-
border: none;
|
|
3308
|
-
font-family: inherit;
|
|
3309
|
-
font-size: 13px;
|
|
3310
|
-
color: #6B6B6B;
|
|
3311
|
-
cursor: pointer;
|
|
3312
|
-
padding: 4px 0;
|
|
3313
|
-
transition: color 0.15s;
|
|
3314
|
-
}
|
|
3315
|
-
.ghost:hover { color: #1A1A1A; }
|
|
3316
|
-
|
|
3317
|
-
/* Primary button */
|
|
3318
|
-
.btn {
|
|
3319
|
-
width: 100%;
|
|
3320
|
-
padding: 12px;
|
|
3321
|
-
margin-top: 16px;
|
|
3322
|
-
background: ${primaryColor};
|
|
3323
|
-
color: #fff;
|
|
3324
|
-
border: none;
|
|
3325
|
-
border-radius: 10px;
|
|
3326
|
-
font-size: 15px;
|
|
3327
|
-
font-weight: 500;
|
|
3328
|
-
font-family: inherit;
|
|
3329
|
-
cursor: pointer;
|
|
3330
|
-
transition: background 0.15s;
|
|
3331
|
-
}
|
|
3332
|
-
.btn:hover:not(:disabled) { background: ${primaryHoverColor}; }
|
|
3333
|
-
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
3334
|
-
|
|
3335
|
-
/* Messages */
|
|
3336
|
-
.msg {
|
|
3337
|
-
font-size: 13px;
|
|
3338
|
-
text-align: center;
|
|
3339
|
-
margin-top: 8px;
|
|
3340
|
-
}
|
|
3341
|
-
.msg--error { color: #c44; }
|
|
3342
|
-
.msg--success { color: ${primaryColor}; }
|
|
3343
|
-
|
|
3344
|
-
/* Strength checklist (setup mode) */
|
|
3345
|
-
.strength-checklist { margin: 8px 0; }
|
|
3346
|
-
.strength-item {
|
|
3347
|
-
font-size: 13px;
|
|
3348
|
-
color: #6B6B6B;
|
|
3349
|
-
padding: 2px 0;
|
|
3350
|
-
transition: color 0.15s;
|
|
3351
|
-
}
|
|
3352
|
-
.strength-icon {
|
|
3353
|
-
display: inline-block;
|
|
3354
|
-
width: 16px;
|
|
3355
|
-
text-align: center;
|
|
3356
|
-
}
|
|
3357
|
-
|
|
3358
|
-
@media (max-width: 440px) {
|
|
3359
|
-
.page { padding: 24px 16px; }
|
|
3360
|
-
}
|
|
3361
|
-
</style>
|
|
3362
|
-
</head>
|
|
3363
|
-
<body>
|
|
3364
|
-
<div class="page">
|
|
3365
|
-
<img src="${escapeHtml(logoPath)}" alt="${escapeHtml(productName)}" class="logo-img">
|
|
3366
|
-
<h1 class="title"${logoContainsName ? ' style="display:none"' : ""}>${escapeHtml(productName)}</h1>
|
|
3367
|
-
<p class="subtitle">${escapeHtml(subtitleText)}</p>
|
|
3368
|
-
|
|
3369
|
-
${successHtml}
|
|
3370
|
-
|
|
3371
|
-
<!-- Login -->
|
|
3372
|
-
<div id="login-section" class="form-wrap" style="display:${loginDisplay}">
|
|
3373
|
-
<form method="POST" action="/__remote-auth/login">
|
|
3374
|
-
<input type="hidden" name="redirect" value="${escapeHtml(redirect)}">
|
|
3375
|
-
<div class="field">
|
|
3376
|
-
<label class="field-label" for="password">Password</label>
|
|
3377
|
-
<input class="field-input" type="password" id="password" name="password" required autofocus ${formDisabled}>
|
|
3378
|
-
</div>
|
|
3379
|
-
<div class="options">
|
|
3380
|
-
<label class="check">
|
|
3381
|
-
<input type="checkbox" id="show-login-pw">
|
|
3382
|
-
<span class="check-box">\u2731</span>
|
|
3383
|
-
<span class="check-label">Show password</span>
|
|
3384
|
-
</label>
|
|
3385
|
-
<button type="button" class="ghost" onclick="toggleMode('change')">Change password</button>
|
|
3386
|
-
</div>
|
|
3387
|
-
<button type="submit" class="btn" ${formDisabled}>Sign in</button>
|
|
3388
|
-
</form>
|
|
3389
|
-
${errorHtml}
|
|
3390
|
-
${lockoutHtml}
|
|
3391
|
-
</div>
|
|
3392
|
-
|
|
3393
|
-
<!-- Change password -->
|
|
3394
|
-
<div id="change-section" class="form-wrap" style="display:${changeDisplay}">
|
|
3395
|
-
<form method="POST" action="/__remote-auth/change-password">
|
|
3396
|
-
<input type="hidden" name="redirect" value="${escapeHtml(redirect)}">
|
|
3397
|
-
<div class="field">
|
|
3398
|
-
<label class="field-label" for="current_password">Current password</label>
|
|
3399
|
-
<input class="field-input" type="password" id="current_password" name="current_password" required ${formDisabled}>
|
|
3400
|
-
</div>
|
|
3401
|
-
<div class="field">
|
|
3402
|
-
<label class="field-label" for="new_password">New password</label>
|
|
3403
|
-
<input class="field-input" type="password" id="new_password" name="new_password" required ${formDisabled}>
|
|
3404
|
-
</div>
|
|
3405
|
-
<div class="field">
|
|
3406
|
-
<label class="field-label" for="confirm_password">Confirm new password</label>
|
|
3407
|
-
<input class="field-input" type="password" id="confirm_password" name="confirm_password" required ${formDisabled}>
|
|
3408
|
-
</div>
|
|
3409
|
-
<div class="options">
|
|
3410
|
-
<label class="check">
|
|
3411
|
-
<input type="checkbox" id="show-change-pw">
|
|
3412
|
-
<span class="check-box">\u2731</span>
|
|
3413
|
-
<span class="check-label">Show passwords</span>
|
|
3414
|
-
</label>
|
|
3415
|
-
<button type="button" class="ghost" onclick="toggleMode('login')">Back to sign in</button>
|
|
3416
|
-
</div>
|
|
3417
|
-
<button type="submit" class="btn" ${formDisabled}>Change password</button>
|
|
3418
|
-
</form>
|
|
3419
|
-
${changeErrorHtml}
|
|
3420
|
-
</div>
|
|
3421
|
-
|
|
3422
|
-
<!-- Setup (initial password) -->
|
|
3423
|
-
<div id="setup-section" class="form-wrap" style="display:${setupDisplay}">
|
|
3424
|
-
<form method="POST" action="/__remote-auth/set-initial-password">
|
|
3425
|
-
<div class="field">
|
|
3426
|
-
<label class="field-label" for="setup_password">Password</label>
|
|
3427
|
-
<input class="field-input" type="password" id="setup_password" name="password" required autofocus>
|
|
3428
|
-
</div>
|
|
3429
|
-
<div class="field">
|
|
3430
|
-
<label class="field-label" for="setup_confirm">Confirm password</label>
|
|
3431
|
-
<input class="field-input" type="password" id="setup_confirm" name="confirm_password" required>
|
|
3432
|
-
</div>
|
|
3433
|
-
<div id="strength-checklist" class="strength-checklist">
|
|
3434
|
-
<div class="strength-item" id="req-length"><span class="strength-icon">\u25CB</span> At least 8 characters</div>
|
|
3435
|
-
<div class="strength-item" id="req-number"><span class="strength-icon">\u25CB</span> Contains a number</div>
|
|
3436
|
-
<div class="strength-item" id="req-special"><span class="strength-icon">\u25CB</span> Contains a special character</div>
|
|
3437
|
-
<div class="strength-item" id="req-spaces"><span class="strength-icon">\u25CB</span> No spaces</div>
|
|
3438
|
-
</div>
|
|
3439
|
-
<div class="options">
|
|
3440
|
-
<label class="check">
|
|
3441
|
-
<input type="checkbox" id="show-setup-pw">
|
|
3442
|
-
<span class="check-box">\u2731</span>
|
|
3443
|
-
<span class="check-label">Show passwords</span>
|
|
3444
|
-
</label>
|
|
3445
|
-
</div>
|
|
3446
|
-
<button type="submit" class="btn" id="setup-submit" disabled>Set password</button>
|
|
3447
|
-
</form>
|
|
3448
|
-
${setupErrorHtml}
|
|
3449
|
-
</div>
|
|
3450
|
-
|
|
3451
|
-
<!-- Success (after initial password set) -->
|
|
3452
|
-
<div id="success-section" class="form-wrap" style="display:${successDisplay}">
|
|
3453
|
-
<p class="msg msg--success" style="font-size:15px;margin-bottom:4px;">\u2713 Password set</p>
|
|
3454
|
-
<p style="font-size:14px;color:#6B6B6B;text-align:center;">You can close this tab and return to the onboarding tab.</p>
|
|
3455
|
-
<script>
|
|
3456
|
-
(function() {
|
|
3457
|
-
try {
|
|
3458
|
-
var ch = new BroadcastChannel('platform-onboarding');
|
|
3459
|
-
setTimeout(function() {
|
|
3460
|
-
ch.postMessage({ type: 'remote-password-set' });
|
|
3461
|
-
ch.close();
|
|
3462
|
-
}, 2500);
|
|
3463
|
-
} catch(e) {}
|
|
3464
|
-
})();
|
|
3465
|
-
</script>
|
|
3466
|
-
</div>
|
|
3467
|
-
</div>
|
|
3468
|
-
|
|
3469
|
-
<script>
|
|
3470
|
-
/* Toggle between login and change-password modes */
|
|
3471
|
-
function toggleMode(mode) {
|
|
3472
|
-
document.getElementById('login-section').style.display = mode === 'login' ? 'block' : 'none';
|
|
3473
|
-
document.getElementById('change-section').style.display = mode === 'change' ? 'block' : 'none';
|
|
3474
|
-
/* Focus first input in the active section */
|
|
3475
|
-
var id = mode === 'login' ? 'password' : 'current_password';
|
|
3476
|
-
var el = document.getElementById(id);
|
|
3477
|
-
if (el) el.focus();
|
|
3478
|
-
}
|
|
3479
|
-
|
|
3480
|
-
/* Show/hide password toggles */
|
|
3481
|
-
function bindShowHide(checkboxId, inputIds) {
|
|
3482
|
-
var cb = document.getElementById(checkboxId);
|
|
3483
|
-
if (!cb) return;
|
|
3484
|
-
cb.addEventListener('change', function() {
|
|
3485
|
-
var type = cb.checked ? 'text' : 'password';
|
|
3486
|
-
inputIds.forEach(function(id) {
|
|
3487
|
-
var el = document.getElementById(id);
|
|
3488
|
-
if (el) el.type = type;
|
|
3489
|
-
});
|
|
3490
|
-
});
|
|
3491
|
-
}
|
|
3492
|
-
bindShowHide('show-login-pw', ['password']);
|
|
3493
|
-
bindShowHide('show-change-pw', ['current_password', 'new_password', 'confirm_password']);
|
|
3494
|
-
bindShowHide('show-setup-pw', ['setup_password', 'setup_confirm']);
|
|
3495
|
-
|
|
3496
|
-
/* Explicit Enter-to-submit for the login password field */
|
|
3497
|
-
var loginPw = document.getElementById('password');
|
|
3498
|
-
if (loginPw) {
|
|
3499
|
-
loginPw.addEventListener('keydown', function(e) {
|
|
3500
|
-
if (e.key === 'Enter') {
|
|
3501
|
-
e.preventDefault();
|
|
3502
|
-
this.closest('form').requestSubmit();
|
|
3503
|
-
}
|
|
3504
|
-
});
|
|
3505
|
-
}
|
|
3506
|
-
|
|
3507
|
-
/* Live password strength validation (setup mode) */
|
|
3508
|
-
(function() {
|
|
3509
|
-
var pw = document.getElementById('setup_password');
|
|
3510
|
-
var confirm = document.getElementById('setup_confirm');
|
|
3511
|
-
var btn = document.getElementById('setup-submit');
|
|
3512
|
-
if (!pw || !confirm || !btn) return;
|
|
3513
|
-
|
|
3514
|
-
var rules = [
|
|
3515
|
-
{ id: 'req-length', test: function(p) { return p.length >= 8; } },
|
|
3516
|
-
{ id: 'req-number', test: function(p) { return /\\d/.test(p); } },
|
|
3517
|
-
{ id: 'req-special', test: function(p) { return /[^A-Za-z0-9]/.test(p); } },
|
|
3518
|
-
{ id: 'req-spaces', test: function(p) { return p.length > 0 && !/\\s/.test(p); } }
|
|
3519
|
-
];
|
|
3520
|
-
|
|
3521
|
-
function check() {
|
|
3522
|
-
var p = pw.value;
|
|
3523
|
-
var allMet = true;
|
|
3524
|
-
rules.forEach(function(r) {
|
|
3525
|
-
var el = document.getElementById(r.id);
|
|
3526
|
-
var met = r.test(p);
|
|
3527
|
-
if (!met) allMet = false;
|
|
3528
|
-
el.querySelector('.strength-icon').textContent = met ? '\\u2713' : '\\u25CB';
|
|
3529
|
-
el.style.color = met ? '${primaryColor}' : '#6B6B6B';
|
|
3530
|
-
});
|
|
3531
|
-
var match = p.length > 0 && p === confirm.value;
|
|
3532
|
-
btn.disabled = !(allMet && match);
|
|
3533
|
-
}
|
|
3534
|
-
|
|
3535
|
-
pw.addEventListener('input', check);
|
|
3536
|
-
confirm.addEventListener('input', check);
|
|
3537
|
-
})();
|
|
3538
|
-
</script>
|
|
3539
|
-
</body>
|
|
3540
|
-
</html>`;
|
|
3541
|
-
}
|
|
3542
|
-
function escapeHtml(str) {
|
|
3543
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
3544
|
-
}
|
|
3545
|
-
|
|
3546
|
-
// app/lib/auth-gate.ts
|
|
3547
|
-
function canAccessAdmin(input) {
|
|
3548
|
-
if (input.isPublicHost(input.host)) {
|
|
3549
|
-
return { allow: false, reason: "public-host" };
|
|
3550
|
-
}
|
|
3551
|
-
const clientIp = resolveClientIp(input.remoteAddress, input.xForwardedFor);
|
|
3552
|
-
if (!isExternalIp(clientIp)) {
|
|
3553
|
-
return { allow: true };
|
|
3554
|
-
}
|
|
3555
|
-
if (!isRemoteAuthConfigured()) {
|
|
3556
|
-
return { allow: false, reason: "remote-unconfigured" };
|
|
3557
|
-
}
|
|
3558
|
-
const token = parseCookieValue(input.cookieHeader, "__remote_session");
|
|
3559
|
-
if (token && validateRemoteSession(token)) {
|
|
3560
|
-
return { allow: true };
|
|
3561
|
-
}
|
|
3562
|
-
return { allow: false, reason: "unauthorized" };
|
|
3563
|
-
}
|
|
3564
|
-
function parseCookieValue(cookieHeader, name) {
|
|
3565
|
-
if (!cookieHeader) return null;
|
|
3566
|
-
const match2 = cookieHeader.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
|
|
3567
|
-
if (!match2) return null;
|
|
3568
|
-
try {
|
|
3569
|
-
return decodeURIComponent(match2[1]);
|
|
3570
|
-
} catch {
|
|
3571
|
-
return null;
|
|
3572
|
-
}
|
|
3573
|
-
}
|
|
3574
|
-
|
|
3575
|
-
// server/ws-proxy.ts
|
|
3576
|
-
import { createConnection } from "net";
|
|
3577
|
-
var WS_PATH = "/websockify";
|
|
3578
|
-
var UPSTREAM_TIMEOUT_MS = 5e3;
|
|
3579
|
-
var HOP_BY_HOP = /* @__PURE__ */ new Set([
|
|
3580
|
-
"connection",
|
|
3581
|
-
"keep-alive",
|
|
3582
|
-
"proxy-authenticate",
|
|
3583
|
-
"proxy-authorization",
|
|
3584
|
-
"te",
|
|
3585
|
-
"trailer",
|
|
3586
|
-
"transfer-encoding",
|
|
3587
|
-
"upgrade"
|
|
3588
|
-
]);
|
|
3589
|
-
function attachVncWsProxy(server, opts) {
|
|
3590
|
-
const upstreamHost = opts.upstreamHost ?? "127.0.0.1";
|
|
3591
|
-
const upstreamPort = opts.upstreamPort ?? 6080;
|
|
3592
|
-
server.on("upgrade", (req, clientSocket, head) => {
|
|
3593
|
-
try {
|
|
3594
|
-
handleUpgrade(req, clientSocket, head, {
|
|
3595
|
-
isPublicHost: opts.isPublicHost,
|
|
3596
|
-
upstreamHost,
|
|
3597
|
-
upstreamPort
|
|
3598
|
-
});
|
|
3599
|
-
} catch (err) {
|
|
3600
|
-
vncLog("ws-upgrade", {
|
|
3601
|
-
decision: "rejected",
|
|
3602
|
-
reason: "handler-exception",
|
|
3603
|
-
err: err.message
|
|
3604
|
-
});
|
|
3605
|
-
clientSocket.destroy();
|
|
3606
|
-
}
|
|
3607
|
-
});
|
|
3608
|
-
}
|
|
3609
|
-
function handleUpgrade(req, clientSocket, head, opts) {
|
|
3610
|
-
const url = req.url ?? "";
|
|
3611
|
-
const qsIndex = url.indexOf("?");
|
|
3612
|
-
const pathname = qsIndex === -1 ? url : url.slice(0, qsIndex);
|
|
3613
|
-
if (pathname !== WS_PATH) {
|
|
3614
|
-
return;
|
|
3615
|
-
}
|
|
3616
|
-
const corrId = newCorrId();
|
|
3617
|
-
const query = qsIndex === -1 ? "" : url.slice(qsIndex + 1);
|
|
3618
|
-
const rawClientCorrId = parseQueryParam(query, "corrId");
|
|
3619
|
-
const clientCorrId = sanitizeClientCorrId(rawClientCorrId);
|
|
3620
|
-
const hostHeader = (req.headers.host ?? "").split(":")[0];
|
|
3621
|
-
const originHeader = headerString(req.headers.origin);
|
|
3622
|
-
const remote = req.socket.remoteAddress;
|
|
3623
|
-
const xff = headerString(req.headers["x-forwarded-for"]);
|
|
3624
|
-
const decision = canAccessAdmin({
|
|
3625
|
-
host: hostHeader,
|
|
3626
|
-
remoteAddress: remote,
|
|
3627
|
-
xForwardedFor: xff,
|
|
3628
|
-
cookieHeader: headerString(req.headers.cookie),
|
|
3629
|
-
isPublicHost: opts.isPublicHost
|
|
3630
|
-
});
|
|
3631
|
-
if (!decision.allow) {
|
|
3632
|
-
const status = decision.reason === "public-host" ? 404 : 401;
|
|
3633
|
-
vncLog("ws-upgrade", {
|
|
3634
|
-
corrId,
|
|
3635
|
-
clientCorrId: clientCorrId ?? null,
|
|
3636
|
-
decision: "rejected",
|
|
3637
|
-
reason: decision.reason,
|
|
3638
|
-
ip: remote,
|
|
3639
|
-
xff: xff ?? null,
|
|
3640
|
-
origin: originHeader ?? null,
|
|
3641
|
-
host: hostHeader
|
|
3642
|
-
});
|
|
3643
|
-
writeStatusAndDestroy(clientSocket, status, decision.reason === "public-host" ? "Not Found" : "Unauthorized");
|
|
3644
|
-
return;
|
|
3645
|
-
}
|
|
3646
|
-
const originHost = parseOriginHost(originHeader);
|
|
3647
|
-
if (!originHost) {
|
|
3648
|
-
vncLog("ws-upgrade", {
|
|
3649
|
-
corrId,
|
|
3650
|
-
clientCorrId: clientCorrId ?? null,
|
|
3651
|
-
decision: "rejected",
|
|
3652
|
-
reason: "origin-missing-or-invalid",
|
|
3653
|
-
origin: originHeader ?? null,
|
|
3654
|
-
host: hostHeader,
|
|
3655
|
-
ip: remote
|
|
3656
|
-
});
|
|
3657
|
-
writeStatusAndDestroy(clientSocket, 403, "Forbidden");
|
|
3658
|
-
return;
|
|
3659
|
-
}
|
|
3660
|
-
if (originHost !== hostHeader) {
|
|
3661
|
-
vncLog("ws-upgrade", {
|
|
3662
|
-
corrId,
|
|
3663
|
-
clientCorrId: clientCorrId ?? null,
|
|
3664
|
-
decision: "rejected",
|
|
3665
|
-
reason: "origin-mismatch",
|
|
3666
|
-
origin_host: originHost,
|
|
3667
|
-
host: hostHeader,
|
|
3668
|
-
ip: remote
|
|
3669
|
-
});
|
|
3670
|
-
writeStatusAndDestroy(clientSocket, 403, "Forbidden");
|
|
3671
|
-
return;
|
|
3672
|
-
}
|
|
3673
|
-
if (opts.isPublicHost(originHost)) {
|
|
3674
|
-
vncLog("ws-upgrade", {
|
|
3675
|
-
corrId,
|
|
3676
|
-
clientCorrId: clientCorrId ?? null,
|
|
3677
|
-
decision: "rejected",
|
|
3678
|
-
reason: "origin-public-host",
|
|
3679
|
-
origin_host: originHost,
|
|
3680
|
-
host: hostHeader,
|
|
3681
|
-
ip: remote
|
|
3682
|
-
});
|
|
3683
|
-
writeStatusAndDestroy(clientSocket, 403, "Forbidden");
|
|
3684
|
-
return;
|
|
3685
|
-
}
|
|
3686
|
-
vncLog("ws-upgrade", {
|
|
3687
|
-
corrId,
|
|
3688
|
-
clientCorrId: clientCorrId ?? null,
|
|
3689
|
-
decision: "accepted",
|
|
3690
|
-
ip: remote,
|
|
3691
|
-
xff: xff ?? null,
|
|
3692
|
-
origin: originHeader ?? null,
|
|
3693
|
-
host: hostHeader,
|
|
3694
|
-
sec_ws_version: headerString(req.headers["sec-websocket-version"]) ?? null,
|
|
3695
|
-
sec_ws_protocol: headerString(req.headers["sec-websocket-protocol"]) ?? null
|
|
3696
|
-
});
|
|
3697
|
-
const upstream = createConnection({ host: opts.upstreamHost, port: opts.upstreamPort });
|
|
3698
|
-
upstream.setTimeout(UPSTREAM_TIMEOUT_MS);
|
|
3699
|
-
let bytesClientToUpstream = 0;
|
|
3700
|
-
let bytesUpstreamToClient = 0;
|
|
3701
|
-
let closedBy = null;
|
|
3702
|
-
let proxyOpened = false;
|
|
3703
|
-
const finish = (side, reason) => {
|
|
3704
|
-
if (closedBy) return;
|
|
3705
|
-
closedBy = side;
|
|
3706
|
-
if (proxyOpened) {
|
|
3707
|
-
vncLog("proxy-close", {
|
|
3708
|
-
corrId,
|
|
3709
|
-
closedBy: side,
|
|
3710
|
-
reason,
|
|
3711
|
-
clientBytes: bytesClientToUpstream,
|
|
3712
|
-
upstreamBytes: bytesUpstreamToClient
|
|
3713
|
-
});
|
|
3714
|
-
}
|
|
3715
|
-
clientSocket.destroy();
|
|
3716
|
-
upstream.destroy();
|
|
3717
|
-
};
|
|
3718
|
-
upstream.once("connect", () => {
|
|
3719
|
-
upstream.setTimeout(0);
|
|
3720
|
-
proxyOpened = true;
|
|
3721
|
-
vncLog("proxy-open", {
|
|
3722
|
-
corrId,
|
|
3723
|
-
upstream: `${opts.upstreamHost}:${opts.upstreamPort}`
|
|
3724
|
-
});
|
|
3725
|
-
const lines = [];
|
|
3726
|
-
lines.push(`${req.method ?? "GET"} ${WS_PATH} HTTP/${req.httpVersion}`);
|
|
3727
|
-
lines.push(`host: ${opts.upstreamHost}:${opts.upstreamPort}`);
|
|
3728
|
-
for (const [name, value] of Object.entries(req.headers)) {
|
|
3729
|
-
if (name === "host") continue;
|
|
3730
|
-
if (HOP_BY_HOP.has(name)) continue;
|
|
3731
|
-
if (value == null) continue;
|
|
3732
|
-
if (Array.isArray(value)) {
|
|
3733
|
-
for (const v of value) lines.push(`${name}: ${v}`);
|
|
3734
|
-
} else {
|
|
3735
|
-
lines.push(`${name}: ${value}`);
|
|
3736
|
-
}
|
|
3737
|
-
}
|
|
3738
|
-
const upgradeHeader = headerString(req.headers.upgrade);
|
|
3739
|
-
const connectionHeader = headerString(req.headers.connection);
|
|
3740
|
-
if (upgradeHeader) lines.push(`upgrade: ${upgradeHeader}`);
|
|
3741
|
-
if (connectionHeader) lines.push(`connection: ${connectionHeader}`);
|
|
3742
|
-
upstream.write(lines.join("\r\n") + "\r\n\r\n");
|
|
3743
|
-
if (head && head.length > 0) upstream.write(head);
|
|
3744
|
-
clientSocket.on("data", (chunk) => {
|
|
3745
|
-
bytesClientToUpstream += chunk.length;
|
|
3746
|
-
});
|
|
3747
|
-
upstream.on("data", (chunk) => {
|
|
3748
|
-
bytesUpstreamToClient += chunk.length;
|
|
3749
|
-
});
|
|
3750
|
-
clientSocket.pipe(upstream);
|
|
3751
|
-
upstream.pipe(clientSocket);
|
|
3752
|
-
clientSocket.once("close", (hadError) => {
|
|
3753
|
-
finish("client", hadError ? "error" : "normal");
|
|
3754
|
-
});
|
|
3755
|
-
upstream.once("close", (hadError) => {
|
|
3756
|
-
finish("upstream", hadError ? "error" : "normal");
|
|
3757
|
-
});
|
|
3758
|
-
clientSocket.once("error", (err) => {
|
|
3759
|
-
vncLog("proxy-error", { corrId, side: "client", err: err.message });
|
|
3760
|
-
finish("client", "error");
|
|
3761
|
-
});
|
|
3762
|
-
upstream.once("error", (err) => {
|
|
3763
|
-
vncLog("proxy-error", { corrId, side: "upstream", err: err.message });
|
|
3764
|
-
finish("upstream", "error");
|
|
3765
|
-
});
|
|
3766
|
-
});
|
|
3767
|
-
upstream.once("timeout", () => {
|
|
3768
|
-
if (proxyOpened) return;
|
|
3769
|
-
vncLog("proxy-error", {
|
|
3770
|
-
corrId,
|
|
3771
|
-
side: "upstream-connect",
|
|
3772
|
-
err: "timeout",
|
|
3773
|
-
timeout_ms: UPSTREAM_TIMEOUT_MS
|
|
3774
|
-
});
|
|
3775
|
-
writeStatusAndDestroy(clientSocket, 504, "Gateway Timeout");
|
|
3776
|
-
upstream.destroy();
|
|
3777
|
-
});
|
|
3778
|
-
upstream.once("error", (err) => {
|
|
3779
|
-
if (proxyOpened) return;
|
|
3780
|
-
vncLog("proxy-error", {
|
|
3781
|
-
corrId,
|
|
3782
|
-
side: "upstream-connect",
|
|
3783
|
-
err: err.message
|
|
3784
|
-
});
|
|
3785
|
-
writeStatusAndDestroy(clientSocket, 502, "Bad Gateway");
|
|
3786
|
-
upstream.destroy();
|
|
3787
|
-
});
|
|
3788
|
-
}
|
|
3789
|
-
function parseQueryParam(query, key) {
|
|
3790
|
-
if (!query) return null;
|
|
3791
|
-
for (const pair of query.split("&")) {
|
|
3792
|
-
const eq = pair.indexOf("=");
|
|
3793
|
-
const k = eq === -1 ? pair : pair.slice(0, eq);
|
|
3794
|
-
if (k !== key) continue;
|
|
3795
|
-
const v = eq === -1 ? "" : pair.slice(eq + 1);
|
|
3796
|
-
try {
|
|
3797
|
-
return decodeURIComponent(v);
|
|
3798
|
-
} catch {
|
|
3799
|
-
return null;
|
|
3800
|
-
}
|
|
3801
|
-
}
|
|
3802
|
-
return null;
|
|
3803
|
-
}
|
|
3804
|
-
function headerString(value) {
|
|
3805
|
-
if (value == null) return void 0;
|
|
3806
|
-
return Array.isArray(value) ? value[0] : value;
|
|
3807
|
-
}
|
|
3808
|
-
function parseOriginHost(origin) {
|
|
3809
|
-
if (!origin) return null;
|
|
3810
|
-
try {
|
|
3811
|
-
return new URL(origin).hostname;
|
|
3812
|
-
} catch {
|
|
3813
|
-
return null;
|
|
3814
|
-
}
|
|
3815
|
-
}
|
|
3816
|
-
function writeStatusAndDestroy(socket, status, statusText) {
|
|
3817
|
-
try {
|
|
3818
|
-
socket.write(
|
|
3819
|
-
`HTTP/1.1 ${status} ${statusText}\r
|
|
3820
|
-
Connection: close\r
|
|
3821
|
-
Content-Length: 0\r
|
|
3822
|
-
\r
|
|
3823
|
-
`
|
|
3824
|
-
);
|
|
3825
|
-
} catch {
|
|
3826
|
-
}
|
|
3827
|
-
socket.destroy();
|
|
3828
|
-
}
|
|
3829
|
-
|
|
3830
|
-
// server/ws-proxy-terminal.ts
|
|
3831
|
-
import { createConnection as createConnection2 } from "net";
|
|
3832
|
-
|
|
3833
|
-
// app/lib/terminal-logger.ts
|
|
3834
|
-
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
3835
|
-
import { resolve as resolve3 } from "path";
|
|
3836
|
-
var TERMINAL_LOG_FILE = resolve3(LOG_DIR, "terminal.log");
|
|
3837
|
-
try {
|
|
3838
|
-
mkdirSync2(LOG_DIR, { recursive: true });
|
|
3839
|
-
} catch (err) {
|
|
3840
|
-
console.error(`[terminal-log-fail] mkdir ${LOG_DIR} failed: ${err.message}`);
|
|
3841
|
-
}
|
|
3842
|
-
function terminalLog(phase, fields = {}) {
|
|
3843
|
-
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
3844
|
-
const kv = Object.entries(fields).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(" ");
|
|
3845
|
-
const line = kv.length > 0 ? `[${ts}] [terminal-${phase}] ${kv}
|
|
3846
|
-
` : `[${ts}] [terminal-${phase}]
|
|
3847
|
-
`;
|
|
3848
|
-
try {
|
|
3849
|
-
appendFileSync2(TERMINAL_LOG_FILE, line);
|
|
3850
|
-
} catch (err) {
|
|
3851
|
-
console.error(`[terminal-log-fail] ${err.message} \u2014 dropped: ${line.slice(0, 300).trim()}`);
|
|
3852
|
-
}
|
|
3853
|
-
}
|
|
3854
|
-
|
|
3855
|
-
// server/ws-proxy-terminal.ts
|
|
3856
|
-
var WS_PATH2 = "/admin/terminal/ws";
|
|
3857
|
-
var UPSTREAM_TIMEOUT_MS2 = 5e3;
|
|
3858
|
-
var FLOW_TICK_MS = 5e3;
|
|
3859
|
-
var FLOW_IDLE_EMIT_MS = 3e4;
|
|
3860
|
-
var HOP_BY_HOP2 = /* @__PURE__ */ new Set([
|
|
3861
|
-
"connection",
|
|
3862
|
-
"keep-alive",
|
|
3863
|
-
"proxy-authenticate",
|
|
3864
|
-
"proxy-authorization",
|
|
3865
|
-
"te",
|
|
3866
|
-
"trailer",
|
|
3867
|
-
"transfer-encoding",
|
|
3868
|
-
"upgrade"
|
|
3869
|
-
]);
|
|
3870
|
-
function attachTerminalWsProxy(server, opts) {
|
|
3871
|
-
const upstreamHost = opts.upstreamHost ?? "127.0.0.1";
|
|
3872
|
-
const upstreamPort = opts.upstreamPort ?? 7681;
|
|
3873
|
-
server.on("upgrade", (req, clientSocket, head) => {
|
|
3874
|
-
try {
|
|
3875
|
-
handleUpgrade2(req, clientSocket, head, {
|
|
3876
|
-
isPublicHost: opts.isPublicHost,
|
|
3877
|
-
upstreamHost,
|
|
3878
|
-
upstreamPort
|
|
3879
|
-
});
|
|
3880
|
-
} catch (err) {
|
|
3881
|
-
terminalLog("ws-upgrade", {
|
|
3882
|
-
decision: "rejected",
|
|
3883
|
-
reason: "handler-exception",
|
|
3884
|
-
err: err.message
|
|
3885
|
-
});
|
|
3886
|
-
clientSocket.destroy();
|
|
3887
|
-
}
|
|
3888
|
-
});
|
|
3889
|
-
}
|
|
3890
|
-
function handleUpgrade2(req, clientSocket, head, opts) {
|
|
3891
|
-
const url = req.url ?? "";
|
|
3892
|
-
const qsIndex = url.indexOf("?");
|
|
3893
|
-
const pathname = qsIndex === -1 ? url : url.slice(0, qsIndex);
|
|
3894
|
-
if (pathname !== WS_PATH2) {
|
|
3895
|
-
return;
|
|
3896
|
-
}
|
|
3897
|
-
const corrId = newCorrId();
|
|
3898
|
-
const query = qsIndex === -1 ? "" : url.slice(qsIndex + 1);
|
|
3899
|
-
const rawClientCorrId = parseQueryParam2(query, "corrId");
|
|
3900
|
-
const clientCorrId = sanitizeClientCorrId(rawClientCorrId);
|
|
3901
|
-
const hostHeader = (req.headers.host ?? "").split(":")[0];
|
|
3902
|
-
const originHeader = headerString2(req.headers.origin);
|
|
3903
|
-
const remote = req.socket.remoteAddress;
|
|
3904
|
-
const xff = headerString2(req.headers["x-forwarded-for"]);
|
|
3905
|
-
const decision = canAccessAdmin({
|
|
3906
|
-
host: hostHeader,
|
|
3907
|
-
remoteAddress: remote,
|
|
3908
|
-
xForwardedFor: xff,
|
|
3909
|
-
cookieHeader: headerString2(req.headers.cookie),
|
|
3910
|
-
isPublicHost: opts.isPublicHost
|
|
3911
|
-
});
|
|
3912
|
-
if (!decision.allow) {
|
|
3913
|
-
const status = decision.reason === "public-host" ? 404 : 401;
|
|
3914
|
-
terminalLog("ws-upgrade", {
|
|
3915
|
-
corrId,
|
|
3916
|
-
clientCorrId: clientCorrId ?? null,
|
|
3917
|
-
decision: "rejected",
|
|
3918
|
-
reason: decision.reason,
|
|
3919
|
-
ip: remote,
|
|
3920
|
-
xff: xff ?? null,
|
|
3921
|
-
origin: originHeader ?? null,
|
|
3922
|
-
host: hostHeader
|
|
3923
|
-
});
|
|
3924
|
-
writeStatusAndDestroy2(clientSocket, status, decision.reason === "public-host" ? "Not Found" : "Unauthorized");
|
|
3925
|
-
return;
|
|
3926
|
-
}
|
|
3927
|
-
const originHost = parseOriginHost2(originHeader);
|
|
3928
|
-
if (!originHost) {
|
|
3929
|
-
terminalLog("ws-upgrade", {
|
|
3930
|
-
corrId,
|
|
3931
|
-
clientCorrId: clientCorrId ?? null,
|
|
3932
|
-
decision: "rejected",
|
|
3933
|
-
reason: "origin-missing-or-invalid",
|
|
3934
|
-
origin: originHeader ?? null,
|
|
3935
|
-
host: hostHeader,
|
|
3936
|
-
ip: remote
|
|
3937
|
-
});
|
|
3938
|
-
writeStatusAndDestroy2(clientSocket, 403, "Forbidden");
|
|
3939
|
-
return;
|
|
3940
|
-
}
|
|
3941
|
-
if (originHost !== hostHeader) {
|
|
3942
|
-
terminalLog("ws-upgrade", {
|
|
3943
|
-
corrId,
|
|
3944
|
-
clientCorrId: clientCorrId ?? null,
|
|
3945
|
-
decision: "rejected",
|
|
3946
|
-
reason: "origin-mismatch",
|
|
3947
|
-
origin_host: originHost,
|
|
3948
|
-
host: hostHeader,
|
|
3949
|
-
ip: remote
|
|
3950
|
-
});
|
|
3951
|
-
writeStatusAndDestroy2(clientSocket, 403, "Forbidden");
|
|
3952
|
-
return;
|
|
3953
|
-
}
|
|
3954
|
-
if (opts.isPublicHost(originHost)) {
|
|
3955
|
-
terminalLog("ws-upgrade", {
|
|
3956
|
-
corrId,
|
|
3957
|
-
clientCorrId: clientCorrId ?? null,
|
|
3958
|
-
decision: "rejected",
|
|
3959
|
-
reason: "origin-public-host",
|
|
3960
|
-
origin_host: originHost,
|
|
3961
|
-
host: hostHeader,
|
|
3962
|
-
ip: remote
|
|
3963
|
-
});
|
|
3964
|
-
writeStatusAndDestroy2(clientSocket, 403, "Forbidden");
|
|
3965
|
-
return;
|
|
3966
|
-
}
|
|
3967
|
-
terminalLog("ws-upgrade", {
|
|
3968
|
-
corrId,
|
|
3969
|
-
clientCorrId: clientCorrId ?? null,
|
|
3970
|
-
decision: "accepted",
|
|
3971
|
-
ip: remote,
|
|
3972
|
-
xff: xff ?? null,
|
|
3973
|
-
origin: originHeader ?? null,
|
|
3974
|
-
host: hostHeader,
|
|
3975
|
-
sec_ws_version: headerString2(req.headers["sec-websocket-version"]) ?? null,
|
|
3976
|
-
sec_ws_protocol: headerString2(req.headers["sec-websocket-protocol"]) ?? null
|
|
3977
|
-
});
|
|
3978
|
-
const upstream = createConnection2({ host: opts.upstreamHost, port: opts.upstreamPort });
|
|
3979
|
-
upstream.setTimeout(UPSTREAM_TIMEOUT_MS2);
|
|
3980
|
-
let bytesClientToUpstream = 0;
|
|
3981
|
-
let bytesUpstreamToClient = 0;
|
|
3982
|
-
let closedBy = null;
|
|
3983
|
-
let proxyOpened = false;
|
|
3984
|
-
let lastUpstreamByteTs = 0;
|
|
3985
|
-
let flowInterval = null;
|
|
3986
|
-
let lastEmittedAt = 0;
|
|
3987
|
-
let lastEmittedUpstreamBytes = 0;
|
|
3988
|
-
const finish = (side, reason) => {
|
|
3989
|
-
if (closedBy) return;
|
|
3990
|
-
closedBy = side;
|
|
3991
|
-
if (flowInterval) {
|
|
3992
|
-
clearInterval(flowInterval);
|
|
3993
|
-
flowInterval = null;
|
|
3994
|
-
}
|
|
3995
|
-
if (proxyOpened) {
|
|
3996
|
-
terminalLog("proxy-close", {
|
|
3997
|
-
corrId,
|
|
3998
|
-
closedBy: side,
|
|
3999
|
-
reason,
|
|
4000
|
-
clientBytes: bytesClientToUpstream,
|
|
4001
|
-
upstreamBytes: bytesUpstreamToClient
|
|
4002
|
-
});
|
|
4003
|
-
}
|
|
4004
|
-
clientSocket.destroy();
|
|
4005
|
-
upstream.destroy();
|
|
4006
|
-
};
|
|
4007
|
-
upstream.once("connect", () => {
|
|
4008
|
-
upstream.setTimeout(0);
|
|
4009
|
-
proxyOpened = true;
|
|
4010
|
-
terminalLog("proxy-open", {
|
|
4011
|
-
corrId,
|
|
4012
|
-
upstream: `${opts.upstreamHost}:${opts.upstreamPort}`
|
|
4013
|
-
});
|
|
4014
|
-
const lines = [];
|
|
4015
|
-
lines.push(`${req.method ?? "GET"} ${WS_PATH2} HTTP/${req.httpVersion}`);
|
|
4016
|
-
lines.push(`host: ${opts.upstreamHost}:${opts.upstreamPort}`);
|
|
4017
|
-
for (const [name, value] of Object.entries(req.headers)) {
|
|
4018
|
-
if (name === "host") continue;
|
|
4019
|
-
if (HOP_BY_HOP2.has(name)) continue;
|
|
4020
|
-
if (value == null) continue;
|
|
4021
|
-
if (Array.isArray(value)) {
|
|
4022
|
-
for (const v of value) lines.push(`${name}: ${v}`);
|
|
4023
|
-
} else {
|
|
4024
|
-
lines.push(`${name}: ${value}`);
|
|
4025
|
-
}
|
|
4026
|
-
}
|
|
4027
|
-
const upgradeHeader = headerString2(req.headers.upgrade);
|
|
4028
|
-
const connectionHeader = headerString2(req.headers.connection);
|
|
4029
|
-
if (upgradeHeader) lines.push(`upgrade: ${upgradeHeader}`);
|
|
4030
|
-
if (connectionHeader) lines.push(`connection: ${connectionHeader}`);
|
|
4031
|
-
upstream.write(lines.join("\r\n") + "\r\n\r\n");
|
|
4032
|
-
if (head && head.length > 0) upstream.write(head);
|
|
4033
|
-
clientSocket.on("data", (chunk) => {
|
|
4034
|
-
bytesClientToUpstream += chunk.length;
|
|
4035
|
-
});
|
|
4036
|
-
upstream.on("data", (chunk) => {
|
|
4037
|
-
bytesUpstreamToClient += chunk.length;
|
|
4038
|
-
lastUpstreamByteTs = Date.now();
|
|
4039
|
-
});
|
|
4040
|
-
clientSocket.pipe(upstream);
|
|
4041
|
-
upstream.pipe(clientSocket);
|
|
4042
|
-
lastEmittedAt = Date.now();
|
|
4043
|
-
lastEmittedUpstreamBytes = 0;
|
|
4044
|
-
flowInterval = setInterval(() => {
|
|
4045
|
-
const now = Date.now();
|
|
4046
|
-
const idleMs = lastUpstreamByteTs === 0 ? now - lastEmittedAt : now - lastUpstreamByteTs;
|
|
4047
|
-
const upstreamMoved = bytesUpstreamToClient !== lastEmittedUpstreamBytes;
|
|
4048
|
-
const sinceLastEmit = now - lastEmittedAt;
|
|
4049
|
-
if (upstreamMoved || sinceLastEmit >= FLOW_IDLE_EMIT_MS) {
|
|
4050
|
-
terminalLog("proxy-flow", {
|
|
4051
|
-
corrId,
|
|
4052
|
-
clientBytes: bytesClientToUpstream,
|
|
4053
|
-
upstreamBytes: bytesUpstreamToClient,
|
|
4054
|
-
idleMs
|
|
4055
|
-
});
|
|
4056
|
-
lastEmittedAt = now;
|
|
4057
|
-
lastEmittedUpstreamBytes = bytesUpstreamToClient;
|
|
4058
|
-
}
|
|
4059
|
-
}, FLOW_TICK_MS);
|
|
4060
|
-
clientSocket.once("close", (hadError) => {
|
|
4061
|
-
finish("client", hadError ? "error" : "normal");
|
|
4062
|
-
});
|
|
4063
|
-
upstream.once("close", (hadError) => {
|
|
4064
|
-
finish("upstream", hadError ? "error" : "normal");
|
|
4065
|
-
});
|
|
4066
|
-
clientSocket.once("error", (err) => {
|
|
4067
|
-
terminalLog("proxy-error", { corrId, side: "client", err: err.message });
|
|
4068
|
-
finish("client", "error");
|
|
4069
|
-
});
|
|
4070
|
-
upstream.once("error", (err) => {
|
|
4071
|
-
terminalLog("proxy-error", { corrId, side: "upstream", err: err.message });
|
|
4072
|
-
finish("upstream", "error");
|
|
4073
|
-
});
|
|
4074
|
-
});
|
|
4075
|
-
upstream.once("timeout", () => {
|
|
4076
|
-
if (proxyOpened) return;
|
|
4077
|
-
terminalLog("proxy-error", {
|
|
4078
|
-
corrId,
|
|
4079
|
-
side: "upstream-connect",
|
|
4080
|
-
err: "timeout",
|
|
4081
|
-
timeout_ms: UPSTREAM_TIMEOUT_MS2
|
|
4082
|
-
});
|
|
4083
|
-
writeStatusAndDestroy2(clientSocket, 504, "Gateway Timeout");
|
|
4084
|
-
upstream.destroy();
|
|
4085
|
-
});
|
|
4086
|
-
upstream.once("error", (err) => {
|
|
4087
|
-
if (proxyOpened) return;
|
|
4088
|
-
terminalLog("proxy-error", {
|
|
4089
|
-
corrId,
|
|
4090
|
-
side: "upstream-connect",
|
|
4091
|
-
err: err.message
|
|
4092
|
-
});
|
|
4093
|
-
writeStatusAndDestroy2(clientSocket, 502, "Bad Gateway");
|
|
4094
|
-
upstream.destroy();
|
|
4095
|
-
});
|
|
4096
|
-
}
|
|
4097
|
-
function parseQueryParam2(query, key) {
|
|
4098
|
-
if (!query) return null;
|
|
4099
|
-
for (const pair of query.split("&")) {
|
|
4100
|
-
const eq = pair.indexOf("=");
|
|
4101
|
-
const k = eq === -1 ? pair : pair.slice(0, eq);
|
|
4102
|
-
if (k !== key) continue;
|
|
4103
|
-
const v = eq === -1 ? "" : pair.slice(eq + 1);
|
|
4104
|
-
try {
|
|
4105
|
-
return decodeURIComponent(v);
|
|
4106
|
-
} catch {
|
|
4107
|
-
return null;
|
|
4108
|
-
}
|
|
4109
|
-
}
|
|
4110
|
-
return null;
|
|
4111
|
-
}
|
|
4112
|
-
function headerString2(value) {
|
|
4113
|
-
if (value == null) return void 0;
|
|
4114
|
-
return Array.isArray(value) ? value[0] : value;
|
|
4115
|
-
}
|
|
4116
|
-
function parseOriginHost2(origin) {
|
|
4117
|
-
if (!origin) return null;
|
|
4118
|
-
try {
|
|
4119
|
-
return new URL(origin).hostname;
|
|
4120
|
-
} catch {
|
|
4121
|
-
return null;
|
|
4122
|
-
}
|
|
4123
|
-
}
|
|
4124
|
-
function writeStatusAndDestroy2(socket, status, statusText) {
|
|
4125
|
-
try {
|
|
4126
|
-
socket.write(
|
|
4127
|
-
`HTTP/1.1 ${status} ${statusText}\r
|
|
4128
|
-
Connection: close\r
|
|
4129
|
-
Content-Length: 0\r
|
|
4130
|
-
\r
|
|
4131
|
-
`
|
|
4132
|
-
);
|
|
4133
|
-
} catch {
|
|
4134
|
-
}
|
|
4135
|
-
socket.destroy();
|
|
4136
|
-
}
|
|
2898
|
+
import { readFileSync as readFileSync24, existsSync as existsSync23, watchFile } from "fs";
|
|
2899
|
+
import { resolve as resolve27, join as join12, basename as basename7 } from "path";
|
|
2900
|
+
import { homedir as homedir4 } from "os";
|
|
4137
2901
|
|
|
4138
2902
|
// app/lib/agent-slug-pattern.ts
|
|
4139
2903
|
var AGENT_SLUG_PATTERN = /^\/([a-z][a-z0-9-]{2,49})$/;
|
|
@@ -4142,9 +2906,9 @@ var AGENT_SLUG_PATTERN = /^\/([a-z][a-z0-9-]{2,49})$/;
|
|
|
4142
2906
|
import Anthropic3 from "@anthropic-ai/sdk";
|
|
4143
2907
|
import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
|
|
4144
2908
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
4145
|
-
import { resolve as
|
|
2909
|
+
import { resolve as resolve5, join as join3 } from "path";
|
|
4146
2910
|
import { platform as osPlatform } from "os";
|
|
4147
|
-
import { readFileSync as
|
|
2911
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, readdirSync as readdirSync2, existsSync as existsSync5, mkdirSync as mkdirSync4, createWriteStream, statSync as statSync3, unlinkSync as unlinkSync3, cpSync, rmSync as rmSync2, appendFileSync, openSync as openSync2, readSync as readSync2, closeSync as closeSync2 } from "fs";
|
|
4148
2912
|
import { lookup as dnsLookup } from "dns/promises";
|
|
4149
2913
|
import { createConnection as netConnect } from "net";
|
|
4150
2914
|
import { StringDecoder } from "string_decoder";
|
|
@@ -4152,34 +2916,34 @@ import { StringDecoder } from "string_decoder";
|
|
|
4152
2916
|
// ../lib/anthropic-key/src/index.ts
|
|
4153
2917
|
var import_dist = __toESM(require_dist());
|
|
4154
2918
|
import {
|
|
4155
|
-
existsSync as
|
|
4156
|
-
mkdirSync
|
|
4157
|
-
readFileSync
|
|
2919
|
+
existsSync as existsSync2,
|
|
2920
|
+
mkdirSync,
|
|
2921
|
+
readFileSync,
|
|
4158
2922
|
unlinkSync,
|
|
4159
|
-
writeFileSync
|
|
2923
|
+
writeFileSync
|
|
4160
2924
|
} from "fs";
|
|
4161
|
-
import { resolve
|
|
4162
|
-
import { homedir
|
|
2925
|
+
import { resolve, join as join2, dirname } from "path";
|
|
2926
|
+
import { homedir } from "os";
|
|
4163
2927
|
var cachedKeyFilePath = null;
|
|
4164
2928
|
function resolveKeyFilePath() {
|
|
4165
2929
|
if (cachedKeyFilePath) return cachedKeyFilePath;
|
|
4166
|
-
let
|
|
4167
|
-
const
|
|
4168
|
-
if (
|
|
4169
|
-
const brandPath =
|
|
4170
|
-
if (!
|
|
2930
|
+
let configDirName = ".maxy";
|
|
2931
|
+
const platformRoot2 = process.env.PLATFORM_ROOT ?? process.env.MAXY_PLATFORM_ROOT;
|
|
2932
|
+
if (platformRoot2) {
|
|
2933
|
+
const brandPath = join2(platformRoot2, "config", "brand.json");
|
|
2934
|
+
if (!existsSync2(brandPath)) {
|
|
4171
2935
|
throw new Error(
|
|
4172
2936
|
`brand.json not found at ${brandPath} \u2014 platform not properly installed`
|
|
4173
2937
|
);
|
|
4174
2938
|
}
|
|
4175
2939
|
try {
|
|
4176
|
-
const brand = JSON.parse(
|
|
2940
|
+
const brand = JSON.parse(readFileSync(brandPath, "utf-8"));
|
|
4177
2941
|
if (!brand.configDir) {
|
|
4178
2942
|
throw new Error(
|
|
4179
2943
|
`brand.json at ${brandPath} is missing the configDir field`
|
|
4180
2944
|
);
|
|
4181
2945
|
}
|
|
4182
|
-
|
|
2946
|
+
configDirName = brand.configDir;
|
|
4183
2947
|
} catch (err) {
|
|
4184
2948
|
if (err instanceof SyntaxError) {
|
|
4185
2949
|
throw new Error(
|
|
@@ -4189,7 +2953,7 @@ function resolveKeyFilePath() {
|
|
|
4189
2953
|
throw err;
|
|
4190
2954
|
}
|
|
4191
2955
|
}
|
|
4192
|
-
cachedKeyFilePath =
|
|
2956
|
+
cachedKeyFilePath = resolve(homedir(), configDirName, ".anthropic-api-key");
|
|
4193
2957
|
return cachedKeyFilePath;
|
|
4194
2958
|
}
|
|
4195
2959
|
function readKey() {
|
|
@@ -4211,7 +2975,7 @@ function readKey() {
|
|
|
4211
2975
|
);
|
|
4212
2976
|
}
|
|
4213
2977
|
try {
|
|
4214
|
-
const raw2 =
|
|
2978
|
+
const raw2 = readFileSync(keyFilePath2, "utf-8").trim();
|
|
4215
2979
|
if (!raw2) {
|
|
4216
2980
|
console.error(
|
|
4217
2981
|
`[anthropic-key] key file exists but is empty: ${keyFilePath2}`
|
|
@@ -4325,14 +3089,14 @@ function contextWindow(model) {
|
|
|
4325
3089
|
}
|
|
4326
3090
|
|
|
4327
3091
|
// app/lib/claude-auth.ts
|
|
4328
|
-
import { readFileSync as
|
|
3092
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
4329
3093
|
var TOKEN_ENDPOINT = "https://platform.claude.com/v1/oauth/token";
|
|
4330
3094
|
var CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
4331
3095
|
var EXPIRING_THRESHOLD_MS = 5 * 60 * 1e3;
|
|
4332
3096
|
function readCredentials() {
|
|
4333
3097
|
let raw2;
|
|
4334
3098
|
try {
|
|
4335
|
-
raw2 =
|
|
3099
|
+
raw2 = readFileSync2(CLAUDE_CREDENTIALS_FILE, "utf-8");
|
|
4336
3100
|
} catch {
|
|
4337
3101
|
return null;
|
|
4338
3102
|
}
|
|
@@ -4360,7 +3124,7 @@ function writeOAuthCredentials(tokens) {
|
|
|
4360
3124
|
try {
|
|
4361
3125
|
let fileData = {};
|
|
4362
3126
|
try {
|
|
4363
|
-
const rawFile =
|
|
3127
|
+
const rawFile = readFileSync2(CLAUDE_CREDENTIALS_FILE, "utf-8");
|
|
4364
3128
|
fileData = JSON.parse(rawFile);
|
|
4365
3129
|
} catch {
|
|
4366
3130
|
}
|
|
@@ -4371,7 +3135,7 @@ function writeOAuthCredentials(tokens) {
|
|
|
4371
3135
|
...tokens.refreshToken != null ? { refreshToken: tokens.refreshToken } : {},
|
|
4372
3136
|
expiresAt: newExpiresAt
|
|
4373
3137
|
};
|
|
4374
|
-
|
|
3138
|
+
writeFileSync2(CLAUDE_CREDENTIALS_FILE, JSON.stringify(fileData, null, 2), "utf-8");
|
|
4375
3139
|
return newExpiresAt;
|
|
4376
3140
|
} catch (err) {
|
|
4377
3141
|
console.error(`[claude-auth] credential write failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -4475,11 +3239,11 @@ async function ensureAuth() {
|
|
|
4475
3239
|
|
|
4476
3240
|
// app/lib/vnc.ts
|
|
4477
3241
|
import { spawnSync, execFileSync } from "child_process";
|
|
4478
|
-
import { createConnection
|
|
4479
|
-
import { mkdirSync as
|
|
4480
|
-
import { resolve as
|
|
4481
|
-
var
|
|
4482
|
-
var VNC_SCRIPT =
|
|
3242
|
+
import { createConnection } from "net";
|
|
3243
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
3244
|
+
import { resolve as resolve2 } from "path";
|
|
3245
|
+
var PLATFORM_ROOT = process.env.MAXY_PLATFORM_ROOT ?? resolve2(process.cwd(), "..");
|
|
3246
|
+
var VNC_SCRIPT = resolve2(PLATFORM_ROOT, "scripts/vnc.sh");
|
|
4483
3247
|
var displayMode = process.env.DISPLAY_MODE ?? "virtual";
|
|
4484
3248
|
if (displayMode === "native") {
|
|
4485
3249
|
console.log(`[vnc] DISPLAY_MODE=native \u2014 local requests use desktop display, remote requests use VNC`);
|
|
@@ -4488,8 +3252,8 @@ function resolveBrowserTransport(req, remoteAddress) {
|
|
|
4488
3252
|
if (displayMode !== "native") return "vnc";
|
|
4489
3253
|
const xff = req.headers.get("x-forwarded-for") ?? void 0;
|
|
4490
3254
|
const clientIp = resolveClientIp(remoteAddress ?? "127.0.0.1", xff);
|
|
4491
|
-
const
|
|
4492
|
-
const transport =
|
|
3255
|
+
const isLoopback = clientIp === "127.0.0.1" || clientIp === "::1" || clientIp === "loopback";
|
|
3256
|
+
const transport = isLoopback && !xff ? "native" : "vnc";
|
|
4493
3257
|
if (remoteAddress && remoteAddress !== "127.0.0.1" && remoteAddress !== "::1") {
|
|
4494
3258
|
const oldClientIp = resolveClientIp("127.0.0.1", xff);
|
|
4495
3259
|
const oldIsLoopback = oldClientIp === "127.0.0.1" || oldClientIp === "::1" || oldClientIp === "loopback";
|
|
@@ -4537,7 +3301,7 @@ function discoverNativeDisplay() {
|
|
|
4537
3301
|
const leaderPid = leaderResult.stdout?.trim();
|
|
4538
3302
|
if (leaderPid) {
|
|
4539
3303
|
try {
|
|
4540
|
-
const environ =
|
|
3304
|
+
const environ = readFileSync3(`/proc/${leaderPid}/environ`, "utf8");
|
|
4541
3305
|
const match2 = environ.split("\0").find((e) => e.startsWith("WAYLAND_DISPLAY="));
|
|
4542
3306
|
if (match2) waylandDisplay = match2.split("=")[1];
|
|
4543
3307
|
} catch {
|
|
@@ -4571,7 +3335,7 @@ async function waitForPort(port2, timeoutMs = 12e3) {
|
|
|
4571
3335
|
const deadline = Date.now() + timeoutMs;
|
|
4572
3336
|
while (Date.now() < deadline) {
|
|
4573
3337
|
const ready = await new Promise((res) => {
|
|
4574
|
-
const socket =
|
|
3338
|
+
const socket = createConnection(port2, "127.0.0.1");
|
|
4575
3339
|
socket.setTimeout(500);
|
|
4576
3340
|
socket.once("connect", () => {
|
|
4577
3341
|
socket.destroy();
|
|
@@ -4592,10 +3356,10 @@ async function waitForPort(port2, timeoutMs = 12e3) {
|
|
|
4592
3356
|
return false;
|
|
4593
3357
|
}
|
|
4594
3358
|
function ensureLogDir() {
|
|
4595
|
-
|
|
3359
|
+
mkdirSync2(LOG_DIR, { recursive: true });
|
|
4596
3360
|
}
|
|
4597
3361
|
function logPath(name) {
|
|
4598
|
-
return
|
|
3362
|
+
return resolve2(LOG_DIR, `${name}.log`);
|
|
4599
3363
|
}
|
|
4600
3364
|
async function ensureVnc() {
|
|
4601
3365
|
const up = await waitForPort(5900, 1e3);
|
|
@@ -4804,9 +3568,9 @@ async function ensureTerminalUpgrade(transport = "vnc") {
|
|
|
4804
3568
|
return { ok: true };
|
|
4805
3569
|
}
|
|
4806
3570
|
function writeChromiumWrapper() {
|
|
4807
|
-
|
|
4808
|
-
const wrapperPath =
|
|
4809
|
-
|
|
3571
|
+
mkdirSync2(BIN_DIR, { recursive: true });
|
|
3572
|
+
const wrapperPath = resolve2(BIN_DIR, "chromium");
|
|
3573
|
+
writeFileSync3(wrapperPath, `#!/bin/bash
|
|
4810
3574
|
LOG="${LOG_DIR}/chromium.log"
|
|
4811
3575
|
echo "==== [$(date)] chromium wrapper ====" >> "$LOG"
|
|
4812
3576
|
echo " DISPLAY=$DISPLAY WAYLAND=$WAYLAND_DISPLAY XDG_SESSION_TYPE=$XDG_SESSION_TYPE" >> "$LOG"
|
|
@@ -4890,15 +3654,15 @@ function buildX11Env(chromiumWrapperPath, transport = "vnc") {
|
|
|
4890
3654
|
import neo4j from "neo4j-driver";
|
|
4891
3655
|
import { randomUUID } from "crypto";
|
|
4892
3656
|
import { spawn } from "child_process";
|
|
4893
|
-
import { readFileSync as
|
|
4894
|
-
import { resolve as
|
|
4895
|
-
var
|
|
3657
|
+
import { readFileSync as readFileSync4, readdirSync, existsSync as existsSync3, openSync, readSync, closeSync, statSync as statSync2, rmSync } from "fs";
|
|
3658
|
+
import { resolve as resolve3 } from "path";
|
|
3659
|
+
var PLATFORM_ROOT2 = process.env.MAXY_PLATFORM_ROOT ?? resolve3(process.cwd(), "..");
|
|
4896
3660
|
var driver = null;
|
|
4897
3661
|
function readPassword() {
|
|
4898
3662
|
if (process.env.NEO4J_PASSWORD) return process.env.NEO4J_PASSWORD;
|
|
4899
|
-
const passwordFile =
|
|
3663
|
+
const passwordFile = resolve3(PLATFORM_ROOT2, "config/.neo4j-password");
|
|
4900
3664
|
try {
|
|
4901
|
-
return
|
|
3665
|
+
return readFileSync4(passwordFile, "utf-8").trim();
|
|
4902
3666
|
} catch {
|
|
4903
3667
|
throw new Error(
|
|
4904
3668
|
`Neo4j password not found. Expected at ${passwordFile} or in NEO4J_PASSWORD env var.`
|
|
@@ -5361,8 +4125,8 @@ async function persistMessage(conversationId, role, content, accountId, tokens,
|
|
|
5361
4125
|
const prev = persistMessageLocks.get(conversationId);
|
|
5362
4126
|
const waited = prev !== void 0;
|
|
5363
4127
|
let release;
|
|
5364
|
-
const mine = new Promise((
|
|
5365
|
-
release =
|
|
4128
|
+
const mine = new Promise((resolve28) => {
|
|
4129
|
+
release = resolve28;
|
|
5366
4130
|
});
|
|
5367
4131
|
const chained = (prev ?? Promise.resolve()).then(() => mine);
|
|
5368
4132
|
persistMessageLocks.set(conversationId, chained);
|
|
@@ -5621,7 +4385,7 @@ ${userContent}`;
|
|
|
5621
4385
|
"dontAsk",
|
|
5622
4386
|
prompt
|
|
5623
4387
|
];
|
|
5624
|
-
return new Promise((
|
|
4388
|
+
return new Promise((resolve28) => {
|
|
5625
4389
|
let stdout = "";
|
|
5626
4390
|
let stderr = "";
|
|
5627
4391
|
const spawnFn = _spawnOverride ?? spawn;
|
|
@@ -5639,35 +4403,35 @@ ${userContent}`;
|
|
|
5639
4403
|
const timer = setTimeout(() => {
|
|
5640
4404
|
proc.kill("SIGTERM");
|
|
5641
4405
|
console.error("[persist] autoLabel: haiku subprocess timed out");
|
|
5642
|
-
|
|
4406
|
+
resolve28(null);
|
|
5643
4407
|
}, SESSION_LABEL_TIMEOUT_MS);
|
|
5644
4408
|
proc.on("error", (err) => {
|
|
5645
4409
|
clearTimeout(timer);
|
|
5646
4410
|
console.error(`[persist] autoLabel: subprocess error \u2014 ${err.message}`);
|
|
5647
|
-
|
|
4411
|
+
resolve28(null);
|
|
5648
4412
|
});
|
|
5649
4413
|
proc.on("close", (code) => {
|
|
5650
4414
|
clearTimeout(timer);
|
|
5651
4415
|
if (code !== 0) {
|
|
5652
4416
|
console.error(`[persist] autoLabel: subprocess exited code=${code}${stderr ? ` stderr=${stderr.trim().slice(0, 200)}` : ""}`);
|
|
5653
|
-
|
|
4417
|
+
resolve28(null);
|
|
5654
4418
|
return;
|
|
5655
4419
|
}
|
|
5656
4420
|
const text = stdout.trim();
|
|
5657
4421
|
if (!text) {
|
|
5658
4422
|
console.error("[persist] autoLabel: haiku returned empty response");
|
|
5659
|
-
|
|
4423
|
+
resolve28(null);
|
|
5660
4424
|
return;
|
|
5661
4425
|
}
|
|
5662
4426
|
if (text === "SKIP") {
|
|
5663
4427
|
console.error("[persist] autoLabel: haiku returned SKIP \u2014 messages too vague");
|
|
5664
|
-
|
|
4428
|
+
resolve28(null);
|
|
5665
4429
|
return;
|
|
5666
4430
|
}
|
|
5667
4431
|
const words = text.split(/\s+/).slice(0, SESSION_LABEL_MAX_WORDS);
|
|
5668
4432
|
const label = words.join(" ");
|
|
5669
4433
|
console.error(`[persist] autoLabel: haiku response="${label}"`);
|
|
5670
|
-
|
|
4434
|
+
resolve28(label);
|
|
5671
4435
|
});
|
|
5672
4436
|
});
|
|
5673
4437
|
}
|
|
@@ -5939,10 +4703,10 @@ var MAX_RECENT_TOOL_FAILURES = 3;
|
|
|
5939
4703
|
var RECENT_FAILURES_TAIL_BYTES = 10 * 1024;
|
|
5940
4704
|
function readRecentToolFailures(accountId, conversationId) {
|
|
5941
4705
|
try {
|
|
5942
|
-
const
|
|
5943
|
-
const logDir =
|
|
5944
|
-
const logPath2 =
|
|
5945
|
-
if (!
|
|
4706
|
+
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? resolve3(process.cwd(), "..");
|
|
4707
|
+
const logDir = resolve3(platformRoot2, "..", "data/accounts", accountId, "logs");
|
|
4708
|
+
const logPath2 = resolve3(logDir, `claude-agent-stream-${conversationId}.log`);
|
|
4709
|
+
if (!existsSync3(logPath2)) {
|
|
5946
4710
|
console.error(`[review-tail-skip] path=${logPath2} reason=file-missing \u2014 first turn of conversation, subprocess not yet spawned, or log rotated`);
|
|
5947
4711
|
return [];
|
|
5948
4712
|
}
|
|
@@ -6098,13 +4862,13 @@ ${taskLines.join("\n")}`);
|
|
|
6098
4862
|
let pendingCount = 0;
|
|
6099
4863
|
let pendingLines = [];
|
|
6100
4864
|
try {
|
|
6101
|
-
const
|
|
6102
|
-
const pendingDir =
|
|
6103
|
-
if (
|
|
4865
|
+
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? resolve3(process.cwd(), "..");
|
|
4866
|
+
const pendingDir = resolve3(platformRoot2, "..", "data/accounts", accountId, "pending-actions");
|
|
4867
|
+
if (existsSync3(pendingDir)) {
|
|
6104
4868
|
const files = readdirSync(pendingDir).filter((f) => f.endsWith(".json") && !f.startsWith("."));
|
|
6105
4869
|
for (const file of files) {
|
|
6106
4870
|
try {
|
|
6107
|
-
const raw2 =
|
|
4871
|
+
const raw2 = readFileSync4(resolve3(pendingDir, file), "utf-8");
|
|
6108
4872
|
const action = JSON.parse(raw2);
|
|
6109
4873
|
if (action.state === "pending") {
|
|
6110
4874
|
const inputSummary = JSON.stringify(action.hookPayload?.tool_input ?? {}).slice(0, 150);
|
|
@@ -6171,12 +4935,12 @@ ${sections.join("\n\n")}
|
|
|
6171
4935
|
}
|
|
6172
4936
|
}
|
|
6173
4937
|
async function consumeStep7FlagUI(session, accountId) {
|
|
6174
|
-
const accountDir =
|
|
6175
|
-
const flagPath =
|
|
6176
|
-
if (!
|
|
4938
|
+
const accountDir = resolve3(PLATFORM_ROOT2, "..", "data/accounts", accountId);
|
|
4939
|
+
const flagPath = resolve3(accountDir, "onboarding", "step7-complete");
|
|
4940
|
+
if (!existsSync3(flagPath)) return false;
|
|
6177
4941
|
let completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6178
4942
|
try {
|
|
6179
|
-
const raw2 =
|
|
4943
|
+
const raw2 = readFileSync4(flagPath, "utf-8").trim();
|
|
6180
4944
|
if (raw2) {
|
|
6181
4945
|
const parsed = JSON.parse(raw2);
|
|
6182
4946
|
if (typeof parsed.completedAt === "string") {
|
|
@@ -6614,8 +5378,8 @@ ${items}
|
|
|
6614
5378
|
}
|
|
6615
5379
|
|
|
6616
5380
|
// app/lib/adherence-ledger.ts
|
|
6617
|
-
import { existsSync as
|
|
6618
|
-
import { resolve as
|
|
5381
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, renameSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
5382
|
+
import { resolve as resolve4, dirname as dirname2 } from "path";
|
|
6619
5383
|
import lockfile from "proper-lockfile";
|
|
6620
5384
|
var LOG_TAG = "[adherence-ledger]";
|
|
6621
5385
|
var ADHERENCE_BLOCK_THRESHOLD = 5;
|
|
@@ -6630,7 +5394,7 @@ function nowIso() {
|
|
|
6630
5394
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
6631
5395
|
}
|
|
6632
5396
|
function ledgerPath(accountDir, agentName) {
|
|
6633
|
-
return
|
|
5397
|
+
return resolve4(accountDir, "agents", agentName, "adherence-ledger.json");
|
|
6634
5398
|
}
|
|
6635
5399
|
function lockPath(accountDir, agentName) {
|
|
6636
5400
|
return `${ledgerPath(accountDir, agentName)}.lock`;
|
|
@@ -6655,8 +5419,8 @@ function pruneViolations(violations) {
|
|
|
6655
5419
|
});
|
|
6656
5420
|
}
|
|
6657
5421
|
function readUnlocked(path2, accountId, agentName) {
|
|
6658
|
-
if (!
|
|
6659
|
-
const content =
|
|
5422
|
+
if (!existsSync4(path2)) return emptyLedger(accountId, agentName);
|
|
5423
|
+
const content = readFileSync5(path2, "utf-8");
|
|
6660
5424
|
if (!content.trim()) return emptyLedger(accountId, agentName);
|
|
6661
5425
|
const data = JSON.parse(content);
|
|
6662
5426
|
if (typeof data !== "object" || data === null) {
|
|
@@ -6675,10 +5439,10 @@ function writeAtomic(path2, data) {
|
|
|
6675
5439
|
}
|
|
6676
5440
|
data.updated_at = nowIso();
|
|
6677
5441
|
const dir = dirname2(path2);
|
|
6678
|
-
|
|
5442
|
+
mkdirSync3(dir, { recursive: true });
|
|
6679
5443
|
const tmp = `${path2}.tmp-${process.pid}-${Date.now()}`;
|
|
6680
5444
|
try {
|
|
6681
|
-
|
|
5445
|
+
writeFileSync4(tmp, JSON.stringify(data, null, 2), "utf-8");
|
|
6682
5446
|
renameSync(tmp, path2);
|
|
6683
5447
|
} catch (err) {
|
|
6684
5448
|
try {
|
|
@@ -6691,9 +5455,9 @@ function writeAtomic(path2, data) {
|
|
|
6691
5455
|
async function withLock(accountDir, agentName, fn) {
|
|
6692
5456
|
const path2 = ledgerPath(accountDir, agentName);
|
|
6693
5457
|
const lock = lockPath(accountDir, agentName);
|
|
6694
|
-
|
|
6695
|
-
if (!
|
|
6696
|
-
|
|
5458
|
+
mkdirSync3(dirname2(lock), { recursive: true });
|
|
5459
|
+
if (!existsSync4(lock)) {
|
|
5460
|
+
writeFileSync4(lock, "", "utf-8");
|
|
6697
5461
|
}
|
|
6698
5462
|
const release = await lockfile.lock(lock, {
|
|
6699
5463
|
realpath: false,
|
|
@@ -7179,20 +5943,20 @@ function agentLogStream(name, accountDir, conversationId) {
|
|
|
7179
5943
|
if (!conversationId) {
|
|
7180
5944
|
throw new Error(`agentLogStream: conversationId is required (name=${name}) \u2014 use preConversationLogStream for pre-session events`);
|
|
7181
5945
|
}
|
|
7182
|
-
const logDir =
|
|
7183
|
-
|
|
5946
|
+
const logDir = resolve5(accountDir, "logs");
|
|
5947
|
+
mkdirSync4(logDir, { recursive: true });
|
|
7184
5948
|
purgeOldLogs(logDir, `${name}-`);
|
|
7185
|
-
const logPath2 =
|
|
5949
|
+
const logPath2 = resolve5(logDir, `${name}-${conversationId}.log`);
|
|
7186
5950
|
const stream = createWriteStream(logPath2, { flags: "a" });
|
|
7187
5951
|
registerStreamLog(stream, { path: logPath2, conversationId, name });
|
|
7188
5952
|
return stream;
|
|
7189
5953
|
}
|
|
7190
5954
|
function preConversationLogStream(name, accountDir) {
|
|
7191
|
-
const logDir =
|
|
7192
|
-
|
|
5955
|
+
const logDir = resolve5(accountDir, "logs");
|
|
5956
|
+
mkdirSync4(logDir, { recursive: true });
|
|
7193
5957
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
7194
5958
|
purgeOldLogs(logDir, `preconversation-${name}-`);
|
|
7195
|
-
const logPath2 =
|
|
5959
|
+
const logPath2 = resolve5(logDir, `preconversation-${name}-${date}.log`);
|
|
7196
5960
|
const stream = createWriteStream(logPath2, { flags: "a" });
|
|
7197
5961
|
registerStreamLog(stream, { path: logPath2, conversationId: null, name: `preconversation-${name}` });
|
|
7198
5962
|
return stream;
|
|
@@ -7211,7 +5975,7 @@ function sigtermFlushStreamLogs(reason, source) {
|
|
|
7211
5975
|
const line = `[${ts}] [server-sigterm] reason=${reason}${convPart} name=${entry.name} source=${source}
|
|
7212
5976
|
`;
|
|
7213
5977
|
try {
|
|
7214
|
-
|
|
5978
|
+
appendFileSync(entry.path, line);
|
|
7215
5979
|
} catch (err) {
|
|
7216
5980
|
const msg = err instanceof Error ? err.message : String(err);
|
|
7217
5981
|
console.error(`[server-sigterm-flush-err] path=${entry.path} reason=${msg}`);
|
|
@@ -7230,7 +5994,7 @@ function purgeOldLogs(logDir, prefix) {
|
|
|
7230
5994
|
}
|
|
7231
5995
|
for (const file of entries) {
|
|
7232
5996
|
if (!file.startsWith(prefix)) continue;
|
|
7233
|
-
const filePath =
|
|
5997
|
+
const filePath = resolve5(logDir, file);
|
|
7234
5998
|
try {
|
|
7235
5999
|
if (statSync3(filePath).mtimeMs < cutoff) unlinkSync3(filePath);
|
|
7236
6000
|
} catch (err) {
|
|
@@ -7299,7 +6063,7 @@ function sampleProcState(pid) {
|
|
|
7299
6063
|
let sockets2 = 0;
|
|
7300
6064
|
for (const tcpFile of ["/proc/" + pid + "/net/tcp", "/proc/" + pid + "/net/tcp6"]) {
|
|
7301
6065
|
try {
|
|
7302
|
-
const raw2 =
|
|
6066
|
+
const raw2 = readFileSync6(tcpFile, "utf-8");
|
|
7303
6067
|
const lines2 = raw2.split("\n");
|
|
7304
6068
|
for (let i = 1; i < lines2.length; i++) {
|
|
7305
6069
|
const line = lines2[i].trim();
|
|
@@ -7315,7 +6079,7 @@ function sampleProcState(pid) {
|
|
|
7315
6079
|
}
|
|
7316
6080
|
let rssMb = 0;
|
|
7317
6081
|
try {
|
|
7318
|
-
const statm =
|
|
6082
|
+
const statm = readFileSync6(`/proc/${pid}/statm`, "utf-8").trim().split(/\s+/);
|
|
7319
6083
|
const rssPages = parseInt(statm[1] ?? "0", 10);
|
|
7320
6084
|
if (Number.isFinite(rssPages)) rssMb = Math.round(rssPages * 4096 / (1024 * 1024));
|
|
7321
6085
|
} catch {
|
|
@@ -7348,21 +6112,21 @@ function sampleProcState(pid) {
|
|
|
7348
6112
|
return `proc_err=${JSON.stringify(msg.slice(0, 60))}`;
|
|
7349
6113
|
}
|
|
7350
6114
|
}
|
|
7351
|
-
var
|
|
7352
|
-
var ACCOUNTS_DIR =
|
|
7353
|
-
if (!
|
|
6115
|
+
var PLATFORM_ROOT3 = process.env.MAXY_PLATFORM_ROOT ?? resolve5(process.cwd(), "..");
|
|
6116
|
+
var ACCOUNTS_DIR = resolve5(PLATFORM_ROOT3, "..", "data/accounts");
|
|
6117
|
+
if (!existsSync5(PLATFORM_ROOT3)) {
|
|
7354
6118
|
throw new Error(
|
|
7355
|
-
`PLATFORM_ROOT does not exist: ${
|
|
6119
|
+
`PLATFORM_ROOT does not exist: ${PLATFORM_ROOT3}
|
|
7356
6120
|
Set the MAXY_PLATFORM_ROOT environment variable to the absolute path of the platform directory.`
|
|
7357
6121
|
);
|
|
7358
6122
|
}
|
|
7359
6123
|
function resolveAccount() {
|
|
7360
|
-
if (!
|
|
7361
|
-
const usersFilePath =
|
|
6124
|
+
if (!existsSync5(ACCOUNTS_DIR)) return null;
|
|
6125
|
+
const usersFilePath = resolve5(PLATFORM_ROOT3, "config", "users.json");
|
|
7362
6126
|
let usersJsonUserId = null;
|
|
7363
|
-
if (
|
|
6127
|
+
if (existsSync5(usersFilePath)) {
|
|
7364
6128
|
try {
|
|
7365
|
-
const raw2 =
|
|
6129
|
+
const raw2 = readFileSync6(usersFilePath, "utf-8").trim();
|
|
7366
6130
|
if (raw2) {
|
|
7367
6131
|
const users = JSON.parse(raw2);
|
|
7368
6132
|
if (users.length > 0) {
|
|
@@ -7376,9 +6140,9 @@ function resolveAccount() {
|
|
|
7376
6140
|
let fallback = null;
|
|
7377
6141
|
for (const entry of entries) {
|
|
7378
6142
|
if (!entry.isDirectory()) continue;
|
|
7379
|
-
const configPath2 =
|
|
7380
|
-
if (!
|
|
7381
|
-
const raw2 =
|
|
6143
|
+
const configPath2 = resolve5(ACCOUNTS_DIR, entry.name, "account.json");
|
|
6144
|
+
if (!existsSync5(configPath2)) continue;
|
|
6145
|
+
const raw2 = readFileSync6(configPath2, "utf-8");
|
|
7382
6146
|
let config;
|
|
7383
6147
|
try {
|
|
7384
6148
|
config = JSON.parse(raw2);
|
|
@@ -7393,7 +6157,7 @@ function resolveAccount() {
|
|
|
7393
6157
|
}
|
|
7394
6158
|
const result = {
|
|
7395
6159
|
accountId: config.accountId,
|
|
7396
|
-
accountDir:
|
|
6160
|
+
accountDir: resolve5(ACCOUNTS_DIR, entry.name),
|
|
7397
6161
|
config
|
|
7398
6162
|
};
|
|
7399
6163
|
if (usersJsonUserId && config.admins?.some((a) => a.userId === usersJsonUserId)) {
|
|
@@ -7411,9 +6175,9 @@ function resolveAccount() {
|
|
|
7411
6175
|
return fallback;
|
|
7412
6176
|
}
|
|
7413
6177
|
function readAgentFile(accountDir, agentName, filename) {
|
|
7414
|
-
const filePath =
|
|
7415
|
-
if (!
|
|
7416
|
-
return
|
|
6178
|
+
const filePath = resolve5(accountDir, "agents", agentName, filename);
|
|
6179
|
+
if (!existsSync5(filePath)) return null;
|
|
6180
|
+
return readFileSync6(filePath, "utf-8");
|
|
7417
6181
|
}
|
|
7418
6182
|
function readIdentity(accountDir, agentName) {
|
|
7419
6183
|
return readAgentFile(accountDir, agentName, "IDENTITY.md");
|
|
@@ -7448,14 +6212,14 @@ function validateAgentSlug(slug) {
|
|
|
7448
6212
|
return true;
|
|
7449
6213
|
}
|
|
7450
6214
|
function resolveDefaultAgentSlug(accountDir) {
|
|
7451
|
-
const configPath2 =
|
|
7452
|
-
if (!
|
|
6215
|
+
const configPath2 = resolve5(accountDir, "account.json");
|
|
6216
|
+
if (!existsSync5(configPath2)) {
|
|
7453
6217
|
console.error("[agent-resolve] account.json not found \u2014 cannot resolve defaultAgent");
|
|
7454
6218
|
return null;
|
|
7455
6219
|
}
|
|
7456
6220
|
let config;
|
|
7457
6221
|
try {
|
|
7458
|
-
config = JSON.parse(
|
|
6222
|
+
config = JSON.parse(readFileSync6(configPath2, "utf-8"));
|
|
7459
6223
|
} catch (err) {
|
|
7460
6224
|
console.error("[agent-resolve] failed to read account.json:", err);
|
|
7461
6225
|
return null;
|
|
@@ -7464,8 +6228,8 @@ function resolveDefaultAgentSlug(accountDir) {
|
|
|
7464
6228
|
console.error("[agent-resolve] defaultAgent not configured in account.json \u2014 set it via the connect-whatsapp skill");
|
|
7465
6229
|
return null;
|
|
7466
6230
|
}
|
|
7467
|
-
const agentConfigPath =
|
|
7468
|
-
if (!
|
|
6231
|
+
const agentConfigPath = resolve5(accountDir, "agents", config.defaultAgent, "config.json");
|
|
6232
|
+
if (!existsSync5(agentConfigPath)) {
|
|
7469
6233
|
console.error(`[agent-resolve] defaultAgent="${config.defaultAgent}" has no config.json at ${agentConfigPath}`);
|
|
7470
6234
|
return null;
|
|
7471
6235
|
}
|
|
@@ -7537,23 +6301,23 @@ function resolveAgentConfig(accountDir, agentName) {
|
|
|
7537
6301
|
}
|
|
7538
6302
|
let knowledge = null;
|
|
7539
6303
|
let knowledgeBaked = false;
|
|
7540
|
-
const agentDir =
|
|
7541
|
-
const knowledgePath =
|
|
7542
|
-
const summaryPath =
|
|
7543
|
-
const hasKnowledge =
|
|
7544
|
-
const hasSummary =
|
|
6304
|
+
const agentDir = resolve5(accountDir, "agents", agentName);
|
|
6305
|
+
const knowledgePath = resolve5(agentDir, "KNOWLEDGE.md");
|
|
6306
|
+
const summaryPath = resolve5(agentDir, "KNOWLEDGE-SUMMARY.md");
|
|
6307
|
+
const hasKnowledge = existsSync5(knowledgePath);
|
|
6308
|
+
const hasSummary = existsSync5(summaryPath);
|
|
7545
6309
|
if (hasKnowledge && hasSummary) {
|
|
7546
6310
|
const knowledgeMtime = statSync3(knowledgePath).mtimeMs;
|
|
7547
6311
|
const summaryMtime = statSync3(summaryPath).mtimeMs;
|
|
7548
6312
|
if (summaryMtime >= knowledgeMtime) {
|
|
7549
|
-
knowledge =
|
|
6313
|
+
knowledge = readFileSync6(summaryPath, "utf-8");
|
|
7550
6314
|
} else {
|
|
7551
6315
|
console.warn(`[agent-config] ${agentName}: KNOWLEDGE-SUMMARY.md is stale (KNOWLEDGE.md is newer) \u2014 using full knowledge`);
|
|
7552
|
-
knowledge =
|
|
6316
|
+
knowledge = readFileSync6(knowledgePath, "utf-8");
|
|
7553
6317
|
}
|
|
7554
6318
|
knowledgeBaked = true;
|
|
7555
6319
|
} else if (hasKnowledge) {
|
|
7556
|
-
knowledge =
|
|
6320
|
+
knowledge = readFileSync6(knowledgePath, "utf-8");
|
|
7557
6321
|
knowledgeBaked = true;
|
|
7558
6322
|
}
|
|
7559
6323
|
let budget = null;
|
|
@@ -7575,11 +6339,11 @@ function resolveAgentConfig(accountDir, agentName) {
|
|
|
7575
6339
|
return { model, plugins, status, displayName, image, imageShape, showAgentName, knowledge, knowledgeBaked, liveMemory, knowledgeKeywords, budget, accessMode };
|
|
7576
6340
|
}
|
|
7577
6341
|
function parsePluginFrontmatter(pluginDir) {
|
|
7578
|
-
const pluginPath =
|
|
7579
|
-
if (!
|
|
6342
|
+
const pluginPath = resolve5(PLATFORM_ROOT3, "plugins", pluginDir, "PLUGIN.md");
|
|
6343
|
+
if (!existsSync5(pluginPath)) return null;
|
|
7580
6344
|
let raw2;
|
|
7581
6345
|
try {
|
|
7582
|
-
raw2 =
|
|
6346
|
+
raw2 = readFileSync6(pluginPath, "utf-8");
|
|
7583
6347
|
} catch {
|
|
7584
6348
|
console.warn(`[plugins] cannot read ${pluginPath}`);
|
|
7585
6349
|
return null;
|
|
@@ -7638,24 +6402,24 @@ function parsePluginFrontmatter(pluginDir) {
|
|
|
7638
6402
|
function autoDeliverPremiumPlugins(purchasedPlugins) {
|
|
7639
6403
|
if (!purchasedPlugins || purchasedPlugins.length === 0) return;
|
|
7640
6404
|
const TAG19 = "[premium-auto-deliver]";
|
|
7641
|
-
const stagingRoot =
|
|
7642
|
-
const pluginsDir =
|
|
7643
|
-
if (!
|
|
6405
|
+
const stagingRoot = resolve5(PLATFORM_ROOT3, "../premium-plugins");
|
|
6406
|
+
const pluginsDir = resolve5(PLATFORM_ROOT3, "plugins");
|
|
6407
|
+
if (!existsSync5(stagingRoot)) {
|
|
7644
6408
|
console.log(`${TAG19} no staging directory \u2014 skipping`);
|
|
7645
6409
|
return;
|
|
7646
6410
|
}
|
|
7647
6411
|
for (const pluginName of purchasedPlugins) {
|
|
7648
|
-
const stagingDir =
|
|
7649
|
-
if (!
|
|
6412
|
+
const stagingDir = resolve5(stagingRoot, pluginName);
|
|
6413
|
+
if (!existsSync5(stagingDir)) {
|
|
7650
6414
|
console.log(`${TAG19} ${pluginName}: not in staging \u2014 skipping`);
|
|
7651
6415
|
continue;
|
|
7652
6416
|
}
|
|
7653
|
-
const bundlePath =
|
|
7654
|
-
const isBundle =
|
|
6417
|
+
const bundlePath = join3(stagingDir, "BUNDLE.md");
|
|
6418
|
+
const isBundle = existsSync5(bundlePath);
|
|
7655
6419
|
if (isBundle) {
|
|
7656
6420
|
let bundleRaw;
|
|
7657
6421
|
try {
|
|
7658
|
-
bundleRaw =
|
|
6422
|
+
bundleRaw = readFileSync6(bundlePath, "utf-8");
|
|
7659
6423
|
} catch (err) {
|
|
7660
6424
|
console.log(`${TAG19} ${pluginName}: cannot read BUNDLE.md \u2014 ${err instanceof Error ? err.message : String(err)}`);
|
|
7661
6425
|
continue;
|
|
@@ -7686,13 +6450,13 @@ function autoDeliverPremiumPlugins(purchasedPlugins) {
|
|
|
7686
6450
|
let delivered = 0;
|
|
7687
6451
|
let skipped = 0;
|
|
7688
6452
|
for (const sub of subPlugins) {
|
|
7689
|
-
const target =
|
|
7690
|
-
if (
|
|
6453
|
+
const target = resolve5(pluginsDir, sub);
|
|
6454
|
+
if (existsSync5(resolve5(target, "PLUGIN.md"))) {
|
|
7691
6455
|
skipped++;
|
|
7692
6456
|
continue;
|
|
7693
6457
|
}
|
|
7694
|
-
const source =
|
|
7695
|
-
if (!
|
|
6458
|
+
const source = resolve5(stagingDir, "plugins", sub);
|
|
6459
|
+
if (!existsSync5(source)) {
|
|
7696
6460
|
console.log(`${TAG19} ${pluginName}/${sub}: source missing in staging \u2014 skipping`);
|
|
7697
6461
|
continue;
|
|
7698
6462
|
}
|
|
@@ -7705,8 +6469,8 @@ function autoDeliverPremiumPlugins(purchasedPlugins) {
|
|
|
7705
6469
|
}
|
|
7706
6470
|
console.log(`${TAG19} ${pluginName} (bundle): ${delivered} delivered, ${skipped} already present`);
|
|
7707
6471
|
} else {
|
|
7708
|
-
const target =
|
|
7709
|
-
if (
|
|
6472
|
+
const target = resolve5(pluginsDir, pluginName);
|
|
6473
|
+
if (existsSync5(resolve5(target, "PLUGIN.md"))) {
|
|
7710
6474
|
console.log(`${TAG19} ${pluginName}: already present \u2014 skipping`);
|
|
7711
6475
|
continue;
|
|
7712
6476
|
}
|
|
@@ -7746,21 +6510,21 @@ function migratePluginRenames(accountDir, config) {
|
|
|
7746
6510
|
return name;
|
|
7747
6511
|
});
|
|
7748
6512
|
if (!changed) return;
|
|
7749
|
-
const configPath2 =
|
|
6513
|
+
const configPath2 = resolve5(accountDir, "account.json");
|
|
7750
6514
|
try {
|
|
7751
|
-
const raw2 =
|
|
6515
|
+
const raw2 = readFileSync6(configPath2, "utf-8");
|
|
7752
6516
|
const parsed = JSON.parse(raw2);
|
|
7753
6517
|
parsed.enabledPlugins = migrated;
|
|
7754
|
-
|
|
6518
|
+
writeFileSync5(configPath2, JSON.stringify(parsed, null, 2) + "\n");
|
|
7755
6519
|
config.enabledPlugins = migrated;
|
|
7756
6520
|
console.log(`${TAG19} account.json updated (${migrated.length} plugins)`);
|
|
7757
6521
|
} catch (err) {
|
|
7758
6522
|
console.error(`${TAG19} failed to update account.json \u2014 ${err instanceof Error ? err.message : String(err)}`);
|
|
7759
6523
|
}
|
|
7760
|
-
const pluginsDir =
|
|
6524
|
+
const pluginsDir = resolve5(PLATFORM_ROOT3, "plugins");
|
|
7761
6525
|
for (const oldName of Object.keys(PLUGIN_RENAMES)) {
|
|
7762
|
-
const orphan =
|
|
7763
|
-
if (
|
|
6526
|
+
const orphan = resolve5(pluginsDir, oldName);
|
|
6527
|
+
if (existsSync5(orphan)) {
|
|
7764
6528
|
try {
|
|
7765
6529
|
rmSync2(orphan, { recursive: true });
|
|
7766
6530
|
console.log(`${TAG19} removed orphan: ${oldName}`);
|
|
@@ -7773,22 +6537,22 @@ function migratePluginRenames(accountDir, config) {
|
|
|
7773
6537
|
function autoDeliverBundleAgents(accountDir, purchasedPlugins) {
|
|
7774
6538
|
if (!purchasedPlugins || purchasedPlugins.length === 0) return;
|
|
7775
6539
|
const TAG19 = "[bundle-agent-deliver]";
|
|
7776
|
-
const stagingRoot =
|
|
7777
|
-
const specialistsDir =
|
|
7778
|
-
if (!
|
|
7779
|
-
if (!
|
|
7780
|
-
|
|
6540
|
+
const stagingRoot = resolve5(PLATFORM_ROOT3, "../premium-plugins");
|
|
6541
|
+
const specialistsDir = resolve5(accountDir, "specialists", "agents");
|
|
6542
|
+
if (!existsSync5(stagingRoot)) return;
|
|
6543
|
+
if (!existsSync5(specialistsDir)) {
|
|
6544
|
+
mkdirSync4(specialistsDir, { recursive: true });
|
|
7781
6545
|
}
|
|
7782
|
-
const agentsmdPath =
|
|
6546
|
+
const agentsmdPath = resolve5(accountDir, "agents", "admin", "AGENTS.md");
|
|
7783
6547
|
let agentsmd = "";
|
|
7784
6548
|
try {
|
|
7785
|
-
agentsmd =
|
|
6549
|
+
agentsmd = existsSync5(agentsmdPath) ? readFileSync6(agentsmdPath, "utf-8") : "";
|
|
7786
6550
|
} catch {
|
|
7787
6551
|
}
|
|
7788
6552
|
let delivered = 0;
|
|
7789
6553
|
for (const pluginName of purchasedPlugins) {
|
|
7790
|
-
const bundleAgentsDir =
|
|
7791
|
-
if (!
|
|
6554
|
+
const bundleAgentsDir = resolve5(stagingRoot, pluginName, "agents");
|
|
6555
|
+
if (!existsSync5(bundleAgentsDir)) continue;
|
|
7792
6556
|
let entries;
|
|
7793
6557
|
try {
|
|
7794
6558
|
entries = readdirSync2(bundleAgentsDir).filter((f) => f.endsWith(".md"));
|
|
@@ -7796,9 +6560,9 @@ function autoDeliverBundleAgents(accountDir, purchasedPlugins) {
|
|
|
7796
6560
|
continue;
|
|
7797
6561
|
}
|
|
7798
6562
|
for (const filename of entries) {
|
|
7799
|
-
const target =
|
|
7800
|
-
if (
|
|
7801
|
-
const source =
|
|
6563
|
+
const target = resolve5(specialistsDir, filename);
|
|
6564
|
+
if (existsSync5(target)) continue;
|
|
6565
|
+
const source = resolve5(bundleAgentsDir, filename);
|
|
7802
6566
|
try {
|
|
7803
6567
|
cpSync(source, target);
|
|
7804
6568
|
} catch (err) {
|
|
@@ -7806,7 +6570,7 @@ function autoDeliverBundleAgents(accountDir, purchasedPlugins) {
|
|
|
7806
6570
|
continue;
|
|
7807
6571
|
}
|
|
7808
6572
|
try {
|
|
7809
|
-
const content =
|
|
6573
|
+
const content = readFileSync6(target, "utf-8");
|
|
7810
6574
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
7811
6575
|
if (fmMatch) {
|
|
7812
6576
|
const nameMatch = fmMatch[1].match(/^name:\s*(.+)/m);
|
|
@@ -7830,7 +6594,7 @@ function autoDeliverBundleAgents(accountDir, purchasedPlugins) {
|
|
|
7830
6594
|
}
|
|
7831
6595
|
if (delivered > 0) {
|
|
7832
6596
|
try {
|
|
7833
|
-
|
|
6597
|
+
writeFileSync5(agentsmdPath, agentsmd);
|
|
7834
6598
|
console.log(`${TAG19} AGENTS.md updated (${delivered} agents added)`);
|
|
7835
6599
|
} catch (err) {
|
|
7836
6600
|
console.error(`${TAG19} AGENTS.md update failed \u2014 ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -7838,11 +6602,11 @@ function autoDeliverBundleAgents(accountDir, purchasedPlugins) {
|
|
|
7838
6602
|
}
|
|
7839
6603
|
}
|
|
7840
6604
|
function assemblePublicPluginContent(pluginDir) {
|
|
7841
|
-
const pluginRoot =
|
|
7842
|
-
const pluginPath =
|
|
6605
|
+
const pluginRoot = resolve5(PLATFORM_ROOT3, "plugins", pluginDir);
|
|
6606
|
+
const pluginPath = resolve5(pluginRoot, "PLUGIN.md");
|
|
7843
6607
|
let raw2;
|
|
7844
6608
|
try {
|
|
7845
|
-
raw2 =
|
|
6609
|
+
raw2 = readFileSync6(pluginPath, "utf-8");
|
|
7846
6610
|
} catch {
|
|
7847
6611
|
return null;
|
|
7848
6612
|
}
|
|
@@ -7851,7 +6615,7 @@ function assemblePublicPluginContent(pluginDir) {
|
|
|
7851
6615
|
const parts = [pluginBody];
|
|
7852
6616
|
let skillCount = 0;
|
|
7853
6617
|
let refCount = 0;
|
|
7854
|
-
const skillsDir =
|
|
6618
|
+
const skillsDir = resolve5(pluginRoot, "skills");
|
|
7855
6619
|
let skillDirs;
|
|
7856
6620
|
try {
|
|
7857
6621
|
skillDirs = readdirSync2(skillsDir).sort();
|
|
@@ -7859,11 +6623,11 @@ function assemblePublicPluginContent(pluginDir) {
|
|
|
7859
6623
|
return { body: pluginBody, skillCount: 0, refCount: 0 };
|
|
7860
6624
|
}
|
|
7861
6625
|
for (const skillName of skillDirs) {
|
|
7862
|
-
const skillDir =
|
|
7863
|
-
const skillMdPath =
|
|
6626
|
+
const skillDir = resolve5(skillsDir, skillName);
|
|
6627
|
+
const skillMdPath = resolve5(skillDir, "SKILL.md");
|
|
7864
6628
|
let skillRaw;
|
|
7865
6629
|
try {
|
|
7866
|
-
skillRaw =
|
|
6630
|
+
skillRaw = readFileSync6(skillMdPath, "utf-8");
|
|
7867
6631
|
} catch {
|
|
7868
6632
|
continue;
|
|
7869
6633
|
}
|
|
@@ -7903,7 +6667,7 @@ function assemblePublicPluginContent(pluginDir) {
|
|
|
7903
6667
|
parts.push(`
|
|
7904
6668
|
<!-- skill: ${skillName} -->`);
|
|
7905
6669
|
parts.push(skillBody);
|
|
7906
|
-
const refsDir =
|
|
6670
|
+
const refsDir = resolve5(skillDir, "references");
|
|
7907
6671
|
let refFiles;
|
|
7908
6672
|
try {
|
|
7909
6673
|
refFiles = readdirSync2(refsDir).filter((f) => f.endsWith(".md")).filter((f) => !publicExcludeReferences.includes(f)).sort();
|
|
@@ -7914,7 +6678,7 @@ function assemblePublicPluginContent(pluginDir) {
|
|
|
7914
6678
|
}
|
|
7915
6679
|
for (const refFile of refFiles) {
|
|
7916
6680
|
try {
|
|
7917
|
-
const refContent =
|
|
6681
|
+
const refContent = readFileSync6(resolve5(refsDir, refFile), "utf-8").trim();
|
|
7918
6682
|
if (refContent) {
|
|
7919
6683
|
parts.push(`
|
|
7920
6684
|
<!-- reference: ${refFile} -->`);
|
|
@@ -7932,7 +6696,7 @@ function assemblePublicPluginContent(pluginDir) {
|
|
|
7932
6696
|
return { body: parts.join("\n"), skillCount, refCount };
|
|
7933
6697
|
}
|
|
7934
6698
|
function loadEmbeddedPlugins(agentType, selectedPlugins, enabledPlugins) {
|
|
7935
|
-
const pluginsDir =
|
|
6699
|
+
const pluginsDir = resolve5(PLATFORM_ROOT3, "plugins");
|
|
7936
6700
|
let dirs;
|
|
7937
6701
|
try {
|
|
7938
6702
|
dirs = readdirSync2(pluginsDir);
|
|
@@ -7985,10 +6749,10 @@ function loadEmbeddedPlugins(agentType, selectedPlugins, enabledPlugins) {
|
|
|
7985
6749
|
console.log(`[plugins] loaded ${dir} for public (${assembled.body.length} chars, ${assembled.skillCount} skills, ${assembled.refCount} refs)`);
|
|
7986
6750
|
}
|
|
7987
6751
|
} else {
|
|
7988
|
-
const pluginPath =
|
|
6752
|
+
const pluginPath = resolve5(pluginsDir, dir, "PLUGIN.md");
|
|
7989
6753
|
let raw2;
|
|
7990
6754
|
try {
|
|
7991
|
-
raw2 =
|
|
6755
|
+
raw2 = readFileSync6(pluginPath, "utf-8");
|
|
7992
6756
|
} catch (err) {
|
|
7993
6757
|
console.warn(`[plugins] ${dir}: failed to read PLUGIN.md for ${agentType} embed: ${String(err)}`);
|
|
7994
6758
|
continue;
|
|
@@ -8012,14 +6776,14 @@ var mcpToolsCache = /* @__PURE__ */ new Map();
|
|
|
8012
6776
|
function fetchMcpToolsList(pluginDir) {
|
|
8013
6777
|
const cached = mcpToolsCache.get(pluginDir);
|
|
8014
6778
|
if (cached) return Promise.resolve(cached);
|
|
8015
|
-
const serverPath =
|
|
8016
|
-
if (!
|
|
6779
|
+
const serverPath = resolve5(PLATFORM_ROOT3, "plugins", pluginDir, "mcp/dist/index.js");
|
|
6780
|
+
if (!existsSync5(serverPath)) return Promise.resolve([]);
|
|
8017
6781
|
const startMs = Date.now();
|
|
8018
6782
|
return new Promise((resolvePromise) => {
|
|
8019
6783
|
const proc = spawn2(process.execPath, [serverPath], {
|
|
8020
6784
|
env: {
|
|
8021
6785
|
...process.env,
|
|
8022
|
-
PLATFORM_ROOT:
|
|
6786
|
+
PLATFORM_ROOT: PLATFORM_ROOT3,
|
|
8023
6787
|
ACCOUNT_ID: "__toolslist__",
|
|
8024
6788
|
PLATFORM_PORT: process.env.PORT ?? "19200"
|
|
8025
6789
|
}
|
|
@@ -8125,7 +6889,7 @@ var SPECIALIST_PLUGIN_DOMAINS = {
|
|
|
8125
6889
|
// agent, so it retains a full manifest entry for routing clarity.
|
|
8126
6890
|
};
|
|
8127
6891
|
async function buildPluginManifest(enabledPlugins) {
|
|
8128
|
-
const pluginsDir =
|
|
6892
|
+
const pluginsDir = resolve5(PLATFORM_ROOT3, "plugins");
|
|
8129
6893
|
let dirs;
|
|
8130
6894
|
try {
|
|
8131
6895
|
dirs = readdirSync2(pluginsDir);
|
|
@@ -8212,16 +6976,16 @@ ${specialist}: ${plugins.join(", ")}`);
|
|
|
8212
6976
|
for (let j = 0; j < adminPlugins.length; j++) {
|
|
8213
6977
|
const { dir, parsed } = adminPlugins[j];
|
|
8214
6978
|
const mcpTools = adminMcpResults[j];
|
|
8215
|
-
const pluginRoot =
|
|
6979
|
+
const pluginRoot = resolve5(pluginsDir, dir);
|
|
8216
6980
|
const skills = [];
|
|
8217
6981
|
const references = [];
|
|
8218
6982
|
const scanDir = (base, prefix, target) => {
|
|
8219
|
-
const scanPath =
|
|
8220
|
-
if (!
|
|
6983
|
+
const scanPath = resolve5(pluginRoot, base);
|
|
6984
|
+
if (!existsSync5(scanPath)) return;
|
|
8221
6985
|
try {
|
|
8222
6986
|
const walk = (current, rel) => {
|
|
8223
6987
|
for (const entry of readdirSync2(current)) {
|
|
8224
|
-
const full =
|
|
6988
|
+
const full = resolve5(current, entry);
|
|
8225
6989
|
try {
|
|
8226
6990
|
const stat5 = statSync3(full);
|
|
8227
6991
|
if (stat5.isDirectory()) {
|
|
@@ -8249,8 +7013,8 @@ ${specialist}: ${plugins.join(", ")}`);
|
|
|
8249
7013
|
toolLines.push(desc ? ` ${tool.name} \u2014 ${desc}` : ` ${tool.name}`);
|
|
8250
7014
|
}
|
|
8251
7015
|
} else if (parsed.tools.length > 0) {
|
|
8252
|
-
const serverPath =
|
|
8253
|
-
if (
|
|
7016
|
+
const serverPath = resolve5(PLATFORM_ROOT3, "plugins", dir, "mcp/dist/index.js");
|
|
7017
|
+
if (existsSync5(serverPath)) {
|
|
8254
7018
|
fallbackSourced++;
|
|
8255
7019
|
console.error(`[plugin-manifest] ${dir}: tools/list empty \u2014 fallback to frontmatter (${parsed.tools.length} tools)`);
|
|
8256
7020
|
}
|
|
@@ -8325,16 +7089,16 @@ function getDefaultAccountId() {
|
|
|
8325
7089
|
return resolveAccount()?.accountId ?? null;
|
|
8326
7090
|
}
|
|
8327
7091
|
function resolveUserAccounts(userId) {
|
|
8328
|
-
if (!
|
|
7092
|
+
if (!existsSync5(ACCOUNTS_DIR)) return [];
|
|
8329
7093
|
const results = [];
|
|
8330
7094
|
const entries = readdirSync2(ACCOUNTS_DIR, { withFileTypes: true });
|
|
8331
7095
|
for (const entry of entries) {
|
|
8332
7096
|
if (!entry.isDirectory()) continue;
|
|
8333
|
-
const configPath2 =
|
|
8334
|
-
if (!
|
|
7097
|
+
const configPath2 = resolve5(ACCOUNTS_DIR, entry.name, "account.json");
|
|
7098
|
+
if (!existsSync5(configPath2)) continue;
|
|
8335
7099
|
let config;
|
|
8336
7100
|
try {
|
|
8337
|
-
config = JSON.parse(
|
|
7101
|
+
config = JSON.parse(readFileSync6(configPath2, "utf-8"));
|
|
8338
7102
|
} catch {
|
|
8339
7103
|
console.error(`[session] account.json corrupt at ${configPath2} \u2014 skipping`);
|
|
8340
7104
|
continue;
|
|
@@ -8343,7 +7107,7 @@ function resolveUserAccounts(userId) {
|
|
|
8343
7107
|
if (adminEntry) {
|
|
8344
7108
|
results.push({
|
|
8345
7109
|
accountId: config.accountId,
|
|
8346
|
-
accountDir:
|
|
7110
|
+
accountDir: resolve5(ACCOUNTS_DIR, entry.name),
|
|
8347
7111
|
config,
|
|
8348
7112
|
role: adminEntry.role
|
|
8349
7113
|
});
|
|
@@ -8404,8 +7168,75 @@ function clearSessionHistory(sessionKey) {
|
|
|
8404
7168
|
session.stalledSubagents = void 0;
|
|
8405
7169
|
session.pendingTrimmedMessages = void 0;
|
|
8406
7170
|
session.pendingCommitmentOffers = void 0;
|
|
7171
|
+
session.pendingTurns = void 0;
|
|
8407
7172
|
return previousConversationId;
|
|
8408
7173
|
}
|
|
7174
|
+
function bufferPendingTurn(sessionKey, turn) {
|
|
7175
|
+
const session = sessionStore.get(sessionKey);
|
|
7176
|
+
if (!session) {
|
|
7177
|
+
console.error(`[conversation-gate] bufferPendingTurn: session not found sessionKey=${sessionKey.slice(0, 8)}\u2026`);
|
|
7178
|
+
return;
|
|
7179
|
+
}
|
|
7180
|
+
if (!session.pendingTurns) session.pendingTurns = [];
|
|
7181
|
+
session.pendingTurns.push(turn);
|
|
7182
|
+
console.log(`[conversation-gate] ${(/* @__PURE__ */ new Date()).toISOString()} buffered sessionKey=${sessionKey.slice(0, 8)} role=${turn.role} turnCount=${session.pendingTurns.filter((t) => t.role === "user").length}`);
|
|
7183
|
+
}
|
|
7184
|
+
function getPendingTurnCount(sessionKey) {
|
|
7185
|
+
const buf = sessionStore.get(sessionKey)?.pendingTurns;
|
|
7186
|
+
if (!buf) return 0;
|
|
7187
|
+
let n = 0;
|
|
7188
|
+
for (const t of buf) if (t.role === "user") n++;
|
|
7189
|
+
return n;
|
|
7190
|
+
}
|
|
7191
|
+
function drainPendingTurns(sessionKey) {
|
|
7192
|
+
const session = sessionStore.get(sessionKey);
|
|
7193
|
+
if (!session?.pendingTurns || session.pendingTurns.length === 0) return void 0;
|
|
7194
|
+
const drained = session.pendingTurns;
|
|
7195
|
+
session.pendingTurns = void 0;
|
|
7196
|
+
return drained;
|
|
7197
|
+
}
|
|
7198
|
+
async function maybeFlushConversationBuffer(sessionKey, agentType, accountId) {
|
|
7199
|
+
const session = sessionStore.get(sessionKey);
|
|
7200
|
+
if (!session) return null;
|
|
7201
|
+
if (session.conversationId) return session.conversationId;
|
|
7202
|
+
if (getPendingTurnCount(sessionKey) < 2) return null;
|
|
7203
|
+
if (session.flushInFlight) return session.flushInFlight;
|
|
7204
|
+
const attempt = (async () => {
|
|
7205
|
+
let conversationId = null;
|
|
7206
|
+
if (agentType === "admin") {
|
|
7207
|
+
const userId = session.userId;
|
|
7208
|
+
if (!userId) {
|
|
7209
|
+
console.error(`[conversation-gate] flush aborted: admin session missing userId sessionKey=${sessionKey.slice(0, 8)}\u2026`);
|
|
7210
|
+
return null;
|
|
7211
|
+
}
|
|
7212
|
+
conversationId = await createNewAdminConversation(userId, accountId, sessionKey);
|
|
7213
|
+
} else {
|
|
7214
|
+
conversationId = await ensureConversation(accountId, "public", sessionKey, void 0, void 0, void 0);
|
|
7215
|
+
}
|
|
7216
|
+
if (!conversationId) return null;
|
|
7217
|
+
session.conversationId = conversationId;
|
|
7218
|
+
const buffered = drainPendingTurns(sessionKey) ?? [];
|
|
7219
|
+
for (const turn of buffered) {
|
|
7220
|
+
persistMessage(conversationId, turn.role, turn.content, accountId, turn.tokens, turn.timestamp, turn.sender).catch((err) => {
|
|
7221
|
+
console.error(`[conversation-gate] replay persistMessage failed role=${turn.role}: ${err instanceof Error ? err.message : String(err)}`);
|
|
7222
|
+
});
|
|
7223
|
+
}
|
|
7224
|
+
console.log(`[conversation-gate] ${(/* @__PURE__ */ new Date()).toISOString()} flushed sessionKey=${sessionKey.slice(0, 8)} conversationId=${conversationId.slice(0, 8)} bufferedMessages=${buffered.length} agentType=${agentType}`);
|
|
7225
|
+
return conversationId;
|
|
7226
|
+
})();
|
|
7227
|
+
session.flushInFlight = attempt;
|
|
7228
|
+
try {
|
|
7229
|
+
return await attempt;
|
|
7230
|
+
} finally {
|
|
7231
|
+
if (session.flushInFlight === attempt) session.flushInFlight = void 0;
|
|
7232
|
+
}
|
|
7233
|
+
}
|
|
7234
|
+
function isDmChannelSessionKey(sessionKey) {
|
|
7235
|
+
return sessionKey.startsWith("whatsapp:") || sessionKey.startsWith("telegram:");
|
|
7236
|
+
}
|
|
7237
|
+
function preflushStreamLogKey(sessionKey) {
|
|
7238
|
+
return `preflush-${sessionKey.slice(0, 12)}`;
|
|
7239
|
+
}
|
|
8409
7240
|
function getAgentNameForSession(sessionKey) {
|
|
8410
7241
|
return sessionStore.get(sessionKey)?.agentName;
|
|
8411
7242
|
}
|
|
@@ -8560,8 +7391,8 @@ function consumeStalledSubagents(sessionKey) {
|
|
|
8560
7391
|
return stalls && stalls.length > 0 ? stalls : void 0;
|
|
8561
7392
|
}
|
|
8562
7393
|
function streamLogPathFor(accountId, conversationId) {
|
|
8563
|
-
const logDir =
|
|
8564
|
-
const streamLogPath =
|
|
7394
|
+
const logDir = resolve5(ACCOUNTS_DIR, accountId, "logs");
|
|
7395
|
+
const streamLogPath = resolve5(logDir, `claude-agent-stream-${conversationId}.log`);
|
|
8565
7396
|
return { logDir, streamLogPath };
|
|
8566
7397
|
}
|
|
8567
7398
|
function buildSpawnEnv(accountId, accountDir, conversationId) {
|
|
@@ -8571,7 +7402,7 @@ function buildSpawnEnv(accountId, accountDir, conversationId) {
|
|
|
8571
7402
|
const { logDir, streamLogPath } = streamLogPathFor(accountId, conversationId);
|
|
8572
7403
|
return {
|
|
8573
7404
|
...process.env,
|
|
8574
|
-
PLATFORM_ROOT:
|
|
7405
|
+
PLATFORM_ROOT: PLATFORM_ROOT3,
|
|
8575
7406
|
ACCOUNT_DIR: accountDir,
|
|
8576
7407
|
ACCOUNT_ID: accountId,
|
|
8577
7408
|
LOG_DIR: logDir,
|
|
@@ -8582,8 +7413,8 @@ var cachedBrandHostname = null;
|
|
|
8582
7413
|
function readBrandHostname() {
|
|
8583
7414
|
if (cachedBrandHostname !== null) return cachedBrandHostname;
|
|
8584
7415
|
try {
|
|
8585
|
-
const brandPath =
|
|
8586
|
-
const parsed = JSON.parse(
|
|
7416
|
+
const brandPath = resolve5(PLATFORM_ROOT3, "config", "brand.json");
|
|
7417
|
+
const parsed = JSON.parse(readFileSync6(brandPath, "utf-8"));
|
|
8587
7418
|
cachedBrandHostname = typeof parsed.hostname === "string" && parsed.hostname.length > 0 ? parsed.hostname : "maxy";
|
|
8588
7419
|
} catch {
|
|
8589
7420
|
cachedBrandHostname = "maxy";
|
|
@@ -8612,7 +7443,7 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
8612
7443
|
const { logDir: LOG_DIR2, streamLogPath: STREAM_LOG_PATH } = streamLogPathFor(accountId, conversationId);
|
|
8613
7444
|
const baseEnv = {
|
|
8614
7445
|
ACCOUNT_ID: accountId,
|
|
8615
|
-
PLATFORM_ROOT:
|
|
7446
|
+
PLATFORM_ROOT: PLATFORM_ROOT3,
|
|
8616
7447
|
LOG_DIR: LOG_DIR2,
|
|
8617
7448
|
STREAM_LOG_PATH,
|
|
8618
7449
|
NEO4J_URI: requireNeo4jUri()
|
|
@@ -8620,37 +7451,37 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
8620
7451
|
const servers = {
|
|
8621
7452
|
"memory": {
|
|
8622
7453
|
command: "node",
|
|
8623
|
-
args: [
|
|
7454
|
+
args: [resolve5(PLATFORM_ROOT3, "plugins/memory/mcp/dist/index.js")],
|
|
8624
7455
|
env: { ...baseEnv, ...userId ? { USER_ID: userId } : {} }
|
|
8625
7456
|
},
|
|
8626
7457
|
"contacts": {
|
|
8627
7458
|
command: "node",
|
|
8628
|
-
args: [
|
|
7459
|
+
args: [resolve5(PLATFORM_ROOT3, "plugins/contacts/mcp/dist/index.js")],
|
|
8629
7460
|
env: { ...baseEnv }
|
|
8630
7461
|
},
|
|
8631
7462
|
"whatsapp": {
|
|
8632
7463
|
command: "node",
|
|
8633
|
-
args: [
|
|
7464
|
+
args: [resolve5(PLATFORM_ROOT3, "plugins/whatsapp/mcp/dist/index.js")],
|
|
8634
7465
|
env: { ...baseEnv, PLATFORM_PORT: process.env.PORT ?? "19200" }
|
|
8635
7466
|
},
|
|
8636
7467
|
"admin": {
|
|
8637
7468
|
command: "node",
|
|
8638
|
-
args: [
|
|
7469
|
+
args: [resolve5(PLATFORM_ROOT3, "plugins/admin/mcp/dist/index.js")],
|
|
8639
7470
|
env: { ...baseEnv, PLATFORM_PORT: process.env.PORT ?? "19200", ...userId ? { USER_ID: userId } : {} }
|
|
8640
7471
|
},
|
|
8641
7472
|
"scheduling": {
|
|
8642
7473
|
command: "node",
|
|
8643
|
-
args: [
|
|
7474
|
+
args: [resolve5(PLATFORM_ROOT3, "plugins/scheduling/mcp/dist/index.js")],
|
|
8644
7475
|
env: { ...baseEnv }
|
|
8645
7476
|
},
|
|
8646
7477
|
"tasks": {
|
|
8647
7478
|
command: "node",
|
|
8648
|
-
args: [
|
|
7479
|
+
args: [resolve5(PLATFORM_ROOT3, "plugins/tasks/mcp/dist/index.js")],
|
|
8649
7480
|
env: { ...baseEnv }
|
|
8650
7481
|
},
|
|
8651
7482
|
"email": {
|
|
8652
7483
|
command: "node",
|
|
8653
|
-
args: [
|
|
7484
|
+
args: [resolve5(PLATFORM_ROOT3, "plugins/email/mcp/dist/index.js")],
|
|
8654
7485
|
env: { ...baseEnv }
|
|
8655
7486
|
},
|
|
8656
7487
|
// Workflows MCP — persistent admin-session server for list/get/update/delete/
|
|
@@ -8661,7 +7492,7 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
8661
7492
|
// ToolSearches fruitlessly before degrading to a task-create stand-in (Task 571).
|
|
8662
7493
|
"workflows": {
|
|
8663
7494
|
command: "node",
|
|
8664
|
-
args: [
|
|
7495
|
+
args: [resolve5(PLATFORM_ROOT3, "plugins/workflows/mcp/dist/index.js")],
|
|
8665
7496
|
env: { ...baseEnv }
|
|
8666
7497
|
},
|
|
8667
7498
|
// Playwright MCP server — browser automation for browser-specialist.
|
|
@@ -8683,7 +7514,7 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
8683
7514
|
// MAXY-PRD.md:627, not in any application-layer filter).
|
|
8684
7515
|
"graph": {
|
|
8685
7516
|
command: "node",
|
|
8686
|
-
args: [
|
|
7517
|
+
args: [resolve5(PLATFORM_ROOT3, "lib/graph-mcp/dist/index.js")],
|
|
8687
7518
|
env: {
|
|
8688
7519
|
...baseEnv,
|
|
8689
7520
|
BRAND: readBrandHostname(),
|
|
@@ -8699,7 +7530,7 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
8699
7530
|
if (tgBotToken) {
|
|
8700
7531
|
servers["telegram"] = {
|
|
8701
7532
|
command: "node",
|
|
8702
|
-
args: [
|
|
7533
|
+
args: [resolve5(PLATFORM_ROOT3, "plugins/telegram/mcp/dist/index.js")],
|
|
8703
7534
|
env: { ...baseEnv, TELEGRAM_BOT_TOKEN: tgBotToken }
|
|
8704
7535
|
};
|
|
8705
7536
|
} else {
|
|
@@ -8707,11 +7538,11 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
8707
7538
|
}
|
|
8708
7539
|
servers["cloudflare"] = {
|
|
8709
7540
|
command: "node",
|
|
8710
|
-
args: [
|
|
7541
|
+
args: [resolve5(PLATFORM_ROOT3, "plugins/cloudflare/mcp/dist/index.js")],
|
|
8711
7542
|
env: { ...baseEnv, PLATFORM_PORT: process.env.PORT ?? "19200" }
|
|
8712
7543
|
};
|
|
8713
7544
|
if (Array.isArray(enabledPlugins) && enabledPlugins.length > 0) {
|
|
8714
|
-
const pluginsDir =
|
|
7545
|
+
const pluginsDir = resolve5(PLATFORM_ROOT3, "plugins");
|
|
8715
7546
|
let dirs;
|
|
8716
7547
|
try {
|
|
8717
7548
|
dirs = readdirSync2(pluginsDir);
|
|
@@ -8734,8 +7565,8 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
8734
7565
|
continue;
|
|
8735
7566
|
}
|
|
8736
7567
|
}
|
|
8737
|
-
const mcpEntry =
|
|
8738
|
-
if (!
|
|
7568
|
+
const mcpEntry = resolve5(PLATFORM_ROOT3, "plugins", dir, "mcp/dist/index.js");
|
|
7569
|
+
if (!existsSync5(mcpEntry)) continue;
|
|
8739
7570
|
servers[dir] = {
|
|
8740
7571
|
command: "node",
|
|
8741
7572
|
args: [mcpEntry],
|
|
@@ -8870,7 +7701,7 @@ var ADMIN_CORE_TOOLS = [
|
|
|
8870
7701
|
function getAdminAllowedTools(enabledPlugins) {
|
|
8871
7702
|
const tools = [...ADMIN_CORE_TOOLS];
|
|
8872
7703
|
if (Array.isArray(enabledPlugins) && enabledPlugins.length > 0) {
|
|
8873
|
-
const pluginsDir =
|
|
7704
|
+
const pluginsDir = resolve5(PLATFORM_ROOT3, "plugins");
|
|
8874
7705
|
let dirs;
|
|
8875
7706
|
try {
|
|
8876
7707
|
dirs = readdirSync2(pluginsDir);
|
|
@@ -8978,18 +7809,18 @@ ${message.slice(0, QUERY_CLASSIFIER_MSG_CAP)}`
|
|
|
8978
7809
|
}
|
|
8979
7810
|
}
|
|
8980
7811
|
async function fetchMemoryContext(accountId, query, sessionKey, options) {
|
|
8981
|
-
const serverPath =
|
|
8982
|
-
if (!
|
|
7812
|
+
const serverPath = resolve5(PLATFORM_ROOT3, "plugins/memory/mcp/dist/index.js");
|
|
7813
|
+
if (!existsSync5(serverPath)) {
|
|
8983
7814
|
console.error(`[fetchMemoryContext] MCP server not found: ${serverPath}`);
|
|
8984
7815
|
return null;
|
|
8985
7816
|
}
|
|
8986
7817
|
const startMs = Date.now();
|
|
8987
|
-
return new Promise((
|
|
7818
|
+
return new Promise((resolve28) => {
|
|
8988
7819
|
const proc = spawn2(process.execPath, [serverPath], {
|
|
8989
7820
|
env: {
|
|
8990
7821
|
...process.env,
|
|
8991
7822
|
ACCOUNT_ID: accountId,
|
|
8992
|
-
PLATFORM_ROOT:
|
|
7823
|
+
PLATFORM_ROOT: PLATFORM_ROOT3,
|
|
8993
7824
|
READ_ONLY: "true",
|
|
8994
7825
|
ALLOWED_SCOPES: "public,shared",
|
|
8995
7826
|
...sessionKey ? { SESSION_ID: sessionKey } : {},
|
|
@@ -9013,7 +7844,7 @@ async function fetchMemoryContext(accountId, query, sessionKey, options) {
|
|
|
9013
7844
|
} else {
|
|
9014
7845
|
console.error(`[fetchMemoryContext] failed: ${reason} (${elapsed}ms)${stderrBuf ? ` stderr: ${stderrBuf.slice(0, 500)}` : ""}`);
|
|
9015
7846
|
}
|
|
9016
|
-
|
|
7847
|
+
resolve28(value);
|
|
9017
7848
|
};
|
|
9018
7849
|
proc.stdout.on("data", (chunk) => {
|
|
9019
7850
|
buffer += chunk.toString();
|
|
@@ -9076,8 +7907,8 @@ async function fetchMemoryContext(accountId, query, sessionKey, options) {
|
|
|
9076
7907
|
}
|
|
9077
7908
|
async function compactTrimmedMessages(accountId, trimmedMessages) {
|
|
9078
7909
|
if (trimmedMessages.length === 0) return true;
|
|
9079
|
-
const serverPath =
|
|
9080
|
-
if (!
|
|
7910
|
+
const serverPath = resolve5(PLATFORM_ROOT3, "plugins/memory/mcp/dist/index.js");
|
|
7911
|
+
if (!existsSync5(serverPath)) return false;
|
|
9081
7912
|
const briefing = trimmedMessages.map((m) => `[${m.role.toUpperCase()}] ${m.content}`).join("\n\n");
|
|
9082
7913
|
return new Promise((resolvePromise) => {
|
|
9083
7914
|
const proc = spawn2(process.execPath, [serverPath], {
|
|
@@ -9394,8 +8225,8 @@ Then respond with only: [COMPACTED]`;
|
|
|
9394
8225
|
var COMPACTION_TIMEOUT_MS = 45e3;
|
|
9395
8226
|
async function* runCompactionTurn(accountDir, accountId, systemPrompt, resumeSessionId, adminModel, conversationId, enabledPlugins) {
|
|
9396
8227
|
const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, conversationId, void 0, enabledPlugins) });
|
|
9397
|
-
const specialistsDir =
|
|
9398
|
-
if (!
|
|
8228
|
+
const specialistsDir = resolve5(accountDir, "specialists");
|
|
8229
|
+
if (!existsSync5(specialistsDir)) agentLogStream("claude-agent-compaction-stream", accountDir, conversationId).write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
|
|
9399
8230
|
`);
|
|
9400
8231
|
const args = [
|
|
9401
8232
|
"--print",
|
|
@@ -9668,7 +8499,7 @@ async function* parseClaudeStream(proc, streamLog, adminModel, conversationId, a
|
|
|
9668
8499
|
const { logDir } = streamLogPathFor(accountId, conversationId);
|
|
9669
8500
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
9670
8501
|
for (const s of failed) {
|
|
9671
|
-
const stderrPath =
|
|
8502
|
+
const stderrPath = resolve5(logDir, `mcp-${s.name}-stderr-${date}.log`);
|
|
9672
8503
|
let tail = "(no stderr file)";
|
|
9673
8504
|
try {
|
|
9674
8505
|
const stats = statSync3(stderrPath);
|
|
@@ -10298,20 +9129,24 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
|
|
|
10298
9129
|
const userTimestamp = clientTimestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
10299
9130
|
const resumeSessionId = sessionKey ? getAgentSessionId(sessionKey) : void 0;
|
|
10300
9131
|
const spawnConvId = sessionKey ? getConversationIdForSession(sessionKey) : void 0;
|
|
10301
|
-
if (!spawnConvId) {
|
|
10302
|
-
throw new Error(`invokeAdminAgent: conversationId missing for sessionKey=${sessionKey
|
|
9132
|
+
if (!spawnConvId && sessionKey && getPendingTurnCount(sessionKey) >= 2) {
|
|
9133
|
+
throw new Error(`invokeAdminAgent: conversationId missing post-flush for sessionKey=${sessionKey.slice(0, 8)} \u2014 maybeFlushConversationBuffer must bind it before invoking the agent`);
|
|
9134
|
+
}
|
|
9135
|
+
const spawnLogKey = spawnConvId ?? (sessionKey ? preflushStreamLogKey(sessionKey) : void 0);
|
|
9136
|
+
if (!spawnLogKey) {
|
|
9137
|
+
throw new Error(`invokeAdminAgent: sessionKey required \u2014 cannot resolve log stream without one`);
|
|
10303
9138
|
}
|
|
10304
9139
|
const cdpOk = await ensureCdp();
|
|
10305
9140
|
if (!cdpOk) {
|
|
10306
|
-
const cdpLog = agentLogStream("claude-agent-stream", accountDir,
|
|
9141
|
+
const cdpLog = agentLogStream("claude-agent-stream", accountDir, spawnLogKey);
|
|
10307
9142
|
cdpLog.write(`[${isoTs()}] [warn] ensureCdp failed \u2014 browser-specialist degraded
|
|
10308
9143
|
`);
|
|
10309
9144
|
cdpLog.end();
|
|
10310
9145
|
}
|
|
10311
9146
|
const ccUserId = sessionKey ? getUserIdForSession(sessionKey) : void 0;
|
|
10312
|
-
const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId,
|
|
10313
|
-
const specialistsDir =
|
|
10314
|
-
if (!
|
|
9147
|
+
const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, spawnLogKey, ccUserId, enabledPlugins) });
|
|
9148
|
+
const specialistsDir = resolve5(accountDir, "specialists");
|
|
9149
|
+
if (!existsSync5(specialistsDir)) agentLogStream("claude-agent-stream", accountDir, spawnLogKey).write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
|
|
10315
9150
|
`);
|
|
10316
9151
|
const args = [
|
|
10317
9152
|
"--print",
|
|
@@ -10342,19 +9177,19 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
|
|
|
10342
9177
|
cwd: accountDir,
|
|
10343
9178
|
stdio: ["ignore", "pipe", "pipe"],
|
|
10344
9179
|
// Task 556: STREAM_LOG_PATH inherited by Bash-tool subprocesses.
|
|
10345
|
-
env: buildSpawnEnv(accountId, accountDir,
|
|
9180
|
+
env: buildSpawnEnv(accountId, accountDir, spawnLogKey)
|
|
10346
9181
|
});
|
|
10347
|
-
const stderrLog = agentLogStream("claude-agent-stderr", accountDir,
|
|
9182
|
+
const stderrLog = agentLogStream("claude-agent-stderr", accountDir, spawnLogKey);
|
|
10348
9183
|
stderrLog.on("error", () => {
|
|
10349
9184
|
});
|
|
10350
9185
|
proc.stderr?.pipe(stderrLog);
|
|
10351
|
-
const streamLog = agentLogStream("claude-agent-stream", accountDir,
|
|
9186
|
+
const streamLog = agentLogStream("claude-agent-stream", accountDir, spawnLogKey);
|
|
10352
9187
|
streamLog.on("error", () => {
|
|
10353
9188
|
});
|
|
10354
9189
|
teeProcStderrToStreamLog(proc, streamLog);
|
|
10355
9190
|
streamLog.write(`[${isoTs()}] [subproc-debug-unavailable] reason=bundled-bun-binary-ignores-node-debug pid=${proc.pid} cli=claude
|
|
10356
9191
|
`);
|
|
10357
|
-
streamLog.write(`[${isoTs()}] [spawn-env] STREAM_LOG_PATH=set pid=${proc.pid} conversationId=${spawnConvId} site=admin
|
|
9192
|
+
streamLog.write(`[${isoTs()}] [spawn-env] STREAM_LOG_PATH=set pid=${proc.pid} conversationId=${spawnConvId ?? "preflush"} logKey=${spawnLogKey} site=admin
|
|
10358
9193
|
`);
|
|
10359
9194
|
if (sessionKey) {
|
|
10360
9195
|
const prev = activeProcesses.get(sessionKey);
|
|
@@ -10364,7 +9199,7 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
|
|
|
10364
9199
|
}
|
|
10365
9200
|
activeProcesses.set(sessionKey, { pid: proc.pid, spawnedAt: Date.now() });
|
|
10366
9201
|
}
|
|
10367
|
-
streamLog.write(`[${isoTs()}] [spawn] pid=${proc.pid} resume=${resumeSessionId ?? "none"} sessionKey=${sessionKey ?? "none"} conversationId=${spawnConvId} pluginDir=${specialistsDir}
|
|
9202
|
+
streamLog.write(`[${isoTs()}] [spawn] pid=${proc.pid} resume=${resumeSessionId ?? "none"} sessionKey=${sessionKey ?? "none"} conversationId=${spawnConvId ?? "preflush"} logKey=${spawnLogKey} pluginDir=${specialistsDir}
|
|
10368
9203
|
`);
|
|
10369
9204
|
streamLog.write(`[${isoTs()}] [stdin] len=${fullMessage.length} preview=${JSON.stringify(fullMessage.slice(0, 80))}
|
|
10370
9205
|
`);
|
|
@@ -10436,7 +9271,7 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
|
|
|
10436
9271
|
}
|
|
10437
9272
|
if (event.type === "usage" && sessionKey && currentAgentSessionId) {
|
|
10438
9273
|
const peakReqPct = event.peak_request_pct ?? 0;
|
|
10439
|
-
if (peakReqPct >= COMPACTION_THRESHOLD) {
|
|
9274
|
+
if (peakReqPct >= COMPACTION_THRESHOLD && spawnConvId) {
|
|
10440
9275
|
const compactionIter = runCompactionTurn(accountDir, accountId, systemPrompt, currentAgentSessionId, adminModel, spawnConvId, enabledPlugins);
|
|
10441
9276
|
let step = await compactionIter.next();
|
|
10442
9277
|
while (!step.done) {
|
|
@@ -10546,9 +9381,9 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
|
|
|
10546
9381
|
} else {
|
|
10547
9382
|
gotDone = true;
|
|
10548
9383
|
if (!sessionWasReset) {
|
|
9384
|
+
const assistantTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
10549
9385
|
const convId = sessionKey ? sessionStore.get(sessionKey)?.conversationId : void 0;
|
|
10550
9386
|
if (convId) {
|
|
10551
|
-
const assistantTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
10552
9387
|
persistMessage(convId, "user", fullMessage, accountId, void 0, userTimestamp).catch(() => {
|
|
10553
9388
|
});
|
|
10554
9389
|
autoLabelSession(convId, fullMessage).catch(() => {
|
|
@@ -10556,7 +9391,14 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
|
|
|
10556
9391
|
if (responseText) persistMessage(convId, "assistant", responseText, accountId, capturedTokens, assistantTimestamp).catch(() => {
|
|
10557
9392
|
});
|
|
10558
9393
|
} else if (sessionKey) {
|
|
10559
|
-
|
|
9394
|
+
bufferPendingTurn(sessionKey, { role: "user", content: fullMessage, timestamp: userTimestamp });
|
|
9395
|
+
if (responseText) bufferPendingTurn(sessionKey, { role: "assistant", content: responseText, timestamp: assistantTimestamp, tokens: capturedTokens });
|
|
9396
|
+
const flushedId = await maybeFlushConversationBuffer(sessionKey, "admin", accountId);
|
|
9397
|
+
if (flushedId) {
|
|
9398
|
+
autoLabelSession(flushedId, fullMessage).catch(() => {
|
|
9399
|
+
});
|
|
9400
|
+
yield { type: "conversation_attributed", conversationId: flushedId };
|
|
9401
|
+
}
|
|
10560
9402
|
}
|
|
10561
9403
|
if (sessionKey) {
|
|
10562
9404
|
const commitSession = sessionStore.get(sessionKey);
|
|
@@ -10619,9 +9461,10 @@ ${summary}`;
|
|
|
10619
9461
|
async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accountId, adminModel, sessionKey, maxTurns = 20, attachments = [], retryCount = 0, enabledPlugins, clientTimestamp, adherenceConstraints, agentName) {
|
|
10620
9462
|
const userTimestamp = clientTimestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
10621
9463
|
const managedConvId = getConversationIdForSession(sessionKey);
|
|
10622
|
-
if (!managedConvId) {
|
|
10623
|
-
throw new Error(`invokeManagedAdminAgent: conversationId missing for sessionKey=${sessionKey.slice(0, 8)} \u2014
|
|
9464
|
+
if (!managedConvId && getPendingTurnCount(sessionKey) >= 2) {
|
|
9465
|
+
throw new Error(`invokeManagedAdminAgent: conversationId missing post-flush for sessionKey=${sessionKey.slice(0, 8)} \u2014 maybeFlushConversationBuffer must bind it first`);
|
|
10624
9466
|
}
|
|
9467
|
+
const managedLogKey = managedConvId ?? preflushStreamLogKey(sessionKey);
|
|
10625
9468
|
const pendingTrimmed = consumePendingTrimmedMessages(sessionKey);
|
|
10626
9469
|
if (pendingTrimmed && pendingTrimmed.length > 0) {
|
|
10627
9470
|
const ok = await compactTrimmedMessages(accountId, pendingTrimmed);
|
|
@@ -10629,7 +9472,7 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
|
|
|
10629
9472
|
storePendingTrimmedMessages(sessionKey, pendingTrimmed);
|
|
10630
9473
|
}
|
|
10631
9474
|
}
|
|
10632
|
-
const streamLog = agentLogStream("claude-agent-stream", accountDir,
|
|
9475
|
+
const streamLog = agentLogStream("claude-agent-stream", accountDir, managedLogKey);
|
|
10633
9476
|
streamLog.on("error", () => {
|
|
10634
9477
|
});
|
|
10635
9478
|
const systemPromptTokens = estimateTokens(systemPrompt);
|
|
@@ -10660,9 +9503,9 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
|
|
|
10660
9503
|
if (!cdpOk) streamLog.write(`[${isoTs()}] [warn] ensureCdp failed \u2014 browser-specialist degraded
|
|
10661
9504
|
`);
|
|
10662
9505
|
const managedUserId = getUserIdForSession(sessionKey);
|
|
10663
|
-
const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId,
|
|
10664
|
-
const specialistsDir =
|
|
10665
|
-
if (!
|
|
9506
|
+
const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, managedLogKey, managedUserId, enabledPlugins) });
|
|
9507
|
+
const specialistsDir = resolve5(accountDir, "specialists");
|
|
9508
|
+
if (!existsSync5(specialistsDir)) streamLog.write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
|
|
10666
9509
|
`);
|
|
10667
9510
|
const fullMessage = attachments.length > 0 ? message + buildAttachmentMetaText(attachments) : message;
|
|
10668
9511
|
const args = [
|
|
@@ -10691,16 +9534,16 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
|
|
|
10691
9534
|
cwd: accountDir,
|
|
10692
9535
|
stdio: ["ignore", "pipe", "pipe"],
|
|
10693
9536
|
// Task 556: STREAM_LOG_PATH inherited by Bash-tool subprocesses.
|
|
10694
|
-
env: buildSpawnEnv(accountId, accountDir,
|
|
9537
|
+
env: buildSpawnEnv(accountId, accountDir, managedLogKey)
|
|
10695
9538
|
});
|
|
10696
|
-
const stderrLog = agentLogStream("claude-agent-stderr", accountDir,
|
|
9539
|
+
const stderrLog = agentLogStream("claude-agent-stderr", accountDir, managedLogKey);
|
|
10697
9540
|
stderrLog.on("error", () => {
|
|
10698
9541
|
});
|
|
10699
9542
|
proc.stderr?.pipe(stderrLog);
|
|
10700
9543
|
teeProcStderrToStreamLog(proc, streamLog);
|
|
10701
9544
|
streamLog.write(`[${isoTs()}] [subproc-debug-unavailable] reason=bundled-bun-binary-ignores-node-debug pid=${proc.pid} cli=claude
|
|
10702
9545
|
`);
|
|
10703
|
-
streamLog.write(`[${isoTs()}] [spawn-env] STREAM_LOG_PATH=set pid=${proc.pid} conversationId=${managedConvId} site=managed
|
|
9546
|
+
streamLog.write(`[${isoTs()}] [spawn-env] STREAM_LOG_PATH=set pid=${proc.pid} conversationId=${managedConvId ?? "preflush"} logKey=${managedLogKey} site=managed
|
|
10704
9547
|
`);
|
|
10705
9548
|
if (sessionKey) {
|
|
10706
9549
|
const prev = activeProcesses.get(sessionKey);
|
|
@@ -10710,13 +9553,13 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
|
|
|
10710
9553
|
}
|
|
10711
9554
|
activeProcesses.set(sessionKey, { pid: proc.pid, spawnedAt: Date.now() });
|
|
10712
9555
|
}
|
|
10713
|
-
streamLog.write(`[${isoTs()}] [managed-spawn] pid=${proc.pid} sessionKey=${sessionKey} conversationId=${managedConvId} historyMessages=${history.length} pluginDir=${specialistsDir}
|
|
9556
|
+
streamLog.write(`[${isoTs()}] [managed-spawn] pid=${proc.pid} sessionKey=${sessionKey} conversationId=${managedConvId ?? "preflush"} logKey=${managedLogKey} historyMessages=${history.length} pluginDir=${specialistsDir}
|
|
10714
9557
|
`);
|
|
10715
9558
|
streamLog.write(`[${isoTs()}] [stdin] len=${fullMessage.length} preview=${JSON.stringify(fullMessage.slice(0, 80))}
|
|
10716
9559
|
`);
|
|
10717
9560
|
proc.on("exit", (code, signal) => {
|
|
10718
|
-
console.log(`[process-exit] pid=${proc.pid} code=${code} signal=${signal} sessionKey=${sessionKey ?? "none"} conversationId=${managedConvId}`);
|
|
10719
|
-
if (!streamLog.destroyed && !streamLog.writableEnded) streamLog.write(`[${isoTs()}] [process-exit] pid=${proc.pid} code=${code} signal=${signal} conversationId=${managedConvId}
|
|
9561
|
+
console.log(`[process-exit] pid=${proc.pid} code=${code} signal=${signal} sessionKey=${sessionKey ?? "none"} conversationId=${managedConvId ?? "preflush"}`);
|
|
9562
|
+
if (!streamLog.destroyed && !streamLog.writableEnded) streamLog.write(`[${isoTs()}] [process-exit] pid=${proc.pid} code=${code} signal=${signal} conversationId=${managedConvId ?? "preflush"}
|
|
10720
9563
|
`);
|
|
10721
9564
|
if (sessionKey) activeProcesses.delete(sessionKey);
|
|
10722
9565
|
});
|
|
@@ -10866,17 +9709,24 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
|
|
|
10866
9709
|
`);
|
|
10867
9710
|
const successSession = sessionStore.get(sessionKey);
|
|
10868
9711
|
if (successSession) successSession.lastPeakContextPct = peakContextPct;
|
|
9712
|
+
const assistantTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
10869
9713
|
const convId = sessionStore.get(sessionKey)?.conversationId;
|
|
10870
9714
|
if (convId) {
|
|
10871
|
-
const assistantTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
10872
9715
|
persistMessage(convId, "user", fullMessage, accountId, void 0, userTimestamp).catch(() => {
|
|
10873
9716
|
});
|
|
10874
9717
|
autoLabelSession(convId, fullMessage).catch(() => {
|
|
10875
9718
|
});
|
|
10876
9719
|
if (responseText) persistMessage(convId, "assistant", responseText, accountId, capturedTokens, assistantTimestamp).catch(() => {
|
|
10877
9720
|
});
|
|
10878
|
-
} else
|
|
10879
|
-
|
|
9721
|
+
} else {
|
|
9722
|
+
bufferPendingTurn(sessionKey, { role: "user", content: fullMessage, timestamp: userTimestamp });
|
|
9723
|
+
if (responseText) bufferPendingTurn(sessionKey, { role: "assistant", content: responseText, timestamp: assistantTimestamp, tokens: capturedTokens });
|
|
9724
|
+
const flushedId = await maybeFlushConversationBuffer(sessionKey, "admin", accountId);
|
|
9725
|
+
if (flushedId) {
|
|
9726
|
+
autoLabelSession(flushedId, fullMessage).catch(() => {
|
|
9727
|
+
});
|
|
9728
|
+
yield { type: "conversation_attributed", conversationId: flushedId };
|
|
9729
|
+
}
|
|
10880
9730
|
}
|
|
10881
9731
|
const commitSession = sessionStore.get(sessionKey);
|
|
10882
9732
|
if (commitSession?.pendingCommitmentOffers && commitSession.pendingCommitmentOffers.length > 0) {
|
|
@@ -10948,10 +9798,14 @@ async function* invokePublicAgent(message, systemPrompt, accountId, accountDir,
|
|
|
10948
9798
|
return;
|
|
10949
9799
|
}
|
|
10950
9800
|
const publicConvId = sessionKey ? getConversationIdForSession(sessionKey) : void 0;
|
|
10951
|
-
if (!publicConvId) {
|
|
10952
|
-
throw new Error(`invokePublicAgent: conversationId missing for sessionKey=${sessionKey
|
|
9801
|
+
if (!publicConvId && sessionKey && getPendingTurnCount(sessionKey) >= 2) {
|
|
9802
|
+
throw new Error(`invokePublicAgent: conversationId missing post-flush for sessionKey=${sessionKey.slice(0, 8)} \u2014 maybeFlushConversationBuffer must bind it first`);
|
|
9803
|
+
}
|
|
9804
|
+
const publicLogKey = publicConvId ?? (sessionKey ? preflushStreamLogKey(sessionKey) : void 0);
|
|
9805
|
+
if (!publicLogKey) {
|
|
9806
|
+
throw new Error(`invokePublicAgent: sessionKey required \u2014 cannot resolve log stream without one`);
|
|
10953
9807
|
}
|
|
10954
|
-
const streamLog = agentLogStream("public-agent-stream", accountDir,
|
|
9808
|
+
const streamLog = agentLogStream("public-agent-stream", accountDir, publicLogKey);
|
|
10955
9809
|
streamLog.write(`[${isoTs()}] [public-user-message] ${JSON.stringify(message)}
|
|
10956
9810
|
`);
|
|
10957
9811
|
if (sessionKey) {
|
|
@@ -11188,10 +10042,10 @@ User messages are prefixed with the sender's name in brackets. Address participa
|
|
|
11188
10042
|
`);
|
|
11189
10043
|
}
|
|
11190
10044
|
const conversationId = sessionKey ? sessionStore.get(sessionKey)?.conversationId : void 0;
|
|
10045
|
+
const assistantTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
10046
|
+
const sess = sessionKey ? sessionStore.get(sessionKey) : void 0;
|
|
10047
|
+
const sender = sess?.groupSlug && sess.visitorId && sess.senderDisplayName ? { visitorId: sess.visitorId, displayName: sess.senderDisplayName } : void 0;
|
|
11191
10048
|
if (conversationId) {
|
|
11192
|
-
const assistantTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
11193
|
-
const sess = sessionKey ? sessionStore.get(sessionKey) : void 0;
|
|
11194
|
-
const sender = sess?.groupSlug && sess.visitorId && sess.senderDisplayName ? { visitorId: sess.visitorId, displayName: sess.senderDisplayName } : void 0;
|
|
11195
10049
|
persistMessage(conversationId, "user", message, accountId, void 0, userTimestamp, sender).catch(() => {
|
|
11196
10050
|
});
|
|
11197
10051
|
autoLabelSession(conversationId, message).catch(() => {
|
|
@@ -11199,7 +10053,14 @@ User messages are prefixed with the sender's name in brackets. Address participa
|
|
|
11199
10053
|
if (fullText) persistMessage(conversationId, "assistant", fullText, accountId, void 0, assistantTimestamp).catch(() => {
|
|
11200
10054
|
});
|
|
11201
10055
|
} else if (sessionKey) {
|
|
11202
|
-
|
|
10056
|
+
bufferPendingTurn(sessionKey, { role: "user", content: message, timestamp: userTimestamp, sender });
|
|
10057
|
+
if (fullText) bufferPendingTurn(sessionKey, { role: "assistant", content: fullText, timestamp: assistantTimestamp });
|
|
10058
|
+
const flushedId = await maybeFlushConversationBuffer(sessionKey, "public", accountId);
|
|
10059
|
+
if (flushedId) {
|
|
10060
|
+
autoLabelSession(flushedId, message).catch(() => {
|
|
10061
|
+
});
|
|
10062
|
+
yield { type: "conversation_attributed", conversationId: flushedId };
|
|
10063
|
+
}
|
|
11203
10064
|
}
|
|
11204
10065
|
streamLog.end();
|
|
11205
10066
|
}
|
|
@@ -11355,10 +10216,10 @@ ${sessionContext}`;
|
|
|
11355
10216
|
console.log(`[onboarding-inject] accountId=${accountId.slice(0, 8)}\u2026 error=neo4j-unreachable injected=false`);
|
|
11356
10217
|
} else if (onboardingStep < 8) {
|
|
11357
10218
|
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 =
|
|
10219
|
+
const skillPath = resolve5(PLATFORM_ROOT3, "plugins/admin/skills/onboarding/SKILL.md");
|
|
11359
10220
|
let skillContent = "";
|
|
11360
10221
|
try {
|
|
11361
|
-
skillContent =
|
|
10222
|
+
skillContent = readFileSync6(skillPath, "utf-8");
|
|
11362
10223
|
} catch (err) {
|
|
11363
10224
|
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
10225
|
}
|
|
@@ -11408,9 +10269,9 @@ ${body}`;
|
|
|
11408
10269
|
|
|
11409
10270
|
${manifest}`;
|
|
11410
10271
|
}
|
|
11411
|
-
const graphRefPath =
|
|
10272
|
+
const graphRefPath = resolve5(PLATFORM_ROOT3, "plugins/memory/references/graph-primitives.md");
|
|
11412
10273
|
try {
|
|
11413
|
-
const graphRef =
|
|
10274
|
+
const graphRef = readFileSync6(graphRefPath, "utf-8");
|
|
11414
10275
|
baseSystemPrompt += `
|
|
11415
10276
|
|
|
11416
10277
|
${graphRef}`;
|
|
@@ -11496,7 +10357,7 @@ Current session key: ${sessionKey}` : systemPromptBase;
|
|
|
11496
10357
|
|
|
11497
10358
|
${gwParts.join("\n")}`;
|
|
11498
10359
|
}
|
|
11499
|
-
if (sessionKey) {
|
|
10360
|
+
if (sessionKey && isDmChannelSessionKey(sessionKey)) {
|
|
11500
10361
|
try {
|
|
11501
10362
|
await ensureConversation(accountId, agentType, sessionKey, void 0, void 0, sessionUserId);
|
|
11502
10363
|
} catch (err) {
|
|
@@ -11625,8 +10486,8 @@ var clientIpMiddleware = async (c, next) => {
|
|
|
11625
10486
|
};
|
|
11626
10487
|
|
|
11627
10488
|
// server/routes/health.ts
|
|
11628
|
-
import { existsSync as
|
|
11629
|
-
import { createConnection as
|
|
10489
|
+
import { existsSync as existsSync10, readFileSync as readFileSync11 } from "fs";
|
|
10490
|
+
import { createConnection as createConnection2 } from "net";
|
|
11630
10491
|
|
|
11631
10492
|
// app/lib/network.ts
|
|
11632
10493
|
import { networkInterfaces } from "os";
|
|
@@ -11650,8 +10511,8 @@ function getLanIp() {
|
|
|
11650
10511
|
import { basename as basename2 } from "path";
|
|
11651
10512
|
|
|
11652
10513
|
// app/lib/review-detector/rules.ts
|
|
11653
|
-
import { readFileSync as
|
|
11654
|
-
import { resolve as
|
|
10514
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync6, statSync as statSync4, mkdirSync as mkdirSync5, renameSync as renameSync2 } from "fs";
|
|
10515
|
+
import { resolve as resolve6, dirname as dirname3 } from "path";
|
|
11655
10516
|
var DEFAULT_SCAN_INTERVAL_MS = 5e3;
|
|
11656
10517
|
var RATE_LIMIT_PATTERN = "rate[- ]?limit(?:ed| reached| hit)|(?:HTTP|status)[^a-z]{0,3}429|too many requests";
|
|
11657
10518
|
var RATE_LIMIT_PATTERN_V1 = "\\b429\\b|rate.?limit|too.?many.?requests";
|
|
@@ -12080,12 +10941,12 @@ function defaultRules() {
|
|
|
12080
10941
|
];
|
|
12081
10942
|
}
|
|
12082
10943
|
function rulesFilePath(configDir2) {
|
|
12083
|
-
return
|
|
10944
|
+
return resolve6(configDir2, "review-rules.json");
|
|
12084
10945
|
}
|
|
12085
10946
|
function ensureRulesFile(configDir2) {
|
|
12086
10947
|
const path2 = rulesFilePath(configDir2);
|
|
12087
|
-
if (
|
|
12088
|
-
|
|
10948
|
+
if (existsSync6(path2)) return { created: false, path: path2 };
|
|
10949
|
+
mkdirSync5(dirname3(path2), { recursive: true });
|
|
12089
10950
|
const body = {
|
|
12090
10951
|
scanIntervalMs: DEFAULT_SCAN_INTERVAL_MS,
|
|
12091
10952
|
rules: defaultRules()
|
|
@@ -12095,10 +10956,10 @@ function ensureRulesFile(configDir2) {
|
|
|
12095
10956
|
}
|
|
12096
10957
|
function loadRules(configDir2) {
|
|
12097
10958
|
const path2 = rulesFilePath(configDir2);
|
|
12098
|
-
if (!
|
|
10959
|
+
if (!existsSync6(path2)) {
|
|
12099
10960
|
throw new Error(`rules file missing at ${path2}`);
|
|
12100
10961
|
}
|
|
12101
|
-
const raw2 =
|
|
10962
|
+
const raw2 = readFileSync7(path2, "utf-8");
|
|
12102
10963
|
let parsed;
|
|
12103
10964
|
try {
|
|
12104
10965
|
parsed = JSON.parse(raw2);
|
|
@@ -12121,7 +10982,7 @@ function saveRules(configDir2, file) {
|
|
|
12121
10982
|
}
|
|
12122
10983
|
function atomicWriteJson(path2, body) {
|
|
12123
10984
|
const tmp = `${path2}.tmp.${process.pid}.${Date.now()}`;
|
|
12124
|
-
|
|
10985
|
+
writeFileSync6(tmp, JSON.stringify(body, null, 2) + "\n", "utf-8");
|
|
12125
10986
|
renameSync2(tmp, path2);
|
|
12126
10987
|
}
|
|
12127
10988
|
function validateRulesFile(input, sourceLabel) {
|
|
@@ -12263,16 +11124,16 @@ function validateRule(input, label, seenIds) {
|
|
|
12263
11124
|
}
|
|
12264
11125
|
|
|
12265
11126
|
// app/lib/review-detector/sources.ts
|
|
12266
|
-
import { existsSync as
|
|
12267
|
-
import { resolve as
|
|
11127
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3, statSync as statSync5, writeFileSync as writeFileSync7, renameSync as renameSync3, mkdirSync as mkdirSync6, openSync as openSync3, readSync as readSync3, closeSync as closeSync3, readFileSync as readFileSync8 } from "fs";
|
|
11128
|
+
import { resolve as resolve7, join as join4, basename, dirname as dirname4 } from "path";
|
|
12268
11129
|
function tailStatePath(configDir2) {
|
|
12269
|
-
return
|
|
11130
|
+
return resolve7(configDir2, "review-state.json");
|
|
12270
11131
|
}
|
|
12271
11132
|
function loadTailState(configDir2) {
|
|
12272
11133
|
const path2 = tailStatePath(configDir2);
|
|
12273
|
-
if (!
|
|
11134
|
+
if (!existsSync7(path2)) return {};
|
|
12274
11135
|
try {
|
|
12275
|
-
const raw2 =
|
|
11136
|
+
const raw2 = readFileSync8(path2, "utf-8");
|
|
12276
11137
|
const parsed = JSON.parse(raw2);
|
|
12277
11138
|
if (!parsed || typeof parsed !== "object") return {};
|
|
12278
11139
|
const clean = {};
|
|
@@ -12290,26 +11151,26 @@ function loadTailState(configDir2) {
|
|
|
12290
11151
|
}
|
|
12291
11152
|
function saveTailState(configDir2, state) {
|
|
12292
11153
|
const path2 = tailStatePath(configDir2);
|
|
12293
|
-
|
|
11154
|
+
mkdirSync6(dirname4(path2), { recursive: true });
|
|
12294
11155
|
const tmp = `${path2}.tmp.${process.pid}.${Date.now()}`;
|
|
12295
|
-
|
|
11156
|
+
writeFileSync7(tmp, JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
12296
11157
|
renameSync3(tmp, path2);
|
|
12297
11158
|
}
|
|
12298
11159
|
function discoverSourceFiles(configDir2, accountLogDir2, logicalSource) {
|
|
12299
11160
|
if (logicalSource === "server") {
|
|
12300
|
-
const p =
|
|
12301
|
-
return
|
|
11161
|
+
const p = resolve7(configDir2, "logs", "server.log");
|
|
11162
|
+
return existsSync7(p) ? [{ logicalSource: "server", filepath: p }] : [];
|
|
12302
11163
|
}
|
|
12303
11164
|
if (logicalSource === "vnc") {
|
|
12304
|
-
const p =
|
|
12305
|
-
return
|
|
11165
|
+
const p = resolve7(configDir2, "logs", "vnc-boot.log");
|
|
11166
|
+
return existsSync7(p) ? [{ logicalSource: "vnc", filepath: p }] : [];
|
|
12306
11167
|
}
|
|
12307
11168
|
if (logicalSource === "cloudflared") {
|
|
12308
11169
|
const files2 = [];
|
|
12309
|
-
const daemon =
|
|
12310
|
-
if (
|
|
12311
|
-
const login =
|
|
12312
|
-
if (
|
|
11170
|
+
const daemon = resolve7(configDir2, "logs", "cloudflared.log");
|
|
11171
|
+
if (existsSync7(daemon)) files2.push({ logicalSource: "cloudflared", filepath: daemon });
|
|
11172
|
+
const login = resolve7(configDir2, "logs", "cloudflared-login.log");
|
|
11173
|
+
if (existsSync7(login)) files2.push({ logicalSource: "cloudflared", filepath: login });
|
|
12313
11174
|
return files2;
|
|
12314
11175
|
}
|
|
12315
11176
|
const prefix = {
|
|
@@ -12319,7 +11180,7 @@ function discoverSourceFiles(configDir2, accountLogDir2, logicalSource) {
|
|
|
12319
11180
|
public: "public-agent-stream-",
|
|
12320
11181
|
mcp: "mcp-"
|
|
12321
11182
|
}[logicalSource];
|
|
12322
|
-
if (!
|
|
11183
|
+
if (!existsSync7(accountLogDir2)) return [];
|
|
12323
11184
|
const files = [];
|
|
12324
11185
|
let scanned = 0;
|
|
12325
11186
|
let skippedPrefixMismatch = 0;
|
|
@@ -12329,7 +11190,7 @@ function discoverSourceFiles(configDir2, accountLogDir2, logicalSource) {
|
|
|
12329
11190
|
const matchesPrefix = entry.startsWith(prefix);
|
|
12330
11191
|
const isLog = entry.endsWith(".log");
|
|
12331
11192
|
if (matchesPrefix && isLog) {
|
|
12332
|
-
files.push({ logicalSource, filepath:
|
|
11193
|
+
files.push({ logicalSource, filepath: join4(accountLogDir2, entry) });
|
|
12333
11194
|
} else if (!matchesPrefix) {
|
|
12334
11195
|
skippedPrefixMismatch += 1;
|
|
12335
11196
|
} else {
|
|
@@ -12361,7 +11222,7 @@ function discoverAllSources(configDir2, accountLogDir2) {
|
|
|
12361
11222
|
];
|
|
12362
11223
|
}
|
|
12363
11224
|
function readNewLines(filepath, prev) {
|
|
12364
|
-
if (!
|
|
11225
|
+
if (!existsSync7(filepath)) return null;
|
|
12365
11226
|
const stat5 = statSync5(filepath);
|
|
12366
11227
|
const size = stat5.size;
|
|
12367
11228
|
const inode = stat5.ino;
|
|
@@ -12414,12 +11275,12 @@ function readNewLines(filepath, prev) {
|
|
|
12414
11275
|
}
|
|
12415
11276
|
}
|
|
12416
11277
|
function countRecentWrites(dir, sinceMs) {
|
|
12417
|
-
if (!
|
|
11278
|
+
if (!existsSync7(dir)) return 0;
|
|
12418
11279
|
let count = 0;
|
|
12419
11280
|
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
12420
11281
|
if (!entry.isFile()) continue;
|
|
12421
11282
|
try {
|
|
12422
|
-
const st = statSync5(
|
|
11283
|
+
const st = statSync5(join4(dir, entry.name));
|
|
12423
11284
|
if (st.mtimeMs >= sinceMs) count += 1;
|
|
12424
11285
|
} catch {
|
|
12425
11286
|
}
|
|
@@ -12427,7 +11288,7 @@ function countRecentWrites(dir, sinceMs) {
|
|
|
12427
11288
|
return count;
|
|
12428
11289
|
}
|
|
12429
11290
|
function fileLastWriteMs(path2) {
|
|
12430
|
-
if (!
|
|
11291
|
+
if (!existsSync7(path2)) return null;
|
|
12431
11292
|
try {
|
|
12432
11293
|
return statSync5(path2).mtimeMs;
|
|
12433
11294
|
} catch {
|
|
@@ -12435,31 +11296,31 @@ function fileLastWriteMs(path2) {
|
|
|
12435
11296
|
}
|
|
12436
11297
|
}
|
|
12437
11298
|
function accountLogDir(accountDir) {
|
|
12438
|
-
return
|
|
11299
|
+
return resolve7(accountDir, "logs");
|
|
12439
11300
|
}
|
|
12440
11301
|
function sourceKey(file) {
|
|
12441
11302
|
return `${file.logicalSource}:${basename(file.filepath)}`;
|
|
12442
11303
|
}
|
|
12443
11304
|
|
|
12444
11305
|
// app/lib/review-detector/writer.ts
|
|
12445
|
-
import { appendFileSync as
|
|
12446
|
-
import { resolve as
|
|
11306
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync8, renameSync as renameSync4, statSync as statSync6 } from "fs";
|
|
11307
|
+
import { resolve as resolve8, dirname as dirname5 } from "path";
|
|
12447
11308
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
12448
11309
|
function reviewLogPath(configDir2) {
|
|
12449
|
-
return
|
|
11310
|
+
return resolve8(configDir2, "logs", "review.log");
|
|
12450
11311
|
}
|
|
12451
11312
|
function pendingAlertsPath(configDir2) {
|
|
12452
|
-
return
|
|
11313
|
+
return resolve8(configDir2, "review-pending-alerts.jsonl");
|
|
12453
11314
|
}
|
|
12454
11315
|
function reviewLog(configDir2, event) {
|
|
12455
11316
|
const path2 = reviewLogPath(configDir2);
|
|
12456
11317
|
try {
|
|
12457
|
-
|
|
11318
|
+
mkdirSync7(dirname5(path2), { recursive: true });
|
|
12458
11319
|
const line = `${new Date(
|
|
12459
11320
|
typeof event.ts === "number" ? event.ts : Date.now()
|
|
12460
11321
|
).toISOString()} [review] ${JSON.stringify(event)}
|
|
12461
11322
|
`;
|
|
12462
|
-
|
|
11323
|
+
appendFileSync2(path2, line, "utf-8");
|
|
12463
11324
|
} catch (err) {
|
|
12464
11325
|
console.error(`[review] failed to write review log at ${path2}: ${err instanceof Error ? err.message : String(err)}`);
|
|
12465
11326
|
}
|
|
@@ -12571,17 +11432,17 @@ async function upsertReviewAlert(accountId, match2) {
|
|
|
12571
11432
|
function queueAlert(configDir2, accountId, match2) {
|
|
12572
11433
|
const path2 = pendingAlertsPath(configDir2);
|
|
12573
11434
|
try {
|
|
12574
|
-
|
|
11435
|
+
mkdirSync7(dirname5(path2), { recursive: true });
|
|
12575
11436
|
const line = JSON.stringify({ accountId, match: match2 }) + "\n";
|
|
12576
|
-
|
|
11437
|
+
appendFileSync2(path2, line, "utf-8");
|
|
12577
11438
|
} catch (err) {
|
|
12578
11439
|
console.error(`[review] failed to queue alert at ${path2}: ${err instanceof Error ? err.message : String(err)}`);
|
|
12579
11440
|
}
|
|
12580
11441
|
}
|
|
12581
11442
|
async function drainPendingAlerts(configDir2) {
|
|
12582
11443
|
const path2 = pendingAlertsPath(configDir2);
|
|
12583
|
-
if (!
|
|
12584
|
-
const raw2 =
|
|
11444
|
+
if (!existsSync8(path2)) return { drained: 0, remaining: 0 };
|
|
11445
|
+
const raw2 = readFileSync9(path2, "utf-8");
|
|
12585
11446
|
const lines = raw2.split("\n").filter((l) => l.trim().length > 0);
|
|
12586
11447
|
if (lines.length === 0) return { drained: 0, remaining: 0 };
|
|
12587
11448
|
const remaining = [];
|
|
@@ -12602,9 +11463,9 @@ async function drainPendingAlerts(configDir2) {
|
|
|
12602
11463
|
}
|
|
12603
11464
|
const tmp = `${path2}.tmp.${process.pid}.${Date.now()}`;
|
|
12604
11465
|
if (remaining.length > 0) {
|
|
12605
|
-
|
|
11466
|
+
writeFileSync8(tmp, remaining.join("\n") + "\n", "utf-8");
|
|
12606
11467
|
} else {
|
|
12607
|
-
|
|
11468
|
+
writeFileSync8(tmp, "", "utf-8");
|
|
12608
11469
|
}
|
|
12609
11470
|
renameSync4(tmp, path2);
|
|
12610
11471
|
return { drained, remaining: remaining.length };
|
|
@@ -12704,7 +11565,7 @@ async function bootDetector() {
|
|
|
12704
11565
|
}
|
|
12705
11566
|
|
|
12706
11567
|
// app/lib/review-detector/scan-loop.ts
|
|
12707
|
-
import { resolve as
|
|
11568
|
+
import { resolve as resolve9 } from "path";
|
|
12708
11569
|
|
|
12709
11570
|
// app/lib/review-detector/evaluator.ts
|
|
12710
11571
|
var SAMPLE_MAX_CHARS = 500;
|
|
@@ -12998,14 +11859,14 @@ async function runScanCycle(runtime) {
|
|
|
12998
11859
|
match2 = result.match;
|
|
12999
11860
|
}
|
|
13000
11861
|
} else if (rule.type === "file-write-storm") {
|
|
13001
|
-
const dir =
|
|
11862
|
+
const dir = resolve9(runtime.configDir, rule.watchPath ?? "");
|
|
13002
11863
|
const sinceMs = cycleStart - rule.thresholdWindowMinutes * 6e4;
|
|
13003
11864
|
const count = countRecentWrites(dir, sinceMs);
|
|
13004
11865
|
const result = evaluateFileWriteStormRule(rule, count, state, cycleStart);
|
|
13005
11866
|
state = result.state;
|
|
13006
11867
|
match2 = result.match;
|
|
13007
11868
|
} else if (rule.type === "stale-log") {
|
|
13008
|
-
const trackedPath =
|
|
11869
|
+
const trackedPath = resolve9(runtime.configDir, rule.watchPath ?? "");
|
|
13009
11870
|
const lastMs = fileLastWriteMs(trackedPath);
|
|
13010
11871
|
const result = evaluateStaleLogRule(rule, lastMs, state, cycleStart);
|
|
13011
11872
|
state = result.state;
|
|
@@ -13244,20 +12105,20 @@ var WhatsAppConfigSchema = z.object({
|
|
|
13244
12105
|
});
|
|
13245
12106
|
|
|
13246
12107
|
// app/lib/whatsapp/config-persist.ts
|
|
13247
|
-
import { readFileSync as
|
|
13248
|
-
import { resolve as
|
|
12108
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync9, existsSync as existsSync9 } from "fs";
|
|
12109
|
+
import { resolve as resolve10, join as join5 } from "path";
|
|
13249
12110
|
var TAG3 = "[whatsapp:config]";
|
|
13250
12111
|
function configPath(accountDir) {
|
|
13251
|
-
return
|
|
12112
|
+
return resolve10(accountDir, "account.json");
|
|
13252
12113
|
}
|
|
13253
12114
|
function readConfig(accountDir) {
|
|
13254
12115
|
const path2 = configPath(accountDir);
|
|
13255
|
-
if (!
|
|
13256
|
-
return JSON.parse(
|
|
12116
|
+
if (!existsSync9(path2)) throw new Error(`account.json not found at ${path2}`);
|
|
12117
|
+
return JSON.parse(readFileSync10(path2, "utf-8"));
|
|
13257
12118
|
}
|
|
13258
12119
|
function writeConfig(accountDir, config) {
|
|
13259
12120
|
const path2 = configPath(accountDir);
|
|
13260
|
-
|
|
12121
|
+
writeFileSync9(path2, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
13261
12122
|
}
|
|
13262
12123
|
function reloadManagerConfig(accountDir) {
|
|
13263
12124
|
try {
|
|
@@ -13427,8 +12288,8 @@ function setPublicAgent(accountDir, slug) {
|
|
|
13427
12288
|
if (!trimmed) {
|
|
13428
12289
|
return { ok: false, error: "Agent slug cannot be empty." };
|
|
13429
12290
|
}
|
|
13430
|
-
const agentConfigPath =
|
|
13431
|
-
if (!
|
|
12291
|
+
const agentConfigPath = join5(accountDir, "agents", trimmed, "config.json");
|
|
12292
|
+
if (!existsSync9(agentConfigPath)) {
|
|
13432
12293
|
return { ok: false, error: `Agent "${trimmed}" not found \u2014 no config.json at ${agentConfigPath}. Check the agent slug and try again.` };
|
|
13433
12294
|
}
|
|
13434
12295
|
try {
|
|
@@ -13703,7 +12564,7 @@ var credsSaveQueue = Promise.resolve();
|
|
|
13703
12564
|
async function drainCredsSaveQueue(timeoutMs = 5e3) {
|
|
13704
12565
|
console.error(`${TAG5} draining credential save queue\u2026`);
|
|
13705
12566
|
const timer = new Promise(
|
|
13706
|
-
(
|
|
12567
|
+
(resolve28) => setTimeout(() => resolve28("timeout"), timeoutMs)
|
|
13707
12568
|
);
|
|
13708
12569
|
const result = await Promise.race([
|
|
13709
12570
|
credsSaveQueue.then(() => "drained"),
|
|
@@ -13831,11 +12692,11 @@ async function createWaSocket(opts) {
|
|
|
13831
12692
|
return sock;
|
|
13832
12693
|
}
|
|
13833
12694
|
async function waitForConnection(sock) {
|
|
13834
|
-
return new Promise((
|
|
12695
|
+
return new Promise((resolve28, reject) => {
|
|
13835
12696
|
const handler = (update) => {
|
|
13836
12697
|
if (update.connection === "open") {
|
|
13837
12698
|
sock.ev.off("connection.update", handler);
|
|
13838
|
-
|
|
12699
|
+
resolve28();
|
|
13839
12700
|
}
|
|
13840
12701
|
if (update.connection === "close") {
|
|
13841
12702
|
sock.ev.off("connection.update", handler);
|
|
@@ -13949,14 +12810,14 @@ ${inspected}`;
|
|
|
13949
12810
|
return inspect2(err, INSPECT_OPTS2);
|
|
13950
12811
|
}
|
|
13951
12812
|
function withTimeout(label, promise, timeoutMs) {
|
|
13952
|
-
return new Promise((
|
|
12813
|
+
return new Promise((resolve28, reject) => {
|
|
13953
12814
|
const timer = setTimeout(() => {
|
|
13954
12815
|
reject(new Error(`${label} timed out after ${timeoutMs}ms`));
|
|
13955
12816
|
}, timeoutMs);
|
|
13956
12817
|
promise.then(
|
|
13957
12818
|
(value) => {
|
|
13958
12819
|
clearTimeout(timer);
|
|
13959
|
-
|
|
12820
|
+
resolve28(value);
|
|
13960
12821
|
},
|
|
13961
12822
|
(err) => {
|
|
13962
12823
|
clearTimeout(timer);
|
|
@@ -14468,7 +13329,7 @@ async function sendMediaMessage(sock, to, media, opts) {
|
|
|
14468
13329
|
// app/lib/whatsapp/inbound/media.ts
|
|
14469
13330
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
14470
13331
|
import { writeFile, mkdir } from "fs/promises";
|
|
14471
|
-
import { join as
|
|
13332
|
+
import { join as join6 } from "path";
|
|
14472
13333
|
import {
|
|
14473
13334
|
downloadMediaMessage,
|
|
14474
13335
|
downloadContentFromMessage,
|
|
@@ -14554,7 +13415,7 @@ async function downloadInboundMedia(msg, sock, opts) {
|
|
|
14554
13415
|
await mkdir(MEDIA_DIR, { recursive: true });
|
|
14555
13416
|
const ext = mimeToExt(mimetype ?? "application/octet-stream");
|
|
14556
13417
|
const filename = `${randomUUID5()}.${ext}`;
|
|
14557
|
-
const filePath =
|
|
13418
|
+
const filePath = join6(MEDIA_DIR, filename);
|
|
14558
13419
|
await writeFile(filePath, buffer);
|
|
14559
13420
|
const sizeKB = (buffer.length / 1024).toFixed(0);
|
|
14560
13421
|
console.error(`${TAG9} media downloaded type=${mimetype ?? "unknown"} size=${sizeKB}KB path=${filePath}`);
|
|
@@ -14898,6 +13759,14 @@ function deriveSessionKey(input) {
|
|
|
14898
13759
|
if (input.isOwnerMirror || input.agentType === "admin") {
|
|
14899
13760
|
return `whatsapp:${input.accountId}`;
|
|
14900
13761
|
}
|
|
13762
|
+
if (input.isGroup) {
|
|
13763
|
+
if (!input.groupJid) {
|
|
13764
|
+
throw new Error(
|
|
13765
|
+
`deriveSessionKey: isGroup=true requires groupJid (accountId=${input.accountId}, senderPhone=${input.senderPhone})`
|
|
13766
|
+
);
|
|
13767
|
+
}
|
|
13768
|
+
return `whatsapp:${input.accountId}:group:${input.groupJid}`;
|
|
13769
|
+
}
|
|
14901
13770
|
return `whatsapp:${input.accountId}:${input.senderPhone}`;
|
|
14902
13771
|
}
|
|
14903
13772
|
async function init(opts) {
|
|
@@ -15162,11 +14031,11 @@ async function connectWithReconnect(conn) {
|
|
|
15162
14031
|
console.error(
|
|
15163
14032
|
`${TAG13} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
|
|
15164
14033
|
);
|
|
15165
|
-
await new Promise((
|
|
15166
|
-
const timer = setTimeout(
|
|
14034
|
+
await new Promise((resolve28) => {
|
|
14035
|
+
const timer = setTimeout(resolve28, delay);
|
|
15167
14036
|
conn.abortController.signal.addEventListener("abort", () => {
|
|
15168
14037
|
clearTimeout(timer);
|
|
15169
|
-
|
|
14038
|
+
resolve28();
|
|
15170
14039
|
}, { once: true });
|
|
15171
14040
|
});
|
|
15172
14041
|
}
|
|
@@ -15174,16 +14043,16 @@ async function connectWithReconnect(conn) {
|
|
|
15174
14043
|
}
|
|
15175
14044
|
}
|
|
15176
14045
|
function waitForDisconnectEvent(conn) {
|
|
15177
|
-
return new Promise((
|
|
14046
|
+
return new Promise((resolve28) => {
|
|
15178
14047
|
if (!conn.sock) {
|
|
15179
|
-
|
|
14048
|
+
resolve28();
|
|
15180
14049
|
return;
|
|
15181
14050
|
}
|
|
15182
14051
|
const sock = conn.sock;
|
|
15183
14052
|
const handler = (update) => {
|
|
15184
14053
|
if (update.connection === "close") {
|
|
15185
14054
|
sock.ev.off("connection.update", handler);
|
|
15186
|
-
|
|
14055
|
+
resolve28();
|
|
15187
14056
|
}
|
|
15188
14057
|
};
|
|
15189
14058
|
sock.ev.on("connection.update", handler);
|
|
@@ -15369,6 +14238,8 @@ async function handleInboundMessage(conn, msg) {
|
|
|
15369
14238
|
agentType: "admin",
|
|
15370
14239
|
accountId: conn.accountId,
|
|
15371
14240
|
senderPhone: senderPhone2,
|
|
14241
|
+
isGroup: isGroup2,
|
|
14242
|
+
groupJid: isGroup2 ? remoteJid : void 0,
|
|
15372
14243
|
isOwnerMirror: true
|
|
15373
14244
|
}),
|
|
15374
14245
|
isOwnerMirror: true
|
|
@@ -15398,8 +14269,8 @@ async function handleInboundMessage(conn, msg) {
|
|
|
15398
14269
|
const conversationKey = isGroup ? remoteJid : senderPhone;
|
|
15399
14270
|
const debounceKey = `${conn.accountId}:${conversationKey}:${senderPhone}`;
|
|
15400
14271
|
let resolvePending;
|
|
15401
|
-
const sttPending = new Promise((
|
|
15402
|
-
resolvePending =
|
|
14272
|
+
const sttPending = new Promise((resolve28) => {
|
|
14273
|
+
resolvePending = resolve28;
|
|
15403
14274
|
});
|
|
15404
14275
|
if (conn.debouncer) conn.debouncer.registerPending(debounceKey, sttPending);
|
|
15405
14276
|
try {
|
|
@@ -15452,7 +14323,9 @@ async function handleInboundMessage(conn, msg) {
|
|
|
15452
14323
|
const sessionKey = deriveSessionKey({
|
|
15453
14324
|
agentType: accessResult.agentType,
|
|
15454
14325
|
accountId: conn.accountId,
|
|
15455
|
-
senderPhone
|
|
14326
|
+
senderPhone,
|
|
14327
|
+
isGroup,
|
|
14328
|
+
groupJid: isGroup ? remoteJid : void 0
|
|
15456
14329
|
});
|
|
15457
14330
|
conn.lastMessageAt = Date.now();
|
|
15458
14331
|
const payload = {
|
|
@@ -15510,44 +14383,30 @@ async function probeApiKey() {
|
|
|
15510
14383
|
return result.status;
|
|
15511
14384
|
}
|
|
15512
14385
|
function checkPort(port2, timeoutMs = 500) {
|
|
15513
|
-
return new Promise((
|
|
15514
|
-
const socket =
|
|
14386
|
+
return new Promise((resolve28) => {
|
|
14387
|
+
const socket = createConnection2(port2, "127.0.0.1");
|
|
15515
14388
|
socket.setTimeout(timeoutMs);
|
|
15516
14389
|
socket.once("connect", () => {
|
|
15517
14390
|
socket.destroy();
|
|
15518
|
-
|
|
14391
|
+
resolve28(true);
|
|
15519
14392
|
});
|
|
15520
14393
|
socket.once("error", () => {
|
|
15521
14394
|
socket.destroy();
|
|
15522
|
-
|
|
14395
|
+
resolve28(false);
|
|
15523
14396
|
});
|
|
15524
14397
|
socket.once("timeout", () => {
|
|
15525
14398
|
socket.destroy();
|
|
15526
|
-
|
|
14399
|
+
resolve28(false);
|
|
15527
14400
|
});
|
|
15528
14401
|
});
|
|
15529
14402
|
}
|
|
15530
|
-
var TTYD_PROBE_CACHE_MS = 2e3;
|
|
15531
|
-
var TTYD_PROBE_TIMEOUT_MS = 200;
|
|
15532
|
-
var cachedTtydReady = null;
|
|
15533
|
-
async function probeTerminalReady() {
|
|
15534
|
-
if (cachedTtydReady && Date.now() - cachedTtydReady.checkedAt < TTYD_PROBE_CACHE_MS) {
|
|
15535
|
-
return cachedTtydReady.ok;
|
|
15536
|
-
}
|
|
15537
|
-
const ok = await checkPort(7681, TTYD_PROBE_TIMEOUT_MS);
|
|
15538
|
-
if (!cachedTtydReady || cachedTtydReady.ok !== ok) {
|
|
15539
|
-
console.log(`[health] terminal_ready: ${cachedTtydReady?.ok ?? "unknown"} \u2192 ${ok}`);
|
|
15540
|
-
}
|
|
15541
|
-
cachedTtydReady = { ok, checkedAt: Date.now() };
|
|
15542
|
-
return ok;
|
|
15543
|
-
}
|
|
15544
14403
|
var app = new Hono2();
|
|
15545
14404
|
app.get("/", async (c) => {
|
|
15546
14405
|
const browserTransport = resolveBrowserTransport(c.req.raw, c.env?.incoming?.socket?.remoteAddress);
|
|
15547
14406
|
let pinConfigured = false;
|
|
15548
14407
|
try {
|
|
15549
|
-
if (
|
|
15550
|
-
const raw2 =
|
|
14408
|
+
if (existsSync10(USERS_FILE)) {
|
|
14409
|
+
const raw2 = readFileSync11(USERS_FILE, "utf-8").trim();
|
|
15551
14410
|
if (raw2) {
|
|
15552
14411
|
const users = JSON.parse(raw2);
|
|
15553
14412
|
pinConfigured = Array.isArray(users) && users.length > 0;
|
|
@@ -15564,10 +14423,9 @@ app.get("/", async (c) => {
|
|
|
15564
14423
|
}
|
|
15565
14424
|
const claudeAuthenticated = authHealth.status === "ok" || authHealth.status === "expiring";
|
|
15566
14425
|
const vncRunning = await checkPort(6080);
|
|
15567
|
-
const terminalReady = await probeTerminalReady();
|
|
15568
14426
|
let apiKeyConfigured = false;
|
|
15569
14427
|
try {
|
|
15570
|
-
apiKeyConfigured =
|
|
14428
|
+
apiKeyConfigured = existsSync10(keyFilePath());
|
|
15571
14429
|
} catch {
|
|
15572
14430
|
}
|
|
15573
14431
|
let apiKeyStatus = "missing";
|
|
@@ -15608,7 +14466,6 @@ app.get("/", async (c) => {
|
|
|
15608
14466
|
claude_authenticated: claudeAuthenticated,
|
|
15609
14467
|
...onboardingComplete !== void 0 && { onboarding_complete: onboardingComplete },
|
|
15610
14468
|
vnc_running: vncRunning,
|
|
15611
|
-
terminal_ready: terminalReady,
|
|
15612
14469
|
browser_transport: browserTransport,
|
|
15613
14470
|
auth_status: authHealth.status,
|
|
15614
14471
|
auth_expires_at: authHealth.expiresAt ?? null,
|
|
@@ -15636,14 +14493,14 @@ app.get("/", async (c) => {
|
|
|
15636
14493
|
var health_default = app;
|
|
15637
14494
|
|
|
15638
14495
|
// server/routes/session.ts
|
|
15639
|
-
import { resolve as
|
|
15640
|
-
import { existsSync as
|
|
14496
|
+
import { resolve as resolve11 } from "path";
|
|
14497
|
+
import { existsSync as existsSync11, writeFileSync as writeFileSync10, mkdirSync as mkdirSync8 } from "fs";
|
|
15641
14498
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
15642
14499
|
function writeBrandingCache(accountId, agentSlug, branding) {
|
|
15643
14500
|
try {
|
|
15644
|
-
const cacheDir =
|
|
15645
|
-
|
|
15646
|
-
|
|
14501
|
+
const cacheDir = resolve11(MAXY_DIR, "branding-cache", accountId);
|
|
14502
|
+
mkdirSync8(cacheDir, { recursive: true });
|
|
14503
|
+
writeFileSync10(resolve11(cacheDir, `${agentSlug}.json`), JSON.stringify(branding), "utf-8");
|
|
15647
14504
|
} catch (err) {
|
|
15648
14505
|
console.error(`[branding] cache write failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
15649
14506
|
}
|
|
@@ -15713,9 +14570,9 @@ app2.post("/", async (c) => {
|
|
|
15713
14570
|
}
|
|
15714
14571
|
let agentConfig = null;
|
|
15715
14572
|
if (account) {
|
|
15716
|
-
const agentDir =
|
|
15717
|
-
const agentConfigPath =
|
|
15718
|
-
if (!
|
|
14573
|
+
const agentDir = resolve11(account.accountDir, "agents", agentSlug);
|
|
14574
|
+
const agentConfigPath = resolve11(agentDir, "config.json");
|
|
14575
|
+
if (!existsSync11(agentDir) || !existsSync11(agentConfigPath)) {
|
|
15719
14576
|
return c.json({ error: "Agent not found" }, 404);
|
|
15720
14577
|
}
|
|
15721
14578
|
agentConfig = resolveAgentConfig(account.accountDir, agentSlug);
|
|
@@ -15905,8 +14762,6 @@ app2.post("/", async (c) => {
|
|
|
15905
14762
|
const newVisitorId = visitorId ?? crypto.randomUUID();
|
|
15906
14763
|
const sessionKey = crypto.randomUUID();
|
|
15907
14764
|
registerSession(sessionKey, "public", accountId, agentSlug);
|
|
15908
|
-
ensureConversation(accountId, "public", sessionKey, newVisitorId, agentSlug).catch(() => {
|
|
15909
|
-
});
|
|
15910
14765
|
const hasImage = agentConfig?.image ? "yes" : "no";
|
|
15911
14766
|
console.log(`[session] new-session visitor=${newVisitorId.slice(0, 8)}\u2026 session=${sessionKey.slice(0, 8)}\u2026 agent=${agentSlug} image=${hasImage} showAgentName=${agentConfig?.showAgentName ?? false}`);
|
|
15912
14767
|
return withVisitorCookie(
|
|
@@ -15961,9 +14816,9 @@ ${raw2}`;
|
|
|
15961
14816
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
15962
14817
|
import { mkdir as mkdir2, readFile, stat as stat2, writeFile as writeFile2 } from "fs/promises";
|
|
15963
14818
|
import { realpathSync } from "fs";
|
|
15964
|
-
import { resolve as
|
|
15965
|
-
var
|
|
15966
|
-
var ATTACHMENTS_ROOT =
|
|
14819
|
+
import { resolve as resolve12, extname, basename as basename3 } from "path";
|
|
14820
|
+
var PLATFORM_ROOT4 = process.env.MAXY_PLATFORM_ROOT ?? resolve12(process.cwd(), "../platform");
|
|
14821
|
+
var ATTACHMENTS_ROOT = resolve12(PLATFORM_ROOT4, "..", "data/uploads");
|
|
15967
14822
|
var SUPPORTED_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
15968
14823
|
"image/jpeg",
|
|
15969
14824
|
"image/png",
|
|
@@ -15987,11 +14842,11 @@ function assertSupportedMime(mimeType) {
|
|
|
15987
14842
|
}
|
|
15988
14843
|
async function writeAttachment(scope, filename, mimeType, sizeBytes, buffer) {
|
|
15989
14844
|
const attachmentId = randomUUID6();
|
|
15990
|
-
const dir =
|
|
14845
|
+
const dir = resolve12(ATTACHMENTS_ROOT, scope, attachmentId);
|
|
15991
14846
|
await mkdir2(dir, { recursive: true });
|
|
15992
14847
|
const ext = extname(filename) || "";
|
|
15993
|
-
const storagePath =
|
|
15994
|
-
const metaPath =
|
|
14848
|
+
const storagePath = resolve12(dir, `${attachmentId}${ext}`);
|
|
14849
|
+
const metaPath = resolve12(dir, `${attachmentId}.meta.json`);
|
|
15995
14850
|
const meta = {
|
|
15996
14851
|
attachmentId,
|
|
15997
14852
|
scope,
|
|
@@ -16066,7 +14921,7 @@ async function storeGeneratedFile(accountId, filePath) {
|
|
|
16066
14921
|
// app/lib/stt/voice-note.ts
|
|
16067
14922
|
import { writeFile as writeFile3, mkdtemp, rm } from "fs/promises";
|
|
16068
14923
|
import { tmpdir } from "os";
|
|
16069
|
-
import { join as
|
|
14924
|
+
import { join as join7 } from "path";
|
|
16070
14925
|
var TAG14 = "[voice]";
|
|
16071
14926
|
var AUDIO_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
16072
14927
|
"audio/ogg",
|
|
@@ -16104,9 +14959,9 @@ async function transcribeVoiceNote(file, source) {
|
|
|
16104
14959
|
let tempDir;
|
|
16105
14960
|
let tempPath;
|
|
16106
14961
|
try {
|
|
16107
|
-
tempDir = await mkdtemp(
|
|
14962
|
+
tempDir = await mkdtemp(join7(tmpdir(), "voice-"));
|
|
16108
14963
|
const ext = audioExtension(mimeType);
|
|
16109
|
-
tempPath =
|
|
14964
|
+
tempPath = join7(tempDir, `recording${ext}`);
|
|
16110
14965
|
const buffer = Buffer.from(await file.arrayBuffer());
|
|
16111
14966
|
await writeFile3(tempPath, buffer);
|
|
16112
14967
|
} catch (err) {
|
|
@@ -16668,16 +15523,16 @@ var group_default = app4;
|
|
|
16668
15523
|
|
|
16669
15524
|
// app/lib/access-gate.ts
|
|
16670
15525
|
import neo4j2 from "neo4j-driver";
|
|
16671
|
-
import { readFileSync as
|
|
16672
|
-
import { resolve as
|
|
15526
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
15527
|
+
import { resolve as resolve13 } from "path";
|
|
16673
15528
|
import { randomUUID as randomUUID7, randomInt } from "crypto";
|
|
16674
|
-
var
|
|
15529
|
+
var PLATFORM_ROOT5 = process.env.MAXY_PLATFORM_ROOT ?? resolve13(process.cwd(), "..");
|
|
16675
15530
|
var driver2 = null;
|
|
16676
15531
|
function readPassword2() {
|
|
16677
15532
|
if (process.env.NEO4J_PASSWORD) return process.env.NEO4J_PASSWORD;
|
|
16678
|
-
const passwordFile =
|
|
15533
|
+
const passwordFile = resolve13(PLATFORM_ROOT5, "config/.neo4j-password");
|
|
16679
15534
|
try {
|
|
16680
|
-
return
|
|
15535
|
+
return readFileSync12(passwordFile, "utf-8").trim();
|
|
16681
15536
|
} catch {
|
|
16682
15537
|
throw new Error(
|
|
16683
15538
|
`Neo4j password not found. Expected at ${passwordFile} or in NEO4J_PASSWORD env var.`
|
|
@@ -16710,13 +15565,13 @@ process.on("SIGINT", async () => {
|
|
|
16710
15565
|
driver2 = null;
|
|
16711
15566
|
}
|
|
16712
15567
|
});
|
|
16713
|
-
var
|
|
15568
|
+
var rateLimitMap = /* @__PURE__ */ new Map();
|
|
16714
15569
|
var ACCESS_MAX_ATTEMPTS = 5;
|
|
16715
15570
|
var ACCESS_LOCKOUT_MS = 15 * 60 * 1e3;
|
|
16716
15571
|
var ACCESS_WINDOW_MS = 15 * 60 * 1e3;
|
|
16717
15572
|
function checkAccessRateLimit(ip, agentSlug) {
|
|
16718
15573
|
const key = `${ip}:${agentSlug}`;
|
|
16719
|
-
const entry =
|
|
15574
|
+
const entry = rateLimitMap.get(key);
|
|
16720
15575
|
if (!entry) return null;
|
|
16721
15576
|
const now = Date.now();
|
|
16722
15577
|
if (entry.lockedUntil && now < entry.lockedUntil) {
|
|
@@ -16725,7 +15580,7 @@ function checkAccessRateLimit(ip, agentSlug) {
|
|
|
16725
15580
|
return `Too many attempts. Try again in ${remainingS}s`;
|
|
16726
15581
|
}
|
|
16727
15582
|
if (now - entry.firstAttempt > ACCESS_WINDOW_MS) {
|
|
16728
|
-
|
|
15583
|
+
rateLimitMap.delete(key);
|
|
16729
15584
|
return null;
|
|
16730
15585
|
}
|
|
16731
15586
|
return null;
|
|
@@ -16733,9 +15588,9 @@ function checkAccessRateLimit(ip, agentSlug) {
|
|
|
16733
15588
|
function recordAccessFailedAttempt(ip, agentSlug) {
|
|
16734
15589
|
const key = `${ip}:${agentSlug}`;
|
|
16735
15590
|
const now = Date.now();
|
|
16736
|
-
const entry =
|
|
15591
|
+
const entry = rateLimitMap.get(key);
|
|
16737
15592
|
if (!entry || now - entry.firstAttempt > ACCESS_WINDOW_MS) {
|
|
16738
|
-
|
|
15593
|
+
rateLimitMap.set(key, { attempts: 1, firstAttempt: now });
|
|
16739
15594
|
return;
|
|
16740
15595
|
}
|
|
16741
15596
|
entry.attempts++;
|
|
@@ -16744,7 +15599,7 @@ function recordAccessFailedAttempt(ip, agentSlug) {
|
|
|
16744
15599
|
}
|
|
16745
15600
|
}
|
|
16746
15601
|
function clearAccessRateLimit(ip, agentSlug) {
|
|
16747
|
-
|
|
15602
|
+
rateLimitMap.delete(`${ip}:${agentSlug}`);
|
|
16748
15603
|
}
|
|
16749
15604
|
var forgotPwdMap = /* @__PURE__ */ new Map();
|
|
16750
15605
|
var FORGOT_PWD_MAX = 3;
|
|
@@ -16988,19 +15843,19 @@ async function findActiveGrantByContact(contactValue, agentSlug, accountId) {
|
|
|
16988
15843
|
}
|
|
16989
15844
|
|
|
16990
15845
|
// app/lib/brevo-sms.ts
|
|
16991
|
-
import { readFileSync as
|
|
15846
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync11, mkdirSync as mkdirSync9, existsSync as existsSync12, chmodSync } from "fs";
|
|
16992
15847
|
import { dirname as dirname6 } from "path";
|
|
16993
|
-
import { resolve as
|
|
16994
|
-
var BREVO_API_KEY_FILE =
|
|
15848
|
+
import { resolve as resolve14 } from "path";
|
|
15849
|
+
var BREVO_API_KEY_FILE = resolve14(MAXY_DIR, ".brevo-api-key");
|
|
16995
15850
|
var BREVO_API_URL = "https://api.brevo.com/v3/transactionalSMS/sms";
|
|
16996
15851
|
var BREVO_TIMEOUT_MS = 1e4;
|
|
16997
15852
|
var BREVO_SENDER = "Maxy";
|
|
16998
|
-
var
|
|
16999
|
-
if (
|
|
15853
|
+
var platformRoot = process.env.MAXY_PLATFORM_ROOT;
|
|
15854
|
+
if (platformRoot) {
|
|
17000
15855
|
try {
|
|
17001
|
-
const brandPath =
|
|
17002
|
-
if (
|
|
17003
|
-
const brand = JSON.parse(
|
|
15856
|
+
const brandPath = resolve14(platformRoot, "config", "brand.json");
|
|
15857
|
+
if (existsSync12(brandPath)) {
|
|
15858
|
+
const brand = JSON.parse(readFileSync13(brandPath, "utf-8"));
|
|
17004
15859
|
if (brand.productName) BREVO_SENDER = brand.productName;
|
|
17005
15860
|
}
|
|
17006
15861
|
} catch {
|
|
@@ -17008,7 +15863,7 @@ if (platformRoot2) {
|
|
|
17008
15863
|
}
|
|
17009
15864
|
function readBrevoApiKey() {
|
|
17010
15865
|
try {
|
|
17011
|
-
const key =
|
|
15866
|
+
const key = readFileSync13(BREVO_API_KEY_FILE, "utf-8").trim();
|
|
17012
15867
|
if (!key) {
|
|
17013
15868
|
throw new Error(`Brevo API key file is empty: ${BREVO_API_KEY_FILE}`);
|
|
17014
15869
|
}
|
|
@@ -17023,7 +15878,7 @@ function readBrevoApiKey() {
|
|
|
17023
15878
|
}
|
|
17024
15879
|
}
|
|
17025
15880
|
function hasBrevoApiKey() {
|
|
17026
|
-
return
|
|
15881
|
+
return existsSync12(BREVO_API_KEY_FILE);
|
|
17027
15882
|
}
|
|
17028
15883
|
async function sendSms(recipient, content, opts) {
|
|
17029
15884
|
let apiKey;
|
|
@@ -17395,8 +16250,8 @@ app5.post("/forgot-password", async (c) => {
|
|
|
17395
16250
|
app5.post("/send-otp", async (c) => {
|
|
17396
16251
|
const socketAddr = c.env?.incoming?.socket?.remoteAddress;
|
|
17397
16252
|
const hasXff = !!c.req.header("x-forwarded-for");
|
|
17398
|
-
const
|
|
17399
|
-
if (!
|
|
16253
|
+
const isLoopback = socketAddr === "127.0.0.1" || socketAddr === "::1" || socketAddr === "::ffff:127.0.0.1";
|
|
16254
|
+
if (!isLoopback || hasXff) {
|
|
17400
16255
|
console.error(`[access-gate] send-otp rejected: socket=${socketAddr ?? "unknown"} xff=${hasXff}`);
|
|
17401
16256
|
return c.json({ error: "Forbidden" }, 403);
|
|
17402
16257
|
}
|
|
@@ -17439,8 +16294,8 @@ app5.post("/send-otp", async (c) => {
|
|
|
17439
16294
|
var access_default = app5;
|
|
17440
16295
|
|
|
17441
16296
|
// server/routes/telegram.ts
|
|
17442
|
-
import { existsSync as
|
|
17443
|
-
import { timingSafeEqual
|
|
16297
|
+
import { existsSync as existsSync13, readFileSync as readFileSync14 } from "fs";
|
|
16298
|
+
import { timingSafeEqual } from "crypto";
|
|
17444
16299
|
|
|
17445
16300
|
// app/lib/telegram/access-control.ts
|
|
17446
16301
|
function checkTelegramAccess(params) {
|
|
@@ -17476,8 +16331,8 @@ var TELEGRAM_API = "https://api.telegram.org";
|
|
|
17476
16331
|
function getWebhookSecret(botType) {
|
|
17477
16332
|
const filePath = botType === "admin" ? TELEGRAM_ADMIN_WEBHOOK_SECRET_FILE : TELEGRAM_WEBHOOK_SECRET_FILE;
|
|
17478
16333
|
try {
|
|
17479
|
-
if (!
|
|
17480
|
-
const secret =
|
|
16334
|
+
if (!existsSync13(filePath)) return null;
|
|
16335
|
+
const secret = readFileSync14(filePath, "utf-8").trim();
|
|
17481
16336
|
return secret || null;
|
|
17482
16337
|
} catch {
|
|
17483
16338
|
return null;
|
|
@@ -17487,7 +16342,7 @@ function verifyWebhookSecret(headerValue, storedSecret) {
|
|
|
17487
16342
|
const a = Buffer.from(headerValue);
|
|
17488
16343
|
const b = Buffer.from(storedSecret);
|
|
17489
16344
|
if (a.length !== b.length) return false;
|
|
17490
|
-
return
|
|
16345
|
+
return timingSafeEqual(a, b);
|
|
17491
16346
|
}
|
|
17492
16347
|
async function handleInbound(params) {
|
|
17493
16348
|
const { chatId, senderId, text, botType, botToken, accountId, agentType } = params;
|
|
@@ -17635,9 +16490,9 @@ app6.post("/webhook", async (c) => {
|
|
|
17635
16490
|
var telegram_default = app6;
|
|
17636
16491
|
|
|
17637
16492
|
// server/routes/whatsapp.ts
|
|
17638
|
-
import { join as
|
|
16493
|
+
import { join as join8, resolve as resolve15, basename as basename4 } from "path";
|
|
17639
16494
|
import { readFile as readFile2, stat as stat3 } from "fs/promises";
|
|
17640
|
-
import { realpathSync as realpathSync2, readdirSync as readdirSync4, readFileSync as
|
|
16495
|
+
import { realpathSync as realpathSync2, readdirSync as readdirSync4, readFileSync as readFileSync15, existsSync as existsSync14 } from "fs";
|
|
17641
16496
|
|
|
17642
16497
|
// app/lib/whatsapp/login.ts
|
|
17643
16498
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
@@ -17743,8 +16598,8 @@ async function startLogin(opts) {
|
|
|
17743
16598
|
resetActiveLogin(accountId);
|
|
17744
16599
|
let resolveQr = null;
|
|
17745
16600
|
let rejectQr = null;
|
|
17746
|
-
const qrPromise = new Promise((
|
|
17747
|
-
resolveQr =
|
|
16601
|
+
const qrPromise = new Promise((resolve28, reject) => {
|
|
16602
|
+
resolveQr = resolve28;
|
|
17748
16603
|
rejectQr = reject;
|
|
17749
16604
|
});
|
|
17750
16605
|
const qrTimer = setTimeout(
|
|
@@ -17960,7 +16815,7 @@ function serializeWhatsAppSchema() {
|
|
|
17960
16815
|
|
|
17961
16816
|
// server/routes/whatsapp.ts
|
|
17962
16817
|
var TAG18 = "[whatsapp:api]";
|
|
17963
|
-
var
|
|
16818
|
+
var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
17964
16819
|
var app7 = new Hono2();
|
|
17965
16820
|
app7.get("/status", (c) => {
|
|
17966
16821
|
try {
|
|
@@ -17978,7 +16833,7 @@ app7.post("/login/start", async (c) => {
|
|
|
17978
16833
|
const body = await c.req.json().catch(() => ({}));
|
|
17979
16834
|
const accountId = validateAccountId(body.accountId);
|
|
17980
16835
|
const force = body.force ?? false;
|
|
17981
|
-
const authDir =
|
|
16836
|
+
const authDir = join8(MAXY_DIR, "credentials", "whatsapp", accountId);
|
|
17982
16837
|
const result = await startLogin({ accountId, authDir, force });
|
|
17983
16838
|
console.error(`${TAG18} login/start result account=${accountId} hasQr=${!!result.qrRaw}${result.selfPhone ? ` phone=${result.selfPhone}` : ""}`);
|
|
17984
16839
|
return c.json(result);
|
|
@@ -18115,17 +16970,17 @@ app7.post("/config", async (c) => {
|
|
|
18115
16970
|
return c.json({ ok: true, slug: currentSlug });
|
|
18116
16971
|
}
|
|
18117
16972
|
case "list-public-agents": {
|
|
18118
|
-
const agentsDir =
|
|
16973
|
+
const agentsDir = resolve15(account.accountDir, "agents");
|
|
18119
16974
|
const agents = [];
|
|
18120
|
-
if (
|
|
16975
|
+
if (existsSync14(agentsDir)) {
|
|
18121
16976
|
try {
|
|
18122
16977
|
const entries = readdirSync4(agentsDir, { withFileTypes: true });
|
|
18123
16978
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
18124
16979
|
if (!entry.isDirectory() || entry.name === "admin") continue;
|
|
18125
|
-
const configPath2 =
|
|
18126
|
-
if (!
|
|
16980
|
+
const configPath2 = resolve15(agentsDir, entry.name, "config.json");
|
|
16981
|
+
if (!existsSync14(configPath2)) continue;
|
|
18127
16982
|
try {
|
|
18128
|
-
const config = JSON.parse(
|
|
16983
|
+
const config = JSON.parse(readFileSync15(configPath2, "utf-8"));
|
|
18129
16984
|
agents.push({ slug: entry.name, displayName: config.displayName ?? entry.name });
|
|
18130
16985
|
} catch {
|
|
18131
16986
|
console.error(`${TAG18} config action=list-public-agents error="failed to parse config.json for agent ${entry.name}" \u2014 skipping`);
|
|
@@ -18197,10 +17052,10 @@ app7.post("/send-document", async (c) => {
|
|
|
18197
17052
|
if (!to || !filePath) {
|
|
18198
17053
|
return c.json({ error: "Missing required fields: to, filePath" }, 400);
|
|
18199
17054
|
}
|
|
18200
|
-
if (!maxyAccountId || !
|
|
17055
|
+
if (!maxyAccountId || !PLATFORM_ROOT6) {
|
|
18201
17056
|
return c.json({ error: "Cannot validate file path: missing account or platform context" }, 400);
|
|
18202
17057
|
}
|
|
18203
|
-
const accountDir =
|
|
17058
|
+
const accountDir = resolve15(PLATFORM_ROOT6, "..", "data/accounts", maxyAccountId);
|
|
18204
17059
|
let resolvedPath;
|
|
18205
17060
|
try {
|
|
18206
17061
|
resolvedPath = realpathSync2(filePath);
|
|
@@ -18337,16 +17192,16 @@ var whatsapp_default = app7;
|
|
|
18337
17192
|
|
|
18338
17193
|
// server/routes/onboarding.ts
|
|
18339
17194
|
import { spawn as spawn3, execFileSync as execFileSync2 } from "child_process";
|
|
18340
|
-
import { openSync as openSync4, closeSync as closeSync4, writeFileSync as
|
|
18341
|
-
import { resolve as
|
|
17195
|
+
import { openSync as openSync4, closeSync as closeSync4, writeFileSync as writeFileSync12, writeSync, existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync16, unlinkSync as unlinkSync4 } from "fs";
|
|
17196
|
+
import { resolve as resolve16, dirname as dirname7 } from "path";
|
|
18342
17197
|
import { createHash, randomUUID as randomUUID9 } from "crypto";
|
|
18343
|
-
var
|
|
17198
|
+
var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
18344
17199
|
function hashPin(pin) {
|
|
18345
17200
|
return createHash("sha256").update(pin).digest("hex");
|
|
18346
17201
|
}
|
|
18347
17202
|
function readUsersFile() {
|
|
18348
|
-
if (!
|
|
18349
|
-
const raw2 =
|
|
17203
|
+
if (!existsSync15(USERS_FILE)) return null;
|
|
17204
|
+
const raw2 = readFileSync16(USERS_FILE, "utf-8").trim();
|
|
18350
17205
|
if (!raw2) return [];
|
|
18351
17206
|
return JSON.parse(raw2);
|
|
18352
17207
|
}
|
|
@@ -18412,7 +17267,7 @@ app8.post("/claude-auth", async (c) => {
|
|
|
18412
17267
|
if (!vncReady) return c.json({ error: "VNC display failed to start" }, 500);
|
|
18413
17268
|
}
|
|
18414
17269
|
await ensureCdp(transport);
|
|
18415
|
-
|
|
17270
|
+
writeFileSync12(logPath("claude-auth"), "");
|
|
18416
17271
|
const chromiumWrapper = writeChromiumWrapper();
|
|
18417
17272
|
const x11Env = buildX11Env(chromiumWrapper, transport);
|
|
18418
17273
|
vncLog("claude-auth", { action: "start", transport });
|
|
@@ -18451,17 +17306,17 @@ app8.post("/set-pin", async (c) => {
|
|
|
18451
17306
|
}
|
|
18452
17307
|
const hash = hashPin(body.pin);
|
|
18453
17308
|
const userId = randomUUID9();
|
|
18454
|
-
|
|
18455
|
-
|
|
17309
|
+
mkdirSync10(dirname7(USERS_FILE), { recursive: true });
|
|
17310
|
+
writeFileSync12(USERS_FILE, JSON.stringify([{ userId, name: "Owner", pin: hash }]), { mode: 384 });
|
|
18456
17311
|
console.log(`[set-pin] created users.json: userId=${userId.slice(0, 8)}\u2026 hash=${hash.slice(0, 8)}\u2026`);
|
|
18457
17312
|
const account = resolveAccount();
|
|
18458
17313
|
if (account) {
|
|
18459
17314
|
try {
|
|
18460
|
-
const config = JSON.parse(
|
|
17315
|
+
const config = JSON.parse(readFileSync16(`${account.accountDir}/account.json`, "utf-8"));
|
|
18461
17316
|
if (!config.admins) config.admins = [];
|
|
18462
17317
|
if (!config.admins.some((a) => a.userId === userId)) {
|
|
18463
17318
|
config.admins.push({ userId, role: "owner" });
|
|
18464
|
-
|
|
17319
|
+
writeFileSync12(`${account.accountDir}/account.json`, JSON.stringify(config, null, 2) + "\n");
|
|
18465
17320
|
console.log(`[set-pin] added userId=${userId.slice(0, 8)}\u2026 to account.json admins`);
|
|
18466
17321
|
}
|
|
18467
17322
|
} catch (err) {
|
|
@@ -18499,7 +17354,7 @@ app8.delete("/set-pin", async (c) => {
|
|
|
18499
17354
|
unlinkSync4(USERS_FILE);
|
|
18500
17355
|
console.log(`[set-pin] cleared users.json (last entry removed): userId=${matchedUser.userId.slice(0, 8)}\u2026`);
|
|
18501
17356
|
} else {
|
|
18502
|
-
|
|
17357
|
+
writeFileSync12(USERS_FILE, JSON.stringify(remaining), { mode: 384 });
|
|
18503
17358
|
console.log(`[set-pin] removed entry from users.json: userId=${matchedUser.userId.slice(0, 8)}\u2026 remaining=${remaining.length}`);
|
|
18504
17359
|
}
|
|
18505
17360
|
return c.json({ ok: true });
|
|
@@ -18512,19 +17367,19 @@ app8.post("/skip", async (c) => {
|
|
|
18512
17367
|
}
|
|
18513
17368
|
const { accountId, accountDir } = account;
|
|
18514
17369
|
let agentName = "Maxy";
|
|
18515
|
-
const brandPath =
|
|
18516
|
-
if (brandPath &&
|
|
17370
|
+
const brandPath = PLATFORM_ROOT7 ? resolve16(PLATFORM_ROOT7, "config", "brand.json") : "";
|
|
17371
|
+
if (brandPath && existsSync15(brandPath)) {
|
|
18517
17372
|
try {
|
|
18518
|
-
const brand = JSON.parse(
|
|
17373
|
+
const brand = JSON.parse(readFileSync16(brandPath, "utf-8"));
|
|
18519
17374
|
if (brand.productName) agentName = brand.productName;
|
|
18520
17375
|
} catch (err) {
|
|
18521
17376
|
console.error(`[onboarding-skip] brand.json read failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
18522
17377
|
}
|
|
18523
17378
|
}
|
|
18524
|
-
const soulPath =
|
|
17379
|
+
const soulPath = resolve16(accountDir, "agents", "admin", "SOUL.md");
|
|
18525
17380
|
try {
|
|
18526
|
-
|
|
18527
|
-
|
|
17381
|
+
mkdirSync10(dirname7(soulPath), { recursive: true });
|
|
17382
|
+
writeFileSync12(soulPath, `You are ${agentName}, an AI operations manager.
|
|
18528
17383
|
`);
|
|
18529
17384
|
console.log(`[onboarding-skip] wrote SOUL.md: ${soulPath}`);
|
|
18530
17385
|
} catch (err) {
|
|
@@ -18562,9 +17417,9 @@ app8.post("/skip", async (c) => {
|
|
|
18562
17417
|
var onboarding_default = app8;
|
|
18563
17418
|
|
|
18564
17419
|
// server/routes/client-error.ts
|
|
18565
|
-
import { appendFileSync as
|
|
18566
|
-
import { join as
|
|
18567
|
-
var CLIENT_ERRORS_LOG =
|
|
17420
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync16, renameSync as renameSync5, statSync as statSync7 } from "fs";
|
|
17421
|
+
import { join as join9 } from "path";
|
|
17422
|
+
var CLIENT_ERRORS_LOG = join9(LOG_DIR, "client-errors.log");
|
|
18568
17423
|
var MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
18569
17424
|
var MAX_BODY_SIZE = 8 * 1024;
|
|
18570
17425
|
var MAX_STACK_LEN = 2e3;
|
|
@@ -18616,7 +17471,7 @@ function stackHead(stack) {
|
|
|
18616
17471
|
}
|
|
18617
17472
|
function rotateIfNeeded() {
|
|
18618
17473
|
try {
|
|
18619
|
-
if (!
|
|
17474
|
+
if (!existsSync16(CLIENT_ERRORS_LOG)) return;
|
|
18620
17475
|
const stats = statSync7(CLIENT_ERRORS_LOG);
|
|
18621
17476
|
if (stats.size < MAX_LOG_SIZE) return;
|
|
18622
17477
|
renameSync5(CLIENT_ERRORS_LOG, CLIENT_ERRORS_LOG + ".1");
|
|
@@ -18699,7 +17554,7 @@ app9.post("/", async (c) => {
|
|
|
18699
17554
|
tag: typeof body.tag === "string" ? truncate2(body.tag, 32) : void 0,
|
|
18700
17555
|
status: typeof body.status === "number" ? body.status : void 0
|
|
18701
17556
|
};
|
|
18702
|
-
|
|
17557
|
+
appendFileSync3(CLIENT_ERRORS_LOG, JSON.stringify(payload) + "\n", "utf-8");
|
|
18703
17558
|
} catch (err) {
|
|
18704
17559
|
console.error(`[client-error] append failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
18705
17560
|
}
|
|
@@ -18709,14 +17564,14 @@ app9.post("/", async (c) => {
|
|
|
18709
17564
|
var client_error_default = app9;
|
|
18710
17565
|
|
|
18711
17566
|
// server/routes/admin/session.ts
|
|
18712
|
-
import { readFileSync as
|
|
17567
|
+
import { readFileSync as readFileSync17, existsSync as existsSync17 } from "fs";
|
|
18713
17568
|
import { createHash as createHash2 } from "crypto";
|
|
18714
17569
|
function hashPin2(pin) {
|
|
18715
17570
|
return createHash2("sha256").update(pin).digest("hex");
|
|
18716
17571
|
}
|
|
18717
17572
|
function readUsersFile2() {
|
|
18718
|
-
if (!
|
|
18719
|
-
const raw2 =
|
|
17573
|
+
if (!existsSync17(USERS_FILE)) return null;
|
|
17574
|
+
const raw2 = readFileSync17(USERS_FILE, "utf-8").trim();
|
|
18720
17575
|
if (!raw2) return [];
|
|
18721
17576
|
return JSON.parse(raw2);
|
|
18722
17577
|
}
|
|
@@ -18738,11 +17593,7 @@ async function createAdminSession(accountId, thinkingView, userId, userName) {
|
|
|
18738
17593
|
businessName = branding?.name || void 0;
|
|
18739
17594
|
} catch {
|
|
18740
17595
|
}
|
|
18741
|
-
|
|
18742
|
-
if (userId) {
|
|
18743
|
-
conversationId = await createNewAdminConversation(userId, accountId, sessionKey);
|
|
18744
|
-
}
|
|
18745
|
-
console.log(`[session] ${(/* @__PURE__ */ new Date()).toISOString()} admin session created: userId=${userId ?? "\u2013"} userName=${userName ?? "\u2013"} accountId=${accountId} conversationId=${conversationId?.slice(0, 8) ?? "\u2013"} sessionKey=${sessionKey.slice(0, 8)}`);
|
|
17596
|
+
console.log(`[session] ${(/* @__PURE__ */ new Date()).toISOString()} admin session created: userId=${userId ?? "\u2013"} userName=${userName ?? "\u2013"} accountId=${accountId} conversationId=deferred sessionKey=${sessionKey.slice(0, 8)}`);
|
|
18746
17597
|
return {
|
|
18747
17598
|
session_key: sessionKey,
|
|
18748
17599
|
agent_id: "admin",
|
|
@@ -18751,7 +17602,7 @@ async function createAdminSession(accountId, thinkingView, userId, userName) {
|
|
|
18751
17602
|
thinkingView: effectiveThinkingView,
|
|
18752
17603
|
onboardingComplete,
|
|
18753
17604
|
businessName,
|
|
18754
|
-
conversationId
|
|
17605
|
+
conversationId: null
|
|
18755
17606
|
};
|
|
18756
17607
|
}
|
|
18757
17608
|
var app10 = new Hono2();
|
|
@@ -18846,11 +17697,11 @@ app10.post("/", async (c) => {
|
|
|
18846
17697
|
var session_default2 = app10;
|
|
18847
17698
|
|
|
18848
17699
|
// server/routes/admin/chat.ts
|
|
18849
|
-
import { resolve as
|
|
17700
|
+
import { resolve as resolve17 } from "path";
|
|
18850
17701
|
|
|
18851
17702
|
// app/lib/script-stream-tailer.ts
|
|
18852
17703
|
import * as childProcess from "child_process";
|
|
18853
|
-
import { appendFileSync as
|
|
17704
|
+
import { appendFileSync as appendFileSync4, createReadStream as createReadStream2, mkdirSync as mkdirSync11, statSync as statSync8 } from "fs";
|
|
18854
17705
|
import { dirname as dirname8 } from "path";
|
|
18855
17706
|
import { StringDecoder as StringDecoder2 } from "string_decoder";
|
|
18856
17707
|
var SCRIPT_STREAM_RE = /^\[([^\]]+)\] \[script:([a-z][a-z0-9-]*)((?::[a-z0-9:_-]+)?)\] (.*)$/;
|
|
@@ -18960,8 +17811,8 @@ function writeRouteMilestone(streamLogPath, scope, line) {
|
|
|
18960
17811
|
}
|
|
18961
17812
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
18962
17813
|
try {
|
|
18963
|
-
|
|
18964
|
-
|
|
17814
|
+
mkdirSync11(dirname8(streamLogPath), { recursive: true });
|
|
17815
|
+
appendFileSync4(streamLogPath, `[${ts}] [script:${scope}] ${line}
|
|
18965
17816
|
`);
|
|
18966
17817
|
} catch (err) {
|
|
18967
17818
|
console.error(
|
|
@@ -19291,7 +18142,7 @@ app11.post("/", requireAdminSession, async (c) => {
|
|
|
19291
18142
|
try {
|
|
19292
18143
|
registerAdminSSE(sseEntry);
|
|
19293
18144
|
if (sseConvId) {
|
|
19294
|
-
const streamLogPath =
|
|
18145
|
+
const streamLogPath = resolve17(account.accountDir, "logs", `claude-agent-stream-${sseConvId}.log`);
|
|
19295
18146
|
tailer = startScriptStreamTailer({
|
|
19296
18147
|
path: streamLogPath,
|
|
19297
18148
|
onEvent: (event) => {
|
|
@@ -19416,8 +18267,8 @@ app12.post("/", requireAdminSession, async (c) => {
|
|
|
19416
18267
|
var compact_default = app12;
|
|
19417
18268
|
|
|
19418
18269
|
// server/routes/admin/logs.ts
|
|
19419
|
-
import { existsSync as
|
|
19420
|
-
import { resolve as
|
|
18270
|
+
import { existsSync as existsSync18, readdirSync as readdirSync5, readFileSync as readFileSync18, statSync as statSync9 } from "fs";
|
|
18271
|
+
import { resolve as resolve18, basename as basename5 } from "path";
|
|
19421
18272
|
var TAIL_BYTES = 8192;
|
|
19422
18273
|
var app13 = new Hono2();
|
|
19423
18274
|
app13.get("/", async (c) => {
|
|
@@ -19426,16 +18277,16 @@ app13.get("/", async (c) => {
|
|
|
19426
18277
|
const conversationIdParam = c.req.query("conversationId");
|
|
19427
18278
|
const download = c.req.query("download") === "1";
|
|
19428
18279
|
const account = resolveAccount();
|
|
19429
|
-
const accountLogDir2 = account ?
|
|
18280
|
+
const accountLogDir2 = account ? resolve18(account.accountDir, "logs") : null;
|
|
19430
18281
|
if (fileParam) {
|
|
19431
18282
|
const safe = basename5(fileParam);
|
|
19432
18283
|
const searched = [];
|
|
19433
18284
|
for (const dir of [accountLogDir2, LOG_DIR]) {
|
|
19434
18285
|
if (!dir) continue;
|
|
19435
|
-
const filePath =
|
|
18286
|
+
const filePath = resolve18(dir, safe);
|
|
19436
18287
|
searched.push(filePath);
|
|
19437
18288
|
try {
|
|
19438
|
-
const content =
|
|
18289
|
+
const content = readFileSync18(filePath, "utf-8");
|
|
19439
18290
|
const headers = { "Content-Type": "text/plain; charset=utf-8" };
|
|
19440
18291
|
if (download) headers["Content-Disposition"] = `attachment; filename="${safe}"`;
|
|
19441
18292
|
return new Response(content, { headers });
|
|
@@ -19474,10 +18325,10 @@ app13.get("/", async (c) => {
|
|
|
19474
18325
|
const searched = [];
|
|
19475
18326
|
for (const dir of [accountLogDir2, LOG_DIR]) {
|
|
19476
18327
|
if (!dir) continue;
|
|
19477
|
-
const filePath =
|
|
18328
|
+
const filePath = resolve18(dir, fileName);
|
|
19478
18329
|
searched.push(filePath);
|
|
19479
18330
|
try {
|
|
19480
|
-
const content =
|
|
18331
|
+
const content = readFileSync18(filePath, "utf-8");
|
|
19481
18332
|
const headers = { "Content-Type": "text/plain; charset=utf-8" };
|
|
19482
18333
|
if (download) headers["Content-Disposition"] = `attachment; filename="${fileName}"`;
|
|
19483
18334
|
return new Response(content, { headers });
|
|
@@ -19492,7 +18343,7 @@ app13.get("/", async (c) => {
|
|
|
19492
18343
|
const seen = /* @__PURE__ */ new Set();
|
|
19493
18344
|
const logs = {};
|
|
19494
18345
|
for (const dir of [accountLogDir2, LOG_DIR]) {
|
|
19495
|
-
if (!dir || !
|
|
18346
|
+
if (!dir || !existsSync18(dir)) continue;
|
|
19496
18347
|
let files;
|
|
19497
18348
|
try {
|
|
19498
18349
|
files = readdirSync5(dir).filter((f) => f.endsWith(".log"));
|
|
@@ -19501,10 +18352,10 @@ app13.get("/", async (c) => {
|
|
|
19501
18352
|
console.warn(`[admin/logs] readdir-fail dir=${dir} reason=${reason}`);
|
|
19502
18353
|
continue;
|
|
19503
18354
|
}
|
|
19504
|
-
files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync9(
|
|
18355
|
+
files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync9(resolve18(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).forEach(({ name }) => {
|
|
19505
18356
|
seen.add(name);
|
|
19506
18357
|
try {
|
|
19507
|
-
const content =
|
|
18358
|
+
const content = readFileSync18(resolve18(dir, name));
|
|
19508
18359
|
const tail = content.length > TAIL_BYTES ? content.subarray(content.length - TAIL_BYTES).toString("utf-8") : content.toString("utf-8");
|
|
19509
18360
|
logs[name] = tail.trim() || "(empty)";
|
|
19510
18361
|
} catch (err) {
|
|
@@ -19544,8 +18395,8 @@ var claude_info_default = app14;
|
|
|
19544
18395
|
|
|
19545
18396
|
// server/routes/admin/attachment.ts
|
|
19546
18397
|
import { readFile as readFile3, readdir } from "fs/promises";
|
|
19547
|
-
import { existsSync as
|
|
19548
|
-
import { resolve as
|
|
18398
|
+
import { existsSync as existsSync19 } from "fs";
|
|
18399
|
+
import { resolve as resolve19 } from "path";
|
|
19549
18400
|
var app15 = new Hono2();
|
|
19550
18401
|
app15.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
19551
18402
|
const attachmentId = c.req.param("attachmentId");
|
|
@@ -19557,12 +18408,12 @@ app15.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
|
19557
18408
|
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(attachmentId)) {
|
|
19558
18409
|
return new Response("Not found", { status: 404 });
|
|
19559
18410
|
}
|
|
19560
|
-
const dir =
|
|
19561
|
-
if (!
|
|
18411
|
+
const dir = resolve19(ATTACHMENTS_ROOT, accountId, attachmentId);
|
|
18412
|
+
if (!existsSync19(dir)) {
|
|
19562
18413
|
return new Response("Not found", { status: 404 });
|
|
19563
18414
|
}
|
|
19564
|
-
const metaPath =
|
|
19565
|
-
if (!
|
|
18415
|
+
const metaPath = resolve19(dir, `${attachmentId}.meta.json`);
|
|
18416
|
+
if (!existsSync19(metaPath)) {
|
|
19566
18417
|
return new Response("Not found", { status: 404 });
|
|
19567
18418
|
}
|
|
19568
18419
|
let meta;
|
|
@@ -19576,7 +18427,7 @@ app15.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
|
19576
18427
|
if (!dataFile) {
|
|
19577
18428
|
return new Response("Not found", { status: 404 });
|
|
19578
18429
|
}
|
|
19579
|
-
const filePath =
|
|
18430
|
+
const filePath = resolve19(dir, dataFile);
|
|
19580
18431
|
const buffer = await readFile3(filePath);
|
|
19581
18432
|
return new Response(new Uint8Array(buffer), {
|
|
19582
18433
|
headers: {
|
|
@@ -19589,8 +18440,8 @@ app15.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
|
19589
18440
|
var attachment_default = app15;
|
|
19590
18441
|
|
|
19591
18442
|
// server/routes/admin/account.ts
|
|
19592
|
-
import { readFileSync as
|
|
19593
|
-
import { resolve as
|
|
18443
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync13 } from "fs";
|
|
18444
|
+
import { resolve as resolve20 } from "path";
|
|
19594
18445
|
var VALID_CONTEXT_MODES = ["managed", "claude-code"];
|
|
19595
18446
|
var app16 = new Hono2();
|
|
19596
18447
|
app16.patch("/", requireAdminSession, async (c) => {
|
|
@@ -19606,12 +18457,12 @@ app16.patch("/", requireAdminSession, async (c) => {
|
|
|
19606
18457
|
}
|
|
19607
18458
|
const account = resolveAccount();
|
|
19608
18459
|
if (!account) return c.json({ error: "No account configured" }, 500);
|
|
19609
|
-
const configPath2 =
|
|
18460
|
+
const configPath2 = resolve20(account.accountDir, "account.json");
|
|
19610
18461
|
try {
|
|
19611
|
-
const raw2 =
|
|
18462
|
+
const raw2 = readFileSync19(configPath2, "utf-8");
|
|
19612
18463
|
const config = JSON.parse(raw2);
|
|
19613
18464
|
config.contextMode = contextMode;
|
|
19614
|
-
|
|
18465
|
+
writeFileSync13(configPath2, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
19615
18466
|
console.error(`[account-update] contextMode=${contextMode}`);
|
|
19616
18467
|
return c.json({ ok: true, contextMode });
|
|
19617
18468
|
} catch (err) {
|
|
@@ -19622,24 +18473,24 @@ app16.patch("/", requireAdminSession, async (c) => {
|
|
|
19622
18473
|
var account_default = app16;
|
|
19623
18474
|
|
|
19624
18475
|
// server/routes/admin/agents.ts
|
|
19625
|
-
import { resolve as
|
|
19626
|
-
import { readdirSync as readdirSync6, readFileSync as
|
|
18476
|
+
import { resolve as resolve21 } from "path";
|
|
18477
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync20, existsSync as existsSync20, rmSync as rmSync3 } from "fs";
|
|
19627
18478
|
var app17 = new Hono2();
|
|
19628
18479
|
app17.get("/", (c) => {
|
|
19629
18480
|
const account = resolveAccount();
|
|
19630
18481
|
if (!account) return c.json({ agents: [] });
|
|
19631
|
-
const agentsDir =
|
|
19632
|
-
if (!
|
|
18482
|
+
const agentsDir = resolve21(account.accountDir, "agents");
|
|
18483
|
+
if (!existsSync20(agentsDir)) return c.json({ agents: [] });
|
|
19633
18484
|
const agents = [];
|
|
19634
18485
|
try {
|
|
19635
18486
|
const entries = readdirSync6(agentsDir, { withFileTypes: true });
|
|
19636
18487
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
19637
18488
|
if (!entry.isDirectory()) continue;
|
|
19638
18489
|
if (entry.name === "admin") continue;
|
|
19639
|
-
const configPath2 =
|
|
19640
|
-
if (!
|
|
18490
|
+
const configPath2 = resolve21(agentsDir, entry.name, "config.json");
|
|
18491
|
+
if (!existsSync20(configPath2)) continue;
|
|
19641
18492
|
try {
|
|
19642
|
-
const config = JSON.parse(
|
|
18493
|
+
const config = JSON.parse(readFileSync20(configPath2, "utf-8"));
|
|
19643
18494
|
agents.push({
|
|
19644
18495
|
slug: entry.name,
|
|
19645
18496
|
displayName: config.displayName ?? entry.name,
|
|
@@ -19665,8 +18516,8 @@ app17.delete("/:slug", (c) => {
|
|
|
19665
18516
|
if (slug.includes("/") || slug.includes("..") || slug.includes("\\")) {
|
|
19666
18517
|
return c.json({ error: "Invalid agent slug" }, 400);
|
|
19667
18518
|
}
|
|
19668
|
-
const agentDir =
|
|
19669
|
-
if (!
|
|
18519
|
+
const agentDir = resolve21(account.accountDir, "agents", slug);
|
|
18520
|
+
if (!existsSync20(agentDir)) {
|
|
19670
18521
|
return c.json({ error: "Agent not found" }, 404);
|
|
19671
18522
|
}
|
|
19672
18523
|
try {
|
|
@@ -19681,27 +18532,27 @@ app17.delete("/:slug", (c) => {
|
|
|
19681
18532
|
var agents_default = app17;
|
|
19682
18533
|
|
|
19683
18534
|
// server/routes/admin/version.ts
|
|
19684
|
-
import { existsSync as
|
|
19685
|
-
import { resolve as
|
|
19686
|
-
var
|
|
18535
|
+
import { existsSync as existsSync21, readFileSync as readFileSync21 } from "fs";
|
|
18536
|
+
import { resolve as resolve22, join as join10 } from "path";
|
|
18537
|
+
var PLATFORM_ROOT8 = process.env.MAXY_PLATFORM_ROOT ?? resolve22(process.cwd(), "..");
|
|
19687
18538
|
var brandHostname = "maxy";
|
|
19688
18539
|
var brandNpmPackage = "@rubytech/create-maxy";
|
|
19689
|
-
var brandJsonPath =
|
|
19690
|
-
if (
|
|
18540
|
+
var brandJsonPath = join10(PLATFORM_ROOT8, "config", "brand.json");
|
|
18541
|
+
if (existsSync21(brandJsonPath)) {
|
|
19691
18542
|
try {
|
|
19692
|
-
const brand = JSON.parse(
|
|
18543
|
+
const brand = JSON.parse(readFileSync21(brandJsonPath, "utf-8"));
|
|
19693
18544
|
if (brand.hostname) brandHostname = brand.hostname;
|
|
19694
18545
|
if (brand.npm?.packageName) brandNpmPackage = brand.npm.packageName;
|
|
19695
18546
|
} catch {
|
|
19696
18547
|
}
|
|
19697
18548
|
}
|
|
19698
|
-
var VERSION_FILE =
|
|
18549
|
+
var VERSION_FILE = resolve22(PLATFORM_ROOT8, `config/.${brandHostname}-version`);
|
|
19699
18550
|
var NPM_PACKAGE = brandNpmPackage;
|
|
19700
18551
|
var REGISTRY_URL = `https://registry.npmjs.org/${NPM_PACKAGE}/latest`;
|
|
19701
18552
|
var FETCH_TIMEOUT_MS = 5e3;
|
|
19702
18553
|
function readInstalled() {
|
|
19703
|
-
if (!
|
|
19704
|
-
const content =
|
|
18554
|
+
if (!existsSync21(VERSION_FILE)) return "unknown";
|
|
18555
|
+
const content = readFileSync21(VERSION_FILE, "utf-8").trim();
|
|
19705
18556
|
return content || "unknown";
|
|
19706
18557
|
}
|
|
19707
18558
|
async function fetchLatest() {
|
|
@@ -19811,16 +18662,10 @@ app19.post("/new", requireAdminSession, async (c) => {
|
|
|
19811
18662
|
const newSessionKey = crypto3.randomUUID();
|
|
19812
18663
|
const userName = getUserNameForSession(oldSessionKey);
|
|
19813
18664
|
registerSession(newSessionKey, "admin", accountId, void 0, userId, userName);
|
|
19814
|
-
const conversationId = await createNewAdminConversation(userId, accountId, newSessionKey);
|
|
19815
|
-
if (!conversationId) {
|
|
19816
|
-
unregisterSession(newSessionKey);
|
|
19817
|
-
console.error(`[session] ${(/* @__PURE__ */ new Date()).toISOString()} new-conversation-failed: userId=${userId} accountId=${accountId.slice(0, 8)}\u2026 oldSessionKey=${oldSessionKey.slice(0, 8)}\u2026 newSessionKey=${newSessionKey.slice(0, 8)}\u2026`);
|
|
19818
|
-
return c.json({ error: "Failed to create conversation" }, 500);
|
|
19819
|
-
}
|
|
19820
18665
|
const previousConversationId = clearSessionHistory(oldSessionKey);
|
|
19821
18666
|
unregisterSession(oldSessionKey);
|
|
19822
|
-
console.log(`[session] ${(/* @__PURE__ */ new Date()).toISOString()} session reset for new conversation: oldSessionKey=${oldSessionKey.slice(0, 8)}\u2026 newSessionKey=${newSessionKey.slice(0, 8)}\u2026 previousConversationId=${previousConversationId?.slice(0, 8) ?? "none"}\u2026 newConversationId
|
|
19823
|
-
return c.json({ session_key: newSessionKey, conversationId });
|
|
18667
|
+
console.log(`[session] ${(/* @__PURE__ */ new Date()).toISOString()} session reset for new conversation: oldSessionKey=${oldSessionKey.slice(0, 8)}\u2026 newSessionKey=${newSessionKey.slice(0, 8)}\u2026 previousConversationId=${previousConversationId?.slice(0, 8) ?? "none"}\u2026 newConversationId=deferred`);
|
|
18668
|
+
return c.json({ session_key: newSessionKey, conversationId: null });
|
|
19824
18669
|
});
|
|
19825
18670
|
app19.delete("/:id", requireAdminSession, async (c) => {
|
|
19826
18671
|
const conversationId = c.req.param("id");
|
|
@@ -20194,9 +19039,9 @@ app23.post("/", async (c) => {
|
|
|
20194
19039
|
var events_default = app23;
|
|
20195
19040
|
|
|
20196
19041
|
// server/routes/admin/cloudflare.ts
|
|
20197
|
-
import { homedir as
|
|
20198
|
-
import { resolve as
|
|
20199
|
-
import { readFileSync as
|
|
19042
|
+
import { homedir as homedir3 } from "os";
|
|
19043
|
+
import { resolve as resolve24 } from "path";
|
|
19044
|
+
import { readFileSync as readFileSync23 } from "fs";
|
|
20200
19045
|
|
|
20201
19046
|
// app/lib/dns-label.ts
|
|
20202
19047
|
var VALID_LABEL = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
@@ -20212,14 +19057,14 @@ function isValidDomain(value) {
|
|
|
20212
19057
|
}
|
|
20213
19058
|
|
|
20214
19059
|
// app/lib/alias-domains.ts
|
|
20215
|
-
import { existsSync as
|
|
19060
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync12, readFileSync as readFileSync22, writeFileSync as writeFileSync14 } from "fs";
|
|
20216
19061
|
import { dirname as dirname9 } from "path";
|
|
20217
|
-
import { resolve as
|
|
20218
|
-
var ALIAS_DOMAINS_PATH =
|
|
19062
|
+
import { resolve as resolve23 } from "path";
|
|
19063
|
+
var ALIAS_DOMAINS_PATH = resolve23(MAXY_DIR, "alias-domains.json");
|
|
20219
19064
|
function readExisting() {
|
|
20220
|
-
if (!
|
|
19065
|
+
if (!existsSync22(ALIAS_DOMAINS_PATH)) return /* @__PURE__ */ new Set();
|
|
20221
19066
|
try {
|
|
20222
|
-
const parsed = JSON.parse(
|
|
19067
|
+
const parsed = JSON.parse(readFileSync22(ALIAS_DOMAINS_PATH, "utf-8"));
|
|
20223
19068
|
if (!Array.isArray(parsed)) return /* @__PURE__ */ new Set();
|
|
20224
19069
|
return new Set(parsed.filter((h) => typeof h === "string"));
|
|
20225
19070
|
} catch {
|
|
@@ -20230,18 +19075,18 @@ function addAliasDomain(hostname2) {
|
|
|
20230
19075
|
const existing = readExisting();
|
|
20231
19076
|
if (existing.has(hostname2)) return;
|
|
20232
19077
|
existing.add(hostname2);
|
|
20233
|
-
|
|
20234
|
-
|
|
19078
|
+
mkdirSync12(dirname9(ALIAS_DOMAINS_PATH), { recursive: true });
|
|
19079
|
+
writeFileSync14(ALIAS_DOMAINS_PATH, JSON.stringify([...existing], null, 2) + "\n", "utf-8");
|
|
20235
19080
|
}
|
|
20236
19081
|
|
|
20237
19082
|
// server/routes/admin/cloudflare.ts
|
|
20238
19083
|
var SETUP_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
20239
19084
|
var DOMAINS_TIMEOUT_MS = 40 * 1e3;
|
|
20240
19085
|
function loadBrandInfo() {
|
|
20241
|
-
const
|
|
20242
|
-
const brandPath =
|
|
19086
|
+
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? resolve24(process.cwd(), "..");
|
|
19087
|
+
const brandPath = resolve24(platformRoot2, "config", "brand.json");
|
|
20243
19088
|
try {
|
|
20244
|
-
const parsed = JSON.parse(
|
|
19089
|
+
const parsed = JSON.parse(readFileSync23(brandPath, "utf-8"));
|
|
20245
19090
|
const hostname2 = typeof parsed.hostname === "string" && parsed.hostname ? parsed.hostname : "maxy";
|
|
20246
19091
|
const configDir2 = typeof parsed.configDir === "string" && parsed.configDir ? parsed.configDir : ".maxy";
|
|
20247
19092
|
return { hostname: hostname2, configDir: configDir2 };
|
|
@@ -20344,7 +19189,7 @@ app24.get("/domains", requireAdminSession, async (c) => {
|
|
|
20344
19189
|
streamLogPath = streamLogPathFor(accountId, correlationId).streamLogPath;
|
|
20345
19190
|
log2(`phase=stream-log-resolved path=${streamLogPath}`);
|
|
20346
19191
|
const brand = loadBrandInfo();
|
|
20347
|
-
const scriptPath =
|
|
19192
|
+
const scriptPath = resolve24(homedir3(), "list-cf-domains.sh");
|
|
20348
19193
|
const result = await runFormSpawn({
|
|
20349
19194
|
scriptPath,
|
|
20350
19195
|
args: [brand.hostname],
|
|
@@ -20469,7 +19314,7 @@ app24.post("/setup", requireAdminSession, async (c) => {
|
|
|
20469
19314
|
}
|
|
20470
19315
|
streamLogPath = streamLogPathFor(accountId, correlationId).streamLogPath;
|
|
20471
19316
|
log2(`phase=stream-log-resolved path=${streamLogPath}`);
|
|
20472
|
-
const scriptPath =
|
|
19317
|
+
const scriptPath = resolve24(homedir3(), "setup-tunnel.sh");
|
|
20473
19318
|
const args = [brand.hostname, String(port2), adminFqdn];
|
|
20474
19319
|
if (publicFqdn) args.push(publicFqdn);
|
|
20475
19320
|
if (apex) args.push(apex);
|
|
@@ -20538,17 +19383,17 @@ var cloudflare_default = app24;
|
|
|
20538
19383
|
import { createReadStream as createReadStream3 } from "fs";
|
|
20539
19384
|
import { readdir as readdir2, readFile as readFile4, stat as stat4, mkdir as mkdir3, writeFile as writeFile4, unlink as unlink2 } from "fs/promises";
|
|
20540
19385
|
import { realpathSync as realpathSync4 } from "fs";
|
|
20541
|
-
import { basename as basename6, dirname as dirname10, join as
|
|
19386
|
+
import { basename as basename6, dirname as dirname10, join as join11, resolve as resolve26, sep as sep2 } from "path";
|
|
20542
19387
|
import { Readable as Readable3 } from "stream";
|
|
20543
19388
|
|
|
20544
19389
|
// app/lib/data-path.ts
|
|
20545
19390
|
import { realpathSync as realpathSync3 } from "fs";
|
|
20546
|
-
import { resolve as
|
|
20547
|
-
var
|
|
20548
|
-
var DATA_ROOT =
|
|
19391
|
+
import { resolve as resolve25, normalize, sep, relative } from "path";
|
|
19392
|
+
var PLATFORM_ROOT9 = process.env.MAXY_PLATFORM_ROOT ?? resolve25(process.cwd(), "../platform");
|
|
19393
|
+
var DATA_ROOT = resolve25(PLATFORM_ROOT9, "..", "data");
|
|
20549
19394
|
function resolveDataPath(raw2) {
|
|
20550
19395
|
const cleaned = normalize("/" + (raw2 ?? "").replace(/\\/g, "/")).replace(/^\/+/, "");
|
|
20551
|
-
const absolute =
|
|
19396
|
+
const absolute = resolve25(DATA_ROOT, cleaned);
|
|
20552
19397
|
let dataRootReal;
|
|
20553
19398
|
try {
|
|
20554
19399
|
dataRootReal = realpathSync3(DATA_ROOT);
|
|
@@ -20815,7 +19660,7 @@ async function cascadeDeleteDocument(params) {
|
|
|
20815
19660
|
var UUID_RE3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
20816
19661
|
async function readMeta(absDir, baseName) {
|
|
20817
19662
|
try {
|
|
20818
|
-
const raw2 = await readFile4(
|
|
19663
|
+
const raw2 = await readFile4(join11(absDir, `${baseName}.meta.json`), "utf8");
|
|
20819
19664
|
const parsed = JSON.parse(raw2);
|
|
20820
19665
|
if (typeof parsed?.filename === "string") {
|
|
20821
19666
|
return { filename: parsed.filename, mimeType: typeof parsed.mimeType === "string" ? parsed.mimeType : void 0 };
|
|
@@ -20826,7 +19671,7 @@ async function readMeta(absDir, baseName) {
|
|
|
20826
19671
|
}
|
|
20827
19672
|
async function readAccountNames() {
|
|
20828
19673
|
const map = /* @__PURE__ */ new Map();
|
|
20829
|
-
const accountsDir =
|
|
19674
|
+
const accountsDir = resolve26(DATA_ROOT, "accounts");
|
|
20830
19675
|
let names;
|
|
20831
19676
|
try {
|
|
20832
19677
|
names = await readdir2(accountsDir);
|
|
@@ -20835,7 +19680,7 @@ async function readAccountNames() {
|
|
|
20835
19680
|
}
|
|
20836
19681
|
for (const name of names) {
|
|
20837
19682
|
if (!UUID_RE3.test(name)) continue;
|
|
20838
|
-
const configPath2 =
|
|
19683
|
+
const configPath2 = resolve26(accountsDir, name, "account.json");
|
|
20839
19684
|
try {
|
|
20840
19685
|
const raw2 = await readFile4(configPath2, "utf8");
|
|
20841
19686
|
const parsed = JSON.parse(raw2);
|
|
@@ -20853,7 +19698,7 @@ async function readAccountNames() {
|
|
|
20853
19698
|
}
|
|
20854
19699
|
async function enrich(absolute, entry, accountNames) {
|
|
20855
19700
|
if (entry.kind === "directory" && UUID_RE3.test(entry.name)) {
|
|
20856
|
-
const meta = await readMeta(
|
|
19701
|
+
const meta = await readMeta(join11(absolute, entry.name), entry.name);
|
|
20857
19702
|
if (meta?.filename) {
|
|
20858
19703
|
entry.displayName = meta.filename;
|
|
20859
19704
|
entry.mimeType = meta.mimeType;
|
|
@@ -20912,7 +19757,7 @@ app25.get("/", requireAdminSession, async (c) => {
|
|
|
20912
19757
|
continue;
|
|
20913
19758
|
}
|
|
20914
19759
|
try {
|
|
20915
|
-
const entryPath =
|
|
19760
|
+
const entryPath = join11(absolute, name);
|
|
20916
19761
|
const s = await stat4(entryPath);
|
|
20917
19762
|
entries.push({
|
|
20918
19763
|
name,
|
|
@@ -21026,8 +19871,8 @@ app25.post("/upload", requireAdminSession, async (c) => {
|
|
|
21026
19871
|
}
|
|
21027
19872
|
const safeName = basename6(file.name).replace(/[\0/\\]/g, "_");
|
|
21028
19873
|
const finalName = `${Date.now()}-${safeName}`;
|
|
21029
|
-
const destDir =
|
|
21030
|
-
const destPath =
|
|
19874
|
+
const destDir = resolve26(DATA_ROOT, "uploads", accountId);
|
|
19875
|
+
const destPath = resolve26(destDir, finalName);
|
|
21031
19876
|
try {
|
|
21032
19877
|
await mkdir3(destDir, { recursive: true });
|
|
21033
19878
|
const dataRootReal = realpathSync4(DATA_ROOT);
|
|
@@ -21085,7 +19930,7 @@ app25.delete("/", requireAdminSession, async (c) => {
|
|
|
21085
19930
|
}
|
|
21086
19931
|
const dot = base.lastIndexOf(".");
|
|
21087
19932
|
const stem = dot === -1 ? base : base.slice(0, dot);
|
|
21088
|
-
const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ?
|
|
19933
|
+
const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ? join11(dirname10(absolute), `${stem}.meta.json`) : null;
|
|
21089
19934
|
await unlink2(absolute);
|
|
21090
19935
|
if (sidecarPath) {
|
|
21091
19936
|
try {
|
|
@@ -21180,24 +20025,35 @@ var GRAPH_LABEL_COLOURS = {
|
|
|
21180
20025
|
Review: "#059669",
|
|
21181
20026
|
ImageObject: "#6EE7B7",
|
|
21182
20027
|
// Conversational
|
|
20028
|
+
//
|
|
20029
|
+
// Task 649 palette — the four sublabelled conversation/message labels
|
|
20030
|
+
// (AdminConversation, PublicConversation, UserMessage, AssistantMessage)
|
|
20031
|
+
// are assigned four pairwise-distinguishable hues: indigo / purple /
|
|
20032
|
+
// orange / yellow. The Task 633 violet-shade split (darker for admin,
|
|
20033
|
+
// lighter for public) was indistinguishable on canvas and in the legend;
|
|
20034
|
+
// hue-shift replaces lightness-shift.
|
|
20035
|
+
//
|
|
20036
|
+
// Constraints honoured:
|
|
20037
|
+
// - Pairwise distinct among the four in-family labels.
|
|
20038
|
+
// - UserMessage=#F97316 reserved (Person/Preference orange cue) —
|
|
20039
|
+
// the other three are non-orange.
|
|
20040
|
+
// - No collision with Task/Project/Event pinks (#DB2777/#BE185D/#EC4899),
|
|
20041
|
+
// Knowledge greens, Workflow cyans, LocalBusiness blues.
|
|
20042
|
+
// - Conversation base (#7C3AED) kept as pre-backfill fallback for
|
|
20043
|
+
// nodes that pre-date the AdminConversation/PublicConversation split.
|
|
20044
|
+
// - Message base reassigned away from old #A78BFA to avoid near-
|
|
20045
|
+
// collision with the new PublicConversation=#A855F7; slate signals
|
|
20046
|
+
// "legacy/system/tool" — nodes carrying only :Message without a
|
|
20047
|
+
// role-sublabel.
|
|
20048
|
+
// - Out of family (intentionally not touched): OnboardingState=#8B5CF6
|
|
20049
|
+
// sits in the violet neighbourhood; palette reassignment outside
|
|
20050
|
+
// the conversation/message family is out of scope for Task 649.
|
|
21183
20051
|
Conversation: "#7C3AED",
|
|
21184
|
-
|
|
21185
|
-
|
|
21186
|
-
|
|
21187
|
-
// alone. Both within the Conversation family — the hue shift is subtle
|
|
21188
|
-
// enough to stay legible on adjacent nodes yet distinct enough to be
|
|
21189
|
-
// unmistakable in the popover legend.
|
|
21190
|
-
AdminConversation: "#5B21B6",
|
|
21191
|
-
PublicConversation: "#A78BFA",
|
|
21192
|
-
Message: "#A78BFA",
|
|
21193
|
-
// Task 633 — user vs assistant disambiguation on /graph. UserMessage
|
|
21194
|
-
// picks up the Person/Preference orange family cue (the user is a
|
|
21195
|
-
// person); AssistantMessage stays violet-adjacent to reinforce the
|
|
21196
|
-
// AdminConversation cue (both are admin-side). system/tool Messages
|
|
21197
|
-
// (not written by the persist path today; reserved shape) stay on the
|
|
21198
|
-
// base `:Message` colour.
|
|
20052
|
+
AdminConversation: "#4338CA",
|
|
20053
|
+
PublicConversation: "#A855F7",
|
|
20054
|
+
Message: "#64748B",
|
|
21199
20055
|
UserMessage: "#F97316",
|
|
21200
|
-
AssistantMessage: "#
|
|
20056
|
+
AssistantMessage: "#FACC15",
|
|
21201
20057
|
ToolCall: "#C4B5FD",
|
|
21202
20058
|
// Tasks / projects / events
|
|
21203
20059
|
Task: "#DB2777",
|
|
@@ -21961,15 +20817,15 @@ app34.route("/adherence", adherence_default);
|
|
|
21961
20817
|
var admin_default = app34;
|
|
21962
20818
|
|
|
21963
20819
|
// server/index.ts
|
|
21964
|
-
var
|
|
21965
|
-
var BRAND_JSON_PATH =
|
|
20820
|
+
var PLATFORM_ROOT10 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
20821
|
+
var BRAND_JSON_PATH = PLATFORM_ROOT10 ? join12(PLATFORM_ROOT10, "config", "brand.json") : "";
|
|
21966
20822
|
var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", domain: "getmaxy.com" };
|
|
21967
|
-
if (BRAND_JSON_PATH && !
|
|
20823
|
+
if (BRAND_JSON_PATH && !existsSync23(BRAND_JSON_PATH)) {
|
|
21968
20824
|
console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
|
|
21969
20825
|
}
|
|
21970
|
-
if (BRAND_JSON_PATH &&
|
|
20826
|
+
if (BRAND_JSON_PATH && existsSync23(BRAND_JSON_PATH)) {
|
|
21971
20827
|
try {
|
|
21972
|
-
const parsed = JSON.parse(
|
|
20828
|
+
const parsed = JSON.parse(readFileSync24(BRAND_JSON_PATH, "utf-8"));
|
|
21973
20829
|
BRAND = { ...BRAND, ...parsed };
|
|
21974
20830
|
} catch (err) {
|
|
21975
20831
|
console.error(`[brand] Failed to parse brand.json: ${err.message}`);
|
|
@@ -21988,11 +20844,11 @@ var brandLoginOpts = {
|
|
|
21988
20844
|
bodyFont: BRAND.defaultFonts?.body,
|
|
21989
20845
|
logoContainsName: !!BRAND.logoContainsName
|
|
21990
20846
|
};
|
|
21991
|
-
var ALIAS_DOMAINS_PATH2 =
|
|
20847
|
+
var ALIAS_DOMAINS_PATH2 = join12(homedir4(), BRAND.configDir, "alias-domains.json");
|
|
21992
20848
|
function loadAliasDomains() {
|
|
21993
20849
|
try {
|
|
21994
|
-
if (!
|
|
21995
|
-
const parsed = JSON.parse(
|
|
20850
|
+
if (!existsSync23(ALIAS_DOMAINS_PATH2)) return null;
|
|
20851
|
+
const parsed = JSON.parse(readFileSync24(ALIAS_DOMAINS_PATH2, "utf-8"));
|
|
21996
20852
|
if (!Array.isArray(parsed)) {
|
|
21997
20853
|
console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
|
|
21998
20854
|
return null;
|
|
@@ -22332,20 +21188,20 @@ app35.get("/agent-assets/:slug/:filename", (c) => {
|
|
|
22332
21188
|
console.error(`[agent-assets] no-account slug=${slug} file=${filename}`);
|
|
22333
21189
|
return c.text("Not found", 404);
|
|
22334
21190
|
}
|
|
22335
|
-
const filePath =
|
|
22336
|
-
const expectedDir =
|
|
21191
|
+
const filePath = resolve27(account.accountDir, "agents", slug, "assets", filename);
|
|
21192
|
+
const expectedDir = resolve27(account.accountDir, "agents", slug, "assets");
|
|
22337
21193
|
if (!filePath.startsWith(expectedDir + "/")) {
|
|
22338
21194
|
console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
|
|
22339
21195
|
return c.text("Forbidden", 403);
|
|
22340
21196
|
}
|
|
22341
|
-
if (!
|
|
21197
|
+
if (!existsSync23(filePath)) {
|
|
22342
21198
|
console.error(`[agent-assets] serve slug=${slug} file=${filename} status=404`);
|
|
22343
21199
|
return c.text("Not found", 404);
|
|
22344
21200
|
}
|
|
22345
21201
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
22346
21202
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
22347
21203
|
console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
|
|
22348
|
-
const body =
|
|
21204
|
+
const body = readFileSync24(filePath);
|
|
22349
21205
|
return c.body(body, 200, {
|
|
22350
21206
|
"Content-Type": contentType,
|
|
22351
21207
|
"Cache-Control": "public, max-age=3600"
|
|
@@ -22362,20 +21218,20 @@ app35.get("/generated/:filename", (c) => {
|
|
|
22362
21218
|
console.error(`[generated] serve file=${filename} status=404`);
|
|
22363
21219
|
return c.text("Not found", 404);
|
|
22364
21220
|
}
|
|
22365
|
-
const filePath =
|
|
22366
|
-
const expectedDir =
|
|
21221
|
+
const filePath = resolve27(account.accountDir, "generated", filename);
|
|
21222
|
+
const expectedDir = resolve27(account.accountDir, "generated");
|
|
22367
21223
|
if (!filePath.startsWith(expectedDir + "/")) {
|
|
22368
21224
|
console.error(`[generated] serve file=${filename} status=403`);
|
|
22369
21225
|
return c.text("Forbidden", 403);
|
|
22370
21226
|
}
|
|
22371
|
-
if (!
|
|
21227
|
+
if (!existsSync23(filePath)) {
|
|
22372
21228
|
console.error(`[generated] serve file=${filename} status=404`);
|
|
22373
21229
|
return c.text("Not found", 404);
|
|
22374
21230
|
}
|
|
22375
21231
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
22376
21232
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
22377
21233
|
console.log(`[generated] serve file=${filename} status=200`);
|
|
22378
|
-
const body =
|
|
21234
|
+
const body = readFileSync24(filePath);
|
|
22379
21235
|
return c.body(body, 200, {
|
|
22380
21236
|
"Content-Type": contentType,
|
|
22381
21237
|
"Cache-Control": "public, max-age=86400"
|
|
@@ -22384,9 +21240,9 @@ app35.get("/generated/:filename", (c) => {
|
|
|
22384
21240
|
var htmlCache = /* @__PURE__ */ new Map();
|
|
22385
21241
|
var brandLogoPath = "/brand/maxy-monochrome.png";
|
|
22386
21242
|
var brandIconPath = "/brand/maxy-monochrome.png";
|
|
22387
|
-
if (BRAND_JSON_PATH &&
|
|
21243
|
+
if (BRAND_JSON_PATH && existsSync23(BRAND_JSON_PATH)) {
|
|
22388
21244
|
try {
|
|
22389
|
-
const fullBrand = JSON.parse(
|
|
21245
|
+
const fullBrand = JSON.parse(readFileSync24(BRAND_JSON_PATH, "utf-8"));
|
|
22390
21246
|
if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
|
|
22391
21247
|
brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
|
|
22392
21248
|
} catch {
|
|
@@ -22402,10 +21258,10 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
|
|
|
22402
21258
|
})}</script>`;
|
|
22403
21259
|
function readInstalledVersion() {
|
|
22404
21260
|
try {
|
|
22405
|
-
if (!
|
|
22406
|
-
const versionFile =
|
|
22407
|
-
if (!
|
|
22408
|
-
const content =
|
|
21261
|
+
if (!PLATFORM_ROOT10) return "unknown";
|
|
21262
|
+
const versionFile = join12(PLATFORM_ROOT10, "config", `.${BRAND.hostname}-version`);
|
|
21263
|
+
if (!existsSync23(versionFile)) return "unknown";
|
|
21264
|
+
const content = readFileSync24(versionFile, "utf-8").trim();
|
|
22409
21265
|
return content || "unknown";
|
|
22410
21266
|
} catch {
|
|
22411
21267
|
return "unknown";
|
|
@@ -22446,9 +21302,9 @@ var clientErrorReporterScript = `<script>
|
|
|
22446
21302
|
function cachedHtml(file) {
|
|
22447
21303
|
let html = htmlCache.get(file);
|
|
22448
21304
|
if (!html) {
|
|
22449
|
-
html =
|
|
22450
|
-
html = html.replace("<title>Maxy</title>", `<title>${
|
|
22451
|
-
html = html.replace('href="/favicon.ico"', `href="${
|
|
21305
|
+
html = readFileSync24(resolve27(process.cwd(), "public", file), "utf-8");
|
|
21306
|
+
html = html.replace("<title>Maxy</title>", `<title>${escapeHtml(BRAND.productName)}</title>`);
|
|
21307
|
+
html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
|
|
22452
21308
|
const headInjection = file === "index.html" ? `${brandScript}
|
|
22453
21309
|
${versionScript}
|
|
22454
21310
|
${clientErrorReporterScript}
|
|
@@ -22461,26 +21317,26 @@ ${clientErrorReporterScript}
|
|
|
22461
21317
|
}
|
|
22462
21318
|
var brandedHtmlCache = /* @__PURE__ */ new Map();
|
|
22463
21319
|
function loadBrandingCache(agentSlug) {
|
|
22464
|
-
const configDir2 =
|
|
21320
|
+
const configDir2 = join12(homedir4(), BRAND.configDir);
|
|
22465
21321
|
try {
|
|
22466
|
-
const accountJsonPath =
|
|
22467
|
-
if (!
|
|
22468
|
-
const account = JSON.parse(
|
|
21322
|
+
const accountJsonPath = join12(configDir2, "account.json");
|
|
21323
|
+
if (!existsSync23(accountJsonPath)) return null;
|
|
21324
|
+
const account = JSON.parse(readFileSync24(accountJsonPath, "utf-8"));
|
|
22469
21325
|
const accountId = account.accountId;
|
|
22470
21326
|
if (!accountId) return null;
|
|
22471
|
-
const cachePath =
|
|
22472
|
-
if (!
|
|
22473
|
-
return JSON.parse(
|
|
21327
|
+
const cachePath = join12(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
|
|
21328
|
+
if (!existsSync23(cachePath)) return null;
|
|
21329
|
+
return JSON.parse(readFileSync24(cachePath, "utf-8"));
|
|
22474
21330
|
} catch {
|
|
22475
21331
|
return null;
|
|
22476
21332
|
}
|
|
22477
21333
|
}
|
|
22478
21334
|
function resolveDefaultSlug() {
|
|
22479
21335
|
try {
|
|
22480
|
-
const configDir2 =
|
|
22481
|
-
const accountJsonPath =
|
|
22482
|
-
if (!
|
|
22483
|
-
const account = JSON.parse(
|
|
21336
|
+
const configDir2 = join12(homedir4(), BRAND.configDir);
|
|
21337
|
+
const accountJsonPath = join12(configDir2, "account.json");
|
|
21338
|
+
if (!existsSync23(accountJsonPath)) return null;
|
|
21339
|
+
const account = JSON.parse(readFileSync24(accountJsonPath, "utf-8"));
|
|
22484
21340
|
return account.defaultAgent || null;
|
|
22485
21341
|
} catch {
|
|
22486
21342
|
return null;
|
|
@@ -22494,26 +21350,26 @@ function brandedPublicHtml(agentSlug) {
|
|
|
22494
21350
|
if (!branding) return baseHtml;
|
|
22495
21351
|
const brandHash = JSON.stringify(branding).length;
|
|
22496
21352
|
if (cached && cached.mtime === brandHash) return cached.html;
|
|
22497
|
-
const title =
|
|
22498
|
-
const description = branding.tagline ?
|
|
21353
|
+
const title = escapeHtml(branding.name);
|
|
21354
|
+
const description = branding.tagline ? escapeHtml(branding.tagline) : "";
|
|
22499
21355
|
const themeColor = branding.primaryColor || "";
|
|
22500
21356
|
const ogImage = branding.logoUrl || "";
|
|
22501
21357
|
const favicon = branding.faviconUrl || "";
|
|
22502
21358
|
const metaTags = [
|
|
22503
21359
|
` <title>${title}</title>`,
|
|
22504
|
-
favicon ? ` <link rel="icon" href="${
|
|
22505
|
-
themeColor ? ` <meta name="theme-color" content="${
|
|
21360
|
+
favicon ? ` <link rel="icon" href="${escapeHtml(favicon)}">` : ` <link rel="icon" href="${escapeHtml(brandFaviconPath)}">`,
|
|
21361
|
+
themeColor ? ` <meta name="theme-color" content="${escapeHtml(themeColor)}">` : "",
|
|
22506
21362
|
` <meta property="og:title" content="${title}">`,
|
|
22507
21363
|
description ? ` <meta property="og:description" content="${description}">` : "",
|
|
22508
|
-
ogImage ? ` <meta property="og:image" content="${
|
|
21364
|
+
ogImage ? ` <meta property="og:image" content="${escapeHtml(ogImage)}">` : "",
|
|
22509
21365
|
' <meta property="og:type" content="website">'
|
|
22510
21366
|
].filter(Boolean).join("\n");
|
|
22511
|
-
const html = baseHtml.replace(` <title>${
|
|
22512
|
-
<link rel="icon" href="${
|
|
21367
|
+
const html = baseHtml.replace(` <title>${escapeHtml(BRAND.productName)}</title>
|
|
21368
|
+
<link rel="icon" href="${escapeHtml(brandFaviconPath)}">`, metaTags);
|
|
22513
21369
|
brandedHtmlCache.set(agentSlug, { html, mtime: brandHash });
|
|
22514
21370
|
return html;
|
|
22515
21371
|
}
|
|
22516
|
-
function
|
|
21372
|
+
function escapeHtml(s) {
|
|
22517
21373
|
return s.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
22518
21374
|
}
|
|
22519
21375
|
app35.get("/", (c) => {
|
|
@@ -22553,11 +21409,11 @@ app35.use("/vnc-popout.html", logViewerFetch);
|
|
|
22553
21409
|
app35.get("/vnc-popout.html", (c) => {
|
|
22554
21410
|
let html = htmlCache.get("vnc-popout.html");
|
|
22555
21411
|
if (!html) {
|
|
22556
|
-
html =
|
|
22557
|
-
const name =
|
|
21412
|
+
html = readFileSync24(resolve27(process.cwd(), "public", "vnc-popout.html"), "utf-8");
|
|
21413
|
+
const name = escapeHtml(BRAND.productName);
|
|
22558
21414
|
html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
|
|
22559
21415
|
html = html.replace("</head>", ` ${brandScript}
|
|
22560
|
-
<link rel="icon" href="${
|
|
21416
|
+
<link rel="icon" href="${escapeHtml(brandFaviconPath)}">
|
|
22561
21417
|
</head>`);
|
|
22562
21418
|
htmlCache.set("vnc-popout.html", html);
|
|
22563
21419
|
}
|
|
@@ -22607,36 +21463,10 @@ app35.get("/:slug", async (c, next) => {
|
|
|
22607
21463
|
await next();
|
|
22608
21464
|
});
|
|
22609
21465
|
app35.use("/*", serveStatic({ root: "./public" }));
|
|
22610
|
-
var port = parseInt(process.env.PORT ?? "
|
|
22611
|
-
var hostname = process.env.HOSTNAME ?? "
|
|
21466
|
+
var port = parseInt(process.env.PORT ?? "19199", 10);
|
|
21467
|
+
var hostname = process.env.HOSTNAME ?? "127.0.0.1";
|
|
22612
21468
|
var httpServer = serve({ fetch: app35.fetch, port, hostname });
|
|
22613
|
-
attachVncWsProxy(httpServer, {
|
|
22614
|
-
isPublicHost,
|
|
22615
|
-
upstreamHost: "127.0.0.1",
|
|
22616
|
-
upstreamPort: 6080
|
|
22617
|
-
});
|
|
22618
|
-
attachTerminalWsProxy(httpServer, {
|
|
22619
|
-
isPublicHost,
|
|
22620
|
-
upstreamHost: "127.0.0.1",
|
|
22621
|
-
upstreamPort: 7681
|
|
22622
|
-
});
|
|
22623
21469
|
console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
22624
|
-
setTimeout(() => {
|
|
22625
|
-
const socket = createConnection5(7681, "127.0.0.1");
|
|
22626
|
-
socket.setTimeout(500);
|
|
22627
|
-
socket.once("connect", () => {
|
|
22628
|
-
socket.destroy();
|
|
22629
|
-
console.log("[ttyd] upstream ready on 127.0.0.1:7681");
|
|
22630
|
-
});
|
|
22631
|
-
socket.once("error", (err) => {
|
|
22632
|
-
socket.destroy();
|
|
22633
|
-
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})`);
|
|
22634
|
-
});
|
|
22635
|
-
socket.once("timeout", () => {
|
|
22636
|
-
socket.destroy();
|
|
22637
|
-
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'.");
|
|
22638
|
-
});
|
|
22639
|
-
}, 5e3);
|
|
22640
21470
|
var SUBAPP_MANIFEST = [
|
|
22641
21471
|
{ prefix: "/api/health", file: "server/routes/health.ts", subapp: health_default },
|
|
22642
21472
|
{ prefix: "/api/session", file: "server/routes/session.ts", subapp: session_default },
|
|
@@ -22669,8 +21499,8 @@ try {
|
|
|
22669
21499
|
(async () => {
|
|
22670
21500
|
try {
|
|
22671
21501
|
let userId = "";
|
|
22672
|
-
if (
|
|
22673
|
-
const users = JSON.parse(
|
|
21502
|
+
if (existsSync23(USERS_FILE)) {
|
|
21503
|
+
const users = JSON.parse(readFileSync24(USERS_FILE, "utf-8").trim() || "[]");
|
|
22674
21504
|
userId = users[0]?.userId ?? "";
|
|
22675
21505
|
}
|
|
22676
21506
|
await backfillNullUserIdConversations(userId);
|
|
@@ -22696,7 +21526,7 @@ if (bootAccountConfig?.whatsapp) {
|
|
|
22696
21526
|
}
|
|
22697
21527
|
init({
|
|
22698
21528
|
configDir: configDirForWhatsApp,
|
|
22699
|
-
platformRoot:
|
|
21529
|
+
platformRoot: resolve27(process.env.MAXY_PLATFORM_ROOT ?? join12(__dirname, "..")),
|
|
22700
21530
|
accountConfig: bootAccountConfig,
|
|
22701
21531
|
onMessage: async (msg) => {
|
|
22702
21532
|
try {
|