@rubytech/create-realagent 1.0.795 → 1.0.798
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/package.json +1 -1
- package/payload/platform/neo4j/migrations/003-person-name-eradicate.cypher +24 -0
- package/payload/platform/neo4j/schema.cypher +34 -0
- package/payload/platform/plugins/admin/PLUGIN.md +4 -0
- package/payload/platform/plugins/admin/hooks/pre-tool-use.sh +9 -5
- package/payload/platform/plugins/admin/mcp/dist/index.js +54 -18
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/docs/references/internals.md +8 -0
- package/payload/platform/plugins/docs/references/settings.md +6 -0
- package/payload/platform/plugins/memory/PLUGIN.md +4 -2
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js +11 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js +124 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts +12 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js +41 -2
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.d.ts +9 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.js +44 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write.test.js +20 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js.map +1 -1
- package/payload/platform/plugins/memory/references/schema-base.md +11 -1
- package/payload/platform/plugins/whatsapp/mcp/dist/call-api.d.ts +14 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/call-api.d.ts.map +1 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/call-api.js +42 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/call-api.js.map +1 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js +50 -19
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
- package/payload/platform/scripts/logs-read.sh +17 -12
- package/payload/platform/templates/agents/admin/IDENTITY.md +6 -0
- package/payload/server/chunk-BURNRCKP.js +3405 -0
- package/payload/server/chunk-JSBRDJBE.js +30 -0
- package/payload/server/chunk-KM23Y7SY.js +9896 -0
- package/payload/server/chunk-SBHI2NMF.js +9910 -0
- package/payload/server/chunk-WHF6YXJ5.js +3456 -0
- package/payload/server/client-pool-4YDRTKAT.js +29 -0
- package/payload/server/client-pool-PV45NUTN.js +29 -0
- package/payload/server/maxy-edge.js +3 -2
- package/payload/server/neo4j-migrations-IUSBODOP.js +51 -0
- package/payload/server/public/assets/admin-C0lKk6WM.js +352 -0
- package/payload/server/public/assets/data-BvV94XHO.js +1 -0
- package/payload/server/public/assets/graph-CBu0rtrP.js +1 -0
- package/payload/server/public/assets/page-BNM63zsb.js +50 -0
- package/payload/server/public/assets/page-DM19J3ur.js +1 -0
- package/payload/server/public/assets/useAdminFetch-iYCQ9lT0.js +1 -0
- package/payload/server/public/data.html +3 -3
- package/payload/server/public/graph.html +3 -3
- package/payload/server/public/index.html +4 -4
- package/payload/server/server.js +470 -137
- package/payload/server/public/assets/admin-D8wbpnrW.js +0 -352
- package/payload/server/public/assets/data-BhrQjgR5.js +0 -1
- package/payload/server/public/assets/graph-Jj7seS-w.js +0 -1
- package/payload/server/public/assets/page-DIG7s5Jp.js +0 -1
- package/payload/server/public/assets/page-sZb3wcOM.js +0 -50
- package/payload/server/public/assets/share-2-BndjMKeG.js +0 -1
package/payload/server/server.js
CHANGED
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
setRemotePassword,
|
|
43
43
|
sleep,
|
|
44
44
|
streamLogPathFor,
|
|
45
|
+
stripAttachmentMetaSuffix,
|
|
45
46
|
validateKey,
|
|
46
47
|
validatePasswordStrength,
|
|
47
48
|
verifyPassword,
|
|
@@ -49,14 +50,12 @@ import {
|
|
|
49
50
|
vncLog,
|
|
50
51
|
waitForExit,
|
|
51
52
|
writeChromiumWrapper
|
|
52
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-SBHI2NMF.js";
|
|
53
54
|
import {
|
|
54
55
|
ACCOUNTS_DIR,
|
|
55
56
|
GREETING_DIRECTIVE,
|
|
56
57
|
HAIKU_MODEL,
|
|
57
58
|
PLATFORM_ROOT,
|
|
58
|
-
__commonJS,
|
|
59
|
-
__toESM,
|
|
60
59
|
agentLogStream,
|
|
61
60
|
backfillNullUserIdConversations,
|
|
62
61
|
bindVisitorToGroup,
|
|
@@ -103,6 +102,7 @@ import {
|
|
|
103
102
|
resolveAgentConfig,
|
|
104
103
|
resolveDefaultAgentSlug,
|
|
105
104
|
resolveUserAccounts,
|
|
105
|
+
runBootMigrations,
|
|
106
106
|
setAgentSessionId,
|
|
107
107
|
setConversationIdForSession,
|
|
108
108
|
setGroupContextForSession,
|
|
@@ -113,7 +113,11 @@ import {
|
|
|
113
113
|
verifyAndGetConversationUpdatedAt,
|
|
114
114
|
verifyConversationOwnership,
|
|
115
115
|
writeAdminUserAndPerson
|
|
116
|
-
} from "./chunk-
|
|
116
|
+
} from "./chunk-WHF6YXJ5.js";
|
|
117
|
+
import {
|
|
118
|
+
__commonJS,
|
|
119
|
+
__toESM
|
|
120
|
+
} from "./chunk-JSBRDJBE.js";
|
|
117
121
|
|
|
118
122
|
// ../lib/graph-trash/dist/index.js
|
|
119
123
|
var require_dist = __commonJS({
|
|
@@ -612,8 +616,8 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
612
616
|
};
|
|
613
617
|
|
|
614
618
|
// server/index.ts
|
|
615
|
-
import { readFileSync as
|
|
616
|
-
import { resolve as
|
|
619
|
+
import { readFileSync as readFileSync17, existsSync as existsSync24, watchFile } from "fs";
|
|
620
|
+
import { resolve as resolve24, join as join10, basename as basename7 } from "path";
|
|
617
621
|
import { homedir as homedir2 } from "os";
|
|
618
622
|
|
|
619
623
|
// app/lib/agent-slug-pattern.ts
|
|
@@ -2193,9 +2197,10 @@ var WhatsAppAccountSchema = z.object({
|
|
|
2193
2197
|
groups: z.record(
|
|
2194
2198
|
z.string(),
|
|
2195
2199
|
z.object({
|
|
2196
|
-
activation: z.enum(["always", "mention", "off"]).optional()
|
|
2200
|
+
activation: z.enum(["always", "mention", "off"]).optional(),
|
|
2201
|
+
publicAgent: z.string().optional().describe("Per-group public-agent slug override. When set, inbound messages in this group route to this agent instead of the per-account or top-level publicAgent.")
|
|
2197
2202
|
}).strict().optional()
|
|
2198
|
-
).optional().describe("Per-group settings
|
|
2203
|
+
).optional().describe("Per-group settings: 'activation' controls when the agent responds ('always', 'mention', 'off'); 'publicAgent' optionally overrides the public-agent slug for this group (precedence: group \u2192 account \u2192 top-level)."),
|
|
2199
2204
|
ackReaction: z.object({
|
|
2200
2205
|
emoji: z.string().optional(),
|
|
2201
2206
|
direct: z.boolean().optional().default(true),
|
|
@@ -2449,20 +2454,107 @@ function setPublicAgent(accountDir, slug) {
|
|
|
2449
2454
|
return { ok: false, error: msg };
|
|
2450
2455
|
}
|
|
2451
2456
|
}
|
|
2452
|
-
function
|
|
2457
|
+
function resolvePublicAgent(accountDir, opts) {
|
|
2453
2458
|
try {
|
|
2454
2459
|
const config = readConfig(accountDir);
|
|
2455
2460
|
const wa = config.whatsapp;
|
|
2456
2461
|
if (!wa) return null;
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2462
|
+
if (opts.groupJid) {
|
|
2463
|
+
const accounts2 = wa.accounts;
|
|
2464
|
+
const account2 = accounts2?.[opts.accountId];
|
|
2465
|
+
const groups = account2?.groups;
|
|
2466
|
+
const group = groups?.[opts.groupJid];
|
|
2467
|
+
const groupSlug = group?.publicAgent;
|
|
2468
|
+
if (typeof groupSlug === "string" && groupSlug.trim()) {
|
|
2469
|
+
return { slug: groupSlug.trim(), source: "group" };
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
const accounts = wa.accounts;
|
|
2473
|
+
const account = accounts?.[opts.accountId];
|
|
2474
|
+
const accountSlug = account?.publicAgent;
|
|
2475
|
+
if (typeof accountSlug === "string" && accountSlug.trim()) {
|
|
2476
|
+
return { slug: accountSlug.trim(), source: "account" };
|
|
2477
|
+
}
|
|
2478
|
+
const topSlug = wa.publicAgent;
|
|
2479
|
+
if (typeof topSlug === "string" && topSlug.trim()) {
|
|
2480
|
+
return { slug: topSlug.trim(), source: "top-level" };
|
|
2460
2481
|
}
|
|
2461
2482
|
return null;
|
|
2462
2483
|
} catch {
|
|
2463
2484
|
return null;
|
|
2464
2485
|
}
|
|
2465
2486
|
}
|
|
2487
|
+
function setGroupPublicAgent(accountDir, accountId, groupJid, slug) {
|
|
2488
|
+
const trimmedSlug = slug.trim();
|
|
2489
|
+
const trimmedGroup = groupJid.trim();
|
|
2490
|
+
const trimmedAccount = accountId.trim();
|
|
2491
|
+
if (!trimmedSlug) return { ok: false, error: "Agent slug cannot be empty." };
|
|
2492
|
+
if (!trimmedGroup) return { ok: false, error: "Group JID cannot be empty." };
|
|
2493
|
+
if (!trimmedAccount) return { ok: false, error: "Account ID cannot be empty." };
|
|
2494
|
+
const agentConfigPath = join3(accountDir, "agents", trimmedSlug, "config.json");
|
|
2495
|
+
if (!existsSync5(agentConfigPath)) {
|
|
2496
|
+
return { ok: false, error: `Agent "${trimmedSlug}" not found \u2014 no config.json at ${agentConfigPath}. Check the agent slug and try again.` };
|
|
2497
|
+
}
|
|
2498
|
+
try {
|
|
2499
|
+
const config = readConfig(accountDir);
|
|
2500
|
+
if (!config.whatsapp || typeof config.whatsapp !== "object") config.whatsapp = {};
|
|
2501
|
+
const wa = config.whatsapp;
|
|
2502
|
+
if (!wa.accounts || typeof wa.accounts !== "object") wa.accounts = {};
|
|
2503
|
+
const accounts = wa.accounts;
|
|
2504
|
+
if (!accounts[trimmedAccount] || typeof accounts[trimmedAccount] !== "object") accounts[trimmedAccount] = {};
|
|
2505
|
+
const account = accounts[trimmedAccount];
|
|
2506
|
+
if (!account.groups || typeof account.groups !== "object") account.groups = {};
|
|
2507
|
+
const groups = account.groups;
|
|
2508
|
+
const existing = groups[trimmedGroup] && typeof groups[trimmedGroup] === "object" ? groups[trimmedGroup] : {};
|
|
2509
|
+
groups[trimmedGroup] = { ...existing, publicAgent: trimmedSlug };
|
|
2510
|
+
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
2511
|
+
if (!parsed.success) {
|
|
2512
|
+
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
2513
|
+
return { ok: false, error: `Validation failed: ${msg}` };
|
|
2514
|
+
}
|
|
2515
|
+
config.whatsapp = parsed.data;
|
|
2516
|
+
writeConfig(accountDir, config);
|
|
2517
|
+
console.error(`${TAG} setGroupPublicAgent account=${trimmedAccount} groupJid=${trimmedGroup} slug=${trimmedSlug}`);
|
|
2518
|
+
reloadManagerConfig(accountDir);
|
|
2519
|
+
return { ok: true, message: `Per-group public agent set to "${trimmedSlug}" for group ${trimmedGroup}.` };
|
|
2520
|
+
} catch (err) {
|
|
2521
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2522
|
+
console.error(`${TAG} setGroupPublicAgent failed: ${msg}`);
|
|
2523
|
+
return { ok: false, error: msg };
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
function unsetGroupPublicAgent(accountDir, accountId, groupJid) {
|
|
2527
|
+
const trimmedGroup = groupJid.trim();
|
|
2528
|
+
const trimmedAccount = accountId.trim();
|
|
2529
|
+
if (!trimmedGroup) return { ok: false, error: "Group JID cannot be empty." };
|
|
2530
|
+
if (!trimmedAccount) return { ok: false, error: "Account ID cannot be empty." };
|
|
2531
|
+
try {
|
|
2532
|
+
const config = readConfig(accountDir);
|
|
2533
|
+
const wa = config.whatsapp ?? {};
|
|
2534
|
+
const accounts = wa.accounts;
|
|
2535
|
+
const account = accounts?.[trimmedAccount];
|
|
2536
|
+
const groups = account?.groups;
|
|
2537
|
+
const group = groups?.[trimmedGroup];
|
|
2538
|
+
if (!group || typeof group.publicAgent !== "string") {
|
|
2539
|
+
return { ok: true, message: `No per-group publicAgent override for group ${trimmedGroup}; nothing to remove.` };
|
|
2540
|
+
}
|
|
2541
|
+
delete group.publicAgent;
|
|
2542
|
+
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
2543
|
+
if (!parsed.success) {
|
|
2544
|
+
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
2545
|
+
return { ok: false, error: `Validation failed: ${msg}` };
|
|
2546
|
+
}
|
|
2547
|
+
config.whatsapp = parsed.data;
|
|
2548
|
+
writeConfig(accountDir, config);
|
|
2549
|
+
console.error(`${TAG} unsetGroupPublicAgent account=${trimmedAccount} groupJid=${trimmedGroup}`);
|
|
2550
|
+
reloadManagerConfig(accountDir);
|
|
2551
|
+
return { ok: true, message: `Per-group public agent override removed for group ${trimmedGroup}.` };
|
|
2552
|
+
} catch (err) {
|
|
2553
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2554
|
+
console.error(`${TAG} unsetGroupPublicAgent failed: ${msg}`);
|
|
2555
|
+
return { ok: false, error: msg };
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2466
2558
|
function updateConfig(accountDir, fields) {
|
|
2467
2559
|
const fieldNames = Object.keys(fields);
|
|
2468
2560
|
if (fieldNames.length === 0) {
|
|
@@ -2698,7 +2790,7 @@ var credsSaveQueue = Promise.resolve();
|
|
|
2698
2790
|
async function drainCredsSaveQueue(timeoutMs = 5e3) {
|
|
2699
2791
|
console.error(`${TAG3} draining credential save queue\u2026`);
|
|
2700
2792
|
const timer2 = new Promise(
|
|
2701
|
-
(
|
|
2793
|
+
(resolve25) => setTimeout(() => resolve25("timeout"), timeoutMs)
|
|
2702
2794
|
);
|
|
2703
2795
|
const result = await Promise.race([
|
|
2704
2796
|
credsSaveQueue.then(() => "drained"),
|
|
@@ -2826,11 +2918,11 @@ async function createWaSocket(opts) {
|
|
|
2826
2918
|
return sock;
|
|
2827
2919
|
}
|
|
2828
2920
|
async function waitForConnection(sock) {
|
|
2829
|
-
return new Promise((
|
|
2921
|
+
return new Promise((resolve25, reject) => {
|
|
2830
2922
|
const handler = (update) => {
|
|
2831
2923
|
if (update.connection === "open") {
|
|
2832
2924
|
sock.ev.off("connection.update", handler);
|
|
2833
|
-
|
|
2925
|
+
resolve25();
|
|
2834
2926
|
}
|
|
2835
2927
|
if (update.connection === "close") {
|
|
2836
2928
|
sock.ev.off("connection.update", handler);
|
|
@@ -2944,14 +3036,14 @@ ${inspected}`;
|
|
|
2944
3036
|
return inspect2(err, INSPECT_OPTS2);
|
|
2945
3037
|
}
|
|
2946
3038
|
function withTimeout(label, promise, timeoutMs) {
|
|
2947
|
-
return new Promise((
|
|
3039
|
+
return new Promise((resolve25, reject) => {
|
|
2948
3040
|
const timer2 = setTimeout(() => {
|
|
2949
3041
|
reject(new Error(`${label} timed out after ${timeoutMs}ms`));
|
|
2950
3042
|
}, timeoutMs);
|
|
2951
3043
|
promise.then(
|
|
2952
3044
|
(value) => {
|
|
2953
3045
|
clearTimeout(timer2);
|
|
2954
|
-
|
|
3046
|
+
resolve25(value);
|
|
2955
3047
|
},
|
|
2956
3048
|
(err) => {
|
|
2957
3049
|
clearTimeout(timer2);
|
|
@@ -4165,11 +4257,11 @@ async function connectWithReconnect(conn) {
|
|
|
4165
4257
|
console.error(
|
|
4166
4258
|
`${TAG11} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
|
|
4167
4259
|
);
|
|
4168
|
-
await new Promise((
|
|
4169
|
-
const timer2 = setTimeout(
|
|
4260
|
+
await new Promise((resolve25) => {
|
|
4261
|
+
const timer2 = setTimeout(resolve25, delay);
|
|
4170
4262
|
conn.abortController.signal.addEventListener("abort", () => {
|
|
4171
4263
|
clearTimeout(timer2);
|
|
4172
|
-
|
|
4264
|
+
resolve25();
|
|
4173
4265
|
}, { once: true });
|
|
4174
4266
|
});
|
|
4175
4267
|
}
|
|
@@ -4177,16 +4269,16 @@ async function connectWithReconnect(conn) {
|
|
|
4177
4269
|
}
|
|
4178
4270
|
}
|
|
4179
4271
|
function waitForDisconnectEvent(conn) {
|
|
4180
|
-
return new Promise((
|
|
4272
|
+
return new Promise((resolve25) => {
|
|
4181
4273
|
if (!conn.sock) {
|
|
4182
|
-
|
|
4274
|
+
resolve25();
|
|
4183
4275
|
return;
|
|
4184
4276
|
}
|
|
4185
4277
|
const sock = conn.sock;
|
|
4186
4278
|
const handler = (update) => {
|
|
4187
4279
|
if (update.connection === "close") {
|
|
4188
4280
|
sock.ev.off("connection.update", handler);
|
|
4189
|
-
|
|
4281
|
+
resolve25();
|
|
4190
4282
|
}
|
|
4191
4283
|
};
|
|
4192
4284
|
sock.ev.on("connection.update", handler);
|
|
@@ -4403,8 +4495,8 @@ async function handleInboundMessage(conn, msg) {
|
|
|
4403
4495
|
const conversationKey = isGroup ? remoteJid : senderPhone;
|
|
4404
4496
|
const debounceKey = `${conn.accountId}:${conversationKey}:${senderPhone}`;
|
|
4405
4497
|
let resolvePending;
|
|
4406
|
-
const sttPending = new Promise((
|
|
4407
|
-
resolvePending =
|
|
4498
|
+
const sttPending = new Promise((resolve25) => {
|
|
4499
|
+
resolvePending = resolve25;
|
|
4408
4500
|
});
|
|
4409
4501
|
if (conn.debouncer) conn.debouncer.registerPending(debounceKey, sttPending);
|
|
4410
4502
|
try {
|
|
@@ -4517,20 +4609,20 @@ async function probeApiKey() {
|
|
|
4517
4609
|
return result.status;
|
|
4518
4610
|
}
|
|
4519
4611
|
function checkPort(port2, timeoutMs = 500) {
|
|
4520
|
-
return new Promise((
|
|
4612
|
+
return new Promise((resolve25) => {
|
|
4521
4613
|
const socket = createConnection(port2, "127.0.0.1");
|
|
4522
4614
|
socket.setTimeout(timeoutMs);
|
|
4523
4615
|
socket.once("connect", () => {
|
|
4524
4616
|
socket.destroy();
|
|
4525
|
-
|
|
4617
|
+
resolve25(true);
|
|
4526
4618
|
});
|
|
4527
4619
|
socket.once("error", () => {
|
|
4528
4620
|
socket.destroy();
|
|
4529
|
-
|
|
4621
|
+
resolve25(false);
|
|
4530
4622
|
});
|
|
4531
4623
|
socket.once("timeout", () => {
|
|
4532
4624
|
socket.destroy();
|
|
4533
|
-
|
|
4625
|
+
resolve25(false);
|
|
4534
4626
|
});
|
|
4535
4627
|
});
|
|
4536
4628
|
}
|
|
@@ -4726,7 +4818,7 @@ app2.post("/", async (c) => {
|
|
|
4726
4818
|
}
|
|
4727
4819
|
const cookieHeader = c.req.header("cookie");
|
|
4728
4820
|
if (body.session_key && typeof body.session_key === "string") {
|
|
4729
|
-
if (validateSession(body.session_key, "public")) {
|
|
4821
|
+
if (validateSession(body.session_key, "public").ok) {
|
|
4730
4822
|
const storedAgent = getAgentNameForSession(body.session_key);
|
|
4731
4823
|
if (storedAgent && storedAgent !== agentSlug) {
|
|
4732
4824
|
console.log(`[session] hot-resume agent mismatch: session=${body.session_key.slice(0, 8)}\u2026 has=${storedAgent} requested=${agentSlug} \u2014 creating new session`);
|
|
@@ -5414,7 +5506,7 @@ app3.post("/", async (c) => {
|
|
|
5414
5506
|
if (!session_key) {
|
|
5415
5507
|
return c.json({ error: "session_key required" }, 400);
|
|
5416
5508
|
}
|
|
5417
|
-
if (!validateSession(session_key, "public")) {
|
|
5509
|
+
if (!validateSession(session_key, "public").ok) {
|
|
5418
5510
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
5419
5511
|
}
|
|
5420
5512
|
const files = formData.getAll("attachments");
|
|
@@ -5483,7 +5575,7 @@ app3.post("/", async (c) => {
|
|
|
5483
5575
|
if (!message) {
|
|
5484
5576
|
return c.json({ error: "message required" }, 400);
|
|
5485
5577
|
}
|
|
5486
|
-
if (!validateSession(session_key, "public")) {
|
|
5578
|
+
if (!validateSession(session_key, "public").ok) {
|
|
5487
5579
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
5488
5580
|
}
|
|
5489
5581
|
}
|
|
@@ -5623,7 +5715,7 @@ app4.get("/messages", async (c) => {
|
|
|
5623
5715
|
if (isNaN(sinceDate.getTime())) {
|
|
5624
5716
|
return c.json({ error: "Invalid since parameter \u2014 expected ISO 8601" }, 400);
|
|
5625
5717
|
}
|
|
5626
|
-
if (!validateSession(sessionKey, "public")) {
|
|
5718
|
+
if (!validateSession(sessionKey, "public").ok) {
|
|
5627
5719
|
return c.json({ error: "Session expired" }, 401);
|
|
5628
5720
|
}
|
|
5629
5721
|
const groupSlug = getGroupSlugForSession(sessionKey);
|
|
@@ -6247,7 +6339,7 @@ app5.post("/create-credentials", async (c) => {
|
|
|
6247
6339
|
const { session_key, password } = body;
|
|
6248
6340
|
if (!session_key || typeof session_key !== "string") return c.json({ error: "session_key required" }, 400);
|
|
6249
6341
|
if (!password || typeof password !== "string") return c.json({ error: "password required" }, 400);
|
|
6250
|
-
if (!validateSession(session_key, "public")) {
|
|
6342
|
+
if (!validateSession(session_key, "public").ok) {
|
|
6251
6343
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
6252
6344
|
}
|
|
6253
6345
|
const grant = getGrantForSession(session_key);
|
|
@@ -6743,8 +6835,8 @@ async function startLogin(opts) {
|
|
|
6743
6835
|
resetActiveLogin(accountId);
|
|
6744
6836
|
let resolveQr = null;
|
|
6745
6837
|
let rejectQr = null;
|
|
6746
|
-
const qrPromise = new Promise((
|
|
6747
|
-
resolveQr =
|
|
6838
|
+
const qrPromise = new Promise((resolve25, reject) => {
|
|
6839
|
+
resolveQr = resolve25;
|
|
6748
6840
|
rejectQr = reject;
|
|
6749
6841
|
});
|
|
6750
6842
|
const qrTimer = setTimeout(
|
|
@@ -7071,7 +7163,7 @@ app7.post("/send", async (c) => {
|
|
|
7071
7163
|
app7.post("/config", async (c) => {
|
|
7072
7164
|
try {
|
|
7073
7165
|
const body = await c.req.json().catch(() => ({}));
|
|
7074
|
-
const { action, phone, slug, fields, accountId } = body;
|
|
7166
|
+
const { action, phone, slug, groupJid, fields, accountId } = body;
|
|
7075
7167
|
if (!action || typeof action !== "string") {
|
|
7076
7168
|
return c.json({ ok: false, error: 'Missing required field "action".' }, 400);
|
|
7077
7169
|
}
|
|
@@ -7110,9 +7202,32 @@ app7.post("/config", async (c) => {
|
|
|
7110
7202
|
return c.json(result, result.ok ? 200 : 400);
|
|
7111
7203
|
}
|
|
7112
7204
|
case "get-public-agent": {
|
|
7113
|
-
const
|
|
7114
|
-
|
|
7115
|
-
|
|
7205
|
+
const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
|
|
7206
|
+
const targetGroup = typeof groupJid === "string" && groupJid.trim() ? groupJid.trim() : void 0;
|
|
7207
|
+
const resolved = resolvePublicAgent(account.accountDir, { accountId: targetAccount, groupJid: targetGroup });
|
|
7208
|
+
console.error(`${TAG16} config action=get-public-agent accountId=${targetAccount} groupJid=${targetGroup ?? "none"} slug=${resolved?.slug ?? "none"} source=${resolved?.source ?? "none"}`);
|
|
7209
|
+
return c.json({ ok: true, slug: resolved?.slug ?? null, source: resolved?.source ?? null });
|
|
7210
|
+
}
|
|
7211
|
+
case "set-group-public-agent": {
|
|
7212
|
+
if (!slug || typeof slug !== "string") {
|
|
7213
|
+
return c.json({ ok: false, error: 'Missing required field "slug" (the public-agent directory name, e.g. "my-agent").' }, 400);
|
|
7214
|
+
}
|
|
7215
|
+
if (!groupJid || typeof groupJid !== "string") {
|
|
7216
|
+
return c.json({ ok: false, error: 'Missing required field "groupJid" (group JID ending in @g.us).' }, 400);
|
|
7217
|
+
}
|
|
7218
|
+
const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
|
|
7219
|
+
const result = setGroupPublicAgent(account.accountDir, targetAccount, groupJid, slug);
|
|
7220
|
+
console.error(`${TAG16} config action=set-group-public-agent accountId=${targetAccount} groupJid=${groupJid} slug=${slug} ok=${result.ok}`);
|
|
7221
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
7222
|
+
}
|
|
7223
|
+
case "unset-group-public-agent": {
|
|
7224
|
+
if (!groupJid || typeof groupJid !== "string") {
|
|
7225
|
+
return c.json({ ok: false, error: 'Missing required field "groupJid" (group JID ending in @g.us).' }, 400);
|
|
7226
|
+
}
|
|
7227
|
+
const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
|
|
7228
|
+
const result = unsetGroupPublicAgent(account.accountDir, targetAccount, groupJid);
|
|
7229
|
+
console.error(`${TAG16} config action=unset-group-public-agent accountId=${targetAccount} groupJid=${groupJid} ok=${result.ok}`);
|
|
7230
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
7116
7231
|
}
|
|
7117
7232
|
case "list-public-agents": {
|
|
7118
7233
|
const agentsDir = resolve10(account.accountDir, "agents");
|
|
@@ -7180,7 +7295,7 @@ app7.post("/config", async (c) => {
|
|
|
7180
7295
|
}
|
|
7181
7296
|
default:
|
|
7182
7297
|
return c.json(
|
|
7183
|
-
{ ok: false, error: `Unknown action "${action}". Valid actions: add-admin-phone, remove-admin-phone, list-admin-phones, set-public-agent, get-public-agent, list-public-agents, update-config, get-config, schema, list-groups.` },
|
|
7298
|
+
{ ok: false, error: `Unknown action "${action}". Valid actions: add-admin-phone, remove-admin-phone, list-admin-phones, set-public-agent, get-public-agent, set-group-public-agent, unset-group-public-agent, list-public-agents, update-config, get-config, schema, list-groups.` },
|
|
7184
7299
|
400
|
|
7185
7300
|
);
|
|
7186
7301
|
}
|
|
@@ -7813,7 +7928,7 @@ async function createAdminSession(accountId, thinkingView, userId, userName, rol
|
|
|
7813
7928
|
var app10 = new Hono();
|
|
7814
7929
|
app10.get("/", async (c) => {
|
|
7815
7930
|
const sessionKey = c.req.query("session_key");
|
|
7816
|
-
if (!sessionKey || !validateSession(sessionKey, "admin")) {
|
|
7931
|
+
if (!sessionKey || !validateSession(sessionKey, "admin").ok) {
|
|
7817
7932
|
return c.json({ error: "Invalid or expired admin session" }, 401);
|
|
7818
7933
|
}
|
|
7819
7934
|
const accountId = getAccountIdForSession(sessionKey);
|
|
@@ -8210,7 +8325,7 @@ var app11 = new Hono();
|
|
|
8210
8325
|
app11.post("/cancel", requireAdminSession, async (c) => {
|
|
8211
8326
|
const session_key = c.var.sessionKey;
|
|
8212
8327
|
try {
|
|
8213
|
-
const { interruptClient: interruptClient2 } = await import("./client-pool-
|
|
8328
|
+
const { interruptClient: interruptClient2 } = await import("./client-pool-4YDRTKAT.js");
|
|
8214
8329
|
await interruptClient2(session_key);
|
|
8215
8330
|
return c.json({ ok: true });
|
|
8216
8331
|
} catch (err) {
|
|
@@ -8661,10 +8776,15 @@ app13.get("/", async (c) => {
|
|
|
8661
8776
|
const filePath = resolve13(dir, safe);
|
|
8662
8777
|
searched.push(filePath);
|
|
8663
8778
|
try {
|
|
8664
|
-
const
|
|
8665
|
-
const
|
|
8779
|
+
const buffer = readFileSync12(filePath);
|
|
8780
|
+
const onDiskBytes = statSync7(filePath).size;
|
|
8781
|
+
const headers = {
|
|
8782
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
8783
|
+
"Content-Length": String(buffer.byteLength)
|
|
8784
|
+
};
|
|
8666
8785
|
if (download) headers["Content-Disposition"] = `attachment; filename="${safe}"`;
|
|
8667
|
-
|
|
8786
|
+
console.info(`[stream-log-download] file=${safe} bytesServed=${buffer.byteLength} onDiskBytes=${onDiskBytes}`);
|
|
8787
|
+
return new Response(buffer, { headers });
|
|
8668
8788
|
} catch (err) {
|
|
8669
8789
|
const reason = err instanceof Error ? err.message : String(err);
|
|
8670
8790
|
console.debug(`[admin/logs] miss dir=${dir} name=${safe} reason=${reason}`);
|
|
@@ -8729,9 +8849,9 @@ app13.get("/", async (c) => {
|
|
|
8729
8849
|
const hit = hits[0];
|
|
8730
8850
|
console.info(`[admin/logs] resolved sessionKey=${sessionKeySlice} conversationId=${conversationIdSlice} shape=${hit.shape} stalePreflushCount=${stalePreflushCount}`);
|
|
8731
8851
|
try {
|
|
8732
|
-
const content = readFileSync12(hit.path, "utf-8");
|
|
8733
8852
|
const filename = basename5(hit.path);
|
|
8734
8853
|
if (stalePreflushCount > 0 && !download) {
|
|
8854
|
+
const content = readFileSync12(hit.path, "utf-8");
|
|
8735
8855
|
return c.json({
|
|
8736
8856
|
log: content,
|
|
8737
8857
|
filename,
|
|
@@ -8739,9 +8859,15 @@ app13.get("/", async (c) => {
|
|
|
8739
8859
|
warnings: stalePreflushPaths.map((path2) => ({ kind: "stale-preflush", path: path2 }))
|
|
8740
8860
|
});
|
|
8741
8861
|
}
|
|
8742
|
-
const
|
|
8862
|
+
const buffer = readFileSync12(hit.path);
|
|
8863
|
+
const onDiskBytes = statSync7(hit.path).size;
|
|
8864
|
+
const headers = {
|
|
8865
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
8866
|
+
"Content-Length": String(buffer.byteLength)
|
|
8867
|
+
};
|
|
8743
8868
|
if (download) headers["Content-Disposition"] = `attachment; filename="${filename}"`;
|
|
8744
|
-
|
|
8869
|
+
console.info(`[stream-log-download] file=${filename} bytesServed=${buffer.byteLength} onDiskBytes=${onDiskBytes}`);
|
|
8870
|
+
return new Response(buffer, { headers });
|
|
8745
8871
|
} catch (err) {
|
|
8746
8872
|
const reason2 = err instanceof Error ? err.message : String(err);
|
|
8747
8873
|
console.warn(`[admin/logs] read-fail path=${hit.path} reason=${reason2}`);
|
|
@@ -8953,7 +9079,35 @@ var agents_default = app16;
|
|
|
8953
9079
|
// server/routes/admin/sessions.ts
|
|
8954
9080
|
import crypto2 from "crypto";
|
|
8955
9081
|
import { resolve as resolvePath } from "path";
|
|
8956
|
-
import { appendFileSync as appendFileSync5 } from "fs";
|
|
9082
|
+
import { appendFileSync as appendFileSync5, existsSync as existsSync18 } from "fs";
|
|
9083
|
+
function validateAndShapeAttachments(raws, conversationAccountId, conversationId, messageId, streamLogPath) {
|
|
9084
|
+
const chips = [];
|
|
9085
|
+
let valid = 0;
|
|
9086
|
+
let invalid = 0;
|
|
9087
|
+
for (const a of raws) {
|
|
9088
|
+
let reason = null;
|
|
9089
|
+
if (!a.attachmentId || !a.filename || !a.mimeType || !a.storagePath) reason = "schema-fail";
|
|
9090
|
+
else if (a.accountId !== conversationAccountId) reason = "account-mismatch";
|
|
9091
|
+
else if (!existsSync18(a.storagePath)) reason = "missing-file";
|
|
9092
|
+
if (reason) {
|
|
9093
|
+
invalid++;
|
|
9094
|
+
try {
|
|
9095
|
+
appendFileSync5(streamLogPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] [attachment-rehydrate-invalid] conversationId=${conversationId.slice(0, 8)} messageId=${messageId.slice(0, 8)} attachmentId=${(a.attachmentId || "").slice(0, 8)} reason=${reason}
|
|
9096
|
+
`);
|
|
9097
|
+
} catch {
|
|
9098
|
+
}
|
|
9099
|
+
continue;
|
|
9100
|
+
}
|
|
9101
|
+
valid++;
|
|
9102
|
+
chips.push({
|
|
9103
|
+
attachmentId: a.attachmentId,
|
|
9104
|
+
filename: a.filename,
|
|
9105
|
+
mimeType: a.mimeType,
|
|
9106
|
+
sizeBytes: a.sizeBytes
|
|
9107
|
+
});
|
|
9108
|
+
}
|
|
9109
|
+
return { chips, valid, invalid };
|
|
9110
|
+
}
|
|
8957
9111
|
function reconstructAssistantEvents(content, components, conversationId, messageId, streamLogPath) {
|
|
8958
9112
|
if (components.length === 0) {
|
|
8959
9113
|
return {
|
|
@@ -9106,7 +9260,17 @@ app17.get("/:id/messages", requireAdminSession, async (c) => {
|
|
|
9106
9260
|
if (!owned) return c.json({ error: "Conversation not found" }, 404);
|
|
9107
9261
|
try {
|
|
9108
9262
|
const messages = await getRecentMessages(conversationId, 50);
|
|
9109
|
-
|
|
9263
|
+
const sanitised = messages.map((m) => ({
|
|
9264
|
+
...m,
|
|
9265
|
+
attachments: m.attachments.map((a) => ({
|
|
9266
|
+
attachmentId: a.attachmentId,
|
|
9267
|
+
filename: a.filename,
|
|
9268
|
+
mimeType: a.mimeType,
|
|
9269
|
+
sizeBytes: a.sizeBytes,
|
|
9270
|
+
ordinal: a.ordinal
|
|
9271
|
+
}))
|
|
9272
|
+
}));
|
|
9273
|
+
return c.json({ messages: sanitised });
|
|
9110
9274
|
} catch (err) {
|
|
9111
9275
|
console.error(`[sessions-messages] Failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
9112
9276
|
return c.json({ error: "Failed to fetch messages" }, 500);
|
|
@@ -9139,10 +9303,23 @@ app17.post("/:id/resume", requireAdminSession, async (c) => {
|
|
|
9139
9303
|
let totalValid = 0;
|
|
9140
9304
|
let totalInvalid = 0;
|
|
9141
9305
|
let totalComponents = 0;
|
|
9306
|
+
let totalAttachments = 0;
|
|
9307
|
+
let totalAttachmentInvalid = 0;
|
|
9142
9308
|
const rehydrated = messages.map((m) => {
|
|
9143
9309
|
const components = m.components ?? [];
|
|
9310
|
+
const rawAttachments = m.attachments ?? [];
|
|
9144
9311
|
if (m.role !== "assistant") {
|
|
9145
|
-
|
|
9312
|
+
const { chips, valid: valid2, invalid: invalid2 } = validateAndShapeAttachments(rawAttachments, accountId, conversationId, m.messageId, streamLogPath);
|
|
9313
|
+
totalAttachments += valid2;
|
|
9314
|
+
totalAttachmentInvalid += invalid2;
|
|
9315
|
+
const cleanedContent = chips.length > 0 ? stripAttachmentMetaSuffix(m.content) : m.content;
|
|
9316
|
+
return {
|
|
9317
|
+
messageId: m.messageId,
|
|
9318
|
+
role: m.role,
|
|
9319
|
+
content: cleanedContent,
|
|
9320
|
+
createdAt: m.createdAt,
|
|
9321
|
+
...chips.length > 0 ? { attachments: chips } : {}
|
|
9322
|
+
};
|
|
9146
9323
|
}
|
|
9147
9324
|
const { events, valid, invalid, submittedEventIndices } = reconstructAssistantEvents(m.content, components, conversationId, m.messageId, streamLogPath);
|
|
9148
9325
|
totalValid += valid;
|
|
@@ -9159,17 +9336,22 @@ app17.post("/:id/resume", requireAdminSession, async (c) => {
|
|
|
9159
9336
|
};
|
|
9160
9337
|
});
|
|
9161
9338
|
const textRuns = rehydrated.reduce((n, m) => n + (m.events?.filter((e) => e.type === "text").length ?? 0), 0);
|
|
9339
|
+
const userMessageCount = rehydrated.filter((m) => m.role !== "assistant").length;
|
|
9162
9340
|
try {
|
|
9163
|
-
appendFileSync5(streamLogPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] [admin-resume] sessionKey=${sessionKey.slice(0, 8)} conversationId=${conversationId.slice(0, 8)} ${tag} loadedMessages=${messages.length} componentCount=${totalComponents}
|
|
9341
|
+
appendFileSync5(streamLogPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] [admin-resume] sessionKey=${sessionKey.slice(0, 8)} conversationId=${conversationId.slice(0, 8)} ${tag} loadedMessages=${messages.length} componentCount=${totalComponents} userAttachmentCount=${totalAttachments}
|
|
9164
9342
|
`);
|
|
9165
9343
|
if (totalComponents > 0) {
|
|
9166
9344
|
appendFileSync5(streamLogPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] [component-rehydrate] conversationId=${conversationId.slice(0, 8)} count=${totalComponents} valid=${totalValid} invalid=${totalInvalid} textRuns=${textRuns}
|
|
9345
|
+
`);
|
|
9346
|
+
}
|
|
9347
|
+
if (totalAttachments > 0 || totalAttachmentInvalid > 0) {
|
|
9348
|
+
appendFileSync5(streamLogPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] [attachment-rehydrate] conversationId=${conversationId.slice(0, 8)} userMessages=${userMessageCount} attachments=${totalAttachments} invalid=${totalAttachmentInvalid}
|
|
9167
9349
|
`);
|
|
9168
9350
|
}
|
|
9169
9351
|
} catch {
|
|
9170
9352
|
}
|
|
9171
9353
|
const age = formatAge(updatedAt);
|
|
9172
|
-
console.log(`[admin-resume] ${(/* @__PURE__ */ new Date()).toISOString()} conversationId=${conversationId.slice(0, 8)}\u2026 age=${age} loaded=${messages.length} messages ${tag} components=${totalComponents} sessionKey=${sessionKey.slice(0, 8)}\u2026`);
|
|
9354
|
+
console.log(`[admin-resume] ${(/* @__PURE__ */ new Date()).toISOString()} conversationId=${conversationId.slice(0, 8)}\u2026 age=${age} loaded=${messages.length} messages ${tag} components=${totalComponents} attachments=${totalAttachments} sessionKey=${sessionKey.slice(0, 8)}\u2026`);
|
|
9173
9355
|
return c.json({ conversationId, messages: rehydrated });
|
|
9174
9356
|
});
|
|
9175
9357
|
app17.post("/:id/label", requireAdminSession, async (c) => {
|
|
@@ -9444,12 +9626,12 @@ function isValidDomain(value) {
|
|
|
9444
9626
|
}
|
|
9445
9627
|
|
|
9446
9628
|
// app/lib/alias-domains.ts
|
|
9447
|
-
import { existsSync as
|
|
9629
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync8, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "fs";
|
|
9448
9630
|
import { dirname as dirname7 } from "path";
|
|
9449
9631
|
import { resolve as resolve16 } from "path";
|
|
9450
9632
|
var ALIAS_DOMAINS_PATH = resolve16(MAXY_DIR, "alias-domains.json");
|
|
9451
9633
|
function readExisting() {
|
|
9452
|
-
if (!
|
|
9634
|
+
if (!existsSync19(ALIAS_DOMAINS_PATH)) return /* @__PURE__ */ new Set();
|
|
9453
9635
|
try {
|
|
9454
9636
|
const parsed = JSON.parse(readFileSync14(ALIAS_DOMAINS_PATH, "utf-8"));
|
|
9455
9637
|
if (!Array.isArray(parsed)) return /* @__PURE__ */ new Set();
|
|
@@ -11786,7 +11968,7 @@ var adherence_default = app30;
|
|
|
11786
11968
|
import neo4j3 from "neo4j-driver";
|
|
11787
11969
|
import { readFile as readFile5, readdir as readdir3, stat as stat5 } from "fs/promises";
|
|
11788
11970
|
import { resolve as resolve20, relative as relative2, isAbsolute } from "path";
|
|
11789
|
-
import { existsSync as
|
|
11971
|
+
import { existsSync as existsSync20 } from "fs";
|
|
11790
11972
|
var LIMIT = 50;
|
|
11791
11973
|
var TEXT_MIME_PREFIXES = ["text/", "application/json", "application/markdown"];
|
|
11792
11974
|
var ADMIN_AGENT_FILES = ["IDENTITY.md", "SOUL.md", "KNOWLEDGE.md"];
|
|
@@ -11934,7 +12116,7 @@ async function fetchAgentTemplateRows(accountDir) {
|
|
|
11934
12116
|
async function unionSpecialistFilenames(overrideDir, bundledDir) {
|
|
11935
12117
|
const names = /* @__PURE__ */ new Set();
|
|
11936
12118
|
for (const dir of [overrideDir, bundledDir]) {
|
|
11937
|
-
if (!
|
|
12119
|
+
if (!existsSync20(dir)) continue;
|
|
11938
12120
|
try {
|
|
11939
12121
|
const entries = await readdir3(dir);
|
|
11940
12122
|
for (const entry of entries) {
|
|
@@ -11949,7 +12131,7 @@ async function unionSpecialistFilenames(overrideDir, bundledDir) {
|
|
|
11949
12131
|
}
|
|
11950
12132
|
async function readAgentTemplateRow(inp) {
|
|
11951
12133
|
let chosenPath = null;
|
|
11952
|
-
if (
|
|
12134
|
+
if (existsSync20(inp.overridePath)) {
|
|
11953
12135
|
try {
|
|
11954
12136
|
validateFilePathInAccount(inp.overridePath, inp.overrideRoot);
|
|
11955
12137
|
chosenPath = inp.overridePath;
|
|
@@ -11960,7 +12142,7 @@ async function readAgentTemplateRow(inp) {
|
|
|
11960
12142
|
);
|
|
11961
12143
|
return null;
|
|
11962
12144
|
}
|
|
11963
|
-
} else if (
|
|
12145
|
+
} else if (existsSync20(inp.bundledPath)) {
|
|
11964
12146
|
if (!isWithin(inp.bundledPath, inp.bundledRoot)) {
|
|
11965
12147
|
console.error(
|
|
11966
12148
|
`[admin/sidebar-artefacts] agent-template-read-failed agent=${inp.displayName} kind=${inp.logName} error="bundled path outside PLATFORM_ROOT"`
|
|
@@ -12002,7 +12184,7 @@ var sidebar_artefacts_default = app31;
|
|
|
12002
12184
|
// server/routes/admin/sidebar-artefact-save.ts
|
|
12003
12185
|
import { mkdir as mkdir4, readdir as readdir4, stat as stat6, writeFile as writeFile5 } from "fs/promises";
|
|
12004
12186
|
import { resolve as resolve21 } from "path";
|
|
12005
|
-
import { existsSync as
|
|
12187
|
+
import { existsSync as existsSync21 } from "fs";
|
|
12006
12188
|
var ADMIN_AGENT_FILES2 = /* @__PURE__ */ new Set(["IDENTITY.md", "SOUL.md", "KNOWLEDGE.md"]);
|
|
12007
12189
|
var UUID_RE4 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
12008
12190
|
var app32 = new Hono();
|
|
@@ -12066,7 +12248,7 @@ async function resolveSavePath(id, accountId, accountDir) {
|
|
|
12066
12248
|
}
|
|
12067
12249
|
if (UUID_RE4.test(id)) {
|
|
12068
12250
|
const dir = resolve21(ATTACHMENTS_ROOT, accountId, id);
|
|
12069
|
-
if (!
|
|
12251
|
+
if (!existsSync21(dir)) {
|
|
12070
12252
|
return { kind: "reject", status: 400, reason: "not-found" };
|
|
12071
12253
|
}
|
|
12072
12254
|
try {
|
|
@@ -12090,7 +12272,7 @@ var sidebar_artefact_save_default = app32;
|
|
|
12090
12272
|
|
|
12091
12273
|
// server/routes/admin/sidebar-artefact-content.ts
|
|
12092
12274
|
import { readFile as readFile6, readdir as readdir5 } from "fs/promises";
|
|
12093
|
-
import { existsSync as
|
|
12275
|
+
import { existsSync as existsSync22 } from "fs";
|
|
12094
12276
|
import { resolve as resolve22 } from "path";
|
|
12095
12277
|
var UUID_RE5 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
12096
12278
|
var app33 = new Hono();
|
|
@@ -12104,7 +12286,7 @@ app33.get("/", requireAdminSession, async (c) => {
|
|
|
12104
12286
|
return new Response("Not found", { status: 404 });
|
|
12105
12287
|
}
|
|
12106
12288
|
const dir = resolve22(ATTACHMENTS_ROOT, accountId, id);
|
|
12107
|
-
if (!
|
|
12289
|
+
if (!existsSync22(dir)) {
|
|
12108
12290
|
console.error(`[admin/sidebar-artefact-content] not-found id=${id.slice(0, 8)}`);
|
|
12109
12291
|
return new Response("Not found", { status: 404 });
|
|
12110
12292
|
}
|
|
@@ -12165,6 +12347,140 @@ app34.route("/sidebar-artefact-save", sidebar_artefact_save_default);
|
|
|
12165
12347
|
app34.route("/sidebar-artefact-content", sidebar_artefact_content_default);
|
|
12166
12348
|
var admin_default = app34;
|
|
12167
12349
|
|
|
12350
|
+
// server/routes/sites.ts
|
|
12351
|
+
import { existsSync as existsSync23, readFileSync as readFileSync16, realpathSync as realpathSync5, statSync as statSync8 } from "fs";
|
|
12352
|
+
import { resolve as resolve23 } from "path";
|
|
12353
|
+
var SAFE_SEG_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
|
|
12354
|
+
var MIME = {
|
|
12355
|
+
".html": "text/html; charset=utf-8",
|
|
12356
|
+
".htm": "text/html; charset=utf-8",
|
|
12357
|
+
".css": "text/css; charset=utf-8",
|
|
12358
|
+
".js": "text/javascript; charset=utf-8",
|
|
12359
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
12360
|
+
".json": "application/json; charset=utf-8",
|
|
12361
|
+
".txt": "text/plain; charset=utf-8",
|
|
12362
|
+
".md": "text/plain; charset=utf-8",
|
|
12363
|
+
".png": "image/png",
|
|
12364
|
+
".jpg": "image/jpeg",
|
|
12365
|
+
".jpeg": "image/jpeg",
|
|
12366
|
+
".gif": "image/gif",
|
|
12367
|
+
".webp": "image/webp",
|
|
12368
|
+
".svg": "image/svg+xml",
|
|
12369
|
+
".ico": "image/x-icon",
|
|
12370
|
+
".woff": "font/woff",
|
|
12371
|
+
".woff2": "font/woff2",
|
|
12372
|
+
".ttf": "font/ttf",
|
|
12373
|
+
".otf": "font/otf"
|
|
12374
|
+
};
|
|
12375
|
+
var HTML_EXTS = /* @__PURE__ */ new Set([".html", ".htm"]);
|
|
12376
|
+
var CSP_HTML = "default-src 'self' https: data:; script-src 'none'";
|
|
12377
|
+
function getExt(p) {
|
|
12378
|
+
const idx = p.lastIndexOf(".");
|
|
12379
|
+
if (idx < 0) return "";
|
|
12380
|
+
if (idx < p.lastIndexOf("/")) return "";
|
|
12381
|
+
return p.slice(idx).toLowerCase();
|
|
12382
|
+
}
|
|
12383
|
+
var app35 = new Hono();
|
|
12384
|
+
app35.get("/:rel{.*}", (c) => {
|
|
12385
|
+
const reqPath = c.req.path;
|
|
12386
|
+
const rawRel = c.req.param("rel") ?? "";
|
|
12387
|
+
const trimmed = rawRel.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
12388
|
+
const isDirRequest = rawRel === "" || rawRel.endsWith("/") || reqPath.endsWith("/");
|
|
12389
|
+
const account = resolveAccount();
|
|
12390
|
+
if (!account) {
|
|
12391
|
+
console.error(`[sites] no-account path=${reqPath} status=404`);
|
|
12392
|
+
return c.text("Not found", 404);
|
|
12393
|
+
}
|
|
12394
|
+
const rawSegments = trimmed === "" ? [] : trimmed.split("/");
|
|
12395
|
+
const segments = [];
|
|
12396
|
+
for (const raw of rawSegments) {
|
|
12397
|
+
let seg;
|
|
12398
|
+
try {
|
|
12399
|
+
seg = decodeURIComponent(raw);
|
|
12400
|
+
} catch {
|
|
12401
|
+
console.error(`[sites] path-traversal-rejected path=${reqPath} reason=segment status=403`);
|
|
12402
|
+
return c.text("Forbidden", 403);
|
|
12403
|
+
}
|
|
12404
|
+
if (seg === ".." || seg === "." || !SAFE_SEG_RE.test(seg)) {
|
|
12405
|
+
console.error(`[sites] path-traversal-rejected path=${reqPath} reason=segment status=403`);
|
|
12406
|
+
return c.text("Forbidden", 403);
|
|
12407
|
+
}
|
|
12408
|
+
segments.push(seg);
|
|
12409
|
+
}
|
|
12410
|
+
const rootDir = resolve23(account.accountDir, "sites");
|
|
12411
|
+
let filePath = segments.length === 0 ? rootDir : resolve23(rootDir, ...segments);
|
|
12412
|
+
if (filePath !== rootDir && !filePath.startsWith(rootDir + "/")) {
|
|
12413
|
+
console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
|
|
12414
|
+
return c.text("Forbidden", 403);
|
|
12415
|
+
}
|
|
12416
|
+
let stat7;
|
|
12417
|
+
try {
|
|
12418
|
+
stat7 = existsSync23(filePath) ? statSync8(filePath) : null;
|
|
12419
|
+
} catch {
|
|
12420
|
+
stat7 = null;
|
|
12421
|
+
}
|
|
12422
|
+
if (stat7?.isDirectory()) {
|
|
12423
|
+
filePath = resolve23(filePath, "index.html");
|
|
12424
|
+
} else if (stat7 === null && isDirRequest) {
|
|
12425
|
+
filePath = resolve23(filePath, "index.html");
|
|
12426
|
+
}
|
|
12427
|
+
if (!filePath.startsWith(rootDir + "/")) {
|
|
12428
|
+
console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
|
|
12429
|
+
return c.text("Forbidden", 403);
|
|
12430
|
+
}
|
|
12431
|
+
if (!existsSync23(filePath)) {
|
|
12432
|
+
console.error(`[sites] not-found path=${reqPath} status=404`);
|
|
12433
|
+
return c.text("Not found", 404);
|
|
12434
|
+
}
|
|
12435
|
+
let realPath;
|
|
12436
|
+
let realRoot;
|
|
12437
|
+
try {
|
|
12438
|
+
realPath = realpathSync5(filePath);
|
|
12439
|
+
realRoot = realpathSync5(rootDir);
|
|
12440
|
+
} catch {
|
|
12441
|
+
console.error(`[sites] not-found path=${reqPath} status=404`);
|
|
12442
|
+
return c.text("Not found", 404);
|
|
12443
|
+
}
|
|
12444
|
+
if (realPath !== realRoot && !realPath.startsWith(realRoot + "/")) {
|
|
12445
|
+
console.error(`[sites] symlink-escape-rejected path=${reqPath} real=${realPath} status=403`);
|
|
12446
|
+
return c.text("Forbidden", 403);
|
|
12447
|
+
}
|
|
12448
|
+
let body;
|
|
12449
|
+
try {
|
|
12450
|
+
body = readFileSync16(realPath);
|
|
12451
|
+
} catch (err) {
|
|
12452
|
+
const code = err?.code;
|
|
12453
|
+
if (code === "EISDIR") {
|
|
12454
|
+
console.error(`[sites] not-found path=${reqPath} reason=is-directory status=404`);
|
|
12455
|
+
return c.text("Not found", 404);
|
|
12456
|
+
}
|
|
12457
|
+
if (code === "ENOENT") {
|
|
12458
|
+
console.error(`[sites] not-found path=${reqPath} status=404`);
|
|
12459
|
+
return c.text("Not found", 404);
|
|
12460
|
+
}
|
|
12461
|
+
throw err;
|
|
12462
|
+
}
|
|
12463
|
+
const ext = getExt(realPath);
|
|
12464
|
+
const contentType = MIME[ext] ?? "application/octet-stream";
|
|
12465
|
+
const isHtml = HTML_EXTS.has(ext);
|
|
12466
|
+
console.log(`[sites] serve path=${reqPath} ext=${ext || "(none)"} status=200 bytes=${body.length}`);
|
|
12467
|
+
const view = Uint8Array.from(body);
|
|
12468
|
+
if (isHtml) {
|
|
12469
|
+
return c.body(view, 200, {
|
|
12470
|
+
"Content-Type": contentType,
|
|
12471
|
+
"Cache-Control": "no-cache",
|
|
12472
|
+
"X-Content-Type-Options": "nosniff",
|
|
12473
|
+
"Content-Security-Policy": CSP_HTML
|
|
12474
|
+
});
|
|
12475
|
+
}
|
|
12476
|
+
return c.body(view, 200, {
|
|
12477
|
+
"Content-Type": contentType,
|
|
12478
|
+
"Cache-Control": "public, max-age=3600",
|
|
12479
|
+
"X-Content-Type-Options": "nosniff"
|
|
12480
|
+
});
|
|
12481
|
+
});
|
|
12482
|
+
var sites_default = app35;
|
|
12483
|
+
|
|
12168
12484
|
// app/lib/graph-health.ts
|
|
12169
12485
|
var HOUR_MS = 60 * 60 * 1e3;
|
|
12170
12486
|
var timer = null;
|
|
@@ -12265,12 +12581,12 @@ function clientFrom(c) {
|
|
|
12265
12581
|
var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
12266
12582
|
var BRAND_JSON_PATH = PLATFORM_ROOT7 ? join10(PLATFORM_ROOT7, "config", "brand.json") : "";
|
|
12267
12583
|
var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", domain: "getmaxy.com" };
|
|
12268
|
-
if (BRAND_JSON_PATH && !
|
|
12584
|
+
if (BRAND_JSON_PATH && !existsSync24(BRAND_JSON_PATH)) {
|
|
12269
12585
|
console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
|
|
12270
12586
|
}
|
|
12271
|
-
if (BRAND_JSON_PATH &&
|
|
12587
|
+
if (BRAND_JSON_PATH && existsSync24(BRAND_JSON_PATH)) {
|
|
12272
12588
|
try {
|
|
12273
|
-
const parsed = JSON.parse(
|
|
12589
|
+
const parsed = JSON.parse(readFileSync17(BRAND_JSON_PATH, "utf-8"));
|
|
12274
12590
|
BRAND = { ...BRAND, ...parsed };
|
|
12275
12591
|
} catch (err) {
|
|
12276
12592
|
console.error(`[brand] Failed to parse brand.json: ${err.message}`);
|
|
@@ -12292,8 +12608,8 @@ var brandLoginOpts = {
|
|
|
12292
12608
|
var ALIAS_DOMAINS_PATH2 = join10(homedir2(), BRAND.configDir, "alias-domains.json");
|
|
12293
12609
|
function loadAliasDomains() {
|
|
12294
12610
|
try {
|
|
12295
|
-
if (!
|
|
12296
|
-
const parsed = JSON.parse(
|
|
12611
|
+
if (!existsSync24(ALIAS_DOMAINS_PATH2)) return null;
|
|
12612
|
+
const parsed = JSON.parse(readFileSync17(ALIAS_DOMAINS_PATH2, "utf-8"));
|
|
12297
12613
|
if (!Array.isArray(parsed)) {
|
|
12298
12614
|
console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
|
|
12299
12615
|
return null;
|
|
@@ -12317,9 +12633,9 @@ watchFile(ALIAS_DOMAINS_PATH2, { interval: 2e3 }, () => {
|
|
|
12317
12633
|
function isPublicHost(host) {
|
|
12318
12634
|
return host.startsWith("public.") || aliasDomains.has(host);
|
|
12319
12635
|
}
|
|
12320
|
-
var
|
|
12321
|
-
|
|
12322
|
-
|
|
12636
|
+
var app36 = new Hono();
|
|
12637
|
+
app36.use("*", clientIpMiddleware);
|
|
12638
|
+
app36.use("*", async (c, next) => {
|
|
12323
12639
|
await next();
|
|
12324
12640
|
c.header("X-Content-Type-Options", "nosniff");
|
|
12325
12641
|
c.header("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
@@ -12339,10 +12655,11 @@ var PUBLIC_ALLOWED_PREFIXES = [
|
|
|
12339
12655
|
"/brand/",
|
|
12340
12656
|
"/agent-assets/",
|
|
12341
12657
|
"/generated/",
|
|
12342
|
-
"/g/"
|
|
12658
|
+
"/g/",
|
|
12659
|
+
"/sites/"
|
|
12343
12660
|
];
|
|
12344
12661
|
var PUBLIC_ALLOWED_EXACT = ["/favicon.ico"];
|
|
12345
|
-
|
|
12662
|
+
app36.use("*", async (c, next) => {
|
|
12346
12663
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
12347
12664
|
if (!isPublicHost(host)) {
|
|
12348
12665
|
await next();
|
|
@@ -12382,7 +12699,7 @@ function resolveRemoteAuthOpts() {
|
|
|
12382
12699
|
return brandLoginOpts;
|
|
12383
12700
|
}
|
|
12384
12701
|
var MAX_LOGIN_BODY = 8 * 1024;
|
|
12385
|
-
|
|
12702
|
+
app36.post("/__remote-auth/login", async (c) => {
|
|
12386
12703
|
const client = clientFrom(c);
|
|
12387
12704
|
const clientIp = client.ip || "unknown";
|
|
12388
12705
|
if (!requestIsTlsTerminated(c)) {
|
|
@@ -12426,7 +12743,7 @@ app35.post("/__remote-auth/login", async (c) => {
|
|
|
12426
12743
|
}
|
|
12427
12744
|
});
|
|
12428
12745
|
});
|
|
12429
|
-
|
|
12746
|
+
app36.get("/__remote-auth/logout", (c) => {
|
|
12430
12747
|
return new Response(null, {
|
|
12431
12748
|
status: 302,
|
|
12432
12749
|
headers: {
|
|
@@ -12436,7 +12753,7 @@ app35.get("/__remote-auth/logout", (c) => {
|
|
|
12436
12753
|
}
|
|
12437
12754
|
});
|
|
12438
12755
|
});
|
|
12439
|
-
|
|
12756
|
+
app36.post("/__remote-auth/change-password", async (c) => {
|
|
12440
12757
|
const client = clientFrom(c);
|
|
12441
12758
|
const clientIp = client.ip || "unknown";
|
|
12442
12759
|
const rateLimited = checkRateLimit(client);
|
|
@@ -12486,13 +12803,13 @@ app35.post("/__remote-auth/change-password", async (c) => {
|
|
|
12486
12803
|
return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), mode: "change", changeError: "Failed to save password", redirect }), 200);
|
|
12487
12804
|
}
|
|
12488
12805
|
});
|
|
12489
|
-
|
|
12806
|
+
app36.get("/__remote-auth/setup", (c) => {
|
|
12490
12807
|
if (isRemoteAuthConfigured()) {
|
|
12491
12808
|
return c.redirect("/");
|
|
12492
12809
|
}
|
|
12493
12810
|
return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), mode: "setup" }), 200);
|
|
12494
12811
|
});
|
|
12495
|
-
|
|
12812
|
+
app36.post("/__remote-auth/set-initial-password", async (c) => {
|
|
12496
12813
|
if (isRemoteAuthConfigured()) {
|
|
12497
12814
|
return c.redirect("/");
|
|
12498
12815
|
}
|
|
@@ -12528,17 +12845,17 @@ app35.post("/__remote-auth/set-initial-password", async (c) => {
|
|
|
12528
12845
|
return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), mode: "setup", setupError: "Failed to save password. Please try again." }), 200);
|
|
12529
12846
|
}
|
|
12530
12847
|
});
|
|
12531
|
-
|
|
12848
|
+
app36.get("/api/remote-auth/status", (c) => {
|
|
12532
12849
|
return c.json({ configured: isRemoteAuthConfigured() });
|
|
12533
12850
|
});
|
|
12534
|
-
|
|
12851
|
+
app36.post("/api/remote-auth/set-password", async (c) => {
|
|
12535
12852
|
let body;
|
|
12536
12853
|
try {
|
|
12537
12854
|
body = await c.req.json();
|
|
12538
12855
|
} catch {
|
|
12539
12856
|
return c.json({ error: "Invalid request" }, 400);
|
|
12540
12857
|
}
|
|
12541
|
-
if (!body.session_key || !validateSession(body.session_key, "admin")) {
|
|
12858
|
+
if (!body.session_key || !validateSession(body.session_key, "admin").ok) {
|
|
12542
12859
|
return c.json({ error: "Unauthorized" }, 401);
|
|
12543
12860
|
}
|
|
12544
12861
|
if (!body.password) {
|
|
@@ -12561,9 +12878,9 @@ app35.post("/api/remote-auth/set-password", async (c) => {
|
|
|
12561
12878
|
return c.json({ error: "Failed to save password" }, 500);
|
|
12562
12879
|
}
|
|
12563
12880
|
});
|
|
12564
|
-
|
|
12881
|
+
app36.route("/api/_client-error", client_error_default);
|
|
12565
12882
|
console.log("[client-error-route] mounted");
|
|
12566
|
-
|
|
12883
|
+
app36.use("*", async (c, next) => {
|
|
12567
12884
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
12568
12885
|
const path2 = c.req.path;
|
|
12569
12886
|
if (path2 === "/favicon.ico" || path2.startsWith("/assets/") || path2.startsWith("/brand/")) {
|
|
@@ -12596,15 +12913,15 @@ app35.use("*", async (c, next) => {
|
|
|
12596
12913
|
console.error(`[remote-auth] login required ip=${clientIp} path=${path2} ${disambig}`);
|
|
12597
12914
|
return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), redirect: path2 }), 200);
|
|
12598
12915
|
});
|
|
12599
|
-
|
|
12600
|
-
|
|
12601
|
-
|
|
12602
|
-
|
|
12603
|
-
|
|
12604
|
-
|
|
12605
|
-
|
|
12606
|
-
|
|
12607
|
-
|
|
12916
|
+
app36.route("/api/health", health_default);
|
|
12917
|
+
app36.route("/api/session", session_default);
|
|
12918
|
+
app36.route("/api/chat", chat_default);
|
|
12919
|
+
app36.route("/api/group", group_default);
|
|
12920
|
+
app36.route("/api/access", access_default);
|
|
12921
|
+
app36.route("/api/telegram", telegram_default);
|
|
12922
|
+
app36.route("/api/whatsapp", whatsapp_default);
|
|
12923
|
+
app36.route("/api/onboarding", onboarding_default);
|
|
12924
|
+
app36.route("/api/admin", admin_default);
|
|
12608
12925
|
var SAFE_SLUG_RE = /^[a-z][a-z0-9-]{2,49}$/;
|
|
12609
12926
|
var SAFE_FILENAME_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
|
|
12610
12927
|
var IMAGE_MIME = {
|
|
@@ -12616,7 +12933,7 @@ var IMAGE_MIME = {
|
|
|
12616
12933
|
".svg": "image/svg+xml",
|
|
12617
12934
|
".ico": "image/x-icon"
|
|
12618
12935
|
};
|
|
12619
|
-
|
|
12936
|
+
app36.get("/agent-assets/:slug/:filename", (c) => {
|
|
12620
12937
|
const slug = c.req.param("slug");
|
|
12621
12938
|
const filename = c.req.param("filename");
|
|
12622
12939
|
if (!SAFE_SLUG_RE.test(slug)) {
|
|
@@ -12632,26 +12949,26 @@ app35.get("/agent-assets/:slug/:filename", (c) => {
|
|
|
12632
12949
|
console.error(`[agent-assets] no-account slug=${slug} file=${filename}`);
|
|
12633
12950
|
return c.text("Not found", 404);
|
|
12634
12951
|
}
|
|
12635
|
-
const filePath =
|
|
12636
|
-
const expectedDir =
|
|
12952
|
+
const filePath = resolve24(account.accountDir, "agents", slug, "assets", filename);
|
|
12953
|
+
const expectedDir = resolve24(account.accountDir, "agents", slug, "assets");
|
|
12637
12954
|
if (!filePath.startsWith(expectedDir + "/")) {
|
|
12638
12955
|
console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
|
|
12639
12956
|
return c.text("Forbidden", 403);
|
|
12640
12957
|
}
|
|
12641
|
-
if (!
|
|
12958
|
+
if (!existsSync24(filePath)) {
|
|
12642
12959
|
console.error(`[agent-assets] serve slug=${slug} file=${filename} status=404`);
|
|
12643
12960
|
return c.text("Not found", 404);
|
|
12644
12961
|
}
|
|
12645
12962
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
12646
12963
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
12647
12964
|
console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
|
|
12648
|
-
const body =
|
|
12965
|
+
const body = readFileSync17(filePath);
|
|
12649
12966
|
return c.body(body, 200, {
|
|
12650
12967
|
"Content-Type": contentType,
|
|
12651
12968
|
"Cache-Control": "public, max-age=3600"
|
|
12652
12969
|
});
|
|
12653
12970
|
});
|
|
12654
|
-
|
|
12971
|
+
app36.get("/generated/:filename", (c) => {
|
|
12655
12972
|
const filename = c.req.param("filename");
|
|
12656
12973
|
if (!SAFE_FILENAME_RE.test(filename) || filename.includes("..")) {
|
|
12657
12974
|
console.error(`[generated] serve file=${filename} status=403`);
|
|
@@ -12662,31 +12979,32 @@ app35.get("/generated/:filename", (c) => {
|
|
|
12662
12979
|
console.error(`[generated] serve file=${filename} status=404`);
|
|
12663
12980
|
return c.text("Not found", 404);
|
|
12664
12981
|
}
|
|
12665
|
-
const filePath =
|
|
12666
|
-
const expectedDir =
|
|
12982
|
+
const filePath = resolve24(account.accountDir, "generated", filename);
|
|
12983
|
+
const expectedDir = resolve24(account.accountDir, "generated");
|
|
12667
12984
|
if (!filePath.startsWith(expectedDir + "/")) {
|
|
12668
12985
|
console.error(`[generated] serve file=${filename} status=403`);
|
|
12669
12986
|
return c.text("Forbidden", 403);
|
|
12670
12987
|
}
|
|
12671
|
-
if (!
|
|
12988
|
+
if (!existsSync24(filePath)) {
|
|
12672
12989
|
console.error(`[generated] serve file=${filename} status=404`);
|
|
12673
12990
|
return c.text("Not found", 404);
|
|
12674
12991
|
}
|
|
12675
12992
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
12676
12993
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
12677
12994
|
console.log(`[generated] serve file=${filename} status=200`);
|
|
12678
|
-
const body =
|
|
12995
|
+
const body = readFileSync17(filePath);
|
|
12679
12996
|
return c.body(body, 200, {
|
|
12680
12997
|
"Content-Type": contentType,
|
|
12681
12998
|
"Cache-Control": "public, max-age=86400"
|
|
12682
12999
|
});
|
|
12683
13000
|
});
|
|
13001
|
+
app36.route("/sites", sites_default);
|
|
12684
13002
|
var htmlCache = /* @__PURE__ */ new Map();
|
|
12685
13003
|
var brandLogoPath = "/brand/maxy-monochrome.png";
|
|
12686
13004
|
var brandIconPath = "/brand/maxy-monochrome.png";
|
|
12687
|
-
if (BRAND_JSON_PATH &&
|
|
13005
|
+
if (BRAND_JSON_PATH && existsSync24(BRAND_JSON_PATH)) {
|
|
12688
13006
|
try {
|
|
12689
|
-
const fullBrand = JSON.parse(
|
|
13007
|
+
const fullBrand = JSON.parse(readFileSync17(BRAND_JSON_PATH, "utf-8"));
|
|
12690
13008
|
if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
|
|
12691
13009
|
brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
|
|
12692
13010
|
} catch {
|
|
@@ -12704,8 +13022,8 @@ function readInstalledVersion() {
|
|
|
12704
13022
|
try {
|
|
12705
13023
|
if (!PLATFORM_ROOT7) return "unknown";
|
|
12706
13024
|
const versionFile = join10(PLATFORM_ROOT7, "config", `.${BRAND.hostname}-version`);
|
|
12707
|
-
if (!
|
|
12708
|
-
const content =
|
|
13025
|
+
if (!existsSync24(versionFile)) return "unknown";
|
|
13026
|
+
const content = readFileSync17(versionFile, "utf-8").trim();
|
|
12709
13027
|
return content || "unknown";
|
|
12710
13028
|
} catch {
|
|
12711
13029
|
return "unknown";
|
|
@@ -12746,7 +13064,7 @@ var clientErrorReporterScript = `<script>
|
|
|
12746
13064
|
function cachedHtml(file) {
|
|
12747
13065
|
let html = htmlCache.get(file);
|
|
12748
13066
|
if (!html) {
|
|
12749
|
-
html =
|
|
13067
|
+
html = readFileSync17(resolve24(process.cwd(), "public", file), "utf-8");
|
|
12750
13068
|
const productNameEsc = escapeHtml(BRAND.productName);
|
|
12751
13069
|
html = html.replace(/<title>([^<]*)<\/title>/, (_match, inner) => `<title>${escapeHtml(inner).replace(/Maxy/g, productNameEsc)}</title>`);
|
|
12752
13070
|
html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
|
|
@@ -12765,13 +13083,13 @@ function loadBrandingCache(agentSlug) {
|
|
|
12765
13083
|
const configDir2 = join10(homedir2(), BRAND.configDir);
|
|
12766
13084
|
try {
|
|
12767
13085
|
const accountJsonPath = join10(configDir2, "account.json");
|
|
12768
|
-
if (!
|
|
12769
|
-
const account = JSON.parse(
|
|
13086
|
+
if (!existsSync24(accountJsonPath)) return null;
|
|
13087
|
+
const account = JSON.parse(readFileSync17(accountJsonPath, "utf-8"));
|
|
12770
13088
|
const accountId = account.accountId;
|
|
12771
13089
|
if (!accountId) return null;
|
|
12772
13090
|
const cachePath = join10(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
|
|
12773
|
-
if (!
|
|
12774
|
-
return JSON.parse(
|
|
13091
|
+
if (!existsSync24(cachePath)) return null;
|
|
13092
|
+
return JSON.parse(readFileSync17(cachePath, "utf-8"));
|
|
12775
13093
|
} catch {
|
|
12776
13094
|
return null;
|
|
12777
13095
|
}
|
|
@@ -12780,8 +13098,8 @@ function resolveDefaultSlug() {
|
|
|
12780
13098
|
try {
|
|
12781
13099
|
const configDir2 = join10(homedir2(), BRAND.configDir);
|
|
12782
13100
|
const accountJsonPath = join10(configDir2, "account.json");
|
|
12783
|
-
if (!
|
|
12784
|
-
const account = JSON.parse(
|
|
13101
|
+
if (!existsSync24(accountJsonPath)) return null;
|
|
13102
|
+
const account = JSON.parse(readFileSync17(accountJsonPath, "utf-8"));
|
|
12785
13103
|
return account.defaultAgent || null;
|
|
12786
13104
|
} catch {
|
|
12787
13105
|
return null;
|
|
@@ -12817,7 +13135,7 @@ function brandedPublicHtml(agentSlug) {
|
|
|
12817
13135
|
function escapeHtml(s) {
|
|
12818
13136
|
return s.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
12819
13137
|
}
|
|
12820
|
-
|
|
13138
|
+
app36.get("/", (c) => {
|
|
12821
13139
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
12822
13140
|
if (isPublicHost(host)) {
|
|
12823
13141
|
const defaultSlug = resolveDefaultSlug();
|
|
@@ -12825,12 +13143,12 @@ app35.get("/", (c) => {
|
|
|
12825
13143
|
}
|
|
12826
13144
|
return c.html(cachedHtml("index.html"));
|
|
12827
13145
|
});
|
|
12828
|
-
|
|
13146
|
+
app36.get("/public", (c) => {
|
|
12829
13147
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
12830
13148
|
if (isPublicHost(host)) return c.text("Not found", 404);
|
|
12831
13149
|
return c.html(cachedHtml("public.html"));
|
|
12832
13150
|
});
|
|
12833
|
-
|
|
13151
|
+
app36.get("/chat", (c) => {
|
|
12834
13152
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
12835
13153
|
if (isPublicHost(host)) return c.text("Not found", 404);
|
|
12836
13154
|
return c.html(cachedHtml("public.html"));
|
|
@@ -12849,12 +13167,12 @@ async function logViewerFetch(c, next) {
|
|
|
12849
13167
|
duration_ms: Date.now() - start
|
|
12850
13168
|
});
|
|
12851
13169
|
}
|
|
12852
|
-
|
|
12853
|
-
|
|
12854
|
-
|
|
13170
|
+
app36.use("/vnc-viewer.html", logViewerFetch);
|
|
13171
|
+
app36.use("/vnc-popout.html", logViewerFetch);
|
|
13172
|
+
app36.get("/vnc-popout.html", (c) => {
|
|
12855
13173
|
let html = htmlCache.get("vnc-popout.html");
|
|
12856
13174
|
if (!html) {
|
|
12857
|
-
html =
|
|
13175
|
+
html = readFileSync17(resolve24(process.cwd(), "public", "vnc-popout.html"), "utf-8");
|
|
12858
13176
|
const name = escapeHtml(BRAND.productName);
|
|
12859
13177
|
html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
|
|
12860
13178
|
html = html.replace("</head>", ` ${brandScript}
|
|
@@ -12864,7 +13182,7 @@ app35.get("/vnc-popout.html", (c) => {
|
|
|
12864
13182
|
}
|
|
12865
13183
|
return c.html(html);
|
|
12866
13184
|
});
|
|
12867
|
-
|
|
13185
|
+
app36.post("/api/vnc/client-event", async (c) => {
|
|
12868
13186
|
let body;
|
|
12869
13187
|
try {
|
|
12870
13188
|
body = await c.req.json();
|
|
@@ -12885,20 +13203,20 @@ app35.post("/api/vnc/client-event", async (c) => {
|
|
|
12885
13203
|
});
|
|
12886
13204
|
return c.json({ ok: true });
|
|
12887
13205
|
});
|
|
12888
|
-
|
|
13206
|
+
app36.get("/g/:slug", (c) => {
|
|
12889
13207
|
return c.html(brandedPublicHtml());
|
|
12890
13208
|
});
|
|
12891
|
-
|
|
13209
|
+
app36.get("/graph", (c) => {
|
|
12892
13210
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
12893
13211
|
if (isPublicHost(host)) return c.text("Not found", 404);
|
|
12894
13212
|
return c.html(cachedHtml("graph.html"));
|
|
12895
13213
|
});
|
|
12896
|
-
|
|
13214
|
+
app36.get("/data", (c) => {
|
|
12897
13215
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
12898
13216
|
if (isPublicHost(host)) return c.text("Not found", 404);
|
|
12899
13217
|
return c.html(cachedHtml("data.html"));
|
|
12900
13218
|
});
|
|
12901
|
-
|
|
13219
|
+
app36.get("/:slug", async (c, next) => {
|
|
12902
13220
|
const slug = c.req.param("slug");
|
|
12903
13221
|
if (AGENT_SLUG_PATTERN.test(`/${slug}`)) {
|
|
12904
13222
|
const branding = loadBrandingCache(slug);
|
|
@@ -12907,10 +13225,10 @@ app35.get("/:slug", async (c, next) => {
|
|
|
12907
13225
|
}
|
|
12908
13226
|
await next();
|
|
12909
13227
|
});
|
|
12910
|
-
|
|
13228
|
+
app36.use("/*", serveStatic({ root: "./public" }));
|
|
12911
13229
|
var port = parseInt(process.env.MAXY_UI_INTERNAL_PORT ?? process.env.PORT ?? "19199", 10);
|
|
12912
13230
|
var hostname = process.env.HOSTNAME ?? "127.0.0.1";
|
|
12913
|
-
var httpServer = serve({ fetch:
|
|
13231
|
+
var httpServer = serve({ fetch: app36.fetch, port, hostname });
|
|
12914
13232
|
console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
12915
13233
|
var SUBAPP_MANIFEST = [
|
|
12916
13234
|
{ prefix: "/api/health", file: "server/routes/health.ts", subapp: health_default },
|
|
@@ -12930,7 +13248,7 @@ for (const m of SUBAPP_MANIFEST) {
|
|
|
12930
13248
|
}
|
|
12931
13249
|
try {
|
|
12932
13250
|
const registered = [];
|
|
12933
|
-
for (const r of
|
|
13251
|
+
for (const r of app36.routes ?? []) {
|
|
12934
13252
|
if (typeof r.path !== "string" || r.path.includes(":") || r.path.includes("*")) continue;
|
|
12935
13253
|
if (AGENT_SLUG_PATTERN.test(r.path)) {
|
|
12936
13254
|
registered.push({ method: (r.method ?? "ALL").toUpperCase(), path: r.path });
|
|
@@ -12944,8 +13262,8 @@ try {
|
|
|
12944
13262
|
(async () => {
|
|
12945
13263
|
try {
|
|
12946
13264
|
let userId = "";
|
|
12947
|
-
if (
|
|
12948
|
-
const users = JSON.parse(
|
|
13265
|
+
if (existsSync24(USERS_FILE)) {
|
|
13266
|
+
const users = JSON.parse(readFileSync17(USERS_FILE, "utf-8").trim() || "[]");
|
|
12949
13267
|
userId = users[0]?.userId ?? "";
|
|
12950
13268
|
}
|
|
12951
13269
|
await backfillNullUserIdConversations(userId);
|
|
@@ -12953,6 +13271,13 @@ try {
|
|
|
12953
13271
|
console.error(`[session] backfill startup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
12954
13272
|
}
|
|
12955
13273
|
})();
|
|
13274
|
+
(async () => {
|
|
13275
|
+
try {
|
|
13276
|
+
await runBootMigrations();
|
|
13277
|
+
} catch (err) {
|
|
13278
|
+
console.error(`[migration] runBootMigrations rejected: ${err instanceof Error ? err.message : String(err)}`);
|
|
13279
|
+
}
|
|
13280
|
+
})();
|
|
12956
13281
|
(async () => {
|
|
12957
13282
|
try {
|
|
12958
13283
|
await startReviewDetector();
|
|
@@ -12964,7 +13289,7 @@ startGraphHealthTimer();
|
|
|
12964
13289
|
var configDirForWhatsApp = basename7(MAXY_DIR) || ".maxy";
|
|
12965
13290
|
var bootAccount = resolveAccount();
|
|
12966
13291
|
var bootAccountConfig = bootAccount?.config;
|
|
12967
|
-
var bootPublicAgent = bootAccount ?
|
|
13292
|
+
var bootPublicAgent = bootAccount ? resolvePublicAgent(bootAccount.accountDir, { accountId: bootAccount.accountId })?.slug ?? null : null;
|
|
12968
13293
|
var bootEntitlement = bootAccountConfig ? resolveEntitlement(
|
|
12969
13294
|
{ configDir: MAXY_DIR, platformRoot: PLATFORM_ROOT, commercialMode: COMMERCIAL_MODE },
|
|
12970
13295
|
{
|
|
@@ -13005,7 +13330,7 @@ if (bootAccountConfig?.whatsapp) {
|
|
|
13005
13330
|
}
|
|
13006
13331
|
init({
|
|
13007
13332
|
configDir: configDirForWhatsApp,
|
|
13008
|
-
platformRoot:
|
|
13333
|
+
platformRoot: resolve24(process.env.MAXY_PLATFORM_ROOT ?? join10(__dirname, "..")),
|
|
13009
13334
|
accountConfig: bootAccountConfig,
|
|
13010
13335
|
onMessage: async (msg) => {
|
|
13011
13336
|
try {
|
|
@@ -13029,12 +13354,20 @@ init({
|
|
|
13029
13354
|
if (msg.agentType === "admin") {
|
|
13030
13355
|
agentName = "admin";
|
|
13031
13356
|
} else {
|
|
13032
|
-
const
|
|
13033
|
-
|
|
13034
|
-
|
|
13357
|
+
const resolved = bootAccount ? resolvePublicAgent(bootAccount.accountDir, { accountId: msg.accountId, groupJid: msg.groupJid }) : null;
|
|
13358
|
+
console.error(`[whatsapp:route] resolved publicAgent account=${msg.accountId} groupJid=${msg.groupJid ?? "none"} source=${resolved?.source ?? "null"} slug=${resolved?.slug ?? "none"}`);
|
|
13359
|
+
if (!resolved) {
|
|
13360
|
+
console.error(`[whatsapp:route] rejected: no publicAgent configured account=${msg.accountId} groupJid=${msg.groupJid ?? "none"} from=${msg.senderPhone}`);
|
|
13035
13361
|
return;
|
|
13036
13362
|
}
|
|
13037
|
-
|
|
13363
|
+
if (bootAccount) {
|
|
13364
|
+
const agentConfigPath = resolveBoot(bootAccount.accountDir, "agents", resolved.slug, "config.json");
|
|
13365
|
+
if (!existsSyncBoot(agentConfigPath)) {
|
|
13366
|
+
console.error(`[whatsapp:route] rejected: slug-missing-agent-dir account=${msg.accountId} groupJid=${msg.groupJid ?? "none"} slug=${resolved.slug} source=${resolved.source} expected=${agentConfigPath}`);
|
|
13367
|
+
return;
|
|
13368
|
+
}
|
|
13369
|
+
}
|
|
13370
|
+
agentName = resolved.slug;
|
|
13038
13371
|
}
|
|
13039
13372
|
let enrichedText = msg.text || msg.mediaPath || msg.mediaType || msg.replyContext ? buildEnrichedText2(msg.text ?? "") : "";
|
|
13040
13373
|
if (msg.sessionKey) {
|