@rubytech/create-maxy 1.0.685 → 1.0.686
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 +23 -215
- package/dist/pinned-binaries.js +10 -41
- package/dist/uninstall.js +23 -23
- package/package.json +1 -1
- package/payload/platform/plugins/cloudflare/scripts/setup-tunnel.sh +35 -9
- package/payload/platform/plugins/docs/PLUGIN.md +2 -0
- package/payload/platform/plugins/docs/references/cloudflare.md +1 -1
- package/payload/platform/plugins/docs/references/deployment.md +13 -10
- package/payload/platform/plugins/docs/references/graph.md +38 -0
- package/payload/platform/plugins/docs/references/platform.md +10 -7
- package/payload/platform/plugins/docs/references/troubleshooting.md +23 -13
- package/payload/platform/scripts/vnc.sh +7 -7
- package/payload/platform/templates/systemd/edge.service.template +5 -4
- package/payload/server/maxy-edge.js +5 -367
- package/payload/server/public/assets/admin-BqLtaMVu.js +352 -0
- package/payload/server/public/assets/{data-DUSyrydY.js → data-BZ7v-zug.js} +1 -1
- package/payload/server/public/assets/{file-CDJ6dUV3.js → file-CScYkZq5.js} +1 -1
- package/payload/server/public/assets/graph-tjXdtwk-.js +50 -0
- package/payload/server/public/assets/{house-CNP_bwvT.js → house-CdFRNujU.js} +1 -1
- package/payload/server/public/assets/{jsx-runtime-BFFQvkdQ.css → jsx-runtime-Og0q7dXg.css} +1 -1
- package/payload/server/public/assets/{public-sHoAccvb.js → public-CrkQJek6.js} +2 -2
- package/payload/server/public/assets/{share-2-DBcb9j6E.js → share-2-Ev-D4Lm9.js} +1 -1
- package/payload/server/public/assets/{useVoiceRecorder-CtSgpc95.js → useVoiceRecorder-DyDXH7EA.js} +2 -2
- package/payload/server/public/assets/{x-CTVJaC_u.js → x-D5W7ddgP.js} +1 -1
- package/payload/server/public/data.html +6 -6
- package/payload/server/public/graph.html +6 -6
- package/payload/server/public/index.html +7 -8
- package/payload/server/public/public.html +4 -4
- package/payload/server/server.js +830 -258
- package/payload/platform/templates/dotfiles/.tmux.conf +0 -1
- package/payload/platform/templates/systemd/ttyd.service.template +0 -30
- package/payload/server/public/assets/admin-BFmYXz1V.js +0 -362
- package/payload/server/public/assets/admin-kHJ-D0s7.css +0 -1
- package/payload/server/public/assets/graph-CWcYp5bE.js +0 -50
- /package/payload/server/public/assets/{jsx-runtime-BVKWELH6.js → jsx-runtime-CHqDsKlc.js} +0 -0
package/payload/server/server.js
CHANGED
|
@@ -1203,14 +1203,14 @@ var Hono = class _Hono {
|
|
|
1203
1203
|
* app.route("/api", app2) // GET /api/user
|
|
1204
1204
|
* ```
|
|
1205
1205
|
*/
|
|
1206
|
-
route(path2,
|
|
1206
|
+
route(path2, app39) {
|
|
1207
1207
|
const subApp = this.basePath(path2);
|
|
1208
|
-
|
|
1208
|
+
app39.routes.map((r) => {
|
|
1209
1209
|
let handler;
|
|
1210
|
-
if (
|
|
1210
|
+
if (app39.errorHandler === errorHandler) {
|
|
1211
1211
|
handler = r.handler;
|
|
1212
1212
|
} else {
|
|
1213
|
-
handler = async (c, next) => (await compose([],
|
|
1213
|
+
handler = async (c, next) => (await compose([], app39.errorHandler)(c, () => r.handler(c, next))).res;
|
|
1214
1214
|
handler[COMPOSED_HANDLER] = r.handler;
|
|
1215
1215
|
}
|
|
1216
1216
|
subApp.#addRoute(r.method, r.path, handler);
|
|
@@ -2393,13 +2393,13 @@ function writeFromReadableStreamDefaultReader(reader, writable, currentReadPromi
|
|
|
2393
2393
|
}
|
|
2394
2394
|
}
|
|
2395
2395
|
}
|
|
2396
|
-
function writeFromReadableStream(
|
|
2397
|
-
if (
|
|
2396
|
+
function writeFromReadableStream(stream2, writable) {
|
|
2397
|
+
if (stream2.locked) {
|
|
2398
2398
|
throw new TypeError("ReadableStream is locked.");
|
|
2399
2399
|
} else if (writable.destroyed) {
|
|
2400
2400
|
return;
|
|
2401
2401
|
}
|
|
2402
|
-
return writeFromReadableStreamDefaultReader(
|
|
2402
|
+
return writeFromReadableStreamDefaultReader(stream2.getReader(), writable);
|
|
2403
2403
|
}
|
|
2404
2404
|
var buildOutgoingHttpHeaders = (headers) => {
|
|
2405
2405
|
const res = {};
|
|
@@ -2528,7 +2528,7 @@ var responseViaResponseObject = async (res, outgoing, options = {}) => {
|
|
|
2528
2528
|
});
|
|
2529
2529
|
if (!chunk) {
|
|
2530
2530
|
if (i === 1) {
|
|
2531
|
-
await new Promise((
|
|
2531
|
+
await new Promise((resolve29) => setTimeout(resolve29));
|
|
2532
2532
|
maxReadCount = 3;
|
|
2533
2533
|
continue;
|
|
2534
2534
|
}
|
|
@@ -2761,24 +2761,24 @@ var pr54206Applied = () => {
|
|
|
2761
2761
|
return major >= 23 || major === 22 && minor >= 7 || major === 20 && minor >= 18;
|
|
2762
2762
|
};
|
|
2763
2763
|
var useReadableToWeb = pr54206Applied();
|
|
2764
|
-
var createStreamBody = (
|
|
2764
|
+
var createStreamBody = (stream2) => {
|
|
2765
2765
|
if (useReadableToWeb) {
|
|
2766
|
-
return Readable2.toWeb(
|
|
2766
|
+
return Readable2.toWeb(stream2);
|
|
2767
2767
|
}
|
|
2768
2768
|
const body = new ReadableStream({
|
|
2769
2769
|
start(controller) {
|
|
2770
|
-
|
|
2770
|
+
stream2.on("data", (chunk) => {
|
|
2771
2771
|
controller.enqueue(chunk);
|
|
2772
2772
|
});
|
|
2773
|
-
|
|
2773
|
+
stream2.on("error", (err) => {
|
|
2774
2774
|
controller.error(err);
|
|
2775
2775
|
});
|
|
2776
|
-
|
|
2776
|
+
stream2.on("end", () => {
|
|
2777
2777
|
controller.close();
|
|
2778
2778
|
});
|
|
2779
2779
|
},
|
|
2780
2780
|
cancel() {
|
|
2781
|
-
|
|
2781
|
+
stream2.destroy();
|
|
2782
2782
|
}
|
|
2783
2783
|
});
|
|
2784
2784
|
return body;
|
|
@@ -2883,10 +2883,10 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
2883
2883
|
end = size - 1;
|
|
2884
2884
|
}
|
|
2885
2885
|
const chunksize = end - start + 1;
|
|
2886
|
-
const
|
|
2886
|
+
const stream2 = createReadStream(path2, { start, end });
|
|
2887
2887
|
c.header("Content-Length", chunksize.toString());
|
|
2888
2888
|
c.header("Content-Range", `bytes ${start}-${end}/${stats.size}`);
|
|
2889
|
-
result = c.body(createStreamBody(
|
|
2889
|
+
result = c.body(createStreamBody(stream2), 206);
|
|
2890
2890
|
}
|
|
2891
2891
|
await options.onFound?.(path2, c);
|
|
2892
2892
|
return result;
|
|
@@ -2894,9 +2894,9 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
2894
2894
|
};
|
|
2895
2895
|
|
|
2896
2896
|
// server/index.ts
|
|
2897
|
-
import { readFileSync as
|
|
2898
|
-
import { resolve as
|
|
2899
|
-
import { homedir as
|
|
2897
|
+
import { readFileSync as readFileSync26, existsSync as existsSync25, watchFile } from "fs";
|
|
2898
|
+
import { resolve as resolve28, join as join13, basename as basename7 } from "path";
|
|
2899
|
+
import { homedir as homedir5 } from "os";
|
|
2900
2900
|
|
|
2901
2901
|
// app/lib/agent-slug-pattern.ts
|
|
2902
2902
|
var AGENT_SLUG_PATTERN = /^\/([a-z][a-z0-9-]{2,49})$/;
|
|
@@ -3992,8 +3992,8 @@ async function persistMessage(conversationId, role, content, accountId, tokens,
|
|
|
3992
3992
|
const prev = persistMessageLocks.get(conversationId);
|
|
3993
3993
|
const waited = prev !== void 0;
|
|
3994
3994
|
let release;
|
|
3995
|
-
const mine = new Promise((
|
|
3996
|
-
release =
|
|
3995
|
+
const mine = new Promise((resolve29) => {
|
|
3996
|
+
release = resolve29;
|
|
3997
3997
|
});
|
|
3998
3998
|
const chained = (prev ?? Promise.resolve()).then(() => mine);
|
|
3999
3999
|
persistMessageLocks.set(conversationId, chained);
|
|
@@ -4252,7 +4252,7 @@ ${userContent}`;
|
|
|
4252
4252
|
"dontAsk",
|
|
4253
4253
|
prompt
|
|
4254
4254
|
];
|
|
4255
|
-
return new Promise((
|
|
4255
|
+
return new Promise((resolve29) => {
|
|
4256
4256
|
let stdout = "";
|
|
4257
4257
|
let stderr = "";
|
|
4258
4258
|
const spawnFn = _spawnOverride ?? spawn;
|
|
@@ -4270,35 +4270,35 @@ ${userContent}`;
|
|
|
4270
4270
|
const timer = setTimeout(() => {
|
|
4271
4271
|
proc.kill("SIGTERM");
|
|
4272
4272
|
console.error("[persist] autoLabel: haiku subprocess timed out");
|
|
4273
|
-
|
|
4273
|
+
resolve29(null);
|
|
4274
4274
|
}, SESSION_LABEL_TIMEOUT_MS);
|
|
4275
4275
|
proc.on("error", (err) => {
|
|
4276
4276
|
clearTimeout(timer);
|
|
4277
4277
|
console.error(`[persist] autoLabel: subprocess error \u2014 ${err.message}`);
|
|
4278
|
-
|
|
4278
|
+
resolve29(null);
|
|
4279
4279
|
});
|
|
4280
4280
|
proc.on("close", (code) => {
|
|
4281
4281
|
clearTimeout(timer);
|
|
4282
4282
|
if (code !== 0) {
|
|
4283
4283
|
console.error(`[persist] autoLabel: subprocess exited code=${code}${stderr ? ` stderr=${stderr.trim().slice(0, 200)}` : ""}`);
|
|
4284
|
-
|
|
4284
|
+
resolve29(null);
|
|
4285
4285
|
return;
|
|
4286
4286
|
}
|
|
4287
4287
|
const text = stdout.trim();
|
|
4288
4288
|
if (!text) {
|
|
4289
4289
|
console.error("[persist] autoLabel: haiku returned empty response");
|
|
4290
|
-
|
|
4290
|
+
resolve29(null);
|
|
4291
4291
|
return;
|
|
4292
4292
|
}
|
|
4293
4293
|
if (text === "SKIP") {
|
|
4294
4294
|
console.error("[persist] autoLabel: haiku returned SKIP \u2014 messages too vague");
|
|
4295
|
-
|
|
4295
|
+
resolve29(null);
|
|
4296
4296
|
return;
|
|
4297
4297
|
}
|
|
4298
4298
|
const words = text.split(/\s+/).slice(0, SESSION_LABEL_MAX_WORDS);
|
|
4299
4299
|
const label = words.join(" ");
|
|
4300
4300
|
console.error(`[persist] autoLabel: haiku response="${label}"`);
|
|
4301
|
-
|
|
4301
|
+
resolve29(label);
|
|
4302
4302
|
});
|
|
4303
4303
|
});
|
|
4304
4304
|
}
|
|
@@ -5909,9 +5909,9 @@ function agentLogStream(name, accountDir, conversationId) {
|
|
|
5909
5909
|
mkdirSync4(logDir, { recursive: true });
|
|
5910
5910
|
purgeOldLogs(logDir, `${name}-`);
|
|
5911
5911
|
const logPath2 = resolve5(logDir, `${name}-${conversationId}.log`);
|
|
5912
|
-
const
|
|
5913
|
-
registerStreamLog(
|
|
5914
|
-
return
|
|
5912
|
+
const stream2 = createWriteStream(logPath2, { flags: "a" });
|
|
5913
|
+
registerStreamLog(stream2, { path: logPath2, conversationId, name });
|
|
5914
|
+
return stream2;
|
|
5915
5915
|
}
|
|
5916
5916
|
function preConversationLogStream(name, accountDir) {
|
|
5917
5917
|
const logDir = resolve5(accountDir, "logs");
|
|
@@ -5919,15 +5919,15 @@ function preConversationLogStream(name, accountDir) {
|
|
|
5919
5919
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
5920
5920
|
purgeOldLogs(logDir, `preconversation-${name}-`);
|
|
5921
5921
|
const logPath2 = resolve5(logDir, `preconversation-${name}-${date}.log`);
|
|
5922
|
-
const
|
|
5923
|
-
registerStreamLog(
|
|
5924
|
-
return
|
|
5922
|
+
const stream2 = createWriteStream(logPath2, { flags: "a" });
|
|
5923
|
+
registerStreamLog(stream2, { path: logPath2, conversationId: null, name: `preconversation-${name}` });
|
|
5924
|
+
return stream2;
|
|
5925
5925
|
}
|
|
5926
5926
|
var openStreamLogs = /* @__PURE__ */ new Map();
|
|
5927
|
-
function registerStreamLog(
|
|
5928
|
-
openStreamLogs.set(
|
|
5929
|
-
|
|
5930
|
-
openStreamLogs.delete(
|
|
5927
|
+
function registerStreamLog(stream2, entry) {
|
|
5928
|
+
openStreamLogs.set(stream2, entry);
|
|
5929
|
+
stream2.once("close", () => {
|
|
5930
|
+
openStreamLogs.delete(stream2);
|
|
5931
5931
|
});
|
|
5932
5932
|
}
|
|
5933
5933
|
function sigtermFlushStreamLogs(reason, source) {
|
|
@@ -7777,7 +7777,7 @@ async function fetchMemoryContext(accountId, query, sessionKey, options) {
|
|
|
7777
7777
|
return null;
|
|
7778
7778
|
}
|
|
7779
7779
|
const startMs = Date.now();
|
|
7780
|
-
return new Promise((
|
|
7780
|
+
return new Promise((resolve29) => {
|
|
7781
7781
|
const proc = spawn2(process.execPath, [serverPath], {
|
|
7782
7782
|
env: {
|
|
7783
7783
|
...process.env,
|
|
@@ -7806,7 +7806,7 @@ async function fetchMemoryContext(accountId, query, sessionKey, options) {
|
|
|
7806
7806
|
} else {
|
|
7807
7807
|
console.error(`[fetchMemoryContext] failed: ${reason} (${elapsed}ms)${stderrBuf ? ` stderr: ${stderrBuf.slice(0, 500)}` : ""}`);
|
|
7808
7808
|
}
|
|
7809
|
-
|
|
7809
|
+
resolve29(value);
|
|
7810
7810
|
};
|
|
7811
7811
|
proc.stdout.on("data", (chunk) => {
|
|
7812
7812
|
buffer += chunk.toString();
|
|
@@ -9882,7 +9882,7 @@ User messages are prefixed with the sender's name in brackets. Address participa
|
|
|
9882
9882
|
try {
|
|
9883
9883
|
while (turnCount < MAX_TOOL_TURNS) {
|
|
9884
9884
|
turnCount++;
|
|
9885
|
-
const
|
|
9885
|
+
const stream2 = client.messages.stream({
|
|
9886
9886
|
model: publicModel,
|
|
9887
9887
|
max_tokens: 1024,
|
|
9888
9888
|
system,
|
|
@@ -9924,13 +9924,13 @@ User messages are prefixed with the sender's name in brackets. Address participa
|
|
|
9924
9924
|
}
|
|
9925
9925
|
}]
|
|
9926
9926
|
});
|
|
9927
|
-
for await (const event of
|
|
9927
|
+
for await (const event of stream2) {
|
|
9928
9928
|
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
|
|
9929
9929
|
fullText += event.delta.text;
|
|
9930
9930
|
yield { type: "text", content: event.delta.text };
|
|
9931
9931
|
}
|
|
9932
9932
|
}
|
|
9933
|
-
const finalMessage = await
|
|
9933
|
+
const finalMessage = await stream2.finalMessage();
|
|
9934
9934
|
if (finalMessage.stop_reason !== "tool_use") break;
|
|
9935
9935
|
const toolUseBlocks = [];
|
|
9936
9936
|
for (const block of finalMessage.content) {
|
|
@@ -12536,7 +12536,7 @@ var credsSaveQueue = Promise.resolve();
|
|
|
12536
12536
|
async function drainCredsSaveQueue(timeoutMs = 5e3) {
|
|
12537
12537
|
console.error(`${TAG5} draining credential save queue\u2026`);
|
|
12538
12538
|
const timer = new Promise(
|
|
12539
|
-
(
|
|
12539
|
+
(resolve29) => setTimeout(() => resolve29("timeout"), timeoutMs)
|
|
12540
12540
|
);
|
|
12541
12541
|
const result = await Promise.race([
|
|
12542
12542
|
credsSaveQueue.then(() => "drained"),
|
|
@@ -12664,11 +12664,11 @@ async function createWaSocket(opts) {
|
|
|
12664
12664
|
return sock;
|
|
12665
12665
|
}
|
|
12666
12666
|
async function waitForConnection(sock) {
|
|
12667
|
-
return new Promise((
|
|
12667
|
+
return new Promise((resolve29, reject) => {
|
|
12668
12668
|
const handler = (update) => {
|
|
12669
12669
|
if (update.connection === "open") {
|
|
12670
12670
|
sock.ev.off("connection.update", handler);
|
|
12671
|
-
|
|
12671
|
+
resolve29();
|
|
12672
12672
|
}
|
|
12673
12673
|
if (update.connection === "close") {
|
|
12674
12674
|
sock.ev.off("connection.update", handler);
|
|
@@ -12782,14 +12782,14 @@ ${inspected}`;
|
|
|
12782
12782
|
return inspect2(err, INSPECT_OPTS2);
|
|
12783
12783
|
}
|
|
12784
12784
|
function withTimeout(label, promise, timeoutMs) {
|
|
12785
|
-
return new Promise((
|
|
12785
|
+
return new Promise((resolve29, reject) => {
|
|
12786
12786
|
const timer = setTimeout(() => {
|
|
12787
12787
|
reject(new Error(`${label} timed out after ${timeoutMs}ms`));
|
|
12788
12788
|
}, timeoutMs);
|
|
12789
12789
|
promise.then(
|
|
12790
12790
|
(value) => {
|
|
12791
12791
|
clearTimeout(timer);
|
|
12792
|
-
|
|
12792
|
+
resolve29(value);
|
|
12793
12793
|
},
|
|
12794
12794
|
(err) => {
|
|
12795
12795
|
clearTimeout(timer);
|
|
@@ -13334,9 +13334,9 @@ function getDownloadableContent(message) {
|
|
|
13334
13334
|
if (msg.stickerMessage) return { downloadable: msg.stickerMessage, mediaType: "sticker" };
|
|
13335
13335
|
return void 0;
|
|
13336
13336
|
}
|
|
13337
|
-
async function streamToBuffer(
|
|
13337
|
+
async function streamToBuffer(stream2) {
|
|
13338
13338
|
const chunks = [];
|
|
13339
|
-
for await (const chunk of
|
|
13339
|
+
for await (const chunk of stream2) {
|
|
13340
13340
|
chunks.push(Buffer.from(chunk));
|
|
13341
13341
|
}
|
|
13342
13342
|
return Buffer.concat(chunks);
|
|
@@ -13367,8 +13367,8 @@ async function downloadInboundMedia(msg, sock, opts) {
|
|
|
13367
13367
|
const downloadable = getDownloadableContent(content);
|
|
13368
13368
|
if (downloadable) {
|
|
13369
13369
|
try {
|
|
13370
|
-
const
|
|
13371
|
-
buffer = await streamToBuffer(
|
|
13370
|
+
const stream2 = await downloadContentFromMessage(downloadable.downloadable, downloadable.mediaType);
|
|
13371
|
+
buffer = await streamToBuffer(stream2);
|
|
13372
13372
|
} catch (fallbackErr) {
|
|
13373
13373
|
console.error(`${TAG9} direct download fallback failed: ${String(fallbackErr)}`);
|
|
13374
13374
|
}
|
|
@@ -13999,15 +13999,15 @@ async function connectWithReconnect(conn) {
|
|
|
13999
13999
|
);
|
|
14000
14000
|
}
|
|
14001
14001
|
if (decision.reason === "short-lived" || cycleError) {
|
|
14002
|
-
const
|
|
14002
|
+
const delay2 = computeBackoff(Math.max(1, decision.nextAttempts));
|
|
14003
14003
|
console.error(
|
|
14004
|
-
`${TAG13} reconnecting account=${conn.accountId} in ${
|
|
14004
|
+
`${TAG13} reconnecting account=${conn.accountId} in ${delay2}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
|
|
14005
14005
|
);
|
|
14006
|
-
await new Promise((
|
|
14007
|
-
const timer = setTimeout(
|
|
14006
|
+
await new Promise((resolve29) => {
|
|
14007
|
+
const timer = setTimeout(resolve29, delay2);
|
|
14008
14008
|
conn.abortController.signal.addEventListener("abort", () => {
|
|
14009
14009
|
clearTimeout(timer);
|
|
14010
|
-
|
|
14010
|
+
resolve29();
|
|
14011
14011
|
}, { once: true });
|
|
14012
14012
|
});
|
|
14013
14013
|
}
|
|
@@ -14015,16 +14015,16 @@ async function connectWithReconnect(conn) {
|
|
|
14015
14015
|
}
|
|
14016
14016
|
}
|
|
14017
14017
|
function waitForDisconnectEvent(conn) {
|
|
14018
|
-
return new Promise((
|
|
14018
|
+
return new Promise((resolve29) => {
|
|
14019
14019
|
if (!conn.sock) {
|
|
14020
|
-
|
|
14020
|
+
resolve29();
|
|
14021
14021
|
return;
|
|
14022
14022
|
}
|
|
14023
14023
|
const sock = conn.sock;
|
|
14024
14024
|
const handler = (update) => {
|
|
14025
14025
|
if (update.connection === "close") {
|
|
14026
14026
|
sock.ev.off("connection.update", handler);
|
|
14027
|
-
|
|
14027
|
+
resolve29();
|
|
14028
14028
|
}
|
|
14029
14029
|
};
|
|
14030
14030
|
sock.ev.on("connection.update", handler);
|
|
@@ -14241,8 +14241,8 @@ async function handleInboundMessage(conn, msg) {
|
|
|
14241
14241
|
const conversationKey = isGroup ? remoteJid : senderPhone;
|
|
14242
14242
|
const debounceKey = `${conn.accountId}:${conversationKey}:${senderPhone}`;
|
|
14243
14243
|
let resolvePending;
|
|
14244
|
-
const sttPending = new Promise((
|
|
14245
|
-
resolvePending =
|
|
14244
|
+
const sttPending = new Promise((resolve29) => {
|
|
14245
|
+
resolvePending = resolve29;
|
|
14246
14246
|
});
|
|
14247
14247
|
if (conn.debouncer) conn.debouncer.registerPending(debounceKey, sttPending);
|
|
14248
14248
|
try {
|
|
@@ -14355,20 +14355,20 @@ async function probeApiKey() {
|
|
|
14355
14355
|
return result.status;
|
|
14356
14356
|
}
|
|
14357
14357
|
function checkPort(port2, timeoutMs = 500) {
|
|
14358
|
-
return new Promise((
|
|
14358
|
+
return new Promise((resolve29) => {
|
|
14359
14359
|
const socket = createConnection2(port2, "127.0.0.1");
|
|
14360
14360
|
socket.setTimeout(timeoutMs);
|
|
14361
14361
|
socket.once("connect", () => {
|
|
14362
14362
|
socket.destroy();
|
|
14363
|
-
|
|
14363
|
+
resolve29(true);
|
|
14364
14364
|
});
|
|
14365
14365
|
socket.once("error", () => {
|
|
14366
14366
|
socket.destroy();
|
|
14367
|
-
|
|
14367
|
+
resolve29(false);
|
|
14368
14368
|
});
|
|
14369
14369
|
socket.once("timeout", () => {
|
|
14370
14370
|
socket.destroy();
|
|
14371
|
-
|
|
14371
|
+
resolve29(false);
|
|
14372
14372
|
});
|
|
14373
14373
|
});
|
|
14374
14374
|
}
|
|
@@ -15338,7 +15338,7 @@ app3.post("/", async (c) => {
|
|
|
15338
15338
|
`[chat-route] session=${session_key.slice(0, 8)} gateway=discard reason=${gatewayResult.screening.reason}`
|
|
15339
15339
|
);
|
|
15340
15340
|
const encoder2 = new TextEncoder();
|
|
15341
|
-
const
|
|
15341
|
+
const stream2 = new ReadableStream({
|
|
15342
15342
|
start(controller) {
|
|
15343
15343
|
controller.enqueue(
|
|
15344
15344
|
encoder2.encode(`data: ${JSON.stringify({ text: "I'm sorry, I can't help with that request." })}
|
|
@@ -15349,7 +15349,7 @@ app3.post("/", async (c) => {
|
|
|
15349
15349
|
controller.close();
|
|
15350
15350
|
}
|
|
15351
15351
|
});
|
|
15352
|
-
return new Response(
|
|
15352
|
+
return new Response(stream2, {
|
|
15353
15353
|
headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" }
|
|
15354
15354
|
});
|
|
15355
15355
|
}
|
|
@@ -16518,12 +16518,12 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
16518
16518
|
return;
|
|
16519
16519
|
}
|
|
16520
16520
|
attempt++;
|
|
16521
|
-
const
|
|
16521
|
+
const delay2 = LOGIN_RECONNECT_DELAYS[attempt - 1] ?? 8e3;
|
|
16522
16522
|
console.error(
|
|
16523
|
-
`${TAG17} status=${classification.statusCode ?? "unknown"} restart required \u2014 reconnecting with saved creds (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}) delay=${
|
|
16523
|
+
`${TAG17} status=${classification.statusCode ?? "unknown"} restart required \u2014 reconnecting with saved creds (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}) delay=${delay2}ms`
|
|
16524
16524
|
);
|
|
16525
16525
|
closeSocket(current.sock);
|
|
16526
|
-
await new Promise((r) => setTimeout(r,
|
|
16526
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
16527
16527
|
const afterDelay = activeLogins.get(accountId);
|
|
16528
16528
|
if (afterDelay?.id !== login.id) return;
|
|
16529
16529
|
try {
|
|
@@ -16570,8 +16570,8 @@ async function startLogin(opts) {
|
|
|
16570
16570
|
resetActiveLogin(accountId);
|
|
16571
16571
|
let resolveQr = null;
|
|
16572
16572
|
let rejectQr = null;
|
|
16573
|
-
const qrPromise = new Promise((
|
|
16574
|
-
resolveQr =
|
|
16573
|
+
const qrPromise = new Promise((resolve29, reject) => {
|
|
16574
|
+
resolveQr = resolve29;
|
|
16575
16575
|
rejectQr = reject;
|
|
16576
16576
|
});
|
|
16577
16577
|
const qrTimer = setTimeout(
|
|
@@ -17721,8 +17721,8 @@ function startScriptStreamTailer(opts) {
|
|
|
17721
17721
|
buffer = "";
|
|
17722
17722
|
}
|
|
17723
17723
|
await new Promise((res, rej) => {
|
|
17724
|
-
const
|
|
17725
|
-
|
|
17724
|
+
const stream2 = createReadStream2(path2, { start: offset, end: size - 1 });
|
|
17725
|
+
stream2.on("data", (chunk) => {
|
|
17726
17726
|
buffer += typeof chunk === "string" ? chunk : utf8.write(chunk);
|
|
17727
17727
|
let idx;
|
|
17728
17728
|
while ((idx = buffer.indexOf("\n")) !== -1) {
|
|
@@ -17731,11 +17731,11 @@ function startScriptStreamTailer(opts) {
|
|
|
17731
17731
|
if (line.length > 0) processLine(line);
|
|
17732
17732
|
}
|
|
17733
17733
|
});
|
|
17734
|
-
|
|
17734
|
+
stream2.on("end", () => {
|
|
17735
17735
|
offset = size;
|
|
17736
17736
|
res();
|
|
17737
17737
|
});
|
|
17738
|
-
|
|
17738
|
+
stream2.on("error", rej);
|
|
17739
17739
|
});
|
|
17740
17740
|
} catch (err) {
|
|
17741
17741
|
if (onError) onError(err instanceof Error ? err : new Error(String(err)));
|
|
@@ -18057,7 +18057,7 @@ app11.post("/", requireAdminSession, async (c) => {
|
|
|
18057
18057
|
`);
|
|
18058
18058
|
lifecycleLog.end();
|
|
18059
18059
|
const encoder2 = new TextEncoder();
|
|
18060
|
-
const
|
|
18060
|
+
const stream2 = new ReadableStream({
|
|
18061
18061
|
start(controller) {
|
|
18062
18062
|
controller.enqueue(encoder2.encode(`data: ${JSON.stringify({ type: "done" })}
|
|
18063
18063
|
|
|
@@ -18065,7 +18065,7 @@ app11.post("/", requireAdminSession, async (c) => {
|
|
|
18065
18065
|
controller.close();
|
|
18066
18066
|
}
|
|
18067
18067
|
});
|
|
18068
|
-
return new Response(
|
|
18068
|
+
return new Response(stream2, {
|
|
18069
18069
|
headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" }
|
|
18070
18070
|
});
|
|
18071
18071
|
}
|
|
@@ -18212,7 +18212,7 @@ var app12 = new Hono2();
|
|
|
18212
18212
|
app12.post("/", requireAdminSession, async (c) => {
|
|
18213
18213
|
const sessionKey = c.var.sessionKey;
|
|
18214
18214
|
const encoder = new TextEncoder();
|
|
18215
|
-
const
|
|
18215
|
+
const stream2 = new ReadableStream({
|
|
18216
18216
|
async start(controller) {
|
|
18217
18217
|
const iter = compactSession(sessionKey);
|
|
18218
18218
|
let step = await iter.next();
|
|
@@ -18228,7 +18228,7 @@ app12.post("/", requireAdminSession, async (c) => {
|
|
|
18228
18228
|
controller.close();
|
|
18229
18229
|
}
|
|
18230
18230
|
});
|
|
18231
|
-
return new Response(
|
|
18231
|
+
return new Response(stream2, {
|
|
18232
18232
|
headers: {
|
|
18233
18233
|
"Content-Type": "text/event-stream",
|
|
18234
18234
|
"Cache-Control": "no-cache",
|
|
@@ -18951,9 +18951,9 @@ app22.post("/", async (c) => {
|
|
|
18951
18951
|
var events_default = app22;
|
|
18952
18952
|
|
|
18953
18953
|
// server/routes/admin/cloudflare.ts
|
|
18954
|
-
import { homedir as
|
|
18955
|
-
import { resolve as
|
|
18956
|
-
import { readFileSync as
|
|
18954
|
+
import { homedir as homedir4 } from "os";
|
|
18955
|
+
import { resolve as resolve25 } from "path";
|
|
18956
|
+
import { readFileSync as readFileSync25 } from "fs";
|
|
18957
18957
|
|
|
18958
18958
|
// app/lib/dns-label.ts
|
|
18959
18959
|
var VALID_LABEL = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
@@ -18991,9 +18991,13 @@ function addAliasDomain(hostname2) {
|
|
|
18991
18991
|
writeFileSync14(ALIAS_DOMAINS_PATH, JSON.stringify([...existing], null, 2) + "\n", "utf-8");
|
|
18992
18992
|
}
|
|
18993
18993
|
|
|
18994
|
-
// server/
|
|
18995
|
-
|
|
18996
|
-
|
|
18994
|
+
// server/lib/action-runner.ts
|
|
18995
|
+
import { spawn as spawn5 } from "child_process";
|
|
18996
|
+
import { createReadStream as createReadStream3, existsSync as existsSync23, mkdirSync as mkdirSync13, readdirSync as readdirSync7, statSync as statSync10, unlinkSync as unlinkSync5, watch } from "fs";
|
|
18997
|
+
import { homedir as homedir3 } from "os";
|
|
18998
|
+
import { join as join11, resolve as resolve24 } from "path";
|
|
18999
|
+
import { readFileSync as readFileSync24 } from "fs";
|
|
19000
|
+
import { setTimeout as delay } from "timers/promises";
|
|
18997
19001
|
function loadBrandInfo() {
|
|
18998
19002
|
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? resolve24(process.cwd(), "..");
|
|
18999
19003
|
const brandPath = resolve24(platformRoot2, "config", "brand.json");
|
|
@@ -19006,6 +19010,333 @@ function loadBrandInfo() {
|
|
|
19006
19010
|
return { hostname: "maxy", configDir: ".maxy" };
|
|
19007
19011
|
}
|
|
19008
19012
|
}
|
|
19013
|
+
function actionLogDir() {
|
|
19014
|
+
const { configDir: configDir2 } = loadBrandInfo();
|
|
19015
|
+
return join11(homedir3(), configDir2, "logs", "actions");
|
|
19016
|
+
}
|
|
19017
|
+
function actionLogPath(actionId) {
|
|
19018
|
+
return join11(actionLogDir(), `${actionId}.log`);
|
|
19019
|
+
}
|
|
19020
|
+
var WHITELIST = {
|
|
19021
|
+
upgrade: {
|
|
19022
|
+
build: ({ sudoPassword }) => {
|
|
19023
|
+
const { hostname: hostname2 } = loadBrandInfo();
|
|
19024
|
+
const pkg = `@rubytech/create-${hostname2}`;
|
|
19025
|
+
const cmd = [
|
|
19026
|
+
// prime: read pw from stdin, silently cache
|
|
19027
|
+
'printf %s "$SUDO_PASSWORD"',
|
|
19028
|
+
"|",
|
|
19029
|
+
"sudo -S -v 2>/dev/null",
|
|
19030
|
+
"&&",
|
|
19031
|
+
// run installer — it will re-prompt only if the cache expires
|
|
19032
|
+
`npx -y ${pkg}@latest`
|
|
19033
|
+
].join(" ");
|
|
19034
|
+
return {
|
|
19035
|
+
argv: ["bash", "-c", cmd],
|
|
19036
|
+
env: sudoPassword ? { SUDO_PASSWORD: sudoPassword } : void 0
|
|
19037
|
+
};
|
|
19038
|
+
}
|
|
19039
|
+
},
|
|
19040
|
+
"cloudflare-setup": {
|
|
19041
|
+
build: ({ positional }) => {
|
|
19042
|
+
if (!positional || positional.length < 3) {
|
|
19043
|
+
throw new Error("cloudflare-setup requires [brand, port, ...hostnames]");
|
|
19044
|
+
}
|
|
19045
|
+
const scriptPath = join11(homedir3(), "setup-tunnel.sh");
|
|
19046
|
+
if (!existsSync23(scriptPath)) {
|
|
19047
|
+
throw new Error(
|
|
19048
|
+
`cloudflare-setup: ~/setup-tunnel.sh not found. Re-run the installer to repair the symlink.`
|
|
19049
|
+
);
|
|
19050
|
+
}
|
|
19051
|
+
return { argv: [scriptPath, ...positional] };
|
|
19052
|
+
}
|
|
19053
|
+
}
|
|
19054
|
+
};
|
|
19055
|
+
function isKnownAction(name) {
|
|
19056
|
+
return Object.prototype.hasOwnProperty.call(WHITELIST, name);
|
|
19057
|
+
}
|
|
19058
|
+
function generateActionId(name) {
|
|
19059
|
+
const ts = Date.now().toString(36);
|
|
19060
|
+
const nonce = Math.floor(Math.random() * 4294967295).toString(16).padStart(8, "0");
|
|
19061
|
+
return `${name}-${ts}-${nonce}`;
|
|
19062
|
+
}
|
|
19063
|
+
function cleanupStaleLogs() {
|
|
19064
|
+
const dir = actionLogDir();
|
|
19065
|
+
if (!existsSync23(dir)) return;
|
|
19066
|
+
const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1e3;
|
|
19067
|
+
for (const entry of readdirSync7(dir)) {
|
|
19068
|
+
const full = join11(dir, entry);
|
|
19069
|
+
try {
|
|
19070
|
+
const st = statSync10(full);
|
|
19071
|
+
if (st.isFile() && st.mtimeMs < cutoff) unlinkSync5(full);
|
|
19072
|
+
} catch {
|
|
19073
|
+
}
|
|
19074
|
+
}
|
|
19075
|
+
}
|
|
19076
|
+
async function isActionActive(actionName) {
|
|
19077
|
+
return new Promise((resolveP) => {
|
|
19078
|
+
const proc = spawn5(
|
|
19079
|
+
"systemctl",
|
|
19080
|
+
["--user", "list-units", "--no-legend", "--state=active", `maxy-action-${actionName}-*`],
|
|
19081
|
+
{ stdio: ["ignore", "pipe", "pipe"] }
|
|
19082
|
+
);
|
|
19083
|
+
let out = "";
|
|
19084
|
+
proc.stdout.on("data", (chunk) => {
|
|
19085
|
+
out += chunk.toString();
|
|
19086
|
+
});
|
|
19087
|
+
proc.on("close", () => resolveP(out.trim().length > 0));
|
|
19088
|
+
proc.on("error", () => resolveP(false));
|
|
19089
|
+
});
|
|
19090
|
+
}
|
|
19091
|
+
async function launchAction(name, args) {
|
|
19092
|
+
const actionId = generateActionId(name);
|
|
19093
|
+
const dir = actionLogDir();
|
|
19094
|
+
mkdirSync13(dir, { recursive: true });
|
|
19095
|
+
cleanupStaleLogs();
|
|
19096
|
+
const logPath2 = actionLogPath(actionId);
|
|
19097
|
+
const unit = `maxy-action-${actionId}`;
|
|
19098
|
+
const built = WHITELIST[name].build(args);
|
|
19099
|
+
const systemdArgs = [
|
|
19100
|
+
"--user",
|
|
19101
|
+
`--unit=${unit}`,
|
|
19102
|
+
"--collect",
|
|
19103
|
+
"--property=RemainAfterExit=false",
|
|
19104
|
+
`--property=StandardOutput=append:${logPath2}`,
|
|
19105
|
+
`--property=StandardError=append:${logPath2}`,
|
|
19106
|
+
`--description=Maxy action ${name} (${actionId})`
|
|
19107
|
+
];
|
|
19108
|
+
if (built.env) {
|
|
19109
|
+
for (const [k, v] of Object.entries(built.env)) {
|
|
19110
|
+
systemdArgs.push(`--setenv=${k}=${v}`);
|
|
19111
|
+
}
|
|
19112
|
+
}
|
|
19113
|
+
systemdArgs.push("--", ...built.argv);
|
|
19114
|
+
await new Promise((resolveP, rejectP) => {
|
|
19115
|
+
const proc = spawn5("systemd-run", systemdArgs, { stdio: ["ignore", "pipe", "pipe"] });
|
|
19116
|
+
let stderr = "";
|
|
19117
|
+
proc.stderr.on("data", (chunk) => {
|
|
19118
|
+
stderr += chunk.toString();
|
|
19119
|
+
});
|
|
19120
|
+
proc.on("close", (code) => {
|
|
19121
|
+
if (code === 0) resolveP();
|
|
19122
|
+
else rejectP(new Error(`systemd-run exited ${code}: ${stderr.trim().slice(0, 400)}`));
|
|
19123
|
+
});
|
|
19124
|
+
proc.on("error", rejectP);
|
|
19125
|
+
});
|
|
19126
|
+
console.log(`[action-runner] launched action=${name} id=${actionId} unit=${unit} log=${logPath2}`);
|
|
19127
|
+
return { actionId, unit, logPath: logPath2 };
|
|
19128
|
+
}
|
|
19129
|
+
async function getUnitStatus(unit) {
|
|
19130
|
+
return new Promise((resolveP) => {
|
|
19131
|
+
const proc = spawn5(
|
|
19132
|
+
"systemctl",
|
|
19133
|
+
["--user", "show", unit, "--property=ActiveState,SubState,MainPID,ExecMainStatus,ExecMainCode"],
|
|
19134
|
+
{ stdio: ["ignore", "pipe", "pipe"] }
|
|
19135
|
+
);
|
|
19136
|
+
let out = "";
|
|
19137
|
+
proc.stdout.on("data", (chunk) => {
|
|
19138
|
+
out += chunk.toString();
|
|
19139
|
+
});
|
|
19140
|
+
proc.on("close", () => {
|
|
19141
|
+
const kv = {};
|
|
19142
|
+
for (const line of out.split("\n")) {
|
|
19143
|
+
const eq = line.indexOf("=");
|
|
19144
|
+
if (eq > 0) kv[line.slice(0, eq)] = line.slice(eq + 1);
|
|
19145
|
+
}
|
|
19146
|
+
if (!kv.ActiveState) return resolveP(null);
|
|
19147
|
+
resolveP({
|
|
19148
|
+
activeState: kv.ActiveState,
|
|
19149
|
+
subState: kv.SubState ?? "",
|
|
19150
|
+
mainPid: kv.MainPID ? Number(kv.MainPID) || null : null,
|
|
19151
|
+
execMainStatus: kv.ExecMainStatus ? Number(kv.ExecMainStatus) : null,
|
|
19152
|
+
execMainSignal: null
|
|
19153
|
+
// systemd reports signal in SIGNAL (SIGTERM etc.), deferred — code is enough for our contract
|
|
19154
|
+
});
|
|
19155
|
+
});
|
|
19156
|
+
proc.on("error", () => resolveP(null));
|
|
19157
|
+
});
|
|
19158
|
+
}
|
|
19159
|
+
var HEARTBEAT_INTERVAL_MS = 5e3;
|
|
19160
|
+
var PHASE_BRACKETED = /\[(\d+)\/(\d+)\]/;
|
|
19161
|
+
var PHASE_STREAM_LOG = /step=([a-z0-9-]+)/;
|
|
19162
|
+
function extractPhase(text) {
|
|
19163
|
+
const m1 = text.match(PHASE_BRACKETED);
|
|
19164
|
+
if (m1) return m1[0];
|
|
19165
|
+
const m2 = text.match(PHASE_STREAM_LOG);
|
|
19166
|
+
if (m2) return m2[1];
|
|
19167
|
+
return null;
|
|
19168
|
+
}
|
|
19169
|
+
async function* streamActionEvents(opts) {
|
|
19170
|
+
const { actionId, unit, signal } = opts;
|
|
19171
|
+
const logPath2 = actionLogPath(actionId);
|
|
19172
|
+
const startedAt = Date.now();
|
|
19173
|
+
let lastLineAt = startedAt;
|
|
19174
|
+
let lastPhase = null;
|
|
19175
|
+
let byteOffset = opts.fromByteOffset ?? 0;
|
|
19176
|
+
if (existsSync23(logPath2)) {
|
|
19177
|
+
const snapshot = statSync10(logPath2).size;
|
|
19178
|
+
if (byteOffset < snapshot) {
|
|
19179
|
+
for await (const ev of readLogLines(logPath2, byteOffset, snapshot)) {
|
|
19180
|
+
const phase = extractPhase(ev.text);
|
|
19181
|
+
if (phase) lastPhase = phase;
|
|
19182
|
+
lastLineAt = ev.ts;
|
|
19183
|
+
byteOffset = ev.byteOffset;
|
|
19184
|
+
yield ev;
|
|
19185
|
+
if (signal.aborted) return;
|
|
19186
|
+
}
|
|
19187
|
+
}
|
|
19188
|
+
}
|
|
19189
|
+
let watching = true;
|
|
19190
|
+
let hbTimer = null;
|
|
19191
|
+
const queue = [];
|
|
19192
|
+
let resolveNext = null;
|
|
19193
|
+
function push(ev) {
|
|
19194
|
+
queue.push(ev);
|
|
19195
|
+
if (resolveNext) {
|
|
19196
|
+
const r = resolveNext;
|
|
19197
|
+
resolveNext = null;
|
|
19198
|
+
r();
|
|
19199
|
+
}
|
|
19200
|
+
}
|
|
19201
|
+
const watcher = existsSync23(logPath2) ? watch(logPath2, async () => {
|
|
19202
|
+
if (!watching) return;
|
|
19203
|
+
try {
|
|
19204
|
+
const size = statSync10(logPath2).size;
|
|
19205
|
+
if (size <= byteOffset) return;
|
|
19206
|
+
for await (const ev of readLogLines(logPath2, byteOffset, size)) {
|
|
19207
|
+
const phase = extractPhase(ev.text);
|
|
19208
|
+
if (phase) lastPhase = phase;
|
|
19209
|
+
lastLineAt = ev.ts;
|
|
19210
|
+
byteOffset = ev.byteOffset;
|
|
19211
|
+
push(ev);
|
|
19212
|
+
}
|
|
19213
|
+
} catch (err) {
|
|
19214
|
+
console.error(`[action-runner] watcher error action=${actionId}: ${err}`);
|
|
19215
|
+
}
|
|
19216
|
+
}) : null;
|
|
19217
|
+
hbTimer = setInterval(async () => {
|
|
19218
|
+
const status = await getUnitStatus(unit);
|
|
19219
|
+
const now = Date.now();
|
|
19220
|
+
push({
|
|
19221
|
+
type: "heartbeat",
|
|
19222
|
+
ts: now,
|
|
19223
|
+
elapsed_since_last_line_ms: now - lastLineAt,
|
|
19224
|
+
systemd_state: status ? status.activeState : "unknown",
|
|
19225
|
+
pid: status ? status.mainPid : null,
|
|
19226
|
+
last_phase: lastPhase
|
|
19227
|
+
});
|
|
19228
|
+
if (status && status.activeState !== "active" && status.activeState !== "activating") {
|
|
19229
|
+
const finalStatus = await getUnitStatus(unit);
|
|
19230
|
+
push({
|
|
19231
|
+
type: "exit",
|
|
19232
|
+
ts: Date.now(),
|
|
19233
|
+
code: finalStatus?.execMainStatus ?? null,
|
|
19234
|
+
signal: null,
|
|
19235
|
+
duration_ms: Date.now() - startedAt
|
|
19236
|
+
});
|
|
19237
|
+
watching = false;
|
|
19238
|
+
}
|
|
19239
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
19240
|
+
try {
|
|
19241
|
+
while (watching && !signal.aborted) {
|
|
19242
|
+
if (queue.length === 0) {
|
|
19243
|
+
await new Promise((r) => {
|
|
19244
|
+
resolveNext = r;
|
|
19245
|
+
});
|
|
19246
|
+
}
|
|
19247
|
+
while (queue.length > 0) {
|
|
19248
|
+
const ev = queue.shift();
|
|
19249
|
+
yield ev;
|
|
19250
|
+
if (ev.type === "exit") {
|
|
19251
|
+
watching = false;
|
|
19252
|
+
break;
|
|
19253
|
+
}
|
|
19254
|
+
}
|
|
19255
|
+
}
|
|
19256
|
+
} finally {
|
|
19257
|
+
if (hbTimer) clearInterval(hbTimer);
|
|
19258
|
+
watcher?.close();
|
|
19259
|
+
}
|
|
19260
|
+
}
|
|
19261
|
+
async function* readLogLines(path2, fromOffset, toOffset) {
|
|
19262
|
+
const stream2 = createReadStream3(path2, { start: fromOffset, end: toOffset - 1, encoding: "utf-8" });
|
|
19263
|
+
let buffer = "";
|
|
19264
|
+
let offset = fromOffset;
|
|
19265
|
+
for await (const chunk of stream2) {
|
|
19266
|
+
buffer += chunk;
|
|
19267
|
+
let nl;
|
|
19268
|
+
while ((nl = buffer.indexOf("\n")) !== -1) {
|
|
19269
|
+
const text = buffer.slice(0, nl);
|
|
19270
|
+
offset += Buffer.byteLength(text, "utf-8") + 1;
|
|
19271
|
+
buffer = buffer.slice(nl + 1);
|
|
19272
|
+
yield { type: "line", ts: Date.now(), stream: "stdout", text, byteOffset: offset };
|
|
19273
|
+
}
|
|
19274
|
+
}
|
|
19275
|
+
}
|
|
19276
|
+
var RATE_LIMIT_WINDOW_MS = 6e4;
|
|
19277
|
+
var RATE_LIMIT_MAX_ATTEMPTS = 5;
|
|
19278
|
+
var rateLimitBuckets = /* @__PURE__ */ new Map();
|
|
19279
|
+
function rateLimitCheck(ip) {
|
|
19280
|
+
const now = Date.now();
|
|
19281
|
+
const bucket = rateLimitBuckets.get(ip);
|
|
19282
|
+
if (!bucket || now - bucket.windowStart > RATE_LIMIT_WINDOW_MS) {
|
|
19283
|
+
rateLimitBuckets.set(ip, { count: 1, windowStart: now });
|
|
19284
|
+
return "ok";
|
|
19285
|
+
}
|
|
19286
|
+
bucket.count += 1;
|
|
19287
|
+
if (bucket.count > RATE_LIMIT_MAX_ATTEMPTS) return "limited";
|
|
19288
|
+
return "ok";
|
|
19289
|
+
}
|
|
19290
|
+
async function primeSudo(password, clientIp) {
|
|
19291
|
+
if (!password) return "invalid";
|
|
19292
|
+
if (rateLimitCheck(clientIp) === "limited") return "limited";
|
|
19293
|
+
return new Promise((resolveP) => {
|
|
19294
|
+
const proc = spawn5("sudo", ["-S", "-v"], { stdio: ["pipe", "ignore", "pipe"] });
|
|
19295
|
+
let stderr = "";
|
|
19296
|
+
proc.stderr.on("data", (chunk) => {
|
|
19297
|
+
stderr += chunk.toString();
|
|
19298
|
+
});
|
|
19299
|
+
proc.on("close", (code) => {
|
|
19300
|
+
if (code === 0) return resolveP("ok");
|
|
19301
|
+
if (/incorrect password|authentication failure/i.test(stderr)) return resolveP("invalid");
|
|
19302
|
+
console.error(`[action-runner] sudo-prime failed code=${code} stderr=${stderr.slice(0, 200)}`);
|
|
19303
|
+
resolveP("error");
|
|
19304
|
+
});
|
|
19305
|
+
proc.on("error", (err) => {
|
|
19306
|
+
console.error(`[action-runner] sudo-prime spawn error: ${err}`);
|
|
19307
|
+
resolveP("error");
|
|
19308
|
+
});
|
|
19309
|
+
proc.stdin.write(`${password}
|
|
19310
|
+
`);
|
|
19311
|
+
proc.stdin.end();
|
|
19312
|
+
});
|
|
19313
|
+
}
|
|
19314
|
+
async function waitForExit(unit, timeoutMs = 10 * 60 * 1e3) {
|
|
19315
|
+
const deadline = Date.now() + timeoutMs;
|
|
19316
|
+
while (Date.now() < deadline) {
|
|
19317
|
+
const status = await getUnitStatus(unit);
|
|
19318
|
+
if (!status) return null;
|
|
19319
|
+
if (status.activeState !== "active" && status.activeState !== "activating") return status;
|
|
19320
|
+
await delay(500);
|
|
19321
|
+
}
|
|
19322
|
+
return null;
|
|
19323
|
+
}
|
|
19324
|
+
|
|
19325
|
+
// server/routes/admin/cloudflare.ts
|
|
19326
|
+
var SETUP_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
19327
|
+
var DOMAINS_TIMEOUT_MS = 40 * 1e3;
|
|
19328
|
+
function loadBrandInfo2() {
|
|
19329
|
+
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? resolve25(process.cwd(), "..");
|
|
19330
|
+
const brandPath = resolve25(platformRoot2, "config", "brand.json");
|
|
19331
|
+
try {
|
|
19332
|
+
const parsed = JSON.parse(readFileSync25(brandPath, "utf-8"));
|
|
19333
|
+
const hostname2 = typeof parsed.hostname === "string" && parsed.hostname ? parsed.hostname : "maxy";
|
|
19334
|
+
const configDir2 = typeof parsed.configDir === "string" && parsed.configDir ? parsed.configDir : ".maxy";
|
|
19335
|
+
return { hostname: hostname2, configDir: configDir2 };
|
|
19336
|
+
} catch {
|
|
19337
|
+
return { hostname: "maxy", configDir: ".maxy" };
|
|
19338
|
+
}
|
|
19339
|
+
}
|
|
19009
19340
|
function resolvePort() {
|
|
19010
19341
|
const raw2 = process.env.PORT;
|
|
19011
19342
|
if (!raw2) throw new Error("PORT env var is not set \u2014 refusing to guess");
|
|
@@ -19100,8 +19431,8 @@ app23.get("/domains", requireAdminSession, async (c) => {
|
|
|
19100
19431
|
if (!correlationId) return err("session", "No active conversation for session \u2014 refresh chat.");
|
|
19101
19432
|
streamLogPath = streamLogPathFor(accountId, correlationId).streamLogPath;
|
|
19102
19433
|
log2(`phase=stream-log-resolved path=${streamLogPath}`);
|
|
19103
|
-
const brand =
|
|
19104
|
-
const scriptPath =
|
|
19434
|
+
const brand = loadBrandInfo2();
|
|
19435
|
+
const scriptPath = resolve25(homedir4(), "list-cf-domains.sh");
|
|
19105
19436
|
const result = await runFormSpawn({
|
|
19106
19437
|
scriptPath,
|
|
19107
19438
|
args: [brand.hostname],
|
|
@@ -19186,8 +19517,8 @@ app23.post("/setup", requireAdminSession, async (c) => {
|
|
|
19186
19517
|
};
|
|
19187
19518
|
return c.json(body2, 200);
|
|
19188
19519
|
}
|
|
19189
|
-
function ok(
|
|
19190
|
-
return c.json(
|
|
19520
|
+
function ok(result) {
|
|
19521
|
+
return c.json(result, 200);
|
|
19191
19522
|
}
|
|
19192
19523
|
let body;
|
|
19193
19524
|
try {
|
|
@@ -19219,93 +19550,90 @@ app23.post("/setup", requireAdminSession, async (c) => {
|
|
|
19219
19550
|
let brand;
|
|
19220
19551
|
let port2;
|
|
19221
19552
|
try {
|
|
19222
|
-
brand =
|
|
19553
|
+
brand = loadBrandInfo2();
|
|
19223
19554
|
port2 = resolvePort();
|
|
19224
19555
|
} catch (e) {
|
|
19225
19556
|
return err("script", `Server misconfigured: ${e instanceof Error ? e.message : String(e)}`);
|
|
19226
19557
|
}
|
|
19227
19558
|
streamLogPath = streamLogPathFor(accountId, correlationId).streamLogPath;
|
|
19228
19559
|
log2(`phase=stream-log-resolved path=${streamLogPath}`);
|
|
19229
|
-
const scriptPath = resolve24(homedir3(), "setup-tunnel.sh");
|
|
19230
19560
|
const args = [brand.hostname, String(port2), adminFqdn];
|
|
19231
19561
|
if (publicFqdn) args.push(publicFqdn);
|
|
19232
19562
|
if (apex) args.push(apex);
|
|
19233
|
-
|
|
19234
|
-
|
|
19235
|
-
|
|
19236
|
-
|
|
19237
|
-
|
|
19238
|
-
|
|
19239
|
-
|
|
19240
|
-
|
|
19241
|
-
|
|
19242
|
-
})
|
|
19243
|
-
|
|
19244
|
-
|
|
19245
|
-
|
|
19246
|
-
|
|
19247
|
-
const
|
|
19248
|
-
|
|
19249
|
-
|
|
19250
|
-
|
|
19251
|
-
const aliasesWritten = [];
|
|
19252
|
-
for (const host of candidates) {
|
|
19253
|
-
if (host === adminFqdn) {
|
|
19254
|
-
log2(`phase=alias-domain-write host=${host} result=skipped-admin`);
|
|
19255
|
-
continue;
|
|
19256
|
-
}
|
|
19257
|
-
if (host.startsWith("public.")) {
|
|
19258
|
-
log2(`phase=alias-domain-write host=${host} result=skipped-public-prefix`);
|
|
19259
|
-
continue;
|
|
19563
|
+
if (await isActionActive("cloudflare-setup")) {
|
|
19564
|
+
return err("request", "Another Cloudflare setup is already running. Wait for it to finish before starting a new one.");
|
|
19565
|
+
}
|
|
19566
|
+
let actionId;
|
|
19567
|
+
let unit;
|
|
19568
|
+
try {
|
|
19569
|
+
const launched = await launchAction("cloudflare-setup", { positional: args });
|
|
19570
|
+
actionId = launched.actionId;
|
|
19571
|
+
unit = launched.unit;
|
|
19572
|
+
} catch (e) {
|
|
19573
|
+
return err("script", `Failed to launch cloudflare-setup action: ${e instanceof Error ? e.message : String(e)}`);
|
|
19574
|
+
}
|
|
19575
|
+
log2(`phase=action-launched id=${actionId} unit=${unit}`);
|
|
19576
|
+
void (async () => {
|
|
19577
|
+
const status = await waitForExit(unit, SETUP_TIMEOUT_MS);
|
|
19578
|
+
if (!status || status.execMainStatus !== 0) {
|
|
19579
|
+
logErr(`phase=post-exit-skipped reason=${status ? `exit=${status.execMainStatus}` : "unit-gone"} id=${actionId}`);
|
|
19580
|
+
return;
|
|
19260
19581
|
}
|
|
19261
|
-
|
|
19262
|
-
|
|
19263
|
-
|
|
19264
|
-
|
|
19265
|
-
|
|
19266
|
-
|
|
19267
|
-
|
|
19268
|
-
`
|
|
19269
|
-
|
|
19270
|
-
|
|
19582
|
+
const candidates = [publicFqdn, apex].filter((h) => typeof h === "string" && h.length > 0);
|
|
19583
|
+
for (const host of candidates) {
|
|
19584
|
+
if (host === adminFqdn) {
|
|
19585
|
+
log2(`phase=alias-domain-write host=${host} result=skipped-admin`);
|
|
19586
|
+
continue;
|
|
19587
|
+
}
|
|
19588
|
+
if (host.startsWith("public.")) {
|
|
19589
|
+
log2(`phase=alias-domain-write host=${host} result=skipped-public-prefix`);
|
|
19590
|
+
continue;
|
|
19591
|
+
}
|
|
19592
|
+
try {
|
|
19593
|
+
addAliasDomain(host);
|
|
19594
|
+
log2(`phase=alias-domain-write host=${host} result=written`);
|
|
19595
|
+
} catch (e) {
|
|
19596
|
+
logErr(`phase=alias-domain-write host=${host} result=error reason="${e instanceof Error ? e.message : String(e)}"`);
|
|
19597
|
+
}
|
|
19271
19598
|
}
|
|
19272
|
-
|
|
19599
|
+
log2(`phase=done action=${actionId}`);
|
|
19600
|
+
})().catch((e) => logErr(`post-exit handler threw: ${e}`));
|
|
19273
19601
|
const total = Date.now() - started;
|
|
19274
|
-
log2(`phase=
|
|
19602
|
+
log2(`phase=response-sent action_id=${actionId} total_ms=${total}`);
|
|
19275
19603
|
writeRouteMilestone(
|
|
19276
19604
|
streamLogPath,
|
|
19277
19605
|
"cloudflare-setup",
|
|
19278
|
-
`phase=
|
|
19606
|
+
`phase=action-launched id=${actionId}`
|
|
19279
19607
|
);
|
|
19280
19608
|
const success = {
|
|
19281
19609
|
ok: true,
|
|
19282
|
-
output:
|
|
19610
|
+
output: `Action launched \u2014 see live log in the form panel.
|
|
19611
|
+
actionId: ${actionId}`,
|
|
19283
19612
|
adminFqdn,
|
|
19284
19613
|
publicFqdn,
|
|
19285
19614
|
apex,
|
|
19286
|
-
aliasesWritten
|
|
19615
|
+
aliasesWritten: [],
|
|
19616
|
+
actionId
|
|
19287
19617
|
};
|
|
19288
|
-
|
|
19289
|
-
log2(`phase=response-sent`);
|
|
19290
|
-
return resp;
|
|
19618
|
+
return ok(success);
|
|
19291
19619
|
});
|
|
19292
19620
|
var cloudflare_default = app23;
|
|
19293
19621
|
|
|
19294
19622
|
// server/routes/admin/files.ts
|
|
19295
|
-
import { createReadStream as
|
|
19623
|
+
import { createReadStream as createReadStream4 } from "fs";
|
|
19296
19624
|
import { readdir as readdir2, readFile as readFile4, stat as stat4, mkdir as mkdir3, writeFile as writeFile4, unlink as unlink2 } from "fs/promises";
|
|
19297
19625
|
import { realpathSync as realpathSync4 } from "fs";
|
|
19298
|
-
import { basename as basename6, dirname as dirname10, join as
|
|
19626
|
+
import { basename as basename6, dirname as dirname10, join as join12, resolve as resolve27, sep as sep2 } from "path";
|
|
19299
19627
|
import { Readable as Readable3 } from "stream";
|
|
19300
19628
|
|
|
19301
19629
|
// app/lib/data-path.ts
|
|
19302
19630
|
import { realpathSync as realpathSync3 } from "fs";
|
|
19303
|
-
import { resolve as
|
|
19304
|
-
var PLATFORM_ROOT9 = process.env.MAXY_PLATFORM_ROOT ??
|
|
19305
|
-
var DATA_ROOT =
|
|
19631
|
+
import { resolve as resolve26, normalize, sep, relative } from "path";
|
|
19632
|
+
var PLATFORM_ROOT9 = process.env.MAXY_PLATFORM_ROOT ?? resolve26(process.cwd(), "../platform");
|
|
19633
|
+
var DATA_ROOT = resolve26(PLATFORM_ROOT9, "..", "data");
|
|
19306
19634
|
function resolveDataPath(raw2) {
|
|
19307
19635
|
const cleaned = normalize("/" + (raw2 ?? "").replace(/\\/g, "/")).replace(/^\/+/, "");
|
|
19308
|
-
const absolute =
|
|
19636
|
+
const absolute = resolve26(DATA_ROOT, cleaned);
|
|
19309
19637
|
let dataRootReal;
|
|
19310
19638
|
try {
|
|
19311
19639
|
dataRootReal = realpathSync3(DATA_ROOT);
|
|
@@ -19653,7 +19981,7 @@ async function cascadeDeleteDocument(params) {
|
|
|
19653
19981
|
var UUID_RE3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
19654
19982
|
async function readMeta(absDir, baseName) {
|
|
19655
19983
|
try {
|
|
19656
|
-
const raw2 = await readFile4(
|
|
19984
|
+
const raw2 = await readFile4(join12(absDir, `${baseName}.meta.json`), "utf8");
|
|
19657
19985
|
const parsed = JSON.parse(raw2);
|
|
19658
19986
|
if (typeof parsed?.filename === "string") {
|
|
19659
19987
|
return { filename: parsed.filename, mimeType: typeof parsed.mimeType === "string" ? parsed.mimeType : void 0 };
|
|
@@ -19664,7 +19992,7 @@ async function readMeta(absDir, baseName) {
|
|
|
19664
19992
|
}
|
|
19665
19993
|
async function readAccountNames() {
|
|
19666
19994
|
const map = /* @__PURE__ */ new Map();
|
|
19667
|
-
const accountsDir =
|
|
19995
|
+
const accountsDir = resolve27(DATA_ROOT, "accounts");
|
|
19668
19996
|
let names;
|
|
19669
19997
|
try {
|
|
19670
19998
|
names = await readdir2(accountsDir);
|
|
@@ -19673,7 +20001,7 @@ async function readAccountNames() {
|
|
|
19673
20001
|
}
|
|
19674
20002
|
for (const name of names) {
|
|
19675
20003
|
if (!UUID_RE3.test(name)) continue;
|
|
19676
|
-
const configPath2 =
|
|
20004
|
+
const configPath2 = resolve27(accountsDir, name, "account.json");
|
|
19677
20005
|
try {
|
|
19678
20006
|
const raw2 = await readFile4(configPath2, "utf8");
|
|
19679
20007
|
const parsed = JSON.parse(raw2);
|
|
@@ -19691,7 +20019,7 @@ async function readAccountNames() {
|
|
|
19691
20019
|
}
|
|
19692
20020
|
async function enrich(absolute, entry, accountNames) {
|
|
19693
20021
|
if (entry.kind === "directory" && UUID_RE3.test(entry.name)) {
|
|
19694
|
-
const meta = await readMeta(
|
|
20022
|
+
const meta = await readMeta(join12(absolute, entry.name), entry.name);
|
|
19695
20023
|
if (meta?.filename) {
|
|
19696
20024
|
entry.displayName = meta.filename;
|
|
19697
20025
|
entry.mimeType = meta.mimeType;
|
|
@@ -19750,7 +20078,7 @@ app24.get("/", requireAdminSession, async (c) => {
|
|
|
19750
20078
|
continue;
|
|
19751
20079
|
}
|
|
19752
20080
|
try {
|
|
19753
|
-
const entryPath =
|
|
20081
|
+
const entryPath = join12(absolute, name);
|
|
19754
20082
|
const s = await stat4(entryPath);
|
|
19755
20083
|
entries.push({
|
|
19756
20084
|
name,
|
|
@@ -19807,7 +20135,7 @@ app24.get("/download", requireAdminSession, async (c) => {
|
|
|
19807
20135
|
}
|
|
19808
20136
|
const filename = basename6(absolute);
|
|
19809
20137
|
const mimeType = detectMimeType(absolute);
|
|
19810
|
-
const nodeStream =
|
|
20138
|
+
const nodeStream = createReadStream4(absolute);
|
|
19811
20139
|
const webStream = Readable3.toWeb(nodeStream);
|
|
19812
20140
|
console.error(`[data] file-download path="${relPath}" size=${info.size}`);
|
|
19813
20141
|
const safeQuotedName = filename.replace(/[\r\n"\\]/g, "_");
|
|
@@ -19864,8 +20192,8 @@ app24.post("/upload", requireAdminSession, async (c) => {
|
|
|
19864
20192
|
}
|
|
19865
20193
|
const safeName = basename6(file.name).replace(/[\0/\\]/g, "_");
|
|
19866
20194
|
const finalName = `${Date.now()}-${safeName}`;
|
|
19867
|
-
const destDir =
|
|
19868
|
-
const destPath =
|
|
20195
|
+
const destDir = resolve27(DATA_ROOT, "uploads", accountId);
|
|
20196
|
+
const destPath = resolve27(destDir, finalName);
|
|
19869
20197
|
try {
|
|
19870
20198
|
await mkdir3(destDir, { recursive: true });
|
|
19871
20199
|
const dataRootReal = realpathSync4(DATA_ROOT);
|
|
@@ -19923,7 +20251,7 @@ app24.delete("/", requireAdminSession, async (c) => {
|
|
|
19923
20251
|
}
|
|
19924
20252
|
const dot = base.lastIndexOf(".");
|
|
19925
20253
|
const stem = dot === -1 ? base : base.slice(0, dot);
|
|
19926
|
-
const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ?
|
|
20254
|
+
const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ? join12(dirname10(absolute), `${stem}.meta.json`) : null;
|
|
19927
20255
|
await unlink2(absolute);
|
|
19928
20256
|
if (sidecarPath) {
|
|
19929
20257
|
try {
|
|
@@ -20200,7 +20528,8 @@ async function handleDefault(c, accountId) {
|
|
|
20200
20528
|
);
|
|
20201
20529
|
}
|
|
20202
20530
|
const warnedClasses = /* @__PURE__ */ new Set();
|
|
20203
|
-
const
|
|
20531
|
+
const conversationWarnings = { loggedMissingMessageCount: false };
|
|
20532
|
+
const nodes = result.rawNodes.map((n) => pruneNode(n, warnedClasses, conversationWarnings));
|
|
20204
20533
|
const edges = result.rawEdges.filter((e) => e && e.id != null);
|
|
20205
20534
|
const trashedSuffix = includeTrashed ? " includeTrashed=1" : "";
|
|
20206
20535
|
console.error(
|
|
@@ -20244,7 +20573,8 @@ async function handleNeighbourhood(c, accountId) {
|
|
|
20244
20573
|
return c.json({ error: "Node not found" }, 404);
|
|
20245
20574
|
}
|
|
20246
20575
|
const warnedClasses = /* @__PURE__ */ new Set();
|
|
20247
|
-
const
|
|
20576
|
+
const conversationWarnings = { loggedMissingMessageCount: false };
|
|
20577
|
+
const nodes = rawNodes.map((n) => pruneNode(n, warnedClasses, conversationWarnings));
|
|
20248
20578
|
const edges = rawEdges.filter((e) => e && e.id != null);
|
|
20249
20579
|
console.error(
|
|
20250
20580
|
`[graph-page] load mode=neighbourhood account=${accountId} elementId=${elementId} nodes=${nodes.length} edges=${edges.length} ms=${elapsed}`
|
|
@@ -20270,6 +20600,11 @@ var DEFAULT_COUNT_CYPHER = `
|
|
|
20270
20600
|
AND n.deletedAt IS NULL
|
|
20271
20601
|
RETURN count(n) AS matched
|
|
20272
20602
|
`;
|
|
20603
|
+
var CONVERSATION_PROPS_PROJECTION = `CASE
|
|
20604
|
+
WHEN 'Conversation' IN labels(x)
|
|
20605
|
+
THEN properties(x) + {messageCount: size([(x)<-[:PART_OF]-(m:Message) WHERE NOT m:Trashed AND m.deletedAt IS NULL | m])}
|
|
20606
|
+
ELSE properties(x)
|
|
20607
|
+
END`;
|
|
20273
20608
|
var DEFAULT_FETCH_CYPHER = `
|
|
20274
20609
|
MATCH (n)
|
|
20275
20610
|
WHERE n.accountId = $accountId
|
|
@@ -20288,7 +20623,7 @@ var DEFAULT_FETCH_CYPHER = `
|
|
|
20288
20623
|
type: type(r)
|
|
20289
20624
|
} END) AS rawEdges
|
|
20290
20625
|
RETURN
|
|
20291
|
-
[x IN nodes | {id: elementId(x), labels: labels(x), properties:
|
|
20626
|
+
[x IN nodes | {id: elementId(x), labels: labels(x), properties: ${CONVERSATION_PROPS_PROJECTION}}] AS nodes,
|
|
20292
20627
|
[e IN rawEdges WHERE e IS NOT NULL] AS edges
|
|
20293
20628
|
`;
|
|
20294
20629
|
var DEFAULT_COUNT_CYPHER_INCLUDE_TRASHED = `
|
|
@@ -20315,7 +20650,7 @@ var DEFAULT_FETCH_CYPHER_INCLUDE_TRASHED = `
|
|
|
20315
20650
|
type: type(r)
|
|
20316
20651
|
} END) AS rawEdges
|
|
20317
20652
|
RETURN
|
|
20318
|
-
[x IN nodes | {id: elementId(x), labels: labels(x), properties:
|
|
20653
|
+
[x IN nodes | {id: elementId(x), labels: labels(x), properties: ${CONVERSATION_PROPS_PROJECTION}}] AS nodes,
|
|
20319
20654
|
[e IN rawEdges WHERE e IS NOT NULL] AS edges
|
|
20320
20655
|
`;
|
|
20321
20656
|
var NEIGHBOURHOOD_CYPHER = `
|
|
@@ -20342,7 +20677,7 @@ var NEIGHBOURHOOD_CYPHER = `
|
|
|
20342
20677
|
type: type(r)
|
|
20343
20678
|
} END) AS rawEdges
|
|
20344
20679
|
RETURN
|
|
20345
|
-
[x IN windowNodes | {id: elementId(x), labels: labels(x), properties:
|
|
20680
|
+
[x IN windowNodes | {id: elementId(x), labels: labels(x), properties: ${CONVERSATION_PROPS_PROJECTION}}] AS nodes,
|
|
20346
20681
|
[e IN rawEdges WHERE e IS NOT NULL] AS edges
|
|
20347
20682
|
`;
|
|
20348
20683
|
var KNOWN_DRIVER_CLASS_NAMES = /* @__PURE__ */ new Set([
|
|
@@ -20384,7 +20719,7 @@ function convertNeo4jValue(value, warnedClasses) {
|
|
|
20384
20719
|
}
|
|
20385
20720
|
return value;
|
|
20386
20721
|
}
|
|
20387
|
-
function pruneNode(node, warnedClasses) {
|
|
20722
|
+
function pruneNode(node, warnedClasses, conversationWarnings) {
|
|
20388
20723
|
const properties = {};
|
|
20389
20724
|
for (const [key, value] of Object.entries(node.properties ?? {})) {
|
|
20390
20725
|
if (STRIPPED_PROPERTIES.has(key)) continue;
|
|
@@ -20392,6 +20727,12 @@ function pruneNode(node, warnedClasses) {
|
|
|
20392
20727
|
}
|
|
20393
20728
|
const trashed = (node.labels ?? []).includes("Trashed");
|
|
20394
20729
|
const labels = (node.labels ?? []).filter((l) => l !== "Trashed");
|
|
20730
|
+
if (labels.includes("Conversation") && typeof properties.messageCount !== "number" && !conversationWarnings.loggedMissingMessageCount) {
|
|
20731
|
+
console.error(
|
|
20732
|
+
`[graph-subgraph] conversation-message-count missing elementId=${node.id} labels=${labels.join(",")}`
|
|
20733
|
+
);
|
|
20734
|
+
conversationWarnings.loggedMissingMessageCount = true;
|
|
20735
|
+
}
|
|
20395
20736
|
return trashed ? { id: node.id, labels, properties, trashed: true } : { id: node.id, labels, properties };
|
|
20396
20737
|
}
|
|
20397
20738
|
var graph_subgraph_default = app26;
|
|
@@ -20781,43 +21122,274 @@ app32.get("/", requireAdminSession, async (c) => {
|
|
|
20781
21122
|
});
|
|
20782
21123
|
var adherence_default = app32;
|
|
20783
21124
|
|
|
20784
|
-
// server/routes/admin/
|
|
21125
|
+
// server/routes/admin/actions/run.ts
|
|
20785
21126
|
var app33 = new Hono2();
|
|
20786
|
-
app33.
|
|
20787
|
-
|
|
20788
|
-
|
|
20789
|
-
|
|
20790
|
-
|
|
20791
|
-
|
|
20792
|
-
|
|
20793
|
-
|
|
20794
|
-
|
|
20795
|
-
|
|
20796
|
-
|
|
20797
|
-
|
|
20798
|
-
|
|
20799
|
-
|
|
20800
|
-
|
|
20801
|
-
|
|
20802
|
-
|
|
20803
|
-
|
|
20804
|
-
|
|
20805
|
-
|
|
20806
|
-
|
|
20807
|
-
|
|
20808
|
-
|
|
20809
|
-
|
|
21127
|
+
app33.post("/:name", requireAdminSession, async (c) => {
|
|
21128
|
+
const name = c.req.param("name");
|
|
21129
|
+
if (!isKnownAction(name)) {
|
|
21130
|
+
return c.json({ error: `unknown action: ${name}` }, 400);
|
|
21131
|
+
}
|
|
21132
|
+
const actionName = name;
|
|
21133
|
+
if (await isActionActive(actionName)) {
|
|
21134
|
+
return c.json({ error: `action ${actionName} is already running`, code: "already-active" }, 409);
|
|
21135
|
+
}
|
|
21136
|
+
let body = {};
|
|
21137
|
+
try {
|
|
21138
|
+
body = await c.req.json();
|
|
21139
|
+
} catch {
|
|
21140
|
+
}
|
|
21141
|
+
const launchArgs = {};
|
|
21142
|
+
if (Array.isArray(body.positional)) {
|
|
21143
|
+
if (!body.positional.every((x) => typeof x === "string")) {
|
|
21144
|
+
return c.json({ error: "positional args must be strings" }, 400);
|
|
21145
|
+
}
|
|
21146
|
+
launchArgs.positional = body.positional;
|
|
21147
|
+
}
|
|
21148
|
+
if (typeof body.sudoPassword === "string" && body.sudoPassword) {
|
|
21149
|
+
launchArgs.sudoPassword = body.sudoPassword;
|
|
21150
|
+
}
|
|
21151
|
+
try {
|
|
21152
|
+
const result = await launchAction(actionName, launchArgs);
|
|
21153
|
+
return c.json({ ok: true, actionId: result.actionId, unit: result.unit, logPath: result.logPath });
|
|
21154
|
+
} catch (err) {
|
|
21155
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
21156
|
+
console.error(`[admin/actions/run] launch failed action=${actionName}: ${message}`);
|
|
21157
|
+
return c.json({ error: `launch failed: ${message}` }, 500);
|
|
21158
|
+
}
|
|
21159
|
+
});
|
|
21160
|
+
var run_default = app33;
|
|
21161
|
+
|
|
21162
|
+
// node_modules/hono/dist/utils/stream.js
|
|
21163
|
+
var StreamingApi = class {
|
|
21164
|
+
writer;
|
|
21165
|
+
encoder;
|
|
21166
|
+
writable;
|
|
21167
|
+
abortSubscribers = [];
|
|
21168
|
+
responseReadable;
|
|
21169
|
+
/**
|
|
21170
|
+
* Whether the stream has been aborted.
|
|
21171
|
+
*/
|
|
21172
|
+
aborted = false;
|
|
21173
|
+
/**
|
|
21174
|
+
* Whether the stream has been closed normally.
|
|
21175
|
+
*/
|
|
21176
|
+
closed = false;
|
|
21177
|
+
constructor(writable, _readable) {
|
|
21178
|
+
this.writable = writable;
|
|
21179
|
+
this.writer = writable.getWriter();
|
|
21180
|
+
this.encoder = new TextEncoder();
|
|
21181
|
+
const reader = _readable.getReader();
|
|
21182
|
+
this.abortSubscribers.push(async () => {
|
|
21183
|
+
await reader.cancel();
|
|
21184
|
+
});
|
|
21185
|
+
this.responseReadable = new ReadableStream({
|
|
21186
|
+
async pull(controller) {
|
|
21187
|
+
const { done, value } = await reader.read();
|
|
21188
|
+
done ? controller.close() : controller.enqueue(value);
|
|
21189
|
+
},
|
|
21190
|
+
cancel: () => {
|
|
21191
|
+
this.abort();
|
|
21192
|
+
}
|
|
21193
|
+
});
|
|
21194
|
+
}
|
|
21195
|
+
async write(input) {
|
|
21196
|
+
try {
|
|
21197
|
+
if (typeof input === "string") {
|
|
21198
|
+
input = this.encoder.encode(input);
|
|
21199
|
+
}
|
|
21200
|
+
await this.writer.write(input);
|
|
21201
|
+
} catch {
|
|
21202
|
+
}
|
|
21203
|
+
return this;
|
|
21204
|
+
}
|
|
21205
|
+
async writeln(input) {
|
|
21206
|
+
await this.write(input + "\n");
|
|
21207
|
+
return this;
|
|
21208
|
+
}
|
|
21209
|
+
sleep(ms) {
|
|
21210
|
+
return new Promise((res) => setTimeout(res, ms));
|
|
21211
|
+
}
|
|
21212
|
+
async close() {
|
|
21213
|
+
try {
|
|
21214
|
+
await this.writer.close();
|
|
21215
|
+
} catch {
|
|
21216
|
+
}
|
|
21217
|
+
this.closed = true;
|
|
21218
|
+
}
|
|
21219
|
+
async pipe(body) {
|
|
21220
|
+
this.writer.releaseLock();
|
|
21221
|
+
await body.pipeTo(this.writable, { preventClose: true });
|
|
21222
|
+
this.writer = this.writable.getWriter();
|
|
21223
|
+
}
|
|
21224
|
+
onAbort(listener) {
|
|
21225
|
+
this.abortSubscribers.push(listener);
|
|
21226
|
+
}
|
|
21227
|
+
/**
|
|
21228
|
+
* Abort the stream.
|
|
21229
|
+
* You can call this method when stream is aborted by external event.
|
|
21230
|
+
*/
|
|
21231
|
+
abort() {
|
|
21232
|
+
if (!this.aborted) {
|
|
21233
|
+
this.aborted = true;
|
|
21234
|
+
this.abortSubscribers.forEach((subscriber) => subscriber());
|
|
21235
|
+
}
|
|
21236
|
+
}
|
|
21237
|
+
};
|
|
21238
|
+
|
|
21239
|
+
// node_modules/hono/dist/helper/streaming/utils.js
|
|
21240
|
+
var isOldBunVersion = () => {
|
|
21241
|
+
const version = typeof Bun !== "undefined" ? Bun.version : void 0;
|
|
21242
|
+
if (version === void 0) {
|
|
21243
|
+
return false;
|
|
21244
|
+
}
|
|
21245
|
+
const result = version.startsWith("1.1") || version.startsWith("1.0") || version.startsWith("0.");
|
|
21246
|
+
isOldBunVersion = () => result;
|
|
21247
|
+
return result;
|
|
21248
|
+
};
|
|
21249
|
+
|
|
21250
|
+
// node_modules/hono/dist/helper/streaming/stream.js
|
|
21251
|
+
var contextStash = /* @__PURE__ */ new WeakMap();
|
|
21252
|
+
var stream = (c, cb, onError) => {
|
|
21253
|
+
const { readable, writable } = new TransformStream();
|
|
21254
|
+
const stream2 = new StreamingApi(writable, readable);
|
|
21255
|
+
if (isOldBunVersion()) {
|
|
21256
|
+
c.req.raw.signal.addEventListener("abort", () => {
|
|
21257
|
+
if (!stream2.closed) {
|
|
21258
|
+
stream2.abort();
|
|
21259
|
+
}
|
|
21260
|
+
});
|
|
21261
|
+
}
|
|
21262
|
+
contextStash.set(stream2.responseReadable, c);
|
|
21263
|
+
(async () => {
|
|
21264
|
+
try {
|
|
21265
|
+
await cb(stream2);
|
|
21266
|
+
} catch (e) {
|
|
21267
|
+
if (e === void 0) {
|
|
21268
|
+
} else if (e instanceof Error && onError) {
|
|
21269
|
+
await onError(e, stream2);
|
|
21270
|
+
} else {
|
|
21271
|
+
console.error(e);
|
|
21272
|
+
}
|
|
21273
|
+
} finally {
|
|
21274
|
+
stream2.close();
|
|
21275
|
+
}
|
|
21276
|
+
})();
|
|
21277
|
+
return c.newResponse(stream2.responseReadable);
|
|
21278
|
+
};
|
|
21279
|
+
|
|
21280
|
+
// server/routes/admin/actions/stream.ts
|
|
21281
|
+
import { existsSync as existsSync24 } from "fs";
|
|
21282
|
+
var app34 = new Hono2();
|
|
21283
|
+
var ACTION_ID_RE = /^[a-z0-9-]+-[a-z0-9]+-[a-f0-9]{8}$/;
|
|
21284
|
+
app34.get("/:id/stream", requireAdminSession, async (c) => {
|
|
21285
|
+
const id = c.req.param("id");
|
|
21286
|
+
if (!ACTION_ID_RE.test(id)) {
|
|
21287
|
+
return c.json({ error: "invalid action id" }, 400);
|
|
21288
|
+
}
|
|
21289
|
+
const logPath2 = actionLogPath(id);
|
|
21290
|
+
if (!existsSync24(logPath2)) {
|
|
21291
|
+
return c.json({ error: "action not found (log missing)" }, 404);
|
|
21292
|
+
}
|
|
21293
|
+
const lastEventId = c.req.header("Last-Event-ID") ?? c.req.query("from") ?? "";
|
|
21294
|
+
const fromByteOffset = lastEventId ? Math.max(0, Number(lastEventId) || 0) : 0;
|
|
21295
|
+
const unit = `maxy-action-${id}`;
|
|
21296
|
+
const abort = new AbortController();
|
|
21297
|
+
c.req.raw.signal.addEventListener("abort", () => abort.abort());
|
|
21298
|
+
return stream(c, async (s) => {
|
|
21299
|
+
s.onAbort(() => abort.abort());
|
|
21300
|
+
c.header("Content-Type", "text/event-stream");
|
|
21301
|
+
c.header("Cache-Control", "no-cache, no-transform");
|
|
21302
|
+
c.header("Connection", "keep-alive");
|
|
21303
|
+
await s.write(": ok\n\n");
|
|
21304
|
+
try {
|
|
21305
|
+
for await (const ev of streamActionEvents({ actionId: id, unit, fromByteOffset, signal: abort.signal })) {
|
|
21306
|
+
const id_ = ev.type === "line" ? String(ev.byteOffset) : "";
|
|
21307
|
+
const payload = JSON.stringify(ev);
|
|
21308
|
+
const frame = `${id_ ? `id: ${id_}
|
|
21309
|
+
` : ""}event: ${ev.type}
|
|
21310
|
+
data: ${payload}
|
|
21311
|
+
|
|
21312
|
+
`;
|
|
21313
|
+
await s.write(frame);
|
|
21314
|
+
}
|
|
21315
|
+
} catch (err) {
|
|
21316
|
+
console.error(`[admin/actions/stream] action=${id} error: ${err instanceof Error ? err.message : err}`);
|
|
21317
|
+
}
|
|
21318
|
+
});
|
|
21319
|
+
});
|
|
21320
|
+
var stream_default = app34;
|
|
21321
|
+
|
|
21322
|
+
// server/routes/admin/actions/sudo-prime.ts
|
|
21323
|
+
var app35 = new Hono2();
|
|
21324
|
+
app35.post("/", requireAdminSession, async (c) => {
|
|
21325
|
+
let body = {};
|
|
21326
|
+
try {
|
|
21327
|
+
body = await c.req.json();
|
|
21328
|
+
} catch {
|
|
21329
|
+
}
|
|
21330
|
+
if (typeof body.password !== "string" || !body.password) {
|
|
21331
|
+
return c.json({ error: "password required" }, 400);
|
|
21332
|
+
}
|
|
21333
|
+
const clientIp = c.var.clientIp ?? "unknown";
|
|
21334
|
+
const result = await primeSudo(body.password, clientIp);
|
|
21335
|
+
switch (result) {
|
|
21336
|
+
case "ok":
|
|
21337
|
+
return c.body(null, 204);
|
|
21338
|
+
case "invalid":
|
|
21339
|
+
return c.json({ error: "invalid password" }, 401);
|
|
21340
|
+
case "limited":
|
|
21341
|
+
return c.json({ error: "too many attempts; wait a minute" }, 429);
|
|
21342
|
+
case "error":
|
|
21343
|
+
return c.json({ error: "sudo prime failed \u2014 check server logs" }, 500);
|
|
21344
|
+
}
|
|
21345
|
+
});
|
|
21346
|
+
var sudo_prime_default = app35;
|
|
21347
|
+
|
|
21348
|
+
// server/routes/admin/actions/index.ts
|
|
21349
|
+
var app36 = new Hono2();
|
|
21350
|
+
app36.route("/sudo-prime", sudo_prime_default);
|
|
21351
|
+
app36.route("/", stream_default);
|
|
21352
|
+
app36.route("/", run_default);
|
|
21353
|
+
var actions_default = app36;
|
|
21354
|
+
|
|
21355
|
+
// server/routes/admin/index.ts
|
|
21356
|
+
var app37 = new Hono2();
|
|
21357
|
+
app37.route("/session", session_default2);
|
|
21358
|
+
app37.route("/chat", chat_default2);
|
|
21359
|
+
app37.route("/compact", compact_default);
|
|
21360
|
+
app37.route("/logs", logs_default);
|
|
21361
|
+
app37.route("/claude-info", claude_info_default);
|
|
21362
|
+
app37.route("/attachment", attachment_default);
|
|
21363
|
+
app37.route("/account", account_default);
|
|
21364
|
+
app37.route("/agents", agents_default);
|
|
21365
|
+
app37.route("/version", version_default);
|
|
21366
|
+
app37.route("/sessions", sessions_default);
|
|
21367
|
+
app37.route("/browser", browser_default);
|
|
21368
|
+
app37.route("/device-browser", device_browser_default);
|
|
21369
|
+
app37.route("/events", events_default);
|
|
21370
|
+
app37.route("/cloudflare", cloudflare_default);
|
|
21371
|
+
app37.route("/files", files_default);
|
|
21372
|
+
app37.route("/graph-search", graph_search_default);
|
|
21373
|
+
app37.route("/graph-subgraph", graph_subgraph_default);
|
|
21374
|
+
app37.route("/graph-delete", graph_delete_default);
|
|
21375
|
+
app37.route("/graph-restore", graph_restore_default);
|
|
21376
|
+
app37.route("/graph-labels-in-graph", graph_labels_in_graph_default);
|
|
21377
|
+
app37.route("/graph-default-view", graph_default_view_default);
|
|
21378
|
+
app37.route("/file-attach", file_attach_default);
|
|
21379
|
+
app37.route("/adherence", adherence_default);
|
|
21380
|
+
app37.route("/actions", actions_default);
|
|
21381
|
+
var admin_default = app37;
|
|
20810
21382
|
|
|
20811
21383
|
// server/index.ts
|
|
20812
21384
|
var PLATFORM_ROOT10 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
20813
|
-
var BRAND_JSON_PATH = PLATFORM_ROOT10 ?
|
|
21385
|
+
var BRAND_JSON_PATH = PLATFORM_ROOT10 ? join13(PLATFORM_ROOT10, "config", "brand.json") : "";
|
|
20814
21386
|
var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", domain: "getmaxy.com" };
|
|
20815
|
-
if (BRAND_JSON_PATH && !
|
|
21387
|
+
if (BRAND_JSON_PATH && !existsSync25(BRAND_JSON_PATH)) {
|
|
20816
21388
|
console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
|
|
20817
21389
|
}
|
|
20818
|
-
if (BRAND_JSON_PATH &&
|
|
21390
|
+
if (BRAND_JSON_PATH && existsSync25(BRAND_JSON_PATH)) {
|
|
20819
21391
|
try {
|
|
20820
|
-
const parsed = JSON.parse(
|
|
21392
|
+
const parsed = JSON.parse(readFileSync26(BRAND_JSON_PATH, "utf-8"));
|
|
20821
21393
|
BRAND = { ...BRAND, ...parsed };
|
|
20822
21394
|
} catch (err) {
|
|
20823
21395
|
console.error(`[brand] Failed to parse brand.json: ${err.message}`);
|
|
@@ -20836,11 +21408,11 @@ var brandLoginOpts = {
|
|
|
20836
21408
|
bodyFont: BRAND.defaultFonts?.body,
|
|
20837
21409
|
logoContainsName: !!BRAND.logoContainsName
|
|
20838
21410
|
};
|
|
20839
|
-
var ALIAS_DOMAINS_PATH2 =
|
|
21411
|
+
var ALIAS_DOMAINS_PATH2 = join13(homedir5(), BRAND.configDir, "alias-domains.json");
|
|
20840
21412
|
function loadAliasDomains() {
|
|
20841
21413
|
try {
|
|
20842
|
-
if (!
|
|
20843
|
-
const parsed = JSON.parse(
|
|
21414
|
+
if (!existsSync25(ALIAS_DOMAINS_PATH2)) return null;
|
|
21415
|
+
const parsed = JSON.parse(readFileSync26(ALIAS_DOMAINS_PATH2, "utf-8"));
|
|
20844
21416
|
if (!Array.isArray(parsed)) {
|
|
20845
21417
|
console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
|
|
20846
21418
|
return null;
|
|
@@ -20864,9 +21436,9 @@ watchFile(ALIAS_DOMAINS_PATH2, { interval: 2e3 }, () => {
|
|
|
20864
21436
|
function isPublicHost(host) {
|
|
20865
21437
|
return host.startsWith("public.") || aliasDomains.has(host);
|
|
20866
21438
|
}
|
|
20867
|
-
var
|
|
20868
|
-
|
|
20869
|
-
|
|
21439
|
+
var app38 = new Hono2();
|
|
21440
|
+
app38.use("*", clientIpMiddleware);
|
|
21441
|
+
app38.use("*", async (c, next) => {
|
|
20870
21442
|
await next();
|
|
20871
21443
|
c.header("X-Content-Type-Options", "nosniff");
|
|
20872
21444
|
c.header("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
@@ -20889,7 +21461,7 @@ var PUBLIC_ALLOWED_PREFIXES = [
|
|
|
20889
21461
|
"/g/"
|
|
20890
21462
|
];
|
|
20891
21463
|
var PUBLIC_ALLOWED_EXACT = ["/favicon.ico"];
|
|
20892
|
-
|
|
21464
|
+
app38.use("*", async (c, next) => {
|
|
20893
21465
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
20894
21466
|
if (!isPublicHost(host)) {
|
|
20895
21467
|
await next();
|
|
@@ -20929,7 +21501,7 @@ function resolveRemoteAuthOpts() {
|
|
|
20929
21501
|
return brandLoginOpts;
|
|
20930
21502
|
}
|
|
20931
21503
|
var MAX_LOGIN_BODY = 8 * 1024;
|
|
20932
|
-
|
|
21504
|
+
app38.post("/__remote-auth/login", async (c) => {
|
|
20933
21505
|
const clientIp = c.var.clientIp || "unknown";
|
|
20934
21506
|
const rateLimited = checkRateLimit(clientIp);
|
|
20935
21507
|
if (rateLimited) {
|
|
@@ -20965,7 +21537,7 @@ app34.post("/__remote-auth/login", async (c) => {
|
|
|
20965
21537
|
}
|
|
20966
21538
|
});
|
|
20967
21539
|
});
|
|
20968
|
-
|
|
21540
|
+
app38.get("/__remote-auth/logout", () => {
|
|
20969
21541
|
return new Response(null, {
|
|
20970
21542
|
status: 302,
|
|
20971
21543
|
headers: {
|
|
@@ -20975,7 +21547,7 @@ app34.get("/__remote-auth/logout", () => {
|
|
|
20975
21547
|
}
|
|
20976
21548
|
});
|
|
20977
21549
|
});
|
|
20978
|
-
|
|
21550
|
+
app38.post("/__remote-auth/change-password", async (c) => {
|
|
20979
21551
|
const clientIp = c.var.clientIp || "unknown";
|
|
20980
21552
|
const rateLimited = checkRateLimit(clientIp);
|
|
20981
21553
|
if (rateLimited) {
|
|
@@ -21024,13 +21596,13 @@ app34.post("/__remote-auth/change-password", async (c) => {
|
|
|
21024
21596
|
return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), mode: "change", changeError: "Failed to save password", redirect }), 200);
|
|
21025
21597
|
}
|
|
21026
21598
|
});
|
|
21027
|
-
|
|
21599
|
+
app38.get("/__remote-auth/setup", (c) => {
|
|
21028
21600
|
if (isRemoteAuthConfigured()) {
|
|
21029
21601
|
return c.redirect("/");
|
|
21030
21602
|
}
|
|
21031
21603
|
return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), mode: "setup" }), 200);
|
|
21032
21604
|
});
|
|
21033
|
-
|
|
21605
|
+
app38.post("/__remote-auth/set-initial-password", async (c) => {
|
|
21034
21606
|
if (isRemoteAuthConfigured()) {
|
|
21035
21607
|
return c.redirect("/");
|
|
21036
21608
|
}
|
|
@@ -21066,10 +21638,10 @@ app34.post("/__remote-auth/set-initial-password", async (c) => {
|
|
|
21066
21638
|
return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), mode: "setup", setupError: "Failed to save password. Please try again." }), 200);
|
|
21067
21639
|
}
|
|
21068
21640
|
});
|
|
21069
|
-
|
|
21641
|
+
app38.get("/api/remote-auth/status", (c) => {
|
|
21070
21642
|
return c.json({ configured: isRemoteAuthConfigured() });
|
|
21071
21643
|
});
|
|
21072
|
-
|
|
21644
|
+
app38.post("/api/remote-auth/set-password", async (c) => {
|
|
21073
21645
|
let body;
|
|
21074
21646
|
try {
|
|
21075
21647
|
body = await c.req.json();
|
|
@@ -21099,9 +21671,9 @@ app34.post("/api/remote-auth/set-password", async (c) => {
|
|
|
21099
21671
|
return c.json({ error: "Failed to save password" }, 500);
|
|
21100
21672
|
}
|
|
21101
21673
|
});
|
|
21102
|
-
|
|
21674
|
+
app38.route("/api/_client-error", client_error_default);
|
|
21103
21675
|
console.log("[client-error-route] mounted");
|
|
21104
|
-
|
|
21676
|
+
app38.use("*", async (c, next) => {
|
|
21105
21677
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
21106
21678
|
const path2 = c.req.path;
|
|
21107
21679
|
if (path2 === "/favicon.ico" || path2.startsWith("/assets/") || path2.startsWith("/brand/")) {
|
|
@@ -21131,15 +21703,15 @@ app34.use("*", async (c, next) => {
|
|
|
21131
21703
|
console.error(`[remote-auth] login required ip=${clientIp} path=${path2}`);
|
|
21132
21704
|
return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), redirect: path2 }), 200);
|
|
21133
21705
|
});
|
|
21134
|
-
|
|
21135
|
-
|
|
21136
|
-
|
|
21137
|
-
|
|
21138
|
-
|
|
21139
|
-
|
|
21140
|
-
|
|
21141
|
-
|
|
21142
|
-
|
|
21706
|
+
app38.route("/api/health", health_default);
|
|
21707
|
+
app38.route("/api/session", session_default);
|
|
21708
|
+
app38.route("/api/chat", chat_default);
|
|
21709
|
+
app38.route("/api/group", group_default);
|
|
21710
|
+
app38.route("/api/access", access_default);
|
|
21711
|
+
app38.route("/api/telegram", telegram_default);
|
|
21712
|
+
app38.route("/api/whatsapp", whatsapp_default);
|
|
21713
|
+
app38.route("/api/onboarding", onboarding_default);
|
|
21714
|
+
app38.route("/api/admin", admin_default);
|
|
21143
21715
|
var SAFE_SLUG_RE = /^[a-z][a-z0-9-]{2,49}$/;
|
|
21144
21716
|
var SAFE_FILENAME_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
|
|
21145
21717
|
var IMAGE_MIME = {
|
|
@@ -21151,7 +21723,7 @@ var IMAGE_MIME = {
|
|
|
21151
21723
|
".svg": "image/svg+xml",
|
|
21152
21724
|
".ico": "image/x-icon"
|
|
21153
21725
|
};
|
|
21154
|
-
|
|
21726
|
+
app38.get("/agent-assets/:slug/:filename", (c) => {
|
|
21155
21727
|
const slug = c.req.param("slug");
|
|
21156
21728
|
const filename = c.req.param("filename");
|
|
21157
21729
|
if (!SAFE_SLUG_RE.test(slug)) {
|
|
@@ -21167,26 +21739,26 @@ app34.get("/agent-assets/:slug/:filename", (c) => {
|
|
|
21167
21739
|
console.error(`[agent-assets] no-account slug=${slug} file=${filename}`);
|
|
21168
21740
|
return c.text("Not found", 404);
|
|
21169
21741
|
}
|
|
21170
|
-
const filePath =
|
|
21171
|
-
const expectedDir =
|
|
21742
|
+
const filePath = resolve28(account.accountDir, "agents", slug, "assets", filename);
|
|
21743
|
+
const expectedDir = resolve28(account.accountDir, "agents", slug, "assets");
|
|
21172
21744
|
if (!filePath.startsWith(expectedDir + "/")) {
|
|
21173
21745
|
console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
|
|
21174
21746
|
return c.text("Forbidden", 403);
|
|
21175
21747
|
}
|
|
21176
|
-
if (!
|
|
21748
|
+
if (!existsSync25(filePath)) {
|
|
21177
21749
|
console.error(`[agent-assets] serve slug=${slug} file=${filename} status=404`);
|
|
21178
21750
|
return c.text("Not found", 404);
|
|
21179
21751
|
}
|
|
21180
21752
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
21181
21753
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
21182
21754
|
console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
|
|
21183
|
-
const body =
|
|
21755
|
+
const body = readFileSync26(filePath);
|
|
21184
21756
|
return c.body(body, 200, {
|
|
21185
21757
|
"Content-Type": contentType,
|
|
21186
21758
|
"Cache-Control": "public, max-age=3600"
|
|
21187
21759
|
});
|
|
21188
21760
|
});
|
|
21189
|
-
|
|
21761
|
+
app38.get("/generated/:filename", (c) => {
|
|
21190
21762
|
const filename = c.req.param("filename");
|
|
21191
21763
|
if (!SAFE_FILENAME_RE.test(filename) || filename.includes("..")) {
|
|
21192
21764
|
console.error(`[generated] serve file=${filename} status=403`);
|
|
@@ -21197,20 +21769,20 @@ app34.get("/generated/:filename", (c) => {
|
|
|
21197
21769
|
console.error(`[generated] serve file=${filename} status=404`);
|
|
21198
21770
|
return c.text("Not found", 404);
|
|
21199
21771
|
}
|
|
21200
|
-
const filePath =
|
|
21201
|
-
const expectedDir =
|
|
21772
|
+
const filePath = resolve28(account.accountDir, "generated", filename);
|
|
21773
|
+
const expectedDir = resolve28(account.accountDir, "generated");
|
|
21202
21774
|
if (!filePath.startsWith(expectedDir + "/")) {
|
|
21203
21775
|
console.error(`[generated] serve file=${filename} status=403`);
|
|
21204
21776
|
return c.text("Forbidden", 403);
|
|
21205
21777
|
}
|
|
21206
|
-
if (!
|
|
21778
|
+
if (!existsSync25(filePath)) {
|
|
21207
21779
|
console.error(`[generated] serve file=${filename} status=404`);
|
|
21208
21780
|
return c.text("Not found", 404);
|
|
21209
21781
|
}
|
|
21210
21782
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
21211
21783
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
21212
21784
|
console.log(`[generated] serve file=${filename} status=200`);
|
|
21213
|
-
const body =
|
|
21785
|
+
const body = readFileSync26(filePath);
|
|
21214
21786
|
return c.body(body, 200, {
|
|
21215
21787
|
"Content-Type": contentType,
|
|
21216
21788
|
"Cache-Control": "public, max-age=86400"
|
|
@@ -21219,9 +21791,9 @@ app34.get("/generated/:filename", (c) => {
|
|
|
21219
21791
|
var htmlCache = /* @__PURE__ */ new Map();
|
|
21220
21792
|
var brandLogoPath = "/brand/maxy-monochrome.png";
|
|
21221
21793
|
var brandIconPath = "/brand/maxy-monochrome.png";
|
|
21222
|
-
if (BRAND_JSON_PATH &&
|
|
21794
|
+
if (BRAND_JSON_PATH && existsSync25(BRAND_JSON_PATH)) {
|
|
21223
21795
|
try {
|
|
21224
|
-
const fullBrand = JSON.parse(
|
|
21796
|
+
const fullBrand = JSON.parse(readFileSync26(BRAND_JSON_PATH, "utf-8"));
|
|
21225
21797
|
if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
|
|
21226
21798
|
brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
|
|
21227
21799
|
} catch {
|
|
@@ -21238,9 +21810,9 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
|
|
|
21238
21810
|
function readInstalledVersion() {
|
|
21239
21811
|
try {
|
|
21240
21812
|
if (!PLATFORM_ROOT10) return "unknown";
|
|
21241
|
-
const versionFile =
|
|
21242
|
-
if (!
|
|
21243
|
-
const content =
|
|
21813
|
+
const versionFile = join13(PLATFORM_ROOT10, "config", `.${BRAND.hostname}-version`);
|
|
21814
|
+
if (!existsSync25(versionFile)) return "unknown";
|
|
21815
|
+
const content = readFileSync26(versionFile, "utf-8").trim();
|
|
21244
21816
|
return content || "unknown";
|
|
21245
21817
|
} catch {
|
|
21246
21818
|
return "unknown";
|
|
@@ -21281,7 +21853,7 @@ var clientErrorReporterScript = `<script>
|
|
|
21281
21853
|
function cachedHtml(file) {
|
|
21282
21854
|
let html = htmlCache.get(file);
|
|
21283
21855
|
if (!html) {
|
|
21284
|
-
html =
|
|
21856
|
+
html = readFileSync26(resolve28(process.cwd(), "public", file), "utf-8");
|
|
21285
21857
|
html = html.replace("<title>Maxy</title>", `<title>${escapeHtml(BRAND.productName)}</title>`);
|
|
21286
21858
|
html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
|
|
21287
21859
|
const headInjection = file === "index.html" ? `${brandScript}
|
|
@@ -21296,26 +21868,26 @@ ${clientErrorReporterScript}
|
|
|
21296
21868
|
}
|
|
21297
21869
|
var brandedHtmlCache = /* @__PURE__ */ new Map();
|
|
21298
21870
|
function loadBrandingCache(agentSlug) {
|
|
21299
|
-
const configDir2 =
|
|
21871
|
+
const configDir2 = join13(homedir5(), BRAND.configDir);
|
|
21300
21872
|
try {
|
|
21301
|
-
const accountJsonPath =
|
|
21302
|
-
if (!
|
|
21303
|
-
const account = JSON.parse(
|
|
21873
|
+
const accountJsonPath = join13(configDir2, "account.json");
|
|
21874
|
+
if (!existsSync25(accountJsonPath)) return null;
|
|
21875
|
+
const account = JSON.parse(readFileSync26(accountJsonPath, "utf-8"));
|
|
21304
21876
|
const accountId = account.accountId;
|
|
21305
21877
|
if (!accountId) return null;
|
|
21306
|
-
const cachePath =
|
|
21307
|
-
if (!
|
|
21308
|
-
return JSON.parse(
|
|
21878
|
+
const cachePath = join13(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
|
|
21879
|
+
if (!existsSync25(cachePath)) return null;
|
|
21880
|
+
return JSON.parse(readFileSync26(cachePath, "utf-8"));
|
|
21309
21881
|
} catch {
|
|
21310
21882
|
return null;
|
|
21311
21883
|
}
|
|
21312
21884
|
}
|
|
21313
21885
|
function resolveDefaultSlug() {
|
|
21314
21886
|
try {
|
|
21315
|
-
const configDir2 =
|
|
21316
|
-
const accountJsonPath =
|
|
21317
|
-
if (!
|
|
21318
|
-
const account = JSON.parse(
|
|
21887
|
+
const configDir2 = join13(homedir5(), BRAND.configDir);
|
|
21888
|
+
const accountJsonPath = join13(configDir2, "account.json");
|
|
21889
|
+
if (!existsSync25(accountJsonPath)) return null;
|
|
21890
|
+
const account = JSON.parse(readFileSync26(accountJsonPath, "utf-8"));
|
|
21319
21891
|
return account.defaultAgent || null;
|
|
21320
21892
|
} catch {
|
|
21321
21893
|
return null;
|
|
@@ -21351,7 +21923,7 @@ function brandedPublicHtml(agentSlug) {
|
|
|
21351
21923
|
function escapeHtml(s) {
|
|
21352
21924
|
return s.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
21353
21925
|
}
|
|
21354
|
-
|
|
21926
|
+
app38.get("/", (c) => {
|
|
21355
21927
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
21356
21928
|
if (isPublicHost(host)) {
|
|
21357
21929
|
const defaultSlug = resolveDefaultSlug();
|
|
@@ -21359,12 +21931,12 @@ app34.get("/", (c) => {
|
|
|
21359
21931
|
}
|
|
21360
21932
|
return c.html(cachedHtml("index.html"));
|
|
21361
21933
|
});
|
|
21362
|
-
|
|
21934
|
+
app38.get("/public", (c) => {
|
|
21363
21935
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
21364
21936
|
if (isPublicHost(host)) return c.text("Not found", 404);
|
|
21365
21937
|
return c.html(cachedHtml("public.html"));
|
|
21366
21938
|
});
|
|
21367
|
-
|
|
21939
|
+
app38.get("/chat", (c) => {
|
|
21368
21940
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
21369
21941
|
if (isPublicHost(host)) return c.text("Not found", 404);
|
|
21370
21942
|
return c.html(cachedHtml("public.html"));
|
|
@@ -21383,12 +21955,12 @@ async function logViewerFetch(c, next) {
|
|
|
21383
21955
|
duration_ms: Date.now() - start
|
|
21384
21956
|
});
|
|
21385
21957
|
}
|
|
21386
|
-
|
|
21387
|
-
|
|
21388
|
-
|
|
21958
|
+
app38.use("/vnc-viewer.html", logViewerFetch);
|
|
21959
|
+
app38.use("/vnc-popout.html", logViewerFetch);
|
|
21960
|
+
app38.get("/vnc-popout.html", (c) => {
|
|
21389
21961
|
let html = htmlCache.get("vnc-popout.html");
|
|
21390
21962
|
if (!html) {
|
|
21391
|
-
html =
|
|
21963
|
+
html = readFileSync26(resolve28(process.cwd(), "public", "vnc-popout.html"), "utf-8");
|
|
21392
21964
|
const name = escapeHtml(BRAND.productName);
|
|
21393
21965
|
html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
|
|
21394
21966
|
html = html.replace("</head>", ` ${brandScript}
|
|
@@ -21398,7 +21970,7 @@ app34.get("/vnc-popout.html", (c) => {
|
|
|
21398
21970
|
}
|
|
21399
21971
|
return c.html(html);
|
|
21400
21972
|
});
|
|
21401
|
-
|
|
21973
|
+
app38.post("/api/vnc/client-event", async (c) => {
|
|
21402
21974
|
let body;
|
|
21403
21975
|
try {
|
|
21404
21976
|
body = await c.req.json();
|
|
@@ -21419,20 +21991,20 @@ app34.post("/api/vnc/client-event", async (c) => {
|
|
|
21419
21991
|
});
|
|
21420
21992
|
return c.json({ ok: true });
|
|
21421
21993
|
});
|
|
21422
|
-
|
|
21994
|
+
app38.get("/g/:slug", (c) => {
|
|
21423
21995
|
return c.html(brandedPublicHtml());
|
|
21424
21996
|
});
|
|
21425
|
-
|
|
21997
|
+
app38.get("/graph", (c) => {
|
|
21426
21998
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
21427
21999
|
if (isPublicHost(host)) return c.text("Not found", 404);
|
|
21428
22000
|
return c.html(cachedHtml("graph.html"));
|
|
21429
22001
|
});
|
|
21430
|
-
|
|
22002
|
+
app38.get("/data", (c) => {
|
|
21431
22003
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
21432
22004
|
if (isPublicHost(host)) return c.text("Not found", 404);
|
|
21433
22005
|
return c.html(cachedHtml("data.html"));
|
|
21434
22006
|
});
|
|
21435
|
-
|
|
22007
|
+
app38.get("/:slug", async (c, next) => {
|
|
21436
22008
|
const slug = c.req.param("slug");
|
|
21437
22009
|
if (AGENT_SLUG_PATTERN.test(`/${slug}`)) {
|
|
21438
22010
|
const branding = loadBrandingCache(slug);
|
|
@@ -21441,10 +22013,10 @@ app34.get("/:slug", async (c, next) => {
|
|
|
21441
22013
|
}
|
|
21442
22014
|
await next();
|
|
21443
22015
|
});
|
|
21444
|
-
|
|
22016
|
+
app38.use("/*", serveStatic({ root: "./public" }));
|
|
21445
22017
|
var port = parseInt(process.env.PORT ?? "19199", 10);
|
|
21446
22018
|
var hostname = process.env.HOSTNAME ?? "127.0.0.1";
|
|
21447
|
-
var httpServer = serve({ fetch:
|
|
22019
|
+
var httpServer = serve({ fetch: app38.fetch, port, hostname });
|
|
21448
22020
|
console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
21449
22021
|
var SUBAPP_MANIFEST = [
|
|
21450
22022
|
{ prefix: "/api/health", file: "server/routes/health.ts", subapp: health_default },
|
|
@@ -21464,7 +22036,7 @@ for (const m of SUBAPP_MANIFEST) {
|
|
|
21464
22036
|
}
|
|
21465
22037
|
try {
|
|
21466
22038
|
const registered = [];
|
|
21467
|
-
for (const r of
|
|
22039
|
+
for (const r of app38.routes ?? []) {
|
|
21468
22040
|
if (typeof r.path !== "string" || r.path.includes(":") || r.path.includes("*")) continue;
|
|
21469
22041
|
if (AGENT_SLUG_PATTERN.test(r.path)) {
|
|
21470
22042
|
registered.push({ method: (r.method ?? "ALL").toUpperCase(), path: r.path });
|
|
@@ -21478,8 +22050,8 @@ try {
|
|
|
21478
22050
|
(async () => {
|
|
21479
22051
|
try {
|
|
21480
22052
|
let userId = "";
|
|
21481
|
-
if (
|
|
21482
|
-
const users = JSON.parse(
|
|
22053
|
+
if (existsSync25(USERS_FILE)) {
|
|
22054
|
+
const users = JSON.parse(readFileSync26(USERS_FILE, "utf-8").trim() || "[]");
|
|
21483
22055
|
userId = users[0]?.userId ?? "";
|
|
21484
22056
|
}
|
|
21485
22057
|
await backfillNullUserIdConversations(userId);
|
|
@@ -21505,7 +22077,7 @@ if (bootAccountConfig?.whatsapp) {
|
|
|
21505
22077
|
}
|
|
21506
22078
|
init({
|
|
21507
22079
|
configDir: configDirForWhatsApp,
|
|
21508
|
-
platformRoot:
|
|
22080
|
+
platformRoot: resolve28(process.env.MAXY_PLATFORM_ROOT ?? join13(__dirname, "..")),
|
|
21509
22081
|
accountConfig: bootAccountConfig,
|
|
21510
22082
|
onMessage: async (msg) => {
|
|
21511
22083
|
try {
|