@rubytech/create-realagent 1.0.627 → 1.0.630
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 +34 -0
- package/package.json +1 -1
- package/payload/platform/lib/graph-mcp/dist/index.d.ts +26 -0
- package/payload/platform/lib/graph-mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/index.js +193 -0
- package/payload/platform/lib/graph-mcp/dist/index.js.map +1 -0
- package/payload/platform/lib/graph-mcp/src/index.ts +225 -0
- package/payload/platform/lib/graph-mcp/tsconfig.json +8 -0
- package/payload/platform/package.json +2 -2
- package/payload/platform/plugins/cloudflare/scripts/_stream-log.sh +124 -0
- package/payload/platform/plugins/cloudflare/scripts/reset-tunnel.sh +45 -3
- package/payload/platform/plugins/cloudflare/scripts/setup-tunnel.sh +151 -10
- package/payload/platform/plugins/docs/references/memory-guide.md +8 -0
- package/payload/platform/plugins/docs/references/plugins-guide.md +2 -0
- package/payload/platform/plugins/memory/mcp/scripts/graph/accept.sh +129 -0
- package/payload/platform/plugins/memory/mcp/scripts/graph/fixture.cypher +59 -0
- package/payload/platform/plugins/memory/references/graph-primitives.md +195 -0
- package/payload/server/public/assets/admin-DirN63aF.js +352 -0
- package/payload/server/public/assets/public-Cizdj15i.js +5 -0
- package/payload/server/public/assets/useVoiceRecorder-DIV9KAk_.css +1 -0
- package/payload/server/public/assets/{useVoiceRecorder-CiYPZu3g.js → useVoiceRecorder-tbj4tUsl.js} +1 -1
- package/payload/server/public/index.html +3 -3
- package/payload/server/public/public.html +3 -3
- package/payload/server/server.js +481 -102
- package/payload/server/public/assets/admin-BxVuKRJZ.js +0 -352
- package/payload/server/public/assets/public-Bgm9WQFZ.js +0 -5
- package/payload/server/public/assets/useVoiceRecorder-BORuG_su.css +0 -1
package/payload/server/server.js
CHANGED
|
@@ -2530,7 +2530,7 @@ var responseViaResponseObject = async (res, outgoing, options = {}) => {
|
|
|
2530
2530
|
});
|
|
2531
2531
|
if (!chunk) {
|
|
2532
2532
|
if (i === 1) {
|
|
2533
|
-
await new Promise((
|
|
2533
|
+
await new Promise((resolve29) => setTimeout(resolve29));
|
|
2534
2534
|
maxReadCount = 3;
|
|
2535
2535
|
continue;
|
|
2536
2536
|
}
|
|
@@ -2897,7 +2897,7 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
2897
2897
|
|
|
2898
2898
|
// server/index.ts
|
|
2899
2899
|
import { readFileSync as readFileSync26, existsSync as existsSync26, watchFile } from "fs";
|
|
2900
|
-
import { resolve as
|
|
2900
|
+
import { resolve as resolve28, join as join13, basename as basename6 } from "path";
|
|
2901
2901
|
import { homedir as homedir4 } from "os";
|
|
2902
2902
|
|
|
2903
2903
|
// app/lib/vnc-logger.ts
|
|
@@ -2971,10 +2971,10 @@ var SCRYPT_R = 8;
|
|
|
2971
2971
|
var SCRYPT_P = 1;
|
|
2972
2972
|
var SCRYPT_KEYLEN = 64;
|
|
2973
2973
|
function scryptAsync(password, salt) {
|
|
2974
|
-
return new Promise((
|
|
2974
|
+
return new Promise((resolve29, reject) => {
|
|
2975
2975
|
scrypt(password, salt, SCRYPT_KEYLEN, { N: SCRYPT_N, r: SCRYPT_R, p: SCRYPT_P }, (err, key) => {
|
|
2976
2976
|
if (err) reject(err);
|
|
2977
|
-
else
|
|
2977
|
+
else resolve29(key);
|
|
2978
2978
|
});
|
|
2979
2979
|
});
|
|
2980
2980
|
}
|
|
@@ -3610,8 +3610,6 @@ function handleUpgrade(req, clientSocket, head, opts) {
|
|
|
3610
3610
|
const qsIndex = url2.indexOf("?");
|
|
3611
3611
|
const pathname = qsIndex === -1 ? url2 : url2.slice(0, qsIndex);
|
|
3612
3612
|
if (pathname !== WS_PATH) {
|
|
3613
|
-
vncLog("ws-upgrade", { decision: "rejected", reason: "wrong-path", path: pathname });
|
|
3614
|
-
clientSocket.destroy();
|
|
3615
3613
|
return;
|
|
3616
3614
|
}
|
|
3617
3615
|
const corrId = newCorrId();
|
|
@@ -3828,9 +3826,203 @@ Content-Length: 0\r
|
|
|
3828
3826
|
socket.destroy();
|
|
3829
3827
|
}
|
|
3830
3828
|
|
|
3829
|
+
// server/graph-proxy.ts
|
|
3830
|
+
import { createConnection as createConnection2 } from "net";
|
|
3831
|
+
var GRAPH_PREFIX = "/graph";
|
|
3832
|
+
var UPSTREAM_TIMEOUT_MS2 = 5e3;
|
|
3833
|
+
var HOP_BY_HOP2 = /* @__PURE__ */ new Set([
|
|
3834
|
+
"connection",
|
|
3835
|
+
"keep-alive",
|
|
3836
|
+
"proxy-authenticate",
|
|
3837
|
+
"proxy-authorization",
|
|
3838
|
+
"te",
|
|
3839
|
+
"trailer",
|
|
3840
|
+
"transfer-encoding",
|
|
3841
|
+
"upgrade"
|
|
3842
|
+
]);
|
|
3843
|
+
function resolveUpstreamPort() {
|
|
3844
|
+
const uri = process.env.NEO4J_URI ?? "bolt://localhost:7687";
|
|
3845
|
+
const m = uri.match(/:(\d+)$/);
|
|
3846
|
+
const boltPort = m ? parseInt(m[1], 10) : 7687;
|
|
3847
|
+
return boltPort - 213;
|
|
3848
|
+
}
|
|
3849
|
+
var UPSTREAM_HOST = "127.0.0.1";
|
|
3850
|
+
var UPSTREAM_PORT = resolveUpstreamPort();
|
|
3851
|
+
function attachGraphHttpRoutes(app2) {
|
|
3852
|
+
const handler = async (c) => {
|
|
3853
|
+
const raw2 = c.req.raw;
|
|
3854
|
+
const url2 = new URL(raw2.url);
|
|
3855
|
+
const pathAfterPrefix = url2.pathname.slice(GRAPH_PREFIX.length) || "/";
|
|
3856
|
+
const upstreamUrl = `http://${UPSTREAM_HOST}:${UPSTREAM_PORT}${pathAfterPrefix}${url2.search}`;
|
|
3857
|
+
const upstreamHeaders = new Headers(raw2.headers);
|
|
3858
|
+
for (const h of HOP_BY_HOP2) upstreamHeaders.delete(h);
|
|
3859
|
+
upstreamHeaders.set("host", `${UPSTREAM_HOST}:${UPSTREAM_PORT}`);
|
|
3860
|
+
try {
|
|
3861
|
+
const upstream = await fetch(upstreamUrl, {
|
|
3862
|
+
method: raw2.method,
|
|
3863
|
+
headers: upstreamHeaders,
|
|
3864
|
+
body: raw2.body,
|
|
3865
|
+
// `duplex: 'half'` is required when forwarding a streaming body; TS
|
|
3866
|
+
// typings do not yet expose it but the undici runtime supports it.
|
|
3867
|
+
...raw2.body ? { duplex: "half" } : {},
|
|
3868
|
+
redirect: "manual"
|
|
3869
|
+
});
|
|
3870
|
+
const resHeaders = new Headers(upstream.headers);
|
|
3871
|
+
for (const h of HOP_BY_HOP2) resHeaders.delete(h);
|
|
3872
|
+
return new Response(upstream.body, {
|
|
3873
|
+
status: upstream.status,
|
|
3874
|
+
statusText: upstream.statusText,
|
|
3875
|
+
headers: resHeaders
|
|
3876
|
+
});
|
|
3877
|
+
} catch (err) {
|
|
3878
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3879
|
+
console.error(`[graph-proxy] upstream fetch failed for ${upstreamUrl}: ${msg}`);
|
|
3880
|
+
return c.text(`Graph proxy upstream unreachable: ${msg}`, 502);
|
|
3881
|
+
}
|
|
3882
|
+
};
|
|
3883
|
+
app2.all(GRAPH_PREFIX, handler);
|
|
3884
|
+
app2.all(`${GRAPH_PREFIX}/*`, handler);
|
|
3885
|
+
}
|
|
3886
|
+
function attachGraphWsProxy(server, opts) {
|
|
3887
|
+
server.on("upgrade", (req, clientSocket, head) => {
|
|
3888
|
+
try {
|
|
3889
|
+
const url2 = req.url ?? "";
|
|
3890
|
+
const qsIndex = url2.indexOf("?");
|
|
3891
|
+
const pathname = qsIndex === -1 ? url2 : url2.slice(0, qsIndex);
|
|
3892
|
+
const isGraphPath = pathname === GRAPH_PREFIX || pathname.startsWith(`${GRAPH_PREFIX}/`);
|
|
3893
|
+
if (!isGraphPath) {
|
|
3894
|
+
if (pathname !== "/websockify") {
|
|
3895
|
+
clientSocket.destroy();
|
|
3896
|
+
}
|
|
3897
|
+
return;
|
|
3898
|
+
}
|
|
3899
|
+
const hostHeader = (req.headers.host ?? "").split(":")[0];
|
|
3900
|
+
const remote = req.socket.remoteAddress;
|
|
3901
|
+
const xff = headerString2(req.headers["x-forwarded-for"]);
|
|
3902
|
+
const cookie = headerString2(req.headers.cookie);
|
|
3903
|
+
const decision = opts.canAccessAdmin({
|
|
3904
|
+
host: hostHeader,
|
|
3905
|
+
remoteAddress: remote,
|
|
3906
|
+
xForwardedFor: xff,
|
|
3907
|
+
cookieHeader: cookie,
|
|
3908
|
+
isPublicHost: opts.isPublicHost
|
|
3909
|
+
});
|
|
3910
|
+
if (!decision.allow) {
|
|
3911
|
+
const status = decision.reason === "public-host" ? 404 : 401;
|
|
3912
|
+
writeStatusAndDestroy2(clientSocket, status, decision.reason === "public-host" ? "Not Found" : "Unauthorized");
|
|
3913
|
+
return;
|
|
3914
|
+
}
|
|
3915
|
+
const originHeader = headerString2(req.headers.origin);
|
|
3916
|
+
const originHost = parseOriginHost2(originHeader);
|
|
3917
|
+
if (!originHost || originHost !== hostHeader || opts.isPublicHost(originHost)) {
|
|
3918
|
+
writeStatusAndDestroy2(clientSocket, 403, "Forbidden");
|
|
3919
|
+
return;
|
|
3920
|
+
}
|
|
3921
|
+
const rewrittenPath = pathname === GRAPH_PREFIX ? "/" : pathname.slice(GRAPH_PREFIX.length);
|
|
3922
|
+
const rewrittenUrl = qsIndex === -1 ? rewrittenPath : rewrittenPath + url2.slice(qsIndex);
|
|
3923
|
+
const upstream = createConnection2({ host: UPSTREAM_HOST, port: UPSTREAM_PORT });
|
|
3924
|
+
upstream.setTimeout(UPSTREAM_TIMEOUT_MS2);
|
|
3925
|
+
upstream.once("connect", () => {
|
|
3926
|
+
upstream.setTimeout(0);
|
|
3927
|
+
const lines = [];
|
|
3928
|
+
lines.push(`${req.method ?? "GET"} ${rewrittenUrl} HTTP/${req.httpVersion}`);
|
|
3929
|
+
lines.push(`host: ${UPSTREAM_HOST}:${UPSTREAM_PORT}`);
|
|
3930
|
+
for (const [name, value] of Object.entries(req.headers)) {
|
|
3931
|
+
if (name === "host") continue;
|
|
3932
|
+
if (HOP_BY_HOP2.has(name)) continue;
|
|
3933
|
+
if (value == null) continue;
|
|
3934
|
+
if (Array.isArray(value)) {
|
|
3935
|
+
for (const v of value) lines.push(`${name}: ${v}`);
|
|
3936
|
+
} else {
|
|
3937
|
+
lines.push(`${name}: ${value}`);
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
const upgradeHeader = headerString2(req.headers.upgrade);
|
|
3941
|
+
const connectionHeader = headerString2(req.headers.connection);
|
|
3942
|
+
if (upgradeHeader) lines.push(`upgrade: ${upgradeHeader}`);
|
|
3943
|
+
if (connectionHeader) lines.push(`connection: ${connectionHeader}`);
|
|
3944
|
+
upstream.write(lines.join("\r\n") + "\r\n\r\n");
|
|
3945
|
+
if (head && head.length > 0) upstream.write(head);
|
|
3946
|
+
clientSocket.pipe(upstream);
|
|
3947
|
+
upstream.pipe(clientSocket);
|
|
3948
|
+
const teardown = () => {
|
|
3949
|
+
clientSocket.destroy();
|
|
3950
|
+
upstream.destroy();
|
|
3951
|
+
};
|
|
3952
|
+
clientSocket.once("close", teardown);
|
|
3953
|
+
upstream.once("close", teardown);
|
|
3954
|
+
clientSocket.once("error", teardown);
|
|
3955
|
+
upstream.once("error", teardown);
|
|
3956
|
+
});
|
|
3957
|
+
upstream.once("timeout", () => {
|
|
3958
|
+
writeStatusAndDestroy2(clientSocket, 504, "Gateway Timeout");
|
|
3959
|
+
upstream.destroy();
|
|
3960
|
+
});
|
|
3961
|
+
upstream.once("error", (err) => {
|
|
3962
|
+
console.error(`[graph-proxy] ws upstream error: ${err.message}`);
|
|
3963
|
+
writeStatusAndDestroy2(clientSocket, 502, "Bad Gateway");
|
|
3964
|
+
upstream.destroy();
|
|
3965
|
+
});
|
|
3966
|
+
} catch (err) {
|
|
3967
|
+
console.error(`[graph-proxy] upgrade handler exception: ${err.message}`);
|
|
3968
|
+
clientSocket.destroy();
|
|
3969
|
+
}
|
|
3970
|
+
});
|
|
3971
|
+
}
|
|
3972
|
+
function graphAuthMiddleware(opts) {
|
|
3973
|
+
return async (c, next) => {
|
|
3974
|
+
const url2 = new URL(c.req.raw.url);
|
|
3975
|
+
if (!url2.pathname.startsWith(GRAPH_PREFIX)) return next();
|
|
3976
|
+
const hostHeader = (c.req.header("host") ?? "").split(":")[0];
|
|
3977
|
+
const incoming = c.env?.incoming;
|
|
3978
|
+
const remote = incoming?.socket?.remoteAddress;
|
|
3979
|
+
const xff = c.req.header("x-forwarded-for");
|
|
3980
|
+
const cookie = c.req.header("cookie");
|
|
3981
|
+
const decision = opts.canAccessAdmin({
|
|
3982
|
+
host: hostHeader,
|
|
3983
|
+
remoteAddress: remote,
|
|
3984
|
+
xForwardedFor: xff,
|
|
3985
|
+
cookieHeader: cookie,
|
|
3986
|
+
isPublicHost: opts.isPublicHost
|
|
3987
|
+
});
|
|
3988
|
+
if (!decision.allow) {
|
|
3989
|
+
return c.text(
|
|
3990
|
+
decision.reason === "public-host" ? "Not Found" : "Unauthorized",
|
|
3991
|
+
decision.reason === "public-host" ? 404 : 401
|
|
3992
|
+
);
|
|
3993
|
+
}
|
|
3994
|
+
return next();
|
|
3995
|
+
};
|
|
3996
|
+
}
|
|
3997
|
+
function headerString2(value) {
|
|
3998
|
+
if (value == null) return void 0;
|
|
3999
|
+
return Array.isArray(value) ? value[0] : value;
|
|
4000
|
+
}
|
|
4001
|
+
function parseOriginHost2(origin) {
|
|
4002
|
+
if (!origin) return null;
|
|
4003
|
+
try {
|
|
4004
|
+
return new URL(origin).hostname;
|
|
4005
|
+
} catch {
|
|
4006
|
+
return null;
|
|
4007
|
+
}
|
|
4008
|
+
}
|
|
4009
|
+
function writeStatusAndDestroy2(socket, status, statusText) {
|
|
4010
|
+
try {
|
|
4011
|
+
socket.write(
|
|
4012
|
+
`HTTP/1.1 ${status} ${statusText}\r
|
|
4013
|
+
Connection: close\r
|
|
4014
|
+
Content-Length: 0\r
|
|
4015
|
+
\r
|
|
4016
|
+
`
|
|
4017
|
+
);
|
|
4018
|
+
} catch {
|
|
4019
|
+
}
|
|
4020
|
+
socket.destroy();
|
|
4021
|
+
}
|
|
4022
|
+
|
|
3831
4023
|
// app/api/health/route.ts
|
|
3832
4024
|
import { existsSync as existsSync11, readFileSync as readFileSync12 } from "fs";
|
|
3833
|
-
import { createConnection as
|
|
4025
|
+
import { createConnection as createConnection4 } from "net";
|
|
3834
4026
|
|
|
3835
4027
|
// app/lib/claude-auth.ts
|
|
3836
4028
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
@@ -4187,7 +4379,7 @@ function contextWindow(model) {
|
|
|
4187
4379
|
|
|
4188
4380
|
// app/lib/vnc.ts
|
|
4189
4381
|
import { spawnSync, execFileSync } from "child_process";
|
|
4190
|
-
import { createConnection as
|
|
4382
|
+
import { createConnection as createConnection3 } from "net";
|
|
4191
4383
|
import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
4192
4384
|
import { resolve as resolve4 } from "path";
|
|
4193
4385
|
var PLATFORM_ROOT2 = process.env.MAXY_PLATFORM_ROOT ?? resolve4(process.cwd(), "..");
|
|
@@ -4282,7 +4474,7 @@ async function waitForPort(port2, timeoutMs = 12e3) {
|
|
|
4282
4474
|
const deadline = Date.now() + timeoutMs;
|
|
4283
4475
|
while (Date.now() < deadline) {
|
|
4284
4476
|
const ready = await new Promise((res) => {
|
|
4285
|
-
const socket =
|
|
4477
|
+
const socket = createConnection3(port2, "127.0.0.1");
|
|
4286
4478
|
socket.setTimeout(500);
|
|
4287
4479
|
socket.once("connect", () => {
|
|
4288
4480
|
socket.destroy();
|
|
@@ -5146,7 +5338,7 @@ ${userContent}`;
|
|
|
5146
5338
|
"dontAsk",
|
|
5147
5339
|
prompt
|
|
5148
5340
|
];
|
|
5149
|
-
return new Promise((
|
|
5341
|
+
return new Promise((resolve29) => {
|
|
5150
5342
|
let stdout = "";
|
|
5151
5343
|
let stderr = "";
|
|
5152
5344
|
const spawnFn = _spawnOverride ?? spawn;
|
|
@@ -5164,35 +5356,35 @@ ${userContent}`;
|
|
|
5164
5356
|
const timer = setTimeout(() => {
|
|
5165
5357
|
proc.kill("SIGTERM");
|
|
5166
5358
|
console.error("[persist] autoLabel: haiku subprocess timed out");
|
|
5167
|
-
|
|
5359
|
+
resolve29(null);
|
|
5168
5360
|
}, SESSION_LABEL_TIMEOUT_MS);
|
|
5169
5361
|
proc.on("error", (err) => {
|
|
5170
5362
|
clearTimeout(timer);
|
|
5171
5363
|
console.error(`[persist] autoLabel: subprocess error \u2014 ${err.message}`);
|
|
5172
|
-
|
|
5364
|
+
resolve29(null);
|
|
5173
5365
|
});
|
|
5174
5366
|
proc.on("close", (code) => {
|
|
5175
5367
|
clearTimeout(timer);
|
|
5176
5368
|
if (code !== 0) {
|
|
5177
5369
|
console.error(`[persist] autoLabel: subprocess exited code=${code}${stderr ? ` stderr=${stderr.trim().slice(0, 200)}` : ""}`);
|
|
5178
|
-
|
|
5370
|
+
resolve29(null);
|
|
5179
5371
|
return;
|
|
5180
5372
|
}
|
|
5181
5373
|
const text = stdout.trim();
|
|
5182
5374
|
if (!text) {
|
|
5183
5375
|
console.error("[persist] autoLabel: haiku returned empty response");
|
|
5184
|
-
|
|
5376
|
+
resolve29(null);
|
|
5185
5377
|
return;
|
|
5186
5378
|
}
|
|
5187
5379
|
if (text === "SKIP") {
|
|
5188
5380
|
console.error("[persist] autoLabel: haiku returned SKIP \u2014 messages too vague");
|
|
5189
|
-
|
|
5381
|
+
resolve29(null);
|
|
5190
5382
|
return;
|
|
5191
5383
|
}
|
|
5192
5384
|
const words = text.split(/\s+/).slice(0, SESSION_LABEL_MAX_WORDS);
|
|
5193
5385
|
const label = words.join(" ");
|
|
5194
5386
|
console.error(`[persist] autoLabel: haiku response="${label}"`);
|
|
5195
|
-
|
|
5387
|
+
resolve29(label);
|
|
5196
5388
|
});
|
|
5197
5389
|
});
|
|
5198
5390
|
}
|
|
@@ -7512,12 +7704,42 @@ function consumeStalledSubagents(sessionKey) {
|
|
|
7512
7704
|
delete session.stalledSubagents;
|
|
7513
7705
|
return stalls && stalls.length > 0 ? stalls : void 0;
|
|
7514
7706
|
}
|
|
7707
|
+
function streamLogPathFor(accountId, conversationId) {
|
|
7708
|
+
const logDir = resolve6(ACCOUNTS_DIR, accountId, "logs");
|
|
7709
|
+
const streamLogPath = resolve6(logDir, `claude-agent-stream-${conversationId}.log`);
|
|
7710
|
+
return { logDir, streamLogPath };
|
|
7711
|
+
}
|
|
7712
|
+
function buildSpawnEnv(accountId, accountDir, conversationId) {
|
|
7713
|
+
if (!conversationId) {
|
|
7714
|
+
throw new Error(`buildSpawnEnv: conversationId is required (accountId=${accountId.slice(0, 8)})`);
|
|
7715
|
+
}
|
|
7716
|
+
const { logDir, streamLogPath } = streamLogPathFor(accountId, conversationId);
|
|
7717
|
+
return {
|
|
7718
|
+
...process.env,
|
|
7719
|
+
PLATFORM_ROOT: PLATFORM_ROOT4,
|
|
7720
|
+
ACCOUNT_DIR: accountDir,
|
|
7721
|
+
ACCOUNT_ID: accountId,
|
|
7722
|
+
LOG_DIR: logDir,
|
|
7723
|
+
STREAM_LOG_PATH: streamLogPath
|
|
7724
|
+
};
|
|
7725
|
+
}
|
|
7726
|
+
var cachedBrandHostname = null;
|
|
7727
|
+
function readBrandHostname() {
|
|
7728
|
+
if (cachedBrandHostname !== null) return cachedBrandHostname;
|
|
7729
|
+
try {
|
|
7730
|
+
const brandPath3 = resolve6(PLATFORM_ROOT4, "config", "brand.json");
|
|
7731
|
+
const parsed = JSON.parse(readFileSync7(brandPath3, "utf-8"));
|
|
7732
|
+
cachedBrandHostname = typeof parsed.hostname === "string" && parsed.hostname.length > 0 ? parsed.hostname : "maxy";
|
|
7733
|
+
} catch {
|
|
7734
|
+
cachedBrandHostname = "maxy";
|
|
7735
|
+
}
|
|
7736
|
+
return cachedBrandHostname;
|
|
7737
|
+
}
|
|
7515
7738
|
function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
7516
7739
|
if (!conversationId) {
|
|
7517
7740
|
throw new Error(`getMcpServers: conversationId is required (accountId=${accountId.slice(0, 8)})`);
|
|
7518
7741
|
}
|
|
7519
|
-
const LOG_DIR2 =
|
|
7520
|
-
const STREAM_LOG_PATH = resolve6(LOG_DIR2, `claude-agent-stream-${conversationId}.log`);
|
|
7742
|
+
const { logDir: LOG_DIR2, streamLogPath: STREAM_LOG_PATH } = streamLogPathFor(accountId, conversationId);
|
|
7521
7743
|
const baseEnv = { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT4, LOG_DIR: LOG_DIR2, STREAM_LOG_PATH };
|
|
7522
7744
|
const servers = {
|
|
7523
7745
|
"memory": {
|
|
@@ -7564,6 +7786,26 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
7564
7786
|
"plugin_playwright_playwright": {
|
|
7565
7787
|
command: "npx",
|
|
7566
7788
|
args: ["-y", "@playwright/mcp@latest", "--cdp-endpoint", "http://127.0.0.1:9222", "--caps", "pdf"]
|
|
7789
|
+
},
|
|
7790
|
+
// Graph MCP shim (Task 557) — spawns upstream `uvx mcp-neo4j-cypher` locked
|
|
7791
|
+
// into read-only mode and namespaced `maxy-graph`, wraps stderr with our
|
|
7792
|
+
// tee, and emits one `[graph-query]` line per tool call. Credentials flow
|
|
7793
|
+
// from the brand's own NEO4J_URI + config/.neo4j-password so a per-brand
|
|
7794
|
+
// admin session only ever sees its own Neo4j instance (isolation is
|
|
7795
|
+
// enforced upstream by the installer's process/port boundary per
|
|
7796
|
+
// MAXY-PRD.md:627, not in any application-layer filter).
|
|
7797
|
+
"graph": {
|
|
7798
|
+
command: "node",
|
|
7799
|
+
args: [resolve6(PLATFORM_ROOT4, "lib/graph-mcp/dist/index.js")],
|
|
7800
|
+
env: {
|
|
7801
|
+
...baseEnv,
|
|
7802
|
+
BRAND: readBrandHostname(),
|
|
7803
|
+
NEO4J_URI: process.env.NEO4J_URI ?? "bolt://localhost:7687",
|
|
7804
|
+
NEO4J_USERNAME: process.env.NEO4J_USERNAME ?? process.env.NEO4J_USER ?? "neo4j",
|
|
7805
|
+
NEO4J_NAMESPACE: "maxy-graph",
|
|
7806
|
+
NEO4J_READ_ONLY: "true",
|
|
7807
|
+
NEO4J_RESPONSE_TOKEN_LIMIT: "20000"
|
|
7808
|
+
}
|
|
7567
7809
|
}
|
|
7568
7810
|
};
|
|
7569
7811
|
const tgConfig = resolveAccount()?.config?.telegram;
|
|
@@ -7627,6 +7869,12 @@ var ADMIN_CORE_TOOLS = [
|
|
|
7627
7869
|
"Glob",
|
|
7628
7870
|
"Grep",
|
|
7629
7871
|
"Agent",
|
|
7872
|
+
// Task 557 — upstream mcp-neo4j-cypher (namespaced maxy-graph, read-only).
|
|
7873
|
+
// maxy-graph_write_neo4j_cypher is intentionally absent: writes go through
|
|
7874
|
+
// the schema-aware memory-write tool, which validates labels, properties,
|
|
7875
|
+
// and embeddings. Admin-only by virtue of not being in the public allow list.
|
|
7876
|
+
"mcp__graph__maxy-graph_read_neo4j_cypher",
|
|
7877
|
+
"mcp__graph__maxy-graph_get_neo4j_schema",
|
|
7630
7878
|
"mcp__memory__memory-search",
|
|
7631
7879
|
"mcp__memory__memory-rank",
|
|
7632
7880
|
"mcp__memory__memory-write",
|
|
@@ -7846,7 +8094,7 @@ async function fetchMemoryContext(accountId, query, sessionKey, options) {
|
|
|
7846
8094
|
return null;
|
|
7847
8095
|
}
|
|
7848
8096
|
const startMs = Date.now();
|
|
7849
|
-
return new Promise((
|
|
8097
|
+
return new Promise((resolve29) => {
|
|
7850
8098
|
const proc = spawn2(process.execPath, [serverPath], {
|
|
7851
8099
|
env: {
|
|
7852
8100
|
...process.env,
|
|
@@ -7875,7 +8123,7 @@ async function fetchMemoryContext(accountId, query, sessionKey, options) {
|
|
|
7875
8123
|
} else {
|
|
7876
8124
|
console.error(`[fetchMemoryContext] failed: ${reason} (${elapsed}ms)${stderrBuf ? ` stderr: ${stderrBuf.slice(0, 500)}` : ""}`);
|
|
7877
8125
|
}
|
|
7878
|
-
|
|
8126
|
+
resolve29(value);
|
|
7879
8127
|
};
|
|
7880
8128
|
proc.stdout.on("data", (chunk) => {
|
|
7881
8129
|
buffer += chunk.toString();
|
|
@@ -8286,15 +8534,10 @@ async function* runCompactionTurn(accountDir, accountId, systemPrompt, resumeSes
|
|
|
8286
8534
|
const proc = spawn2("claude", args, {
|
|
8287
8535
|
cwd: accountDir,
|
|
8288
8536
|
stdio: ["ignore", "pipe", "pipe"],
|
|
8289
|
-
|
|
8290
|
-
|
|
8291
|
-
|
|
8292
|
-
|
|
8293
|
-
// Task 535: NODE_DEBUG removed. The Claude Code CLI is a bundled Bun
|
|
8294
|
-
// binary and Bun ignores Node's NODE_DEBUG flag, so setting it here was
|
|
8295
|
-
// a no-op that misled future readers. The [subproc-debug-unavailable]
|
|
8296
|
-
// line below records the source-of-silence explicitly.
|
|
8297
|
-
}
|
|
8537
|
+
// Task 556: STREAM_LOG_PATH is inherited by every Bash-tool subprocess
|
|
8538
|
+
// the Claude CLI spawns; opt-in shell scripts tee their cloudflared/etc.
|
|
8539
|
+
// output into the same per-conversation stream log the agent writes to.
|
|
8540
|
+
env: buildSpawnEnv(accountId, accountDir, conversationId)
|
|
8298
8541
|
});
|
|
8299
8542
|
const stderrLog = agentLogStream("claude-agent-compaction-stderr", accountDir, conversationId);
|
|
8300
8543
|
stderrLog.on("error", () => {
|
|
@@ -8305,6 +8548,8 @@ async function* runCompactionTurn(accountDir, accountId, systemPrompt, resumeSes
|
|
|
8305
8548
|
});
|
|
8306
8549
|
teeProcStderrToStreamLog(proc, streamLog);
|
|
8307
8550
|
streamLog.write(`[${isoTs()}] [subproc-debug-unavailable] reason=bundled-bun-binary-ignores-node-debug pid=${proc.pid} cli=claude
|
|
8551
|
+
`);
|
|
8552
|
+
streamLog.write(`[${isoTs()}] [spawn-env] STREAM_LOG_PATH=set pid=${proc.pid} conversationId=${conversationId} site=compaction
|
|
8308
8553
|
`);
|
|
8309
8554
|
streamLog.write(`[${isoTs()}] [compaction-start] resumeSessionId=${resumeSessionId}
|
|
8310
8555
|
`);
|
|
@@ -9176,15 +9421,8 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
|
|
|
9176
9421
|
const proc = spawn2("claude", args, {
|
|
9177
9422
|
cwd: accountDir,
|
|
9178
9423
|
stdio: ["ignore", "pipe", "pipe"],
|
|
9179
|
-
|
|
9180
|
-
|
|
9181
|
-
PLATFORM_ROOT: PLATFORM_ROOT4,
|
|
9182
|
-
ACCOUNT_DIR: accountDir
|
|
9183
|
-
// Task 535: NODE_DEBUG removed. The Claude Code CLI is a bundled Bun
|
|
9184
|
-
// binary and Bun ignores Node's NODE_DEBUG flag, so setting it here was
|
|
9185
|
-
// a no-op that misled future readers. The [subproc-debug-unavailable]
|
|
9186
|
-
// line below records the source-of-silence explicitly.
|
|
9187
|
-
}
|
|
9424
|
+
// Task 556: STREAM_LOG_PATH inherited by Bash-tool subprocesses.
|
|
9425
|
+
env: buildSpawnEnv(accountId, accountDir, spawnConvId)
|
|
9188
9426
|
});
|
|
9189
9427
|
const stderrLog = agentLogStream("claude-agent-stderr", accountDir, spawnConvId);
|
|
9190
9428
|
stderrLog.on("error", () => {
|
|
@@ -9195,6 +9433,8 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
|
|
|
9195
9433
|
});
|
|
9196
9434
|
teeProcStderrToStreamLog(proc, streamLog);
|
|
9197
9435
|
streamLog.write(`[${isoTs()}] [subproc-debug-unavailable] reason=bundled-bun-binary-ignores-node-debug pid=${proc.pid} cli=claude
|
|
9436
|
+
`);
|
|
9437
|
+
streamLog.write(`[${isoTs()}] [spawn-env] STREAM_LOG_PATH=set pid=${proc.pid} conversationId=${spawnConvId} site=admin
|
|
9198
9438
|
`);
|
|
9199
9439
|
if (sessionKey) {
|
|
9200
9440
|
const prev = activeProcesses.get(sessionKey);
|
|
@@ -9520,15 +9760,8 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
|
|
|
9520
9760
|
const proc = spawn2("claude", args, {
|
|
9521
9761
|
cwd: accountDir,
|
|
9522
9762
|
stdio: ["ignore", "pipe", "pipe"],
|
|
9523
|
-
|
|
9524
|
-
|
|
9525
|
-
PLATFORM_ROOT: PLATFORM_ROOT4,
|
|
9526
|
-
ACCOUNT_DIR: accountDir
|
|
9527
|
-
// Task 535: NODE_DEBUG removed. The Claude Code CLI is a bundled Bun
|
|
9528
|
-
// binary and Bun ignores Node's NODE_DEBUG flag, so setting it here was
|
|
9529
|
-
// a no-op that misled future readers. The [subproc-debug-unavailable]
|
|
9530
|
-
// line below records the source-of-silence explicitly.
|
|
9531
|
-
}
|
|
9763
|
+
// Task 556: STREAM_LOG_PATH inherited by Bash-tool subprocesses.
|
|
9764
|
+
env: buildSpawnEnv(accountId, accountDir, managedConvId)
|
|
9532
9765
|
});
|
|
9533
9766
|
const stderrLog = agentLogStream("claude-agent-stderr", accountDir, managedConvId);
|
|
9534
9767
|
stderrLog.on("error", () => {
|
|
@@ -9536,6 +9769,8 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
|
|
|
9536
9769
|
proc.stderr?.pipe(stderrLog);
|
|
9537
9770
|
teeProcStderrToStreamLog(proc, streamLog);
|
|
9538
9771
|
streamLog.write(`[${isoTs()}] [subproc-debug-unavailable] reason=bundled-bun-binary-ignores-node-debug pid=${proc.pid} cli=claude
|
|
9772
|
+
`);
|
|
9773
|
+
streamLog.write(`[${isoTs()}] [spawn-env] STREAM_LOG_PATH=set pid=${proc.pid} conversationId=${managedConvId} site=managed
|
|
9539
9774
|
`);
|
|
9540
9775
|
if (sessionKey) {
|
|
9541
9776
|
const prev = activeProcesses.get(sessionKey);
|
|
@@ -10212,6 +10447,15 @@ ${body}`;
|
|
|
10212
10447
|
|
|
10213
10448
|
${manifest}`;
|
|
10214
10449
|
}
|
|
10450
|
+
const graphRefPath = resolve6(PLATFORM_ROOT4, "plugins/memory/references/graph-primitives.md");
|
|
10451
|
+
try {
|
|
10452
|
+
const graphRef = readFileSync7(graphRefPath, "utf-8");
|
|
10453
|
+
baseSystemPrompt += `
|
|
10454
|
+
|
|
10455
|
+
${graphRef}`;
|
|
10456
|
+
} catch (err) {
|
|
10457
|
+
console.error(`[graph-primitives] reference missing at ${graphRefPath} \u2014 admin session will have no Cypher cookbook: ${err instanceof Error ? err.message : String(err)}`);
|
|
10458
|
+
}
|
|
10215
10459
|
}
|
|
10216
10460
|
if (agentConfig?.budget) {
|
|
10217
10461
|
const pluginTokens = embeddedPlugins.reduce((sum, p) => sum + estimateTokens(p.body), 0);
|
|
@@ -26125,7 +26369,7 @@ var credsSaveQueue = Promise.resolve();
|
|
|
26125
26369
|
async function drainCredsSaveQueue(timeoutMs = 5e3) {
|
|
26126
26370
|
console.error(`${TAG4} draining credential save queue\u2026`);
|
|
26127
26371
|
const timer = new Promise(
|
|
26128
|
-
(
|
|
26372
|
+
(resolve29) => setTimeout(() => resolve29("timeout"), timeoutMs)
|
|
26129
26373
|
);
|
|
26130
26374
|
const result = await Promise.race([
|
|
26131
26375
|
credsSaveQueue.then(() => "drained"),
|
|
@@ -26253,11 +26497,11 @@ async function createWaSocket(opts) {
|
|
|
26253
26497
|
return sock;
|
|
26254
26498
|
}
|
|
26255
26499
|
async function waitForConnection(sock) {
|
|
26256
|
-
return new Promise((
|
|
26500
|
+
return new Promise((resolve29, reject) => {
|
|
26257
26501
|
const handler = (update) => {
|
|
26258
26502
|
if (update.connection === "open") {
|
|
26259
26503
|
sock.ev.off("connection.update", handler);
|
|
26260
|
-
|
|
26504
|
+
resolve29();
|
|
26261
26505
|
}
|
|
26262
26506
|
if (update.connection === "close") {
|
|
26263
26507
|
sock.ev.off("connection.update", handler);
|
|
@@ -26371,14 +26615,14 @@ ${inspected}`;
|
|
|
26371
26615
|
return inspect2(err, INSPECT_OPTS2);
|
|
26372
26616
|
}
|
|
26373
26617
|
function withTimeout(label, promise2, timeoutMs) {
|
|
26374
|
-
return new Promise((
|
|
26618
|
+
return new Promise((resolve29, reject) => {
|
|
26375
26619
|
const timer = setTimeout(() => {
|
|
26376
26620
|
reject(new Error(`${label} timed out after ${timeoutMs}ms`));
|
|
26377
26621
|
}, timeoutMs);
|
|
26378
26622
|
promise2.then(
|
|
26379
26623
|
(value) => {
|
|
26380
26624
|
clearTimeout(timer);
|
|
26381
|
-
|
|
26625
|
+
resolve29(value);
|
|
26382
26626
|
},
|
|
26383
26627
|
(err) => {
|
|
26384
26628
|
clearTimeout(timer);
|
|
@@ -27578,11 +27822,11 @@ async function connectWithReconnect(conn) {
|
|
|
27578
27822
|
console.error(
|
|
27579
27823
|
`${TAG12} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
|
|
27580
27824
|
);
|
|
27581
|
-
await new Promise((
|
|
27582
|
-
const timer = setTimeout(
|
|
27825
|
+
await new Promise((resolve29) => {
|
|
27826
|
+
const timer = setTimeout(resolve29, delay);
|
|
27583
27827
|
conn.abortController.signal.addEventListener("abort", () => {
|
|
27584
27828
|
clearTimeout(timer);
|
|
27585
|
-
|
|
27829
|
+
resolve29();
|
|
27586
27830
|
}, { once: true });
|
|
27587
27831
|
});
|
|
27588
27832
|
}
|
|
@@ -27590,16 +27834,16 @@ async function connectWithReconnect(conn) {
|
|
|
27590
27834
|
}
|
|
27591
27835
|
}
|
|
27592
27836
|
function waitForDisconnectEvent(conn) {
|
|
27593
|
-
return new Promise((
|
|
27837
|
+
return new Promise((resolve29) => {
|
|
27594
27838
|
if (!conn.sock) {
|
|
27595
|
-
|
|
27839
|
+
resolve29();
|
|
27596
27840
|
return;
|
|
27597
27841
|
}
|
|
27598
27842
|
const sock = conn.sock;
|
|
27599
27843
|
const handler = (update) => {
|
|
27600
27844
|
if (update.connection === "close") {
|
|
27601
27845
|
sock.ev.off("connection.update", handler);
|
|
27602
|
-
|
|
27846
|
+
resolve29();
|
|
27603
27847
|
}
|
|
27604
27848
|
};
|
|
27605
27849
|
sock.ev.on("connection.update", handler);
|
|
@@ -27809,8 +28053,8 @@ async function handleInboundMessage(conn, msg) {
|
|
|
27809
28053
|
const conversationKey = isGroup ? remoteJid : senderPhone;
|
|
27810
28054
|
const debounceKey = `${conn.accountId}:${conversationKey}:${senderPhone}`;
|
|
27811
28055
|
let resolvePending;
|
|
27812
|
-
const sttPending = new Promise((
|
|
27813
|
-
resolvePending =
|
|
28056
|
+
const sttPending = new Promise((resolve29) => {
|
|
28057
|
+
resolvePending = resolve29;
|
|
27814
28058
|
});
|
|
27815
28059
|
if (conn.debouncer) conn.debouncer.registerPending(debounceKey, sttPending);
|
|
27816
28060
|
try {
|
|
@@ -27917,20 +28161,20 @@ async function probeApiKey() {
|
|
|
27917
28161
|
return result.status;
|
|
27918
28162
|
}
|
|
27919
28163
|
function checkPort(port2, timeoutMs = 500) {
|
|
27920
|
-
return new Promise((
|
|
27921
|
-
const socket =
|
|
28164
|
+
return new Promise((resolve29) => {
|
|
28165
|
+
const socket = createConnection4(port2, "127.0.0.1");
|
|
27922
28166
|
socket.setTimeout(timeoutMs);
|
|
27923
28167
|
socket.once("connect", () => {
|
|
27924
28168
|
socket.destroy();
|
|
27925
|
-
|
|
28169
|
+
resolve29(true);
|
|
27926
28170
|
});
|
|
27927
28171
|
socket.once("error", () => {
|
|
27928
28172
|
socket.destroy();
|
|
27929
|
-
|
|
28173
|
+
resolve29(false);
|
|
27930
28174
|
});
|
|
27931
28175
|
socket.once("timeout", () => {
|
|
27932
28176
|
socket.destroy();
|
|
27933
|
-
|
|
28177
|
+
resolve29(false);
|
|
27934
28178
|
});
|
|
27935
28179
|
});
|
|
27936
28180
|
}
|
|
@@ -30121,8 +30365,8 @@ async function startLogin(opts) {
|
|
|
30121
30365
|
resetActiveLogin(accountId);
|
|
30122
30366
|
let resolveQr = null;
|
|
30123
30367
|
let rejectQr = null;
|
|
30124
|
-
const qrPromise = new Promise((
|
|
30125
|
-
resolveQr =
|
|
30368
|
+
const qrPromise = new Promise((resolve29, reject) => {
|
|
30369
|
+
resolveQr = resolve29;
|
|
30126
30370
|
rejectQr = reject;
|
|
30127
30371
|
});
|
|
30128
30372
|
const qrTimer = setTimeout(
|
|
@@ -31199,6 +31443,106 @@ async function createAdminSession(accountId, thinkingView, userId, userName) {
|
|
|
31199
31443
|
});
|
|
31200
31444
|
}
|
|
31201
31445
|
|
|
31446
|
+
// app/api/admin/chat/route.ts
|
|
31447
|
+
import { resolve as resolve19 } from "path";
|
|
31448
|
+
|
|
31449
|
+
// app/lib/script-stream-tailer.ts
|
|
31450
|
+
import { createReadStream as createReadStream2, statSync as statSync7 } from "fs";
|
|
31451
|
+
import { StringDecoder as StringDecoder2 } from "string_decoder";
|
|
31452
|
+
var SCRIPT_STREAM_RE = /^\[([^\]]+)\] \[(setup-tunnel|reset-tunnel)((?::[^\]]+)?)\] (.*)$/;
|
|
31453
|
+
function parseLine(line) {
|
|
31454
|
+
const m = line.match(SCRIPT_STREAM_RE);
|
|
31455
|
+
if (!m) return void 0;
|
|
31456
|
+
const [, timestamp, scope, tagSuffix, rest] = m;
|
|
31457
|
+
return {
|
|
31458
|
+
type: "script_stream",
|
|
31459
|
+
source: scope + (tagSuffix ?? ""),
|
|
31460
|
+
timestamp,
|
|
31461
|
+
line: rest
|
|
31462
|
+
};
|
|
31463
|
+
}
|
|
31464
|
+
function startScriptStreamTailer(opts) {
|
|
31465
|
+
const { path: path2, onEvent, onError } = opts;
|
|
31466
|
+
let offset;
|
|
31467
|
+
try {
|
|
31468
|
+
offset = statSync7(path2).size;
|
|
31469
|
+
} catch {
|
|
31470
|
+
offset = 0;
|
|
31471
|
+
}
|
|
31472
|
+
const utf8 = new StringDecoder2("utf8");
|
|
31473
|
+
let buffer = "";
|
|
31474
|
+
let stopped = false;
|
|
31475
|
+
let pendingRead = false;
|
|
31476
|
+
let timer;
|
|
31477
|
+
const processLine = (line) => {
|
|
31478
|
+
const event = parseLine(line);
|
|
31479
|
+
if (event) onEvent(event);
|
|
31480
|
+
};
|
|
31481
|
+
const readDelta = async () => {
|
|
31482
|
+
if (pendingRead) return;
|
|
31483
|
+
pendingRead = true;
|
|
31484
|
+
try {
|
|
31485
|
+
let size;
|
|
31486
|
+
try {
|
|
31487
|
+
size = statSync7(path2).size;
|
|
31488
|
+
} catch {
|
|
31489
|
+
return;
|
|
31490
|
+
}
|
|
31491
|
+
if (size === offset) return;
|
|
31492
|
+
if (size < offset) {
|
|
31493
|
+
offset = 0;
|
|
31494
|
+
buffer = "";
|
|
31495
|
+
}
|
|
31496
|
+
await new Promise((res, rej) => {
|
|
31497
|
+
const stream = createReadStream2(path2, { start: offset, end: size - 1 });
|
|
31498
|
+
stream.on("data", (chunk) => {
|
|
31499
|
+
buffer += typeof chunk === "string" ? chunk : utf8.write(chunk);
|
|
31500
|
+
let idx;
|
|
31501
|
+
while ((idx = buffer.indexOf("\n")) !== -1) {
|
|
31502
|
+
const line = buffer.slice(0, idx);
|
|
31503
|
+
buffer = buffer.slice(idx + 1);
|
|
31504
|
+
if (line.length > 0) processLine(line);
|
|
31505
|
+
}
|
|
31506
|
+
});
|
|
31507
|
+
stream.on("end", () => {
|
|
31508
|
+
offset = size;
|
|
31509
|
+
res();
|
|
31510
|
+
});
|
|
31511
|
+
stream.on("error", rej);
|
|
31512
|
+
});
|
|
31513
|
+
} catch (err) {
|
|
31514
|
+
if (onError) onError(err instanceof Error ? err : new Error(String(err)));
|
|
31515
|
+
} finally {
|
|
31516
|
+
pendingRead = false;
|
|
31517
|
+
}
|
|
31518
|
+
};
|
|
31519
|
+
const tick = () => {
|
|
31520
|
+
if (stopped) return;
|
|
31521
|
+
readDelta().catch(() => {
|
|
31522
|
+
}).finally(() => {
|
|
31523
|
+
if (!stopped) timer = setTimeout(tick, 200);
|
|
31524
|
+
});
|
|
31525
|
+
};
|
|
31526
|
+
timer = setTimeout(tick, 0);
|
|
31527
|
+
return {
|
|
31528
|
+
async stop() {
|
|
31529
|
+
if (stopped) return;
|
|
31530
|
+
stopped = true;
|
|
31531
|
+
if (timer) clearTimeout(timer);
|
|
31532
|
+
while (pendingRead) {
|
|
31533
|
+
await new Promise((r) => setImmediate(r));
|
|
31534
|
+
}
|
|
31535
|
+
await readDelta();
|
|
31536
|
+
buffer += utf8.end();
|
|
31537
|
+
if (buffer.length > 0) {
|
|
31538
|
+
const event = parseLine(buffer);
|
|
31539
|
+
if (event) onEvent(event);
|
|
31540
|
+
buffer = "";
|
|
31541
|
+
}
|
|
31542
|
+
}
|
|
31543
|
+
};
|
|
31544
|
+
}
|
|
31545
|
+
|
|
31202
31546
|
// app/api/admin/chat/route.ts
|
|
31203
31547
|
function isComponentDone(parsed) {
|
|
31204
31548
|
return typeof parsed === "object" && parsed !== null && parsed._componentDone === true && typeof parsed.component === "string" && typeof parsed.payload === "string";
|
|
@@ -31390,8 +31734,32 @@ async function POST21(req) {
|
|
|
31390
31734
|
const sseConvId = getConversationIdForSession(session_key);
|
|
31391
31735
|
const sseLog = sseConvId ? agentLogStream("sse-events", account.accountDir, sseConvId) : preConversationLogStream("sse-events", account.accountDir);
|
|
31392
31736
|
const sk = sseConvId?.slice(0, 8) ?? session_key.slice(0, 8);
|
|
31737
|
+
let tailer = null;
|
|
31393
31738
|
const readable = new ReadableStream({
|
|
31394
31739
|
async start(controller) {
|
|
31740
|
+
let controllerOpen = true;
|
|
31741
|
+
if (sseConvId) {
|
|
31742
|
+
const streamLogPath = resolve19(account.accountDir, "logs", `claude-agent-stream-${sseConvId}.log`);
|
|
31743
|
+
tailer = startScriptStreamTailer({
|
|
31744
|
+
path: streamLogPath,
|
|
31745
|
+
onEvent: (event) => {
|
|
31746
|
+
if (!controllerOpen) return;
|
|
31747
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
|
|
31748
|
+
sseLog.write(`[${ts}] [${sk}] admin: ${JSON.stringify(event)}
|
|
31749
|
+
`);
|
|
31750
|
+
try {
|
|
31751
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}
|
|
31752
|
+
|
|
31753
|
+
`));
|
|
31754
|
+
} catch {
|
|
31755
|
+
controllerOpen = false;
|
|
31756
|
+
}
|
|
31757
|
+
},
|
|
31758
|
+
onError: (err) => {
|
|
31759
|
+
console.error(`[script-stream-tailer] ${streamLogPath}: ${err.message}`);
|
|
31760
|
+
}
|
|
31761
|
+
});
|
|
31762
|
+
}
|
|
31395
31763
|
try {
|
|
31396
31764
|
for await (const event of invokeAgent(
|
|
31397
31765
|
{ type: "admin", skipTopicCheck },
|
|
@@ -31439,6 +31807,13 @@ async function POST21(req) {
|
|
|
31439
31807
|
}
|
|
31440
31808
|
}
|
|
31441
31809
|
} finally {
|
|
31810
|
+
if (tailer) {
|
|
31811
|
+
try {
|
|
31812
|
+
await tailer.stop();
|
|
31813
|
+
} catch {
|
|
31814
|
+
}
|
|
31815
|
+
}
|
|
31816
|
+
controllerOpen = false;
|
|
31442
31817
|
sseLog.end();
|
|
31443
31818
|
try {
|
|
31444
31819
|
controller.close();
|
|
@@ -31498,8 +31873,8 @@ async function POST22(req) {
|
|
|
31498
31873
|
}
|
|
31499
31874
|
|
|
31500
31875
|
// app/api/admin/logs/route.ts
|
|
31501
|
-
import { existsSync as existsSync19, readdirSync as readdirSync5, readFileSync as readFileSync20, statSync as
|
|
31502
|
-
import { resolve as
|
|
31876
|
+
import { existsSync as existsSync19, readdirSync as readdirSync5, readFileSync as readFileSync20, statSync as statSync8 } from "fs";
|
|
31877
|
+
import { resolve as resolve20, basename as basename5 } from "path";
|
|
31503
31878
|
var TAIL_BYTES = 8192;
|
|
31504
31879
|
async function GET9(request) {
|
|
31505
31880
|
const { searchParams } = new URL(request.url);
|
|
@@ -31508,13 +31883,13 @@ async function GET9(request) {
|
|
|
31508
31883
|
const conversationIdParam = searchParams.get("conversationId");
|
|
31509
31884
|
const download = searchParams.get("download") === "1";
|
|
31510
31885
|
const account = resolveAccount();
|
|
31511
|
-
const accountLogDir2 = account ?
|
|
31886
|
+
const accountLogDir2 = account ? resolve20(account.accountDir, "logs") : null;
|
|
31512
31887
|
if (fileParam) {
|
|
31513
31888
|
const safe = basename5(fileParam);
|
|
31514
31889
|
const searched = [];
|
|
31515
31890
|
for (const dir of [accountLogDir2, LOG_DIR]) {
|
|
31516
31891
|
if (!dir) continue;
|
|
31517
|
-
const filePath =
|
|
31892
|
+
const filePath = resolve20(dir, safe);
|
|
31518
31893
|
searched.push(filePath);
|
|
31519
31894
|
try {
|
|
31520
31895
|
const content = readFileSync20(filePath, "utf-8");
|
|
@@ -31556,7 +31931,7 @@ async function GET9(request) {
|
|
|
31556
31931
|
const searched = [];
|
|
31557
31932
|
for (const dir of [accountLogDir2, LOG_DIR]) {
|
|
31558
31933
|
if (!dir) continue;
|
|
31559
|
-
const filePath =
|
|
31934
|
+
const filePath = resolve20(dir, fileName);
|
|
31560
31935
|
searched.push(filePath);
|
|
31561
31936
|
try {
|
|
31562
31937
|
const content = readFileSync20(filePath, "utf-8");
|
|
@@ -31583,10 +31958,10 @@ async function GET9(request) {
|
|
|
31583
31958
|
console.warn(`[admin/logs] readdir-fail dir=${dir} reason=${reason}`);
|
|
31584
31959
|
continue;
|
|
31585
31960
|
}
|
|
31586
|
-
files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime:
|
|
31961
|
+
files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync8(resolve20(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).forEach(({ name }) => {
|
|
31587
31962
|
seen.add(name);
|
|
31588
31963
|
try {
|
|
31589
|
-
const content = readFileSync20(
|
|
31964
|
+
const content = readFileSync20(resolve20(dir, name));
|
|
31590
31965
|
const tail = content.length > TAIL_BYTES ? content.subarray(content.length - TAIL_BYTES).toString("utf-8") : content.toString("utf-8");
|
|
31591
31966
|
logs[name] = tail.trim() || "(empty)";
|
|
31592
31967
|
} catch (err) {
|
|
@@ -31624,7 +31999,7 @@ async function GET10() {
|
|
|
31624
31999
|
// app/api/admin/attachment/[attachmentId]/route.ts
|
|
31625
32000
|
import { readFile as readFile3, readdir } from "fs/promises";
|
|
31626
32001
|
import { existsSync as existsSync20 } from "fs";
|
|
31627
|
-
import { resolve as
|
|
32002
|
+
import { resolve as resolve21 } from "path";
|
|
31628
32003
|
async function GET11(req, attachmentId) {
|
|
31629
32004
|
const sessionKey = new URL(req.url).searchParams.get("session_key") ?? "";
|
|
31630
32005
|
if (!validateSession(sessionKey, "admin")) {
|
|
@@ -31637,11 +32012,11 @@ async function GET11(req, attachmentId) {
|
|
|
31637
32012
|
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(attachmentId)) {
|
|
31638
32013
|
return new Response("Not found", { status: 404 });
|
|
31639
32014
|
}
|
|
31640
|
-
const dir =
|
|
32015
|
+
const dir = resolve21(ATTACHMENTS_ROOT, accountId, attachmentId);
|
|
31641
32016
|
if (!existsSync20(dir)) {
|
|
31642
32017
|
return new Response("Not found", { status: 404 });
|
|
31643
32018
|
}
|
|
31644
|
-
const metaPath =
|
|
32019
|
+
const metaPath = resolve21(dir, `${attachmentId}.meta.json`);
|
|
31645
32020
|
if (!existsSync20(metaPath)) {
|
|
31646
32021
|
return new Response("Not found", { status: 404 });
|
|
31647
32022
|
}
|
|
@@ -31656,7 +32031,7 @@ async function GET11(req, attachmentId) {
|
|
|
31656
32031
|
if (!dataFile) {
|
|
31657
32032
|
return new Response("Not found", { status: 404 });
|
|
31658
32033
|
}
|
|
31659
|
-
const filePath =
|
|
32034
|
+
const filePath = resolve21(dir, dataFile);
|
|
31660
32035
|
const buffer = await readFile3(filePath);
|
|
31661
32036
|
return new Response(buffer, {
|
|
31662
32037
|
headers: {
|
|
@@ -31669,7 +32044,7 @@ async function GET11(req, attachmentId) {
|
|
|
31669
32044
|
|
|
31670
32045
|
// app/api/admin/account/route.ts
|
|
31671
32046
|
import { readFileSync as readFileSync21, writeFileSync as writeFileSync15 } from "fs";
|
|
31672
|
-
import { resolve as
|
|
32047
|
+
import { resolve as resolve22 } from "path";
|
|
31673
32048
|
var VALID_CONTEXT_MODES = ["managed", "claude-code"];
|
|
31674
32049
|
async function PATCH(req) {
|
|
31675
32050
|
let body;
|
|
@@ -31692,7 +32067,7 @@ async function PATCH(req) {
|
|
|
31692
32067
|
if (!account) {
|
|
31693
32068
|
return Response.json({ error: "No account configured" }, { status: 500 });
|
|
31694
32069
|
}
|
|
31695
|
-
const configPath2 =
|
|
32070
|
+
const configPath2 = resolve22(account.accountDir, "account.json");
|
|
31696
32071
|
try {
|
|
31697
32072
|
const raw2 = readFileSync21(configPath2, "utf-8");
|
|
31698
32073
|
const config2 = JSON.parse(raw2);
|
|
@@ -31707,14 +32082,14 @@ async function PATCH(req) {
|
|
|
31707
32082
|
}
|
|
31708
32083
|
|
|
31709
32084
|
// app/api/admin/agents/route.ts
|
|
31710
|
-
import { resolve as
|
|
32085
|
+
import { resolve as resolve23 } from "path";
|
|
31711
32086
|
import { readdirSync as readdirSync6, readFileSync as readFileSync22, existsSync as existsSync21 } from "fs";
|
|
31712
32087
|
async function GET12() {
|
|
31713
32088
|
const account = resolveAccount();
|
|
31714
32089
|
if (!account) {
|
|
31715
32090
|
return Response.json({ agents: [] });
|
|
31716
32091
|
}
|
|
31717
|
-
const agentsDir =
|
|
32092
|
+
const agentsDir = resolve23(account.accountDir, "agents");
|
|
31718
32093
|
if (!existsSync21(agentsDir)) {
|
|
31719
32094
|
return Response.json({ agents: [] });
|
|
31720
32095
|
}
|
|
@@ -31724,7 +32099,7 @@ async function GET12() {
|
|
|
31724
32099
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
31725
32100
|
if (!entry.isDirectory()) continue;
|
|
31726
32101
|
if (entry.name === "admin") continue;
|
|
31727
|
-
const configPath2 =
|
|
32102
|
+
const configPath2 = resolve23(agentsDir, entry.name, "config.json");
|
|
31728
32103
|
if (!existsSync21(configPath2)) continue;
|
|
31729
32104
|
try {
|
|
31730
32105
|
const config2 = JSON.parse(readFileSync22(configPath2, "utf-8"));
|
|
@@ -31745,7 +32120,7 @@ async function GET12() {
|
|
|
31745
32120
|
}
|
|
31746
32121
|
|
|
31747
32122
|
// app/api/admin/agents/[slug]/route.ts
|
|
31748
|
-
import { resolve as
|
|
32123
|
+
import { resolve as resolve24 } from "path";
|
|
31749
32124
|
import { existsSync as existsSync22, rmSync as rmSync2 } from "fs";
|
|
31750
32125
|
async function DELETE2(_req, { params }) {
|
|
31751
32126
|
const { slug } = await params;
|
|
@@ -31759,7 +32134,7 @@ async function DELETE2(_req, { params }) {
|
|
|
31759
32134
|
if (slug.includes("/") || slug.includes("..") || slug.includes("\\")) {
|
|
31760
32135
|
return Response.json({ error: "Invalid agent slug" }, { status: 400 });
|
|
31761
32136
|
}
|
|
31762
|
-
const agentDir =
|
|
32137
|
+
const agentDir = resolve24(account.accountDir, "agents", slug);
|
|
31763
32138
|
if (!existsSync22(agentDir)) {
|
|
31764
32139
|
return Response.json({ error: "Agent not found" }, { status: 404 });
|
|
31765
32140
|
}
|
|
@@ -31775,8 +32150,8 @@ async function DELETE2(_req, { params }) {
|
|
|
31775
32150
|
|
|
31776
32151
|
// app/api/admin/version/route.ts
|
|
31777
32152
|
import { readFileSync as readFileSync23, existsSync as existsSync23 } from "fs";
|
|
31778
|
-
import { resolve as
|
|
31779
|
-
var PLATFORM_ROOT9 = process.env.MAXY_PLATFORM_ROOT ??
|
|
32153
|
+
import { resolve as resolve25, join as join10 } from "path";
|
|
32154
|
+
var PLATFORM_ROOT9 = process.env.MAXY_PLATFORM_ROOT ?? resolve25(process.cwd(), "..");
|
|
31780
32155
|
var brandHostname = "maxy";
|
|
31781
32156
|
var brandNpmPackage = "@rubytech/create-maxy";
|
|
31782
32157
|
var brandJsonPath = join10(PLATFORM_ROOT9, "config", "brand.json");
|
|
@@ -31788,7 +32163,7 @@ if (existsSync23(brandJsonPath)) {
|
|
|
31788
32163
|
} catch {
|
|
31789
32164
|
}
|
|
31790
32165
|
}
|
|
31791
|
-
var VERSION_FILE =
|
|
32166
|
+
var VERSION_FILE = resolve25(PLATFORM_ROOT9, `config/.${brandHostname}-version`);
|
|
31792
32167
|
var NPM_PACKAGE = brandNpmPackage;
|
|
31793
32168
|
var REGISTRY_URL = `https://registry.npmjs.org/${NPM_PACKAGE}/latest`;
|
|
31794
32169
|
var FETCH_TIMEOUT_MS = 5e3;
|
|
@@ -31845,9 +32220,9 @@ async function GET13() {
|
|
|
31845
32220
|
|
|
31846
32221
|
// app/api/admin/version/upgrade/route.ts
|
|
31847
32222
|
import { spawn as spawn4 } from "child_process";
|
|
31848
|
-
import { existsSync as existsSync24, statSync as
|
|
31849
|
-
import { resolve as
|
|
31850
|
-
var PLATFORM_ROOT10 = process.env.MAXY_PLATFORM_ROOT ??
|
|
32223
|
+
import { existsSync as existsSync24, statSync as statSync9, writeFileSync as writeFileSync16, readFileSync as readFileSync24, openSync as openSync4, closeSync as closeSync4 } from "fs";
|
|
32224
|
+
import { resolve as resolve26, join as join11 } from "path";
|
|
32225
|
+
var PLATFORM_ROOT10 = process.env.MAXY_PLATFORM_ROOT ?? resolve26(process.cwd(), "..");
|
|
31851
32226
|
var upgradePkg = "@rubytech/create-maxy";
|
|
31852
32227
|
var upgradeHostname = "maxy";
|
|
31853
32228
|
var brandPath = join11(PLATFORM_ROOT10, "config", "brand.json");
|
|
@@ -31865,7 +32240,7 @@ var LOCK_MAX_AGE_MS = 20 * 60 * 1e3;
|
|
|
31865
32240
|
function isLockFresh() {
|
|
31866
32241
|
if (!existsSync24(LOCK_FILE)) return false;
|
|
31867
32242
|
try {
|
|
31868
|
-
const stat4 =
|
|
32243
|
+
const stat4 = statSync9(LOCK_FILE);
|
|
31869
32244
|
return Date.now() - stat4.mtimeMs < LOCK_MAX_AGE_MS;
|
|
31870
32245
|
} catch {
|
|
31871
32246
|
return false;
|
|
@@ -31907,7 +32282,7 @@ async function POST23(req) {
|
|
|
31907
32282
|
detached: true,
|
|
31908
32283
|
stdio: ["ignore", logFd, logFd],
|
|
31909
32284
|
env: { ...process.env, npm_config_yes: "true" },
|
|
31910
|
-
cwd:
|
|
32285
|
+
cwd: resolve26(process.cwd(), "..")
|
|
31911
32286
|
});
|
|
31912
32287
|
child.unref();
|
|
31913
32288
|
closeSync4(logFd);
|
|
@@ -31924,8 +32299,8 @@ async function POST23(req) {
|
|
|
31924
32299
|
|
|
31925
32300
|
// app/api/admin/version/upgrade/progress/route.ts
|
|
31926
32301
|
import { existsSync as existsSync25, readFileSync as readFileSync25 } from "fs";
|
|
31927
|
-
import { resolve as
|
|
31928
|
-
var PLATFORM_ROOT11 = process.env.MAXY_PLATFORM_ROOT ??
|
|
32302
|
+
import { resolve as resolve27, join as join12 } from "path";
|
|
32303
|
+
var PLATFORM_ROOT11 = process.env.MAXY_PLATFORM_ROOT ?? resolve27(process.cwd(), "..");
|
|
31929
32304
|
var upgradeHostname2 = "maxy";
|
|
31930
32305
|
var brandPath2 = join12(PLATFORM_ROOT11, "config", "brand.json");
|
|
31931
32306
|
if (existsSync25(brandPath2)) {
|
|
@@ -32851,8 +33226,8 @@ app.get("/agent-assets/:slug/:filename", (c) => {
|
|
|
32851
33226
|
console.error(`[agent-assets] no-account slug=${slug} file=${filename}`);
|
|
32852
33227
|
return c.text("Not found", 404);
|
|
32853
33228
|
}
|
|
32854
|
-
const filePath =
|
|
32855
|
-
const expectedDir =
|
|
33229
|
+
const filePath = resolve28(account.accountDir, "agents", slug, "assets", filename);
|
|
33230
|
+
const expectedDir = resolve28(account.accountDir, "agents", slug, "assets");
|
|
32856
33231
|
if (!filePath.startsWith(expectedDir + "/")) {
|
|
32857
33232
|
console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
|
|
32858
33233
|
return c.text("Forbidden", 403);
|
|
@@ -32881,8 +33256,8 @@ app.get("/generated/:filename", (c) => {
|
|
|
32881
33256
|
console.error(`[generated] serve file=${filename} status=404`);
|
|
32882
33257
|
return c.text("Not found", 404);
|
|
32883
33258
|
}
|
|
32884
|
-
const filePath =
|
|
32885
|
-
const expectedDir =
|
|
33259
|
+
const filePath = resolve28(account.accountDir, "generated", filename);
|
|
33260
|
+
const expectedDir = resolve28(account.accountDir, "generated");
|
|
32886
33261
|
if (!filePath.startsWith(expectedDir + "/")) {
|
|
32887
33262
|
console.error(`[generated] serve file=${filename} status=403`);
|
|
32888
33263
|
return c.text("Forbidden", 403);
|
|
@@ -32922,7 +33297,7 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
|
|
|
32922
33297
|
function cachedHtml(file2) {
|
|
32923
33298
|
let html = htmlCache.get(file2);
|
|
32924
33299
|
if (!html) {
|
|
32925
|
-
html = readFileSync26(
|
|
33300
|
+
html = readFileSync26(resolve28(process.cwd(), "public", file2), "utf-8");
|
|
32926
33301
|
html = html.replace("<title>Maxy</title>", `<title>${escapeHtml2(BRAND.productName)}</title>`);
|
|
32927
33302
|
html = html.replace('href="/favicon.ico"', `href="${escapeHtml2(brandFaviconPath)}"`);
|
|
32928
33303
|
html = html.replace("</head>", `${brandScript}
|
|
@@ -33025,7 +33400,7 @@ app.use("/vnc-popout.html", logViewerFetch);
|
|
|
33025
33400
|
app.get("/vnc-popout.html", (c) => {
|
|
33026
33401
|
let html = htmlCache.get("vnc-popout.html");
|
|
33027
33402
|
if (!html) {
|
|
33028
|
-
html = readFileSync26(
|
|
33403
|
+
html = readFileSync26(resolve28(process.cwd(), "public", "vnc-popout.html"), "utf-8");
|
|
33029
33404
|
const name = escapeHtml2(BRAND.productName);
|
|
33030
33405
|
html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
|
|
33031
33406
|
html = html.replace("</head>", ` ${brandScript}
|
|
@@ -33066,6 +33441,9 @@ app.get("/:slug", async (c, next) => {
|
|
|
33066
33441
|
}
|
|
33067
33442
|
await next();
|
|
33068
33443
|
});
|
|
33444
|
+
app.use("/graph/*", graphAuthMiddleware({ isPublicHost, canAccessAdmin }));
|
|
33445
|
+
app.use("/graph", graphAuthMiddleware({ isPublicHost, canAccessAdmin }));
|
|
33446
|
+
attachGraphHttpRoutes(app);
|
|
33069
33447
|
app.use("/*", serveStatic({ root: "./public" }));
|
|
33070
33448
|
var port = parseInt(process.env.PORT ?? "19200", 10);
|
|
33071
33449
|
var hostname3 = process.env.HOSTNAME ?? "0.0.0.0";
|
|
@@ -33075,6 +33453,7 @@ attachVncWsProxy(httpServer, {
|
|
|
33075
33453
|
upstreamHost: "127.0.0.1",
|
|
33076
33454
|
upstreamPort: 6080
|
|
33077
33455
|
});
|
|
33456
|
+
attachGraphWsProxy(httpServer, { isPublicHost, canAccessAdmin });
|
|
33078
33457
|
console.log(`${BRAND.productName} listening on http://${hostname3}:${port}`);
|
|
33079
33458
|
(async () => {
|
|
33080
33459
|
try {
|
|
@@ -33106,7 +33485,7 @@ if (bootAccountConfig?.whatsapp) {
|
|
|
33106
33485
|
}
|
|
33107
33486
|
init({
|
|
33108
33487
|
configDir: configDirForWhatsApp,
|
|
33109
|
-
platformRoot:
|
|
33488
|
+
platformRoot: resolve28(process.env.MAXY_PLATFORM_ROOT ?? join13(__dirname, "..")),
|
|
33110
33489
|
accountConfig: bootAccountConfig,
|
|
33111
33490
|
onMessage: async (msg) => {
|
|
33112
33491
|
try {
|