@rubytech/create-maxy-code 0.1.281 → 0.1.283
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/config/brand.json +0 -1
- package/payload/platform/plugins/admin/PLUGIN.md +2 -1
- package/payload/platform/plugins/admin/hooks/__tests__/prompt-optimiser-compliance.test.sh +154 -0
- package/payload/platform/plugins/admin/hooks/__tests__/prompt-optimiser-directive.test.sh +24 -0
- package/payload/platform/plugins/admin/hooks/prompt-optimiser-compliance.sh +174 -0
- package/payload/platform/plugins/admin/hooks/prompt-optimiser-directive.sh +22 -2
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +5 -5
- package/payload/platform/plugins/business-assistant/skills/e-sign/SKILL.md +378 -56
- package/payload/platform/plugins/cloudflare/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/cloudflare/PLUGIN.md +5 -5
- package/payload/platform/plugins/cloudflare/references/api.md +107 -34
- package/payload/platform/plugins/cloudflare/references/d1-data-capture.md +29 -21
- package/payload/platform/plugins/cloudflare/references/dashboard-guide.md +3 -2
- package/payload/platform/plugins/cloudflare/references/hosting-sites.md +7 -5
- package/payload/platform/plugins/cloudflare/references/manual-setup.md +3 -2
- package/payload/platform/plugins/cloudflare/skills/cloudflare/SKILL.md +6 -6
- package/payload/platform/plugins/docs/references/cloudflare.md +1 -1
- package/payload/platform/plugins/docs/references/telegram-guide.md +3 -3
- package/payload/platform/plugins/email/mcp/dist/__tests__/attachment-resolve.test.js +25 -1
- package/payload/platform/plugins/email/mcp/dist/__tests__/attachment-resolve.test.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/attachment-resolve.d.ts +7 -0
- package/payload/platform/plugins/email/mcp/dist/lib/attachment-resolve.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/attachment-resolve.js +9 -0
- package/payload/platform/plugins/email/mcp/dist/lib/attachment-resolve.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-ingest.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-ingest.js +6 -2
- package/payload/platform/plugins/email/mcp/dist/tools/email-ingest.js.map +1 -1
- package/payload/platform/plugins/prompt-optimiser/skills/prompt-optimiser/SKILL.md +1 -0
- package/payload/platform/plugins/telegram/PLUGIN.md +7 -2
- package/payload/platform/plugins/telegram/mcp/dist/index.js +66 -11
- package/payload/platform/plugins/telegram/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.d.ts +6 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.d.ts.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.js +20 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.js.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.d.ts +21 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.d.ts.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.js +52 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.js.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/message.d.ts.map +1 -1
- package/payload/platform/plugins/telegram/mcp/dist/tools/message.js +5 -2
- package/payload/platform/plugins/telegram/mcp/dist/tools/message.js.map +1 -1
- package/payload/platform/plugins/telegram/references/setup-guide.md +10 -7
- package/payload/platform/plugins/telegram/skills/configure/SKILL.md +79 -0
- package/payload/platform/plugins/whatsapp/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/whatsapp/PLUGIN.md +7 -11
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js +10 -56
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +26 -2
- package/payload/platform/plugins/whatsapp/skills/manage-whatsapp-config/SKILL.md +9 -14
- package/payload/platform/scripts/setup-account.sh +7 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +0 -2
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +80 -3
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +6 -0
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.js +14 -0
- package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.js +5 -3
- package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
- package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
- package/payload/server/public/assets/AdminShell-8Y7-maNi.js +1 -0
- package/payload/server/public/assets/{admin-0_VxffD5.js → admin-DyuZ4NT5.js} +1 -1
- package/payload/server/public/assets/{browser-Cxjkhtsk.js → browser-XrVAUQdG.js} +1 -1
- package/payload/server/public/assets/{data-BOgwOWnw.js → data-tcu3MXmK.js} +1 -1
- package/payload/server/public/assets/{graph-BqD2GjQd.js → graph-I4GyC2MW.js} +1 -1
- package/payload/server/public/browser.html +2 -2
- package/payload/server/public/data.html +2 -2
- package/payload/server/public/graph.html +2 -2
- package/payload/server/public/index.html +2 -2
- package/payload/server/server.js +882 -767
- package/payload/server/public/assets/AdminShell-FO766lOW.js +0 -1
package/payload/server/server.js
CHANGED
|
@@ -1201,9 +1201,9 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
1201
1201
|
};
|
|
1202
1202
|
|
|
1203
1203
|
// server/index.ts
|
|
1204
|
-
import { readFileSync as
|
|
1205
|
-
import { resolve as
|
|
1206
|
-
import { homedir as
|
|
1204
|
+
import { readFileSync as readFileSync24, existsSync as existsSync24, watchFile } from "fs";
|
|
1205
|
+
import { resolve as resolve27, join as join19, basename as basename7 } from "path";
|
|
1206
|
+
import { homedir as homedir3 } from "os";
|
|
1207
1207
|
import { monitorEventLoopDelay } from "perf_hooks";
|
|
1208
1208
|
|
|
1209
1209
|
// app/lib/agent-slug-pattern.ts
|
|
@@ -1513,24 +1513,14 @@ function getLanIp() {
|
|
|
1513
1513
|
// app/lib/whatsapp/schema.ts
|
|
1514
1514
|
import { z } from "zod";
|
|
1515
1515
|
var DmPolicySchema = z.enum(["open", "allowlist", "disabled"]);
|
|
1516
|
-
var GroupPolicySchema = z.enum(["open", "allowlist", "disabled"]);
|
|
1517
1516
|
var WhatsAppAccountSchema = z.object({
|
|
1518
1517
|
name: z.string().optional().describe("Display name for this WhatsApp account (e.g. 'Main', 'Sales')."),
|
|
1519
1518
|
enabled: z.boolean().optional().describe("If disabled, this account won't connect on startup."),
|
|
1520
1519
|
dmPolicy: DmPolicySchema.optional().describe("Who can message your public agent via WhatsApp DM. 'Open' lets anyone message. 'Allowlist' restricts to approved phone numbers. 'Disabled' blocks all public messages."),
|
|
1521
|
-
groupPolicy: GroupPolicySchema.optional().describe("Whether your agent responds in WhatsApp group chats. 'Open' enables group responses (subject to per-group activation). 'Allowlist' restricts to approved senders. 'Disabled' ignores all group messages."),
|
|
1522
1520
|
selfChatMode: z.boolean().optional().describe("Enable if the bot uses the owner's personal WhatsApp number (same phone for admin and bot). Changes how self-messages are routed."),
|
|
1523
1521
|
adminPhones: z.array(z.string()).optional().describe("Phone numbers (E.164) that route to the admin agent. Managed via add-admin-phone / remove-admin-phone."),
|
|
1524
1522
|
allowFrom: z.array(z.string()).optional().describe("Phone numbers allowed to message the public agent when DM policy is 'allowlist'. Add '*' for open access. Does not affect admin routing \u2014 use adminPhones for that."),
|
|
1525
|
-
groupAllowFrom: z.array(z.string()).optional().describe("Phone numbers allowed to trigger the agent in groups when group policy is 'allowlist'."),
|
|
1526
1523
|
sendReadReceipts: z.boolean().optional().default(true).describe("Whether to send read receipts (blue ticks) when messages are received. Disabling may feel less responsive but preserves privacy."),
|
|
1527
|
-
groups: z.record(
|
|
1528
|
-
z.string(),
|
|
1529
|
-
z.object({
|
|
1530
|
-
activation: z.enum(["always", "mention", "off"]).optional(),
|
|
1531
|
-
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.")
|
|
1532
|
-
}).strict().optional()
|
|
1533
|
-
).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)."),
|
|
1534
1524
|
ackReaction: z.object({
|
|
1535
1525
|
emoji: z.string().optional(),
|
|
1536
1526
|
direct: z.boolean().optional().default(true),
|
|
@@ -1552,10 +1542,8 @@ var WhatsAppAccountSchema = z.object({
|
|
|
1552
1542
|
var WhatsAppConfigSchema = z.object({
|
|
1553
1543
|
accounts: z.record(z.string(), WhatsAppAccountSchema.optional()).optional().describe("Per-account config keyed by account ID."),
|
|
1554
1544
|
dmPolicy: DmPolicySchema.optional().default("disabled").describe("Who can message your public agent via WhatsApp DM. 'Open' lets anyone message. 'Allowlist' restricts to approved phone numbers. 'Disabled' blocks all public messages."),
|
|
1555
|
-
groupPolicy: GroupPolicySchema.optional().default("disabled").describe("Whether your agent responds in WhatsApp group chats. 'Open' enables group responses (subject to per-group activation). 'Allowlist' restricts to approved senders. 'Disabled' ignores all group messages."),
|
|
1556
1545
|
adminPhones: z.array(z.string()).optional().describe("Phone numbers (E.164) that route to the admin agent. Managed via add-admin-phone / remove-admin-phone."),
|
|
1557
1546
|
allowFrom: z.array(z.string()).optional().describe("Phone numbers allowed to message the public agent when DM policy is 'allowlist'. Add '*' for open access. Does not affect admin routing \u2014 use adminPhones for that."),
|
|
1558
|
-
groupAllowFrom: z.array(z.string()).optional().describe("Phone numbers allowed to trigger the agent in groups when group policy is 'allowlist'."),
|
|
1559
1547
|
sendReadReceipts: z.boolean().optional().default(true).describe("Whether to send read receipts (blue ticks) when messages are received. Disabling may feel less responsive but preserves privacy."),
|
|
1560
1548
|
mediaMaxMb: z.number().int().positive().optional().default(50).describe("Maximum file size in MB for media the agent will download and process."),
|
|
1561
1549
|
textChunkLimit: z.number().int().positive().optional().default(4e3).describe("Maximum characters per outbound message. Longer replies are split into multiple messages."),
|
|
@@ -1572,6 +1560,36 @@ var WhatsAppConfigSchema = z.object({
|
|
|
1572
1560
|
message: 'dmPolicy="open" requires allowFrom to include "*"'
|
|
1573
1561
|
});
|
|
1574
1562
|
});
|
|
1563
|
+
function parseWhatsAppConfigTolerant(raw) {
|
|
1564
|
+
const first = WhatsAppConfigSchema.safeParse(raw);
|
|
1565
|
+
if (first.success) {
|
|
1566
|
+
return { ok: true, data: first.data, droppedKeys: [] };
|
|
1567
|
+
}
|
|
1568
|
+
const issues = first.error.issues;
|
|
1569
|
+
const onlyUnknownKeys = issues.length > 0 && issues.every((i) => i.code === "unrecognized_keys");
|
|
1570
|
+
if (!onlyUnknownKeys) {
|
|
1571
|
+
return { ok: false, error: first.error };
|
|
1572
|
+
}
|
|
1573
|
+
const clone = JSON.parse(JSON.stringify(raw));
|
|
1574
|
+
const droppedKeys = [];
|
|
1575
|
+
for (const issue of issues) {
|
|
1576
|
+
if (issue.code !== "unrecognized_keys") continue;
|
|
1577
|
+
const container = issue.path.reduce(
|
|
1578
|
+
(acc, seg) => acc && typeof acc === "object" ? acc[seg] : void 0,
|
|
1579
|
+
clone
|
|
1580
|
+
);
|
|
1581
|
+
if (!container || typeof container !== "object") continue;
|
|
1582
|
+
for (const key of issue.keys) {
|
|
1583
|
+
delete container[key];
|
|
1584
|
+
droppedKeys.push([...issue.path, key].join("."));
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
const second = WhatsAppConfigSchema.safeParse(clone);
|
|
1588
|
+
if (second.success) {
|
|
1589
|
+
return { ok: true, data: second.data, droppedKeys };
|
|
1590
|
+
}
|
|
1591
|
+
return { ok: false, error: second.error };
|
|
1592
|
+
}
|
|
1575
1593
|
|
|
1576
1594
|
// app/lib/whatsapp/auth-store.ts
|
|
1577
1595
|
import fsSync from "fs";
|
|
@@ -1763,7 +1781,7 @@ var credsSaveQueue = Promise.resolve();
|
|
|
1763
1781
|
async function drainCredsSaveQueue(timeoutMs = 5e3) {
|
|
1764
1782
|
console.error(`${TAG2} draining credential save queue\u2026`);
|
|
1765
1783
|
const timer2 = new Promise(
|
|
1766
|
-
(
|
|
1784
|
+
(resolve28) => setTimeout(() => resolve28("timeout"), timeoutMs)
|
|
1767
1785
|
);
|
|
1768
1786
|
const result = await Promise.race([
|
|
1769
1787
|
credsSaveQueue.then(() => "drained"),
|
|
@@ -1891,11 +1909,11 @@ async function createWaSocket(opts) {
|
|
|
1891
1909
|
return sock;
|
|
1892
1910
|
}
|
|
1893
1911
|
async function waitForConnection(sock) {
|
|
1894
|
-
return new Promise((
|
|
1912
|
+
return new Promise((resolve28, reject) => {
|
|
1895
1913
|
const handler = (update) => {
|
|
1896
1914
|
if (update.connection === "open") {
|
|
1897
1915
|
sock.ev.off("connection.update", handler);
|
|
1898
|
-
|
|
1916
|
+
resolve28();
|
|
1899
1917
|
}
|
|
1900
1918
|
if (update.connection === "close") {
|
|
1901
1919
|
sock.ev.off("connection.update", handler);
|
|
@@ -2009,14 +2027,14 @@ ${inspected}`;
|
|
|
2009
2027
|
return inspect2(err, INSPECT_OPTS2);
|
|
2010
2028
|
}
|
|
2011
2029
|
function withTimeout(label, promise, timeoutMs) {
|
|
2012
|
-
return new Promise((
|
|
2030
|
+
return new Promise((resolve28, reject) => {
|
|
2013
2031
|
const timer2 = setTimeout(() => {
|
|
2014
2032
|
reject(new Error(`${label} timed out after ${timeoutMs}ms`));
|
|
2015
2033
|
}, timeoutMs);
|
|
2016
2034
|
promise.then(
|
|
2017
2035
|
(value) => {
|
|
2018
2036
|
clearTimeout(timer2);
|
|
2019
|
-
|
|
2037
|
+
resolve28(value);
|
|
2020
2038
|
},
|
|
2021
2039
|
(err) => {
|
|
2022
2040
|
clearTimeout(timer2);
|
|
@@ -2321,10 +2339,8 @@ function extractMessage(msg) {
|
|
|
2321
2339
|
function resolveAccountConfig(config, accountConfig) {
|
|
2322
2340
|
return {
|
|
2323
2341
|
dmPolicy: accountConfig?.dmPolicy ?? config.dmPolicy ?? "disabled",
|
|
2324
|
-
groupPolicy: accountConfig?.groupPolicy ?? config.groupPolicy ?? "disabled",
|
|
2325
2342
|
adminPhones: accountConfig?.adminPhones ?? config.adminPhones ?? [],
|
|
2326
2343
|
allowFrom: accountConfig?.allowFrom ?? config.allowFrom ?? [],
|
|
2327
|
-
groupAllowFrom: accountConfig?.groupAllowFrom ?? config.groupAllowFrom ?? [],
|
|
2328
2344
|
selfChatMode: accountConfig?.selfChatMode ?? false
|
|
2329
2345
|
};
|
|
2330
2346
|
}
|
|
@@ -2361,36 +2377,8 @@ function checkDmAccess(params) {
|
|
|
2361
2377
|
return { allowed: false, reason: "unknown-dm-policy", agentType: "public" };
|
|
2362
2378
|
}
|
|
2363
2379
|
}
|
|
2364
|
-
function checkGroupAccess(
|
|
2365
|
-
|
|
2366
|
-
const cfg = resolveAccountConfig(params.config, params.accountConfig);
|
|
2367
|
-
if (phonesMatch(senderPhone, selfPhone)) {
|
|
2368
|
-
return { allowed: true, reason: "self-phone-group", agentType: "admin" };
|
|
2369
|
-
}
|
|
2370
|
-
switch (cfg.groupPolicy) {
|
|
2371
|
-
case "open": {
|
|
2372
|
-
const groupConfig = params.accountConfig?.groups?.[groupJid];
|
|
2373
|
-
const activation = groupConfig?.activation ?? "mention";
|
|
2374
|
-
if (activation === "off") {
|
|
2375
|
-
return { allowed: false, reason: "group-activation-off", agentType: "public" };
|
|
2376
|
-
}
|
|
2377
|
-
if (activation === "mention" && !isMentioned) {
|
|
2378
|
-
return { allowed: false, reason: "mention-gating", agentType: "public" };
|
|
2379
|
-
}
|
|
2380
|
-
return { allowed: true, reason: "group-policy-open", agentType: "public" };
|
|
2381
|
-
}
|
|
2382
|
-
case "allowlist": {
|
|
2383
|
-
const inList = cfg.groupAllowFrom.some((entry) => phonesMatch(entry, senderPhone)) || cfg.adminPhones.some((entry) => phonesMatch(entry, senderPhone));
|
|
2384
|
-
if (!inList) {
|
|
2385
|
-
return { allowed: false, reason: "not-in-group-allowlist", agentType: "public" };
|
|
2386
|
-
}
|
|
2387
|
-
return { allowed: true, reason: "group-allowlist-match", agentType: "public" };
|
|
2388
|
-
}
|
|
2389
|
-
case "disabled":
|
|
2390
|
-
return { allowed: false, reason: "group-policy-disabled", agentType: "public" };
|
|
2391
|
-
default:
|
|
2392
|
-
return { allowed: false, reason: "unknown-group-policy", agentType: "public" };
|
|
2393
|
-
}
|
|
2380
|
+
function checkGroupAccess(_params) {
|
|
2381
|
+
return { allowed: false, reason: "group-observe-only", agentType: "public" };
|
|
2394
2382
|
}
|
|
2395
2383
|
|
|
2396
2384
|
// app/lib/whatsapp/inbound/dedupe.ts
|
|
@@ -2601,8 +2589,8 @@ async function persistWhatsAppMessage(input) {
|
|
|
2601
2589
|
const { givenName, familyName } = splitName(input.pushName);
|
|
2602
2590
|
const prev = sessionWriteLocks.get(input.cacheKey);
|
|
2603
2591
|
let release;
|
|
2604
|
-
const mine = new Promise((
|
|
2605
|
-
release =
|
|
2592
|
+
const mine = new Promise((resolve28) => {
|
|
2593
|
+
release = resolve28;
|
|
2606
2594
|
});
|
|
2607
2595
|
const chained = (prev ?? Promise.resolve()).then(() => mine);
|
|
2608
2596
|
sessionWriteLocks.set(input.cacheKey, chained);
|
|
@@ -3275,11 +3263,20 @@ async function transcribe(audioPath, mimetype) {
|
|
|
3275
3263
|
}
|
|
3276
3264
|
}
|
|
3277
3265
|
|
|
3266
|
+
// app/lib/whatsapp/reply-error.ts
|
|
3267
|
+
var WaReplyError = class extends Error {
|
|
3268
|
+
terminal;
|
|
3269
|
+
constructor(message, terminal) {
|
|
3270
|
+
super(message);
|
|
3271
|
+
this.name = "WaReplyError";
|
|
3272
|
+
this.terminal = terminal;
|
|
3273
|
+
}
|
|
3274
|
+
};
|
|
3275
|
+
|
|
3278
3276
|
// app/lib/whatsapp/manager.ts
|
|
3279
3277
|
var TAG12 = "[whatsapp:manager]";
|
|
3280
3278
|
var MAX_RECONNECT_ATTEMPTS = 10;
|
|
3281
3279
|
var MESSAGE_STORE_MAX = 500;
|
|
3282
|
-
var GROUP_META_TTL = 5 * 60 * 1e3;
|
|
3283
3280
|
var connections = /* @__PURE__ */ new Map();
|
|
3284
3281
|
var configDir = null;
|
|
3285
3282
|
var whatsAppConfig = {};
|
|
@@ -3375,8 +3372,7 @@ async function startConnection(accountId) {
|
|
|
3375
3372
|
reconnectAttempts: 0,
|
|
3376
3373
|
abortController: new AbortController(),
|
|
3377
3374
|
debouncer: null,
|
|
3378
|
-
lidMapping: null
|
|
3379
|
-
groupMetaCache: /* @__PURE__ */ new Map()
|
|
3375
|
+
lidMapping: null
|
|
3380
3376
|
};
|
|
3381
3377
|
connections.set(accountId, conn);
|
|
3382
3378
|
await connectWithReconnect(conn);
|
|
@@ -3412,7 +3408,8 @@ function getStatus() {
|
|
|
3412
3408
|
reconnectAttempts: conn.reconnectAttempts,
|
|
3413
3409
|
lastConnectedAt: conn.lastConnectedAt,
|
|
3414
3410
|
lastError: conn.lastError,
|
|
3415
|
-
sessionStuckReason: conn.sessionStuckReason
|
|
3411
|
+
sessionStuckReason: conn.sessionStuckReason,
|
|
3412
|
+
terminalReason: conn.terminalReason
|
|
3416
3413
|
}));
|
|
3417
3414
|
}
|
|
3418
3415
|
function getSocket(accountId) {
|
|
@@ -3456,8 +3453,7 @@ async function registerLoginSocket(accountId, sock, authDir) {
|
|
|
3456
3453
|
lastConnectedAt: Date.now(),
|
|
3457
3454
|
abortController: new AbortController(),
|
|
3458
3455
|
debouncer: null,
|
|
3459
|
-
lidMapping
|
|
3460
|
-
groupMetaCache: /* @__PURE__ */ new Map()
|
|
3456
|
+
lidMapping
|
|
3461
3457
|
};
|
|
3462
3458
|
connections.set(accountId, conn);
|
|
3463
3459
|
try {
|
|
@@ -3534,13 +3530,20 @@ function loadConfig(accountConfig) {
|
|
|
3534
3530
|
const raw = accountConfig?.whatsapp;
|
|
3535
3531
|
if (raw && typeof raw === "object") {
|
|
3536
3532
|
const wa = raw;
|
|
3537
|
-
const
|
|
3538
|
-
if (
|
|
3539
|
-
whatsAppConfig =
|
|
3533
|
+
const result = parseWhatsAppConfigTolerant(wa);
|
|
3534
|
+
if (result.ok) {
|
|
3535
|
+
whatsAppConfig = result.data;
|
|
3536
|
+
if (result.droppedKeys.length > 0) {
|
|
3537
|
+
console.error(`${TAG12} config: dropped unknown keys=[${result.droppedKeys.join(",")}] preserved known config`);
|
|
3538
|
+
}
|
|
3540
3539
|
} else {
|
|
3541
|
-
console.error(`${TAG12} config validation failed: ${
|
|
3540
|
+
console.error(`${TAG12} config validation failed: ${result.error.message}`);
|
|
3542
3541
|
whatsAppConfig = {};
|
|
3543
3542
|
}
|
|
3543
|
+
const diskAdmin = Array.isArray(wa.adminPhones) ? wa.adminPhones : [];
|
|
3544
|
+
if (diskAdmin.length > 0 && (whatsAppConfig.adminPhones?.length ?? 0) === 0) {
|
|
3545
|
+
console.error(`${TAG12} WARN adminPhones present on disk but empty after load`);
|
|
3546
|
+
}
|
|
3544
3547
|
}
|
|
3545
3548
|
} catch (err) {
|
|
3546
3549
|
console.error(`${TAG12} config load error: ${String(err)}`);
|
|
@@ -3570,6 +3573,7 @@ async function connectWithReconnect(conn) {
|
|
|
3570
3573
|
conn.selfLid = selfId.lid;
|
|
3571
3574
|
conn.lastConnectedAt = connectedAt;
|
|
3572
3575
|
conn.lastError = void 0;
|
|
3576
|
+
conn.terminalReason = void 0;
|
|
3573
3577
|
const rawLidMapping = sock.signalRepository?.lidMapping ?? null;
|
|
3574
3578
|
conn.lidMapping = withSelfLidShortcut(rawLidMapping, selfId.lid, selfId.jid);
|
|
3575
3579
|
if (selfId.lid) {
|
|
@@ -3612,6 +3616,7 @@ async function connectWithReconnect(conn) {
|
|
|
3612
3616
|
conn.lastError = classification.message;
|
|
3613
3617
|
console.error(`${TAG12} disconnect account=${conn.accountId}: ${classification.kind} \u2014 ${classification.message}`);
|
|
3614
3618
|
if (!classification.shouldRetry) {
|
|
3619
|
+
conn.terminalReason = classification.message;
|
|
3615
3620
|
console.error(`${TAG12} terminal disconnect for account=${conn.accountId}, stopping reconnection`);
|
|
3616
3621
|
return;
|
|
3617
3622
|
}
|
|
@@ -3646,11 +3651,11 @@ async function connectWithReconnect(conn) {
|
|
|
3646
3651
|
console.error(
|
|
3647
3652
|
`${TAG12} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
|
|
3648
3653
|
);
|
|
3649
|
-
await new Promise((
|
|
3650
|
-
const timer2 = setTimeout(
|
|
3654
|
+
await new Promise((resolve28) => {
|
|
3655
|
+
const timer2 = setTimeout(resolve28, delay);
|
|
3651
3656
|
conn.abortController.signal.addEventListener("abort", () => {
|
|
3652
3657
|
clearTimeout(timer2);
|
|
3653
|
-
|
|
3658
|
+
resolve28();
|
|
3654
3659
|
}, { once: true });
|
|
3655
3660
|
});
|
|
3656
3661
|
}
|
|
@@ -3658,16 +3663,16 @@ async function connectWithReconnect(conn) {
|
|
|
3658
3663
|
}
|
|
3659
3664
|
}
|
|
3660
3665
|
function waitForDisconnectEvent(conn) {
|
|
3661
|
-
return new Promise((
|
|
3666
|
+
return new Promise((resolve28) => {
|
|
3662
3667
|
if (!conn.sock) {
|
|
3663
|
-
|
|
3668
|
+
resolve28();
|
|
3664
3669
|
return;
|
|
3665
3670
|
}
|
|
3666
3671
|
const sock = conn.sock;
|
|
3667
3672
|
const handler = (update) => {
|
|
3668
3673
|
if (update.connection === "close") {
|
|
3669
3674
|
sock.ev.off("connection.update", handler);
|
|
3670
|
-
|
|
3675
|
+
resolve28();
|
|
3671
3676
|
}
|
|
3672
3677
|
};
|
|
3673
3678
|
sock.ev.on("connection.update", handler);
|
|
@@ -3687,37 +3692,6 @@ function watchForDisconnect(conn) {
|
|
|
3687
3692
|
}
|
|
3688
3693
|
});
|
|
3689
3694
|
}
|
|
3690
|
-
async function getGroupMeta(conn, jid) {
|
|
3691
|
-
const cached3 = conn.groupMetaCache.get(jid);
|
|
3692
|
-
if (cached3 && cached3.expires > Date.now()) return cached3;
|
|
3693
|
-
if (!conn.sock) return null;
|
|
3694
|
-
console.error(`${TAG12} group metadata cache miss for ${jid}, fetching from Baileys account=${conn.accountId}`);
|
|
3695
|
-
try {
|
|
3696
|
-
const meta = await conn.sock.groupMetadata(jid);
|
|
3697
|
-
const participants = await Promise.all(
|
|
3698
|
-
(meta.participants ?? []).map(async (p) => {
|
|
3699
|
-
return await resolveJidToE164(p.id, conn.lidMapping) ?? p.id;
|
|
3700
|
-
})
|
|
3701
|
-
);
|
|
3702
|
-
const entry = {
|
|
3703
|
-
subject: meta.subject,
|
|
3704
|
-
participants,
|
|
3705
|
-
expires: Date.now() + GROUP_META_TTL
|
|
3706
|
-
};
|
|
3707
|
-
conn.groupMetaCache.set(jid, entry);
|
|
3708
|
-
console.error(
|
|
3709
|
-
`${TAG12} group metadata cached for ${jid}: "${meta.subject}", ${participants.length} participants, expires in ${GROUP_META_TTL}ms account=${conn.accountId}`
|
|
3710
|
-
);
|
|
3711
|
-
return entry;
|
|
3712
|
-
} catch (err) {
|
|
3713
|
-
console.error(
|
|
3714
|
-
`${TAG12} group metadata fetch failed for ${jid}: ${err instanceof Error ? err.message : String(err)}, caching empty entry for ${GROUP_META_TTL}ms account=${conn.accountId}`
|
|
3715
|
-
);
|
|
3716
|
-
const emptyEntry = { expires: Date.now() + GROUP_META_TTL };
|
|
3717
|
-
conn.groupMetaCache.set(jid, emptyEntry);
|
|
3718
|
-
return null;
|
|
3719
|
-
}
|
|
3720
|
-
}
|
|
3721
3695
|
function monitorInbound(conn) {
|
|
3722
3696
|
if (!conn.sock || !onInboundMessage) return;
|
|
3723
3697
|
const sock = conn.sock;
|
|
@@ -3926,8 +3900,8 @@ async function handleInboundMessage(conn, msg) {
|
|
|
3926
3900
|
const conversationKey = isGroup ? remoteJid : senderPhone;
|
|
3927
3901
|
const debounceKey = `${conn.accountId}:${conversationKey}:${senderPhone}`;
|
|
3928
3902
|
let resolvePending;
|
|
3929
|
-
const sttPending = new Promise((
|
|
3930
|
-
resolvePending =
|
|
3903
|
+
const sttPending = new Promise((resolve28) => {
|
|
3904
|
+
resolvePending = resolve28;
|
|
3931
3905
|
});
|
|
3932
3906
|
if (conn.debouncer) conn.debouncer.registerPending(debounceKey, sttPending);
|
|
3933
3907
|
try {
|
|
@@ -3941,24 +3915,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
3941
3915
|
}
|
|
3942
3916
|
let accessResult;
|
|
3943
3917
|
if (isGroup) {
|
|
3944
|
-
accessResult = checkGroupAccess({
|
|
3945
|
-
senderPhone,
|
|
3946
|
-
selfPhone,
|
|
3947
|
-
groupJid: remoteJid,
|
|
3948
|
-
// Three-way mention recognition (Sub-fix 4): legacy phone-JID equality,
|
|
3949
|
-
// legacy E.164-via-jidToE164 equality, plus the new self-LID equality so
|
|
3950
|
-
// mentions in LID-addressed groups (deliver as `<self-lid>@lid`) match.
|
|
3951
|
-
// Pre-LID groups continue matching the first two — additive, never blocks.
|
|
3952
|
-
// Each clause guards against null on its right operand; a creds-parse
|
|
3953
|
-
// failure leaves all three of selfJid/selfLid/selfPhone null and
|
|
3954
|
-
// jidToE164 of a malformed mention also returns null — without the
|
|
3955
|
-
// guards, `null === null` would spuriously match every nullified field.
|
|
3956
|
-
isMentioned: extracted.mentionedJids.some(
|
|
3957
|
-
(jid) => conn.selfJid !== null && jid === conn.selfJid || conn.selfLid !== null && jid === conn.selfLid || conn.selfPhone !== null && jidToE164(jid) === conn.selfPhone
|
|
3958
|
-
),
|
|
3959
|
-
config: whatsAppConfig,
|
|
3960
|
-
accountConfig: whatsAppConfig.accounts?.[conn.accountId]
|
|
3961
|
-
});
|
|
3918
|
+
accessResult = checkGroupAccess({ groupJid: remoteJid });
|
|
3962
3919
|
} else {
|
|
3963
3920
|
accessResult = checkDmAccess({
|
|
3964
3921
|
senderPhone,
|
|
@@ -3971,18 +3928,20 @@ async function handleInboundMessage(conn, msg) {
|
|
|
3971
3928
|
`${TAG12} inbound account=${conn.accountId} from=${senderPhone} group=${isGroup} access=${accessResult.allowed ? "allowed" : "blocked"}(${accessResult.reason}) agent=${accessResult.agentType}` + (extracted.mediaType ? ` media=${extracted.mediaType}` : "") + (mediaResult ? ` mediaPath=${mediaResult.path}` : "") + (extracted.quotedMessage ? ` replyTo=${extracted.quotedMessage.id}` : "")
|
|
3972
3929
|
);
|
|
3973
3930
|
if (!accessResult.allowed) return;
|
|
3974
|
-
let groupSubject;
|
|
3975
|
-
if (isGroup) {
|
|
3976
|
-
const groupMeta = await getGroupMeta(conn, remoteJid);
|
|
3977
|
-
groupSubject = groupMeta?.subject;
|
|
3978
|
-
}
|
|
3979
3931
|
const sendReceipts = whatsAppConfig.accounts?.[conn.accountId]?.sendReadReceipts ?? whatsAppConfig.sendReadReceipts ?? true;
|
|
3980
3932
|
if (sendReceipts && msg.key.id) {
|
|
3981
3933
|
sendReadReceipt(conn.sock, remoteJid, [msg.key.id], isGroup ? senderJid : void 0);
|
|
3982
3934
|
}
|
|
3983
3935
|
const reply = async (text) => {
|
|
3936
|
+
if (isGroupJid(remoteJid)) {
|
|
3937
|
+
console.error(`${TAG12} op=group-reply-blocked jid=${remoteJid}`);
|
|
3938
|
+
throw new WaReplyError("group reply blocked \u2014 observe-only", false);
|
|
3939
|
+
}
|
|
3984
3940
|
const currentSock = conn.sock;
|
|
3985
|
-
if (!currentSock)
|
|
3941
|
+
if (!currentSock) {
|
|
3942
|
+
const reason = conn.terminalReason;
|
|
3943
|
+
throw new WaReplyError(reason ?? "WhatsApp disconnected \u2014 cannot reply", Boolean(reason));
|
|
3944
|
+
}
|
|
3986
3945
|
await sendTextMessage(currentSock, remoteJid, text, { accountId: conn.accountId });
|
|
3987
3946
|
};
|
|
3988
3947
|
const composing = async () => {
|
|
@@ -4004,7 +3963,6 @@ async function handleInboundMessage(conn, msg) {
|
|
|
4004
3963
|
text: extracted.text,
|
|
4005
3964
|
isGroup,
|
|
4006
3965
|
groupJid: isGroup ? remoteJid : void 0,
|
|
4007
|
-
groupSubject,
|
|
4008
3966
|
reply,
|
|
4009
3967
|
composing,
|
|
4010
3968
|
cacheKey,
|
|
@@ -4334,20 +4292,20 @@ function buildX11Env(chromiumWrapperPath, transport = "vnc") {
|
|
|
4334
4292
|
|
|
4335
4293
|
// server/routes/health.ts
|
|
4336
4294
|
function checkPort(port2, timeoutMs = 500) {
|
|
4337
|
-
return new Promise((
|
|
4295
|
+
return new Promise((resolve28) => {
|
|
4338
4296
|
const socket = createConnection2(port2, "127.0.0.1");
|
|
4339
4297
|
socket.setTimeout(timeoutMs);
|
|
4340
4298
|
socket.once("connect", () => {
|
|
4341
4299
|
socket.destroy();
|
|
4342
|
-
|
|
4300
|
+
resolve28(true);
|
|
4343
4301
|
});
|
|
4344
4302
|
socket.once("error", () => {
|
|
4345
4303
|
socket.destroy();
|
|
4346
|
-
|
|
4304
|
+
resolve28(false);
|
|
4347
4305
|
});
|
|
4348
4306
|
socket.once("timeout", () => {
|
|
4349
4307
|
socket.destroy();
|
|
4350
|
-
|
|
4308
|
+
resolve28(false);
|
|
4351
4309
|
});
|
|
4352
4310
|
});
|
|
4353
4311
|
}
|
|
@@ -4383,13 +4341,15 @@ app.get("/", async (c) => {
|
|
|
4383
4341
|
selfPhone: a.selfPhone ?? null,
|
|
4384
4342
|
reconnectAttempts: a.reconnectAttempts,
|
|
4385
4343
|
lastError: a.lastError ?? null,
|
|
4386
|
-
sessionStuckReason: a.sessionStuckReason ?? null
|
|
4344
|
+
sessionStuckReason: a.sessionStuckReason ?? null,
|
|
4345
|
+
terminalReason: a.terminalReason ?? null
|
|
4387
4346
|
}));
|
|
4388
4347
|
} catch (err) {
|
|
4389
4348
|
console.error(`[health] failed to read WhatsApp status: ${err instanceof Error ? err.message : String(err)}`);
|
|
4390
4349
|
}
|
|
4391
4350
|
const whatsappAnyConnected = whatsappAccounts.some((a) => a.connected);
|
|
4392
4351
|
const whatsappAnyStuck = whatsappAccounts.some((a) => Boolean(a.sessionStuckReason));
|
|
4352
|
+
const whatsappAnyTerminal = whatsappAccounts.some((a) => Boolean(a.terminalReason));
|
|
4393
4353
|
const account = resolveAccount();
|
|
4394
4354
|
const missingPlugins = findMissingPlugins(account?.config.enabledPlugins);
|
|
4395
4355
|
return c.json({
|
|
@@ -4404,6 +4364,7 @@ app.get("/", async (c) => {
|
|
|
4404
4364
|
whatsapp: {
|
|
4405
4365
|
any_connected: whatsappAnyConnected,
|
|
4406
4366
|
any_stuck: whatsappAnyStuck,
|
|
4367
|
+
any_terminal: whatsappAnyTerminal,
|
|
4407
4368
|
accounts: whatsappAccounts
|
|
4408
4369
|
}
|
|
4409
4370
|
});
|
|
@@ -4677,7 +4638,10 @@ async function managerRcSpawn(opts) {
|
|
|
4677
4638
|
sliceToken: opts.sliceToken,
|
|
4678
4639
|
personId: opts.personId,
|
|
4679
4640
|
userId: opts.userId,
|
|
4680
|
-
waChannel: opts.waChannel
|
|
4641
|
+
waChannel: opts.waChannel,
|
|
4642
|
+
role: opts.role,
|
|
4643
|
+
channel: opts.channel,
|
|
4644
|
+
senderId: opts.senderId
|
|
4681
4645
|
})
|
|
4682
4646
|
}).catch((err) => ({ __throw: err instanceof Error ? err.message : String(err) }));
|
|
4683
4647
|
if ("__throw" in res) {
|
|
@@ -5286,6 +5250,13 @@ async function ensureEntry(input) {
|
|
|
5286
5250
|
spawned = await managerRcSpawn({
|
|
5287
5251
|
sessionId: adminSessionId,
|
|
5288
5252
|
name: nameValue ?? void 0,
|
|
5253
|
+
// Task 724 — pass the real channel + role + senderId so the manager writes
|
|
5254
|
+
// the classification sidecar for this admin channel (webchat, email, …),
|
|
5255
|
+
// not just WhatsApp. Without these the live session was left sidecar-less
|
|
5256
|
+
// and the sidebar channel badge / archive classification mis-read it.
|
|
5257
|
+
role: "admin",
|
|
5258
|
+
channel: input.channel,
|
|
5259
|
+
senderId: input.senderId,
|
|
5289
5260
|
logContext: `channel=${input.channel} senderId=${input.senderId}`
|
|
5290
5261
|
});
|
|
5291
5262
|
} else {
|
|
@@ -5422,12 +5393,12 @@ async function dispatchOnce(input) {
|
|
|
5422
5393
|
});
|
|
5423
5394
|
if (!entry) return { error: "spawn-failed" };
|
|
5424
5395
|
entry.lastInboundAt = Date.now();
|
|
5425
|
-
let
|
|
5396
|
+
let resolve28;
|
|
5426
5397
|
const turnPromise = new Promise((r) => {
|
|
5427
|
-
|
|
5398
|
+
resolve28 = r;
|
|
5428
5399
|
});
|
|
5429
5400
|
const listener = (text) => {
|
|
5430
|
-
|
|
5401
|
+
resolve28(text);
|
|
5431
5402
|
};
|
|
5432
5403
|
entry.subscribers.add(listener);
|
|
5433
5404
|
const writeAtMs = Date.now();
|
|
@@ -5987,16 +5958,6 @@ function resolvePublicAgent(accountDir, opts) {
|
|
|
5987
5958
|
const config = readConfig(accountDir);
|
|
5988
5959
|
const wa = config.whatsapp;
|
|
5989
5960
|
if (!wa) return null;
|
|
5990
|
-
if (opts.groupJid) {
|
|
5991
|
-
const accounts2 = wa.accounts;
|
|
5992
|
-
const account2 = accounts2?.[opts.accountId];
|
|
5993
|
-
const groups = account2?.groups;
|
|
5994
|
-
const group = groups?.[opts.groupJid];
|
|
5995
|
-
const groupSlug = group?.publicAgent;
|
|
5996
|
-
if (typeof groupSlug === "string" && groupSlug.trim()) {
|
|
5997
|
-
return { slug: groupSlug.trim(), source: "group" };
|
|
5998
|
-
}
|
|
5999
|
-
}
|
|
6000
5961
|
const accounts = wa.accounts;
|
|
6001
5962
|
const account = accounts?.[opts.accountId];
|
|
6002
5963
|
const accountSlug = account?.publicAgent;
|
|
@@ -6012,77 +5973,6 @@ function resolvePublicAgent(accountDir, opts) {
|
|
|
6012
5973
|
return null;
|
|
6013
5974
|
}
|
|
6014
5975
|
}
|
|
6015
|
-
function setGroupPublicAgent(accountDir, accountId, groupJid, slug) {
|
|
6016
|
-
const trimmedSlug = slug.trim();
|
|
6017
|
-
const trimmedGroup = groupJid.trim();
|
|
6018
|
-
const trimmedAccount = accountId.trim();
|
|
6019
|
-
if (!trimmedSlug) return { ok: false, error: "Agent slug cannot be empty." };
|
|
6020
|
-
if (!trimmedGroup) return { ok: false, error: "Group JID cannot be empty." };
|
|
6021
|
-
if (!trimmedAccount) return { ok: false, error: "Account ID cannot be empty." };
|
|
6022
|
-
const agentConfigPath = join4(accountDir, "agents", trimmedSlug, "config.json");
|
|
6023
|
-
if (!existsSync4(agentConfigPath)) {
|
|
6024
|
-
return { ok: false, error: `Agent "${trimmedSlug}" not found \u2014 no config.json at ${agentConfigPath}. Check the agent slug and try again.` };
|
|
6025
|
-
}
|
|
6026
|
-
try {
|
|
6027
|
-
const config = readConfig(accountDir);
|
|
6028
|
-
if (!config.whatsapp || typeof config.whatsapp !== "object") config.whatsapp = {};
|
|
6029
|
-
const wa = config.whatsapp;
|
|
6030
|
-
if (!wa.accounts || typeof wa.accounts !== "object") wa.accounts = {};
|
|
6031
|
-
const accounts = wa.accounts;
|
|
6032
|
-
if (!accounts[trimmedAccount] || typeof accounts[trimmedAccount] !== "object") accounts[trimmedAccount] = {};
|
|
6033
|
-
const account = accounts[trimmedAccount];
|
|
6034
|
-
if (!account.groups || typeof account.groups !== "object") account.groups = {};
|
|
6035
|
-
const groups = account.groups;
|
|
6036
|
-
const existing = groups[trimmedGroup] && typeof groups[trimmedGroup] === "object" ? groups[trimmedGroup] : {};
|
|
6037
|
-
groups[trimmedGroup] = { ...existing, publicAgent: trimmedSlug };
|
|
6038
|
-
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
6039
|
-
if (!parsed.success) {
|
|
6040
|
-
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
6041
|
-
return { ok: false, error: `Validation failed: ${msg}` };
|
|
6042
|
-
}
|
|
6043
|
-
config.whatsapp = parsed.data;
|
|
6044
|
-
writeConfig(accountDir, config);
|
|
6045
|
-
console.error(`${TAG17} setGroupPublicAgent account=${trimmedAccount} groupJid=${trimmedGroup} slug=${trimmedSlug}`);
|
|
6046
|
-
reloadManagerConfig(accountDir);
|
|
6047
|
-
return { ok: true, message: `Per-group public agent set to "${trimmedSlug}" for group ${trimmedGroup}.` };
|
|
6048
|
-
} catch (err) {
|
|
6049
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
6050
|
-
console.error(`${TAG17} setGroupPublicAgent failed: ${msg}`);
|
|
6051
|
-
return { ok: false, error: msg };
|
|
6052
|
-
}
|
|
6053
|
-
}
|
|
6054
|
-
function unsetGroupPublicAgent(accountDir, accountId, groupJid) {
|
|
6055
|
-
const trimmedGroup = groupJid.trim();
|
|
6056
|
-
const trimmedAccount = accountId.trim();
|
|
6057
|
-
if (!trimmedGroup) return { ok: false, error: "Group JID cannot be empty." };
|
|
6058
|
-
if (!trimmedAccount) return { ok: false, error: "Account ID cannot be empty." };
|
|
6059
|
-
try {
|
|
6060
|
-
const config = readConfig(accountDir);
|
|
6061
|
-
const wa = config.whatsapp ?? {};
|
|
6062
|
-
const accounts = wa.accounts;
|
|
6063
|
-
const account = accounts?.[trimmedAccount];
|
|
6064
|
-
const groups = account?.groups;
|
|
6065
|
-
const group = groups?.[trimmedGroup];
|
|
6066
|
-
if (!group || typeof group.publicAgent !== "string") {
|
|
6067
|
-
return { ok: true, message: `No per-group publicAgent override for group ${trimmedGroup}; nothing to remove.` };
|
|
6068
|
-
}
|
|
6069
|
-
delete group.publicAgent;
|
|
6070
|
-
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
6071
|
-
if (!parsed.success) {
|
|
6072
|
-
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
6073
|
-
return { ok: false, error: `Validation failed: ${msg}` };
|
|
6074
|
-
}
|
|
6075
|
-
config.whatsapp = parsed.data;
|
|
6076
|
-
writeConfig(accountDir, config);
|
|
6077
|
-
console.error(`${TAG17} unsetGroupPublicAgent account=${trimmedAccount} groupJid=${trimmedGroup}`);
|
|
6078
|
-
reloadManagerConfig(accountDir);
|
|
6079
|
-
return { ok: true, message: `Per-group public agent override removed for group ${trimmedGroup}.` };
|
|
6080
|
-
} catch (err) {
|
|
6081
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
6082
|
-
console.error(`${TAG17} unsetGroupPublicAgent failed: ${msg}`);
|
|
6083
|
-
return { ok: false, error: msg };
|
|
6084
|
-
}
|
|
6085
|
-
}
|
|
6086
5976
|
function updateConfig(accountDir, fields) {
|
|
6087
5977
|
const fieldNames = Object.keys(fields);
|
|
6088
5978
|
if (fieldNames.length === 0) {
|
|
@@ -6126,6 +6016,35 @@ function getConfig(accountDir) {
|
|
|
6126
6016
|
return null;
|
|
6127
6017
|
}
|
|
6128
6018
|
}
|
|
6019
|
+
function migrateRemovedConfigKeys(accountDir) {
|
|
6020
|
+
try {
|
|
6021
|
+
const config = readConfig(accountDir);
|
|
6022
|
+
const wa = config.whatsapp;
|
|
6023
|
+
if (!wa || typeof wa !== "object") return { dropped: [] };
|
|
6024
|
+
const result = parseWhatsAppConfigTolerant(wa);
|
|
6025
|
+
if (!result.ok || result.droppedKeys.length === 0) return { dropped: [] };
|
|
6026
|
+
const block = wa;
|
|
6027
|
+
for (const dotted of result.droppedKeys) {
|
|
6028
|
+
const segs = dotted.split(".");
|
|
6029
|
+
const key = segs.pop();
|
|
6030
|
+
const container = segs.reduce(
|
|
6031
|
+
(acc, seg) => acc && typeof acc === "object" ? acc[seg] : void 0,
|
|
6032
|
+
block
|
|
6033
|
+
);
|
|
6034
|
+
if (container && typeof container === "object") {
|
|
6035
|
+
delete container[key];
|
|
6036
|
+
}
|
|
6037
|
+
}
|
|
6038
|
+
writeConfig(accountDir, config);
|
|
6039
|
+
console.error(
|
|
6040
|
+
`${TAG17} migration: stripped unknown keys=[${result.droppedKeys.join(",")}] from account.json`
|
|
6041
|
+
);
|
|
6042
|
+
return { dropped: result.droppedKeys };
|
|
6043
|
+
} catch (err) {
|
|
6044
|
+
console.error(`${TAG17} migration failed: ${String(err)}`);
|
|
6045
|
+
return { dropped: [] };
|
|
6046
|
+
}
|
|
6047
|
+
}
|
|
6129
6048
|
|
|
6130
6049
|
// app/lib/whatsapp/login.ts
|
|
6131
6050
|
var TAG18 = "[whatsapp:login]";
|
|
@@ -6265,8 +6184,8 @@ async function startLogin(opts) {
|
|
|
6265
6184
|
await clearAuth(authDir);
|
|
6266
6185
|
let resolveCode = null;
|
|
6267
6186
|
let rejectCode = null;
|
|
6268
|
-
const codePromise = new Promise((
|
|
6269
|
-
resolveCode =
|
|
6187
|
+
const codePromise = new Promise((resolve28, reject) => {
|
|
6188
|
+
resolveCode = resolve28;
|
|
6270
6189
|
rejectCode = reject;
|
|
6271
6190
|
});
|
|
6272
6191
|
const codeTimer = setTimeout(
|
|
@@ -6615,29 +6534,10 @@ app3.post("/reconnect", async (c) => {
|
|
|
6615
6534
|
return c.json({ error: String(err) }, 500);
|
|
6616
6535
|
}
|
|
6617
6536
|
});
|
|
6618
|
-
app3.post("/send", async (c) => {
|
|
6619
|
-
try {
|
|
6620
|
-
const body = await c.req.json();
|
|
6621
|
-
const { to, text } = body;
|
|
6622
|
-
const accountId = validateAccountId(body.accountId);
|
|
6623
|
-
if (!to || !text) {
|
|
6624
|
-
return c.json({ error: "Missing required fields: to, text" }, 400);
|
|
6625
|
-
}
|
|
6626
|
-
const sock = getSocket(accountId);
|
|
6627
|
-
if (!sock) {
|
|
6628
|
-
return c.json({ error: `WhatsApp account "${accountId}" is not connected` }, 503);
|
|
6629
|
-
}
|
|
6630
|
-
const result = await sendTextMessage(sock, to, text, { accountId });
|
|
6631
|
-
return c.json(result);
|
|
6632
|
-
} catch (err) {
|
|
6633
|
-
console.error(`${TAG20} send error: ${String(err)}`);
|
|
6634
|
-
return c.json({ error: String(err) }, 500);
|
|
6635
|
-
}
|
|
6636
|
-
});
|
|
6637
6537
|
app3.post("/config", async (c) => {
|
|
6638
6538
|
try {
|
|
6639
6539
|
const body = await c.req.json().catch(() => ({}));
|
|
6640
|
-
const { action, phone, slug,
|
|
6540
|
+
const { action, phone, slug, fields, accountId } = body;
|
|
6641
6541
|
if (!action || typeof action !== "string") {
|
|
6642
6542
|
return c.json({ ok: false, error: 'Missing required field "action".' }, 400);
|
|
6643
6543
|
}
|
|
@@ -6677,32 +6577,10 @@ app3.post("/config", async (c) => {
|
|
|
6677
6577
|
}
|
|
6678
6578
|
case "get-public-agent": {
|
|
6679
6579
|
const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
|
|
6680
|
-
const
|
|
6681
|
-
|
|
6682
|
-
console.error(`${TAG20} config action=get-public-agent accountId=${targetAccount} groupJid=${targetGroup ?? "none"} slug=${resolved?.slug ?? "none"} source=${resolved?.source ?? "none"}`);
|
|
6580
|
+
const resolved = resolvePublicAgent(account.accountDir, { accountId: targetAccount });
|
|
6581
|
+
console.error(`${TAG20} config action=get-public-agent accountId=${targetAccount} slug=${resolved?.slug ?? "none"} source=${resolved?.source ?? "none"}`);
|
|
6683
6582
|
return c.json({ ok: true, slug: resolved?.slug ?? null, source: resolved?.source ?? null });
|
|
6684
6583
|
}
|
|
6685
|
-
case "set-group-public-agent": {
|
|
6686
|
-
if (!slug || typeof slug !== "string") {
|
|
6687
|
-
return c.json({ ok: false, error: 'Missing required field "slug" (the public-agent directory name, e.g. "my-agent").' }, 400);
|
|
6688
|
-
}
|
|
6689
|
-
if (!groupJid || typeof groupJid !== "string") {
|
|
6690
|
-
return c.json({ ok: false, error: 'Missing required field "groupJid" (group JID ending in @g.us).' }, 400);
|
|
6691
|
-
}
|
|
6692
|
-
const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
|
|
6693
|
-
const result = setGroupPublicAgent(account.accountDir, targetAccount, groupJid, slug);
|
|
6694
|
-
console.error(`${TAG20} config action=set-group-public-agent accountId=${targetAccount} groupJid=${groupJid} slug=${slug} ok=${result.ok}`);
|
|
6695
|
-
return c.json(result, result.ok ? 200 : 400);
|
|
6696
|
-
}
|
|
6697
|
-
case "unset-group-public-agent": {
|
|
6698
|
-
if (!groupJid || typeof groupJid !== "string") {
|
|
6699
|
-
return c.json({ ok: false, error: 'Missing required field "groupJid" (group JID ending in @g.us).' }, 400);
|
|
6700
|
-
}
|
|
6701
|
-
const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
|
|
6702
|
-
const result = unsetGroupPublicAgent(account.accountDir, targetAccount, groupJid);
|
|
6703
|
-
console.error(`${TAG20} config action=unset-group-public-agent accountId=${targetAccount} groupJid=${groupJid} ok=${result.ok}`);
|
|
6704
|
-
return c.json(result, result.ok ? 200 : 400);
|
|
6705
|
-
}
|
|
6706
6584
|
case "list-public-agents": {
|
|
6707
6585
|
const agentsDir = resolve7(account.accountDir, "agents");
|
|
6708
6586
|
const agents = [];
|
|
@@ -6769,7 +6647,7 @@ app3.post("/config", async (c) => {
|
|
|
6769
6647
|
}
|
|
6770
6648
|
default:
|
|
6771
6649
|
return c.json(
|
|
6772
|
-
{ ok: false, error: `Unknown action "${action}". Valid actions: add-admin-phone, remove-admin-phone, list-admin-phones, set-public-agent, get-public-agent,
|
|
6650
|
+
{ 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.` },
|
|
6773
6651
|
400
|
|
6774
6652
|
);
|
|
6775
6653
|
}
|
|
@@ -7018,7 +6896,7 @@ function enumerateJsonls(projectsRoot) {
|
|
|
7018
6896
|
}
|
|
7019
6897
|
for (const entry of entries) {
|
|
7020
6898
|
if (entry.isFile() && SESSION_ID_RE.test(entry.name)) {
|
|
7021
|
-
out.push({ path: join7(slugDir, entry.name), isSubagent: false });
|
|
6899
|
+
out.push({ path: join7(slugDir, entry.name), isSubagent: false, archived: false });
|
|
7022
6900
|
} else if (entry.isDirectory() && entry.name === "subagents") {
|
|
7023
6901
|
const subDir = join7(slugDir, entry.name);
|
|
7024
6902
|
let subEntries;
|
|
@@ -7031,7 +6909,22 @@ function enumerateJsonls(projectsRoot) {
|
|
|
7031
6909
|
}
|
|
7032
6910
|
for (const sub of subEntries) {
|
|
7033
6911
|
if (sub.isFile() && SESSION_ID_RE.test(sub.name)) {
|
|
7034
|
-
out.push({ path: join7(subDir, sub.name), isSubagent: true });
|
|
6912
|
+
out.push({ path: join7(subDir, sub.name), isSubagent: true, archived: false });
|
|
6913
|
+
}
|
|
6914
|
+
}
|
|
6915
|
+
} else if (entry.isDirectory() && entry.name === "archive") {
|
|
6916
|
+
const archiveDir = join7(slugDir, entry.name);
|
|
6917
|
+
let archiveEntries;
|
|
6918
|
+
try {
|
|
6919
|
+
archiveEntries = readdirSync4(archiveDir, { withFileTypes: true });
|
|
6920
|
+
} catch (err) {
|
|
6921
|
+
const code = err.code ?? "unknown";
|
|
6922
|
+
console.error(`[admin-sessions-list] archive-skipped slug=${slug} code=${code}`);
|
|
6923
|
+
continue;
|
|
6924
|
+
}
|
|
6925
|
+
for (const arc of archiveEntries) {
|
|
6926
|
+
if (arc.isFile() && SESSION_ID_RE.test(arc.name)) {
|
|
6927
|
+
out.push({ path: join7(archiveDir, arc.name), isSubagent: false, archived: true });
|
|
7035
6928
|
}
|
|
7036
6929
|
}
|
|
7037
6930
|
}
|
|
@@ -7171,8 +7064,9 @@ app4.get("/", requireAdminSession, async (c) => {
|
|
|
7171
7064
|
let liveCount = 0;
|
|
7172
7065
|
let subagentCount = 0;
|
|
7173
7066
|
let nonResumableCount = 0;
|
|
7067
|
+
let archivedCount = 0;
|
|
7174
7068
|
const titleSourceCounts = { user: 0, ai: 0, "first-message": 0, prefix: 0 };
|
|
7175
|
-
for (const { path: path2, isSubagent } of jsonls) {
|
|
7069
|
+
for (const { path: path2, isSubagent, archived } of jsonls) {
|
|
7176
7070
|
const lastSlash = path2.lastIndexOf("/");
|
|
7177
7071
|
const base = path2.slice(lastSlash + 1);
|
|
7178
7072
|
const projectDir = path2.slice(0, lastSlash);
|
|
@@ -7207,15 +7101,17 @@ app4.get("/", requireAdminSession, async (c) => {
|
|
|
7207
7101
|
pid,
|
|
7208
7102
|
projectDir,
|
|
7209
7103
|
bridgeIds,
|
|
7210
|
-
resumable
|
|
7104
|
+
resumable,
|
|
7105
|
+
archived
|
|
7211
7106
|
});
|
|
7212
7107
|
if (live) liveCount += 1;
|
|
7213
7108
|
if (isSubagent) subagentCount += 1;
|
|
7214
7109
|
if (!resumable) nonResumableCount += 1;
|
|
7110
|
+
if (archived) archivedCount += 1;
|
|
7215
7111
|
}
|
|
7216
7112
|
rows.sort((a, b) => a.startedAt < b.startedAt ? 1 : -1);
|
|
7217
7113
|
console.log(
|
|
7218
|
-
`[admin-sessions-list] rows=${rows.length} live=${liveCount} subagents=${subagentCount} nonResumable=${nonResumableCount} titles user=${titleSourceCounts.user} ai=${titleSourceCounts.ai} first=${titleSourceCounts["first-message"]} prefix=${titleSourceCounts.prefix} accountId=${accountId ? accountId.slice(0, 8) : "unset"}`
|
|
7114
|
+
`[admin-sessions-list] rows=${rows.length} live=${liveCount} subagents=${subagentCount} archived=${archivedCount} nonResumable=${nonResumableCount} titles user=${titleSourceCounts.user} ai=${titleSourceCounts.ai} first=${titleSourceCounts["first-message"]} prefix=${titleSourceCounts.prefix} accountId=${accountId ? accountId.slice(0, 8) : "unset"}`
|
|
7219
7115
|
);
|
|
7220
7116
|
return c.json({ sessions: rows, accountId });
|
|
7221
7117
|
});
|
|
@@ -7427,14 +7323,168 @@ app5.get("/stream", requireAdminSession, (c) => {
|
|
|
7427
7323
|
});
|
|
7428
7324
|
var whatsapp_reader_default = app5;
|
|
7429
7325
|
|
|
7326
|
+
// server/routes/telegram.ts
|
|
7327
|
+
import { homedir } from "os";
|
|
7328
|
+
import { resolve as resolve10, join as join9 } from "path";
|
|
7329
|
+
import { existsSync as existsSync6, readFileSync as readFileSync10 } from "fs";
|
|
7330
|
+
|
|
7331
|
+
// app/lib/telegram/access-control.ts
|
|
7332
|
+
function checkTelegramAccess(params) {
|
|
7333
|
+
const { senderId, botType, config } = params;
|
|
7334
|
+
if (botType === "admin") {
|
|
7335
|
+
const adminUsers = config.adminUsers ?? [];
|
|
7336
|
+
if (adminUsers.includes(senderId)) {
|
|
7337
|
+
return { allowed: true, reason: "admin-binding", agentType: "admin" };
|
|
7338
|
+
}
|
|
7339
|
+
return { allowed: false, reason: "not-admin-user", agentType: "admin" };
|
|
7340
|
+
}
|
|
7341
|
+
const policy = config.dmPolicy ?? "disabled";
|
|
7342
|
+
switch (policy) {
|
|
7343
|
+
case "open":
|
|
7344
|
+
return { allowed: true, reason: "dm-policy-open", agentType: "public" };
|
|
7345
|
+
case "allowlist": {
|
|
7346
|
+
const allowFrom = config.allowFrom ?? [];
|
|
7347
|
+
if (allowFrom.includes(senderId)) {
|
|
7348
|
+
return { allowed: true, reason: "allowlist-match", agentType: "public" };
|
|
7349
|
+
}
|
|
7350
|
+
return { allowed: false, reason: "not-in-allowlist", agentType: "public" };
|
|
7351
|
+
}
|
|
7352
|
+
case "disabled":
|
|
7353
|
+
return { allowed: false, reason: "dm-policy-disabled", agentType: "public" };
|
|
7354
|
+
default:
|
|
7355
|
+
return { allowed: false, reason: "unknown-dm-policy", agentType: "public" };
|
|
7356
|
+
}
|
|
7357
|
+
}
|
|
7358
|
+
|
|
7359
|
+
// app/lib/telegram/route-inbound.ts
|
|
7360
|
+
function routeTelegramUpdate(input) {
|
|
7361
|
+
const m = input.update.message;
|
|
7362
|
+
if (!m || typeof m.from?.id !== "number" || typeof m.chat?.id !== "number") {
|
|
7363
|
+
return { kind: "ignore", reason: "no-message" };
|
|
7364
|
+
}
|
|
7365
|
+
if (typeof m.text !== "string" || m.text.length === 0) {
|
|
7366
|
+
return { kind: "ignore", reason: "no-text" };
|
|
7367
|
+
}
|
|
7368
|
+
const access = checkTelegramAccess({ senderId: m.from.id, botType: input.botType, config: input.config });
|
|
7369
|
+
if (!access.allowed) {
|
|
7370
|
+
return { kind: "denied", reason: access.reason, agentType: access.agentType };
|
|
7371
|
+
}
|
|
7372
|
+
return { kind: "dispatch", senderId: String(m.from.id), chatId: m.chat.id, text: m.text, agentType: access.agentType };
|
|
7373
|
+
}
|
|
7374
|
+
|
|
7375
|
+
// server/routes/telegram.ts
|
|
7376
|
+
var TAG21 = "[telegram-inbound]";
|
|
7377
|
+
var DISPATCH_TIMEOUT_MS = 12e4;
|
|
7378
|
+
function configDirName() {
|
|
7379
|
+
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? resolve10(process.cwd(), "..");
|
|
7380
|
+
const brandPath = join9(platformRoot2, "config", "brand.json");
|
|
7381
|
+
if (existsSync6(brandPath)) {
|
|
7382
|
+
try {
|
|
7383
|
+
return JSON.parse(readFileSync10(brandPath, "utf-8")).configDir ?? ".maxy";
|
|
7384
|
+
} catch {
|
|
7385
|
+
}
|
|
7386
|
+
}
|
|
7387
|
+
return ".maxy";
|
|
7388
|
+
}
|
|
7389
|
+
function secretPath(botType) {
|
|
7390
|
+
const filename = botType === "admin" ? ".telegram-admin-webhook-secret" : ".telegram-webhook-secret";
|
|
7391
|
+
return resolve10(homedir(), configDirName(), filename);
|
|
7392
|
+
}
|
|
7393
|
+
async function sendTelegram(botToken, chatId, text) {
|
|
7394
|
+
try {
|
|
7395
|
+
const res = await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {
|
|
7396
|
+
method: "POST",
|
|
7397
|
+
headers: { "Content-Type": "application/json" },
|
|
7398
|
+
body: JSON.stringify({ chat_id: chatId, text })
|
|
7399
|
+
});
|
|
7400
|
+
const data = await res.json();
|
|
7401
|
+
return data.ok ? { ok: true } : { ok: false, error: data.description ?? "send failed" };
|
|
7402
|
+
} catch (e) {
|
|
7403
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
7404
|
+
}
|
|
7405
|
+
}
|
|
7406
|
+
var app6 = new Hono();
|
|
7407
|
+
app6.post("/", async (c) => {
|
|
7408
|
+
const botParam = c.req.query("bot");
|
|
7409
|
+
const botType = botParam === "admin" ? "admin" : botParam === "public" ? "public" : null;
|
|
7410
|
+
if (!botType) {
|
|
7411
|
+
console.error(`${TAG21} op=reject reason=missing-bot-param`);
|
|
7412
|
+
return c.json({ ok: false }, 400);
|
|
7413
|
+
}
|
|
7414
|
+
const sp = secretPath(botType);
|
|
7415
|
+
if (!existsSync6(sp)) {
|
|
7416
|
+
console.error(`${TAG21} op=reject reason=no-secret-file botType=${botType}`);
|
|
7417
|
+
return c.json({ ok: false }, 401);
|
|
7418
|
+
}
|
|
7419
|
+
const expected = readFileSync10(sp, "utf-8").trim();
|
|
7420
|
+
const got = c.req.header("x-telegram-bot-api-secret-token") ?? "";
|
|
7421
|
+
if (got !== expected) {
|
|
7422
|
+
console.error(`${TAG21} op=reject reason=bad-secret botType=${botType}`);
|
|
7423
|
+
return c.json({ ok: false }, 401);
|
|
7424
|
+
}
|
|
7425
|
+
let update;
|
|
7426
|
+
try {
|
|
7427
|
+
update = await c.req.json();
|
|
7428
|
+
} catch {
|
|
7429
|
+
return c.json({ ok: true }, 200);
|
|
7430
|
+
}
|
|
7431
|
+
const account = resolveAccount();
|
|
7432
|
+
if (!account) {
|
|
7433
|
+
console.error(`${TAG21} op=reject reason=no-account`);
|
|
7434
|
+
return c.json({ ok: true }, 200);
|
|
7435
|
+
}
|
|
7436
|
+
const decision = routeTelegramUpdate({ update, botType, config: account.config.telegram ?? {} });
|
|
7437
|
+
if (decision.kind === "ignore") {
|
|
7438
|
+
console.error(`${TAG21} op=ignore reason=${decision.reason}`);
|
|
7439
|
+
return c.json({ ok: true }, 200);
|
|
7440
|
+
}
|
|
7441
|
+
if (decision.kind === "denied") {
|
|
7442
|
+
console.error(`${TAG21} op=denied reason=${decision.reason} agentType=${decision.agentType}`);
|
|
7443
|
+
return c.json({ ok: true }, 200);
|
|
7444
|
+
}
|
|
7445
|
+
const role = decision.agentType === "admin" ? "admin" : "public";
|
|
7446
|
+
const agentSlug = role === "admin" ? "admin" : resolveDefaultAgentSlug(account.accountDir);
|
|
7447
|
+
if (!agentSlug) {
|
|
7448
|
+
console.error(`${TAG21} op=reject reason=no-default-agent`);
|
|
7449
|
+
return c.json({ ok: true }, 200);
|
|
7450
|
+
}
|
|
7451
|
+
console.error(`${TAG21} op=update accountId=${account.accountId} from=${decision.senderId}`);
|
|
7452
|
+
const botToken = botType === "admin" ? account.config.telegram?.adminBotToken : account.config.telegram?.publicBotToken;
|
|
7453
|
+
const { senderId, chatId, text } = decision;
|
|
7454
|
+
void (async () => {
|
|
7455
|
+
const result = await dispatchOnce({
|
|
7456
|
+
accountId: account.accountId,
|
|
7457
|
+
senderId,
|
|
7458
|
+
role,
|
|
7459
|
+
channel: "telegram",
|
|
7460
|
+
agentSlug,
|
|
7461
|
+
text,
|
|
7462
|
+
timeoutMs: DISPATCH_TIMEOUT_MS
|
|
7463
|
+
});
|
|
7464
|
+
if ("error" in result) {
|
|
7465
|
+
console.error(`${TAG21} op=dispatch-failed from=${senderId} error=${result.error}`);
|
|
7466
|
+
return;
|
|
7467
|
+
}
|
|
7468
|
+
if (!botToken) {
|
|
7469
|
+
const reason = account.config.telegram ? "no-bot-token" : "no-telegram-config";
|
|
7470
|
+
console.error(`${TAG21} op=reply-dropped reason=${reason} botType=${botType}`);
|
|
7471
|
+
return;
|
|
7472
|
+
}
|
|
7473
|
+
const sent = await sendTelegram(botToken, chatId, result.turnText);
|
|
7474
|
+
console.error(`${TAG21} op=reply-sent from=${senderId} ok=${sent.ok}${sent.ok ? "" : ` error=${sent.error}`}`);
|
|
7475
|
+
})();
|
|
7476
|
+
return c.json({ ok: true }, 200);
|
|
7477
|
+
});
|
|
7478
|
+
var telegram_default = app6;
|
|
7479
|
+
|
|
7430
7480
|
// server/routes/onboarding.ts
|
|
7431
7481
|
import { spawn, spawnSync as spawnSync2, execFileSync as execFileSync2 } from "child_process";
|
|
7432
|
-
import { openSync as openSync2, closeSync as closeSync2, writeFileSync as writeFileSync6, writeSync, existsSync as
|
|
7482
|
+
import { openSync as openSync2, closeSync as closeSync2, writeFileSync as writeFileSync6, writeSync, existsSync as existsSync8, readFileSync as readFileSync12, unlinkSync } from "fs";
|
|
7433
7483
|
import { createHash as createHash2, randomUUID as randomUUID7 } from "crypto";
|
|
7434
7484
|
|
|
7435
7485
|
// ../lib/admins-write/src/index.ts
|
|
7436
|
-
import { existsSync as
|
|
7437
|
-
import { dirname as dirname2, join as
|
|
7486
|
+
import { existsSync as existsSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync5, renameSync, mkdirSync as mkdirSync2, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
|
|
7487
|
+
import { dirname as dirname2, join as join10 } from "path";
|
|
7438
7488
|
function logLine(input, result) {
|
|
7439
7489
|
const userIdShort = input.userId.slice(0, 8);
|
|
7440
7490
|
console.error(
|
|
@@ -7451,8 +7501,8 @@ function writeAdminEntry(input) {
|
|
|
7451
7501
|
const result = { usersJsonResult: "noop", accountJsonResult: "noop" };
|
|
7452
7502
|
try {
|
|
7453
7503
|
let users = [];
|
|
7454
|
-
if (
|
|
7455
|
-
const raw =
|
|
7504
|
+
if (existsSync7(input.usersFile)) {
|
|
7505
|
+
const raw = readFileSync11(input.usersFile, "utf-8").trim();
|
|
7456
7506
|
if (raw) {
|
|
7457
7507
|
const parsed = JSON.parse(raw);
|
|
7458
7508
|
if (!Array.isArray(parsed)) {
|
|
@@ -7476,11 +7526,11 @@ function writeAdminEntry(input) {
|
|
|
7476
7526
|
return result;
|
|
7477
7527
|
}
|
|
7478
7528
|
try {
|
|
7479
|
-
const accountJsonPath =
|
|
7480
|
-
if (!
|
|
7529
|
+
const accountJsonPath = join10(input.accountDir, "account.json");
|
|
7530
|
+
if (!existsSync7(accountJsonPath)) {
|
|
7481
7531
|
throw new Error(`account.json not found at ${accountJsonPath}`);
|
|
7482
7532
|
}
|
|
7483
|
-
const config = JSON.parse(
|
|
7533
|
+
const config = JSON.parse(readFileSync11(accountJsonPath, "utf-8"));
|
|
7484
7534
|
const admins = config.admins ?? [];
|
|
7485
7535
|
if (admins.some((a) => a.userId === input.userId)) {
|
|
7486
7536
|
result.accountJsonResult = "noop";
|
|
@@ -7504,9 +7554,9 @@ function checkAdminAuthInvariant(input) {
|
|
|
7504
7554
|
usersWithoutAccount: []
|
|
7505
7555
|
};
|
|
7506
7556
|
const usersUserIds = /* @__PURE__ */ new Set();
|
|
7507
|
-
if (
|
|
7557
|
+
if (existsSync7(input.usersFile)) {
|
|
7508
7558
|
try {
|
|
7509
|
-
const raw =
|
|
7559
|
+
const raw = readFileSync11(input.usersFile, "utf-8").trim();
|
|
7510
7560
|
if (raw) {
|
|
7511
7561
|
const users = JSON.parse(raw);
|
|
7512
7562
|
for (const u of users) {
|
|
@@ -7519,7 +7569,7 @@ function checkAdminAuthInvariant(input) {
|
|
|
7519
7569
|
}
|
|
7520
7570
|
}
|
|
7521
7571
|
const accountUserIds = /* @__PURE__ */ new Set();
|
|
7522
|
-
if (
|
|
7572
|
+
if (existsSync7(input.accountsDir)) {
|
|
7523
7573
|
let entries;
|
|
7524
7574
|
try {
|
|
7525
7575
|
entries = readdirSync5(input.accountsDir);
|
|
@@ -7531,17 +7581,17 @@ function checkAdminAuthInvariant(input) {
|
|
|
7531
7581
|
}
|
|
7532
7582
|
for (const entry of entries) {
|
|
7533
7583
|
if (entry.startsWith(".")) continue;
|
|
7534
|
-
const accountDir =
|
|
7584
|
+
const accountDir = join10(input.accountsDir, entry);
|
|
7535
7585
|
try {
|
|
7536
7586
|
if (!statSync4(accountDir).isDirectory()) continue;
|
|
7537
7587
|
} catch {
|
|
7538
7588
|
continue;
|
|
7539
7589
|
}
|
|
7540
|
-
const accountJsonPath =
|
|
7541
|
-
if (!
|
|
7590
|
+
const accountJsonPath = join10(accountDir, "account.json");
|
|
7591
|
+
if (!existsSync7(accountJsonPath)) continue;
|
|
7542
7592
|
let admins = [];
|
|
7543
7593
|
try {
|
|
7544
|
-
const config = JSON.parse(
|
|
7594
|
+
const config = JSON.parse(readFileSync11(accountJsonPath, "utf-8"));
|
|
7545
7595
|
admins = config.admins ?? [];
|
|
7546
7596
|
} catch (err) {
|
|
7547
7597
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -7581,8 +7631,8 @@ function hashPin(pin) {
|
|
|
7581
7631
|
return createHash2("sha256").update(pin).digest("hex");
|
|
7582
7632
|
}
|
|
7583
7633
|
function readUsersFile() {
|
|
7584
|
-
if (!
|
|
7585
|
-
const raw =
|
|
7634
|
+
if (!existsSync8(USERS_FILE)) return null;
|
|
7635
|
+
const raw = readFileSync12(USERS_FILE, "utf-8").trim();
|
|
7586
7636
|
if (!raw) return [];
|
|
7587
7637
|
return JSON.parse(raw);
|
|
7588
7638
|
}
|
|
@@ -7612,11 +7662,11 @@ async function waitForAuthPage(timeoutMs = 2e4) {
|
|
|
7612
7662
|
}
|
|
7613
7663
|
return false;
|
|
7614
7664
|
}
|
|
7615
|
-
var
|
|
7616
|
-
|
|
7665
|
+
var app7 = new Hono();
|
|
7666
|
+
app7.get("/claude-auth", async (c) => {
|
|
7617
7667
|
return c.json({ authenticated: await checkAuthStatus() });
|
|
7618
7668
|
});
|
|
7619
|
-
|
|
7669
|
+
app7.post("/claude-auth", async (c) => {
|
|
7620
7670
|
let body = {};
|
|
7621
7671
|
try {
|
|
7622
7672
|
body = await c.req.json();
|
|
@@ -7680,7 +7730,7 @@ app6.post("/claude-auth", async (c) => {
|
|
|
7680
7730
|
await waitForAuthPage(2e4);
|
|
7681
7731
|
return c.json({ started: true, transport });
|
|
7682
7732
|
});
|
|
7683
|
-
|
|
7733
|
+
app7.post("/set-pin", async (c) => {
|
|
7684
7734
|
let existingUsers = null;
|
|
7685
7735
|
try {
|
|
7686
7736
|
existingUsers = readUsersFile();
|
|
@@ -7736,9 +7786,9 @@ app6.post("/set-pin", async (c) => {
|
|
|
7736
7786
|
console.log(`[set-pin] wrote users.json + account.json admins: userId=${userId.slice(0, 8)}\u2026 role=owner`);
|
|
7737
7787
|
if (process.platform === "linux") {
|
|
7738
7788
|
let installOwner = null;
|
|
7739
|
-
if (
|
|
7789
|
+
if (existsSync8(INSTALL_OWNER_FILE)) {
|
|
7740
7790
|
try {
|
|
7741
|
-
const raw =
|
|
7791
|
+
const raw = readFileSync12(INSTALL_OWNER_FILE, "utf-8").trim();
|
|
7742
7792
|
if (raw.length > 0) installOwner = raw;
|
|
7743
7793
|
} catch (err) {
|
|
7744
7794
|
console.error(`[set-pin] install-owner-read-failed path=${INSTALL_OWNER_FILE} error=${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -7782,7 +7832,7 @@ ${body.pin}
|
|
|
7782
7832
|
}
|
|
7783
7833
|
return c.json({ ok: true });
|
|
7784
7834
|
});
|
|
7785
|
-
|
|
7835
|
+
app7.delete("/set-pin", async (c) => {
|
|
7786
7836
|
let users = null;
|
|
7787
7837
|
try {
|
|
7788
7838
|
users = readUsersFile();
|
|
@@ -7816,12 +7866,12 @@ app6.delete("/set-pin", async (c) => {
|
|
|
7816
7866
|
}
|
|
7817
7867
|
return c.json({ ok: true });
|
|
7818
7868
|
});
|
|
7819
|
-
var onboarding_default =
|
|
7869
|
+
var onboarding_default = app7;
|
|
7820
7870
|
|
|
7821
7871
|
// server/routes/client-error.ts
|
|
7822
|
-
import { appendFileSync, existsSync as
|
|
7823
|
-
import { join as
|
|
7824
|
-
var CLIENT_ERRORS_LOG =
|
|
7872
|
+
import { appendFileSync, existsSync as existsSync9, renameSync as renameSync2, statSync as statSync5 } from "fs";
|
|
7873
|
+
import { join as join11 } from "path";
|
|
7874
|
+
var CLIENT_ERRORS_LOG = join11(LOG_DIR, "client-errors.log");
|
|
7825
7875
|
var MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
7826
7876
|
var MAX_BODY_SIZE = 8 * 1024;
|
|
7827
7877
|
var MAX_STACK_LEN = 2e3;
|
|
@@ -7864,7 +7914,7 @@ function stackHead(stack) {
|
|
|
7864
7914
|
}
|
|
7865
7915
|
function rotateIfNeeded() {
|
|
7866
7916
|
try {
|
|
7867
|
-
if (!
|
|
7917
|
+
if (!existsSync9(CLIENT_ERRORS_LOG)) return;
|
|
7868
7918
|
const stats = statSync5(CLIENT_ERRORS_LOG);
|
|
7869
7919
|
if (stats.size < MAX_LOG_SIZE) return;
|
|
7870
7920
|
renameSync2(CLIENT_ERRORS_LOG, CLIENT_ERRORS_LOG + ".1");
|
|
@@ -7872,8 +7922,8 @@ function rotateIfNeeded() {
|
|
|
7872
7922
|
console.error(`[client-error] log rotation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
7873
7923
|
}
|
|
7874
7924
|
}
|
|
7875
|
-
var
|
|
7876
|
-
|
|
7925
|
+
var app8 = new Hono();
|
|
7926
|
+
app8.post("/", async (c) => {
|
|
7877
7927
|
const client = resolveClientIp(
|
|
7878
7928
|
c.env?.incoming?.socket?.remoteAddress,
|
|
7879
7929
|
c.req.header("x-forwarded-for")
|
|
@@ -7975,21 +8025,21 @@ app7.post("/", async (c) => {
|
|
|
7975
8025
|
}
|
|
7976
8026
|
return c.json({ ok: true });
|
|
7977
8027
|
});
|
|
7978
|
-
var client_error_default =
|
|
8028
|
+
var client_error_default = app8;
|
|
7979
8029
|
|
|
7980
8030
|
// server/routes/admin/session.ts
|
|
7981
8031
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
7982
8032
|
|
|
7983
8033
|
// app/lib/admin-identity/pin-validator.ts
|
|
7984
8034
|
import { createHash as createHash3 } from "crypto";
|
|
7985
|
-
import { existsSync as
|
|
7986
|
-
import { join as
|
|
8035
|
+
import { existsSync as existsSync10, readFileSync as readFileSync13, readdirSync as readdirSync6, statSync as statSync6 } from "fs";
|
|
8036
|
+
import { join as join12 } from "path";
|
|
7987
8037
|
function hashPin2(pin) {
|
|
7988
8038
|
return createHash3("sha256").update(pin).digest("hex");
|
|
7989
8039
|
}
|
|
7990
8040
|
function readUsersFile2(usersFilePath) {
|
|
7991
|
-
if (!
|
|
7992
|
-
const raw =
|
|
8041
|
+
if (!existsSync10(usersFilePath)) return null;
|
|
8042
|
+
const raw = readFileSync13(usersFilePath, "utf-8").trim();
|
|
7993
8043
|
if (!raw) return [];
|
|
7994
8044
|
return JSON.parse(raw);
|
|
7995
8045
|
}
|
|
@@ -8008,7 +8058,7 @@ function validatePin(pin, usersFilePath) {
|
|
|
8008
8058
|
function scanForOrphanedAccountAdmins(users, accountsDir) {
|
|
8009
8059
|
const known = new Set(users.map((u) => u.userId));
|
|
8010
8060
|
const orphans = [];
|
|
8011
|
-
if (!
|
|
8061
|
+
if (!existsSync10(accountsDir)) return orphans;
|
|
8012
8062
|
let entries;
|
|
8013
8063
|
try {
|
|
8014
8064
|
entries = readdirSync6(accountsDir);
|
|
@@ -8017,16 +8067,16 @@ function scanForOrphanedAccountAdmins(users, accountsDir) {
|
|
|
8017
8067
|
}
|
|
8018
8068
|
for (const entry of entries) {
|
|
8019
8069
|
if (entry.startsWith(".")) continue;
|
|
8020
|
-
const accountDir =
|
|
8070
|
+
const accountDir = join12(accountsDir, entry);
|
|
8021
8071
|
try {
|
|
8022
8072
|
if (!statSync6(accountDir).isDirectory()) continue;
|
|
8023
8073
|
} catch {
|
|
8024
8074
|
continue;
|
|
8025
8075
|
}
|
|
8026
|
-
const accountJsonPath =
|
|
8027
|
-
if (!
|
|
8076
|
+
const accountJsonPath = join12(accountDir, "account.json");
|
|
8077
|
+
if (!existsSync10(accountJsonPath)) continue;
|
|
8028
8078
|
try {
|
|
8029
|
-
const config = JSON.parse(
|
|
8079
|
+
const config = JSON.parse(readFileSync13(accountJsonPath, "utf-8"));
|
|
8030
8080
|
const admins = config.admins ?? [];
|
|
8031
8081
|
for (const a of admins) {
|
|
8032
8082
|
if (typeof a.userId === "string" && !known.has(a.userId)) orphans.push(a.userId);
|
|
@@ -8093,8 +8143,8 @@ async function createAdminSession(accountId, thinkingView, userId, userName, rol
|
|
|
8093
8143
|
sessionId: initialSessionId
|
|
8094
8144
|
};
|
|
8095
8145
|
}
|
|
8096
|
-
var
|
|
8097
|
-
|
|
8146
|
+
var app9 = new Hono();
|
|
8147
|
+
app9.get("/", async (c) => {
|
|
8098
8148
|
const signedSessionToken = c.req.query("session_key");
|
|
8099
8149
|
if (!signedSessionToken) {
|
|
8100
8150
|
return c.json({ error: "Invalid or expired admin session" }, 401);
|
|
@@ -8140,7 +8190,7 @@ app8.get("/", async (c) => {
|
|
|
8140
8190
|
sessionId: getSessionIdForSession(cacheKey) ?? null
|
|
8141
8191
|
});
|
|
8142
8192
|
});
|
|
8143
|
-
|
|
8193
|
+
app9.post("/", async (c) => {
|
|
8144
8194
|
let body;
|
|
8145
8195
|
try {
|
|
8146
8196
|
body = await c.req.json();
|
|
@@ -8203,21 +8253,21 @@ app8.post("/", async (c) => {
|
|
|
8203
8253
|
const payload = await createAdminSession(selected.accountId, selected.config.thinkingView, userId, userName, selected.role, avatar);
|
|
8204
8254
|
return c.json(payload);
|
|
8205
8255
|
});
|
|
8206
|
-
var session_default =
|
|
8256
|
+
var session_default = app9;
|
|
8207
8257
|
|
|
8208
8258
|
// server/routes/admin/logs.ts
|
|
8209
|
-
import { existsSync as
|
|
8210
|
-
import { resolve as
|
|
8259
|
+
import { existsSync as existsSync12, readdirSync as readdirSync7, readFileSync as readFileSync14, statSync as statSync7 } from "fs";
|
|
8260
|
+
import { resolve as resolve11, basename as basename3 } from "path";
|
|
8211
8261
|
|
|
8212
8262
|
// app/lib/logs-read-resolve.ts
|
|
8213
|
-
import { existsSync as
|
|
8214
|
-
import { join as
|
|
8263
|
+
import { existsSync as existsSync11 } from "fs";
|
|
8264
|
+
import { join as join13 } from "path";
|
|
8215
8265
|
function resolveSessionLogPaths(filename, logDirs) {
|
|
8216
8266
|
const tried = [filename];
|
|
8217
8267
|
const hits = [];
|
|
8218
8268
|
for (const dir of logDirs) {
|
|
8219
|
-
const fullPath =
|
|
8220
|
-
if (
|
|
8269
|
+
const fullPath = join13(dir, filename);
|
|
8270
|
+
if (existsSync11(fullPath)) {
|
|
8221
8271
|
hits.push({ path: fullPath, dir });
|
|
8222
8272
|
}
|
|
8223
8273
|
}
|
|
@@ -8226,15 +8276,15 @@ function resolveSessionLogPaths(filename, logDirs) {
|
|
|
8226
8276
|
|
|
8227
8277
|
// server/routes/admin/logs.ts
|
|
8228
8278
|
var TAIL_BYTES = 8192;
|
|
8229
|
-
var
|
|
8230
|
-
|
|
8279
|
+
var app10 = new Hono();
|
|
8280
|
+
app10.get("/", async (c) => {
|
|
8231
8281
|
const fileParam = c.req.query("file");
|
|
8232
8282
|
const typeParam = c.req.query("type");
|
|
8233
8283
|
const sessionIdParam = c.req.query("sessionId");
|
|
8234
8284
|
const cacheKeyParam = c.req.query("cacheKey");
|
|
8235
8285
|
const download = c.req.query("download") === "1";
|
|
8236
8286
|
const account = resolveAccount();
|
|
8237
|
-
const accountLogDir = account ?
|
|
8287
|
+
const accountLogDir = account ? resolve11(account.accountDir, "logs") : null;
|
|
8238
8288
|
const logDirs = [];
|
|
8239
8289
|
if (accountLogDir) logDirs.push(accountLogDir);
|
|
8240
8290
|
logDirs.push(LOG_DIR);
|
|
@@ -8242,10 +8292,10 @@ app9.get("/", async (c) => {
|
|
|
8242
8292
|
const safe = basename3(fileParam);
|
|
8243
8293
|
const searched = [];
|
|
8244
8294
|
for (const dir of logDirs) {
|
|
8245
|
-
const filePath =
|
|
8295
|
+
const filePath = resolve11(dir, safe);
|
|
8246
8296
|
searched.push(filePath);
|
|
8247
8297
|
try {
|
|
8248
|
-
const buffer =
|
|
8298
|
+
const buffer = readFileSync14(filePath);
|
|
8249
8299
|
const onDiskBytes = statSync7(filePath).size;
|
|
8250
8300
|
const headers = {
|
|
8251
8301
|
"Content-Type": "text/plain; charset=utf-8",
|
|
@@ -8310,7 +8360,7 @@ app9.get("/", async (c) => {
|
|
|
8310
8360
|
console.info(`[admin/logs] resolved cacheKey=${cacheKeySlice} sessionId=${sessionIdSlice} via=${primaryId === cacheKey ? "cacheKey" : primaryId === sessionKeyFromConv ? "reverse-lookup" : "sessionId-fallback"}`);
|
|
8311
8361
|
try {
|
|
8312
8362
|
const filename = basename3(hit.path);
|
|
8313
|
-
const buffer =
|
|
8363
|
+
const buffer = readFileSync14(hit.path);
|
|
8314
8364
|
const onDiskBytes = statSync7(hit.path).size;
|
|
8315
8365
|
const headers = {
|
|
8316
8366
|
"Content-Type": "text/plain; charset=utf-8",
|
|
@@ -8345,7 +8395,7 @@ app9.get("/", async (c) => {
|
|
|
8345
8395
|
const seen = /* @__PURE__ */ new Set();
|
|
8346
8396
|
const logs = {};
|
|
8347
8397
|
for (const dir of logDirs) {
|
|
8348
|
-
if (!
|
|
8398
|
+
if (!existsSync12(dir)) continue;
|
|
8349
8399
|
let files;
|
|
8350
8400
|
try {
|
|
8351
8401
|
files = readdirSync7(dir).filter((f) => f.endsWith(".log"));
|
|
@@ -8354,10 +8404,10 @@ app9.get("/", async (c) => {
|
|
|
8354
8404
|
console.warn(`[admin/logs] readdir-fail dir=${dir} reason=${reason}`);
|
|
8355
8405
|
continue;
|
|
8356
8406
|
}
|
|
8357
|
-
files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync7(
|
|
8407
|
+
files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync7(resolve11(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).forEach(({ name }) => {
|
|
8358
8408
|
seen.add(name);
|
|
8359
8409
|
try {
|
|
8360
|
-
const content =
|
|
8410
|
+
const content = readFileSync14(resolve11(dir, name));
|
|
8361
8411
|
const tail = content.length > TAIL_BYTES ? content.subarray(content.length - TAIL_BYTES).toString("utf-8") : content.toString("utf-8");
|
|
8362
8412
|
logs[name] = tail.trim() || "(empty)";
|
|
8363
8413
|
} catch (err) {
|
|
@@ -8369,12 +8419,12 @@ app9.get("/", async (c) => {
|
|
|
8369
8419
|
}
|
|
8370
8420
|
return c.json({ logs });
|
|
8371
8421
|
});
|
|
8372
|
-
var logs_default =
|
|
8422
|
+
var logs_default = app10;
|
|
8373
8423
|
|
|
8374
8424
|
// server/routes/admin/claude-info.ts
|
|
8375
8425
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
8376
|
-
var
|
|
8377
|
-
|
|
8426
|
+
var app11 = new Hono();
|
|
8427
|
+
app11.get("/", (c) => {
|
|
8378
8428
|
let version = "unknown";
|
|
8379
8429
|
try {
|
|
8380
8430
|
const raw = execFileSync3("claude", ["--version"], { encoding: "utf-8", timeout: 5e3 });
|
|
@@ -8392,14 +8442,14 @@ app10.get("/", (c) => {
|
|
|
8392
8442
|
const thinkingView = resolvedAccount?.config.thinkingView ?? "default";
|
|
8393
8443
|
return c.json({ version, account, model, thinkingView });
|
|
8394
8444
|
});
|
|
8395
|
-
var claude_info_default =
|
|
8445
|
+
var claude_info_default = app11;
|
|
8396
8446
|
|
|
8397
8447
|
// server/routes/admin/attachment.ts
|
|
8398
8448
|
import { readFile as readFile2, readdir } from "fs/promises";
|
|
8399
|
-
import { existsSync as
|
|
8400
|
-
import { resolve as
|
|
8401
|
-
var
|
|
8402
|
-
|
|
8449
|
+
import { existsSync as existsSync13 } from "fs";
|
|
8450
|
+
import { resolve as resolve12 } from "path";
|
|
8451
|
+
var app12 = new Hono();
|
|
8452
|
+
app12.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
8403
8453
|
const attachmentId = c.req.param("attachmentId");
|
|
8404
8454
|
const cacheKey = c.var.cacheKey;
|
|
8405
8455
|
const accountId = getAccountIdForSession(cacheKey);
|
|
@@ -8409,12 +8459,12 @@ app11.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
|
8409
8459
|
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(attachmentId)) {
|
|
8410
8460
|
return new Response("Not found", { status: 404 });
|
|
8411
8461
|
}
|
|
8412
|
-
const dir =
|
|
8413
|
-
if (!
|
|
8462
|
+
const dir = resolve12(ATTACHMENTS_ROOT, accountId, attachmentId);
|
|
8463
|
+
if (!existsSync13(dir)) {
|
|
8414
8464
|
return new Response("Not found", { status: 404 });
|
|
8415
8465
|
}
|
|
8416
|
-
const metaPath =
|
|
8417
|
-
if (!
|
|
8466
|
+
const metaPath = resolve12(dir, `${attachmentId}.meta.json`);
|
|
8467
|
+
if (!existsSync13(metaPath)) {
|
|
8418
8468
|
return new Response("Not found", { status: 404 });
|
|
8419
8469
|
}
|
|
8420
8470
|
let meta;
|
|
@@ -8428,7 +8478,7 @@ app11.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
|
8428
8478
|
if (!dataFile) {
|
|
8429
8479
|
return new Response("Not found", { status: 404 });
|
|
8430
8480
|
}
|
|
8431
|
-
const filePath =
|
|
8481
|
+
const filePath = resolve12(dir, dataFile);
|
|
8432
8482
|
const buffer = await readFile2(filePath);
|
|
8433
8483
|
return new Response(new Uint8Array(buffer), {
|
|
8434
8484
|
headers: {
|
|
@@ -8438,27 +8488,27 @@ app11.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
|
8438
8488
|
}
|
|
8439
8489
|
});
|
|
8440
8490
|
});
|
|
8441
|
-
var attachment_default =
|
|
8491
|
+
var attachment_default = app12;
|
|
8442
8492
|
|
|
8443
8493
|
// server/routes/admin/agents.ts
|
|
8444
|
-
import { resolve as
|
|
8445
|
-
import { readdirSync as readdirSync8, readFileSync as
|
|
8446
|
-
var
|
|
8447
|
-
|
|
8494
|
+
import { resolve as resolve13 } from "path";
|
|
8495
|
+
import { readdirSync as readdirSync8, readFileSync as readFileSync15, existsSync as existsSync14, rmSync } from "fs";
|
|
8496
|
+
var app13 = new Hono();
|
|
8497
|
+
app13.get("/", (c) => {
|
|
8448
8498
|
const account = resolveAccount();
|
|
8449
8499
|
if (!account) return c.json({ agents: [] });
|
|
8450
|
-
const agentsDir =
|
|
8451
|
-
if (!
|
|
8500
|
+
const agentsDir = resolve13(account.accountDir, "agents");
|
|
8501
|
+
if (!existsSync14(agentsDir)) return c.json({ agents: [] });
|
|
8452
8502
|
const agents = [];
|
|
8453
8503
|
try {
|
|
8454
8504
|
const entries = readdirSync8(agentsDir, { withFileTypes: true });
|
|
8455
8505
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
8456
8506
|
if (!entry.isDirectory()) continue;
|
|
8457
8507
|
if (entry.name === "admin") continue;
|
|
8458
|
-
const configPath2 =
|
|
8459
|
-
if (!
|
|
8508
|
+
const configPath2 = resolve13(agentsDir, entry.name, "config.json");
|
|
8509
|
+
if (!existsSync14(configPath2)) continue;
|
|
8460
8510
|
try {
|
|
8461
|
-
const config = JSON.parse(
|
|
8511
|
+
const config = JSON.parse(readFileSync15(configPath2, "utf-8"));
|
|
8462
8512
|
agents.push({
|
|
8463
8513
|
slug: entry.name,
|
|
8464
8514
|
displayName: config.displayName ?? entry.name,
|
|
@@ -8474,7 +8524,7 @@ app12.get("/", (c) => {
|
|
|
8474
8524
|
}
|
|
8475
8525
|
return c.json({ agents });
|
|
8476
8526
|
});
|
|
8477
|
-
|
|
8527
|
+
app13.delete("/:slug", async (c) => {
|
|
8478
8528
|
const slug = c.req.param("slug");
|
|
8479
8529
|
const account = resolveAccount();
|
|
8480
8530
|
if (!account) return c.json({ error: "No account resolved" }, 400);
|
|
@@ -8484,8 +8534,8 @@ app12.delete("/:slug", async (c) => {
|
|
|
8484
8534
|
if (slug.includes("/") || slug.includes("..") || slug.includes("\\")) {
|
|
8485
8535
|
return c.json({ error: "Invalid agent slug" }, 400);
|
|
8486
8536
|
}
|
|
8487
|
-
const agentDir =
|
|
8488
|
-
if (!
|
|
8537
|
+
const agentDir = resolve13(account.accountDir, "agents", slug);
|
|
8538
|
+
if (!existsSync14(agentDir)) {
|
|
8489
8539
|
return c.json({ error: "Agent not found" }, 404);
|
|
8490
8540
|
}
|
|
8491
8541
|
try {
|
|
@@ -8504,7 +8554,7 @@ app12.delete("/:slug", async (c) => {
|
|
|
8504
8554
|
return c.json({ error: "Failed to delete agent" }, 500);
|
|
8505
8555
|
}
|
|
8506
8556
|
});
|
|
8507
|
-
|
|
8557
|
+
app13.post("/:slug/project", async (c) => {
|
|
8508
8558
|
const slug = c.req.param("slug");
|
|
8509
8559
|
const account = resolveAccount();
|
|
8510
8560
|
if (!account) return c.json({ error: "No account resolved" }, 400);
|
|
@@ -8514,8 +8564,8 @@ app12.post("/:slug/project", async (c) => {
|
|
|
8514
8564
|
if (slug.includes("/") || slug.includes("..") || slug.includes("\\")) {
|
|
8515
8565
|
return c.json({ error: "Invalid agent slug" }, 400);
|
|
8516
8566
|
}
|
|
8517
|
-
const agentDir =
|
|
8518
|
-
if (!
|
|
8567
|
+
const agentDir = resolve13(account.accountDir, "agents", slug);
|
|
8568
|
+
if (!existsSync14(agentDir)) {
|
|
8519
8569
|
return c.json({ error: "Agent not found on disk" }, 404);
|
|
8520
8570
|
}
|
|
8521
8571
|
try {
|
|
@@ -8526,12 +8576,12 @@ app12.post("/:slug/project", async (c) => {
|
|
|
8526
8576
|
return c.json({ error: `Projection failed: ${msg}` }, 500);
|
|
8527
8577
|
}
|
|
8528
8578
|
});
|
|
8529
|
-
var agents_default =
|
|
8579
|
+
var agents_default = app13;
|
|
8530
8580
|
|
|
8531
8581
|
// server/routes/admin/sessions.ts
|
|
8532
8582
|
import crypto2 from "crypto";
|
|
8533
8583
|
import { resolve as resolvePath } from "path";
|
|
8534
|
-
import { existsSync as
|
|
8584
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync3, createWriteStream } from "fs";
|
|
8535
8585
|
|
|
8536
8586
|
// ../lib/admin-conversation-purge/src/index.ts
|
|
8537
8587
|
async function purgeAdminConversationJsonls(args) {
|
|
@@ -8669,7 +8719,7 @@ var replayJsonl = (..._args) => null;
|
|
|
8669
8719
|
var resolveJsonlPath = (..._args) => null;
|
|
8670
8720
|
|
|
8671
8721
|
// server/routes/admin/sessions.ts
|
|
8672
|
-
import { homedir } from "os";
|
|
8722
|
+
import { homedir as homedir2 } from "os";
|
|
8673
8723
|
|
|
8674
8724
|
// app/lib/claude-agent/component-attachment.ts
|
|
8675
8725
|
var pickComponentBytes = (..._args) => null;
|
|
@@ -8691,7 +8741,7 @@ function validateAndShapeAttachments(raws, conversationAccountId, sessionId, mes
|
|
|
8691
8741
|
let reason = null;
|
|
8692
8742
|
if (!a.attachmentId || !a.filename || !a.mimeType || !a.storagePath) reason = "schema-fail";
|
|
8693
8743
|
else if (a.accountId !== conversationAccountId) reason = "account-mismatch";
|
|
8694
|
-
else if (!
|
|
8744
|
+
else if (!existsSync15(a.storagePath)) reason = "missing-file";
|
|
8695
8745
|
if (reason) {
|
|
8696
8746
|
invalid++;
|
|
8697
8747
|
try {
|
|
@@ -8798,8 +8848,8 @@ function formatAge(updatedAtStr) {
|
|
|
8798
8848
|
return "unknown";
|
|
8799
8849
|
}
|
|
8800
8850
|
}
|
|
8801
|
-
var
|
|
8802
|
-
|
|
8851
|
+
var app14 = new Hono();
|
|
8852
|
+
app14.get("/", requireAdminSession, async (c) => {
|
|
8803
8853
|
const cacheKey = c.var.cacheKey;
|
|
8804
8854
|
const accountId = getAccountIdForSession(cacheKey);
|
|
8805
8855
|
if (!accountId) return c.json({ error: "Account not found for session" }, 401);
|
|
@@ -8829,7 +8879,7 @@ app13.get("/", requireAdminSession, async (c) => {
|
|
|
8829
8879
|
return c.json({ error: "Failed to fetch sessions" }, 500);
|
|
8830
8880
|
}
|
|
8831
8881
|
});
|
|
8832
|
-
|
|
8882
|
+
app14.post("/new", requireAdminSession, async (c) => {
|
|
8833
8883
|
const oldCacheKey = c.var.cacheKey;
|
|
8834
8884
|
console.log(`[admin-chat] new-conversation outcome=ingress cacheKey=${oldCacheKey.slice(0, 8)}`);
|
|
8835
8885
|
const accountId = getAccountIdForSession(oldCacheKey);
|
|
@@ -8846,7 +8896,7 @@ app13.post("/new", requireAdminSession, async (c) => {
|
|
|
8846
8896
|
console.log(`[admin-chat] new-conversation outcome=ok oldKey=${oldCacheKey.slice(0, 8)} newKey=${newCacheKey.slice(0, 8)}`);
|
|
8847
8897
|
return c.json({ session_key: newSignedSessionToken, sessionId: null });
|
|
8848
8898
|
});
|
|
8849
|
-
|
|
8899
|
+
app14.post("/switch", requireAdminSession, async (c) => {
|
|
8850
8900
|
const cacheKey = c.var.cacheKey;
|
|
8851
8901
|
const accountId = getAccountIdForSession(cacheKey);
|
|
8852
8902
|
const userId = getUserIdForSession(cacheKey);
|
|
@@ -8874,7 +8924,7 @@ app13.post("/switch", requireAdminSession, async (c) => {
|
|
|
8874
8924
|
console.log(`[session-switch] from=${cacheKey.slice(0, 8)} to=${targetCacheKey.slice(0, 8)} targetConvId=${targetSessionId?.slice(0, 8) ?? "none"} accountId=${accountId.slice(0, 8)}`);
|
|
8875
8925
|
return c.json({ session_key: targetSignedSessionToken, sessionId: targetSessionId });
|
|
8876
8926
|
});
|
|
8877
|
-
|
|
8927
|
+
app14.delete("/:id", requireAdminSession, async (c) => {
|
|
8878
8928
|
const sessionId = c.req.param("id");
|
|
8879
8929
|
const cacheKey = c.var.cacheKey;
|
|
8880
8930
|
const accountId = getAccountIdForSession(cacheKey);
|
|
@@ -8919,7 +8969,7 @@ app13.delete("/:id", requireAdminSession, async (c) => {
|
|
|
8919
8969
|
500
|
|
8920
8970
|
);
|
|
8921
8971
|
});
|
|
8922
|
-
|
|
8972
|
+
app14.post("/:id/resume", async (c) => {
|
|
8923
8973
|
const sessionId = c.req.param("id");
|
|
8924
8974
|
const t0 = Date.now();
|
|
8925
8975
|
const signedSessionToken = c.req.query("session_key") ?? "";
|
|
@@ -8984,7 +9034,7 @@ app13.post("/:id/resume", async (c) => {
|
|
|
8984
9034
|
return c.json({ error: "Failed to load conversation messages" }, 500);
|
|
8985
9035
|
}
|
|
8986
9036
|
if (persistedAgentSessionId) {
|
|
8987
|
-
const jsonlPath = resolveJsonlPath(
|
|
9037
|
+
const jsonlPath = resolveJsonlPath(homedir2(), resolvePath(ACCOUNTS_DIR, accountId), persistedAgentSessionId);
|
|
8988
9038
|
const replay = replayJsonl(jsonlPath);
|
|
8989
9039
|
jsonlMissing = replay.jsonlMissing;
|
|
8990
9040
|
jsonlMalformedLines = replay.malformedLines;
|
|
@@ -9188,7 +9238,7 @@ app13.post("/:id/resume", async (c) => {
|
|
|
9188
9238
|
}
|
|
9189
9239
|
});
|
|
9190
9240
|
});
|
|
9191
|
-
|
|
9241
|
+
app14.put("/:id/label", requireAdminSession, async (c) => {
|
|
9192
9242
|
const sessionId = c.req.param("id");
|
|
9193
9243
|
const cacheKey = c.var.cacheKey;
|
|
9194
9244
|
let body;
|
|
@@ -9214,11 +9264,11 @@ app13.put("/:id/label", requireAdminSession, async (c) => {
|
|
|
9214
9264
|
return c.json({ error: "Failed to rename session" }, 500);
|
|
9215
9265
|
}
|
|
9216
9266
|
});
|
|
9217
|
-
var sessions_default =
|
|
9267
|
+
var sessions_default = app14;
|
|
9218
9268
|
|
|
9219
9269
|
// app/lib/claude-agent/spawn-context.ts
|
|
9220
|
-
import { existsSync as
|
|
9221
|
-
import { dirname as dirname3, resolve as
|
|
9270
|
+
import { existsSync as existsSync16, readFileSync as readFileSync16, readdirSync as readdirSync9, statSync as statSync8 } from "fs";
|
|
9271
|
+
import { dirname as dirname3, resolve as resolve14, join as join14 } from "path";
|
|
9222
9272
|
async function resolveOwnerProfileBlock(accountId, userId) {
|
|
9223
9273
|
if (!userId) return { ok: false, reason: "missing-user-id" };
|
|
9224
9274
|
try {
|
|
@@ -9247,14 +9297,14 @@ function frontmatterField(manifest, field) {
|
|
|
9247
9297
|
function extractPluginToolDescriptions(pluginDir) {
|
|
9248
9298
|
const out = /* @__PURE__ */ new Map();
|
|
9249
9299
|
const candidates = [
|
|
9250
|
-
|
|
9251
|
-
|
|
9300
|
+
join14(pluginDir, "mcp/dist/index.js"),
|
|
9301
|
+
join14(pluginDir, "mcp/src/index.ts")
|
|
9252
9302
|
];
|
|
9253
9303
|
let raw = null;
|
|
9254
9304
|
for (const path2 of candidates) {
|
|
9255
|
-
if (!
|
|
9305
|
+
if (!existsSync16(path2)) continue;
|
|
9256
9306
|
try {
|
|
9257
|
-
raw =
|
|
9307
|
+
raw = readFileSync16(path2, "utf-8");
|
|
9258
9308
|
break;
|
|
9259
9309
|
} catch {
|
|
9260
9310
|
}
|
|
@@ -9303,24 +9353,24 @@ function parseSkillPathsFromFrontmatter(fm) {
|
|
|
9303
9353
|
}
|
|
9304
9354
|
function listPluginDirs() {
|
|
9305
9355
|
const out = /* @__PURE__ */ new Map();
|
|
9306
|
-
const platformPlugins =
|
|
9307
|
-
if (
|
|
9356
|
+
const platformPlugins = resolve14(PLATFORM_ROOT, "plugins");
|
|
9357
|
+
if (existsSync16(platformPlugins)) {
|
|
9308
9358
|
for (const name of readdirSync9(platformPlugins)) {
|
|
9309
|
-
const dir =
|
|
9359
|
+
const dir = join14(platformPlugins, name);
|
|
9310
9360
|
try {
|
|
9311
9361
|
if (statSync8(dir).isDirectory()) out.set(name, dir);
|
|
9312
9362
|
} catch {
|
|
9313
9363
|
}
|
|
9314
9364
|
}
|
|
9315
9365
|
}
|
|
9316
|
-
const premiumRoot =
|
|
9317
|
-
if (
|
|
9366
|
+
const premiumRoot = resolve14(dirname3(PLATFORM_ROOT), "premium-plugins");
|
|
9367
|
+
if (existsSync16(premiumRoot)) {
|
|
9318
9368
|
for (const bundle of readdirSync9(premiumRoot)) {
|
|
9319
|
-
const bundlePlugins =
|
|
9320
|
-
if (!
|
|
9369
|
+
const bundlePlugins = join14(premiumRoot, bundle, "plugins");
|
|
9370
|
+
if (!existsSync16(bundlePlugins)) continue;
|
|
9321
9371
|
try {
|
|
9322
9372
|
for (const name of readdirSync9(bundlePlugins)) {
|
|
9323
|
-
const dir =
|
|
9373
|
+
const dir = join14(bundlePlugins, name);
|
|
9324
9374
|
try {
|
|
9325
9375
|
if (statSync8(dir).isDirectory()) out.set(name, dir);
|
|
9326
9376
|
} catch {
|
|
@@ -9333,10 +9383,10 @@ function listPluginDirs() {
|
|
|
9333
9383
|
return out;
|
|
9334
9384
|
}
|
|
9335
9385
|
function readEnabledPlugins(accountId) {
|
|
9336
|
-
const accountFile =
|
|
9337
|
-
if (!
|
|
9386
|
+
const accountFile = resolve14(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
|
|
9387
|
+
if (!existsSync16(accountFile)) return /* @__PURE__ */ new Set();
|
|
9338
9388
|
try {
|
|
9339
|
-
const parsed = JSON.parse(
|
|
9389
|
+
const parsed = JSON.parse(readFileSync16(accountFile, "utf-8"));
|
|
9340
9390
|
return new Set(
|
|
9341
9391
|
Array.isArray(parsed.enabledPlugins) ? parsed.enabledPlugins.filter((p) => typeof p === "string") : []
|
|
9342
9392
|
);
|
|
@@ -9345,10 +9395,10 @@ function readEnabledPlugins(accountId) {
|
|
|
9345
9395
|
}
|
|
9346
9396
|
}
|
|
9347
9397
|
function readFrontmatter(path2) {
|
|
9348
|
-
if (!
|
|
9398
|
+
if (!existsSync16(path2)) return null;
|
|
9349
9399
|
let raw;
|
|
9350
9400
|
try {
|
|
9351
|
-
raw =
|
|
9401
|
+
raw = readFileSync16(path2, "utf-8");
|
|
9352
9402
|
} catch {
|
|
9353
9403
|
return null;
|
|
9354
9404
|
}
|
|
@@ -9363,7 +9413,7 @@ function computeActivePlugins(accountId) {
|
|
|
9363
9413
|
for (const name of Array.from(enabled).sort()) {
|
|
9364
9414
|
const dir = pluginDirs.get(name);
|
|
9365
9415
|
if (!dir) continue;
|
|
9366
|
-
const fm = readFrontmatter(
|
|
9416
|
+
const fm = readFrontmatter(join14(dir, "PLUGIN.md"));
|
|
9367
9417
|
if (!fm) continue;
|
|
9368
9418
|
const description = frontmatterField(`---
|
|
9369
9419
|
${fm}
|
|
@@ -9380,7 +9430,7 @@ ${fm}
|
|
|
9380
9430
|
`plugin-manifest-tool-coverage plugin=${name} tools=${tools.length} with-desc=${withDesc}`
|
|
9381
9431
|
);
|
|
9382
9432
|
if (toolNames.length > 0 && descMap.size === 0) {
|
|
9383
|
-
const hasSource =
|
|
9433
|
+
const hasSource = existsSync16(join14(dir, "mcp/dist/index.js")) || existsSync16(join14(dir, "mcp/src/index.ts"));
|
|
9384
9434
|
if (hasSource) {
|
|
9385
9435
|
console.warn(
|
|
9386
9436
|
`[spawn-context] WARN plugin=${name} mcp source present but tool-description regex matched zero entries \u2014 SDK upgrade may have changed the registration shape`
|
|
@@ -9390,7 +9440,7 @@ ${fm}
|
|
|
9390
9440
|
const skillPaths = parseSkillPathsFromFrontmatter(fm);
|
|
9391
9441
|
const skills = [];
|
|
9392
9442
|
for (const relPath of skillPaths) {
|
|
9393
|
-
const skillPath =
|
|
9443
|
+
const skillPath = join14(dir, relPath);
|
|
9394
9444
|
const skillFm = readFrontmatter(skillPath);
|
|
9395
9445
|
if (!skillFm) continue;
|
|
9396
9446
|
const skillName = frontmatterField(`---
|
|
@@ -9406,8 +9456,8 @@ ${skillFm}
|
|
|
9406
9456
|
return out;
|
|
9407
9457
|
}
|
|
9408
9458
|
function computeSpecialistDomains(accountId) {
|
|
9409
|
-
const specialistsDir =
|
|
9410
|
-
if (!
|
|
9459
|
+
const specialistsDir = resolve14(PLATFORM_ROOT, "..", "data/accounts", accountId, "specialists", "agents");
|
|
9460
|
+
if (!existsSync16(specialistsDir)) return [];
|
|
9411
9461
|
let entries;
|
|
9412
9462
|
try {
|
|
9413
9463
|
entries = readdirSync9(specialistsDir);
|
|
@@ -9434,7 +9484,7 @@ function computeSpecialistDomains(accountId) {
|
|
|
9434
9484
|
const out = [];
|
|
9435
9485
|
for (const file of entries.sort()) {
|
|
9436
9486
|
if (!file.endsWith(".md")) continue;
|
|
9437
|
-
const fm = readFrontmatter(
|
|
9487
|
+
const fm = readFrontmatter(join14(specialistsDir, file));
|
|
9438
9488
|
if (!fm) continue;
|
|
9439
9489
|
const wrapped = `---
|
|
9440
9490
|
${fm}
|
|
@@ -9452,11 +9502,11 @@ ${fm}
|
|
|
9452
9502
|
return out;
|
|
9453
9503
|
}
|
|
9454
9504
|
function computeDormantPlugins(accountId) {
|
|
9455
|
-
const accountFile =
|
|
9456
|
-
if (!
|
|
9505
|
+
const accountFile = resolve14(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
|
|
9506
|
+
if (!existsSync16(accountFile)) return [];
|
|
9457
9507
|
let enabled;
|
|
9458
9508
|
try {
|
|
9459
|
-
const raw =
|
|
9509
|
+
const raw = readFileSync16(accountFile, "utf-8");
|
|
9460
9510
|
const parsed = JSON.parse(raw);
|
|
9461
9511
|
enabled = new Set(
|
|
9462
9512
|
Array.isArray(parsed.enabledPlugins) ? parsed.enabledPlugins.filter((p) => typeof p === "string") : []
|
|
@@ -9467,8 +9517,8 @@ function computeDormantPlugins(accountId) {
|
|
|
9467
9517
|
);
|
|
9468
9518
|
return [];
|
|
9469
9519
|
}
|
|
9470
|
-
const pluginsDir =
|
|
9471
|
-
if (!
|
|
9520
|
+
const pluginsDir = resolve14(PLATFORM_ROOT, "plugins");
|
|
9521
|
+
if (!existsSync16(pluginsDir)) return [];
|
|
9472
9522
|
let entries;
|
|
9473
9523
|
try {
|
|
9474
9524
|
entries = readdirSync9(pluginsDir);
|
|
@@ -9478,11 +9528,11 @@ function computeDormantPlugins(accountId) {
|
|
|
9478
9528
|
const out = [];
|
|
9479
9529
|
for (const name of entries) {
|
|
9480
9530
|
if (enabled.has(name)) continue;
|
|
9481
|
-
const manifestPath =
|
|
9482
|
-
if (!
|
|
9531
|
+
const manifestPath = resolve14(pluginsDir, name, "PLUGIN.md");
|
|
9532
|
+
if (!existsSync16(manifestPath)) continue;
|
|
9483
9533
|
let manifest;
|
|
9484
9534
|
try {
|
|
9485
|
-
manifest =
|
|
9535
|
+
manifest = readFileSync16(manifestPath, "utf-8");
|
|
9486
9536
|
} catch {
|
|
9487
9537
|
continue;
|
|
9488
9538
|
}
|
|
@@ -9496,13 +9546,13 @@ function computeDormantPlugins(accountId) {
|
|
|
9496
9546
|
}
|
|
9497
9547
|
|
|
9498
9548
|
// server/routes/admin/claude-sessions.ts
|
|
9499
|
-
import { existsSync as
|
|
9500
|
-
import { resolve as
|
|
9549
|
+
import { existsSync as existsSync17, readFileSync as readFileSync17 } from "fs";
|
|
9550
|
+
import { resolve as resolve15 } from "path";
|
|
9501
9551
|
function readTunnelState(brandConfigDir) {
|
|
9502
9552
|
const statePath = `${process.env.HOME ?? ""}/${brandConfigDir}/cloudflared/tunnel.state`;
|
|
9503
|
-
if (!
|
|
9553
|
+
if (!existsSync17(statePath)) return null;
|
|
9504
9554
|
try {
|
|
9505
|
-
const parsed = JSON.parse(
|
|
9555
|
+
const parsed = JSON.parse(readFileSync17(statePath, "utf-8"));
|
|
9506
9556
|
const tunnelId = typeof parsed.tunnelId === "string" ? parsed.tunnelId : null;
|
|
9507
9557
|
const tunnelName = typeof parsed.tunnelName === "string" ? parsed.tunnelName : null;
|
|
9508
9558
|
const domain = typeof parsed.domain === "string" ? parsed.domain : null;
|
|
@@ -9513,10 +9563,10 @@ function readTunnelState(brandConfigDir) {
|
|
|
9513
9563
|
}
|
|
9514
9564
|
}
|
|
9515
9565
|
function resolveTunnelUrl() {
|
|
9516
|
-
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? process.env.PLATFORM_ROOT ??
|
|
9566
|
+
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? process.env.PLATFORM_ROOT ?? resolve15(process.cwd(), "..");
|
|
9517
9567
|
let brand;
|
|
9518
9568
|
try {
|
|
9519
|
-
brand = JSON.parse(
|
|
9569
|
+
brand = JSON.parse(readFileSync17(resolve15(platformRoot2, "config", "brand.json"), "utf-8"));
|
|
9520
9570
|
} catch {
|
|
9521
9571
|
return null;
|
|
9522
9572
|
}
|
|
@@ -9527,7 +9577,7 @@ function resolveTunnelUrl() {
|
|
|
9527
9577
|
if (!state) return null;
|
|
9528
9578
|
return `https://${hostname2}.${state.domain}`;
|
|
9529
9579
|
}
|
|
9530
|
-
var
|
|
9580
|
+
var TAG22 = "[claude-session-manager:wrapper]";
|
|
9531
9581
|
async function refuseIfClaudeAuthDead(c, route, sessionId) {
|
|
9532
9582
|
const auth = await ensureAuth();
|
|
9533
9583
|
if (auth.status !== "dead" && auth.status !== "missing") return null;
|
|
@@ -9539,18 +9589,18 @@ async function refuseIfClaudeAuthDead(c, route, sessionId) {
|
|
|
9539
9589
|
503
|
|
9540
9590
|
);
|
|
9541
9591
|
}
|
|
9542
|
-
var
|
|
9592
|
+
var app15 = new Hono();
|
|
9543
9593
|
async function performSpawnWithInitialMessage(args) {
|
|
9544
9594
|
const ownerStart = Date.now();
|
|
9545
9595
|
const aboutOwner = await resolveOwnerProfileBlock(args.senderId, args.userId);
|
|
9546
9596
|
const ownerMs = Date.now() - ownerStart;
|
|
9547
9597
|
const aboutOwnerStatus = aboutOwner == null ? "absent" : "ok" in aboutOwner && aboutOwner.ok === false ? `unresolved:${aboutOwner.reason}` : "ok";
|
|
9548
|
-
console.log(`${
|
|
9598
|
+
console.log(`${TAG22} about-owner-resolved status=${aboutOwnerStatus} ms=${ownerMs}`);
|
|
9549
9599
|
const dormantPlugins = computeDormantPlugins(args.senderId);
|
|
9550
9600
|
const activePlugins = computeActivePlugins(args.senderId);
|
|
9551
9601
|
const specialistDomains = computeSpecialistDomains(args.senderId);
|
|
9552
9602
|
const tunnelUrl = resolveTunnelUrl();
|
|
9553
|
-
console.log(`${
|
|
9603
|
+
console.log(`${TAG22} tunnel-url-resolved value=${tunnelUrl ?? "null"}`);
|
|
9554
9604
|
const upstreamPayload = JSON.stringify({
|
|
9555
9605
|
senderId: args.senderId,
|
|
9556
9606
|
// Task 205 — pass userId through to the manager so it lands as
|
|
@@ -9577,24 +9627,24 @@ async function performSpawnWithInitialMessage(args) {
|
|
|
9577
9627
|
// unshapely values.
|
|
9578
9628
|
conversationNodeId: args.conversationNodeId
|
|
9579
9629
|
});
|
|
9580
|
-
console.log(`${
|
|
9630
|
+
console.log(`${TAG22} forward-spawn-start managerBase=${managerBase3("claude-session-manager:wrapper")} bytes=${upstreamPayload.length} hidden=${args.hidden} specialist=${args.specialist ?? "none"}`);
|
|
9581
9631
|
const forwardStart = Date.now();
|
|
9582
9632
|
const upstream = await fetch(`${managerBase3("claude-session-manager:wrapper")}/public-spawn`, {
|
|
9583
9633
|
method: "POST",
|
|
9584
9634
|
headers: { "content-type": "application/json" },
|
|
9585
9635
|
body: upstreamPayload
|
|
9586
9636
|
}).catch((err) => {
|
|
9587
|
-
console.error(`${
|
|
9637
|
+
console.error(`${TAG22} fetch-failed op=spawn message=${err instanceof Error ? err.message : String(err)} ms=${Date.now() - forwardStart}`);
|
|
9588
9638
|
return null;
|
|
9589
9639
|
});
|
|
9590
9640
|
if (!upstream) return {
|
|
9591
9641
|
response: new Response(JSON.stringify({ error: "manager-unreachable" }), { status: 503, headers: { "content-type": "application/json" } }),
|
|
9592
9642
|
claudeSessionId: null
|
|
9593
9643
|
};
|
|
9594
|
-
console.log(`${
|
|
9644
|
+
console.log(`${TAG22} forward-spawn-done status=${upstream.status} ms=${Date.now() - forwardStart}`);
|
|
9595
9645
|
if (args.initialMessage) {
|
|
9596
9646
|
const inputBytes = Buffer.byteLength(args.initialMessage, "utf8");
|
|
9597
|
-
console.log(`${
|
|
9647
|
+
console.log(`${TAG22} initial-message-inlined bytes=${inputBytes}`);
|
|
9598
9648
|
}
|
|
9599
9649
|
const bodyText = await upstream.text().catch(() => "");
|
|
9600
9650
|
let claudeSessionId = null;
|
|
@@ -9616,8 +9666,8 @@ function mintTranscriptEdge(sessionId, claudeSessionId, accountId) {
|
|
|
9616
9666
|
if (!sessionId || !claudeSessionId) return;
|
|
9617
9667
|
void linkConversationTranscript(sessionId, claudeSessionId, accountId);
|
|
9618
9668
|
}
|
|
9619
|
-
|
|
9620
|
-
|
|
9669
|
+
app15.use("*", requireAdminSession);
|
|
9670
|
+
app15.post("/", async (c) => {
|
|
9621
9671
|
const routeStart = Date.now();
|
|
9622
9672
|
const cacheKey = c.get("cacheKey") ?? "";
|
|
9623
9673
|
let body = {};
|
|
@@ -9629,7 +9679,7 @@ app14.post("/", async (c) => {
|
|
|
9629
9679
|
if (refusal) return refusal;
|
|
9630
9680
|
const senderId = getAccountIdForSession(cacheKey) ?? "";
|
|
9631
9681
|
if (!senderId) {
|
|
9632
|
-
console.error(`${
|
|
9682
|
+
console.error(`${TAG22} reject reason=no-account-id cacheKey-prefix=${cacheKey.slice(0, 8)}`);
|
|
9633
9683
|
return c.json({ error: "admin-account-not-resolved" }, 500);
|
|
9634
9684
|
}
|
|
9635
9685
|
const userId = getUserIdForSession(cacheKey) ?? void 0;
|
|
@@ -9638,7 +9688,7 @@ app14.post("/", async (c) => {
|
|
|
9638
9688
|
const permissionMode = typeof body.permissionMode === "string" ? body.permissionMode : void 0;
|
|
9639
9689
|
const specialist = typeof body.specialist === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(body.specialist) ? body.specialist : void 0;
|
|
9640
9690
|
const model = typeof body.model === "string" && /^[A-Za-z0-9._-]{1,64}$/.test(body.model) ? body.model : void 0;
|
|
9641
|
-
console.log(`${
|
|
9691
|
+
console.log(`${TAG22} spawn-request-in surface=cookie accountId=${senderId.slice(0, 8)} userId=${userId ? userId.slice(0, 8) : "absent"} channel=${channel} permissionMode=${permissionMode ?? "default"} specialist=${specialist ?? "none"} model=${model ?? "default"} initialMessage=${initialMessage ? "yes" : "no"}`);
|
|
9642
9692
|
const conversationNodeId = cacheKey ? getSessionIdForSession(cacheKey) : void 0;
|
|
9643
9693
|
const { response, claudeSessionId } = await performSpawnWithInitialMessage({
|
|
9644
9694
|
senderId,
|
|
@@ -9657,24 +9707,24 @@ app14.post("/", async (c) => {
|
|
|
9657
9707
|
claudeSessionId,
|
|
9658
9708
|
senderId
|
|
9659
9709
|
);
|
|
9660
|
-
console.log(`${
|
|
9710
|
+
console.log(`${TAG22} route-done surface=cookie status=${response.status} route-ms=${Date.now() - routeStart}`);
|
|
9661
9711
|
return response;
|
|
9662
9712
|
});
|
|
9663
|
-
var claude_sessions_default =
|
|
9713
|
+
var claude_sessions_default = app15;
|
|
9664
9714
|
|
|
9665
9715
|
// server/routes/admin/log-ingest.ts
|
|
9666
|
-
var
|
|
9716
|
+
var TAG23 = "[log-ingest]";
|
|
9667
9717
|
var TAG_PATTERN = /^[A-Za-z0-9_:-]{1,32}$/;
|
|
9668
9718
|
var LEVELS = /* @__PURE__ */ new Set(["debug", "info", "warn", "error"]);
|
|
9669
9719
|
var MAX_LINE_BYTES = 4096;
|
|
9670
|
-
var
|
|
9720
|
+
var app16 = new Hono();
|
|
9671
9721
|
function isLoopbackAddr(addr) {
|
|
9672
9722
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
9673
9723
|
}
|
|
9674
|
-
|
|
9724
|
+
app16.post("/", async (c) => {
|
|
9675
9725
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
9676
9726
|
if (!isLoopbackAddr(remoteAddr)) {
|
|
9677
|
-
console.error(`${
|
|
9727
|
+
console.error(`${TAG23} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
9678
9728
|
return c.json({ error: "log-ingest-loopback-only" }, 403);
|
|
9679
9729
|
}
|
|
9680
9730
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
@@ -9683,7 +9733,7 @@ app15.post("/", async (c) => {
|
|
|
9683
9733
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
9684
9734
|
const offender = tokens.find((t) => !isLoopbackAddr(t));
|
|
9685
9735
|
if (offender !== void 0) {
|
|
9686
|
-
console.error(`${
|
|
9736
|
+
console.error(`${TAG23} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
9687
9737
|
return c.json({ error: "log-ingest-loopback-only" }, 403);
|
|
9688
9738
|
}
|
|
9689
9739
|
}
|
|
@@ -9714,7 +9764,7 @@ app15.post("/", async (c) => {
|
|
|
9714
9764
|
else console.log(formatted);
|
|
9715
9765
|
return c.json({ ok: true }, 200);
|
|
9716
9766
|
});
|
|
9717
|
-
var log_ingest_default =
|
|
9767
|
+
var log_ingest_default = app16;
|
|
9718
9768
|
|
|
9719
9769
|
// server/routes/admin/events.ts
|
|
9720
9770
|
var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
|
|
@@ -9723,20 +9773,20 @@ var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
|
|
|
9723
9773
|
"device-url:vnc-surface-shown",
|
|
9724
9774
|
"device-url:malformed"
|
|
9725
9775
|
]);
|
|
9726
|
-
var
|
|
9727
|
-
|
|
9728
|
-
const
|
|
9776
|
+
var app17 = new Hono();
|
|
9777
|
+
app17.post("/", async (c) => {
|
|
9778
|
+
const TAG31 = "[admin:events]";
|
|
9729
9779
|
let body;
|
|
9730
9780
|
try {
|
|
9731
9781
|
body = await c.req.json();
|
|
9732
9782
|
} catch (err) {
|
|
9733
9783
|
const detail = err instanceof Error ? err.message : String(err);
|
|
9734
|
-
console.error(`${
|
|
9784
|
+
console.error(`${TAG31} reject reason=body-not-json detail=${detail}`);
|
|
9735
9785
|
return c.json({ ok: false, detail: "Request body was not valid JSON" }, 400);
|
|
9736
9786
|
}
|
|
9737
9787
|
const event = typeof body.event === "string" ? body.event : "";
|
|
9738
9788
|
if (!ALLOWED_EVENTS.has(event)) {
|
|
9739
|
-
console.error(`${
|
|
9789
|
+
console.error(`${TAG31} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
|
|
9740
9790
|
return c.json({ ok: false, detail: `Event "${event}" is not allowed` }, 400);
|
|
9741
9791
|
}
|
|
9742
9792
|
const rawFields = body.fields && typeof body.fields === "object" ? body.fields : {};
|
|
@@ -9755,21 +9805,21 @@ app16.post("/", async (c) => {
|
|
|
9755
9805
|
console.error(`[${event}] ${formatted}`);
|
|
9756
9806
|
return c.json({ ok: true });
|
|
9757
9807
|
});
|
|
9758
|
-
var events_default =
|
|
9808
|
+
var events_default = app17;
|
|
9759
9809
|
|
|
9760
9810
|
// server/routes/admin/files.ts
|
|
9761
9811
|
import { createReadStream as createReadStream2 } from "fs";
|
|
9762
9812
|
import { readdir as readdir3, readFile as readFile4, stat as stat4, mkdir as mkdir3, writeFile as writeFile4, unlink as unlink2 } from "fs/promises";
|
|
9763
9813
|
import { realpathSync as realpathSync4 } from "fs";
|
|
9764
|
-
import { basename as basename5, dirname as dirname4, join as
|
|
9814
|
+
import { basename as basename5, dirname as dirname4, join as join16, resolve as resolve18, sep as sep5 } from "path";
|
|
9765
9815
|
import { Readable as Readable2 } from "stream";
|
|
9766
9816
|
|
|
9767
9817
|
// app/lib/data-path.ts
|
|
9768
9818
|
import { realpathSync as realpathSync3 } from "fs";
|
|
9769
|
-
import { resolve as
|
|
9770
|
-
var PLATFORM_ROOT5 = process.env.MAXY_PLATFORM_ROOT ??
|
|
9771
|
-
var DATA_ROOT =
|
|
9772
|
-
var CLAUDE_UPLOADS_ROOT = process.env.CLAUDE_CONFIG_DIR ?
|
|
9819
|
+
import { resolve as resolve16, normalize, sep as sep3, relative } from "path";
|
|
9820
|
+
var PLATFORM_ROOT5 = process.env.MAXY_PLATFORM_ROOT ?? resolve16(process.cwd(), "../platform");
|
|
9821
|
+
var DATA_ROOT = resolve16(PLATFORM_ROOT5, "..", "data");
|
|
9822
|
+
var CLAUDE_UPLOADS_ROOT = process.env.CLAUDE_CONFIG_DIR ? resolve16(process.env.CLAUDE_CONFIG_DIR, "uploads") : null;
|
|
9773
9823
|
var ACCOUNT_PARTITION_DIRS = /* @__PURE__ */ new Set(["uploads", "accounts"]);
|
|
9774
9824
|
var ACCOUNT_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
9775
9825
|
function crossesForeignAccountPartition(relPath, accountId) {
|
|
@@ -9782,7 +9832,7 @@ function crossesForeignAccountPartition(relPath, accountId) {
|
|
|
9782
9832
|
}
|
|
9783
9833
|
function resolveUnderRoot(raw, rootAbs, rootLabel) {
|
|
9784
9834
|
const cleaned = normalize("/" + (raw ?? "").replace(/\\/g, "/")).replace(/^\/+/, "");
|
|
9785
|
-
const absolute =
|
|
9835
|
+
const absolute = resolve16(rootAbs, cleaned);
|
|
9786
9836
|
let rootReal;
|
|
9787
9837
|
try {
|
|
9788
9838
|
rootReal = realpathSync3(rootAbs);
|
|
@@ -10141,7 +10191,7 @@ async function cascadeDeleteDocument(params) {
|
|
|
10141
10191
|
|
|
10142
10192
|
// app/lib/file-index.ts
|
|
10143
10193
|
import * as fsp from "fs/promises";
|
|
10144
|
-
import { resolve as
|
|
10194
|
+
import { resolve as resolve17, relative as relative2, join as join15, basename as basename4, extname as extname2, sep as sep4 } from "path";
|
|
10145
10195
|
import { tmpdir as tmpdir2 } from "os";
|
|
10146
10196
|
import { execFile as execFile2 } from "child_process";
|
|
10147
10197
|
import { promisify as promisify2 } from "util";
|
|
@@ -10212,7 +10262,7 @@ async function extractPdf(absolute) {
|
|
|
10212
10262
|
return stdout.trim();
|
|
10213
10263
|
}
|
|
10214
10264
|
async function ocrPdf(absolute) {
|
|
10215
|
-
const outPdf =
|
|
10265
|
+
const outPdf = join15(tmpdir2(), `file-index-ocr-${process.pid}-${Date.now()}.pdf`);
|
|
10216
10266
|
try {
|
|
10217
10267
|
await execFileAsync2(
|
|
10218
10268
|
"ocrmypdf",
|
|
@@ -10274,7 +10324,7 @@ async function readDisplayName(absolute) {
|
|
|
10274
10324
|
const stem = dot === -1 ? base : base.slice(0, dot);
|
|
10275
10325
|
if (base === `${stem}.meta.json`) return null;
|
|
10276
10326
|
try {
|
|
10277
|
-
const raw = await fsp.readFile(
|
|
10327
|
+
const raw = await fsp.readFile(join15(dir, `${stem}.meta.json`), "utf-8");
|
|
10278
10328
|
const meta = JSON.parse(raw);
|
|
10279
10329
|
const dn = meta.displayName ?? meta.filename;
|
|
10280
10330
|
return typeof dn === "string" && dn.length > 0 ? dn : null;
|
|
@@ -10294,7 +10344,7 @@ async function walkSubtree(root, dataRoot, out) {
|
|
|
10294
10344
|
let clean = true;
|
|
10295
10345
|
for (const entry of entries) {
|
|
10296
10346
|
if (entry.isSymbolicLink()) continue;
|
|
10297
|
-
const abs =
|
|
10347
|
+
const abs = join15(root, entry.name);
|
|
10298
10348
|
if (entry.isDirectory()) {
|
|
10299
10349
|
const sub = await walkSubtree(abs, dataRoot, out);
|
|
10300
10350
|
if (!sub.clean) clean = false;
|
|
@@ -10449,8 +10499,8 @@ async function reconcileFileIndex(accountId, opts) {
|
|
|
10449
10499
|
return withSession(opts, async (session) => {
|
|
10450
10500
|
const walked = [];
|
|
10451
10501
|
const subtreeRoots = [
|
|
10452
|
-
|
|
10453
|
-
|
|
10502
|
+
resolve17(dataRoot, "uploads", accountId),
|
|
10503
|
+
resolve17(dataRoot, "accounts", accountId)
|
|
10454
10504
|
];
|
|
10455
10505
|
let clean = true;
|
|
10456
10506
|
for (const root of subtreeRoots) {
|
|
@@ -10531,7 +10581,7 @@ async function reconcileFileIndexDebounced(accountId, opts) {
|
|
|
10531
10581
|
async function indexFile(accountId, relativePath, opts) {
|
|
10532
10582
|
const dataRoot = opts?.dataRoot ?? DATA_ROOT;
|
|
10533
10583
|
const embed2 = opts?.embed ?? embed;
|
|
10534
|
-
const absolute =
|
|
10584
|
+
const absolute = resolve17(dataRoot, relativePath);
|
|
10535
10585
|
await withSession(opts, async (session) => {
|
|
10536
10586
|
const st = await fsp.stat(absolute);
|
|
10537
10587
|
const wf = {
|
|
@@ -10565,7 +10615,7 @@ async function dropFileIndex(accountId, relativePath, opts) {
|
|
|
10565
10615
|
var UUID_RE3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
10566
10616
|
async function readMeta(absDir, baseName) {
|
|
10567
10617
|
try {
|
|
10568
|
-
const raw = await readFile4(
|
|
10618
|
+
const raw = await readFile4(join16(absDir, `${baseName}.meta.json`), "utf8");
|
|
10569
10619
|
const parsed = JSON.parse(raw);
|
|
10570
10620
|
if (typeof parsed?.filename === "string") {
|
|
10571
10621
|
return { filename: parsed.filename, mimeType: typeof parsed.mimeType === "string" ? parsed.mimeType : void 0 };
|
|
@@ -10576,7 +10626,7 @@ async function readMeta(absDir, baseName) {
|
|
|
10576
10626
|
}
|
|
10577
10627
|
async function readAccountNames() {
|
|
10578
10628
|
const map = /* @__PURE__ */ new Map();
|
|
10579
|
-
const accountsDir =
|
|
10629
|
+
const accountsDir = resolve18(DATA_ROOT, "accounts");
|
|
10580
10630
|
let names;
|
|
10581
10631
|
try {
|
|
10582
10632
|
names = await readdir3(accountsDir);
|
|
@@ -10585,7 +10635,7 @@ async function readAccountNames() {
|
|
|
10585
10635
|
}
|
|
10586
10636
|
for (const name of names) {
|
|
10587
10637
|
if (!UUID_RE3.test(name)) continue;
|
|
10588
|
-
const configPath2 =
|
|
10638
|
+
const configPath2 = resolve18(accountsDir, name, "account.json");
|
|
10589
10639
|
try {
|
|
10590
10640
|
const raw = await readFile4(configPath2, "utf8");
|
|
10591
10641
|
const parsed = JSON.parse(raw);
|
|
@@ -10603,7 +10653,7 @@ async function readAccountNames() {
|
|
|
10603
10653
|
}
|
|
10604
10654
|
async function enrich(absolute, entry, accountNames) {
|
|
10605
10655
|
if (entry.kind === "directory" && UUID_RE3.test(entry.name)) {
|
|
10606
|
-
const meta = await readMeta(
|
|
10656
|
+
const meta = await readMeta(join16(absolute, entry.name), entry.name);
|
|
10607
10657
|
if (meta?.filename) {
|
|
10608
10658
|
entry.displayName = meta.filename;
|
|
10609
10659
|
entry.mimeType = meta.mimeType;
|
|
@@ -10663,8 +10713,8 @@ async function knowledgeDocOwnedByAccount(accountId, attachmentId) {
|
|
|
10663
10713
|
await session.close();
|
|
10664
10714
|
}
|
|
10665
10715
|
}
|
|
10666
|
-
var
|
|
10667
|
-
|
|
10716
|
+
var app18 = new Hono();
|
|
10717
|
+
app18.get("/", requireAdminSession, async (c) => {
|
|
10668
10718
|
const cacheKey = c.var.cacheKey;
|
|
10669
10719
|
const accountId = getAccountIdForSession(cacheKey);
|
|
10670
10720
|
if (!accountId) {
|
|
@@ -10701,7 +10751,7 @@ app17.get("/", requireAdminSession, async (c) => {
|
|
|
10701
10751
|
continue;
|
|
10702
10752
|
}
|
|
10703
10753
|
try {
|
|
10704
|
-
const entryPath =
|
|
10754
|
+
const entryPath = join16(absolute, name);
|
|
10705
10755
|
const s = await stat4(entryPath);
|
|
10706
10756
|
entries.push({
|
|
10707
10757
|
name,
|
|
@@ -10730,7 +10780,7 @@ app17.get("/", requireAdminSession, async (c) => {
|
|
|
10730
10780
|
return c.json({ error: message }, 500);
|
|
10731
10781
|
}
|
|
10732
10782
|
});
|
|
10733
|
-
|
|
10783
|
+
app18.get("/download", requireAdminSession, async (c) => {
|
|
10734
10784
|
const cacheKey = c.var.cacheKey;
|
|
10735
10785
|
const accountId = getAccountIdForSession(cacheKey);
|
|
10736
10786
|
if (!accountId) {
|
|
@@ -10794,7 +10844,7 @@ app17.get("/download", requireAdminSession, async (c) => {
|
|
|
10794
10844
|
return c.json({ error: message }, 500);
|
|
10795
10845
|
}
|
|
10796
10846
|
});
|
|
10797
|
-
|
|
10847
|
+
app18.post("/upload", requireAdminSession, async (c) => {
|
|
10798
10848
|
const cacheKey = c.var.cacheKey;
|
|
10799
10849
|
const accountId = getAccountIdForSession(cacheKey);
|
|
10800
10850
|
if (!accountId) {
|
|
@@ -10826,8 +10876,8 @@ app17.post("/upload", requireAdminSession, async (c) => {
|
|
|
10826
10876
|
}
|
|
10827
10877
|
const safeName = basename5(file.name).replace(/[\0/\\]/g, "_");
|
|
10828
10878
|
const finalName = `${Date.now()}-${safeName}`;
|
|
10829
|
-
const destDir =
|
|
10830
|
-
const destPath =
|
|
10879
|
+
const destDir = resolve18(DATA_ROOT, "uploads", accountId);
|
|
10880
|
+
const destPath = resolve18(destDir, finalName);
|
|
10831
10881
|
try {
|
|
10832
10882
|
await mkdir3(destDir, { recursive: true });
|
|
10833
10883
|
const dataRootReal = realpathSync4(DATA_ROOT);
|
|
@@ -10855,7 +10905,7 @@ app17.post("/upload", requireAdminSession, async (c) => {
|
|
|
10855
10905
|
mimeType: file.type
|
|
10856
10906
|
});
|
|
10857
10907
|
});
|
|
10858
|
-
|
|
10908
|
+
app18.delete("/", requireAdminSession, async (c) => {
|
|
10859
10909
|
const cacheKey = c.var.cacheKey;
|
|
10860
10910
|
const accountId = getAccountIdForSession(cacheKey);
|
|
10861
10911
|
if (!accountId) {
|
|
@@ -10892,7 +10942,7 @@ app17.delete("/", requireAdminSession, async (c) => {
|
|
|
10892
10942
|
}
|
|
10893
10943
|
const dot = base.lastIndexOf(".");
|
|
10894
10944
|
const stem = dot === -1 ? base : base.slice(0, dot);
|
|
10895
|
-
const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ?
|
|
10945
|
+
const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ? join16(dirname4(absolute), `${stem}.meta.json`) : null;
|
|
10896
10946
|
await unlink2(absolute);
|
|
10897
10947
|
if (sidecarPath) {
|
|
10898
10948
|
try {
|
|
@@ -10931,7 +10981,7 @@ app17.delete("/", requireAdminSession, async (c) => {
|
|
|
10931
10981
|
return c.json({ error: message }, 500);
|
|
10932
10982
|
}
|
|
10933
10983
|
});
|
|
10934
|
-
var files_default =
|
|
10984
|
+
var files_default = app18;
|
|
10935
10985
|
|
|
10936
10986
|
// ../lib/graph-search/src/index.ts
|
|
10937
10987
|
var import_dist = __toESM(require_dist());
|
|
@@ -11696,8 +11746,8 @@ var DEFAULT_LIMIT = 20;
|
|
|
11696
11746
|
var MAX_LIMIT = 2e3;
|
|
11697
11747
|
var MESSAGE_FAMILY_LABELS = ["Message", "UserMessage", "AssistantMessage", "WhatsAppMessage"];
|
|
11698
11748
|
var CONVERSATION_PARENT_LABELS = /* @__PURE__ */ new Set(["AdminConversation", "PublicConversation"]);
|
|
11699
|
-
var
|
|
11700
|
-
|
|
11749
|
+
var app19 = new Hono();
|
|
11750
|
+
app19.get("/", requireAdminSession, async (c) => {
|
|
11701
11751
|
const cacheKey = c.var.cacheKey;
|
|
11702
11752
|
const q = (c.req.query("q") ?? "").trim();
|
|
11703
11753
|
const rawLimit = c.req.query("limit");
|
|
@@ -11833,7 +11883,7 @@ app18.get("/", requireAdminSession, async (c) => {
|
|
|
11833
11883
|
await session.close();
|
|
11834
11884
|
}
|
|
11835
11885
|
});
|
|
11836
|
-
var graph_search_default =
|
|
11886
|
+
var graph_search_default = app19;
|
|
11837
11887
|
|
|
11838
11888
|
// server/routes/admin/graph-subgraph.ts
|
|
11839
11889
|
import neo4j from "neo4j-driver";
|
|
@@ -11911,8 +11961,8 @@ var STRIPPED_PROPERTIES = /* @__PURE__ */ new Set([
|
|
|
11911
11961
|
"otpCode",
|
|
11912
11962
|
"cacheKey"
|
|
11913
11963
|
]);
|
|
11914
|
-
var
|
|
11915
|
-
|
|
11964
|
+
var app20 = new Hono();
|
|
11965
|
+
app20.get("/", requireAdminSession, async (c) => {
|
|
11916
11966
|
const cacheKey = c.var.cacheKey;
|
|
11917
11967
|
const accountId = getAccountIdForSession(cacheKey);
|
|
11918
11968
|
if (!accountId) {
|
|
@@ -12495,12 +12545,12 @@ function pruneNode(node, warnedClasses, conversationWarnings) {
|
|
|
12495
12545
|
}
|
|
12496
12546
|
return trashed ? { id: node.id, labels, properties, trashed: true } : { id: node.id, labels, properties };
|
|
12497
12547
|
}
|
|
12498
|
-
var graph_subgraph_default =
|
|
12548
|
+
var graph_subgraph_default = app20;
|
|
12499
12549
|
|
|
12500
12550
|
// server/routes/admin/graph-delete.ts
|
|
12501
12551
|
var ALLOWED_BY = ["graph-page", "graph-drag-trash", "graph-side-panel-trash"];
|
|
12502
|
-
var
|
|
12503
|
-
|
|
12552
|
+
var app21 = new Hono();
|
|
12553
|
+
app21.post("/", requireAdminSession, async (c) => {
|
|
12504
12554
|
const cacheKey = c.var.cacheKey;
|
|
12505
12555
|
const accountId = getAccountIdForSession(cacheKey);
|
|
12506
12556
|
if (!accountId) {
|
|
@@ -12662,11 +12712,11 @@ app20.post("/", requireAdminSession, async (c) => {
|
|
|
12662
12712
|
}
|
|
12663
12713
|
}
|
|
12664
12714
|
});
|
|
12665
|
-
var graph_delete_default =
|
|
12715
|
+
var graph_delete_default = app21;
|
|
12666
12716
|
|
|
12667
12717
|
// server/routes/admin/graph-restore.ts
|
|
12668
|
-
var
|
|
12669
|
-
|
|
12718
|
+
var app22 = new Hono();
|
|
12719
|
+
app22.post("/", requireAdminSession, async (c) => {
|
|
12670
12720
|
const cacheKey = c.var.cacheKey;
|
|
12671
12721
|
const accountId = getAccountIdForSession(cacheKey);
|
|
12672
12722
|
if (!accountId) {
|
|
@@ -12730,11 +12780,11 @@ app21.post("/", requireAdminSession, async (c) => {
|
|
|
12730
12780
|
}
|
|
12731
12781
|
}
|
|
12732
12782
|
});
|
|
12733
|
-
var graph_restore_default =
|
|
12783
|
+
var graph_restore_default = app22;
|
|
12734
12784
|
|
|
12735
12785
|
// server/routes/admin/graph-labels-in-graph.ts
|
|
12736
|
-
var
|
|
12737
|
-
|
|
12786
|
+
var app23 = new Hono();
|
|
12787
|
+
app23.get("/", requireAdminSession, async (c) => {
|
|
12738
12788
|
const cacheKey = c.var.cacheKey;
|
|
12739
12789
|
const accountId = getAccountIdForSession(cacheKey);
|
|
12740
12790
|
if (!accountId) {
|
|
@@ -12800,11 +12850,11 @@ var LABELS_IN_GRAPH_CYPHER = `
|
|
|
12800
12850
|
sum(halfEdges) AS relDegree
|
|
12801
12851
|
RETURN label, nodeCount, relDegree
|
|
12802
12852
|
`;
|
|
12803
|
-
var graph_labels_in_graph_default =
|
|
12853
|
+
var graph_labels_in_graph_default = app23;
|
|
12804
12854
|
|
|
12805
12855
|
// server/routes/admin/graph-default-view.ts
|
|
12806
|
-
var
|
|
12807
|
-
|
|
12856
|
+
var app24 = new Hono();
|
|
12857
|
+
app24.get("/", requireAdminSession, async (c) => {
|
|
12808
12858
|
const cacheKey = c.var.cacheKey;
|
|
12809
12859
|
const accountId = getAccountIdForSession(cacheKey);
|
|
12810
12860
|
const userId = getUserIdForSession(cacheKey);
|
|
@@ -12842,7 +12892,7 @@ app23.get("/", requireAdminSession, async (c) => {
|
|
|
12842
12892
|
}
|
|
12843
12893
|
}
|
|
12844
12894
|
});
|
|
12845
|
-
|
|
12895
|
+
app24.put("/", requireAdminSession, async (c) => {
|
|
12846
12896
|
const cacheKey = c.var.cacheKey;
|
|
12847
12897
|
const accountId = getAccountIdForSession(cacheKey);
|
|
12848
12898
|
const userId = getUserIdForSession(cacheKey);
|
|
@@ -12931,20 +12981,20 @@ var WRITE_CYPHER = `
|
|
|
12931
12981
|
p.updatedAt = $updatedAt
|
|
12932
12982
|
RETURN p.labels AS labels
|
|
12933
12983
|
`;
|
|
12934
|
-
var graph_default_view_default =
|
|
12984
|
+
var graph_default_view_default = app24;
|
|
12935
12985
|
|
|
12936
12986
|
// server/routes/admin/sidebar-artefacts.ts
|
|
12937
12987
|
import { readdir as readdir4, stat as stat5 } from "fs/promises";
|
|
12938
|
-
import { resolve as
|
|
12939
|
-
import { existsSync as
|
|
12988
|
+
import { resolve as resolve19, relative as relative3, isAbsolute, sep as sep6, basename as basename6 } from "path";
|
|
12989
|
+
import { existsSync as existsSync18 } from "fs";
|
|
12940
12990
|
var LIMIT = 50;
|
|
12941
12991
|
var ADMIN_AGENT_FILES = ["IDENTITY.md", "SOUL.md", "KNOWLEDGE.md"];
|
|
12942
12992
|
function toDataRootRelPath(absPath) {
|
|
12943
12993
|
if (absPath !== DATA_ROOT && !absPath.startsWith(DATA_ROOT + sep6)) return null;
|
|
12944
12994
|
return relative3(DATA_ROOT, absPath);
|
|
12945
12995
|
}
|
|
12946
|
-
var
|
|
12947
|
-
|
|
12996
|
+
var app25 = new Hono();
|
|
12997
|
+
app25.get("/", requireAdminSession, async (c) => {
|
|
12948
12998
|
const cacheKey = c.var.cacheKey;
|
|
12949
12999
|
const accountId = getAccountIdForSession(cacheKey);
|
|
12950
13000
|
if (!accountId) {
|
|
@@ -12955,7 +13005,7 @@ app24.get("/", requireAdminSession, async (c) => {
|
|
|
12955
13005
|
if (accountFiles === null) {
|
|
12956
13006
|
return c.json({ error: "Failed to load artefacts" }, 500);
|
|
12957
13007
|
}
|
|
12958
|
-
const accountDir =
|
|
13008
|
+
const accountDir = resolve19(ACCOUNTS_DIR, accountId);
|
|
12959
13009
|
const agents = await fetchAgentTemplateRows(accountDir);
|
|
12960
13010
|
const artefacts = [...accountFiles, ...agents].sort(
|
|
12961
13011
|
(a, b) => (b.updatedAt ?? "").localeCompare(a.updatedAt ?? "")
|
|
@@ -13006,8 +13056,8 @@ async function fetchAccountFileArtefacts(accountId) {
|
|
|
13006
13056
|
async function fetchAgentTemplateRows(accountDir) {
|
|
13007
13057
|
const rows = [];
|
|
13008
13058
|
for (const filename of ADMIN_AGENT_FILES) {
|
|
13009
|
-
const overridePath =
|
|
13010
|
-
const bundledPath =
|
|
13059
|
+
const overridePath = resolve19(accountDir, "agents", "admin", filename);
|
|
13060
|
+
const bundledPath = resolve19(PLATFORM_ROOT, "templates", "agents", "admin", filename);
|
|
13011
13061
|
const labelStem = filename.replace(/\.md$/, "");
|
|
13012
13062
|
const row = await readAgentTemplateRow({
|
|
13013
13063
|
id: `agent-template:admin:${filename}`,
|
|
@@ -13020,12 +13070,12 @@ async function fetchAgentTemplateRows(accountDir) {
|
|
|
13020
13070
|
});
|
|
13021
13071
|
if (row) rows.push(row);
|
|
13022
13072
|
}
|
|
13023
|
-
const overrideDir =
|
|
13024
|
-
const bundledDir =
|
|
13073
|
+
const overrideDir = resolve19(accountDir, "specialists", "agents");
|
|
13074
|
+
const bundledDir = resolve19(PLATFORM_ROOT, "templates", "specialists", "agents");
|
|
13025
13075
|
const specialistNames = await unionSpecialistFilenames(overrideDir, bundledDir);
|
|
13026
13076
|
for (const filename of specialistNames) {
|
|
13027
|
-
const overridePath =
|
|
13028
|
-
const bundledPath =
|
|
13077
|
+
const overridePath = resolve19(overrideDir, filename);
|
|
13078
|
+
const bundledPath = resolve19(bundledDir, filename);
|
|
13029
13079
|
const row = await readAgentTemplateRow({
|
|
13030
13080
|
id: `agent-template:specialist:${filename}`,
|
|
13031
13081
|
displayName: filename.replace(/\.md$/, ""),
|
|
@@ -13042,7 +13092,7 @@ async function fetchAgentTemplateRows(accountDir) {
|
|
|
13042
13092
|
async function unionSpecialistFilenames(overrideDir, bundledDir) {
|
|
13043
13093
|
const names = /* @__PURE__ */ new Set();
|
|
13044
13094
|
for (const dir of [overrideDir, bundledDir]) {
|
|
13045
|
-
if (!
|
|
13095
|
+
if (!existsSync18(dir)) continue;
|
|
13046
13096
|
try {
|
|
13047
13097
|
const entries = await readdir4(dir);
|
|
13048
13098
|
for (const entry of entries) {
|
|
@@ -13057,7 +13107,7 @@ async function unionSpecialistFilenames(overrideDir, bundledDir) {
|
|
|
13057
13107
|
}
|
|
13058
13108
|
async function readAgentTemplateRow(inp) {
|
|
13059
13109
|
let chosenPath = null;
|
|
13060
|
-
if (
|
|
13110
|
+
if (existsSync18(inp.overridePath)) {
|
|
13061
13111
|
try {
|
|
13062
13112
|
validateFilePathInAccount(inp.overridePath, inp.overrideRoot);
|
|
13063
13113
|
chosenPath = inp.overridePath;
|
|
@@ -13068,7 +13118,7 @@ async function readAgentTemplateRow(inp) {
|
|
|
13068
13118
|
);
|
|
13069
13119
|
return null;
|
|
13070
13120
|
}
|
|
13071
|
-
} else if (
|
|
13121
|
+
} else if (existsSync18(inp.bundledPath)) {
|
|
13072
13122
|
if (!isWithin(inp.bundledPath, inp.bundledRoot)) {
|
|
13073
13123
|
console.error(
|
|
13074
13124
|
`[admin/sidebar-artefacts] agent-template-read-failed agent=${inp.displayName} kind=${inp.logName} error="bundled path outside PLATFORM_ROOT"`
|
|
@@ -13107,11 +13157,11 @@ function isWithin(target, root) {
|
|
|
13107
13157
|
const rel = relative3(root, target);
|
|
13108
13158
|
return !rel.startsWith("..") && !isAbsolute(rel);
|
|
13109
13159
|
}
|
|
13110
|
-
var sidebar_artefacts_default =
|
|
13160
|
+
var sidebar_artefacts_default = app25;
|
|
13111
13161
|
|
|
13112
13162
|
// server/routes/admin/session-delete.ts
|
|
13113
|
-
var
|
|
13114
|
-
|
|
13163
|
+
var app26 = new Hono();
|
|
13164
|
+
app26.post("/", requireAdminSession, async (c) => {
|
|
13115
13165
|
let body;
|
|
13116
13166
|
try {
|
|
13117
13167
|
body = await c.req.json();
|
|
@@ -13149,11 +13199,11 @@ app25.post("/", requireAdminSession, async (c) => {
|
|
|
13149
13199
|
console.error(`[session-delete] sessionId=${sessionId} delete-status=${del.status}`);
|
|
13150
13200
|
return c.json({ error: `manager-status-${del.status}` }, 502);
|
|
13151
13201
|
});
|
|
13152
|
-
var session_delete_default =
|
|
13202
|
+
var session_delete_default = app26;
|
|
13153
13203
|
|
|
13154
13204
|
// server/routes/admin/session-stop.ts
|
|
13155
|
-
var
|
|
13156
|
-
|
|
13205
|
+
var app27 = new Hono();
|
|
13206
|
+
app27.post("/", requireAdminSession, async (c) => {
|
|
13157
13207
|
let body;
|
|
13158
13208
|
try {
|
|
13159
13209
|
body = await c.req.json();
|
|
@@ -13178,11 +13228,63 @@ app26.post("/", requireAdminSession, async (c) => {
|
|
|
13178
13228
|
console.error(`[session-stop] sessionId=${sessionId} manager-status=${res.status}`);
|
|
13179
13229
|
return c.json({ error: `manager-status-${res.status}` }, 502);
|
|
13180
13230
|
});
|
|
13181
|
-
var session_stop_default =
|
|
13231
|
+
var session_stop_default = app27;
|
|
13232
|
+
|
|
13233
|
+
// server/routes/admin/session-archive.ts
|
|
13234
|
+
var app28 = new Hono();
|
|
13235
|
+
app28.post("/", requireAdminSession, async (c) => {
|
|
13236
|
+
let body;
|
|
13237
|
+
try {
|
|
13238
|
+
body = await c.req.json();
|
|
13239
|
+
} catch {
|
|
13240
|
+
return c.json({ error: "invalid JSON body" }, 400);
|
|
13241
|
+
}
|
|
13242
|
+
const sessionId = typeof body.sessionId === "string" ? body.sessionId : "";
|
|
13243
|
+
if (!SESSION_ID_RE2.test(sessionId)) {
|
|
13244
|
+
return c.json({ error: "sessionId must be a v4 UUID" }, 400);
|
|
13245
|
+
}
|
|
13246
|
+
const mode = body.mode;
|
|
13247
|
+
if (mode !== "archive" && mode !== "unarchive") {
|
|
13248
|
+
return c.json({ error: "mode must be 'archive' or 'unarchive'" }, 400);
|
|
13249
|
+
}
|
|
13250
|
+
let res;
|
|
13251
|
+
try {
|
|
13252
|
+
res = await fetch(`${managerBase3("session-archive:wrapper")}/${sessionId}/archive`, {
|
|
13253
|
+
method: "POST",
|
|
13254
|
+
headers: { "Content-Type": "application/json" },
|
|
13255
|
+
body: JSON.stringify({ mode })
|
|
13256
|
+
});
|
|
13257
|
+
} catch (err) {
|
|
13258
|
+
console.error(`[session-archive] sessionId=${sessionId} manager-unreachable err=${err instanceof Error ? err.message : String(err)}`);
|
|
13259
|
+
return c.json({ error: "manager-unreachable" }, 502);
|
|
13260
|
+
}
|
|
13261
|
+
if (res.status === 200) {
|
|
13262
|
+
const dir = mode === "archive" ? "top\u2192archive" : "archive\u2192top";
|
|
13263
|
+
console.log(`[session-archive] sessionId=${sessionId.slice(0, 8)} moved ${dir}`);
|
|
13264
|
+
return c.json({ archived: mode === "archive" });
|
|
13265
|
+
}
|
|
13266
|
+
if (res.status === 409) {
|
|
13267
|
+
console.error(`[session-archive] sessionId=${sessionId.slice(0, 8)} pty-still-alive`);
|
|
13268
|
+
return c.json({ error: "pty-still-alive", detail: "end the session before archiving" }, 409);
|
|
13269
|
+
}
|
|
13270
|
+
if (res.status === 404) {
|
|
13271
|
+
console.error(`[session-archive] sessionId=${sessionId.slice(0, 8)} session-not-found`);
|
|
13272
|
+
return c.json({ error: "session-not-found" }, 404);
|
|
13273
|
+
}
|
|
13274
|
+
if (res.status === 500) {
|
|
13275
|
+
const body2 = await res.json().catch(() => ({}));
|
|
13276
|
+
const detail = typeof body2.detail === "string" ? body2.detail : body2.error ?? "archive-failed";
|
|
13277
|
+
console.error(`[session-archive] sessionId=${sessionId.slice(0, 8)} archive-failed detail=${detail}`);
|
|
13278
|
+
return c.json({ error: "archive-failed", detail }, 502);
|
|
13279
|
+
}
|
|
13280
|
+
console.error(`[session-archive] sessionId=${sessionId.slice(0, 8)} manager-status=${res.status}`);
|
|
13281
|
+
return c.json({ error: `manager-status-${res.status}` }, 502);
|
|
13282
|
+
});
|
|
13283
|
+
var session_archive_default = app28;
|
|
13182
13284
|
|
|
13183
13285
|
// server/routes/admin/session-rc-spawn.ts
|
|
13184
|
-
var
|
|
13185
|
-
|
|
13286
|
+
var app29 = new Hono();
|
|
13287
|
+
app29.post("/", requireAdminSession, async (c) => {
|
|
13186
13288
|
let body;
|
|
13187
13289
|
try {
|
|
13188
13290
|
body = await c.req.json();
|
|
@@ -13216,7 +13318,7 @@ app27.post("/", requireAdminSession, async (c) => {
|
|
|
13216
13318
|
}
|
|
13217
13319
|
return c.json(parsed ?? {});
|
|
13218
13320
|
});
|
|
13219
|
-
var session_rc_spawn_default =
|
|
13321
|
+
var session_rc_spawn_default = app29;
|
|
13220
13322
|
|
|
13221
13323
|
// server/routes/admin/system-stats.ts
|
|
13222
13324
|
import { readFile as readFile5 } from "fs/promises";
|
|
@@ -13313,8 +13415,8 @@ async function sample() {
|
|
|
13313
13415
|
if (process.platform === "linux") return sampleLinux();
|
|
13314
13416
|
return sampleOsFallback();
|
|
13315
13417
|
}
|
|
13316
|
-
var
|
|
13317
|
-
|
|
13418
|
+
var app30 = new Hono();
|
|
13419
|
+
app30.get("/", async (c) => {
|
|
13318
13420
|
const started = Date.now();
|
|
13319
13421
|
if (!inflight) {
|
|
13320
13422
|
inflight = sample().finally(() => {
|
|
@@ -13337,27 +13439,27 @@ app28.get("/", async (c) => {
|
|
|
13337
13439
|
return c.json({ degraded: true, reason, sampledAtMs: Date.now() });
|
|
13338
13440
|
}
|
|
13339
13441
|
});
|
|
13340
|
-
var system_stats_default =
|
|
13442
|
+
var system_stats_default = app30;
|
|
13341
13443
|
|
|
13342
13444
|
// server/routes/admin/health.ts
|
|
13343
|
-
import { existsSync as
|
|
13344
|
-
import { resolve as
|
|
13345
|
-
var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT ??
|
|
13445
|
+
import { existsSync as existsSync19, readFileSync as readFileSync18 } from "fs";
|
|
13446
|
+
import { resolve as resolve20, join as join17 } from "path";
|
|
13447
|
+
var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT ?? resolve20(process.cwd(), "..");
|
|
13346
13448
|
var brandHostname = "maxy";
|
|
13347
|
-
var brandJsonPath =
|
|
13348
|
-
if (
|
|
13449
|
+
var brandJsonPath = join17(PLATFORM_ROOT6, "config", "brand.json");
|
|
13450
|
+
if (existsSync19(brandJsonPath)) {
|
|
13349
13451
|
try {
|
|
13350
|
-
const brand = JSON.parse(
|
|
13452
|
+
const brand = JSON.parse(readFileSync18(brandJsonPath, "utf-8"));
|
|
13351
13453
|
if (brand.hostname) brandHostname = brand.hostname;
|
|
13352
13454
|
} catch {
|
|
13353
13455
|
}
|
|
13354
13456
|
}
|
|
13355
|
-
var VERSION_FILE =
|
|
13457
|
+
var VERSION_FILE = resolve20(PLATFORM_ROOT6, `config/.${brandHostname}-version`);
|
|
13356
13458
|
var PROCESS_STARTED_AT = (/* @__PURE__ */ new Date()).toISOString();
|
|
13357
13459
|
var PROBE_TIMEOUT_MS = 1e3;
|
|
13358
13460
|
function readVersion() {
|
|
13359
|
-
if (!
|
|
13360
|
-
return
|
|
13461
|
+
if (!existsSync19(VERSION_FILE)) return "unknown";
|
|
13462
|
+
return readFileSync18(VERSION_FILE, "utf-8").trim() || "unknown";
|
|
13361
13463
|
}
|
|
13362
13464
|
async function probeConversationDb() {
|
|
13363
13465
|
let session;
|
|
@@ -13382,8 +13484,8 @@ async function probeConversationDb() {
|
|
|
13382
13484
|
});
|
|
13383
13485
|
}
|
|
13384
13486
|
}
|
|
13385
|
-
var
|
|
13386
|
-
|
|
13487
|
+
var app31 = new Hono();
|
|
13488
|
+
app31.get("/", async (c) => {
|
|
13387
13489
|
const version = readVersion();
|
|
13388
13490
|
const probe = await probeConversationDb();
|
|
13389
13491
|
const uptimeMs = Date.now() - new Date(PROCESS_STARTED_AT).getTime();
|
|
@@ -13405,11 +13507,11 @@ app29.get("/", async (c) => {
|
|
|
13405
13507
|
uptimeMs
|
|
13406
13508
|
});
|
|
13407
13509
|
});
|
|
13408
|
-
var health_default2 =
|
|
13510
|
+
var health_default2 = app31;
|
|
13409
13511
|
|
|
13410
13512
|
// server/routes/admin/linkedin-ingest.ts
|
|
13411
13513
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
13412
|
-
var
|
|
13514
|
+
var TAG24 = "[linkedin-ingest-route]";
|
|
13413
13515
|
var UUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
13414
13516
|
var ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
13415
13517
|
var LINKEDIN_URL = /^https:\/\/www\.linkedin\.com\//;
|
|
@@ -13507,35 +13609,35 @@ function buildInitialMessage(env) {
|
|
|
13507
13609
|
}
|
|
13508
13610
|
return header;
|
|
13509
13611
|
}
|
|
13510
|
-
var
|
|
13511
|
-
|
|
13612
|
+
var app32 = new Hono();
|
|
13613
|
+
app32.post("/", requireAdminSession, async (c) => {
|
|
13512
13614
|
let body;
|
|
13513
13615
|
try {
|
|
13514
13616
|
body = await c.req.json();
|
|
13515
13617
|
} catch {
|
|
13516
|
-
console.error(
|
|
13618
|
+
console.error(TAG24 + " rejected status=400 reason=schema:body-not-json");
|
|
13517
13619
|
return c.json({ ok: false, error: "schema", reason: "body-not-json" }, 400);
|
|
13518
13620
|
}
|
|
13519
13621
|
const v = validate(body);
|
|
13520
13622
|
if (!v.ok) {
|
|
13521
|
-
console.error(
|
|
13623
|
+
console.error(TAG24 + " rejected status=" + v.status + " reason=" + v.reason + " missing=" + v.missing.join(","));
|
|
13522
13624
|
return c.json({ ok: false, error: v.error, reason: v.reason, missing: v.missing }, v.status);
|
|
13523
13625
|
}
|
|
13524
13626
|
const envelope = v.envelope;
|
|
13525
13627
|
const cacheKey = c.var.cacheKey ?? "";
|
|
13526
13628
|
const senderId = getAccountIdForSession(cacheKey) ?? "";
|
|
13527
13629
|
if (!senderId) {
|
|
13528
|
-
console.error(
|
|
13630
|
+
console.error(TAG24 + " rejected status=500 reason=admin-account-not-resolved");
|
|
13529
13631
|
return c.json({ ok: false, error: "admin-account-not-resolved" }, 500);
|
|
13530
13632
|
}
|
|
13531
13633
|
const payloadBytes = JSON.stringify(envelope).length;
|
|
13532
13634
|
console.log(
|
|
13533
|
-
|
|
13635
|
+
TAG24 + " received kind=" + envelope.kind + " account=" + senderId.slice(0, 8) + " pageUrl=" + envelope.pageUrl + " dispatchId=" + envelope.dispatchId + " payloadBytes=" + payloadBytes
|
|
13534
13636
|
);
|
|
13535
13637
|
const initialMessage = buildInitialMessage(envelope);
|
|
13536
13638
|
const spawnStart = Date.now();
|
|
13537
13639
|
const sessionId = randomUUID9();
|
|
13538
|
-
console.log(
|
|
13640
|
+
console.log(TAG24 + " route target=rc-spawn dispatchId=" + envelope.dispatchId + " sessionId=" + sessionId.slice(0, 8));
|
|
13539
13641
|
const spawned = await managerRcSpawn({
|
|
13540
13642
|
sessionId,
|
|
13541
13643
|
initialMessage,
|
|
@@ -13544,20 +13646,20 @@ app30.post("/", requireAdminSession, async (c) => {
|
|
|
13544
13646
|
});
|
|
13545
13647
|
if ("error" in spawned) {
|
|
13546
13648
|
console.error(
|
|
13547
|
-
|
|
13649
|
+
TAG24 + " dispatch-failed dispatchId=" + envelope.dispatchId + " status=" + spawned.status + " ms=" + (Date.now() - spawnStart) + " message=" + spawned.error
|
|
13548
13650
|
);
|
|
13549
13651
|
return c.json({ ok: false, error: "dispatch-failed", upstreamStatus: spawned.status, detail: spawned.error }, 502);
|
|
13550
13652
|
}
|
|
13551
13653
|
console.log(
|
|
13552
|
-
|
|
13654
|
+
TAG24 + " dispatched dispatchId=" + envelope.dispatchId + " taskId=" + spawned.sessionId + " ms=" + (Date.now() - spawnStart)
|
|
13553
13655
|
);
|
|
13554
13656
|
return c.json({ ok: true, dispatchId: envelope.dispatchId, taskId: spawned.sessionId }, 202);
|
|
13555
13657
|
});
|
|
13556
|
-
var linkedin_ingest_default =
|
|
13658
|
+
var linkedin_ingest_default = app32;
|
|
13557
13659
|
|
|
13558
13660
|
// server/routes/admin/post-turn-context.ts
|
|
13559
13661
|
import neo4j2 from "neo4j-driver";
|
|
13560
|
-
var
|
|
13662
|
+
var TAG25 = "[post-turn-context]";
|
|
13561
13663
|
var STRIPPED_PROPERTIES2 = /* @__PURE__ */ new Set([
|
|
13562
13664
|
"embedding",
|
|
13563
13665
|
"passwordHash",
|
|
@@ -13595,11 +13697,11 @@ function pruneProperties(raw) {
|
|
|
13595
13697
|
}
|
|
13596
13698
|
return out;
|
|
13597
13699
|
}
|
|
13598
|
-
var
|
|
13599
|
-
|
|
13700
|
+
var app33 = new Hono();
|
|
13701
|
+
app33.get("/", async (c) => {
|
|
13600
13702
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
13601
13703
|
if (!isLoopbackAddr2(remoteAddr)) {
|
|
13602
|
-
console.error(`${
|
|
13704
|
+
console.error(`${TAG25} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
13603
13705
|
return c.json({ error: "post-turn-context-loopback-only" }, 403);
|
|
13604
13706
|
}
|
|
13605
13707
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
@@ -13608,7 +13710,7 @@ app31.get("/", async (c) => {
|
|
|
13608
13710
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
13609
13711
|
const offender = tokens.find((t) => !isLoopbackAddr2(t));
|
|
13610
13712
|
if (offender !== void 0) {
|
|
13611
|
-
console.error(`${
|
|
13713
|
+
console.error(`${TAG25} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
13612
13714
|
return c.json({ error: "post-turn-context-loopback-only" }, 403);
|
|
13613
13715
|
}
|
|
13614
13716
|
}
|
|
@@ -13640,7 +13742,7 @@ app31.get("/", async (c) => {
|
|
|
13640
13742
|
writes.sort((a, b) => a.sortKey.localeCompare(b.sortKey));
|
|
13641
13743
|
const total = Date.now() - started;
|
|
13642
13744
|
console.log(
|
|
13643
|
-
`${
|
|
13745
|
+
`${TAG25} sessionId=${sessionId} accountId=${accountId} writes=${writes.length} ms=${total}`
|
|
13644
13746
|
);
|
|
13645
13747
|
return c.json({
|
|
13646
13748
|
writes: writes.map(({ elementId, labels, properties }) => ({ elementId, labels, properties }))
|
|
@@ -13649,18 +13751,18 @@ app31.get("/", async (c) => {
|
|
|
13649
13751
|
const elapsed = Date.now() - started;
|
|
13650
13752
|
const message = err instanceof Error ? err.message : String(err);
|
|
13651
13753
|
console.error(
|
|
13652
|
-
`${
|
|
13754
|
+
`${TAG25} neo4j-unreachable sessionId=${sessionId} ms=${elapsed} err="${message}"`
|
|
13653
13755
|
);
|
|
13654
13756
|
return c.json({ error: `post-turn-context unavailable: ${message}` }, 503);
|
|
13655
13757
|
} finally {
|
|
13656
13758
|
await session.close();
|
|
13657
13759
|
}
|
|
13658
13760
|
});
|
|
13659
|
-
var post_turn_context_default =
|
|
13761
|
+
var post_turn_context_default = app33;
|
|
13660
13762
|
|
|
13661
13763
|
// server/routes/admin/public-session-context.ts
|
|
13662
13764
|
import neo4j3 from "neo4j-driver";
|
|
13663
|
-
var
|
|
13765
|
+
var TAG26 = "[public-session-context]";
|
|
13664
13766
|
var STRIPPED_PROPERTIES3 = /* @__PURE__ */ new Set([
|
|
13665
13767
|
"embedding",
|
|
13666
13768
|
"passwordHash",
|
|
@@ -13698,11 +13800,11 @@ function pruneProperties2(raw) {
|
|
|
13698
13800
|
}
|
|
13699
13801
|
return out;
|
|
13700
13802
|
}
|
|
13701
|
-
var
|
|
13702
|
-
|
|
13803
|
+
var app34 = new Hono();
|
|
13804
|
+
app34.get("/", async (c) => {
|
|
13703
13805
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
13704
13806
|
if (!isLoopbackAddr3(remoteAddr)) {
|
|
13705
|
-
console.error(`${
|
|
13807
|
+
console.error(`${TAG26} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
13706
13808
|
return c.json({ error: "public-session-context-loopback-only" }, 403);
|
|
13707
13809
|
}
|
|
13708
13810
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
@@ -13711,7 +13813,7 @@ app32.get("/", async (c) => {
|
|
|
13711
13813
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
13712
13814
|
const offender = tokens.find((t) => !isLoopbackAddr3(t));
|
|
13713
13815
|
if (offender !== void 0) {
|
|
13714
|
-
console.error(`${
|
|
13816
|
+
console.error(`${TAG26} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
13715
13817
|
return c.json({ error: "public-session-context-loopback-only" }, 403);
|
|
13716
13818
|
}
|
|
13717
13819
|
}
|
|
@@ -13742,7 +13844,7 @@ app32.get("/", async (c) => {
|
|
|
13742
13844
|
writes.sort((a, b) => a.sortKey.localeCompare(b.sortKey));
|
|
13743
13845
|
const total = Date.now() - started;
|
|
13744
13846
|
console.log(
|
|
13745
|
-
`${
|
|
13847
|
+
`${TAG26} sliceToken=${sliceToken.slice(0, 8)} writes=${writes.length} ms=${total}`
|
|
13746
13848
|
);
|
|
13747
13849
|
return c.json({
|
|
13748
13850
|
writes: writes.map(({ elementId, labels, properties }) => ({ elementId, labels, properties }))
|
|
@@ -13751,25 +13853,25 @@ app32.get("/", async (c) => {
|
|
|
13751
13853
|
const elapsed = Date.now() - started;
|
|
13752
13854
|
const message = err instanceof Error ? err.message : String(err);
|
|
13753
13855
|
console.error(
|
|
13754
|
-
`${
|
|
13856
|
+
`${TAG26} neo4j-unreachable sliceToken=${sliceToken.slice(0, 8)} ms=${elapsed} err="${message}"`
|
|
13755
13857
|
);
|
|
13756
13858
|
return c.json({ error: `public-session-context unavailable: ${message}` }, 503);
|
|
13757
13859
|
} finally {
|
|
13758
13860
|
await session.close();
|
|
13759
13861
|
}
|
|
13760
13862
|
});
|
|
13761
|
-
var public_session_context_default =
|
|
13863
|
+
var public_session_context_default = app34;
|
|
13762
13864
|
|
|
13763
13865
|
// server/routes/admin/public-session-exit.ts
|
|
13764
|
-
var
|
|
13866
|
+
var TAG27 = "[public-session-exit-route]";
|
|
13765
13867
|
function isLoopbackAddr4(addr) {
|
|
13766
13868
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
13767
13869
|
}
|
|
13768
|
-
var
|
|
13769
|
-
|
|
13870
|
+
var app35 = new Hono();
|
|
13871
|
+
app35.post("/", async (c) => {
|
|
13770
13872
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
13771
13873
|
if (!isLoopbackAddr4(remoteAddr)) {
|
|
13772
|
-
console.error(`${
|
|
13874
|
+
console.error(`${TAG27} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
13773
13875
|
return c.json({ error: "public-session-exit-loopback-only" }, 403);
|
|
13774
13876
|
}
|
|
13775
13877
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
@@ -13778,7 +13880,7 @@ app33.post("/", async (c) => {
|
|
|
13778
13880
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
13779
13881
|
const offender = tokens.find((t) => !isLoopbackAddr4(t));
|
|
13780
13882
|
if (offender !== void 0) {
|
|
13781
|
-
console.error(`${
|
|
13883
|
+
console.error(`${TAG27} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
13782
13884
|
return c.json({ error: "public-session-exit-loopback-only" }, 403);
|
|
13783
13885
|
}
|
|
13784
13886
|
}
|
|
@@ -13793,18 +13895,18 @@ app33.post("/", async (c) => {
|
|
|
13793
13895
|
handlePublicSessionExit(sessionId);
|
|
13794
13896
|
return c.json({ ok: true });
|
|
13795
13897
|
});
|
|
13796
|
-
var public_session_exit_default =
|
|
13898
|
+
var public_session_exit_default = app35;
|
|
13797
13899
|
|
|
13798
13900
|
// server/routes/admin/access-session-evict.ts
|
|
13799
|
-
var
|
|
13901
|
+
var TAG28 = "[access-session-evict]";
|
|
13800
13902
|
function isLoopbackAddr5(addr) {
|
|
13801
13903
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
13802
13904
|
}
|
|
13803
|
-
var
|
|
13804
|
-
|
|
13905
|
+
var app36 = new Hono();
|
|
13906
|
+
app36.post("/", async (c) => {
|
|
13805
13907
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
13806
13908
|
if (!isLoopbackAddr5(remoteAddr)) {
|
|
13807
|
-
console.error(`${
|
|
13909
|
+
console.error(`${TAG28} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
13808
13910
|
return c.json({ error: "access-session-evict-loopback-only" }, 403);
|
|
13809
13911
|
}
|
|
13810
13912
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
@@ -13813,7 +13915,7 @@ app34.post("/", async (c) => {
|
|
|
13813
13915
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
13814
13916
|
const offender = tokens.find((t) => !isLoopbackAddr5(t));
|
|
13815
13917
|
if (offender !== void 0) {
|
|
13816
|
-
console.error(`${
|
|
13918
|
+
console.error(`${TAG28} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
13817
13919
|
return c.json({ error: "access-session-evict-loopback-only" }, 403);
|
|
13818
13920
|
}
|
|
13819
13921
|
}
|
|
@@ -13826,14 +13928,14 @@ app34.post("/", async (c) => {
|
|
|
13826
13928
|
const grantId = typeof body.grantId === "string" ? body.grantId.trim() : "";
|
|
13827
13929
|
if (!grantId) return c.json({ error: "grantId required" }, 400);
|
|
13828
13930
|
const dropped = evictAccessSessionsByGrant(grantId);
|
|
13829
|
-
console.log(`${
|
|
13931
|
+
console.log(`${TAG28} grantId=${grantId} dropped=${dropped}`);
|
|
13830
13932
|
return c.json({ ok: true, dropped });
|
|
13831
13933
|
});
|
|
13832
|
-
var access_session_evict_default =
|
|
13934
|
+
var access_session_evict_default = app36;
|
|
13833
13935
|
|
|
13834
13936
|
// server/routes/admin/browser.ts
|
|
13835
|
-
var
|
|
13836
|
-
|
|
13937
|
+
var app37 = new Hono();
|
|
13938
|
+
app37.post("/launch", requireAdminSession, async (c) => {
|
|
13837
13939
|
try {
|
|
13838
13940
|
const transport = resolveBrowserTransport(c.req.raw, c.env?.incoming?.socket?.remoteAddress);
|
|
13839
13941
|
console.error(`[admin/browser/launch] op=request transport=${transport}`);
|
|
@@ -13861,53 +13963,54 @@ app35.post("/launch", requireAdminSession, async (c) => {
|
|
|
13861
13963
|
);
|
|
13862
13964
|
}
|
|
13863
13965
|
});
|
|
13864
|
-
var browser_default =
|
|
13966
|
+
var browser_default = app37;
|
|
13865
13967
|
|
|
13866
13968
|
// server/routes/admin/index.ts
|
|
13867
|
-
var
|
|
13868
|
-
|
|
13869
|
-
|
|
13870
|
-
|
|
13871
|
-
|
|
13872
|
-
|
|
13873
|
-
|
|
13874
|
-
|
|
13875
|
-
|
|
13876
|
-
|
|
13877
|
-
|
|
13878
|
-
|
|
13879
|
-
|
|
13880
|
-
|
|
13881
|
-
|
|
13882
|
-
|
|
13883
|
-
|
|
13884
|
-
|
|
13885
|
-
|
|
13886
|
-
|
|
13887
|
-
|
|
13888
|
-
|
|
13889
|
-
|
|
13890
|
-
|
|
13891
|
-
|
|
13892
|
-
|
|
13893
|
-
|
|
13894
|
-
|
|
13895
|
-
|
|
13896
|
-
|
|
13897
|
-
|
|
13969
|
+
var app38 = new Hono();
|
|
13970
|
+
app38.route("/session", session_default);
|
|
13971
|
+
app38.route("/logs", logs_default);
|
|
13972
|
+
app38.route("/claude-info", claude_info_default);
|
|
13973
|
+
app38.route("/attachment", attachment_default);
|
|
13974
|
+
app38.route("/agents", agents_default);
|
|
13975
|
+
app38.route("/sessions", sessions_default);
|
|
13976
|
+
app38.route("/claude-sessions", claude_sessions_default);
|
|
13977
|
+
app38.route("/log-ingest", log_ingest_default);
|
|
13978
|
+
app38.route("/events", events_default);
|
|
13979
|
+
app38.route("/files", files_default);
|
|
13980
|
+
app38.route("/graph-search", graph_search_default);
|
|
13981
|
+
app38.route("/graph-subgraph", graph_subgraph_default);
|
|
13982
|
+
app38.route("/graph-delete", graph_delete_default);
|
|
13983
|
+
app38.route("/graph-restore", graph_restore_default);
|
|
13984
|
+
app38.route("/graph-labels-in-graph", graph_labels_in_graph_default);
|
|
13985
|
+
app38.route("/graph-default-view", graph_default_view_default);
|
|
13986
|
+
app38.route("/sidebar-artefacts", sidebar_artefacts_default);
|
|
13987
|
+
app38.route("/sidebar-sessions", sidebar_sessions_default);
|
|
13988
|
+
app38.route("/session-delete", session_delete_default);
|
|
13989
|
+
app38.route("/session-stop", session_stop_default);
|
|
13990
|
+
app38.route("/session-archive", session_archive_default);
|
|
13991
|
+
app38.route("/browser", browser_default);
|
|
13992
|
+
app38.route("/session-rc-spawn", session_rc_spawn_default);
|
|
13993
|
+
app38.route("/system-stats", system_stats_default);
|
|
13994
|
+
app38.route("/health-brand", health_default2);
|
|
13995
|
+
app38.route("/linkedin-ingest", linkedin_ingest_default);
|
|
13996
|
+
app38.route("/post-turn-context", post_turn_context_default);
|
|
13997
|
+
app38.route("/public-session-context", public_session_context_default);
|
|
13998
|
+
app38.route("/public-session-exit", public_session_exit_default);
|
|
13999
|
+
app38.route("/access-session-evict", access_session_evict_default);
|
|
14000
|
+
var admin_default = app38;
|
|
13898
14001
|
|
|
13899
14002
|
// app/lib/access-gate.ts
|
|
13900
14003
|
import neo4j4 from "neo4j-driver";
|
|
13901
|
-
import { readFileSync as
|
|
13902
|
-
import { resolve as
|
|
14004
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
14005
|
+
import { resolve as resolve21 } from "path";
|
|
13903
14006
|
import { randomUUID as randomUUID10 } from "crypto";
|
|
13904
|
-
var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT ??
|
|
14007
|
+
var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT ?? resolve21(process.cwd(), "..");
|
|
13905
14008
|
var driver = null;
|
|
13906
14009
|
function readPassword() {
|
|
13907
14010
|
if (process.env.NEO4J_PASSWORD) return process.env.NEO4J_PASSWORD;
|
|
13908
|
-
const passwordFile =
|
|
14011
|
+
const passwordFile = resolve21(PLATFORM_ROOT7, "config/.neo4j-password");
|
|
13909
14012
|
try {
|
|
13910
|
-
return
|
|
14013
|
+
return readFileSync19(passwordFile, "utf-8").trim();
|
|
13911
14014
|
} catch {
|
|
13912
14015
|
throw new Error(
|
|
13913
14016
|
`Neo4j password not found. Expected at ${passwordFile} or in NEO4J_PASSWORD env var.`
|
|
@@ -14105,11 +14208,11 @@ async function generateNewMagicToken(grantId) {
|
|
|
14105
14208
|
}
|
|
14106
14209
|
|
|
14107
14210
|
// server/routes/access/verify-token.ts
|
|
14108
|
-
var
|
|
14211
|
+
var TAG29 = "[access-verify]";
|
|
14109
14212
|
var MINT_TAG = "[access-session-mint]";
|
|
14110
14213
|
var COOKIE_NAME = "__access_session";
|
|
14111
|
-
var
|
|
14112
|
-
|
|
14214
|
+
var app39 = new Hono();
|
|
14215
|
+
app39.post("/", async (c) => {
|
|
14113
14216
|
const ip = c.var.clientIp || "unknown";
|
|
14114
14217
|
let body;
|
|
14115
14218
|
try {
|
|
@@ -14124,39 +14227,39 @@ app37.post("/", async (c) => {
|
|
|
14124
14227
|
}
|
|
14125
14228
|
const rateMsg = checkAccessRateLimit(ip, agentSlug);
|
|
14126
14229
|
if (rateMsg) {
|
|
14127
|
-
console.error(`${
|
|
14230
|
+
console.error(`${TAG29} grantId=- agentSlug=${agentSlug} result=rate-limited ip=${ip}`);
|
|
14128
14231
|
return c.json({ error: rateMsg }, 429);
|
|
14129
14232
|
}
|
|
14130
14233
|
const grant = await findGrantByMagicToken(token);
|
|
14131
14234
|
if (!grant) {
|
|
14132
14235
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
14133
|
-
console.error(`${
|
|
14236
|
+
console.error(`${TAG29} grantId=- agentSlug=${agentSlug} result=notfound ip=${ip}`);
|
|
14134
14237
|
return c.json({ error: "invalid-or-expired-link" }, 401);
|
|
14135
14238
|
}
|
|
14136
14239
|
if (grant.agentSlug !== agentSlug) {
|
|
14137
14240
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
14138
14241
|
console.error(
|
|
14139
|
-
`${
|
|
14242
|
+
`${TAG29} grantId=${grant.grantId} agentSlug=${agentSlug} result=agent-mismatch grantAgent=${grant.agentSlug} ip=${ip}`
|
|
14140
14243
|
);
|
|
14141
14244
|
return c.json({ error: "invalid-or-expired-link" }, 401);
|
|
14142
14245
|
}
|
|
14143
14246
|
if (grant.status === "expired" || grant.status === "revoked") {
|
|
14144
14247
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
14145
14248
|
console.error(
|
|
14146
|
-
`${
|
|
14249
|
+
`${TAG29} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired status=${grant.status} ip=${ip}`
|
|
14147
14250
|
);
|
|
14148
14251
|
return c.json({ error: "access-no-longer-valid" }, 401);
|
|
14149
14252
|
}
|
|
14150
14253
|
if (grant.expiresAt !== null && grant.expiresAt < Date.now()) {
|
|
14151
14254
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
14152
14255
|
console.error(
|
|
14153
|
-
`${
|
|
14256
|
+
`${TAG29} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired reason=expiresAt-past ip=${ip}`
|
|
14154
14257
|
);
|
|
14155
14258
|
return c.json({ error: "access-no-longer-valid" }, 401);
|
|
14156
14259
|
}
|
|
14157
14260
|
if (!grant.sliceToken) {
|
|
14158
14261
|
console.error(
|
|
14159
|
-
`${
|
|
14262
|
+
`${TAG29} grantId=${grant.grantId} agentSlug=${agentSlug} result=no-slice-token reason=schema-violation`
|
|
14160
14263
|
);
|
|
14161
14264
|
return c.json({ error: "grant-misconfigured" }, 500);
|
|
14162
14265
|
}
|
|
@@ -14172,12 +14275,12 @@ app37.post("/", async (c) => {
|
|
|
14172
14275
|
await consumeMagicTokenAndActivate(grant.grantId);
|
|
14173
14276
|
} catch (err) {
|
|
14174
14277
|
console.error(
|
|
14175
|
-
`${
|
|
14278
|
+
`${TAG29} grantId=${grant.grantId} agentSlug=${agentSlug} result=consume-failed err="${err instanceof Error ? err.message : String(err)}"`
|
|
14176
14279
|
);
|
|
14177
14280
|
return c.json({ error: "verification-failed" }, 500);
|
|
14178
14281
|
}
|
|
14179
14282
|
clearAccessRateLimit(ip, agentSlug);
|
|
14180
|
-
console.log(`${
|
|
14283
|
+
console.log(`${TAG29} grantId=${grant.grantId} agentSlug=${agentSlug} result=ok ip=${ip}`);
|
|
14181
14284
|
console.log(
|
|
14182
14285
|
`${MINT_TAG} grantId=${grant.grantId} sliceToken=${grant.sliceToken.slice(0, 8)} agentSlug=${agentSlug} personId=${grant.personId ?? "none"}`
|
|
14183
14286
|
);
|
|
@@ -14191,13 +14294,13 @@ app37.post("/", async (c) => {
|
|
|
14191
14294
|
displayName: grant.displayName
|
|
14192
14295
|
});
|
|
14193
14296
|
});
|
|
14194
|
-
var verify_token_default =
|
|
14297
|
+
var verify_token_default = app39;
|
|
14195
14298
|
|
|
14196
14299
|
// app/lib/access-email.ts
|
|
14197
14300
|
import { spawn as spawn2 } from "child_process";
|
|
14198
|
-
import { resolve as
|
|
14199
|
-
var PLATFORM_ROOT8 = process.env.MAXY_PLATFORM_ROOT ??
|
|
14200
|
-
var SEND_SCRIPT =
|
|
14301
|
+
import { resolve as resolve22 } from "path";
|
|
14302
|
+
var PLATFORM_ROOT8 = process.env.MAXY_PLATFORM_ROOT ?? resolve22(process.cwd(), "..");
|
|
14303
|
+
var SEND_SCRIPT = resolve22(
|
|
14201
14304
|
PLATFORM_ROOT8,
|
|
14202
14305
|
"plugins",
|
|
14203
14306
|
"email",
|
|
@@ -14253,10 +14356,10 @@ async function sendMagicLinkEmail(payload) {
|
|
|
14253
14356
|
}
|
|
14254
14357
|
|
|
14255
14358
|
// server/routes/access/request-magic-link.ts
|
|
14256
|
-
var
|
|
14257
|
-
var
|
|
14359
|
+
var TAG30 = "[access-request-link]";
|
|
14360
|
+
var app40 = new Hono();
|
|
14258
14361
|
var VISITOR_MESSAGE = "If that email is on the invite list, a fresh link is on the way.";
|
|
14259
|
-
|
|
14362
|
+
app40.post("/", async (c) => {
|
|
14260
14363
|
let body;
|
|
14261
14364
|
try {
|
|
14262
14365
|
body = await c.req.json();
|
|
@@ -14270,18 +14373,18 @@ app38.post("/", async (c) => {
|
|
|
14270
14373
|
}
|
|
14271
14374
|
const rateMsg = checkRequestLinkRateLimit(contactValue);
|
|
14272
14375
|
if (rateMsg) {
|
|
14273
|
-
console.error(`${
|
|
14376
|
+
console.error(`${TAG30} contactValue=${maskContact(contactValue)} result=rate-limited`);
|
|
14274
14377
|
return c.json({ error: rateMsg }, 429);
|
|
14275
14378
|
}
|
|
14276
14379
|
recordRequestLinkAttempt(contactValue);
|
|
14277
14380
|
const accountId = process.env.ACCOUNT_ID ?? "";
|
|
14278
14381
|
if (!accountId) {
|
|
14279
|
-
console.error(`${
|
|
14382
|
+
console.error(`${TAG30} contactValue=${maskContact(contactValue)} result=no-account-id`);
|
|
14280
14383
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
14281
14384
|
}
|
|
14282
14385
|
const grant = await findActiveGrantByContact(contactValue, agentSlug, accountId);
|
|
14283
14386
|
if (!grant) {
|
|
14284
|
-
console.log(`${
|
|
14387
|
+
console.log(`${TAG30} contactValue=${maskContact(contactValue)} result=notfound`);
|
|
14285
14388
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
14286
14389
|
}
|
|
14287
14390
|
let token;
|
|
@@ -14289,7 +14392,7 @@ app38.post("/", async (c) => {
|
|
|
14289
14392
|
token = await generateNewMagicToken(grant.grantId);
|
|
14290
14393
|
} catch (err) {
|
|
14291
14394
|
console.error(
|
|
14292
|
-
`${
|
|
14395
|
+
`${TAG30} contactValue=${maskContact(contactValue)} result=mint-failed err="${err instanceof Error ? err.message : String(err)}"`
|
|
14293
14396
|
);
|
|
14294
14397
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
14295
14398
|
}
|
|
@@ -14321,26 +14424,26 @@ app38.post("/", async (c) => {
|
|
|
14321
14424
|
});
|
|
14322
14425
|
if (!sendResult.ok) {
|
|
14323
14426
|
console.error(
|
|
14324
|
-
`${
|
|
14427
|
+
`${TAG30} contactValue=${maskContact(contactValue)} result=send-failed err="${sendResult.error}"`
|
|
14325
14428
|
);
|
|
14326
14429
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
14327
14430
|
}
|
|
14328
14431
|
console.log(
|
|
14329
|
-
`${
|
|
14432
|
+
`${TAG30} contactValue=${maskContact(contactValue)} result=ok messageId=${sendResult.messageId}`
|
|
14330
14433
|
);
|
|
14331
14434
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
14332
14435
|
});
|
|
14333
|
-
var request_magic_link_default =
|
|
14436
|
+
var request_magic_link_default = app40;
|
|
14334
14437
|
|
|
14335
14438
|
// server/routes/access/index.ts
|
|
14336
|
-
var
|
|
14337
|
-
|
|
14338
|
-
|
|
14339
|
-
var access_default =
|
|
14439
|
+
var app41 = new Hono();
|
|
14440
|
+
app41.route("/verify-token", verify_token_default);
|
|
14441
|
+
app41.route("/request-magic-link", request_magic_link_default);
|
|
14442
|
+
var access_default = app41;
|
|
14340
14443
|
|
|
14341
14444
|
// server/routes/sites.ts
|
|
14342
|
-
import { existsSync as
|
|
14343
|
-
import { resolve as
|
|
14445
|
+
import { existsSync as existsSync20, readFileSync as readFileSync20, realpathSync as realpathSync5, statSync as statSync9 } from "fs";
|
|
14446
|
+
import { resolve as resolve23 } from "path";
|
|
14344
14447
|
var SAFE_SEG_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
|
|
14345
14448
|
var MIME = {
|
|
14346
14449
|
".html": "text/html; charset=utf-8",
|
|
@@ -14371,8 +14474,8 @@ function getExt(p) {
|
|
|
14371
14474
|
if (idx < p.lastIndexOf("/")) return "";
|
|
14372
14475
|
return p.slice(idx).toLowerCase();
|
|
14373
14476
|
}
|
|
14374
|
-
var
|
|
14375
|
-
|
|
14477
|
+
var app42 = new Hono();
|
|
14478
|
+
app42.get("/:rel{.*}", (c) => {
|
|
14376
14479
|
const reqPath = c.req.path;
|
|
14377
14480
|
const rawRel = c.req.param("rel") ?? "";
|
|
14378
14481
|
const trimmed = rawRel.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
@@ -14397,15 +14500,15 @@ app40.get("/:rel{.*}", (c) => {
|
|
|
14397
14500
|
}
|
|
14398
14501
|
segments.push(seg);
|
|
14399
14502
|
}
|
|
14400
|
-
const rootDir =
|
|
14401
|
-
let filePath = segments.length === 0 ? rootDir :
|
|
14503
|
+
const rootDir = resolve23(account.accountDir, "sites");
|
|
14504
|
+
let filePath = segments.length === 0 ? rootDir : resolve23(rootDir, ...segments);
|
|
14402
14505
|
if (filePath !== rootDir && !filePath.startsWith(rootDir + "/")) {
|
|
14403
14506
|
console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
|
|
14404
14507
|
return c.text("Forbidden", 403);
|
|
14405
14508
|
}
|
|
14406
14509
|
let stat7;
|
|
14407
14510
|
try {
|
|
14408
|
-
stat7 =
|
|
14511
|
+
stat7 = existsSync20(filePath) ? statSync9(filePath) : null;
|
|
14409
14512
|
} catch {
|
|
14410
14513
|
stat7 = null;
|
|
14411
14514
|
}
|
|
@@ -14418,13 +14521,13 @@ app40.get("/:rel{.*}", (c) => {
|
|
|
14418
14521
|
return c.redirect(target, 301);
|
|
14419
14522
|
}
|
|
14420
14523
|
if (stat7?.isDirectory()) {
|
|
14421
|
-
filePath =
|
|
14524
|
+
filePath = resolve23(filePath, "index.html");
|
|
14422
14525
|
}
|
|
14423
14526
|
if (!filePath.startsWith(rootDir + "/")) {
|
|
14424
14527
|
console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
|
|
14425
14528
|
return c.text("Forbidden", 403);
|
|
14426
14529
|
}
|
|
14427
|
-
if (!
|
|
14530
|
+
if (!existsSync20(filePath)) {
|
|
14428
14531
|
console.error(`[sites] not-found path=${reqPath} status=404`);
|
|
14429
14532
|
return c.text("Not found", 404);
|
|
14430
14533
|
}
|
|
@@ -14443,7 +14546,7 @@ app40.get("/:rel{.*}", (c) => {
|
|
|
14443
14546
|
}
|
|
14444
14547
|
let body;
|
|
14445
14548
|
try {
|
|
14446
|
-
body =
|
|
14549
|
+
body = readFileSync20(realPath);
|
|
14447
14550
|
} catch (err) {
|
|
14448
14551
|
const code = err?.code;
|
|
14449
14552
|
if (code === "EISDIR") {
|
|
@@ -14475,11 +14578,11 @@ app40.get("/:rel{.*}", (c) => {
|
|
|
14475
14578
|
"X-Content-Type-Options": "nosniff"
|
|
14476
14579
|
});
|
|
14477
14580
|
});
|
|
14478
|
-
var sites_default =
|
|
14581
|
+
var sites_default = app42;
|
|
14479
14582
|
|
|
14480
14583
|
// app/lib/visitor-token.ts
|
|
14481
14584
|
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
14482
|
-
import { mkdirSync as mkdirSync4, readFileSync as
|
|
14585
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync21, writeFileSync as writeFileSync7 } from "fs";
|
|
14483
14586
|
import { dirname as dirname5 } from "path";
|
|
14484
14587
|
var TOKEN_PREFIX = "v1.";
|
|
14485
14588
|
var SECRET_BYTES = 32;
|
|
@@ -14488,7 +14591,7 @@ var cachedSecret = null;
|
|
|
14488
14591
|
function getSecret() {
|
|
14489
14592
|
if (cachedSecret) return cachedSecret;
|
|
14490
14593
|
try {
|
|
14491
|
-
const hex2 =
|
|
14594
|
+
const hex2 = readFileSync21(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
|
|
14492
14595
|
if (hex2.length === SECRET_BYTES * 2) {
|
|
14493
14596
|
cachedSecret = Buffer.from(hex2, "hex");
|
|
14494
14597
|
return cachedSecret;
|
|
@@ -14502,7 +14605,7 @@ function getSecret() {
|
|
|
14502
14605
|
console.log(`[visitor-token] secret minted path=${VISITOR_TOKEN_SECRET_FILE}`);
|
|
14503
14606
|
} catch {
|
|
14504
14607
|
}
|
|
14505
|
-
const hex =
|
|
14608
|
+
const hex = readFileSync21(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
|
|
14506
14609
|
cachedSecret = Buffer.from(hex, "hex");
|
|
14507
14610
|
return cachedSecret;
|
|
14508
14611
|
}
|
|
@@ -14555,8 +14658,8 @@ var VISITOR_COOKIE_NAME = "mxy_v";
|
|
|
14555
14658
|
var VISITOR_COOKIE_MAX_AGE_SECONDS = Math.floor(DEFAULT_TTL_MS / 1e3);
|
|
14556
14659
|
|
|
14557
14660
|
// app/lib/brand-config.ts
|
|
14558
|
-
import { existsSync as
|
|
14559
|
-
import { join as
|
|
14661
|
+
import { existsSync as existsSync21, readFileSync as readFileSync22 } from "fs";
|
|
14662
|
+
import { join as join18 } from "path";
|
|
14560
14663
|
var cached2 = null;
|
|
14561
14664
|
var cachedAttempted = false;
|
|
14562
14665
|
function readBrandConfig() {
|
|
@@ -14564,10 +14667,10 @@ function readBrandConfig() {
|
|
|
14564
14667
|
cachedAttempted = true;
|
|
14565
14668
|
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT;
|
|
14566
14669
|
if (!platformRoot2) return null;
|
|
14567
|
-
const brandPath =
|
|
14568
|
-
if (!
|
|
14670
|
+
const brandPath = join18(platformRoot2, "config", "brand.json");
|
|
14671
|
+
if (!existsSync21(brandPath)) return null;
|
|
14569
14672
|
try {
|
|
14570
|
-
cached2 = JSON.parse(
|
|
14673
|
+
cached2 = JSON.parse(readFileSync22(brandPath, "utf-8"));
|
|
14571
14674
|
return cached2;
|
|
14572
14675
|
} catch {
|
|
14573
14676
|
return null;
|
|
@@ -14575,7 +14678,7 @@ function readBrandConfig() {
|
|
|
14575
14678
|
}
|
|
14576
14679
|
|
|
14577
14680
|
// server/routes/visitor-consent.ts
|
|
14578
|
-
var
|
|
14681
|
+
var app43 = new Hono();
|
|
14579
14682
|
var CONSENT_COOKIE_NAME = "mxy_consent";
|
|
14580
14683
|
var CONSENT_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 365;
|
|
14581
14684
|
var DEFAULT_CONSENT_COPY = {
|
|
@@ -14620,17 +14723,17 @@ function siteSlugFromReferer(referer) {
|
|
|
14620
14723
|
return "";
|
|
14621
14724
|
}
|
|
14622
14725
|
}
|
|
14623
|
-
|
|
14726
|
+
app43.options("/consent", (c) => {
|
|
14624
14727
|
const origin = getOrigin(c);
|
|
14625
14728
|
setCorsHeaders(c, origin);
|
|
14626
14729
|
return c.body(null, 204);
|
|
14627
14730
|
});
|
|
14628
|
-
|
|
14731
|
+
app43.options("/brand-config", (c) => {
|
|
14629
14732
|
const origin = getOrigin(c);
|
|
14630
14733
|
setCorsHeaders(c, origin);
|
|
14631
14734
|
return c.body(null, 204);
|
|
14632
14735
|
});
|
|
14633
|
-
|
|
14736
|
+
app43.post("/consent", async (c) => {
|
|
14634
14737
|
const origin = getOrigin(c);
|
|
14635
14738
|
setCorsHeaders(c, origin);
|
|
14636
14739
|
let raw;
|
|
@@ -14670,7 +14773,7 @@ app41.post("/consent", async (c) => {
|
|
|
14670
14773
|
console.log(`[consent] ${parsed.decision} site=${site} brand=${brandName} tokenBound=${tokenBound}`);
|
|
14671
14774
|
return c.body(null, 204);
|
|
14672
14775
|
});
|
|
14673
|
-
|
|
14776
|
+
app43.get("/brand-config", (c) => {
|
|
14674
14777
|
const origin = getOrigin(c);
|
|
14675
14778
|
setCorsHeaders(c, origin);
|
|
14676
14779
|
const brand = readBrandConfig();
|
|
@@ -14680,7 +14783,7 @@ app41.get("/brand-config", (c) => {
|
|
|
14680
14783
|
c.header("Cache-Control", "public, max-age=300");
|
|
14681
14784
|
return c.json({ consent: { copy, palette } });
|
|
14682
14785
|
});
|
|
14683
|
-
var visitor_consent_default =
|
|
14786
|
+
var visitor_consent_default = app43;
|
|
14684
14787
|
|
|
14685
14788
|
// server/routes/listings.ts
|
|
14686
14789
|
function getCookie(headerValue, name) {
|
|
@@ -14707,8 +14810,8 @@ function appendConsentParams(pageUrl, token) {
|
|
|
14707
14810
|
}
|
|
14708
14811
|
var SAFE_SLUG_RE = /^[a-z0-9](?:[a-z0-9-]{0,118}[a-z0-9])?$/;
|
|
14709
14812
|
var CHAT_SESSION_KEY_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{7,127}$/;
|
|
14710
|
-
var
|
|
14711
|
-
|
|
14813
|
+
var app44 = new Hono();
|
|
14814
|
+
app44.get("/:slug/click", async (c) => {
|
|
14712
14815
|
const slug = c.req.param("slug") ?? "";
|
|
14713
14816
|
const rawSession = c.req.query("session") ?? "";
|
|
14714
14817
|
const sessionKey = CHAT_SESSION_KEY_RE.test(rawSession) ? rawSession : "invalid";
|
|
@@ -14772,10 +14875,10 @@ app42.get("/:slug/click", async (c) => {
|
|
|
14772
14875
|
console.log(`[property-card-click] sessionKey=${sessionKey} listingSlug=${slug} consent=${consentCookie ?? "absent"} ts=${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
14773
14876
|
return c.redirect(redirectUrl, 302);
|
|
14774
14877
|
});
|
|
14775
|
-
var listings_default =
|
|
14878
|
+
var listings_default = app44;
|
|
14776
14879
|
|
|
14777
14880
|
// server/routes/visitor-event.ts
|
|
14778
|
-
var
|
|
14881
|
+
var app45 = new Hono();
|
|
14779
14882
|
var BOT_UA_RE = /\b(bot|crawl|spider|slurp|headlesschrome|phantomjs|googlebot|bingbot|yandex|baiduspider|ahrefsbot|semrushbot|mj12bot|dotbot|petalbot)\b/i;
|
|
14780
14883
|
var buckets = /* @__PURE__ */ new Map();
|
|
14781
14884
|
var RATE_LIMIT = 60;
|
|
@@ -14842,12 +14945,12 @@ function originAllowed(origin, allowlist) {
|
|
|
14842
14945
|
return false;
|
|
14843
14946
|
}
|
|
14844
14947
|
}
|
|
14845
|
-
|
|
14948
|
+
app45.options("/event", (c) => {
|
|
14846
14949
|
const origin = getOrigin2(c);
|
|
14847
14950
|
setCorsHeaders2(c, origin);
|
|
14848
14951
|
return c.body(null, 204);
|
|
14849
14952
|
});
|
|
14850
|
-
|
|
14953
|
+
app45.post("/event", async (c) => {
|
|
14851
14954
|
const origin = getOrigin2(c);
|
|
14852
14955
|
setCorsHeaders2(c, origin);
|
|
14853
14956
|
const ua = c.req.header("user-agent") ?? "";
|
|
@@ -15052,17 +15155,17 @@ async function writeEvent(opts) {
|
|
|
15052
15155
|
);
|
|
15053
15156
|
}
|
|
15054
15157
|
}
|
|
15055
|
-
var visitor_event_default =
|
|
15158
|
+
var visitor_event_default = app45;
|
|
15056
15159
|
|
|
15057
15160
|
// server/routes/session.ts
|
|
15058
|
-
import { resolve as
|
|
15059
|
-
import { existsSync as
|
|
15161
|
+
import { resolve as resolve24 } from "path";
|
|
15162
|
+
import { existsSync as existsSync22, writeFileSync as writeFileSync8, mkdirSync as mkdirSync5 } from "fs";
|
|
15060
15163
|
var UUID_RE4 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
15061
15164
|
function writeBrandingCache(accountId, agentSlug, branding) {
|
|
15062
15165
|
try {
|
|
15063
|
-
const cacheDir =
|
|
15166
|
+
const cacheDir = resolve24(MAXY_DIR, "branding-cache", accountId);
|
|
15064
15167
|
mkdirSync5(cacheDir, { recursive: true });
|
|
15065
|
-
writeFileSync8(
|
|
15168
|
+
writeFileSync8(resolve24(cacheDir, `${agentSlug}.json`), JSON.stringify(branding), "utf-8");
|
|
15066
15169
|
} catch (err) {
|
|
15067
15170
|
console.error(`[branding] cache write failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
15068
15171
|
}
|
|
@@ -15098,8 +15201,8 @@ function withVisitorCookie(response, visitorId) {
|
|
|
15098
15201
|
headers
|
|
15099
15202
|
});
|
|
15100
15203
|
}
|
|
15101
|
-
var
|
|
15102
|
-
|
|
15204
|
+
var app46 = new Hono();
|
|
15205
|
+
app46.post("/", async (c) => {
|
|
15103
15206
|
let body;
|
|
15104
15207
|
try {
|
|
15105
15208
|
body = await c.req.json();
|
|
@@ -15150,8 +15253,8 @@ app44.post("/", async (c) => {
|
|
|
15150
15253
|
}
|
|
15151
15254
|
let agentConfig = null;
|
|
15152
15255
|
if (account) {
|
|
15153
|
-
const agentDir =
|
|
15154
|
-
if (!
|
|
15256
|
+
const agentDir = resolve24(account.accountDir, "agents", agentSlug);
|
|
15257
|
+
if (!existsSync22(agentDir) || !existsSync22(resolve24(agentDir, "config.json"))) {
|
|
15155
15258
|
return c.json({ error: "Agent not found" }, 404);
|
|
15156
15259
|
}
|
|
15157
15260
|
agentConfig = resolveAgentConfig(account.accountDir, agentSlug);
|
|
@@ -15310,7 +15413,7 @@ app44.post("/", async (c) => {
|
|
|
15310
15413
|
newVisitorId
|
|
15311
15414
|
);
|
|
15312
15415
|
});
|
|
15313
|
-
var session_default2 =
|
|
15416
|
+
var session_default2 = app46;
|
|
15314
15417
|
|
|
15315
15418
|
// app/lib/graph-health.ts
|
|
15316
15419
|
var import_dist4 = __toESM(require_dist3(), 1);
|
|
@@ -15431,7 +15534,7 @@ function startGraphHealthTimer() {
|
|
|
15431
15534
|
|
|
15432
15535
|
// app/lib/file-watcher.ts
|
|
15433
15536
|
import * as fsp2 from "fs/promises";
|
|
15434
|
-
import { resolve as
|
|
15537
|
+
import { resolve as resolve25, sep as sep7 } from "path";
|
|
15435
15538
|
var ACCOUNT_UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
15436
15539
|
var DEFAULT_COALESCE_MS = 500;
|
|
15437
15540
|
var ROOTS = ["uploads", "accounts"];
|
|
@@ -15450,7 +15553,7 @@ async function startFileWatcher(opts = {}) {
|
|
|
15450
15553
|
const dropFn = opts.drop ?? dropFileIndex;
|
|
15451
15554
|
for (const r of ROOTS) {
|
|
15452
15555
|
try {
|
|
15453
|
-
await fsp2.mkdir(
|
|
15556
|
+
await fsp2.mkdir(resolve25(dataRoot, r), { recursive: true });
|
|
15454
15557
|
} catch (err) {
|
|
15455
15558
|
console.error(
|
|
15456
15559
|
`[file-watcher] start-failed root="${r}" err="${err.message}" \u2014 index will be maintained by the 5-min reconcile backstop only`
|
|
@@ -15471,7 +15574,7 @@ async function startFileWatcher(opts = {}) {
|
|
|
15471
15574
|
timers.set(relativePath, t);
|
|
15472
15575
|
}
|
|
15473
15576
|
async function runHook(relativePath, accountId) {
|
|
15474
|
-
const absolute =
|
|
15577
|
+
const absolute = resolve25(dataRoot, relativePath);
|
|
15475
15578
|
let exists = false;
|
|
15476
15579
|
try {
|
|
15477
15580
|
const st = await fsp2.stat(absolute);
|
|
@@ -15495,7 +15598,7 @@ async function startFileWatcher(opts = {}) {
|
|
|
15495
15598
|
}
|
|
15496
15599
|
}
|
|
15497
15600
|
async function watchRoot(rootName) {
|
|
15498
|
-
const absRoot =
|
|
15601
|
+
const absRoot = resolve25(dataRoot, rootName);
|
|
15499
15602
|
try {
|
|
15500
15603
|
const iter = fsp2.watch(absRoot, { recursive: true, signal: controller.signal });
|
|
15501
15604
|
for await (const event of iter) {
|
|
@@ -15800,8 +15903,8 @@ var streamSSE = (c, cb, onError) => {
|
|
|
15800
15903
|
|
|
15801
15904
|
// app/lib/whatsapp/gateway/routes.ts
|
|
15802
15905
|
function createWaChannelRoutes(deps) {
|
|
15803
|
-
const
|
|
15804
|
-
|
|
15906
|
+
const app48 = new Hono();
|
|
15907
|
+
app48.get("/wa-channel/inbound", (c) => {
|
|
15805
15908
|
const senderId = c.req.query("senderId");
|
|
15806
15909
|
if (!senderId) return c.json({ error: "senderId required" }, 400);
|
|
15807
15910
|
return streamSSE(c, async (stream2) => {
|
|
@@ -15819,7 +15922,7 @@ function createWaChannelRoutes(deps) {
|
|
|
15819
15922
|
}
|
|
15820
15923
|
});
|
|
15821
15924
|
});
|
|
15822
|
-
|
|
15925
|
+
app48.post("/wa-channel/reply", async (c) => {
|
|
15823
15926
|
const body = await c.req.json().catch(() => null);
|
|
15824
15927
|
const senderId = body?.senderId;
|
|
15825
15928
|
const text = body?.text;
|
|
@@ -15830,15 +15933,17 @@ function createWaChannelRoutes(deps) {
|
|
|
15830
15933
|
try {
|
|
15831
15934
|
await deps.sendOutbound(senderId, text);
|
|
15832
15935
|
} catch (err) {
|
|
15936
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
15937
|
+
const terminal = err instanceof WaReplyError ? err.terminal : false;
|
|
15833
15938
|
console.error(
|
|
15834
|
-
`[whatsapp-native] op=reply-failed senderId=${senderId} bytes=${bytes} error=${
|
|
15939
|
+
`[whatsapp-native] op=reply-failed senderId=${senderId} bytes=${bytes} terminal=${terminal} error=${message}`
|
|
15835
15940
|
);
|
|
15836
|
-
return c.json({ error:
|
|
15941
|
+
return c.json({ error: message, terminal }, 500);
|
|
15837
15942
|
}
|
|
15838
15943
|
console.error(`[whatsapp-native] op=reply-dispatch senderId=${senderId} bytes=${bytes}`);
|
|
15839
15944
|
return c.json({ ok: true });
|
|
15840
15945
|
});
|
|
15841
|
-
|
|
15946
|
+
app48.post("/wa-channel/ready", async (c) => {
|
|
15842
15947
|
const body = await c.req.json().catch(() => null);
|
|
15843
15948
|
const senderId = body?.senderId;
|
|
15844
15949
|
if (typeof senderId !== "string") {
|
|
@@ -15847,7 +15952,7 @@ function createWaChannelRoutes(deps) {
|
|
|
15847
15952
|
deps.onReady?.(senderId);
|
|
15848
15953
|
return c.json({ ok: true });
|
|
15849
15954
|
});
|
|
15850
|
-
|
|
15955
|
+
app48.post("/wa-channel/received", async (c) => {
|
|
15851
15956
|
const body = await c.req.json().catch(() => null);
|
|
15852
15957
|
const senderId = body?.senderId;
|
|
15853
15958
|
const waMessageId = body?.waMessageId;
|
|
@@ -15857,7 +15962,7 @@ function createWaChannelRoutes(deps) {
|
|
|
15857
15962
|
deps.onReceived?.(senderId, waMessageId);
|
|
15858
15963
|
return c.json({ ok: true });
|
|
15859
15964
|
});
|
|
15860
|
-
return
|
|
15965
|
+
return app48;
|
|
15861
15966
|
}
|
|
15862
15967
|
|
|
15863
15968
|
// app/lib/whatsapp/gateway/wa-gateway.ts
|
|
@@ -16050,8 +16155,8 @@ function broadcastAdminShutdown(reason) {
|
|
|
16050
16155
|
|
|
16051
16156
|
// ../lib/entitlement/src/index.ts
|
|
16052
16157
|
import { createPublicKey, createHash as createHash5, verify as cryptoVerify } from "crypto";
|
|
16053
|
-
import { existsSync as
|
|
16054
|
-
import { resolve as
|
|
16158
|
+
import { existsSync as existsSync23, readFileSync as readFileSync23, statSync as statSync10 } from "fs";
|
|
16159
|
+
import { resolve as resolve26 } from "path";
|
|
16055
16160
|
|
|
16056
16161
|
// ../lib/entitlement/src/canonicalize.ts
|
|
16057
16162
|
function canonicalize(value) {
|
|
@@ -16086,7 +16191,7 @@ var PUBKEY_SHA256 = "8eee6bcb33545fd13b16d3199a5735ca5db5062834c7b49dfe4f23801d9
|
|
|
16086
16191
|
var GRACE_DAYS = 7;
|
|
16087
16192
|
var GRACE_MS = GRACE_DAYS * 24 * 60 * 60 * 1e3;
|
|
16088
16193
|
function pubkeyPath(brand) {
|
|
16089
|
-
return
|
|
16194
|
+
return resolve26(brand.platformRoot, "lib", "entitlement", "rubytech-pubkey.pem");
|
|
16090
16195
|
}
|
|
16091
16196
|
var memo = null;
|
|
16092
16197
|
function memoKey(mtimeMs, account) {
|
|
@@ -16098,8 +16203,8 @@ function resolveEntitlement(brand, account) {
|
|
|
16098
16203
|
if (brand.commercialMode !== true) {
|
|
16099
16204
|
return logResolved(implicitTrust(account), null);
|
|
16100
16205
|
}
|
|
16101
|
-
const entitlementPath =
|
|
16102
|
-
if (!
|
|
16206
|
+
const entitlementPath = resolve26(brand.configDir, "entitlement.json");
|
|
16207
|
+
if (!existsSync23(entitlementPath)) {
|
|
16103
16208
|
return logResolved(anonymousFallback("missing"), { reason: "missing" });
|
|
16104
16209
|
}
|
|
16105
16210
|
const stat7 = statSync10(entitlementPath);
|
|
@@ -16114,7 +16219,7 @@ function resolveEntitlement(brand, account) {
|
|
|
16114
16219
|
function verifyAndResolve(brand, entitlementPath, account) {
|
|
16115
16220
|
let pubkeyPem;
|
|
16116
16221
|
try {
|
|
16117
|
-
pubkeyPem =
|
|
16222
|
+
pubkeyPem = readFileSync23(pubkeyPath(brand), "utf-8");
|
|
16118
16223
|
} catch (err) {
|
|
16119
16224
|
return logResolved(anonymousFallback("pubkey-missing"), {
|
|
16120
16225
|
reason: "pubkey-missing"
|
|
@@ -16128,7 +16233,7 @@ function verifyAndResolve(brand, entitlementPath, account) {
|
|
|
16128
16233
|
}
|
|
16129
16234
|
let envelope;
|
|
16130
16235
|
try {
|
|
16131
|
-
envelope = JSON.parse(
|
|
16236
|
+
envelope = JSON.parse(readFileSync23(entitlementPath, "utf-8"));
|
|
16132
16237
|
} catch {
|
|
16133
16238
|
return logResolved(anonymousFallback("malformed"), { reason: "malformed" });
|
|
16134
16239
|
}
|
|
@@ -16289,14 +16394,14 @@ function clientFrom(c) {
|
|
|
16289
16394
|
);
|
|
16290
16395
|
}
|
|
16291
16396
|
var PLATFORM_ROOT9 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
16292
|
-
var BRAND_JSON_PATH = PLATFORM_ROOT9 ?
|
|
16397
|
+
var BRAND_JSON_PATH = PLATFORM_ROOT9 ? join19(PLATFORM_ROOT9, "config", "brand.json") : "";
|
|
16293
16398
|
var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", marketingUrl: "https://getmaxy.com" };
|
|
16294
|
-
if (BRAND_JSON_PATH && !
|
|
16399
|
+
if (BRAND_JSON_PATH && !existsSync24(BRAND_JSON_PATH)) {
|
|
16295
16400
|
console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
|
|
16296
16401
|
}
|
|
16297
|
-
if (BRAND_JSON_PATH &&
|
|
16402
|
+
if (BRAND_JSON_PATH && existsSync24(BRAND_JSON_PATH)) {
|
|
16298
16403
|
try {
|
|
16299
|
-
const parsed = JSON.parse(
|
|
16404
|
+
const parsed = JSON.parse(readFileSync24(BRAND_JSON_PATH, "utf-8"));
|
|
16300
16405
|
BRAND = { ...BRAND, ...parsed };
|
|
16301
16406
|
} catch (err) {
|
|
16302
16407
|
console.error(`[brand] Failed to parse brand.json: ${err.message}`);
|
|
@@ -16315,11 +16420,11 @@ var brandLoginOpts = {
|
|
|
16315
16420
|
bodyFont: BRAND.defaultFonts?.body,
|
|
16316
16421
|
logoContainsName: !!BRAND.logoContainsName
|
|
16317
16422
|
};
|
|
16318
|
-
var ALIAS_DOMAINS_PATH =
|
|
16423
|
+
var ALIAS_DOMAINS_PATH = join19(homedir3(), BRAND.configDir, "alias-domains.json");
|
|
16319
16424
|
function loadAliasDomains() {
|
|
16320
16425
|
try {
|
|
16321
|
-
if (!
|
|
16322
|
-
const parsed = JSON.parse(
|
|
16426
|
+
if (!existsSync24(ALIAS_DOMAINS_PATH)) return null;
|
|
16427
|
+
const parsed = JSON.parse(readFileSync24(ALIAS_DOMAINS_PATH, "utf-8"));
|
|
16323
16428
|
if (!Array.isArray(parsed)) {
|
|
16324
16429
|
console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
|
|
16325
16430
|
return null;
|
|
@@ -16343,16 +16448,21 @@ watchFile(ALIAS_DOMAINS_PATH, { interval: 2e3 }, () => {
|
|
|
16343
16448
|
function isPublicHost(host) {
|
|
16344
16449
|
return host.startsWith("public.") || aliasDomains.has(host);
|
|
16345
16450
|
}
|
|
16346
|
-
var
|
|
16451
|
+
var app47 = new Hono();
|
|
16347
16452
|
var waGateway = new WaGateway({
|
|
16348
16453
|
gatewayUrl: `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`,
|
|
16349
|
-
serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ??
|
|
16454
|
+
serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve27(process.env.MAXY_PLATFORM_ROOT ?? join19(__dirname, ".."), "services/whatsapp-channel/dist/server.js"),
|
|
16350
16455
|
ensureChannelSession: async ({ accountId, senderId, gatewayUrl, serverPath }) => {
|
|
16351
16456
|
const userId = resolveAdminUserId({ accountId, senderPhone: senderId }) ?? void 0;
|
|
16352
16457
|
console.error(`[whatsapp-native] op=admin-identity senderId=${senderId} userId=${userId ?? "owner-fallback"}`);
|
|
16353
16458
|
const result = await managerRcSpawn({
|
|
16354
16459
|
sessionId: adminSessionIdFor(accountId, senderId),
|
|
16355
16460
|
userId,
|
|
16461
|
+
// Task 724 — supply the channel so /rc-spawn writes the classification
|
|
16462
|
+
// sidecar from the body, the same path every other admin channel uses.
|
|
16463
|
+
// senderId for the sidecar continues to come from the waChannel binding.
|
|
16464
|
+
role: "admin",
|
|
16465
|
+
channel: "whatsapp",
|
|
16356
16466
|
waChannel: { senderId, gatewayUrl, serverPath },
|
|
16357
16467
|
logContext: `channel=whatsapp-native senderId=${senderId}`
|
|
16358
16468
|
});
|
|
@@ -16361,10 +16471,10 @@ var waGateway = new WaGateway({
|
|
|
16361
16471
|
}
|
|
16362
16472
|
}
|
|
16363
16473
|
});
|
|
16364
|
-
|
|
16474
|
+
app47.route("/", waGateway.routes());
|
|
16365
16475
|
waGateway.startSweeper();
|
|
16366
|
-
|
|
16367
|
-
|
|
16476
|
+
app47.use("*", clientIpMiddleware);
|
|
16477
|
+
app47.use("*", async (c, next) => {
|
|
16368
16478
|
await next();
|
|
16369
16479
|
c.header("X-Content-Type-Options", "nosniff");
|
|
16370
16480
|
c.header("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
@@ -16374,7 +16484,7 @@ app45.use("*", async (c, next) => {
|
|
|
16374
16484
|
);
|
|
16375
16485
|
});
|
|
16376
16486
|
var HTTP_LOG_PATHS = /* @__PURE__ */ new Set(["/vnc-viewer.html", "/vnc-popout.html"]);
|
|
16377
|
-
|
|
16487
|
+
app47.use("*", async (c, next) => {
|
|
16378
16488
|
if (!HTTP_LOG_PATHS.has(c.req.path)) {
|
|
16379
16489
|
await next();
|
|
16380
16490
|
return;
|
|
@@ -16392,7 +16502,7 @@ app45.use("*", async (c, next) => {
|
|
|
16392
16502
|
});
|
|
16393
16503
|
}
|
|
16394
16504
|
});
|
|
16395
|
-
|
|
16505
|
+
app47.use("*", async (c, next) => {
|
|
16396
16506
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
16397
16507
|
if (!isPublicHost(host)) {
|
|
16398
16508
|
await next();
|
|
@@ -16423,7 +16533,7 @@ function resolveRemoteAuthOpts() {
|
|
|
16423
16533
|
return brandLoginOpts;
|
|
16424
16534
|
}
|
|
16425
16535
|
var MAX_LOGIN_BODY = 8 * 1024;
|
|
16426
|
-
|
|
16536
|
+
app47.post("/__remote-auth/login", async (c) => {
|
|
16427
16537
|
const client = clientFrom(c);
|
|
16428
16538
|
const clientIp = client.ip || "unknown";
|
|
16429
16539
|
if (!requestIsTlsTerminated(c)) {
|
|
@@ -16468,7 +16578,7 @@ app45.post("/__remote-auth/login", async (c) => {
|
|
|
16468
16578
|
}
|
|
16469
16579
|
});
|
|
16470
16580
|
});
|
|
16471
|
-
|
|
16581
|
+
app47.get("/__remote-auth/logout", (c) => {
|
|
16472
16582
|
const client = clientFrom(c);
|
|
16473
16583
|
const clientIp = client.ip || "unknown";
|
|
16474
16584
|
console.error(`[remote-auth] logout ip=${clientIp}`);
|
|
@@ -16481,7 +16591,7 @@ app45.get("/__remote-auth/logout", (c) => {
|
|
|
16481
16591
|
}
|
|
16482
16592
|
});
|
|
16483
16593
|
});
|
|
16484
|
-
|
|
16594
|
+
app47.post("/__remote-auth/change-password", async (c) => {
|
|
16485
16595
|
const client = clientFrom(c);
|
|
16486
16596
|
const clientIp = client.ip || "unknown";
|
|
16487
16597
|
const rateLimited = checkRateLimit(client);
|
|
@@ -16532,13 +16642,13 @@ app45.post("/__remote-auth/change-password", async (c) => {
|
|
|
16532
16642
|
return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), mode: "change", changeError: "Failed to save password", redirect }), 200);
|
|
16533
16643
|
}
|
|
16534
16644
|
});
|
|
16535
|
-
|
|
16645
|
+
app47.get("/__remote-auth/setup", (c) => {
|
|
16536
16646
|
if (isRemoteAuthConfigured()) {
|
|
16537
16647
|
return c.redirect("/");
|
|
16538
16648
|
}
|
|
16539
16649
|
return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), mode: "setup" }), 200);
|
|
16540
16650
|
});
|
|
16541
|
-
|
|
16651
|
+
app47.post("/__remote-auth/set-initial-password", async (c) => {
|
|
16542
16652
|
if (isRemoteAuthConfigured()) {
|
|
16543
16653
|
return c.redirect("/");
|
|
16544
16654
|
}
|
|
@@ -16576,10 +16686,10 @@ app45.post("/__remote-auth/set-initial-password", async (c) => {
|
|
|
16576
16686
|
return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), mode: "setup", setupError: "Failed to save password. Please try again." }), 200);
|
|
16577
16687
|
}
|
|
16578
16688
|
});
|
|
16579
|
-
|
|
16689
|
+
app47.get("/api/remote-auth/status", (c) => {
|
|
16580
16690
|
return c.json({ configured: isRemoteAuthConfigured() });
|
|
16581
16691
|
});
|
|
16582
|
-
|
|
16692
|
+
app47.post("/api/remote-auth/set-password", async (c) => {
|
|
16583
16693
|
let body;
|
|
16584
16694
|
try {
|
|
16585
16695
|
body = await c.req.json();
|
|
@@ -16610,9 +16720,9 @@ app45.post("/api/remote-auth/set-password", async (c) => {
|
|
|
16610
16720
|
return c.json({ error: "Failed to save password" }, 500);
|
|
16611
16721
|
}
|
|
16612
16722
|
});
|
|
16613
|
-
|
|
16723
|
+
app47.route("/api/_client-error", client_error_default);
|
|
16614
16724
|
console.log("[client-error-route] mounted");
|
|
16615
|
-
|
|
16725
|
+
app47.use("*", async (c, next) => {
|
|
16616
16726
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
16617
16727
|
const path2 = c.req.path;
|
|
16618
16728
|
if (path2 === "/favicon.ico" || path2.startsWith("/assets/") || path2.startsWith("/brand/")) {
|
|
@@ -16645,14 +16755,15 @@ app45.use("*", async (c, next) => {
|
|
|
16645
16755
|
console.error(`[remote-auth] login required ip=${clientIp} path=${path2} ${disambig}`);
|
|
16646
16756
|
return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), redirect: path2 }), 200);
|
|
16647
16757
|
});
|
|
16648
|
-
|
|
16649
|
-
|
|
16650
|
-
|
|
16651
|
-
|
|
16652
|
-
|
|
16653
|
-
|
|
16654
|
-
|
|
16655
|
-
|
|
16758
|
+
app47.route("/api/health", health_default);
|
|
16759
|
+
app47.route("/api/chat", chat_default);
|
|
16760
|
+
app47.route("/api/whatsapp", whatsapp_default);
|
|
16761
|
+
app47.route("/api/whatsapp-reader", whatsapp_reader_default);
|
|
16762
|
+
app47.route("/api/telegram", telegram_default);
|
|
16763
|
+
app47.route("/api/onboarding", onboarding_default);
|
|
16764
|
+
app47.route("/api/admin", admin_default);
|
|
16765
|
+
app47.route("/api/access", access_default);
|
|
16766
|
+
app47.route("/api/session", session_default2);
|
|
16656
16767
|
var SAFE_SLUG_RE2 = /^[a-z][a-z0-9-]{2,49}$/;
|
|
16657
16768
|
var SAFE_FILENAME_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
|
|
16658
16769
|
var IMAGE_MIME = {
|
|
@@ -16664,7 +16775,7 @@ var IMAGE_MIME = {
|
|
|
16664
16775
|
".svg": "image/svg+xml",
|
|
16665
16776
|
".ico": "image/x-icon"
|
|
16666
16777
|
};
|
|
16667
|
-
|
|
16778
|
+
app47.get("/agent-assets/:slug/:filename", (c) => {
|
|
16668
16779
|
const slug = c.req.param("slug");
|
|
16669
16780
|
const filename = c.req.param("filename");
|
|
16670
16781
|
if (!SAFE_SLUG_RE2.test(slug)) {
|
|
@@ -16680,26 +16791,26 @@ app45.get("/agent-assets/:slug/:filename", (c) => {
|
|
|
16680
16791
|
console.error(`[agent-assets] no-account slug=${slug} file=${filename}`);
|
|
16681
16792
|
return c.text("Not found", 404);
|
|
16682
16793
|
}
|
|
16683
|
-
const filePath =
|
|
16684
|
-
const expectedDir =
|
|
16794
|
+
const filePath = resolve27(account.accountDir, "agents", slug, "assets", filename);
|
|
16795
|
+
const expectedDir = resolve27(account.accountDir, "agents", slug, "assets");
|
|
16685
16796
|
if (!filePath.startsWith(expectedDir + "/")) {
|
|
16686
16797
|
console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
|
|
16687
16798
|
return c.text("Forbidden", 403);
|
|
16688
16799
|
}
|
|
16689
|
-
if (!
|
|
16800
|
+
if (!existsSync24(filePath)) {
|
|
16690
16801
|
console.error(`[agent-assets] serve slug=${slug} file=${filename} status=404`);
|
|
16691
16802
|
return c.text("Not found", 404);
|
|
16692
16803
|
}
|
|
16693
16804
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
16694
16805
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
16695
16806
|
console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
|
|
16696
|
-
const body =
|
|
16807
|
+
const body = readFileSync24(filePath);
|
|
16697
16808
|
return c.body(body, 200, {
|
|
16698
16809
|
"Content-Type": contentType,
|
|
16699
16810
|
"Cache-Control": "public, max-age=3600"
|
|
16700
16811
|
});
|
|
16701
16812
|
});
|
|
16702
|
-
|
|
16813
|
+
app47.get("/generated/:filename", (c) => {
|
|
16703
16814
|
const filename = c.req.param("filename");
|
|
16704
16815
|
if (!SAFE_FILENAME_RE.test(filename) || filename.includes("..")) {
|
|
16705
16816
|
console.error(`[generated] serve file=${filename} status=403`);
|
|
@@ -16710,35 +16821,35 @@ app45.get("/generated/:filename", (c) => {
|
|
|
16710
16821
|
console.error(`[generated] serve file=${filename} status=404`);
|
|
16711
16822
|
return c.text("Not found", 404);
|
|
16712
16823
|
}
|
|
16713
|
-
const filePath =
|
|
16714
|
-
const expectedDir =
|
|
16824
|
+
const filePath = resolve27(account.accountDir, "generated", filename);
|
|
16825
|
+
const expectedDir = resolve27(account.accountDir, "generated");
|
|
16715
16826
|
if (!filePath.startsWith(expectedDir + "/")) {
|
|
16716
16827
|
console.error(`[generated] serve file=${filename} status=403`);
|
|
16717
16828
|
return c.text("Forbidden", 403);
|
|
16718
16829
|
}
|
|
16719
|
-
if (!
|
|
16830
|
+
if (!existsSync24(filePath)) {
|
|
16720
16831
|
console.error(`[generated] serve file=${filename} status=404`);
|
|
16721
16832
|
return c.text("Not found", 404);
|
|
16722
16833
|
}
|
|
16723
16834
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
16724
16835
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
16725
16836
|
console.log(`[generated] serve file=${filename} status=200`);
|
|
16726
|
-
const body =
|
|
16837
|
+
const body = readFileSync24(filePath);
|
|
16727
16838
|
return c.body(body, 200, {
|
|
16728
16839
|
"Content-Type": contentType,
|
|
16729
16840
|
"Cache-Control": "public, max-age=86400"
|
|
16730
16841
|
});
|
|
16731
16842
|
});
|
|
16732
|
-
|
|
16733
|
-
|
|
16734
|
-
|
|
16735
|
-
|
|
16843
|
+
app47.route("/sites", sites_default);
|
|
16844
|
+
app47.route("/listings", listings_default);
|
|
16845
|
+
app47.route("/v", visitor_event_default);
|
|
16846
|
+
app47.route("/v", visitor_consent_default);
|
|
16736
16847
|
var htmlCache = /* @__PURE__ */ new Map();
|
|
16737
16848
|
var brandLogoPath = "/brand/maxy-monochrome.png";
|
|
16738
16849
|
var brandIconPath = "/brand/maxy-monochrome.png";
|
|
16739
|
-
if (BRAND_JSON_PATH &&
|
|
16850
|
+
if (BRAND_JSON_PATH && existsSync24(BRAND_JSON_PATH)) {
|
|
16740
16851
|
try {
|
|
16741
|
-
const fullBrand = JSON.parse(
|
|
16852
|
+
const fullBrand = JSON.parse(readFileSync24(BRAND_JSON_PATH, "utf-8"));
|
|
16742
16853
|
if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
|
|
16743
16854
|
brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
|
|
16744
16855
|
} catch {
|
|
@@ -16755,9 +16866,9 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
|
|
|
16755
16866
|
function readInstalledVersion() {
|
|
16756
16867
|
try {
|
|
16757
16868
|
if (!PLATFORM_ROOT9) return "unknown";
|
|
16758
|
-
const versionFile =
|
|
16759
|
-
if (!
|
|
16760
|
-
const content =
|
|
16869
|
+
const versionFile = join19(PLATFORM_ROOT9, "config", `.${BRAND.hostname}-version`);
|
|
16870
|
+
if (!existsSync24(versionFile)) return "unknown";
|
|
16871
|
+
const content = readFileSync24(versionFile, "utf-8").trim();
|
|
16761
16872
|
return content || "unknown";
|
|
16762
16873
|
} catch {
|
|
16763
16874
|
return "unknown";
|
|
@@ -16798,7 +16909,7 @@ var clientErrorReporterScript = `<script>
|
|
|
16798
16909
|
function cachedHtml(file) {
|
|
16799
16910
|
let html = htmlCache.get(file);
|
|
16800
16911
|
if (!html) {
|
|
16801
|
-
html =
|
|
16912
|
+
html = readFileSync24(resolve27(process.cwd(), "public", file), "utf-8");
|
|
16802
16913
|
const productNameEsc = escapeHtml(BRAND.productName);
|
|
16803
16914
|
html = html.replace(/<title>([^<]*)<\/title>/, (_match, inner) => `<title>${escapeHtml(inner).replace(/Maxy/g, productNameEsc)}</title>`);
|
|
16804
16915
|
html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
|
|
@@ -16814,13 +16925,13 @@ ${clientErrorReporterScript}
|
|
|
16814
16925
|
}
|
|
16815
16926
|
var brandedHtmlCache = /* @__PURE__ */ new Map();
|
|
16816
16927
|
function loadBrandingCache(agentSlug) {
|
|
16817
|
-
const configDir2 =
|
|
16928
|
+
const configDir2 = join19(homedir3(), BRAND.configDir);
|
|
16818
16929
|
try {
|
|
16819
16930
|
const accountId = getDefaultAccountId();
|
|
16820
16931
|
if (!accountId) return null;
|
|
16821
|
-
const cachePath =
|
|
16822
|
-
if (!
|
|
16823
|
-
return JSON.parse(
|
|
16932
|
+
const cachePath = join19(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
|
|
16933
|
+
if (!existsSync24(cachePath)) return null;
|
|
16934
|
+
return JSON.parse(readFileSync24(cachePath, "utf-8"));
|
|
16824
16935
|
} catch {
|
|
16825
16936
|
return null;
|
|
16826
16937
|
}
|
|
@@ -16863,7 +16974,7 @@ function escapeHtml(s) {
|
|
|
16863
16974
|
function agentUnavailableHtml() {
|
|
16864
16975
|
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>${escapeHtml(BRAND.productName)}</title></head><body style="font-family:system-ui,sans-serif;max-width:32rem;margin:4rem auto;padding:0 1.5rem;color:#222"><h1 style="font-size:1.25rem">Agent unavailable</h1><p>This agent isn't available right now. If you reached this page from a saved link, the agent may have been turned off.</p></body></html>`;
|
|
16865
16976
|
}
|
|
16866
|
-
|
|
16977
|
+
app47.get("/", (c) => {
|
|
16867
16978
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
16868
16979
|
if (isPublicHost(host)) {
|
|
16869
16980
|
const defaultSlug = resolveDefaultSlug();
|
|
@@ -16879,12 +16990,12 @@ app45.get("/", (c) => {
|
|
|
16879
16990
|
}
|
|
16880
16991
|
return c.html(cachedHtml("index.html"));
|
|
16881
16992
|
});
|
|
16882
|
-
|
|
16993
|
+
app47.get("/public", (c) => {
|
|
16883
16994
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
16884
16995
|
if (isPublicHost(host)) return c.text("Not found", 404);
|
|
16885
16996
|
return c.html(cachedHtml("public.html"));
|
|
16886
16997
|
});
|
|
16887
|
-
|
|
16998
|
+
app47.get("/chat", (c) => {
|
|
16888
16999
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
16889
17000
|
if (isPublicHost(host)) return c.text("Not found", 404);
|
|
16890
17001
|
return c.html(cachedHtml("public.html"));
|
|
@@ -16903,12 +17014,12 @@ async function logViewerFetch(c, next) {
|
|
|
16903
17014
|
duration_ms: Date.now() - start
|
|
16904
17015
|
});
|
|
16905
17016
|
}
|
|
16906
|
-
|
|
16907
|
-
|
|
16908
|
-
|
|
17017
|
+
app47.use("/vnc-viewer.html", logViewerFetch);
|
|
17018
|
+
app47.use("/vnc-popout.html", logViewerFetch);
|
|
17019
|
+
app47.get("/vnc-popout.html", (c) => {
|
|
16909
17020
|
let html = htmlCache.get("vnc-popout.html");
|
|
16910
17021
|
if (!html) {
|
|
16911
|
-
html =
|
|
17022
|
+
html = readFileSync24(resolve27(process.cwd(), "public", "vnc-popout.html"), "utf-8");
|
|
16912
17023
|
const name = escapeHtml(BRAND.productName);
|
|
16913
17024
|
html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
|
|
16914
17025
|
html = html.replace("</head>", ` ${brandScript}
|
|
@@ -16918,7 +17029,7 @@ app45.get("/vnc-popout.html", (c) => {
|
|
|
16918
17029
|
}
|
|
16919
17030
|
return c.html(html);
|
|
16920
17031
|
});
|
|
16921
|
-
|
|
17032
|
+
app47.post("/api/vnc/client-event", async (c) => {
|
|
16922
17033
|
let body;
|
|
16923
17034
|
try {
|
|
16924
17035
|
body = await c.req.json();
|
|
@@ -16939,30 +17050,30 @@ app45.post("/api/vnc/client-event", async (c) => {
|
|
|
16939
17050
|
});
|
|
16940
17051
|
return c.json({ ok: true });
|
|
16941
17052
|
});
|
|
16942
|
-
|
|
17053
|
+
app47.get("/g/:slug", (c) => {
|
|
16943
17054
|
return c.html(brandedPublicHtml());
|
|
16944
17055
|
});
|
|
16945
|
-
|
|
17056
|
+
app47.get("/graph", (c) => {
|
|
16946
17057
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
16947
17058
|
if (isPublicHost(host)) return c.text("Not found", 404);
|
|
16948
17059
|
return c.html(cachedHtml("graph.html"));
|
|
16949
17060
|
});
|
|
16950
|
-
|
|
17061
|
+
app47.get("/sessions", (c) => {
|
|
16951
17062
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
16952
17063
|
if (isPublicHost(host)) return c.text("Not found", 404);
|
|
16953
17064
|
return c.html(cachedHtml("sessions.html"));
|
|
16954
17065
|
});
|
|
16955
|
-
|
|
17066
|
+
app47.get("/data", (c) => {
|
|
16956
17067
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
16957
17068
|
if (isPublicHost(host)) return c.text("Not found", 404);
|
|
16958
17069
|
return c.html(cachedHtml("data.html"));
|
|
16959
17070
|
});
|
|
16960
|
-
|
|
17071
|
+
app47.get("/browser", (c) => {
|
|
16961
17072
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
16962
17073
|
if (isPublicHost(host)) return c.text("Not found", 404);
|
|
16963
17074
|
return c.html(cachedHtml("browser.html"));
|
|
16964
17075
|
});
|
|
16965
|
-
|
|
17076
|
+
app47.get("/:slug", async (c, next) => {
|
|
16966
17077
|
const slug = c.req.param("slug");
|
|
16967
17078
|
if (AGENT_SLUG_PATTERN.test(`/${slug}`)) {
|
|
16968
17079
|
const account = resolveAccount();
|
|
@@ -16977,13 +17088,13 @@ app45.get("/:slug", async (c, next) => {
|
|
|
16977
17088
|
await next();
|
|
16978
17089
|
});
|
|
16979
17090
|
if (brandFaviconPath !== "/favicon.ico") {
|
|
16980
|
-
|
|
17091
|
+
app47.get("/favicon.ico", (c) => {
|
|
16981
17092
|
c.header("Cache-Control", "public, max-age=300");
|
|
16982
17093
|
return c.redirect(brandFaviconPath, 302);
|
|
16983
17094
|
});
|
|
16984
17095
|
}
|
|
16985
|
-
|
|
16986
|
-
|
|
17096
|
+
app47.use("/*", serveStatic({ root: "./public" }));
|
|
17097
|
+
app47.all("*", (c) => {
|
|
16987
17098
|
const host = (c.req.header("host") ?? "").split(":")[0];
|
|
16988
17099
|
const path2 = c.req.path;
|
|
16989
17100
|
if (isPublicHost(host)) {
|
|
@@ -16997,7 +17108,7 @@ app45.all("*", (c) => {
|
|
|
16997
17108
|
});
|
|
16998
17109
|
var port = requirePortEnv("MAXY_UI_INTERNAL_PORT", { tag: "ui-server" });
|
|
16999
17110
|
var hostname = process.env.HOSTNAME ?? "127.0.0.1";
|
|
17000
|
-
var httpServer = serve({ fetch:
|
|
17111
|
+
var httpServer = serve({ fetch: app47.fetch, port, hostname });
|
|
17001
17112
|
console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
17002
17113
|
startAuthHealthHeartbeat();
|
|
17003
17114
|
{
|
|
@@ -17020,6 +17131,7 @@ var SUBAPP_MANIFEST = [
|
|
|
17020
17131
|
{ prefix: "/api/chat", file: "server/routes/chat.ts", subapp: chat_default },
|
|
17021
17132
|
{ prefix: "/api/whatsapp", file: "server/routes/whatsapp.ts", subapp: whatsapp_default },
|
|
17022
17133
|
{ prefix: "/api/whatsapp-reader", file: "server/routes/whatsapp-reader.ts", subapp: whatsapp_reader_default },
|
|
17134
|
+
{ prefix: "/api/telegram", file: "server/routes/telegram.ts", subapp: telegram_default },
|
|
17023
17135
|
{ prefix: "/api/onboarding", file: "server/routes/onboarding.ts", subapp: onboarding_default },
|
|
17024
17136
|
{ prefix: "/api/admin", file: "server/routes/admin/index.ts", subapp: admin_default },
|
|
17025
17137
|
{ prefix: "/api/access", file: "server/routes/access/index.ts", subapp: access_default },
|
|
@@ -17035,7 +17147,7 @@ for (const m of SUBAPP_MANIFEST) {
|
|
|
17035
17147
|
}
|
|
17036
17148
|
try {
|
|
17037
17149
|
const registered = [];
|
|
17038
|
-
for (const r of
|
|
17150
|
+
for (const r of app47.routes ?? []) {
|
|
17039
17151
|
if (typeof r.path !== "string" || r.path.includes(":") || r.path.includes("*")) continue;
|
|
17040
17152
|
if (AGENT_SLUG_PATTERN.test(r.path)) {
|
|
17041
17153
|
registered.push({ method: (r.method ?? "ALL").toUpperCase(), path: r.path });
|
|
@@ -17063,11 +17175,11 @@ try {
|
|
|
17063
17175
|
var ADMINUSER_RECONCILE_INTERVAL_MS = 60 * 60 * 1e3;
|
|
17064
17176
|
async function runAdminUserReconcileTick() {
|
|
17065
17177
|
try {
|
|
17066
|
-
if (!
|
|
17178
|
+
if (!existsSync24(USERS_FILE)) {
|
|
17067
17179
|
console.error("[adminuser-self-heal] skip reason=no-users-file");
|
|
17068
17180
|
return;
|
|
17069
17181
|
}
|
|
17070
|
-
const usersRaw =
|
|
17182
|
+
const usersRaw = readFileSync24(USERS_FILE, "utf-8").trim();
|
|
17071
17183
|
if (!usersRaw) {
|
|
17072
17184
|
console.error("[adminuser-self-heal] skip reason=empty-users-file");
|
|
17073
17185
|
return;
|
|
@@ -17117,6 +17229,9 @@ try {
|
|
|
17117
17229
|
}
|
|
17118
17230
|
var configDirForWhatsApp = basename7(MAXY_DIR) || ".maxy";
|
|
17119
17231
|
var bootAccount = resolveAccount();
|
|
17232
|
+
if (bootAccount) {
|
|
17233
|
+
migrateRemovedConfigKeys(bootAccount.accountDir);
|
|
17234
|
+
}
|
|
17120
17235
|
var bootAccountConfig = bootAccount?.config;
|
|
17121
17236
|
var bootPublicAgent = bootAccount ? resolvePublicAgent(bootAccount.accountDir, { accountId: bootAccount.accountId })?.slug ?? null : null;
|
|
17122
17237
|
var bootEntitlement = bootAccountConfig ? resolveEntitlement(
|
|
@@ -17174,7 +17289,7 @@ if (bootAccountConfig?.whatsapp) {
|
|
|
17174
17289
|
}
|
|
17175
17290
|
init({
|
|
17176
17291
|
configDir: configDirForWhatsApp,
|
|
17177
|
-
platformRoot:
|
|
17292
|
+
platformRoot: resolve27(process.env.MAXY_PLATFORM_ROOT ?? join19(__dirname, "..")),
|
|
17178
17293
|
accountConfig: bootAccountConfig,
|
|
17179
17294
|
onMessage: async (msg) => {
|
|
17180
17295
|
if (msg.isOwnerMirror) {
|