@rubytech/create-maxy-code 0.1.313 → 0.1.315
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/plugin-install.test.js +23 -1
- package/dist/index.js +47 -2
- package/dist/lib/plugin-install.js +16 -0
- package/package.json +1 -1
- package/payload/platform/lib/graph-search/dist/index.d.ts +18 -31
- package/payload/platform/lib/graph-search/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-search/dist/index.js +20 -36
- package/payload/platform/lib/graph-search/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-search/src/__tests__/fulltext-coverage.test.ts +55 -64
- package/payload/platform/lib/graph-search/src/index.ts +19 -37
- package/payload/platform/lib/graph-style/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-style/dist/index.js +5 -3
- package/payload/platform/lib/graph-style/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-style/src/index.ts +5 -3
- package/payload/platform/neo4j/schema.cypher +21 -57
- package/payload/platform/plugins/admin/skills/file-presentation/SKILL.md +1 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +12 -13
- package/payload/platform/plugins/admin/skills/professional-document/SKILL.md +1 -1
- package/payload/platform/plugins/docs/references/admin-session.md +2 -0
- package/payload/platform/plugins/docs/references/admin-ui.md +2 -2
- package/payload/platform/plugins/docs/references/internals.md +9 -10
- package/payload/platform/plugins/docs/references/memory-guide.md +1 -1
- package/payload/platform/plugins/graph-viewer/mcp/dist/index.js +1 -1
- package/payload/platform/plugins/graph-viewer/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.d.ts.map +1 -1
- package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js +8 -7
- package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js.map +1 -1
- package/payload/platform/plugins/graph-viewer/skills/render-graph/SKILL.md +8 -5
- package/payload/platform/plugins/memory/mcp/dist/index.js +14 -15
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/relationship-patterns.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/relationship-patterns.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/relationship-patterns.test.js +146 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/relationship-patterns.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-cypher-drift.test.js +4 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-cypher-drift.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/relationship-patterns.d.ts +66 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/relationship-patterns.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/relationship-patterns.js +102 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/relationship-patterns.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts +9 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js +6 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-open.test.js +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-open.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-edge.d.ts +9 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-edge.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-edge.js +25 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-edge.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +21 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/vitest.config.ts +2 -0
- package/payload/platform/plugins/memory/references/schema-construction.md +37 -15
- package/payload/platform/scripts/check-no-esm-require.mjs +4 -0
- 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 +53 -4
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +1 -0
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +15 -11
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +102 -47
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts +5 -6
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +6 -6
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/session-sidecar.d.ts +10 -0
- package/payload/platform/services/claude-session-manager/dist/session-sidecar.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/session-sidecar.js +15 -0
- package/payload/platform/services/claude-session-manager/dist/session-sidecar.js.map +1 -1
- package/payload/platform/services/webchat-channel/dist/instructions.d.ts.map +1 -1
- package/payload/platform/services/webchat-channel/dist/instructions.js +8 -1
- package/payload/platform/services/webchat-channel/dist/instructions.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.js +8 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
- package/payload/platform/templates/specialists/agents/content-producer.md +1 -1
- package/payload/platform/templates/specialists/agents/public-session-reviewer.md +2 -2
- package/payload/server/public/assets/AccessGate-B0sCa0QJ.js +1 -0
- package/payload/server/public/assets/{AdminLoginScreens-DgwQi3HN.js → AdminLoginScreens-C7Oaqpr6.js} +1 -1
- package/payload/server/public/assets/AdminShell-DgK7TWCI.js +1 -0
- package/payload/server/public/assets/{Checkbox-C54JSreC.js → Checkbox-C4m1jfdP.js} +1 -1
- package/payload/server/public/assets/OperatorConversations-z2mSY_eQ.js +1 -0
- package/payload/server/public/assets/{admin-B25rkI9Q.js → admin-DxYk1Ghu.js} +1 -1
- package/payload/server/public/assets/audio-attachment-mime-BXj3DB3Y.js +30 -0
- package/payload/server/public/assets/{brand-TWflEl22.css → brand-BwWvCHLs.css} +1 -1
- package/payload/server/public/assets/{brand-CYjs10m-.js → brand-DOgCGEoD.js} +1 -1
- package/payload/server/public/assets/{browser-C9Z5dLlN.js → browser-NzxOujfD.js} +1 -1
- package/payload/server/public/assets/chat-CEQTEN5z.js +1 -0
- package/payload/server/public/assets/{data-DCQDh0-Y.js → data-CTROHvif.js} +1 -1
- package/payload/server/public/assets/graph-Dx2a6vGB.js +51 -0
- package/payload/server/public/assets/graph-labels-Dxqq08v6.js +1 -0
- package/payload/server/public/assets/operator-BTHMTwNI.js +1 -0
- package/payload/server/public/assets/page-B9fQp1g9.js +1 -0
- package/payload/server/public/assets/{public-Cs5N4-Az.js → public-cuggl7du.js} +3 -3
- package/payload/server/public/assets/public-next-apqAebbA.js +1 -0
- package/payload/server/public/assets/{useSelectionMode-Cs-vtuKI.js → useSelectionMode-DlgEcMtA.js} +1 -1
- package/payload/server/public/browser.html +6 -6
- package/payload/server/public/chat.html +8 -8
- package/payload/server/public/data.html +5 -5
- package/payload/server/public/graph.html +8 -8
- package/payload/server/public/index.html +9 -9
- package/payload/server/public/operator.html +10 -10
- package/payload/server/public/public-next.html +9 -9
- package/payload/server/public/public.html +7 -7
- package/payload/server/server.js +968 -521
- package/payload/server/public/assets/AccessGate-Byskkf47.js +0 -1
- package/payload/server/public/assets/AdminShell-DQfL2c_L.js +0 -1
- package/payload/server/public/assets/OperatorConversations-DulQyF8c.js +0 -1
- package/payload/server/public/assets/audio-attachment-mime-BlwrgPQa.js +0 -30
- package/payload/server/public/assets/chat-CGNWDmid.js +0 -1
- package/payload/server/public/assets/graph-labels-BBtL--_M.js +0 -1
- package/payload/server/public/assets/graph-sMzOI71g.js +0 -51
- package/payload/server/public/assets/operator-hI6kJi0I.js +0 -1
- package/payload/server/public/assets/page-DbsqlpuX.js +0 -1
- package/payload/server/public/assets/public-next-DoGPYX6T.js +0 -1
package/payload/server/server.js
CHANGED
|
@@ -429,12 +429,14 @@ var require_dist2 = __commonJS({
|
|
|
429
429
|
// apart from people-terracotta, viewing-amber, and task-rose bands so
|
|
430
430
|
// siteoffice legends read as their own group. Top-level (operator-entry)
|
|
431
431
|
// labels only; child labels (LineItem, Milestone, Valuation,
|
|
432
|
-
// VariationNote,
|
|
432
|
+
// VariationNote, CheckIn) intentionally stay fallback-grey. The
|
|
433
|
+
// field-worker Worker label is an additional label on Person and takes
|
|
434
|
+
// the Person colour. A subcontractor company's received invoices use the
|
|
435
|
+
// supplier model (Organization + InboundInvoice), so they carry no
|
|
436
|
+
// construction-specific colour.
|
|
433
437
|
Job: "#8C3B2E",
|
|
434
438
|
QuoteDocument: "#A65A4A",
|
|
435
439
|
InboundInvoice: "#6B2A20",
|
|
436
|
-
SubContractor: "#BF7E6E",
|
|
437
|
-
SubInvoice: "#52201A",
|
|
438
440
|
WhatsAppGroup: "#D4A096",
|
|
439
441
|
// External business entity — schema:Organization, written by memory-write
|
|
440
442
|
// as the Supplier label. Warm tobacco — distinct hue from internal-business
|
|
@@ -1271,8 +1273,8 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
1271
1273
|
};
|
|
1272
1274
|
|
|
1273
1275
|
// server/index.ts
|
|
1274
|
-
import { readFileSync as
|
|
1275
|
-
import { resolve as
|
|
1276
|
+
import { readFileSync as readFileSync31, existsSync as existsSync31, watchFile } from "fs";
|
|
1277
|
+
import { resolve as resolve33, join as join28, basename as basename10 } from "path";
|
|
1276
1278
|
import { homedir as homedir3 } from "os";
|
|
1277
1279
|
import { monitorEventLoopDelay } from "perf_hooks";
|
|
1278
1280
|
|
|
@@ -1920,7 +1922,7 @@ var credsSaveQueue = Promise.resolve();
|
|
|
1920
1922
|
async function drainCredsSaveQueue(timeoutMs = 5e3) {
|
|
1921
1923
|
console.error(`${TAG2} draining credential save queue\u2026`);
|
|
1922
1924
|
const timer2 = new Promise(
|
|
1923
|
-
(
|
|
1925
|
+
(resolve34) => setTimeout(() => resolve34("timeout"), timeoutMs)
|
|
1924
1926
|
);
|
|
1925
1927
|
const result = await Promise.race([
|
|
1926
1928
|
credsSaveQueue.then(() => "drained"),
|
|
@@ -2048,11 +2050,11 @@ async function createWaSocket(opts) {
|
|
|
2048
2050
|
return sock;
|
|
2049
2051
|
}
|
|
2050
2052
|
async function waitForConnection(sock) {
|
|
2051
|
-
return new Promise((
|
|
2053
|
+
return new Promise((resolve34, reject) => {
|
|
2052
2054
|
const handler = (update) => {
|
|
2053
2055
|
if (update.connection === "open") {
|
|
2054
2056
|
sock.ev.off("connection.update", handler);
|
|
2055
|
-
|
|
2057
|
+
resolve34();
|
|
2056
2058
|
}
|
|
2057
2059
|
if (update.connection === "close") {
|
|
2058
2060
|
sock.ev.off("connection.update", handler);
|
|
@@ -2166,14 +2168,14 @@ ${inspected}`;
|
|
|
2166
2168
|
return inspect2(err, INSPECT_OPTS2);
|
|
2167
2169
|
}
|
|
2168
2170
|
function withTimeout(label, promise, timeoutMs) {
|
|
2169
|
-
return new Promise((
|
|
2171
|
+
return new Promise((resolve34, reject) => {
|
|
2170
2172
|
const timer2 = setTimeout(() => {
|
|
2171
2173
|
reject(new Error(`${label} timed out after ${timeoutMs}ms`));
|
|
2172
2174
|
}, timeoutMs);
|
|
2173
2175
|
promise.then(
|
|
2174
2176
|
(value) => {
|
|
2175
2177
|
clearTimeout(timer2);
|
|
2176
|
-
|
|
2178
|
+
resolve34(value);
|
|
2177
2179
|
},
|
|
2178
2180
|
(err) => {
|
|
2179
2181
|
clearTimeout(timer2);
|
|
@@ -3057,8 +3059,8 @@ async function persistWhatsAppMessage(input) {
|
|
|
3057
3059
|
const { givenName, familyName } = splitName(input.pushName);
|
|
3058
3060
|
const prev = sessionWriteLocks.get(input.cacheKey);
|
|
3059
3061
|
let release;
|
|
3060
|
-
const mine = new Promise((
|
|
3061
|
-
release =
|
|
3062
|
+
const mine = new Promise((resolve34) => {
|
|
3063
|
+
release = resolve34;
|
|
3062
3064
|
});
|
|
3063
3065
|
const chained = (prev ?? Promise.resolve()).then(() => mine);
|
|
3064
3066
|
sessionWriteLocks.set(input.cacheKey, chained);
|
|
@@ -4138,11 +4140,11 @@ async function connectWithReconnect(conn) {
|
|
|
4138
4140
|
console.error(
|
|
4139
4141
|
`${TAG13} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
|
|
4140
4142
|
);
|
|
4141
|
-
await new Promise((
|
|
4142
|
-
const timer2 = setTimeout(
|
|
4143
|
+
await new Promise((resolve34) => {
|
|
4144
|
+
const timer2 = setTimeout(resolve34, delay);
|
|
4143
4145
|
conn.abortController.signal.addEventListener("abort", () => {
|
|
4144
4146
|
clearTimeout(timer2);
|
|
4145
|
-
|
|
4147
|
+
resolve34();
|
|
4146
4148
|
}, { once: true });
|
|
4147
4149
|
});
|
|
4148
4150
|
}
|
|
@@ -4150,16 +4152,16 @@ async function connectWithReconnect(conn) {
|
|
|
4150
4152
|
}
|
|
4151
4153
|
}
|
|
4152
4154
|
function waitForDisconnectEvent(conn) {
|
|
4153
|
-
return new Promise((
|
|
4155
|
+
return new Promise((resolve34) => {
|
|
4154
4156
|
if (!conn.sock) {
|
|
4155
|
-
|
|
4157
|
+
resolve34();
|
|
4156
4158
|
return;
|
|
4157
4159
|
}
|
|
4158
4160
|
const sock = conn.sock;
|
|
4159
4161
|
const handler = (update) => {
|
|
4160
4162
|
if (update.connection === "close") {
|
|
4161
4163
|
sock.ev.off("connection.update", handler);
|
|
4162
|
-
|
|
4164
|
+
resolve34();
|
|
4163
4165
|
}
|
|
4164
4166
|
};
|
|
4165
4167
|
sock.ev.on("connection.update", handler);
|
|
@@ -4419,8 +4421,8 @@ async function handleInboundMessage(conn, msg) {
|
|
|
4419
4421
|
const conversationKey = isGroup ? remoteJid : senderPhone;
|
|
4420
4422
|
const debounceKey = `${conn.accountId}:${conversationKey}:${senderPhone}`;
|
|
4421
4423
|
let resolvePending;
|
|
4422
|
-
const sttPending = new Promise((
|
|
4423
|
-
resolvePending =
|
|
4424
|
+
const sttPending = new Promise((resolve34) => {
|
|
4425
|
+
resolvePending = resolve34;
|
|
4424
4426
|
});
|
|
4425
4427
|
if (conn.debouncer) conn.debouncer.registerPending(debounceKey, sttPending);
|
|
4426
4428
|
try {
|
|
@@ -4814,20 +4816,20 @@ function buildX11Env(chromiumWrapperPath, transport = "vnc") {
|
|
|
4814
4816
|
|
|
4815
4817
|
// server/routes/health.ts
|
|
4816
4818
|
function checkPort(port2, timeoutMs = 500) {
|
|
4817
|
-
return new Promise((
|
|
4819
|
+
return new Promise((resolve34) => {
|
|
4818
4820
|
const socket = createConnection2(port2, "127.0.0.1");
|
|
4819
4821
|
socket.setTimeout(timeoutMs);
|
|
4820
4822
|
socket.once("connect", () => {
|
|
4821
4823
|
socket.destroy();
|
|
4822
|
-
|
|
4824
|
+
resolve34(true);
|
|
4823
4825
|
});
|
|
4824
4826
|
socket.once("error", () => {
|
|
4825
4827
|
socket.destroy();
|
|
4826
|
-
|
|
4828
|
+
resolve34(false);
|
|
4827
4829
|
});
|
|
4828
4830
|
socket.once("timeout", () => {
|
|
4829
4831
|
socket.destroy();
|
|
4830
|
-
|
|
4832
|
+
resolve34(false);
|
|
4831
4833
|
});
|
|
4832
4834
|
});
|
|
4833
4835
|
}
|
|
@@ -5383,6 +5385,13 @@ ${result.result.text}` : result.result.text;
|
|
|
5383
5385
|
sliceToken = session.sliceToken;
|
|
5384
5386
|
personId = session.personId;
|
|
5385
5387
|
}
|
|
5388
|
+
let visitorId;
|
|
5389
|
+
if (accessMode === "open") {
|
|
5390
|
+
const cookieHeader = c.req.header("cookie") ?? "";
|
|
5391
|
+
const m = cookieHeader.match(/(?:^|;\s*)maxy_visitor=([^;]*)/);
|
|
5392
|
+
const v = m ? decodeURIComponent(m[1]).trim() : "";
|
|
5393
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v)) visitorId = v;
|
|
5394
|
+
}
|
|
5386
5395
|
const sk = session_key.slice(0, 8);
|
|
5387
5396
|
console.error(
|
|
5388
5397
|
`${WEBCHAT_TAG} inbound from=${sk} bytes=${Buffer.byteLength(fullMessage, "utf8")} attachments=${attachmentCount} accountId=${account.accountId}`
|
|
@@ -5405,6 +5414,7 @@ ${result.result.text}` : result.result.text;
|
|
|
5405
5414
|
agentSlug,
|
|
5406
5415
|
...sliceToken ? { sliceToken } : {},
|
|
5407
5416
|
...personId ? { personId } : {},
|
|
5417
|
+
...visitorId ? { visitorId } : {},
|
|
5408
5418
|
name: publicTitle
|
|
5409
5419
|
}
|
|
5410
5420
|
});
|
|
@@ -5871,8 +5881,8 @@ async function startLogin(opts) {
|
|
|
5871
5881
|
await clearAuth(authDir);
|
|
5872
5882
|
let resolveCode = null;
|
|
5873
5883
|
let rejectCode = null;
|
|
5874
|
-
const codePromise = new Promise((
|
|
5875
|
-
resolveCode =
|
|
5884
|
+
const codePromise = new Promise((resolve34, reject) => {
|
|
5885
|
+
resolveCode = resolve34;
|
|
5876
5886
|
rejectCode = reject;
|
|
5877
5887
|
});
|
|
5878
5888
|
const codeTimer = setTimeout(
|
|
@@ -6008,9 +6018,6 @@ function normalizeJid2(to) {
|
|
|
6008
6018
|
function routeKey(to, filePath) {
|
|
6009
6019
|
return `${normalizeJid2(to)}\0${filePath}`;
|
|
6010
6020
|
}
|
|
6011
|
-
function documentOutboundAt(to) {
|
|
6012
|
-
return lastDocumentOutboundAt.get(normalizeJid2(to));
|
|
6013
|
-
}
|
|
6014
6021
|
function recordDocumentOutbound(to) {
|
|
6015
6022
|
lastDocumentOutboundAt.set(normalizeJid2(to), Date.now());
|
|
6016
6023
|
}
|
|
@@ -6021,14 +6028,14 @@ function routeDocumentOutboundAt(to, filePath) {
|
|
|
6021
6028
|
return lastRouteDocumentOutboundAt.get(routeKey(to, filePath));
|
|
6022
6029
|
}
|
|
6023
6030
|
async function sendWhatsAppDocument(input) {
|
|
6024
|
-
const { to, filePath, caption, accountId, maxyAccountId, platformRoot:
|
|
6031
|
+
const { to, filePath, caption, accountId, maxyAccountId, platformRoot: platformRoot4 } = input;
|
|
6025
6032
|
if (!to || !filePath) {
|
|
6026
6033
|
return { ok: false, status: 400, error: "Missing required fields: to, filePath" };
|
|
6027
6034
|
}
|
|
6028
|
-
if (!maxyAccountId || !
|
|
6035
|
+
if (!maxyAccountId || !platformRoot4) {
|
|
6029
6036
|
return { ok: false, status: 400, error: "Cannot validate file path: missing account or platform context" };
|
|
6030
6037
|
}
|
|
6031
|
-
const accountDir = resolve8(
|
|
6038
|
+
const accountDir = resolve8(platformRoot4, "..", "data/accounts", maxyAccountId);
|
|
6032
6039
|
let resolvedPath;
|
|
6033
6040
|
try {
|
|
6034
6041
|
resolvedPath = realpathSync3(filePath);
|
|
@@ -6634,8 +6641,8 @@ app2.get("/group-info", async (c) => {
|
|
|
6634
6641
|
var whatsapp_default = app2;
|
|
6635
6642
|
|
|
6636
6643
|
// server/routes/whatsapp-reader.ts
|
|
6637
|
-
import { readFileSync as
|
|
6638
|
-
import { basename as basename2, dirname, join as
|
|
6644
|
+
import { readFileSync as readFileSync12, watch, statSync as statSync4, openSync, readSync, closeSync, existsSync as existsSync7, readdirSync as readdirSync7 } from "fs";
|
|
6645
|
+
import { basename as basename2, dirname, isAbsolute, join as join11, relative as relative2, resolve as resolve11, sep as sep3 } from "path";
|
|
6639
6646
|
|
|
6640
6647
|
// server/routes/admin/sidebar-sessions.ts
|
|
6641
6648
|
import { readdirSync as readdirSync4, readFileSync as readFileSync9, statSync as statSync2 } from "fs";
|
|
@@ -6657,12 +6664,30 @@ function selectReaderChannelSessions(rows) {
|
|
|
6657
6664
|
operatorName: null,
|
|
6658
6665
|
whatsappName: null,
|
|
6659
6666
|
lastMessageAt: r.lastMessageAt,
|
|
6660
|
-
modelGated: r.modelGated
|
|
6667
|
+
modelGated: r.modelGated,
|
|
6668
|
+
model: r.model
|
|
6661
6669
|
}));
|
|
6662
6670
|
}
|
|
6663
6671
|
|
|
6664
6672
|
// server/routes/admin/sidebar-sessions.ts
|
|
6665
6673
|
var SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.jsonl$/i;
|
|
6674
|
+
function lastAssistantModel(body) {
|
|
6675
|
+
const lines = body.split("\n");
|
|
6676
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
6677
|
+
const line = lines[i];
|
|
6678
|
+
if (!line || line[0] !== "{") continue;
|
|
6679
|
+
let rec;
|
|
6680
|
+
try {
|
|
6681
|
+
rec = JSON.parse(line);
|
|
6682
|
+
} catch {
|
|
6683
|
+
continue;
|
|
6684
|
+
}
|
|
6685
|
+
if (rec.type !== "assistant") continue;
|
|
6686
|
+
const model = rec.message?.model;
|
|
6687
|
+
if (typeof model === "string" && model !== "<synthetic>") return model;
|
|
6688
|
+
}
|
|
6689
|
+
return null;
|
|
6690
|
+
}
|
|
6666
6691
|
var CLI_MARKER_PREFIXES = ["<local-command-", "<command-name>", "<command-message>"];
|
|
6667
6692
|
var ADMIN_ROLE = "admin";
|
|
6668
6693
|
var WEBCHAT_CHANNEL = "webchat";
|
|
@@ -6778,7 +6803,7 @@ function loadUserTitles(accountDir) {
|
|
|
6778
6803
|
return out;
|
|
6779
6804
|
}
|
|
6780
6805
|
function readSidecarMeta(metaPath) {
|
|
6781
|
-
const empty = { bridgeIds: [], role: null, channel: null, senderId: null, startedAt: null, personId: null, adminUserId: null };
|
|
6806
|
+
const empty = { bridgeIds: [], role: null, channel: null, senderId: null, startedAt: null, personId: null, adminUserId: null, visitorId: null, agentSlug: null };
|
|
6782
6807
|
let raw;
|
|
6783
6808
|
try {
|
|
6784
6809
|
raw = readFileSync9(metaPath, "utf8");
|
|
@@ -6801,7 +6826,9 @@ function readSidecarMeta(metaPath) {
|
|
|
6801
6826
|
senderId: typeof r.senderId === "string" ? r.senderId : null,
|
|
6802
6827
|
startedAt: typeof r.startedAt === "string" ? r.startedAt : null,
|
|
6803
6828
|
personId: typeof r.personId === "string" ? r.personId : null,
|
|
6804
|
-
adminUserId: typeof r.adminUserId === "string" ? r.adminUserId : null
|
|
6829
|
+
adminUserId: typeof r.adminUserId === "string" ? r.adminUserId : null,
|
|
6830
|
+
visitorId: typeof r.visitorId === "string" ? r.visitorId : null,
|
|
6831
|
+
agentSlug: typeof r.agentSlug === "string" ? r.agentSlug : null
|
|
6805
6832
|
};
|
|
6806
6833
|
}
|
|
6807
6834
|
function startsWithCliMarker(s) {
|
|
@@ -6932,7 +6959,8 @@ app3.get("/", requireAdminSession, async (c) => {
|
|
|
6932
6959
|
bridgeIds,
|
|
6933
6960
|
archived,
|
|
6934
6961
|
channel: resolved.channel,
|
|
6935
|
-
personName: null
|
|
6962
|
+
personName: null,
|
|
6963
|
+
model: lastAssistantModel(body)
|
|
6936
6964
|
});
|
|
6937
6965
|
if (live) liveCount += 1;
|
|
6938
6966
|
if (isSubagent) subagentCount += 1;
|
|
@@ -7086,6 +7114,7 @@ var WA_REPLY_DOCUMENT_TOOL = "mcp__whatsapp-channel__reply-document";
|
|
|
7086
7114
|
var WEBCHAT_REPLY_TOOL = "mcp__webchat-channel__reply";
|
|
7087
7115
|
var CHANNEL_REPLY_TOOL = "mcp__channel__reply";
|
|
7088
7116
|
var CHANNEL_REPLY_DOCUMENT_TOOL = "mcp__channel__reply-document";
|
|
7117
|
+
var SEND_USER_FILE_TOOL = "SendUserFile";
|
|
7089
7118
|
var CLI_MARKER_PREFIXES2 = ["<local-command-", "<command-name>", "<command-message>"];
|
|
7090
7119
|
var CHANNEL_WRAPPER2 = /^<channel\b([^>]*)>\n?([\s\S]*?)\n?<\/channel>$/;
|
|
7091
7120
|
var SOURCE_ATTR2 = /\bsource="([^"]+)"/;
|
|
@@ -7111,17 +7140,29 @@ function unwrapChannel(text) {
|
|
|
7111
7140
|
return source ? { text: inner, source } : { text: inner };
|
|
7112
7141
|
}
|
|
7113
7142
|
var COMPOSED_ADMIN_FRAME = /^## Context\n([\s\S]*)\n\n## Instruction\n[\s\S]*$/;
|
|
7143
|
+
var UPLOAD_ANNOTATION = /(?:\n\n)?\[The operator uploaded \d+ file\(s\): [\s\S]*\]$/;
|
|
7114
7144
|
function rawFromComposedAdminFrame(text) {
|
|
7115
|
-
|
|
7145
|
+
const context = text.match(COMPOSED_ADMIN_FRAME)?.[1];
|
|
7146
|
+
if (context === void 0) return void 0;
|
|
7147
|
+
return context.replace(UPLOAD_ANNOTATION, "");
|
|
7148
|
+
}
|
|
7149
|
+
var PUBLIC_UPLOAD_ANNOTATION = /(?:\n\n)?\[The user uploaded \d+ file\(s\): [\s\S]*\]$/;
|
|
7150
|
+
var UPLOAD_COUNT = /\[The (?:operator|user) uploaded (\d+) file\(s\):/;
|
|
7151
|
+
function uploadCountOf(text) {
|
|
7152
|
+
const n = Number(text.match(UPLOAD_COUNT)?.[1]);
|
|
7153
|
+
return Number.isInteger(n) && n > 0 ? n : void 0;
|
|
7116
7154
|
}
|
|
7117
7155
|
function operatorInboundTurn(u, ts) {
|
|
7118
7156
|
const rawBody = rawFromComposedAdminFrame(u.text);
|
|
7157
|
+
const uploadCount = uploadCountOf(u.text);
|
|
7158
|
+
const text = rawBody === void 0 ? u.text.replace(PUBLIC_UPLOAD_ANNOTATION, "") : u.text;
|
|
7119
7159
|
return {
|
|
7120
7160
|
kind: "operator-inbound",
|
|
7121
|
-
text
|
|
7161
|
+
text,
|
|
7122
7162
|
ts,
|
|
7123
7163
|
...u.source ? { source: u.source } : {},
|
|
7124
|
-
...rawBody !== void 0 ? { rawBody } : {}
|
|
7164
|
+
...rawBody !== void 0 ? { rawBody } : {},
|
|
7165
|
+
...uploadCount !== void 0 ? { uploadCount } : {}
|
|
7125
7166
|
};
|
|
7126
7167
|
}
|
|
7127
7168
|
function asString(content) {
|
|
@@ -7157,6 +7198,11 @@ function assistantTurns(content, ts) {
|
|
|
7157
7198
|
const files = Array.isArray(input.files) ? input.files.filter((f) => typeof f === "string") : [];
|
|
7158
7199
|
const caption = typeof input.caption === "string" ? input.caption : null;
|
|
7159
7200
|
out.push({ kind: "agent-reply-document", files, caption, ts });
|
|
7201
|
+
} else if (b.type === "tool_use" && b.name === SEND_USER_FILE_TOOL) {
|
|
7202
|
+
const input = b.input ?? {};
|
|
7203
|
+
const files = Array.isArray(input.files) ? input.files.filter((f) => typeof f === "string") : [];
|
|
7204
|
+
const caption = typeof input.caption === "string" ? input.caption : null;
|
|
7205
|
+
out.push({ kind: "agent-file", files, caption, ts });
|
|
7160
7206
|
} else if (b.type === "tool_use" && typeof b.name === "string") {
|
|
7161
7207
|
out.push({ kind: "tool-call", name: b.name, input: b.input ?? null, ts });
|
|
7162
7208
|
}
|
|
@@ -7194,9 +7240,8 @@ function renderOrSuppressChannelInbound(u, ts, out, queuedPendingSuppress) {
|
|
|
7194
7240
|
out.push(operatorInboundTurn(u, ts));
|
|
7195
7241
|
}
|
|
7196
7242
|
}
|
|
7197
|
-
function parseTranscript(lines) {
|
|
7243
|
+
function parseTranscript(lines, queuedPendingSuppress = /* @__PURE__ */ new Map()) {
|
|
7198
7244
|
const out = [];
|
|
7199
|
-
const queuedPendingSuppress = /* @__PURE__ */ new Map();
|
|
7200
7245
|
for (const line of lines) {
|
|
7201
7246
|
if (!line || line[0] !== "{") continue;
|
|
7202
7247
|
let row;
|
|
@@ -7256,6 +7301,65 @@ function parseTranscript(lines) {
|
|
|
7256
7301
|
return out;
|
|
7257
7302
|
}
|
|
7258
7303
|
|
|
7304
|
+
// app/lib/whatsapp-reader/enrich-attachments.ts
|
|
7305
|
+
import { readFileSync as readFileSync11, readdirSync as readdirSync6 } from "fs";
|
|
7306
|
+
import { join as join10 } from "path";
|
|
7307
|
+
var ADMIN_UPLOAD_PATH = /\/uploads\/(?!public\/)([0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})\//gi;
|
|
7308
|
+
function adminAttachmentIds(text) {
|
|
7309
|
+
const ids = [];
|
|
7310
|
+
for (const m of text.matchAll(ADMIN_UPLOAD_PATH)) {
|
|
7311
|
+
const id = m[1].toLowerCase();
|
|
7312
|
+
if (!ids.includes(id)) ids.push(id);
|
|
7313
|
+
}
|
|
7314
|
+
return ids;
|
|
7315
|
+
}
|
|
7316
|
+
function adminAttachmentsFromText(text, uploadsBase) {
|
|
7317
|
+
const out = [];
|
|
7318
|
+
for (const id of adminAttachmentIds(text)) {
|
|
7319
|
+
const meta = readAttachmentMeta(join10(uploadsBase, id));
|
|
7320
|
+
if (meta) out.push(meta);
|
|
7321
|
+
}
|
|
7322
|
+
return out;
|
|
7323
|
+
}
|
|
7324
|
+
function readSidecar(dir, attachmentId) {
|
|
7325
|
+
let parsed;
|
|
7326
|
+
try {
|
|
7327
|
+
parsed = JSON.parse(readFileSync11(join10(dir, `${attachmentId}.meta.json`), "utf8"));
|
|
7328
|
+
} catch {
|
|
7329
|
+
return null;
|
|
7330
|
+
}
|
|
7331
|
+
const filename = parsed.filename;
|
|
7332
|
+
const mimeType = parsed.mimeType;
|
|
7333
|
+
const sizeBytes = parsed.sizeBytes;
|
|
7334
|
+
const storedAt = parsed.storedAt;
|
|
7335
|
+
if (typeof filename !== "string" || typeof mimeType !== "string" || typeof sizeBytes !== "number" || typeof storedAt !== "string") {
|
|
7336
|
+
return null;
|
|
7337
|
+
}
|
|
7338
|
+
return { attachmentId, filename, mimeType, sizeBytes, storedAt };
|
|
7339
|
+
}
|
|
7340
|
+
function readAttachmentMeta(dir) {
|
|
7341
|
+
const attachmentId = dir.split("/").filter(Boolean).pop() ?? "";
|
|
7342
|
+
const sidecar = readSidecar(dir, attachmentId);
|
|
7343
|
+
if (!sidecar) return null;
|
|
7344
|
+
const { storedAt: _storedAt, ...render } = sidecar;
|
|
7345
|
+
return render;
|
|
7346
|
+
}
|
|
7347
|
+
function listSessionAttachmentsInDir(dir) {
|
|
7348
|
+
let entries;
|
|
7349
|
+
try {
|
|
7350
|
+
entries = readdirSync6(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
7351
|
+
} catch {
|
|
7352
|
+
return [];
|
|
7353
|
+
}
|
|
7354
|
+
const sidecars = [];
|
|
7355
|
+
for (const name of entries) {
|
|
7356
|
+
const sidecar = readSidecar(join10(dir, name), name);
|
|
7357
|
+
if (sidecar) sidecars.push(sidecar);
|
|
7358
|
+
}
|
|
7359
|
+
sidecars.sort((a, b) => a.storedAt.localeCompare(b.storedAt));
|
|
7360
|
+
return sidecars.map(({ storedAt: _storedAt, ...render }) => render);
|
|
7361
|
+
}
|
|
7362
|
+
|
|
7259
7363
|
// app/lib/whatsapp-reader/tail.ts
|
|
7260
7364
|
function splitNewLines(buf, baseOffset) {
|
|
7261
7365
|
const text = buf.toString("utf8");
|
|
@@ -7284,17 +7388,17 @@ var app4 = new Hono();
|
|
|
7284
7388
|
app4.get("/conversations", requireAdminSession, async (c) => {
|
|
7285
7389
|
const cfg = claudeConfigDir();
|
|
7286
7390
|
if (!cfg) return c.json({ conversations: [] });
|
|
7287
|
-
const projectsRoot =
|
|
7391
|
+
const projectsRoot = join11(cfg, "projects");
|
|
7288
7392
|
const userTitles = loadUserTitles(ACCOUNTS_DIR ?? null);
|
|
7289
7393
|
const rows = [];
|
|
7290
7394
|
for (const { path: path2 } of enumerateJsonls(projectsRoot)) {
|
|
7291
7395
|
const sessionId = basename2(path2).replace(/\.jsonl$/, "");
|
|
7292
7396
|
const projectDir = dirname(path2);
|
|
7293
|
-
const meta = readSidecarMeta(
|
|
7397
|
+
const meta = readSidecarMeta(join11(projectDir, `${sessionId}.meta.json`));
|
|
7294
7398
|
if (!isReaderChannelSession(meta.role, meta.channel)) continue;
|
|
7295
7399
|
let body = "";
|
|
7296
7400
|
try {
|
|
7297
|
-
body =
|
|
7401
|
+
body = readFileSync12(path2, "utf8");
|
|
7298
7402
|
} catch {
|
|
7299
7403
|
}
|
|
7300
7404
|
const { title } = resolveTitle(body, sessionId, userTitles);
|
|
@@ -7311,7 +7415,8 @@ app4.get("/conversations", requireAdminSession, async (c) => {
|
|
|
7311
7415
|
channel: meta.channel,
|
|
7312
7416
|
senderId: meta.senderId,
|
|
7313
7417
|
lastMessageAt,
|
|
7314
|
-
modelGated
|
|
7418
|
+
modelGated,
|
|
7419
|
+
model: lastAssistantModel(body)
|
|
7315
7420
|
});
|
|
7316
7421
|
}
|
|
7317
7422
|
const selected = selectReaderChannelSessions(rows);
|
|
@@ -7377,15 +7482,31 @@ function resumeOffset(lastEventId, fileEnd) {
|
|
|
7377
7482
|
if (!Number.isFinite(n) || n < 0) return 0;
|
|
7378
7483
|
return Math.min(n, fileEnd);
|
|
7379
7484
|
}
|
|
7485
|
+
function enrichDownloads(turn) {
|
|
7486
|
+
if (turn.kind !== "agent-file") return turn;
|
|
7487
|
+
const downloads = turn.files.map((f) => {
|
|
7488
|
+
const rel = relative2(DATA_ROOT, f);
|
|
7489
|
+
return !rel || rel.startsWith("..") || isAbsolute(rel) ? null : rel;
|
|
7490
|
+
});
|
|
7491
|
+
return { ...turn, downloads };
|
|
7492
|
+
}
|
|
7493
|
+
function enrichAdminAttachments(turn) {
|
|
7494
|
+
if (turn.kind !== "operator-inbound") return turn;
|
|
7495
|
+
if (adminAttachmentIds(turn.text).length === 0) return turn;
|
|
7496
|
+
const account = resolveAccount();
|
|
7497
|
+
if (!account) return turn;
|
|
7498
|
+
const attachments = adminAttachmentsFromText(turn.text, resolve11(DATA_ROOT, "accounts", account.accountId, "uploads"));
|
|
7499
|
+
return attachments.length > 0 ? { ...turn, attachments } : turn;
|
|
7500
|
+
}
|
|
7380
7501
|
app4.get("/stream", requireAdminSession, (c) => {
|
|
7381
7502
|
const sessionId = c.req.query("sessionId") ?? "";
|
|
7382
7503
|
const projectDir = c.req.query("projectDir") ?? "";
|
|
7383
7504
|
const cfg = claudeConfigDir();
|
|
7384
|
-
const projectsRoot = cfg ? resolve11(
|
|
7505
|
+
const projectsRoot = cfg ? resolve11(join11(cfg, "projects")) : null;
|
|
7385
7506
|
if (!cfg || !projectsRoot || !SESSION_ID_RE2.test(sessionId)) {
|
|
7386
7507
|
return c.json({ error: "bad session reference" }, 400);
|
|
7387
7508
|
}
|
|
7388
|
-
const jsonlPath = resolve11(
|
|
7509
|
+
const jsonlPath = resolve11(join11(projectDir, `${sessionId}.jsonl`));
|
|
7389
7510
|
if (!jsonlPath.startsWith(projectsRoot + sep3)) {
|
|
7390
7511
|
return c.json({ error: "bad session reference" }, 400);
|
|
7391
7512
|
}
|
|
@@ -7408,6 +7529,7 @@ data: ${JSON.stringify(turn)}
|
|
|
7408
7529
|
})();
|
|
7409
7530
|
let offset = resumeOffset(lastEventId, fileEnd0);
|
|
7410
7531
|
console.log(`[wa-stream] op=open conn=${connId} sessionId=${sessionId} channel=reader resume=${offset}`);
|
|
7532
|
+
const queuedPendingSuppress = /* @__PURE__ */ new Map();
|
|
7411
7533
|
const drain = () => {
|
|
7412
7534
|
try {
|
|
7413
7535
|
const { buf, end } = readFrom(jsonlPath, offset);
|
|
@@ -7415,8 +7537,8 @@ data: ${JSON.stringify(turn)}
|
|
|
7415
7537
|
const { lines, nextOffset } = splitNewLines(buf, offset);
|
|
7416
7538
|
offset = nextOffset;
|
|
7417
7539
|
let n = 0;
|
|
7418
|
-
for (const turn of parseTranscript(lines)) {
|
|
7419
|
-
send(controller, turn, offset);
|
|
7540
|
+
for (const turn of parseTranscript(lines, queuedPendingSuppress)) {
|
|
7541
|
+
send(controller, enrichAdminAttachments(enrichDownloads(turn)), offset);
|
|
7420
7542
|
if (turn.kind === "agent-error") {
|
|
7421
7543
|
console.log(`[wa-stream] op=agent-error conn=${connId} sessionId=${sessionId} code=${turn.code} source=jsonl`);
|
|
7422
7544
|
}
|
|
@@ -7431,8 +7553,8 @@ data: ${JSON.stringify(turn)}
|
|
|
7431
7553
|
const { buf, end } = readFrom(jsonlPath, 0);
|
|
7432
7554
|
offset = end;
|
|
7433
7555
|
let n = 0;
|
|
7434
|
-
for (const turn of parseTranscript(buf.toString("utf8").split("\n"))) {
|
|
7435
|
-
send(controller, turn, offset);
|
|
7556
|
+
for (const turn of parseTranscript(buf.toString("utf8").split("\n"), queuedPendingSuppress)) {
|
|
7557
|
+
send(controller, enrichAdminAttachments(enrichDownloads(turn)), offset);
|
|
7436
7558
|
if (turn.kind === "agent-error") {
|
|
7437
7559
|
console.log(`[wa-stream] op=agent-error conn=${connId} sessionId=${sessionId} code=${turn.code} source=jsonl`);
|
|
7438
7560
|
}
|
|
@@ -7487,7 +7609,7 @@ app4.get("/directives", requireAdminSession, (c) => {
|
|
|
7487
7609
|
if (!dir || !existsSync7(dir)) return c.json({ entries: [] });
|
|
7488
7610
|
let names;
|
|
7489
7611
|
try {
|
|
7490
|
-
names =
|
|
7612
|
+
names = readdirSync7(dir).filter((n) => DIRECTIVE_NAME_RE.test(n));
|
|
7491
7613
|
} catch {
|
|
7492
7614
|
return c.json({ entries: [] });
|
|
7493
7615
|
}
|
|
@@ -7497,7 +7619,7 @@ app4.get("/directives", requireAdminSession, (c) => {
|
|
|
7497
7619
|
const pid = Number(pidPart);
|
|
7498
7620
|
let len = 0;
|
|
7499
7621
|
try {
|
|
7500
|
-
len = statSync4(
|
|
7622
|
+
len = statSync4(join11(dir, name)).size;
|
|
7501
7623
|
} catch {
|
|
7502
7624
|
}
|
|
7503
7625
|
return { name, secs, pid, ts: Number.isFinite(secs) ? new Date(secs * 1e3).toISOString() : null, len };
|
|
@@ -7512,11 +7634,11 @@ app4.get("/directive", requireAdminSession, (c) => {
|
|
|
7512
7634
|
}
|
|
7513
7635
|
const dir = directiveStoreDir(sessionId);
|
|
7514
7636
|
if (!dir) return c.json({ error: "no account" }, 404);
|
|
7515
|
-
const path2 = resolve11(
|
|
7637
|
+
const path2 = resolve11(join11(dir, name));
|
|
7516
7638
|
if (!path2.startsWith(dir + sep3)) return c.json({ error: "bad reference" }, 400);
|
|
7517
7639
|
let body;
|
|
7518
7640
|
try {
|
|
7519
|
-
body =
|
|
7641
|
+
body = readFileSync12(path2, "utf8");
|
|
7520
7642
|
} catch {
|
|
7521
7643
|
return c.json({ error: "not found" }, 404);
|
|
7522
7644
|
}
|
|
@@ -7525,8 +7647,8 @@ app4.get("/directive", requireAdminSession, (c) => {
|
|
|
7525
7647
|
var whatsapp_reader_default = app4;
|
|
7526
7648
|
|
|
7527
7649
|
// server/routes/public-reader.ts
|
|
7528
|
-
import { statSync as statSync5, openSync as openSync2, readSync as readSync2, closeSync as closeSync2, watch as watch2 } from "fs";
|
|
7529
|
-
import { basename as basename3, dirname as dirname2, join as
|
|
7650
|
+
import { statSync as statSync5, openSync as openSync2, readSync as readSync2, closeSync as closeSync2, watch as watch2, readFileSync as readFileSync13, readdirSync as readdirSync8 } from "fs";
|
|
7651
|
+
import { basename as basename3, dirname as dirname2, join as join12, resolve as resolve12, sep as sep4 } from "path";
|
|
7530
7652
|
|
|
7531
7653
|
// app/lib/whatsapp-reader/delivered-kinds.ts
|
|
7532
7654
|
var PUBLIC_DELIVERED_KINDS = /* @__PURE__ */ new Set([
|
|
@@ -7563,6 +7685,13 @@ function resolveVisitorSession(rows, visitorPersonId) {
|
|
|
7563
7685
|
(best, r) => (r.startedAt ?? "") > (best.startedAt ?? "") ? r : best
|
|
7564
7686
|
);
|
|
7565
7687
|
}
|
|
7688
|
+
function resolveOpenVisitorSession(rows, visitorId, agentSlug) {
|
|
7689
|
+
const matches = rows.filter(
|
|
7690
|
+
(r) => r.role === "public" && r.channel === "webchat" && r.personId === null && r.visitorId === visitorId && r.agentSlug === agentSlug
|
|
7691
|
+
);
|
|
7692
|
+
if (matches.length === 0) return null;
|
|
7693
|
+
return matches.reduce((best, r) => (r.startedAt ?? "") > (best.startedAt ?? "") ? r : best);
|
|
7694
|
+
}
|
|
7566
7695
|
|
|
7567
7696
|
// server/routes/public-reader.ts
|
|
7568
7697
|
var app5 = new Hono();
|
|
@@ -7572,19 +7701,31 @@ function parseAccessSessionId(cookieHeader) {
|
|
|
7572
7701
|
const part = cookieHeader.split(";").map((p) => p.trim()).find((p) => p.startsWith("__access_session="));
|
|
7573
7702
|
return part ? part.slice("__access_session=".length) : null;
|
|
7574
7703
|
}
|
|
7704
|
+
var VISITOR_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
7705
|
+
function parseVisitorCookie(cookieHeader) {
|
|
7706
|
+
if (!cookieHeader) return null;
|
|
7707
|
+
const m = cookieHeader.match(/(?:^|;\s*)maxy_visitor=([^;]*)/);
|
|
7708
|
+
if (!m) return null;
|
|
7709
|
+
const v = decodeURIComponent(m[1]).trim();
|
|
7710
|
+
return VISITOR_UUID_RE.test(v) ? v : null;
|
|
7711
|
+
}
|
|
7575
7712
|
function resolveVisitor(c) {
|
|
7576
|
-
const
|
|
7577
|
-
const
|
|
7578
|
-
|
|
7713
|
+
const cookie = c.req.header("cookie");
|
|
7714
|
+
const grantId = parseAccessSessionId(cookie);
|
|
7715
|
+
const session = grantId ? getAccessSession(grantId) : null;
|
|
7716
|
+
if (session && session.personId) return { kind: "person", personId: session.personId };
|
|
7717
|
+
const visitorId = parseVisitorCookie(cookie);
|
|
7718
|
+
if (visitorId) return { kind: "visitor", visitorId };
|
|
7719
|
+
return null;
|
|
7579
7720
|
}
|
|
7580
7721
|
function enumeratePublicRows() {
|
|
7581
7722
|
const cfg = claudeConfigDir();
|
|
7582
7723
|
if (!cfg) return [];
|
|
7583
7724
|
const rows = [];
|
|
7584
|
-
for (const { path: path2 } of enumerateJsonls(
|
|
7725
|
+
for (const { path: path2 } of enumerateJsonls(join12(cfg, "projects"))) {
|
|
7585
7726
|
const sessionId = basename3(path2).replace(/\.jsonl$/, "");
|
|
7586
7727
|
const projectDir = dirname2(path2);
|
|
7587
|
-
const meta = readSidecarMeta(
|
|
7728
|
+
const meta = readSidecarMeta(join12(projectDir, `${sessionId}.meta.json`));
|
|
7588
7729
|
rows.push({
|
|
7589
7730
|
sessionId,
|
|
7590
7731
|
projectDir,
|
|
@@ -7592,7 +7733,9 @@ function enumeratePublicRows() {
|
|
|
7592
7733
|
channel: meta.channel,
|
|
7593
7734
|
personId: meta.personId,
|
|
7594
7735
|
senderId: meta.senderId,
|
|
7595
|
-
startedAt: meta.startedAt
|
|
7736
|
+
startedAt: meta.startedAt,
|
|
7737
|
+
visitorId: meta.visitorId,
|
|
7738
|
+
agentSlug: meta.agentSlug
|
|
7596
7739
|
});
|
|
7597
7740
|
}
|
|
7598
7741
|
return rows;
|
|
@@ -7600,18 +7743,43 @@ function enumeratePublicRows() {
|
|
|
7600
7743
|
app5.get("/session", (c) => {
|
|
7601
7744
|
const visitor = resolveVisitor(c);
|
|
7602
7745
|
if (!visitor) {
|
|
7603
|
-
console.error(`${TAG20} op=reader-refused reason=no-
|
|
7746
|
+
console.error(`${TAG20} op=reader-refused reason=no-anchor path=/session`);
|
|
7604
7747
|
return c.json({ error: "gate-required" }, 401);
|
|
7605
7748
|
}
|
|
7606
|
-
const
|
|
7749
|
+
const rows = enumeratePublicRows();
|
|
7750
|
+
let found = null;
|
|
7751
|
+
if (visitor.kind === "person") {
|
|
7752
|
+
found = resolveVisitorSession(rows, visitor.personId);
|
|
7753
|
+
} else {
|
|
7754
|
+
const account = resolveAccount();
|
|
7755
|
+
const agentParam = c.req.query("agent");
|
|
7756
|
+
let effectiveSlug;
|
|
7757
|
+
if (agentParam) {
|
|
7758
|
+
effectiveSlug = account && validateAgentSlug(agentParam) && isActiveAgentSlug(account.accountDir, agentParam) ? agentParam : null;
|
|
7759
|
+
} else {
|
|
7760
|
+
effectiveSlug = account ? resolveDefaultAgentSlug(account.accountDir) : null;
|
|
7761
|
+
}
|
|
7762
|
+
found = effectiveSlug ? resolveOpenVisitorSession(rows, visitor.visitorId, effectiveSlug) : null;
|
|
7763
|
+
}
|
|
7607
7764
|
if (found) {
|
|
7608
|
-
console.log(`${TAG20} op=session-resume key=${(found.senderId ?? "").slice(0, 8)} sessionId=${found.sessionId.slice(0, 8)}`);
|
|
7765
|
+
console.log(`${TAG20} op=session-resume anchor=${visitor.kind} key=${(found.senderId ?? "").slice(0, 8)} sessionId=${found.sessionId.slice(0, 8)}`);
|
|
7609
7766
|
return c.json({ sessionId: found.sessionId, projectDir: found.projectDir, sessionKey: found.senderId });
|
|
7610
7767
|
}
|
|
7611
7768
|
const sessionKey = crypto.randomUUID();
|
|
7612
|
-
console.log(`${TAG20} op=session-new key=${sessionKey.slice(0, 8)}`);
|
|
7769
|
+
console.log(`${TAG20} op=session-new anchor=${visitor.kind} key=${sessionKey.slice(0, 8)}`);
|
|
7613
7770
|
return c.json({ sessionId: null, projectDir: null, sessionKey });
|
|
7614
7771
|
});
|
|
7772
|
+
function publicUploadsDir(accountId, sessionId) {
|
|
7773
|
+
return resolve12(DATA_ROOT, "accounts", accountId, "uploads", "public", sessionId);
|
|
7774
|
+
}
|
|
7775
|
+
function enrichPublicAttachments(turns, sessionAttachments, cursor) {
|
|
7776
|
+
for (const turn of turns) {
|
|
7777
|
+
if (turn.kind !== "operator-inbound" || !turn.uploadCount) continue;
|
|
7778
|
+
const slice = sessionAttachments.slice(cursor.consumed, cursor.consumed + turn.uploadCount);
|
|
7779
|
+
cursor.consumed += turn.uploadCount;
|
|
7780
|
+
if (slice.length > 0) turn.attachments = slice;
|
|
7781
|
+
}
|
|
7782
|
+
}
|
|
7615
7783
|
function readFrom2(path2, from) {
|
|
7616
7784
|
const end = statSync5(path2).size;
|
|
7617
7785
|
if (end <= from) return { buf: Buffer.alloc(0), end: from };
|
|
@@ -7627,37 +7795,43 @@ function readFrom2(path2, from) {
|
|
|
7627
7795
|
app5.get("/stream", (c) => {
|
|
7628
7796
|
const visitor = resolveVisitor(c);
|
|
7629
7797
|
if (!visitor) {
|
|
7630
|
-
console.error(`${TAG20} op=reader-refused reason=no-
|
|
7798
|
+
console.error(`${TAG20} op=reader-refused reason=no-anchor path=/stream`);
|
|
7631
7799
|
return c.json({ error: "gate-required" }, 401);
|
|
7632
7800
|
}
|
|
7633
7801
|
const sessionId = c.req.query("sessionId") ?? "";
|
|
7634
7802
|
const projectDir = c.req.query("projectDir") ?? "";
|
|
7635
7803
|
const cfg = claudeConfigDir();
|
|
7636
|
-
const projectsRoot = cfg ? resolve12(
|
|
7804
|
+
const projectsRoot = cfg ? resolve12(join12(cfg, "projects")) : null;
|
|
7637
7805
|
if (!cfg || !projectsRoot || !SESSION_ID_RE2.test(sessionId)) {
|
|
7638
7806
|
return c.json({ error: "bad session reference" }, 400);
|
|
7639
7807
|
}
|
|
7640
|
-
const jsonlPath = resolve12(
|
|
7808
|
+
const jsonlPath = resolve12(join12(projectDir, `${sessionId}.jsonl`));
|
|
7641
7809
|
if (!jsonlPath.startsWith(projectsRoot + sep4)) {
|
|
7642
7810
|
return c.json({ error: "bad session reference" }, 400);
|
|
7643
7811
|
}
|
|
7644
|
-
const meta = readSidecarMeta(
|
|
7645
|
-
|
|
7646
|
-
|
|
7812
|
+
const meta = readSidecarMeta(join12(projectDir, `${sessionId}.meta.json`));
|
|
7813
|
+
const isPublicWebchat = meta.role === "public" && meta.channel === "webchat";
|
|
7814
|
+
const owns = visitor.kind === "person" ? meta.personId === visitor.personId : meta.personId === null && meta.visitorId === visitor.visitorId;
|
|
7815
|
+
if (!(isPublicWebchat && owns)) {
|
|
7816
|
+
console.error(`${TAG20} op=reader-refused reason=not-owner anchor=${visitor.kind} sessionId=${sessionId.slice(0, 8)}`);
|
|
7647
7817
|
return c.json({ error: "forbidden" }, 403);
|
|
7648
7818
|
}
|
|
7649
7819
|
const senderShort = (meta.senderId ?? "").slice(0, 8);
|
|
7650
7820
|
const encoder = new TextEncoder();
|
|
7651
|
-
console.log(`${TAG20} op=reader-open key=${senderShort} mode=delivered-only sessionId=${sessionId.slice(0, 8)}`);
|
|
7821
|
+
console.log(`${TAG20} op=reader-open anchor=${visitor.kind} key=${senderShort} mode=delivered-only sessionId=${sessionId.slice(0, 8)}`);
|
|
7652
7822
|
const send = (controller, turn, id) => controller.enqueue(encoder.encode(`id: ${id}
|
|
7653
7823
|
data: ${JSON.stringify(turn)}
|
|
7654
7824
|
|
|
7655
7825
|
`));
|
|
7826
|
+
const account = resolveAccount();
|
|
7827
|
+
const uploadsDir = account ? publicUploadsDir(account.accountId, sessionId) : null;
|
|
7828
|
+
const attachmentCursor = { consumed: 0 };
|
|
7656
7829
|
const readable = new ReadableStream({
|
|
7657
7830
|
start(controller) {
|
|
7658
7831
|
let offset = 0;
|
|
7659
7832
|
const emit = (lines) => {
|
|
7660
7833
|
const { turns, dropped } = filterDeliveredFrames(lines);
|
|
7834
|
+
if (uploadsDir) enrichPublicAttachments(turns, listSessionAttachmentsInDir(uploadsDir), attachmentCursor);
|
|
7661
7835
|
for (const turn of turns) send(controller, turn, offset);
|
|
7662
7836
|
if (dropped > 0) console.log(`${TAG20} op=reader-filtered key=${senderShort} dropped=${dropped}`);
|
|
7663
7837
|
};
|
|
@@ -7706,24 +7880,83 @@ data: ${JSON.stringify(turn)}
|
|
|
7706
7880
|
headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" }
|
|
7707
7881
|
});
|
|
7708
7882
|
});
|
|
7883
|
+
var ATT_TAG = "[public-attachment]";
|
|
7884
|
+
var ATTACHMENT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
7885
|
+
app5.get("/attachment/:attachmentId", (c) => {
|
|
7886
|
+
const visitor = resolveVisitor(c);
|
|
7887
|
+
const attachmentId = c.req.param("attachmentId");
|
|
7888
|
+
const sessionId = c.req.query("sessionId") ?? "";
|
|
7889
|
+
const ses = sessionId.slice(0, 8);
|
|
7890
|
+
const at = attachmentId.slice(0, 8);
|
|
7891
|
+
if (!visitor) {
|
|
7892
|
+
console.error(`${ATT_TAG} op=denied session=${ses} att=${at} reason=no-anchor`);
|
|
7893
|
+
return new Response("Unauthorized", { status: 401 });
|
|
7894
|
+
}
|
|
7895
|
+
console.log(`${ATT_TAG} op=request session=${ses} att=${at}`);
|
|
7896
|
+
if (!ATTACHMENT_ID_RE.test(attachmentId) || !SESSION_ID_RE2.test(sessionId)) {
|
|
7897
|
+
console.error(`${ATT_TAG} op=denied session=${ses} att=${at} reason=bad-id`);
|
|
7898
|
+
return new Response("Not found", { status: 404 });
|
|
7899
|
+
}
|
|
7900
|
+
const row = enumeratePublicRows().find((r) => r.sessionId === sessionId);
|
|
7901
|
+
const owns = row != null && row.role === "public" && row.channel === "webchat" && (visitor.kind === "person" ? row.personId === visitor.personId : row.personId === null && row.visitorId === visitor.visitorId);
|
|
7902
|
+
if (!owns) {
|
|
7903
|
+
console.error(`${ATT_TAG} op=denied session=${ses} att=${at} reason=foreign-session`);
|
|
7904
|
+
return new Response("Not found", { status: 404 });
|
|
7905
|
+
}
|
|
7906
|
+
const account = resolveAccount();
|
|
7907
|
+
if (!account) {
|
|
7908
|
+
console.error(`${ATT_TAG} op=denied session=${ses} att=${at} reason=not-found`);
|
|
7909
|
+
return new Response("Not found", { status: 404 });
|
|
7910
|
+
}
|
|
7911
|
+
const uploadsRoot = publicUploadsDir(account.accountId, sessionId);
|
|
7912
|
+
const dir = resolve12(uploadsRoot, attachmentId);
|
|
7913
|
+
if (!dir.startsWith(uploadsRoot + sep4)) {
|
|
7914
|
+
console.error(`${ATT_TAG} op=denied session=${ses} att=${at} reason=bad-id`);
|
|
7915
|
+
return new Response("Not found", { status: 404 });
|
|
7916
|
+
}
|
|
7917
|
+
const meta = readAttachmentMeta(dir);
|
|
7918
|
+
if (!meta) {
|
|
7919
|
+
console.error(`${ATT_TAG} op=denied session=${ses} att=${at} reason=not-found`);
|
|
7920
|
+
return new Response("Not found", { status: 404 });
|
|
7921
|
+
}
|
|
7922
|
+
let dataFile;
|
|
7923
|
+
let buffer;
|
|
7924
|
+
try {
|
|
7925
|
+
dataFile = readdirSync8(dir).find((f) => !f.endsWith(".meta.json"));
|
|
7926
|
+
if (!dataFile) throw new Error("no data file");
|
|
7927
|
+
buffer = readFileSync13(resolve12(dir, dataFile));
|
|
7928
|
+
} catch {
|
|
7929
|
+
console.error(`${ATT_TAG} op=denied session=${ses} att=${at} reason=not-found`);
|
|
7930
|
+
return new Response("Not found", { status: 404 });
|
|
7931
|
+
}
|
|
7932
|
+
console.log(`${ATT_TAG} op=served session=${ses} att=${at} bytes=${buffer.length} mime=${meta.mimeType}`);
|
|
7933
|
+
const safeName = meta.filename.replace(/["\\\r\n]/g, "_");
|
|
7934
|
+
return new Response(new Uint8Array(buffer), {
|
|
7935
|
+
headers: {
|
|
7936
|
+
"Content-Type": meta.mimeType,
|
|
7937
|
+
"Content-Disposition": `inline; filename="${safeName}"`,
|
|
7938
|
+
"Cache-Control": "private, max-age=3600"
|
|
7939
|
+
}
|
|
7940
|
+
});
|
|
7941
|
+
});
|
|
7709
7942
|
var public_reader_default = app5;
|
|
7710
7943
|
|
|
7711
7944
|
// server/routes/webchat.ts
|
|
7712
7945
|
import { basename as basename4, dirname as dirname3 } from "path";
|
|
7713
|
-
import { join as
|
|
7714
|
-
import { existsSync as existsSync9, readdirSync as
|
|
7946
|
+
import { join as join15 } from "path";
|
|
7947
|
+
import { existsSync as existsSync9, readdirSync as readdirSync10, readFileSync as readFileSync16, renameSync as renameSync3, statSync as statSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
7715
7948
|
|
|
7716
7949
|
// server/canonical-webchat-override.ts
|
|
7717
|
-
import { readFileSync as
|
|
7718
|
-
import { join as
|
|
7950
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync5, renameSync } from "fs";
|
|
7951
|
+
import { join as join13 } from "path";
|
|
7719
7952
|
var FILE = "canonical-webchat-session.json";
|
|
7720
7953
|
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}$/;
|
|
7721
7954
|
function canonicalOverridePath(accountDir) {
|
|
7722
|
-
return
|
|
7955
|
+
return join13(accountDir, FILE);
|
|
7723
7956
|
}
|
|
7724
7957
|
function readCanonicalOverrideId(accountDir) {
|
|
7725
7958
|
try {
|
|
7726
|
-
const raw = JSON.parse(
|
|
7959
|
+
const raw = JSON.parse(readFileSync14(canonicalOverridePath(accountDir), "utf8"));
|
|
7727
7960
|
return typeof raw.sessionId === "string" && UUID.test(raw.sessionId) ? raw.sessionId : null;
|
|
7728
7961
|
} catch {
|
|
7729
7962
|
return null;
|
|
@@ -7749,8 +7982,8 @@ function adminSessionIdFor(accountId, senderId, personId) {
|
|
|
7749
7982
|
}
|
|
7750
7983
|
|
|
7751
7984
|
// ../services/claude-session-manager/src/sidecar-store.ts
|
|
7752
|
-
import { existsSync as existsSync8, mkdirSync as mkdirSync2, readdirSync as
|
|
7753
|
-
import { join as
|
|
7985
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync2, readdirSync as readdirSync9, readFileSync as readFileSync15, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync6 } from "fs";
|
|
7986
|
+
import { join as join14 } from "path";
|
|
7754
7987
|
function escapeRegExp(s) {
|
|
7755
7988
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7756
7989
|
}
|
|
@@ -7758,7 +7991,7 @@ function createSidecarStore(config) {
|
|
|
7758
7991
|
const { suffix, validate: validate2 } = config;
|
|
7759
7992
|
const suffixRe = new RegExp(`${escapeRegExp(suffix)}$`);
|
|
7760
7993
|
function pathFor(sessionsDir, id) {
|
|
7761
|
-
return
|
|
7994
|
+
return join14(sessionsDir, `${id}${suffix}`);
|
|
7762
7995
|
}
|
|
7763
7996
|
function write(sessionsDir, id, record) {
|
|
7764
7997
|
mkdirSync2(sessionsDir, { recursive: true });
|
|
@@ -7779,7 +8012,7 @@ function createSidecarStore(config) {
|
|
|
7779
8012
|
function readFileAt(path2) {
|
|
7780
8013
|
let raw;
|
|
7781
8014
|
try {
|
|
7782
|
-
raw =
|
|
8015
|
+
raw = readFileSync15(path2, "utf8");
|
|
7783
8016
|
} catch {
|
|
7784
8017
|
return null;
|
|
7785
8018
|
}
|
|
@@ -7795,17 +8028,17 @@ function createSidecarStore(config) {
|
|
|
7795
8028
|
function readAll(sessionsDir, onSkip) {
|
|
7796
8029
|
let names;
|
|
7797
8030
|
try {
|
|
7798
|
-
names =
|
|
8031
|
+
names = readdirSync9(sessionsDir, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name);
|
|
7799
8032
|
} catch {
|
|
7800
8033
|
return [];
|
|
7801
8034
|
}
|
|
7802
8035
|
const out = [];
|
|
7803
8036
|
for (const name of names) {
|
|
7804
8037
|
if (!suffixRe.test(name)) continue;
|
|
7805
|
-
const path2 =
|
|
8038
|
+
const path2 = join14(sessionsDir, name);
|
|
7806
8039
|
let raw;
|
|
7807
8040
|
try {
|
|
7808
|
-
raw =
|
|
8041
|
+
raw = readFileSync15(path2, "utf8");
|
|
7809
8042
|
} catch {
|
|
7810
8043
|
onSkip?.(name, "unreadable");
|
|
7811
8044
|
continue;
|
|
@@ -7879,10 +8112,10 @@ var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9
|
|
|
7879
8112
|
function locateSession(sessionId) {
|
|
7880
8113
|
const cfg = claudeConfigDir();
|
|
7881
8114
|
if (!cfg) return { projectDir: null, channel: null };
|
|
7882
|
-
for (const { path: path2 } of enumerateJsonls(
|
|
8115
|
+
for (const { path: path2 } of enumerateJsonls(join15(cfg, "projects"))) {
|
|
7883
8116
|
if (basename4(path2) === `${sessionId}.jsonl`) {
|
|
7884
8117
|
const dir = dirname3(path2);
|
|
7885
|
-
const meta = readSidecarMeta(
|
|
8118
|
+
const meta = readSidecarMeta(join15(dir, `${sessionId}.meta.json`));
|
|
7886
8119
|
return { projectDir: dir, channel: meta.channel };
|
|
7887
8120
|
}
|
|
7888
8121
|
}
|
|
@@ -7891,15 +8124,15 @@ function locateSession(sessionId) {
|
|
|
7891
8124
|
function locateSidecar(sessionId) {
|
|
7892
8125
|
const cfg = claudeConfigDir();
|
|
7893
8126
|
if (!cfg) return null;
|
|
7894
|
-
const projectsRoot =
|
|
8127
|
+
const projectsRoot = join15(cfg, "projects");
|
|
7895
8128
|
let slugs;
|
|
7896
8129
|
try {
|
|
7897
|
-
slugs =
|
|
8130
|
+
slugs = readdirSync10(projectsRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
7898
8131
|
} catch {
|
|
7899
8132
|
return null;
|
|
7900
8133
|
}
|
|
7901
8134
|
for (const slug of slugs) {
|
|
7902
|
-
const metaPath =
|
|
8135
|
+
const metaPath = join15(projectsRoot, slug, `${sessionId}.meta.json`);
|
|
7903
8136
|
if (existsSync9(metaPath)) {
|
|
7904
8137
|
return { channel: readSidecarMeta(metaPath).channel };
|
|
7905
8138
|
}
|
|
@@ -7912,7 +8145,7 @@ function sessionSidecarExists(sessionId) {
|
|
|
7912
8145
|
function jsonlSizeBytes(projectDir, sessionId) {
|
|
7913
8146
|
if (projectDir === null) return null;
|
|
7914
8147
|
try {
|
|
7915
|
-
return statSync6(
|
|
8148
|
+
return statSync6(join15(projectDir, `${sessionId}.jsonl`)).size;
|
|
7916
8149
|
} catch {
|
|
7917
8150
|
return null;
|
|
7918
8151
|
}
|
|
@@ -7981,10 +8214,10 @@ function latestAdminWebchatSessionId() {
|
|
|
7981
8214
|
const cfg = claudeConfigDir();
|
|
7982
8215
|
if (!cfg) return null;
|
|
7983
8216
|
let best = null;
|
|
7984
|
-
for (const { path: path2, isSubagent, archived } of enumerateJsonls(
|
|
8217
|
+
for (const { path: path2, isSubagent, archived } of enumerateJsonls(join15(cfg, "projects"))) {
|
|
7985
8218
|
if (isSubagent || archived) continue;
|
|
7986
8219
|
const id = basename4(path2).slice(0, -".jsonl".length);
|
|
7987
|
-
const meta = readSidecarMeta(
|
|
8220
|
+
const meta = readSidecarMeta(join15(dirname3(path2), `${id}.meta.json`));
|
|
7988
8221
|
if (meta.role !== "admin" || meta.channel !== "webchat") continue;
|
|
7989
8222
|
let mtimeMs;
|
|
7990
8223
|
try {
|
|
@@ -8213,9 +8446,9 @@ ${note}` : note;
|
|
|
8213
8446
|
console.error(`[webchat:settings] op=${op} outcome=refused value=${value} reason=account-unresolved`);
|
|
8214
8447
|
return c.json({ error: "account unresolved" }, 503);
|
|
8215
8448
|
}
|
|
8216
|
-
const accountJsonPath =
|
|
8449
|
+
const accountJsonPath = join15(account.accountDir, "account.json");
|
|
8217
8450
|
try {
|
|
8218
|
-
const parsed = JSON.parse(
|
|
8451
|
+
const parsed = JSON.parse(readFileSync16(accountJsonPath, "utf8"));
|
|
8219
8452
|
if (op === "model") parsed.adminModel = value;
|
|
8220
8453
|
else if (op === "effort") parsed.effort = value;
|
|
8221
8454
|
else parsed.adminPermissionMode = value;
|
|
@@ -8252,7 +8485,7 @@ ${note}` : note;
|
|
|
8252
8485
|
const cfg2 = claudeConfigDir();
|
|
8253
8486
|
if (cfg2) {
|
|
8254
8487
|
const source2 = resolveBridgeSource(
|
|
8255
|
-
|
|
8488
|
+
join15(cfg2, "sessions"),
|
|
8256
8489
|
target,
|
|
8257
8490
|
(id) => locateSession(id).projectDir !== null
|
|
8258
8491
|
);
|
|
@@ -8281,7 +8514,7 @@ ${note}` : note;
|
|
|
8281
8514
|
const cfg = claudeConfigDir();
|
|
8282
8515
|
let projectDir = null;
|
|
8283
8516
|
if (cfg) {
|
|
8284
|
-
for (const { path: path2 } of enumerateJsonls(
|
|
8517
|
+
for (const { path: path2 } of enumerateJsonls(join15(cfg, "projects"))) {
|
|
8285
8518
|
if (basename4(path2) === `${sessionId}.jsonl`) {
|
|
8286
8519
|
projectDir = dirname3(path2);
|
|
8287
8520
|
break;
|
|
@@ -8298,8 +8531,8 @@ ${note}` : note;
|
|
|
8298
8531
|
import { resolve as resolve13 } from "path";
|
|
8299
8532
|
|
|
8300
8533
|
// app/lib/claude-agent/specialist-roster.ts
|
|
8301
|
-
import { existsSync as existsSync10, readFileSync as
|
|
8302
|
-
import { join as
|
|
8534
|
+
import { existsSync as existsSync10, readFileSync as readFileSync17, readdirSync as readdirSync11 } from "fs";
|
|
8535
|
+
import { join as join16 } from "path";
|
|
8303
8536
|
function field(fm, name) {
|
|
8304
8537
|
const m = fm.match(new RegExp(`^${name}:\\s*(.*?)\\r?$`, "m"));
|
|
8305
8538
|
if (!m) return null;
|
|
@@ -8311,14 +8544,14 @@ function field(fm, name) {
|
|
|
8311
8544
|
}
|
|
8312
8545
|
function readSpecialistRoster(specialistsDir) {
|
|
8313
8546
|
if (!existsSync10(specialistsDir)) return { specialists: [], skipped: [] };
|
|
8314
|
-
const entries =
|
|
8547
|
+
const entries = readdirSync11(specialistsDir);
|
|
8315
8548
|
const specialists = [];
|
|
8316
8549
|
const skipped = [];
|
|
8317
8550
|
for (const file of entries.sort()) {
|
|
8318
8551
|
if (!file.endsWith(".md")) continue;
|
|
8319
8552
|
let raw;
|
|
8320
8553
|
try {
|
|
8321
|
-
raw =
|
|
8554
|
+
raw = readFileSync17(join16(specialistsDir, file), "utf-8");
|
|
8322
8555
|
} catch (err) {
|
|
8323
8556
|
skipped.push({ file, reason: err instanceof Error ? err.message : String(err) });
|
|
8324
8557
|
continue;
|
|
@@ -8435,11 +8668,11 @@ var webchat_greeting_default = app6;
|
|
|
8435
8668
|
|
|
8436
8669
|
// server/routes/telegram.ts
|
|
8437
8670
|
import { homedir } from "os";
|
|
8438
|
-
import { resolve as
|
|
8439
|
-
import { existsSync as existsSync11, readFileSync as
|
|
8671
|
+
import { resolve as resolve17, join as join17 } from "path";
|
|
8672
|
+
import { existsSync as existsSync11, readFileSync as readFileSync18 } from "fs";
|
|
8440
8673
|
|
|
8441
8674
|
// app/lib/channel-pty-bridge/bridge.ts
|
|
8442
|
-
import { resolve as
|
|
8675
|
+
import { resolve as resolve16 } from "path";
|
|
8443
8676
|
|
|
8444
8677
|
// app/lib/channel-pty-bridge/manager-client.ts
|
|
8445
8678
|
function managerBase2() {
|
|
@@ -8470,6 +8703,7 @@ async function managerSpawn(opts) {
|
|
|
8470
8703
|
specialistDomains: opts.specialistDomains,
|
|
8471
8704
|
sliceToken: opts.sliceToken,
|
|
8472
8705
|
personId: opts.personId,
|
|
8706
|
+
visitorId: opts.visitorId,
|
|
8473
8707
|
name: opts.name,
|
|
8474
8708
|
webchatChannel: opts.webchatChannel
|
|
8475
8709
|
})
|
|
@@ -8878,81 +9112,114 @@ async function fanOut(subscribers, text, onError, tag) {
|
|
|
8878
9112
|
);
|
|
8879
9113
|
}
|
|
8880
9114
|
|
|
9115
|
+
// app/lib/channel-pty-bridge/file-delivery.ts
|
|
9116
|
+
var SEND_USER_FILE = "SendUserFile";
|
|
9117
|
+
function makeFileDelivery(opts) {
|
|
9118
|
+
const { entry, tag, channel, sendFile } = opts;
|
|
9119
|
+
let failedFiles = [];
|
|
9120
|
+
let attempts = 0;
|
|
9121
|
+
return {
|
|
9122
|
+
isFileDeliveryTool(toolName) {
|
|
9123
|
+
return toolName === SEND_USER_FILE;
|
|
9124
|
+
},
|
|
9125
|
+
async onFileToolUse(use) {
|
|
9126
|
+
if (use.toolName !== SEND_USER_FILE) return;
|
|
9127
|
+
const input = use.input ?? {};
|
|
9128
|
+
const files = Array.isArray(input.files) ? input.files.filter((f) => typeof f === "string") : [];
|
|
9129
|
+
const caption = typeof input.caption === "string" ? input.caption : void 0;
|
|
9130
|
+
for (let i = 0; i < files.length; i++) {
|
|
9131
|
+
attempts++;
|
|
9132
|
+
const result = await sendFile(files[i], i === 0 ? caption : void 0);
|
|
9133
|
+
const name = files[i].split("/").pop() ?? files[i];
|
|
9134
|
+
console.error(`${tag} op=file-forward channel=${channel} file=${name} outcome=${result.ok ? "ok" : "fail"}`);
|
|
9135
|
+
if (!result.ok) failedFiles.push(files[i]);
|
|
9136
|
+
}
|
|
9137
|
+
},
|
|
9138
|
+
onTurnEnd(firedTools) {
|
|
9139
|
+
const failed = failedFiles;
|
|
9140
|
+
const tried = attempts;
|
|
9141
|
+
failedFiles = [];
|
|
9142
|
+
attempts = 0;
|
|
9143
|
+
const sid = entry.sessionId.slice(0, 8);
|
|
9144
|
+
for (const file of failed) {
|
|
9145
|
+
console.error(
|
|
9146
|
+
`${tag} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${SEND_USER_FILE} file=${file}`
|
|
9147
|
+
);
|
|
9148
|
+
}
|
|
9149
|
+
if (firedTools.includes(SEND_USER_FILE) && tried === 0) {
|
|
9150
|
+
console.error(
|
|
9151
|
+
`${tag} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${SEND_USER_FILE}`
|
|
9152
|
+
);
|
|
9153
|
+
}
|
|
9154
|
+
}
|
|
9155
|
+
};
|
|
9156
|
+
}
|
|
9157
|
+
|
|
8881
9158
|
// app/lib/whatsapp/inbound/file-delivery-bridge.ts
|
|
8882
9159
|
var TAG22 = "[whatsapp-adaptor]";
|
|
8883
|
-
var
|
|
9160
|
+
var SEND_USER_FILE2 = "SendUserFile";
|
|
8884
9161
|
var WHATSAPP_SEND_DOCUMENT = "whatsapp-send-document";
|
|
8885
9162
|
function platformRoot() {
|
|
8886
9163
|
return process.env.MAXY_PLATFORM_ROOT || "";
|
|
8887
9164
|
}
|
|
9165
|
+
function makeWhatsAppSendFile(entry) {
|
|
9166
|
+
return async (filePath, caption) => {
|
|
9167
|
+
let maxyAccountId;
|
|
9168
|
+
try {
|
|
9169
|
+
maxyAccountId = resolvePlatformAccountId();
|
|
9170
|
+
} catch (err) {
|
|
9171
|
+
console.error(
|
|
9172
|
+
`${TAG22} file-delivery reject reason=account-unresolved sender=${entry.senderId} message=${err instanceof Error ? err.message : String(err)}`
|
|
9173
|
+
);
|
|
9174
|
+
return { ok: false, error: "account-unresolved" };
|
|
9175
|
+
}
|
|
9176
|
+
const result = await sendWhatsAppDocument({
|
|
9177
|
+
to: entry.senderId,
|
|
9178
|
+
filePath,
|
|
9179
|
+
caption,
|
|
9180
|
+
accountId: entry.accountId,
|
|
9181
|
+
maxyAccountId,
|
|
9182
|
+
platformRoot: platformRoot()
|
|
9183
|
+
});
|
|
9184
|
+
if (result.ok) return { ok: true };
|
|
9185
|
+
console.error(
|
|
9186
|
+
`${TAG22} file-delivery reject reason=send-failed sender=${entry.senderId} status=${result.status} message=${result.error}`
|
|
9187
|
+
);
|
|
9188
|
+
return { ok: false, error: result.error };
|
|
9189
|
+
};
|
|
9190
|
+
}
|
|
8888
9191
|
function makeWhatsAppFileDelivery(entry) {
|
|
9192
|
+
const shared = makeFileDelivery({
|
|
9193
|
+
entry,
|
|
9194
|
+
tag: TAG22,
|
|
9195
|
+
channel: "whatsapp",
|
|
9196
|
+
sendFile: makeWhatsAppSendFile(entry)
|
|
9197
|
+
});
|
|
8889
9198
|
let turnStartedAt = null;
|
|
8890
|
-
let failedFiles = [];
|
|
8891
|
-
let sendUserFileAttempts = 0;
|
|
8892
9199
|
let routeCalls = [];
|
|
8893
9200
|
return {
|
|
8894
9201
|
isFileDeliveryTool(toolName) {
|
|
8895
|
-
return toolName ===
|
|
9202
|
+
return toolName === SEND_USER_FILE2 || toolName === WHATSAPP_SEND_DOCUMENT;
|
|
8896
9203
|
},
|
|
8897
9204
|
async onFileToolUse(use) {
|
|
8898
9205
|
if (turnStartedAt === null) turnStartedAt = Date.now();
|
|
8899
9206
|
if (use.toolName === WHATSAPP_SEND_DOCUMENT) {
|
|
8900
|
-
const
|
|
9207
|
+
const input = use.input ?? {};
|
|
8901
9208
|
routeCalls.push({
|
|
8902
|
-
to: typeof
|
|
8903
|
-
filePath: typeof
|
|
9209
|
+
to: typeof input.to === "string" ? input.to : void 0,
|
|
9210
|
+
filePath: typeof input.filePath === "string" ? input.filePath : void 0
|
|
8904
9211
|
});
|
|
8905
9212
|
return;
|
|
8906
9213
|
}
|
|
8907
|
-
|
|
8908
|
-
const input = use.input ?? {};
|
|
8909
|
-
const files = Array.isArray(input.files) ? input.files.filter((f) => typeof f === "string") : [];
|
|
8910
|
-
const caption = typeof input.caption === "string" ? input.caption : void 0;
|
|
8911
|
-
if (files.length === 0) return;
|
|
8912
|
-
let maxyAccountId;
|
|
8913
|
-
try {
|
|
8914
|
-
maxyAccountId = resolvePlatformAccountId();
|
|
8915
|
-
} catch (err) {
|
|
8916
|
-
console.error(
|
|
8917
|
-
`${TAG22} file-delivery reject reason=account-unresolved sender=${entry.senderId} message=${err instanceof Error ? err.message : String(err)}`
|
|
8918
|
-
);
|
|
8919
|
-
return;
|
|
8920
|
-
}
|
|
8921
|
-
for (let i = 0; i < files.length; i++) {
|
|
8922
|
-
sendUserFileAttempts++;
|
|
8923
|
-
const result = await sendWhatsAppDocument({
|
|
8924
|
-
to: entry.senderId,
|
|
8925
|
-
filePath: files[i],
|
|
8926
|
-
caption: i === 0 ? caption : void 0,
|
|
8927
|
-
accountId: entry.accountId,
|
|
8928
|
-
maxyAccountId,
|
|
8929
|
-
platformRoot: platformRoot()
|
|
8930
|
-
});
|
|
8931
|
-
if (!result.ok) {
|
|
8932
|
-
failedFiles.push(files[i]);
|
|
8933
|
-
console.error(
|
|
8934
|
-
`${TAG22} file-delivery reject reason=send-failed sender=${entry.senderId} status=${result.status} message=${result.error}`
|
|
8935
|
-
);
|
|
8936
|
-
}
|
|
8937
|
-
}
|
|
9214
|
+
await shared.onFileToolUse(use);
|
|
8938
9215
|
},
|
|
8939
9216
|
onTurnEnd(firedTools) {
|
|
8940
9217
|
const startedAt = turnStartedAt ?? 0;
|
|
8941
|
-
const failed = failedFiles;
|
|
8942
|
-
const attempts = sendUserFileAttempts;
|
|
8943
9218
|
const routes = routeCalls;
|
|
8944
9219
|
turnStartedAt = null;
|
|
8945
|
-
failedFiles = [];
|
|
8946
|
-
sendUserFileAttempts = 0;
|
|
8947
9220
|
routeCalls = [];
|
|
8948
9221
|
const sid = entry.sessionId.slice(0, 8);
|
|
8949
|
-
|
|
8950
|
-
console.error(
|
|
8951
|
-
`${TAG22} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${SEND_USER_FILE} file=${file}`
|
|
8952
|
-
);
|
|
8953
|
-
}
|
|
8954
|
-
const okAt = documentOutboundAt(entry.senderId);
|
|
8955
|
-
const documentWentOut = okAt !== void 0 && okAt >= startedAt;
|
|
9222
|
+
shared.onTurnEnd(firedTools);
|
|
8956
9223
|
for (const call of routes) {
|
|
8957
9224
|
const routeAt = call.to !== void 0 && call.filePath !== void 0 ? routeDocumentOutboundAt(call.to, call.filePath) : void 0;
|
|
8958
9225
|
const delivered = routeAt !== void 0 && routeAt >= startedAt;
|
|
@@ -8963,21 +9230,186 @@ function makeWhatsAppFileDelivery(entry) {
|
|
|
8963
9230
|
);
|
|
8964
9231
|
}
|
|
8965
9232
|
}
|
|
8966
|
-
|
|
8967
|
-
|
|
8968
|
-
|
|
8969
|
-
|
|
9233
|
+
}
|
|
9234
|
+
};
|
|
9235
|
+
}
|
|
9236
|
+
|
|
9237
|
+
// app/lib/telegram/outbound/send-document.ts
|
|
9238
|
+
import { realpathSync as realpathSync4 } from "fs";
|
|
9239
|
+
import { readFile as readFile2, stat as stat3 } from "fs/promises";
|
|
9240
|
+
import { resolve as resolve14, basename as basename5 } from "path";
|
|
9241
|
+
var TAG23 = "[telegram:outbound]";
|
|
9242
|
+
var TELEGRAM_DOCUMENT_MAX_BYTES = 50 * 1024 * 1024;
|
|
9243
|
+
async function sendTelegramDocument(input) {
|
|
9244
|
+
const { botToken, chatId, filePath, caption, maxyAccountId, platformRoot: platformRoot4 } = input;
|
|
9245
|
+
if (!botToken || !filePath) {
|
|
9246
|
+
return { ok: false, status: 400, error: "Missing required fields: botToken, filePath" };
|
|
9247
|
+
}
|
|
9248
|
+
if (!maxyAccountId || !platformRoot4) {
|
|
9249
|
+
return { ok: false, status: 400, error: "Cannot validate file path: missing account or platform context" };
|
|
9250
|
+
}
|
|
9251
|
+
const accountDir = resolve14(platformRoot4, "..", "data/accounts", maxyAccountId);
|
|
9252
|
+
let resolvedPath;
|
|
9253
|
+
try {
|
|
9254
|
+
resolvedPath = realpathSync4(filePath);
|
|
9255
|
+
const accountResolved = realpathSync4(accountDir);
|
|
9256
|
+
if (!resolvedPath.startsWith(accountResolved + "/")) {
|
|
9257
|
+
console.error(`${TAG23} document REJECTED reason=outside_account_directory`);
|
|
9258
|
+
return { ok: false, status: 403, error: "Access denied: file is outside the account directory" };
|
|
9259
|
+
}
|
|
9260
|
+
} catch (err) {
|
|
9261
|
+
const code = err.code;
|
|
9262
|
+
if (code === "ENOENT") {
|
|
9263
|
+
console.error(`${TAG23} document ENOENT path=${filePath}`);
|
|
9264
|
+
return { ok: false, status: 404, error: `File not found: ${filePath}` };
|
|
9265
|
+
}
|
|
9266
|
+
console.error(`${TAG23} document path error: ${String(err)}`);
|
|
9267
|
+
return { ok: false, status: 500, error: String(err) };
|
|
9268
|
+
}
|
|
9269
|
+
const fileStat = await stat3(resolvedPath);
|
|
9270
|
+
if (fileStat.size > TELEGRAM_DOCUMENT_MAX_BYTES) {
|
|
9271
|
+
return {
|
|
9272
|
+
ok: false,
|
|
9273
|
+
status: 400,
|
|
9274
|
+
error: `File exceeds 50 MB limit (${(fileStat.size / 1024 / 1024).toFixed(1)} MB)`
|
|
9275
|
+
};
|
|
9276
|
+
}
|
|
9277
|
+
const filename = basename5(resolvedPath);
|
|
9278
|
+
const buffer = Buffer.from(await readFile2(resolvedPath));
|
|
9279
|
+
const form = new FormData();
|
|
9280
|
+
form.append("chat_id", String(chatId));
|
|
9281
|
+
if (caption) form.append("caption", caption);
|
|
9282
|
+
form.append("document", new File([buffer], filename, { type: detectMimeType(resolvedPath) }));
|
|
9283
|
+
let ok = false;
|
|
9284
|
+
let messageId;
|
|
9285
|
+
let error = "send failed";
|
|
9286
|
+
try {
|
|
9287
|
+
const res = await fetch(`https://api.telegram.org/bot${botToken}/sendDocument`, {
|
|
9288
|
+
method: "POST",
|
|
9289
|
+
body: form
|
|
9290
|
+
});
|
|
9291
|
+
const data = await res.json();
|
|
9292
|
+
ok = data.ok;
|
|
9293
|
+
messageId = data.result?.message_id;
|
|
9294
|
+
if (!data.ok) error = data.description ?? "Unknown Telegram error";
|
|
9295
|
+
} catch (e) {
|
|
9296
|
+
error = e instanceof Error ? e.message : String(e);
|
|
9297
|
+
}
|
|
9298
|
+
console.error(
|
|
9299
|
+
`${TAG23} sent document to=${chatId} file=${filename} bytes=${fileStat.size} ok=${ok}` + (messageId !== void 0 ? ` id=${messageId}` : "")
|
|
9300
|
+
);
|
|
9301
|
+
return ok ? { ok: true, messageId } : { ok: false, status: 500, error };
|
|
9302
|
+
}
|
|
9303
|
+
|
|
9304
|
+
// app/lib/telegram/outbound/file-delivery.ts
|
|
9305
|
+
var TAG24 = "[telegram:outbound]";
|
|
9306
|
+
function platformRoot2() {
|
|
9307
|
+
return process.env.MAXY_PLATFORM_ROOT || "";
|
|
9308
|
+
}
|
|
9309
|
+
function makeTelegramSendFile(entry) {
|
|
9310
|
+
let botToken;
|
|
9311
|
+
return async (filePath, caption) => {
|
|
9312
|
+
if (botToken === void 0) {
|
|
9313
|
+
const tg = resolveAccount()?.config.telegram;
|
|
9314
|
+
botToken = (entry.role === "admin" ? tg?.adminBotToken : tg?.publicBotToken) ?? null;
|
|
9315
|
+
}
|
|
9316
|
+
if (!botToken) {
|
|
9317
|
+
console.error(`${TAG24} file-delivery reject reason=no-bot-token sender=${entry.senderId} role=${entry.role}`);
|
|
9318
|
+
return { ok: false, error: "no-bot-token" };
|
|
9319
|
+
}
|
|
9320
|
+
if (entry.replyTarget == null) {
|
|
9321
|
+
console.error(`${TAG24} file-delivery reject reason=no-reply-target sender=${entry.senderId} role=${entry.role}`);
|
|
9322
|
+
return { ok: false, error: "no-reply-target" };
|
|
9323
|
+
}
|
|
9324
|
+
let maxyAccountId;
|
|
9325
|
+
try {
|
|
9326
|
+
maxyAccountId = resolvePlatformAccountId();
|
|
9327
|
+
} catch (err) {
|
|
9328
|
+
console.error(
|
|
9329
|
+
`${TAG24} file-delivery reject reason=account-unresolved sender=${entry.senderId} message=${err instanceof Error ? err.message : String(err)}`
|
|
9330
|
+
);
|
|
9331
|
+
return { ok: false, error: "account-unresolved" };
|
|
9332
|
+
}
|
|
9333
|
+
const result = await sendTelegramDocument({
|
|
9334
|
+
botToken,
|
|
9335
|
+
chatId: Number(entry.replyTarget),
|
|
9336
|
+
filePath,
|
|
9337
|
+
caption,
|
|
9338
|
+
maxyAccountId,
|
|
9339
|
+
platformRoot: platformRoot2()
|
|
9340
|
+
});
|
|
9341
|
+
return result.ok ? { ok: true } : { ok: false, error: result.error };
|
|
9342
|
+
};
|
|
9343
|
+
}
|
|
9344
|
+
function makeTelegramFileDelivery(entry) {
|
|
9345
|
+
return makeFileDelivery({
|
|
9346
|
+
entry,
|
|
9347
|
+
tag: TAG24,
|
|
9348
|
+
channel: "telegram",
|
|
9349
|
+
sendFile: makeTelegramSendFile(entry)
|
|
9350
|
+
});
|
|
9351
|
+
}
|
|
9352
|
+
|
|
9353
|
+
// app/lib/webchat/file-delivery.ts
|
|
9354
|
+
import { realpathSync as realpathSync5 } from "fs";
|
|
9355
|
+
import { resolve as resolve15 } from "path";
|
|
9356
|
+
var TAG25 = "[webchat-adaptor]";
|
|
9357
|
+
function platformRoot3() {
|
|
9358
|
+
return process.env.MAXY_PLATFORM_ROOT || "";
|
|
9359
|
+
}
|
|
9360
|
+
function makeWebchatSendFile(entry) {
|
|
9361
|
+
return async (filePath) => {
|
|
9362
|
+
let maxyAccountId;
|
|
9363
|
+
try {
|
|
9364
|
+
maxyAccountId = resolvePlatformAccountId();
|
|
9365
|
+
} catch (err) {
|
|
9366
|
+
console.error(
|
|
9367
|
+
`${TAG25} file-delivery reject reason=account-unresolved sender=${entry.senderId} message=${err instanceof Error ? err.message : String(err)}`
|
|
9368
|
+
);
|
|
9369
|
+
return { ok: false, error: "account-unresolved" };
|
|
9370
|
+
}
|
|
9371
|
+
const accountDir = resolve15(platformRoot3(), "..", "data/accounts", maxyAccountId);
|
|
9372
|
+
try {
|
|
9373
|
+
const resolved = realpathSync5(filePath);
|
|
9374
|
+
const accountResolved = realpathSync5(accountDir);
|
|
9375
|
+
if (!resolved.startsWith(accountResolved + "/")) {
|
|
9376
|
+
console.error(`${TAG25} file-delivery reject reason=outside_account_directory sender=${entry.senderId}`);
|
|
9377
|
+
return { ok: false, error: "outside-account" };
|
|
8970
9378
|
}
|
|
9379
|
+
return { ok: true };
|
|
9380
|
+
} catch (err) {
|
|
9381
|
+
const code = err.code;
|
|
9382
|
+
console.error(
|
|
9383
|
+
`${TAG25} file-delivery reject reason=${code === "ENOENT" ? "not-found" : "path-error"} sender=${entry.senderId}`
|
|
9384
|
+
);
|
|
9385
|
+
return { ok: false, error: code === "ENOENT" ? "not-found" : "path-error" };
|
|
8971
9386
|
}
|
|
8972
9387
|
};
|
|
8973
9388
|
}
|
|
9389
|
+
function makeWebchatFileDelivery(entry) {
|
|
9390
|
+
return makeFileDelivery({
|
|
9391
|
+
entry,
|
|
9392
|
+
tag: TAG25,
|
|
9393
|
+
channel: "webchat",
|
|
9394
|
+
sendFile: makeWebchatSendFile(entry)
|
|
9395
|
+
});
|
|
9396
|
+
}
|
|
8974
9397
|
|
|
8975
9398
|
// app/lib/channel-pty-bridge/bridge.ts
|
|
8976
9399
|
function tagFor(channel) {
|
|
8977
9400
|
return `[${channel}-adaptor]`;
|
|
8978
9401
|
}
|
|
8979
9402
|
function fileDeliveryFor(entry) {
|
|
8980
|
-
|
|
9403
|
+
switch (entry.channel) {
|
|
9404
|
+
case "whatsapp":
|
|
9405
|
+
return makeWhatsAppFileDelivery(entry);
|
|
9406
|
+
case "telegram":
|
|
9407
|
+
return makeTelegramFileDelivery(entry);
|
|
9408
|
+
case "webchat":
|
|
9409
|
+
return makeWebchatFileDelivery(entry);
|
|
9410
|
+
case "email":
|
|
9411
|
+
return null;
|
|
9412
|
+
}
|
|
8981
9413
|
}
|
|
8982
9414
|
function publicIdleMs() {
|
|
8983
9415
|
return Number(process.env.CHANNEL_PTY_IDLE_MS ?? String(5 * 6e4));
|
|
@@ -8995,6 +9427,7 @@ async function ensureEntry(input) {
|
|
|
8995
9427
|
const sliceTokenValue = input.sliceToken ?? "";
|
|
8996
9428
|
const personIdValue = input.personId ?? null;
|
|
8997
9429
|
const nameValue = input.name ?? null;
|
|
9430
|
+
const replyTargetValue = input.replyTarget ?? null;
|
|
8998
9431
|
const key = indexKey(input.accountId, input.agentSlug, input.senderId, sliceTokenValue, personIdValue);
|
|
8999
9432
|
const existing = index.get(key);
|
|
9000
9433
|
const tag = tagFor(input.channel);
|
|
@@ -9016,6 +9449,7 @@ async function ensureEntry(input) {
|
|
|
9016
9449
|
});
|
|
9017
9450
|
existing.followerRunning = true;
|
|
9018
9451
|
}
|
|
9452
|
+
existing.replyTarget = replyTargetValue;
|
|
9019
9453
|
return existing;
|
|
9020
9454
|
}
|
|
9021
9455
|
let spawned;
|
|
@@ -9036,7 +9470,7 @@ async function ensureEntry(input) {
|
|
|
9036
9470
|
});
|
|
9037
9471
|
} else {
|
|
9038
9472
|
console.error(`${tag} route role=${input.role} target=public-spawn senderId=${input.senderId}`);
|
|
9039
|
-
const attachmentDir =
|
|
9473
|
+
const attachmentDir = resolve16(DATA_ROOT, "accounts", input.accountId, "uploads", "public", input.senderId);
|
|
9040
9474
|
spawned = await managerSpawn({
|
|
9041
9475
|
senderId: input.senderId,
|
|
9042
9476
|
role: input.role,
|
|
@@ -9072,7 +9506,8 @@ async function ensureEntry(input) {
|
|
|
9072
9506
|
followerRunning: false,
|
|
9073
9507
|
sliceToken: sliceTokenValue,
|
|
9074
9508
|
personId: personIdValue,
|
|
9075
|
-
name: nameValue
|
|
9509
|
+
name: nameValue,
|
|
9510
|
+
replyTarget: replyTargetValue
|
|
9076
9511
|
};
|
|
9077
9512
|
entry.followerAbort = startFollower({
|
|
9078
9513
|
entry,
|
|
@@ -9124,7 +9559,8 @@ async function writeInput(entry, text) {
|
|
|
9124
9559
|
agentSlug: entry.agentSlug,
|
|
9125
9560
|
sliceToken: entry.sliceToken.length > 0 ? entry.sliceToken : void 0,
|
|
9126
9561
|
personId: entry.personId,
|
|
9127
|
-
name: entry.name
|
|
9562
|
+
name: entry.name,
|
|
9563
|
+
replyTarget: entry.replyTarget ?? void 0
|
|
9128
9564
|
});
|
|
9129
9565
|
if (!respawned) return false;
|
|
9130
9566
|
for (const cb of entry.subscribers) respawned.subscribers.add(cb);
|
|
@@ -9164,16 +9600,17 @@ async function dispatchOnce(input) {
|
|
|
9164
9600
|
agentSlug: input.agentSlug,
|
|
9165
9601
|
sliceToken: input.sliceToken,
|
|
9166
9602
|
personId: input.personId,
|
|
9167
|
-
name: input.name
|
|
9603
|
+
name: input.name,
|
|
9604
|
+
replyTarget: input.replyTarget
|
|
9168
9605
|
});
|
|
9169
9606
|
if (!entry) return { error: "spawn-failed" };
|
|
9170
9607
|
entry.lastInboundAt = Date.now();
|
|
9171
|
-
let
|
|
9608
|
+
let resolve34;
|
|
9172
9609
|
const turnPromise = new Promise((r) => {
|
|
9173
|
-
|
|
9610
|
+
resolve34 = r;
|
|
9174
9611
|
});
|
|
9175
9612
|
const listener = (text) => {
|
|
9176
|
-
|
|
9613
|
+
resolve34(text);
|
|
9177
9614
|
};
|
|
9178
9615
|
entry.subscribers.add(listener);
|
|
9179
9616
|
const writeAtMs = Date.now();
|
|
@@ -9320,14 +9757,14 @@ function routeTelegramUpdate(input) {
|
|
|
9320
9757
|
}
|
|
9321
9758
|
|
|
9322
9759
|
// server/routes/telegram.ts
|
|
9323
|
-
var
|
|
9760
|
+
var TAG26 = "[telegram-inbound]";
|
|
9324
9761
|
var DISPATCH_TIMEOUT_MS = 12e4;
|
|
9325
9762
|
function configDirName() {
|
|
9326
|
-
const
|
|
9327
|
-
const brandPath =
|
|
9763
|
+
const platformRoot4 = process.env.MAXY_PLATFORM_ROOT ?? resolve17(process.cwd(), "..");
|
|
9764
|
+
const brandPath = join17(platformRoot4, "config", "brand.json");
|
|
9328
9765
|
if (existsSync11(brandPath)) {
|
|
9329
9766
|
try {
|
|
9330
|
-
return JSON.parse(
|
|
9767
|
+
return JSON.parse(readFileSync18(brandPath, "utf-8")).configDir ?? ".maxy";
|
|
9331
9768
|
} catch {
|
|
9332
9769
|
}
|
|
9333
9770
|
}
|
|
@@ -9335,7 +9772,7 @@ function configDirName() {
|
|
|
9335
9772
|
}
|
|
9336
9773
|
function secretPath(botType) {
|
|
9337
9774
|
const filename = botType === "admin" ? ".telegram-admin-webhook-secret" : ".telegram-webhook-secret";
|
|
9338
|
-
return
|
|
9775
|
+
return resolve17(homedir(), configDirName(), filename);
|
|
9339
9776
|
}
|
|
9340
9777
|
async function sendTelegram(botToken, chatId, text) {
|
|
9341
9778
|
try {
|
|
@@ -9355,18 +9792,18 @@ app7.post("/", async (c) => {
|
|
|
9355
9792
|
const botParam = c.req.query("bot");
|
|
9356
9793
|
const botType = botParam === "admin" ? "admin" : botParam === "public" ? "public" : null;
|
|
9357
9794
|
if (!botType) {
|
|
9358
|
-
console.error(`${
|
|
9795
|
+
console.error(`${TAG26} op=reject reason=missing-bot-param`);
|
|
9359
9796
|
return c.json({ ok: false }, 400);
|
|
9360
9797
|
}
|
|
9361
9798
|
const sp = secretPath(botType);
|
|
9362
9799
|
if (!existsSync11(sp)) {
|
|
9363
|
-
console.error(`${
|
|
9800
|
+
console.error(`${TAG26} op=reject reason=no-secret-file botType=${botType}`);
|
|
9364
9801
|
return c.json({ ok: false }, 401);
|
|
9365
9802
|
}
|
|
9366
|
-
const expected =
|
|
9803
|
+
const expected = readFileSync18(sp, "utf-8").trim();
|
|
9367
9804
|
const got = c.req.header("x-telegram-bot-api-secret-token") ?? "";
|
|
9368
9805
|
if (got !== expected) {
|
|
9369
|
-
console.error(`${
|
|
9806
|
+
console.error(`${TAG26} op=reject reason=bad-secret botType=${botType}`);
|
|
9370
9807
|
return c.json({ ok: false }, 401);
|
|
9371
9808
|
}
|
|
9372
9809
|
let update;
|
|
@@ -9377,25 +9814,25 @@ app7.post("/", async (c) => {
|
|
|
9377
9814
|
}
|
|
9378
9815
|
const account = resolveAccount();
|
|
9379
9816
|
if (!account) {
|
|
9380
|
-
console.error(`${
|
|
9817
|
+
console.error(`${TAG26} op=reject reason=no-account`);
|
|
9381
9818
|
return c.json({ ok: true }, 200);
|
|
9382
9819
|
}
|
|
9383
9820
|
const decision = routeTelegramUpdate({ update, botType, config: account.config.telegram ?? {} });
|
|
9384
9821
|
if (decision.kind === "ignore") {
|
|
9385
|
-
console.error(`${
|
|
9822
|
+
console.error(`${TAG26} op=ignore reason=${decision.reason}`);
|
|
9386
9823
|
return c.json({ ok: true }, 200);
|
|
9387
9824
|
}
|
|
9388
9825
|
if (decision.kind === "denied") {
|
|
9389
|
-
console.error(`${
|
|
9826
|
+
console.error(`${TAG26} op=denied reason=${decision.reason} agentType=${decision.agentType}`);
|
|
9390
9827
|
return c.json({ ok: true }, 200);
|
|
9391
9828
|
}
|
|
9392
9829
|
const role = decision.agentType === "admin" ? "admin" : "public";
|
|
9393
9830
|
const agentSlug = role === "admin" ? "admin" : resolveDefaultAgentSlug(account.accountDir);
|
|
9394
9831
|
if (!agentSlug) {
|
|
9395
|
-
console.error(`${
|
|
9832
|
+
console.error(`${TAG26} op=reject reason=no-default-agent`);
|
|
9396
9833
|
return c.json({ ok: true }, 200);
|
|
9397
9834
|
}
|
|
9398
|
-
console.error(`${
|
|
9835
|
+
console.error(`${TAG26} op=update accountId=${account.accountId} from=${decision.senderId}`);
|
|
9399
9836
|
const botToken = botType === "admin" ? account.config.telegram?.adminBotToken : account.config.telegram?.publicBotToken;
|
|
9400
9837
|
const { senderId, chatId, text } = decision;
|
|
9401
9838
|
void (async () => {
|
|
@@ -9406,70 +9843,73 @@ app7.post("/", async (c) => {
|
|
|
9406
9843
|
channel: "telegram",
|
|
9407
9844
|
agentSlug,
|
|
9408
9845
|
text,
|
|
9846
|
+
// Task 924 — the file adapter delivers SendUserFile to this chat id, so a
|
|
9847
|
+
// group turn's document reaches the group rather than the sender's DM.
|
|
9848
|
+
replyTarget: String(chatId),
|
|
9409
9849
|
timeoutMs: DISPATCH_TIMEOUT_MS
|
|
9410
9850
|
});
|
|
9411
9851
|
if ("error" in result) {
|
|
9412
|
-
console.error(`${
|
|
9852
|
+
console.error(`${TAG26} op=dispatch-failed from=${senderId} error=${result.error}`);
|
|
9413
9853
|
return;
|
|
9414
9854
|
}
|
|
9415
9855
|
if (!botToken) {
|
|
9416
9856
|
const reason = account.config.telegram ? "no-bot-token" : "no-telegram-config";
|
|
9417
|
-
console.error(`${
|
|
9857
|
+
console.error(`${TAG26} op=reply-dropped reason=${reason} botType=${botType}`);
|
|
9418
9858
|
return;
|
|
9419
9859
|
}
|
|
9420
9860
|
const sent = await sendTelegram(botToken, chatId, result.turnText);
|
|
9421
|
-
console.error(`${
|
|
9861
|
+
console.error(`${TAG26} op=reply-sent from=${senderId} ok=${sent.ok}${sent.ok ? "" : ` error=${sent.error}`}`);
|
|
9422
9862
|
})();
|
|
9423
9863
|
return c.json({ ok: true }, 200);
|
|
9424
9864
|
});
|
|
9425
9865
|
var telegram_default = app7;
|
|
9426
9866
|
|
|
9427
9867
|
// server/routes/quickbooks.ts
|
|
9428
|
-
import { join as
|
|
9429
|
-
import { existsSync as existsSync12, readFileSync as
|
|
9430
|
-
var
|
|
9868
|
+
import { join as join18 } from "path";
|
|
9869
|
+
import { existsSync as existsSync12, readFileSync as readFileSync19, writeFileSync as writeFileSync8, mkdirSync as mkdirSync3, rmSync, renameSync as renameSync4 } from "fs";
|
|
9870
|
+
var TAG27 = "[quickbooks]";
|
|
9431
9871
|
var INTUIT_TOKEN_URL = "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer";
|
|
9432
9872
|
var STATE_RE = /^[A-Za-z0-9_-]+$/;
|
|
9433
9873
|
function pendingPath(accountDir, state) {
|
|
9434
|
-
return
|
|
9874
|
+
return join18(accountDir, "secrets", "quickbooks-pending", `${state}.json`);
|
|
9435
9875
|
}
|
|
9436
9876
|
function storePath(accountDir) {
|
|
9437
|
-
return
|
|
9877
|
+
return join18(accountDir, "secrets", "quickbooks.json");
|
|
9438
9878
|
}
|
|
9439
9879
|
async function completeConsent(args) {
|
|
9440
9880
|
const { accountDir, code, realmId } = args;
|
|
9441
9881
|
const state = args.state;
|
|
9442
9882
|
if (!state || !STATE_RE.test(state) || !existsSync12(pendingPath(accountDir, state))) {
|
|
9443
|
-
console.error(`${
|
|
9883
|
+
console.error(`${TAG27} op=callback state=${state ?? ""} stateValid=false realmId=${realmId ?? ""}`);
|
|
9444
9884
|
return { ok: false, status: 400, message: "Invalid or unknown authorization state.", stateValid: false };
|
|
9445
9885
|
}
|
|
9446
9886
|
const claimFile = `${pendingPath(accountDir, state)}.claimed`;
|
|
9447
9887
|
try {
|
|
9448
9888
|
renameSync4(pendingPath(accountDir, state), claimFile);
|
|
9449
9889
|
} catch {
|
|
9450
|
-
console.error(`${
|
|
9890
|
+
console.error(`${TAG27} op=callback state=${state} stateValid=false realmId=${realmId ?? ""}`);
|
|
9451
9891
|
return { ok: false, status: 400, message: "Invalid or unknown authorization state.", stateValid: false };
|
|
9452
9892
|
}
|
|
9453
9893
|
try {
|
|
9454
9894
|
let pending;
|
|
9455
9895
|
try {
|
|
9456
|
-
pending = JSON.parse(
|
|
9896
|
+
pending = JSON.parse(readFileSync19(claimFile, "utf-8"));
|
|
9457
9897
|
} catch {
|
|
9458
|
-
console.error(`${
|
|
9898
|
+
console.error(`${TAG27} op=callback state=${state} stateValid=false realmId=${realmId ?? ""}`);
|
|
9459
9899
|
return { ok: false, status: 400, message: "Authorization state could not be read.", stateValid: false };
|
|
9460
9900
|
}
|
|
9461
9901
|
if (Date.now() > pending.expiresAt) {
|
|
9462
|
-
console.error(`${
|
|
9902
|
+
console.error(`${TAG27} op=callback state=${state} stateValid=false realmId=${realmId ?? ""}`);
|
|
9463
9903
|
return { ok: false, status: 400, message: "Authorization request expired. Generate a new consent link.", stateValid: false };
|
|
9464
9904
|
}
|
|
9465
|
-
console.error(`${
|
|
9905
|
+
console.error(`${TAG27} op=callback state=${state} stateValid=true realmId=${realmId ?? ""}`);
|
|
9466
9906
|
if (!code || !realmId) {
|
|
9467
9907
|
return { ok: false, status: 400, message: "Missing code or realmId in the callback.", stateValid: true };
|
|
9468
9908
|
}
|
|
9469
9909
|
if (!existsSync12(storePath(accountDir))) {
|
|
9470
9910
|
return { ok: false, status: 500, message: "No QuickBooks credentials are stored for this account.", stateValid: true };
|
|
9471
9911
|
}
|
|
9472
|
-
const store2 = JSON.parse(
|
|
9912
|
+
const store2 = JSON.parse(readFileSync19(storePath(accountDir), "utf-8"));
|
|
9473
9913
|
const form = new URLSearchParams({
|
|
9474
9914
|
grant_type: "authorization_code",
|
|
9475
9915
|
code,
|
|
@@ -9486,12 +9926,12 @@ async function completeConsent(args) {
|
|
|
9486
9926
|
});
|
|
9487
9927
|
const text = await res.text();
|
|
9488
9928
|
if (!res.ok) {
|
|
9489
|
-
console.error(`${
|
|
9929
|
+
console.error(`${TAG27} op=token-exchange realmId=${realmId} persisted=false`);
|
|
9490
9930
|
return { ok: false, status: 502, message: `Token exchange failed (${res.status}). Generate a new consent link to retry.`, stateValid: true };
|
|
9491
9931
|
}
|
|
9492
9932
|
const body = JSON.parse(text);
|
|
9493
9933
|
if (!body.refresh_token) {
|
|
9494
|
-
console.error(`${
|
|
9934
|
+
console.error(`${TAG27} op=token-exchange realmId=${realmId} persisted=false`);
|
|
9495
9935
|
return { ok: false, status: 502, message: "Token exchange returned no refresh token.", stateValid: true };
|
|
9496
9936
|
}
|
|
9497
9937
|
const now = Date.now();
|
|
@@ -9501,10 +9941,10 @@ async function completeConsent(args) {
|
|
|
9501
9941
|
refreshTokenExpiry: now + (body.x_refresh_token_expires_in ?? 864e4) * 1e3,
|
|
9502
9942
|
lastRefresh: now
|
|
9503
9943
|
};
|
|
9504
|
-
mkdirSync3(
|
|
9944
|
+
mkdirSync3(join18(accountDir, "secrets"), { recursive: true });
|
|
9505
9945
|
writeFileSync8(storePath(accountDir), JSON.stringify(store2, null, 2), { mode: 384 });
|
|
9506
|
-
const persisted = JSON.parse(
|
|
9507
|
-
console.error(`${
|
|
9946
|
+
const persisted = JSON.parse(readFileSync19(storePath(accountDir), "utf-8")).connections?.[realmId]?.refreshToken === body.refresh_token;
|
|
9947
|
+
console.error(`${TAG27} op=token-exchange realmId=${realmId} persisted=${persisted}`);
|
|
9508
9948
|
return { ok: persisted, status: persisted ? 200 : 500, message: persisted ? `Connected company ${realmId}.` : "Failed to persist the connection.", stateValid: true };
|
|
9509
9949
|
} finally {
|
|
9510
9950
|
rmSync(claimFile, { force: true });
|
|
@@ -9517,7 +9957,7 @@ var app8 = new Hono();
|
|
|
9517
9957
|
app8.get("/callback", async (c) => {
|
|
9518
9958
|
const account = resolveAccount();
|
|
9519
9959
|
if (!account) {
|
|
9520
|
-
console.error(`${
|
|
9960
|
+
console.error(`${TAG27} op=callback stateValid=false realmId= reason=no-account`);
|
|
9521
9961
|
return c.html(page("QuickBooks", "This install has no account configured."), 500);
|
|
9522
9962
|
}
|
|
9523
9963
|
const result = await completeConsent({
|
|
@@ -9534,12 +9974,12 @@ var quickbooks_default = app8;
|
|
|
9534
9974
|
|
|
9535
9975
|
// server/routes/onboarding.ts
|
|
9536
9976
|
import { spawn, spawnSync as spawnSync2, execFileSync as execFileSync2 } from "child_process";
|
|
9537
|
-
import { openSync as openSync3, closeSync as closeSync3, writeFileSync as writeFileSync10, writeSync, existsSync as existsSync14, readFileSync as
|
|
9977
|
+
import { openSync as openSync3, closeSync as closeSync3, writeFileSync as writeFileSync10, writeSync, existsSync as existsSync14, readFileSync as readFileSync21, unlinkSync as unlinkSync2 } from "fs";
|
|
9538
9978
|
import { createHash as createHash3, randomUUID as randomUUID8 } from "crypto";
|
|
9539
9979
|
|
|
9540
9980
|
// ../lib/admins-write/src/index.ts
|
|
9541
|
-
import { existsSync as existsSync13, readFileSync as
|
|
9542
|
-
import { dirname as dirname4, join as
|
|
9981
|
+
import { existsSync as existsSync13, readFileSync as readFileSync20, writeFileSync as writeFileSync9, renameSync as renameSync5, mkdirSync as mkdirSync4, readdirSync as readdirSync12, statSync as statSync7 } from "fs";
|
|
9982
|
+
import { dirname as dirname4, join as join19 } from "path";
|
|
9543
9983
|
function logLine(input, result) {
|
|
9544
9984
|
const userIdShort = input.userId.slice(0, 8);
|
|
9545
9985
|
console.error(
|
|
@@ -9557,7 +9997,7 @@ function writeAdminEntry(input) {
|
|
|
9557
9997
|
try {
|
|
9558
9998
|
let users = [];
|
|
9559
9999
|
if (existsSync13(input.usersFile)) {
|
|
9560
|
-
const raw =
|
|
10000
|
+
const raw = readFileSync20(input.usersFile, "utf-8").trim();
|
|
9561
10001
|
if (raw) {
|
|
9562
10002
|
const parsed = JSON.parse(raw);
|
|
9563
10003
|
if (!Array.isArray(parsed)) {
|
|
@@ -9581,11 +10021,11 @@ function writeAdminEntry(input) {
|
|
|
9581
10021
|
return result;
|
|
9582
10022
|
}
|
|
9583
10023
|
try {
|
|
9584
|
-
const accountJsonPath =
|
|
10024
|
+
const accountJsonPath = join19(input.accountDir, "account.json");
|
|
9585
10025
|
if (!existsSync13(accountJsonPath)) {
|
|
9586
10026
|
throw new Error(`account.json not found at ${accountJsonPath}`);
|
|
9587
10027
|
}
|
|
9588
|
-
const config = JSON.parse(
|
|
10028
|
+
const config = JSON.parse(readFileSync20(accountJsonPath, "utf-8"));
|
|
9589
10029
|
const admins = config.admins ?? [];
|
|
9590
10030
|
if (admins.some((a) => a.userId === input.userId)) {
|
|
9591
10031
|
result.accountJsonResult = "noop";
|
|
@@ -9611,7 +10051,7 @@ function checkAdminAuthInvariant(input) {
|
|
|
9611
10051
|
const usersUserIds = /* @__PURE__ */ new Set();
|
|
9612
10052
|
if (existsSync13(input.usersFile)) {
|
|
9613
10053
|
try {
|
|
9614
|
-
const raw =
|
|
10054
|
+
const raw = readFileSync20(input.usersFile, "utf-8").trim();
|
|
9615
10055
|
if (raw) {
|
|
9616
10056
|
const users = JSON.parse(raw);
|
|
9617
10057
|
for (const u of users) {
|
|
@@ -9627,7 +10067,7 @@ function checkAdminAuthInvariant(input) {
|
|
|
9627
10067
|
if (existsSync13(input.accountsDir)) {
|
|
9628
10068
|
let entries;
|
|
9629
10069
|
try {
|
|
9630
|
-
entries =
|
|
10070
|
+
entries = readdirSync12(input.accountsDir);
|
|
9631
10071
|
} catch (err) {
|
|
9632
10072
|
const msg = err instanceof Error ? err.message : String(err);
|
|
9633
10073
|
console.error(`[${input.tag}] accounts-dir unreadable accountsDir=${input.accountsDir} error=${msg}`);
|
|
@@ -9636,17 +10076,17 @@ function checkAdminAuthInvariant(input) {
|
|
|
9636
10076
|
}
|
|
9637
10077
|
for (const entry of entries) {
|
|
9638
10078
|
if (entry.startsWith(".")) continue;
|
|
9639
|
-
const accountDir =
|
|
10079
|
+
const accountDir = join19(input.accountsDir, entry);
|
|
9640
10080
|
try {
|
|
9641
10081
|
if (!statSync7(accountDir).isDirectory()) continue;
|
|
9642
10082
|
} catch {
|
|
9643
10083
|
continue;
|
|
9644
10084
|
}
|
|
9645
|
-
const accountJsonPath =
|
|
10085
|
+
const accountJsonPath = join19(accountDir, "account.json");
|
|
9646
10086
|
if (!existsSync13(accountJsonPath)) continue;
|
|
9647
10087
|
let admins = [];
|
|
9648
10088
|
try {
|
|
9649
|
-
const config = JSON.parse(
|
|
10089
|
+
const config = JSON.parse(readFileSync20(accountJsonPath, "utf-8"));
|
|
9650
10090
|
admins = config.admins ?? [];
|
|
9651
10091
|
} catch (err) {
|
|
9652
10092
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -9687,7 +10127,7 @@ function hashPin2(pin) {
|
|
|
9687
10127
|
}
|
|
9688
10128
|
function readUsersFile2() {
|
|
9689
10129
|
if (!existsSync14(USERS_FILE)) return null;
|
|
9690
|
-
const raw =
|
|
10130
|
+
const raw = readFileSync21(USERS_FILE, "utf-8").trim();
|
|
9691
10131
|
if (!raw) return [];
|
|
9692
10132
|
return JSON.parse(raw);
|
|
9693
10133
|
}
|
|
@@ -9849,7 +10289,7 @@ app9.post("/set-pin", async (c) => {
|
|
|
9849
10289
|
let installOwner = null;
|
|
9850
10290
|
if (existsSync14(INSTALL_OWNER_FILE)) {
|
|
9851
10291
|
try {
|
|
9852
|
-
const raw =
|
|
10292
|
+
const raw = readFileSync21(INSTALL_OWNER_FILE, "utf-8").trim();
|
|
9853
10293
|
if (raw.length > 0) installOwner = raw;
|
|
9854
10294
|
} catch (err) {
|
|
9855
10295
|
console.error(`[set-pin] install-owner-read-failed path=${INSTALL_OWNER_FILE} error=${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -9931,8 +10371,8 @@ var onboarding_default = app9;
|
|
|
9931
10371
|
|
|
9932
10372
|
// server/routes/client-error.ts
|
|
9933
10373
|
import { appendFileSync, existsSync as existsSync15, renameSync as renameSync6, statSync as statSync8 } from "fs";
|
|
9934
|
-
import { join as
|
|
9935
|
-
var CLIENT_ERRORS_LOG =
|
|
10374
|
+
import { join as join20 } from "path";
|
|
10375
|
+
var CLIENT_ERRORS_LOG = join20(LOG_DIR, "client-errors.log");
|
|
9936
10376
|
var MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
9937
10377
|
var MAX_BODY_SIZE = 8 * 1024;
|
|
9938
10378
|
var MAX_STACK_LEN = 2e3;
|
|
@@ -10257,17 +10697,17 @@ app11.post("/", async (c) => {
|
|
|
10257
10697
|
var session_default = app11;
|
|
10258
10698
|
|
|
10259
10699
|
// server/routes/admin/logs.ts
|
|
10260
|
-
import { existsSync as existsSync17, readdirSync as
|
|
10261
|
-
import { resolve as
|
|
10700
|
+
import { existsSync as existsSync17, readdirSync as readdirSync13, readFileSync as readFileSync22, statSync as statSync9 } from "fs";
|
|
10701
|
+
import { resolve as resolve18, basename as basename6 } from "path";
|
|
10262
10702
|
|
|
10263
10703
|
// app/lib/logs-read-resolve.ts
|
|
10264
10704
|
import { existsSync as existsSync16 } from "fs";
|
|
10265
|
-
import { join as
|
|
10705
|
+
import { join as join21 } from "path";
|
|
10266
10706
|
function resolveSessionLogPaths(filename, logDirs) {
|
|
10267
10707
|
const tried = [filename];
|
|
10268
10708
|
const hits = [];
|
|
10269
10709
|
for (const dir of logDirs) {
|
|
10270
|
-
const fullPath =
|
|
10710
|
+
const fullPath = join21(dir, filename);
|
|
10271
10711
|
if (existsSync16(fullPath)) {
|
|
10272
10712
|
hits.push({ path: fullPath, dir });
|
|
10273
10713
|
}
|
|
@@ -10285,18 +10725,18 @@ app12.get("/", async (c) => {
|
|
|
10285
10725
|
const cacheKeyParam = c.req.query("cacheKey");
|
|
10286
10726
|
const download = c.req.query("download") === "1";
|
|
10287
10727
|
const account = resolveAccount();
|
|
10288
|
-
const accountLogDir = account ?
|
|
10728
|
+
const accountLogDir = account ? resolve18(account.accountDir, "logs") : null;
|
|
10289
10729
|
const logDirs = [];
|
|
10290
10730
|
if (accountLogDir) logDirs.push(accountLogDir);
|
|
10291
10731
|
logDirs.push(LOG_DIR);
|
|
10292
10732
|
if (fileParam) {
|
|
10293
|
-
const safe =
|
|
10733
|
+
const safe = basename6(fileParam);
|
|
10294
10734
|
const searched = [];
|
|
10295
10735
|
for (const dir of logDirs) {
|
|
10296
|
-
const filePath =
|
|
10736
|
+
const filePath = resolve18(dir, safe);
|
|
10297
10737
|
searched.push(filePath);
|
|
10298
10738
|
try {
|
|
10299
|
-
const buffer =
|
|
10739
|
+
const buffer = readFileSync22(filePath);
|
|
10300
10740
|
const onDiskBytes = statSync9(filePath).size;
|
|
10301
10741
|
const headers = {
|
|
10302
10742
|
"Content-Type": "text/plain; charset=utf-8",
|
|
@@ -10360,8 +10800,8 @@ app12.get("/", async (c) => {
|
|
|
10360
10800
|
if (hit) {
|
|
10361
10801
|
console.info(`[admin/logs] resolved cacheKey=${cacheKeySlice} sessionId=${sessionIdSlice} via=${primaryId === cacheKey ? "cacheKey" : primaryId === sessionKeyFromConv ? "reverse-lookup" : "sessionId-fallback"}`);
|
|
10362
10802
|
try {
|
|
10363
|
-
const filename =
|
|
10364
|
-
const buffer =
|
|
10803
|
+
const filename = basename6(hit.path);
|
|
10804
|
+
const buffer = readFileSync22(hit.path);
|
|
10365
10805
|
const onDiskBytes = statSync9(hit.path).size;
|
|
10366
10806
|
const headers = {
|
|
10367
10807
|
"Content-Type": "text/plain; charset=utf-8",
|
|
@@ -10399,16 +10839,16 @@ app12.get("/", async (c) => {
|
|
|
10399
10839
|
if (!existsSync17(dir)) continue;
|
|
10400
10840
|
let files;
|
|
10401
10841
|
try {
|
|
10402
|
-
files =
|
|
10842
|
+
files = readdirSync13(dir).filter((f) => f.endsWith(".log"));
|
|
10403
10843
|
} catch (err) {
|
|
10404
10844
|
const reason = err instanceof Error ? err.message : String(err);
|
|
10405
10845
|
console.warn(`[admin/logs] readdir-fail dir=${dir} reason=${reason}`);
|
|
10406
10846
|
continue;
|
|
10407
10847
|
}
|
|
10408
|
-
files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync9(
|
|
10848
|
+
files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync9(resolve18(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).forEach(({ name }) => {
|
|
10409
10849
|
seen.add(name);
|
|
10410
10850
|
try {
|
|
10411
|
-
const content =
|
|
10851
|
+
const content = readFileSync22(resolve18(dir, name));
|
|
10412
10852
|
const tail = content.length > TAIL_BYTES ? content.subarray(content.length - TAIL_BYTES).toString("utf-8") : content.toString("utf-8");
|
|
10413
10853
|
logs[name] = tail.trim() || "(empty)";
|
|
10414
10854
|
} catch (err) {
|
|
@@ -10446,9 +10886,9 @@ app13.get("/", (c) => {
|
|
|
10446
10886
|
var claude_info_default = app13;
|
|
10447
10887
|
|
|
10448
10888
|
// server/routes/admin/attachment.ts
|
|
10449
|
-
import { readFile as
|
|
10889
|
+
import { readFile as readFile3, readdir } from "fs/promises";
|
|
10450
10890
|
import { existsSync as existsSync18 } from "fs";
|
|
10451
|
-
import { resolve as
|
|
10891
|
+
import { resolve as resolve19 } from "path";
|
|
10452
10892
|
var app14 = new Hono();
|
|
10453
10893
|
app14.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
10454
10894
|
const attachmentId = c.req.param("attachmentId");
|
|
@@ -10460,17 +10900,17 @@ app14.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
|
10460
10900
|
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(attachmentId)) {
|
|
10461
10901
|
return new Response("Not found", { status: 404 });
|
|
10462
10902
|
}
|
|
10463
|
-
const dir =
|
|
10903
|
+
const dir = resolve19(DATA_ROOT, "accounts", accountId, "uploads", attachmentId);
|
|
10464
10904
|
if (!existsSync18(dir)) {
|
|
10465
10905
|
return new Response("Not found", { status: 404 });
|
|
10466
10906
|
}
|
|
10467
|
-
const metaPath =
|
|
10907
|
+
const metaPath = resolve19(dir, `${attachmentId}.meta.json`);
|
|
10468
10908
|
if (!existsSync18(metaPath)) {
|
|
10469
10909
|
return new Response("Not found", { status: 404 });
|
|
10470
10910
|
}
|
|
10471
10911
|
let meta;
|
|
10472
10912
|
try {
|
|
10473
|
-
meta = JSON.parse(await
|
|
10913
|
+
meta = JSON.parse(await readFile3(metaPath, "utf-8"));
|
|
10474
10914
|
} catch {
|
|
10475
10915
|
return new Response("Not found", { status: 404 });
|
|
10476
10916
|
}
|
|
@@ -10479,8 +10919,8 @@ app14.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
|
10479
10919
|
if (!dataFile) {
|
|
10480
10920
|
return new Response("Not found", { status: 404 });
|
|
10481
10921
|
}
|
|
10482
|
-
const filePath =
|
|
10483
|
-
const buffer = await
|
|
10922
|
+
const filePath = resolve19(dir, dataFile);
|
|
10923
|
+
const buffer = await readFile3(filePath);
|
|
10484
10924
|
return new Response(new Uint8Array(buffer), {
|
|
10485
10925
|
headers: {
|
|
10486
10926
|
"Content-Type": meta.mimeType,
|
|
@@ -10492,24 +10932,24 @@ app14.get("/:attachmentId", requireAdminSession, async (c) => {
|
|
|
10492
10932
|
var attachment_default = app14;
|
|
10493
10933
|
|
|
10494
10934
|
// server/routes/admin/agents.ts
|
|
10495
|
-
import { resolve as
|
|
10496
|
-
import { readdirSync as
|
|
10935
|
+
import { resolve as resolve20 } from "path";
|
|
10936
|
+
import { readdirSync as readdirSync14, readFileSync as readFileSync23, existsSync as existsSync19, rmSync as rmSync2 } from "fs";
|
|
10497
10937
|
var app15 = new Hono();
|
|
10498
10938
|
app15.get("/", (c) => {
|
|
10499
10939
|
const account = resolveAccount();
|
|
10500
10940
|
if (!account) return c.json({ agents: [] });
|
|
10501
|
-
const agentsDir =
|
|
10941
|
+
const agentsDir = resolve20(account.accountDir, "agents");
|
|
10502
10942
|
if (!existsSync19(agentsDir)) return c.json({ agents: [] });
|
|
10503
10943
|
const agents = [];
|
|
10504
10944
|
try {
|
|
10505
|
-
const entries =
|
|
10945
|
+
const entries = readdirSync14(agentsDir, { withFileTypes: true });
|
|
10506
10946
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
10507
10947
|
if (!entry.isDirectory()) continue;
|
|
10508
10948
|
if (entry.name === "admin") continue;
|
|
10509
|
-
const configPath2 =
|
|
10949
|
+
const configPath2 = resolve20(agentsDir, entry.name, "config.json");
|
|
10510
10950
|
if (!existsSync19(configPath2)) continue;
|
|
10511
10951
|
try {
|
|
10512
|
-
const config = JSON.parse(
|
|
10952
|
+
const config = JSON.parse(readFileSync23(configPath2, "utf-8"));
|
|
10513
10953
|
agents.push({
|
|
10514
10954
|
slug: entry.name,
|
|
10515
10955
|
displayName: config.displayName ?? entry.name,
|
|
@@ -10535,7 +10975,7 @@ app15.delete("/:slug", async (c) => {
|
|
|
10535
10975
|
if (slug.includes("/") || slug.includes("..") || slug.includes("\\")) {
|
|
10536
10976
|
return c.json({ error: "Invalid agent slug" }, 400);
|
|
10537
10977
|
}
|
|
10538
|
-
const agentDir =
|
|
10978
|
+
const agentDir = resolve20(account.accountDir, "agents", slug);
|
|
10539
10979
|
if (!existsSync19(agentDir)) {
|
|
10540
10980
|
return c.json({ error: "Agent not found" }, 404);
|
|
10541
10981
|
}
|
|
@@ -10565,7 +11005,7 @@ app15.post("/:slug/project", async (c) => {
|
|
|
10565
11005
|
if (slug.includes("/") || slug.includes("..") || slug.includes("\\")) {
|
|
10566
11006
|
return c.json({ error: "Invalid agent slug" }, 400);
|
|
10567
11007
|
}
|
|
10568
|
-
const agentDir =
|
|
11008
|
+
const agentDir = resolve20(account.accountDir, "agents", slug);
|
|
10569
11009
|
if (!existsSync19(agentDir)) {
|
|
10570
11010
|
return c.json({ error: "Agent not found on disk" }, 404);
|
|
10571
11011
|
}
|
|
@@ -11268,8 +11708,8 @@ app16.put("/:id/label", requireAdminSession, async (c) => {
|
|
|
11268
11708
|
var sessions_default = app16;
|
|
11269
11709
|
|
|
11270
11710
|
// app/lib/claude-agent/spawn-context.ts
|
|
11271
|
-
import { existsSync as existsSync21, readFileSync as
|
|
11272
|
-
import { dirname as dirname5, resolve as
|
|
11711
|
+
import { existsSync as existsSync21, readFileSync as readFileSync24, readdirSync as readdirSync15, statSync as statSync10 } from "fs";
|
|
11712
|
+
import { dirname as dirname5, resolve as resolve21, join as join22 } from "path";
|
|
11273
11713
|
async function resolveOwnerProfileBlock(accountId, userId) {
|
|
11274
11714
|
if (!userId) return { ok: false, reason: "missing-user-id" };
|
|
11275
11715
|
try {
|
|
@@ -11310,14 +11750,14 @@ function frontmatterField(manifest, field2) {
|
|
|
11310
11750
|
function extractPluginToolDescriptions(pluginDir) {
|
|
11311
11751
|
const out = /* @__PURE__ */ new Map();
|
|
11312
11752
|
const candidates = [
|
|
11313
|
-
|
|
11314
|
-
|
|
11753
|
+
join22(pluginDir, "mcp/dist/index.js"),
|
|
11754
|
+
join22(pluginDir, "mcp/src/index.ts")
|
|
11315
11755
|
];
|
|
11316
11756
|
let raw = null;
|
|
11317
11757
|
for (const path2 of candidates) {
|
|
11318
11758
|
if (!existsSync21(path2)) continue;
|
|
11319
11759
|
try {
|
|
11320
|
-
raw =
|
|
11760
|
+
raw = readFileSync24(path2, "utf-8");
|
|
11321
11761
|
break;
|
|
11322
11762
|
} catch {
|
|
11323
11763
|
}
|
|
@@ -11366,24 +11806,24 @@ function parseSkillPathsFromFrontmatter(fm) {
|
|
|
11366
11806
|
}
|
|
11367
11807
|
function listPluginDirs() {
|
|
11368
11808
|
const out = /* @__PURE__ */ new Map();
|
|
11369
|
-
const platformPlugins =
|
|
11809
|
+
const platformPlugins = resolve21(PLATFORM_ROOT, "plugins");
|
|
11370
11810
|
if (existsSync21(platformPlugins)) {
|
|
11371
|
-
for (const name of
|
|
11372
|
-
const dir =
|
|
11811
|
+
for (const name of readdirSync15(platformPlugins)) {
|
|
11812
|
+
const dir = join22(platformPlugins, name);
|
|
11373
11813
|
try {
|
|
11374
11814
|
if (statSync10(dir).isDirectory()) out.set(name, dir);
|
|
11375
11815
|
} catch {
|
|
11376
11816
|
}
|
|
11377
11817
|
}
|
|
11378
11818
|
}
|
|
11379
|
-
const premiumRoot =
|
|
11819
|
+
const premiumRoot = resolve21(dirname5(PLATFORM_ROOT), "premium-plugins");
|
|
11380
11820
|
if (existsSync21(premiumRoot)) {
|
|
11381
|
-
for (const bundle of
|
|
11382
|
-
const bundlePlugins =
|
|
11821
|
+
for (const bundle of readdirSync15(premiumRoot)) {
|
|
11822
|
+
const bundlePlugins = join22(premiumRoot, bundle, "plugins");
|
|
11383
11823
|
if (!existsSync21(bundlePlugins)) continue;
|
|
11384
11824
|
try {
|
|
11385
|
-
for (const name of
|
|
11386
|
-
const dir =
|
|
11825
|
+
for (const name of readdirSync15(bundlePlugins)) {
|
|
11826
|
+
const dir = join22(bundlePlugins, name);
|
|
11387
11827
|
try {
|
|
11388
11828
|
if (statSync10(dir).isDirectory()) out.set(name, dir);
|
|
11389
11829
|
} catch {
|
|
@@ -11396,10 +11836,10 @@ function listPluginDirs() {
|
|
|
11396
11836
|
return out;
|
|
11397
11837
|
}
|
|
11398
11838
|
function readEnabledPlugins(accountId) {
|
|
11399
|
-
const accountFile =
|
|
11839
|
+
const accountFile = resolve21(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
|
|
11400
11840
|
if (!existsSync21(accountFile)) return /* @__PURE__ */ new Set();
|
|
11401
11841
|
try {
|
|
11402
|
-
const parsed = JSON.parse(
|
|
11842
|
+
const parsed = JSON.parse(readFileSync24(accountFile, "utf-8"));
|
|
11403
11843
|
return new Set(
|
|
11404
11844
|
Array.isArray(parsed.enabledPlugins) ? parsed.enabledPlugins.filter((p) => typeof p === "string") : []
|
|
11405
11845
|
);
|
|
@@ -11411,7 +11851,7 @@ function readFrontmatter(path2) {
|
|
|
11411
11851
|
if (!existsSync21(path2)) return null;
|
|
11412
11852
|
let raw;
|
|
11413
11853
|
try {
|
|
11414
|
-
raw =
|
|
11854
|
+
raw = readFileSync24(path2, "utf-8");
|
|
11415
11855
|
} catch {
|
|
11416
11856
|
return null;
|
|
11417
11857
|
}
|
|
@@ -11426,7 +11866,7 @@ function computeActivePlugins(accountId) {
|
|
|
11426
11866
|
for (const name of Array.from(enabled).sort()) {
|
|
11427
11867
|
const dir = pluginDirs.get(name);
|
|
11428
11868
|
if (!dir) continue;
|
|
11429
|
-
const fm = readFrontmatter(
|
|
11869
|
+
const fm = readFrontmatter(join22(dir, "PLUGIN.md"));
|
|
11430
11870
|
if (!fm) continue;
|
|
11431
11871
|
const description = frontmatterField(`---
|
|
11432
11872
|
${fm}
|
|
@@ -11443,7 +11883,7 @@ ${fm}
|
|
|
11443
11883
|
`plugin-manifest-tool-coverage plugin=${name} tools=${tools.length} with-desc=${withDesc}`
|
|
11444
11884
|
);
|
|
11445
11885
|
if (toolNames.length > 0 && descMap.size === 0) {
|
|
11446
|
-
const hasSource = existsSync21(
|
|
11886
|
+
const hasSource = existsSync21(join22(dir, "mcp/dist/index.js")) || existsSync21(join22(dir, "mcp/src/index.ts"));
|
|
11447
11887
|
if (hasSource) {
|
|
11448
11888
|
console.warn(
|
|
11449
11889
|
`[spawn-context] WARN plugin=${name} mcp source present but tool-description regex matched zero entries \u2014 SDK upgrade may have changed the registration shape`
|
|
@@ -11453,7 +11893,7 @@ ${fm}
|
|
|
11453
11893
|
const skillPaths = parseSkillPathsFromFrontmatter(fm);
|
|
11454
11894
|
const skills = [];
|
|
11455
11895
|
for (const relPath of skillPaths) {
|
|
11456
|
-
const skillPath =
|
|
11896
|
+
const skillPath = join22(dir, relPath);
|
|
11457
11897
|
const skillFm = readFrontmatter(skillPath);
|
|
11458
11898
|
if (!skillFm) continue;
|
|
11459
11899
|
const skillName = frontmatterField(`---
|
|
@@ -11469,11 +11909,11 @@ ${skillFm}
|
|
|
11469
11909
|
return out;
|
|
11470
11910
|
}
|
|
11471
11911
|
function computeSpecialistDomains(accountId) {
|
|
11472
|
-
const specialistsDir =
|
|
11912
|
+
const specialistsDir = resolve21(PLATFORM_ROOT, "..", "data/accounts", accountId, "specialists", "agents");
|
|
11473
11913
|
if (!existsSync21(specialistsDir)) return [];
|
|
11474
11914
|
let entries;
|
|
11475
11915
|
try {
|
|
11476
|
-
entries =
|
|
11916
|
+
entries = readdirSync15(specialistsDir);
|
|
11477
11917
|
} catch {
|
|
11478
11918
|
return [];
|
|
11479
11919
|
}
|
|
@@ -11497,7 +11937,7 @@ function computeSpecialistDomains(accountId) {
|
|
|
11497
11937
|
const out = [];
|
|
11498
11938
|
for (const file of entries.sort()) {
|
|
11499
11939
|
if (!file.endsWith(".md")) continue;
|
|
11500
|
-
const fm = readFrontmatter(
|
|
11940
|
+
const fm = readFrontmatter(join22(specialistsDir, file));
|
|
11501
11941
|
if (!fm) continue;
|
|
11502
11942
|
const wrapped = `---
|
|
11503
11943
|
${fm}
|
|
@@ -11515,11 +11955,11 @@ ${fm}
|
|
|
11515
11955
|
return out;
|
|
11516
11956
|
}
|
|
11517
11957
|
function computeDormantPlugins(accountId) {
|
|
11518
|
-
const accountFile =
|
|
11958
|
+
const accountFile = resolve21(PLATFORM_ROOT, "..", "data/accounts", accountId, "account.json");
|
|
11519
11959
|
if (!existsSync21(accountFile)) return [];
|
|
11520
11960
|
let enabled;
|
|
11521
11961
|
try {
|
|
11522
|
-
const raw =
|
|
11962
|
+
const raw = readFileSync24(accountFile, "utf-8");
|
|
11523
11963
|
const parsed = JSON.parse(raw);
|
|
11524
11964
|
enabled = new Set(
|
|
11525
11965
|
Array.isArray(parsed.enabledPlugins) ? parsed.enabledPlugins.filter((p) => typeof p === "string") : []
|
|
@@ -11530,22 +11970,22 @@ function computeDormantPlugins(accountId) {
|
|
|
11530
11970
|
);
|
|
11531
11971
|
return [];
|
|
11532
11972
|
}
|
|
11533
|
-
const pluginsDir =
|
|
11973
|
+
const pluginsDir = resolve21(PLATFORM_ROOT, "plugins");
|
|
11534
11974
|
if (!existsSync21(pluginsDir)) return [];
|
|
11535
11975
|
let entries;
|
|
11536
11976
|
try {
|
|
11537
|
-
entries =
|
|
11977
|
+
entries = readdirSync15(pluginsDir);
|
|
11538
11978
|
} catch {
|
|
11539
11979
|
return [];
|
|
11540
11980
|
}
|
|
11541
11981
|
const out = [];
|
|
11542
11982
|
for (const name of entries) {
|
|
11543
11983
|
if (enabled.has(name)) continue;
|
|
11544
|
-
const manifestPath =
|
|
11984
|
+
const manifestPath = resolve21(pluginsDir, name, "PLUGIN.md");
|
|
11545
11985
|
if (!existsSync21(manifestPath)) continue;
|
|
11546
11986
|
let manifest;
|
|
11547
11987
|
try {
|
|
11548
|
-
manifest =
|
|
11988
|
+
manifest = readFileSync24(manifestPath, "utf-8");
|
|
11549
11989
|
} catch {
|
|
11550
11990
|
continue;
|
|
11551
11991
|
}
|
|
@@ -11559,13 +11999,13 @@ function computeDormantPlugins(accountId) {
|
|
|
11559
11999
|
}
|
|
11560
12000
|
|
|
11561
12001
|
// server/routes/admin/claude-sessions.ts
|
|
11562
|
-
import { existsSync as existsSync22, readFileSync as
|
|
11563
|
-
import { resolve as
|
|
12002
|
+
import { existsSync as existsSync22, readFileSync as readFileSync25 } from "fs";
|
|
12003
|
+
import { resolve as resolve22 } from "path";
|
|
11564
12004
|
function readTunnelState(brandConfigDir) {
|
|
11565
12005
|
const statePath = `${process.env.HOME ?? ""}/${brandConfigDir}/cloudflared/tunnel.state`;
|
|
11566
12006
|
if (!existsSync22(statePath)) return null;
|
|
11567
12007
|
try {
|
|
11568
|
-
const parsed = JSON.parse(
|
|
12008
|
+
const parsed = JSON.parse(readFileSync25(statePath, "utf-8"));
|
|
11569
12009
|
const tunnelId = typeof parsed.tunnelId === "string" ? parsed.tunnelId : null;
|
|
11570
12010
|
const tunnelName = typeof parsed.tunnelName === "string" ? parsed.tunnelName : null;
|
|
11571
12011
|
const domain = typeof parsed.domain === "string" ? parsed.domain : null;
|
|
@@ -11576,10 +12016,10 @@ function readTunnelState(brandConfigDir) {
|
|
|
11576
12016
|
}
|
|
11577
12017
|
}
|
|
11578
12018
|
function resolveTunnelUrl() {
|
|
11579
|
-
const
|
|
12019
|
+
const platformRoot4 = process.env.MAXY_PLATFORM_ROOT ?? process.env.PLATFORM_ROOT ?? resolve22(process.cwd(), "..");
|
|
11580
12020
|
let brand;
|
|
11581
12021
|
try {
|
|
11582
|
-
brand = JSON.parse(
|
|
12022
|
+
brand = JSON.parse(readFileSync25(resolve22(platformRoot4, "config", "brand.json"), "utf-8"));
|
|
11583
12023
|
} catch {
|
|
11584
12024
|
return null;
|
|
11585
12025
|
}
|
|
@@ -11590,7 +12030,7 @@ function resolveTunnelUrl() {
|
|
|
11590
12030
|
if (!state) return null;
|
|
11591
12031
|
return `https://${hostname2}.${state.domain}`;
|
|
11592
12032
|
}
|
|
11593
|
-
var
|
|
12033
|
+
var TAG28 = "[claude-session-manager:wrapper]";
|
|
11594
12034
|
async function refuseIfClaudeAuthDead(c, route, sessionId) {
|
|
11595
12035
|
const auth = await ensureAuth();
|
|
11596
12036
|
if (auth.status !== "dead" && auth.status !== "missing") return null;
|
|
@@ -11608,12 +12048,12 @@ async function performSpawnWithInitialMessage(args) {
|
|
|
11608
12048
|
const aboutOwner = await resolveOwnerProfileBlock(args.senderId, args.userId);
|
|
11609
12049
|
const ownerMs = Date.now() - ownerStart;
|
|
11610
12050
|
const aboutOwnerStatus = aboutOwner == null ? "absent" : "ok" in aboutOwner && aboutOwner.ok === false ? `unresolved:${aboutOwner.reason}` : "ok";
|
|
11611
|
-
console.log(`${
|
|
12051
|
+
console.log(`${TAG28} about-owner-resolved status=${aboutOwnerStatus} ms=${ownerMs}`);
|
|
11612
12052
|
const dormantPlugins = computeDormantPlugins(args.senderId);
|
|
11613
12053
|
const activePlugins = computeActivePlugins(args.senderId);
|
|
11614
12054
|
const specialistDomains = computeSpecialistDomains(args.senderId);
|
|
11615
12055
|
const tunnelUrl = resolveTunnelUrl();
|
|
11616
|
-
console.log(`${
|
|
12056
|
+
console.log(`${TAG28} tunnel-url-resolved value=${tunnelUrl ?? "null"}`);
|
|
11617
12057
|
const upstreamPayload = JSON.stringify({
|
|
11618
12058
|
senderId: args.senderId,
|
|
11619
12059
|
// Task 205 — pass userId through to the manager so it lands as
|
|
@@ -11640,24 +12080,24 @@ async function performSpawnWithInitialMessage(args) {
|
|
|
11640
12080
|
// unshapely values.
|
|
11641
12081
|
conversationNodeId: args.conversationNodeId
|
|
11642
12082
|
});
|
|
11643
|
-
console.log(`${
|
|
12083
|
+
console.log(`${TAG28} forward-spawn-start managerBase=${managerBase("claude-session-manager:wrapper")} bytes=${upstreamPayload.length} hidden=${args.hidden} specialist=${args.specialist ?? "none"}`);
|
|
11644
12084
|
const forwardStart = Date.now();
|
|
11645
12085
|
const upstream = await fetch(`${managerBase("claude-session-manager:wrapper")}/public-spawn`, {
|
|
11646
12086
|
method: "POST",
|
|
11647
12087
|
headers: { "content-type": "application/json" },
|
|
11648
12088
|
body: upstreamPayload
|
|
11649
12089
|
}).catch((err) => {
|
|
11650
|
-
console.error(`${
|
|
12090
|
+
console.error(`${TAG28} fetch-failed op=spawn message=${err instanceof Error ? err.message : String(err)} ms=${Date.now() - forwardStart}`);
|
|
11651
12091
|
return null;
|
|
11652
12092
|
});
|
|
11653
12093
|
if (!upstream) return {
|
|
11654
12094
|
response: new Response(JSON.stringify({ error: "manager-unreachable" }), { status: 503, headers: { "content-type": "application/json" } }),
|
|
11655
12095
|
claudeSessionId: null
|
|
11656
12096
|
};
|
|
11657
|
-
console.log(`${
|
|
12097
|
+
console.log(`${TAG28} forward-spawn-done status=${upstream.status} ms=${Date.now() - forwardStart}`);
|
|
11658
12098
|
if (args.initialMessage) {
|
|
11659
12099
|
const inputBytes = Buffer.byteLength(args.initialMessage, "utf8");
|
|
11660
|
-
console.log(`${
|
|
12100
|
+
console.log(`${TAG28} initial-message-inlined bytes=${inputBytes}`);
|
|
11661
12101
|
}
|
|
11662
12102
|
const bodyText = await upstream.text().catch(() => "");
|
|
11663
12103
|
let claudeSessionId = null;
|
|
@@ -11692,7 +12132,7 @@ app17.post("/", async (c) => {
|
|
|
11692
12132
|
if (refusal) return refusal;
|
|
11693
12133
|
const senderId = getAccountIdForSession(cacheKey) ?? "";
|
|
11694
12134
|
if (!senderId) {
|
|
11695
|
-
console.error(`${
|
|
12135
|
+
console.error(`${TAG28} reject reason=no-account-id cacheKey-prefix=${cacheKey.slice(0, 8)}`);
|
|
11696
12136
|
return c.json({ error: "admin-account-not-resolved" }, 500);
|
|
11697
12137
|
}
|
|
11698
12138
|
const userId = getUserIdForSession(cacheKey) ?? void 0;
|
|
@@ -11701,7 +12141,7 @@ app17.post("/", async (c) => {
|
|
|
11701
12141
|
const permissionMode = typeof body.permissionMode === "string" ? body.permissionMode : void 0;
|
|
11702
12142
|
const specialist = typeof body.specialist === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(body.specialist) ? body.specialist : void 0;
|
|
11703
12143
|
const model = typeof body.model === "string" && /^[A-Za-z0-9._-]{1,64}$/.test(body.model) ? body.model : void 0;
|
|
11704
|
-
console.log(`${
|
|
12144
|
+
console.log(`${TAG28} 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"}`);
|
|
11705
12145
|
const conversationNodeId = cacheKey ? getSessionIdForSession(cacheKey) : void 0;
|
|
11706
12146
|
const { response, claudeSessionId } = await performSpawnWithInitialMessage({
|
|
11707
12147
|
senderId,
|
|
@@ -11720,13 +12160,13 @@ app17.post("/", async (c) => {
|
|
|
11720
12160
|
claudeSessionId,
|
|
11721
12161
|
senderId
|
|
11722
12162
|
);
|
|
11723
|
-
console.log(`${
|
|
12163
|
+
console.log(`${TAG28} route-done surface=cookie status=${response.status} route-ms=${Date.now() - routeStart}`);
|
|
11724
12164
|
return response;
|
|
11725
12165
|
});
|
|
11726
12166
|
var claude_sessions_default = app17;
|
|
11727
12167
|
|
|
11728
12168
|
// server/routes/admin/log-ingest.ts
|
|
11729
|
-
var
|
|
12169
|
+
var TAG29 = "[log-ingest]";
|
|
11730
12170
|
var TAG_PATTERN = /^[A-Za-z0-9_:-]{1,32}$/;
|
|
11731
12171
|
var LEVELS = /* @__PURE__ */ new Set(["debug", "info", "warn", "error"]);
|
|
11732
12172
|
var MAX_LINE_BYTES = 4096;
|
|
@@ -11737,7 +12177,7 @@ function isLoopbackAddr(addr) {
|
|
|
11737
12177
|
app18.post("/", async (c) => {
|
|
11738
12178
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
11739
12179
|
if (!isLoopbackAddr(remoteAddr)) {
|
|
11740
|
-
console.error(`${
|
|
12180
|
+
console.error(`${TAG29} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
11741
12181
|
return c.json({ error: "log-ingest-loopback-only" }, 403);
|
|
11742
12182
|
}
|
|
11743
12183
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
@@ -11746,7 +12186,7 @@ app18.post("/", async (c) => {
|
|
|
11746
12186
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
11747
12187
|
const offender = tokens.find((t) => !isLoopbackAddr(t));
|
|
11748
12188
|
if (offender !== void 0) {
|
|
11749
|
-
console.error(`${
|
|
12189
|
+
console.error(`${TAG29} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
11750
12190
|
return c.json({ error: "log-ingest-loopback-only" }, 403);
|
|
11751
12191
|
}
|
|
11752
12192
|
}
|
|
@@ -11788,18 +12228,18 @@ var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
|
|
|
11788
12228
|
]);
|
|
11789
12229
|
var app19 = new Hono();
|
|
11790
12230
|
app19.post("/", async (c) => {
|
|
11791
|
-
const
|
|
12231
|
+
const TAG38 = "[admin:events]";
|
|
11792
12232
|
let body;
|
|
11793
12233
|
try {
|
|
11794
12234
|
body = await c.req.json();
|
|
11795
12235
|
} catch (err) {
|
|
11796
12236
|
const detail = err instanceof Error ? err.message : String(err);
|
|
11797
|
-
console.error(`${
|
|
12237
|
+
console.error(`${TAG38} reject reason=body-not-json detail=${detail}`);
|
|
11798
12238
|
return c.json({ ok: false, detail: "Request body was not valid JSON" }, 400);
|
|
11799
12239
|
}
|
|
11800
12240
|
const event = typeof body.event === "string" ? body.event : "";
|
|
11801
12241
|
if (!ALLOWED_EVENTS.has(event)) {
|
|
11802
|
-
console.error(`${
|
|
12242
|
+
console.error(`${TAG38} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
|
|
11803
12243
|
return c.json({ ok: false, detail: `Event "${event}" is not allowed` }, 400);
|
|
11804
12244
|
}
|
|
11805
12245
|
const rawFields = body.fields && typeof body.fields === "object" ? body.fields : {};
|
|
@@ -11822,10 +12262,10 @@ var events_default = app19;
|
|
|
11822
12262
|
|
|
11823
12263
|
// server/routes/admin/files.ts
|
|
11824
12264
|
import { createReadStream as createReadStream2, createWriteStream as createWriteStream2, existsSync as existsSync23 } from "fs";
|
|
11825
|
-
import { readdir as readdir3, readFile as
|
|
11826
|
-
import { realpathSync as
|
|
12265
|
+
import { readdir as readdir3, readFile as readFile5, stat as stat5, mkdir as mkdir3, unlink as unlink2, rename } from "fs/promises";
|
|
12266
|
+
import { realpathSync as realpathSync6 } from "fs";
|
|
11827
12267
|
import { randomUUID as randomUUID10 } from "crypto";
|
|
11828
|
-
import { basename as
|
|
12268
|
+
import { basename as basename8, dirname as dirname6, join as join24, resolve as resolve24, sep as sep6 } from "path";
|
|
11829
12269
|
import { Readable as Readable2 } from "stream";
|
|
11830
12270
|
|
|
11831
12271
|
// ../lib/graph-trash/src/index.ts
|
|
@@ -12143,7 +12583,7 @@ async function cascadeDeleteDocument(params) {
|
|
|
12143
12583
|
|
|
12144
12584
|
// app/lib/file-index.ts
|
|
12145
12585
|
import * as fsp from "fs/promises";
|
|
12146
|
-
import { resolve as
|
|
12586
|
+
import { resolve as resolve23, relative as relative3, join as join23, basename as basename7, extname as extname2, sep as sep5 } from "path";
|
|
12147
12587
|
import { tmpdir as tmpdir2 } from "os";
|
|
12148
12588
|
import { execFile as execFile2 } from "child_process";
|
|
12149
12589
|
import { promisify as promisify2 } from "util";
|
|
@@ -12225,7 +12665,7 @@ async function extractPdf(absolute) {
|
|
|
12225
12665
|
return stdout.trim();
|
|
12226
12666
|
}
|
|
12227
12667
|
async function ocrPdf(absolute) {
|
|
12228
|
-
const outPdf =
|
|
12668
|
+
const outPdf = join23(tmpdir2(), `file-index-ocr-${process.pid}-${Date.now()}.pdf`);
|
|
12229
12669
|
try {
|
|
12230
12670
|
await execFileAsync2(
|
|
12231
12671
|
"ocrmypdf",
|
|
@@ -12276,18 +12716,18 @@ async function extractFileContent(absolute) {
|
|
|
12276
12716
|
return { content: "", route: "name-only" };
|
|
12277
12717
|
} catch (err) {
|
|
12278
12718
|
const msg = err instanceof Error ? err.message : String(err);
|
|
12279
|
-
console.error(`[file-index] extract-failed path="${
|
|
12719
|
+
console.error(`[file-index] extract-failed path="${relative3(DATA_ROOT, absolute)}" err="${msg}"`);
|
|
12280
12720
|
return { content: "", route: "name-only" };
|
|
12281
12721
|
}
|
|
12282
12722
|
}
|
|
12283
12723
|
async function readDisplayName(absolute) {
|
|
12284
|
-
const dir = absolute.slice(0, absolute.length -
|
|
12285
|
-
const base =
|
|
12724
|
+
const dir = absolute.slice(0, absolute.length - basename7(absolute).length);
|
|
12725
|
+
const base = basename7(absolute);
|
|
12286
12726
|
const dot = base.lastIndexOf(".");
|
|
12287
12727
|
const stem = dot === -1 ? base : base.slice(0, dot);
|
|
12288
12728
|
if (base === `${stem}.meta.json`) return null;
|
|
12289
12729
|
try {
|
|
12290
|
-
const raw = await fsp.readFile(
|
|
12730
|
+
const raw = await fsp.readFile(join23(dir, `${stem}.meta.json`), "utf-8");
|
|
12291
12731
|
const meta = JSON.parse(raw);
|
|
12292
12732
|
const dn = meta.displayName ?? meta.filename;
|
|
12293
12733
|
return typeof dn === "string" && dn.length > 0 ? dn : null;
|
|
@@ -12301,14 +12741,14 @@ async function walkSubtree(root, dataRoot, out) {
|
|
|
12301
12741
|
entries = await fsp.readdir(root, { withFileTypes: true });
|
|
12302
12742
|
} catch (err) {
|
|
12303
12743
|
if (err.code === "ENOENT") return { clean: true };
|
|
12304
|
-
console.error(`[file-index] walk-error dir="${
|
|
12744
|
+
console.error(`[file-index] walk-error dir="${relative3(dataRoot, root)}" err="${err.message}"`);
|
|
12305
12745
|
return { clean: false };
|
|
12306
12746
|
}
|
|
12307
12747
|
let clean = true;
|
|
12308
12748
|
for (const entry of entries) {
|
|
12309
12749
|
if (entry.isSymbolicLink()) continue;
|
|
12310
12750
|
if (entry.isDirectory() && entry.name === ".uploads-tmp") continue;
|
|
12311
|
-
const abs =
|
|
12751
|
+
const abs = join23(root, entry.name);
|
|
12312
12752
|
if (entry.isDirectory()) {
|
|
12313
12753
|
const sub = await walkSubtree(abs, dataRoot, out);
|
|
12314
12754
|
if (!sub.clean) clean = false;
|
|
@@ -12317,12 +12757,12 @@ async function walkSubtree(root, dataRoot, out) {
|
|
|
12317
12757
|
const st = await fsp.stat(abs);
|
|
12318
12758
|
out.push({
|
|
12319
12759
|
absolute: abs,
|
|
12320
|
-
relativePath:
|
|
12760
|
+
relativePath: relative3(dataRoot, abs).split(sep5).join("/"),
|
|
12321
12761
|
sizeBytes: st.size,
|
|
12322
12762
|
modifiedAt: st.mtime.toISOString()
|
|
12323
12763
|
});
|
|
12324
12764
|
} catch (err) {
|
|
12325
|
-
console.error(`[file-index] stat-error path="${
|
|
12765
|
+
console.error(`[file-index] stat-error path="${relative3(dataRoot, abs)}" err="${err.message}"`);
|
|
12326
12766
|
clean = false;
|
|
12327
12767
|
}
|
|
12328
12768
|
}
|
|
@@ -12420,7 +12860,7 @@ async function cascadeTrashLinkedKnowledgeDocs(session, accountId, relativePaths
|
|
|
12420
12860
|
return { kds, sections, chunks };
|
|
12421
12861
|
}
|
|
12422
12862
|
async function buildArtifact(accountId, wf, embed2) {
|
|
12423
|
-
const name =
|
|
12863
|
+
const name = basename7(wf.absolute);
|
|
12424
12864
|
const { content, route } = await extractFileContent(wf.absolute);
|
|
12425
12865
|
const displayName = await readDisplayName(wf.absolute);
|
|
12426
12866
|
const embedInput = `${name}
|
|
@@ -12463,7 +12903,7 @@ async function reconcileFileIndex(accountId, opts) {
|
|
|
12463
12903
|
return withSession(opts, async (session) => {
|
|
12464
12904
|
const walked = [];
|
|
12465
12905
|
const subtreeRoots = [
|
|
12466
|
-
|
|
12906
|
+
resolve23(dataRoot, "accounts", accountId)
|
|
12467
12907
|
];
|
|
12468
12908
|
let clean = true;
|
|
12469
12909
|
for (const root of subtreeRoots) {
|
|
@@ -12546,7 +12986,7 @@ async function reconcileFileIndexDebounced(accountId, opts) {
|
|
|
12546
12986
|
async function indexFile(accountId, relativePath, opts) {
|
|
12547
12987
|
const dataRoot = opts?.dataRoot ?? DATA_ROOT;
|
|
12548
12988
|
const embed2 = opts?.embed ?? embed;
|
|
12549
|
-
const absolute =
|
|
12989
|
+
const absolute = resolve23(dataRoot, relativePath);
|
|
12550
12990
|
await withSession(opts, async (session) => {
|
|
12551
12991
|
const st = await fsp.stat(absolute);
|
|
12552
12992
|
const wf = {
|
|
@@ -12581,7 +13021,7 @@ var UPLOAD_TMP_DIR = ".uploads-tmp";
|
|
|
12581
13021
|
var UUID_RE3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
12582
13022
|
async function readMeta(absDir, baseName) {
|
|
12583
13023
|
try {
|
|
12584
|
-
const raw = await
|
|
13024
|
+
const raw = await readFile5(join24(absDir, `${baseName}.meta.json`), "utf8");
|
|
12585
13025
|
const parsed = JSON.parse(raw);
|
|
12586
13026
|
if (typeof parsed?.filename === "string") {
|
|
12587
13027
|
return { filename: parsed.filename, mimeType: typeof parsed.mimeType === "string" ? parsed.mimeType : void 0 };
|
|
@@ -12592,7 +13032,7 @@ async function readMeta(absDir, baseName) {
|
|
|
12592
13032
|
}
|
|
12593
13033
|
async function readAccountNames() {
|
|
12594
13034
|
const map = /* @__PURE__ */ new Map();
|
|
12595
|
-
const accountsDir =
|
|
13035
|
+
const accountsDir = resolve24(DATA_ROOT, "accounts");
|
|
12596
13036
|
let names;
|
|
12597
13037
|
try {
|
|
12598
13038
|
names = await readdir3(accountsDir);
|
|
@@ -12601,9 +13041,9 @@ async function readAccountNames() {
|
|
|
12601
13041
|
}
|
|
12602
13042
|
for (const name of names) {
|
|
12603
13043
|
if (!UUID_RE3.test(name)) continue;
|
|
12604
|
-
const configPath2 =
|
|
13044
|
+
const configPath2 = resolve24(accountsDir, name, "account.json");
|
|
12605
13045
|
try {
|
|
12606
|
-
const raw = await
|
|
13046
|
+
const raw = await readFile5(configPath2, "utf8");
|
|
12607
13047
|
const parsed = JSON.parse(raw);
|
|
12608
13048
|
if (typeof parsed?.name === "string" && parsed.name.length > 0) {
|
|
12609
13049
|
map.set(name, parsed.name);
|
|
@@ -12619,7 +13059,7 @@ async function readAccountNames() {
|
|
|
12619
13059
|
}
|
|
12620
13060
|
async function enrich(absolute, entry, accountNames) {
|
|
12621
13061
|
if (entry.kind === "directory" && UUID_RE3.test(entry.name)) {
|
|
12622
|
-
const meta = await readMeta(
|
|
13062
|
+
const meta = await readMeta(join24(absolute, entry.name), entry.name);
|
|
12623
13063
|
if (meta?.filename) {
|
|
12624
13064
|
entry.displayName = meta.filename;
|
|
12625
13065
|
entry.mimeType = meta.mimeType;
|
|
@@ -12765,7 +13205,7 @@ app20.get("/", requireAdminSession, async (c) => {
|
|
|
12765
13205
|
return c.json({ error: "Not found" }, 404);
|
|
12766
13206
|
}
|
|
12767
13207
|
try {
|
|
12768
|
-
const info = await
|
|
13208
|
+
const info = await stat5(absolute);
|
|
12769
13209
|
if (!info.isDirectory()) {
|
|
12770
13210
|
return c.json({ error: "Path is not a directory \u2014 use /api/admin/files/download for files" }, 400);
|
|
12771
13211
|
}
|
|
@@ -12783,8 +13223,8 @@ app20.get("/", requireAdminSession, async (c) => {
|
|
|
12783
13223
|
const childRel = relPath === "" || relPath === "." ? name : `${relPath}/${name}`;
|
|
12784
13224
|
const protectedEntry = isProtectedFromDeletion(childRel).protected;
|
|
12785
13225
|
try {
|
|
12786
|
-
const entryPath =
|
|
12787
|
-
const s = await
|
|
13226
|
+
const entryPath = join24(absolute, name);
|
|
13227
|
+
const s = await stat5(entryPath);
|
|
12788
13228
|
entries.push({
|
|
12789
13229
|
name,
|
|
12790
13230
|
kind: s.isDirectory() ? "directory" : s.isFile() ? "file" : "other",
|
|
@@ -12846,11 +13286,11 @@ app20.get("/download", requireAdminSession, async (c) => {
|
|
|
12846
13286
|
return c.json({ error: "Not found" }, 404);
|
|
12847
13287
|
}
|
|
12848
13288
|
try {
|
|
12849
|
-
const info = await
|
|
13289
|
+
const info = await stat5(absolute);
|
|
12850
13290
|
if (!info.isFile()) {
|
|
12851
13291
|
return c.json({ error: "Path is not a file" }, 400);
|
|
12852
13292
|
}
|
|
12853
|
-
const filename =
|
|
13293
|
+
const filename = basename8(absolute);
|
|
12854
13294
|
const mimeType = detectMimeType(absolute);
|
|
12855
13295
|
const nodeStream = createReadStream2(absolute);
|
|
12856
13296
|
const webStream = Readable2.toWeb(nodeStream);
|
|
@@ -12889,14 +13329,14 @@ app20.post("/upload", requireAdminSession, async (c) => {
|
|
|
12889
13329
|
return c.json({ error: "Account not found for session" }, 401);
|
|
12890
13330
|
}
|
|
12891
13331
|
const rawName = c.req.query("filename") ?? "";
|
|
12892
|
-
const safeName =
|
|
13332
|
+
const safeName = basename8(rawName).replace(/[\0/\\]/g, "_");
|
|
12893
13333
|
if (!safeName) return c.json({ error: "filename query param required" }, 400);
|
|
12894
13334
|
const uid = `${Date.now()}-${randomUUID10().slice(0, 8)}`;
|
|
12895
13335
|
const declaredBytes = c.req.header("content-length") ?? "unknown";
|
|
12896
13336
|
const rawRelpath = c.req.query("relpath") ?? "";
|
|
12897
13337
|
const relpath = rawRelpath !== "" ? rawRelpath : null;
|
|
12898
13338
|
const token = c.req.query("token") ?? "";
|
|
12899
|
-
const finalName = relpath ?
|
|
13339
|
+
const finalName = relpath ? basename8(relpath).replace(/[\0/\\]/g, "_") : `${Date.now()}-${safeName}`;
|
|
12900
13340
|
const mimeType = (c.req.header("content-type") ?? "").split(";")[0].trim();
|
|
12901
13341
|
if (!SUPPORTED_MIME_TYPES.has(mimeType)) {
|
|
12902
13342
|
console.error(`[data-upload] op=reject-mime uid=${uid} mime="${mimeType}" token=${token} rel="${relpath ?? ""}"`);
|
|
@@ -12908,7 +13348,7 @@ app20.post("/upload", requireAdminSession, async (c) => {
|
|
|
12908
13348
|
const base = resolveOwnAccountWrite(rawDir, accountId, "POST /api/admin/files/upload");
|
|
12909
13349
|
if (!base.ok) return c.json({ error: base.error }, base.status);
|
|
12910
13350
|
const relDir = relpath ? dirname6(relpath) : ".";
|
|
12911
|
-
const destRel = relDir === "." ? base.relative :
|
|
13351
|
+
const destRel = relDir === "." ? base.relative : join24(base.relative, relDir);
|
|
12912
13352
|
if (crossesForeignAccountPartition(destRel, accountId)) {
|
|
12913
13353
|
console.error(`[data] account-scope-blocked endpoint="POST /api/admin/files/upload" path="${destRel}" account=${accountId.slice(0, 8)}\u2026`);
|
|
12914
13354
|
return c.json({ error: "Not found" }, 404);
|
|
@@ -12917,17 +13357,17 @@ app20.post("/upload", requireAdminSession, async (c) => {
|
|
|
12917
13357
|
console.error(`[data] write-scope-blocked endpoint="POST /api/admin/files/upload" path="${destRel}" account=${accountId.slice(0, 8)}\u2026`);
|
|
12918
13358
|
return c.json({ error: "Path is outside your account folder" }, 403);
|
|
12919
13359
|
}
|
|
12920
|
-
const destDir = relDir === "." ? base.absolute :
|
|
12921
|
-
const destPath =
|
|
13360
|
+
const destDir = relDir === "." ? base.absolute : resolve24(base.absolute, relDir);
|
|
13361
|
+
const destPath = resolve24(destDir, finalName);
|
|
12922
13362
|
if (relpath) {
|
|
12923
13363
|
const firstCreated = await mkdir3(destDir, { recursive: true });
|
|
12924
13364
|
if (firstCreated) console.error(`[data-upload] op=mkdir uid=${uid} token=${token} dir="${destRel}"`);
|
|
12925
13365
|
} else {
|
|
12926
|
-
const info = await
|
|
13366
|
+
const info = await stat5(destDir).catch(() => null);
|
|
12927
13367
|
if (!info || !info.isDirectory()) return c.json({ error: "Upload destination is not a directory" }, 400);
|
|
12928
13368
|
}
|
|
12929
|
-
const dataRootReal =
|
|
12930
|
-
const destDirReal =
|
|
13369
|
+
const dataRootReal = realpathSync6(DATA_ROOT);
|
|
13370
|
+
const destDirReal = realpathSync6(destDir);
|
|
12931
13371
|
if (destDirReal !== dataRootReal && !destDirReal.startsWith(dataRootReal + sep6)) {
|
|
12932
13372
|
console.error(`[data-upload] op=path-traversal-blocked uid=${uid} requested="${destRel}" resolved="${destDirReal}"`);
|
|
12933
13373
|
return c.json({ error: "Upload destination escapes DATA_ROOT" }, 403);
|
|
@@ -12936,9 +13376,9 @@ app20.post("/upload", requireAdminSession, async (c) => {
|
|
|
12936
13376
|
if (!body) return c.json({ error: "Empty request body" }, 400);
|
|
12937
13377
|
console.error(`[data-upload] op=request uid=${uid} account=${accountId} dir="${destRel}" token=${token} rel="${relpath ?? ""}" declaredBytes=${declaredBytes}`);
|
|
12938
13378
|
const ceiling = dataUploadCeiling();
|
|
12939
|
-
const tmpDir =
|
|
13379
|
+
const tmpDir = resolve24(DATA_ROOT, "accounts", accountId, UPLOAD_TMP_DIR);
|
|
12940
13380
|
await mkdir3(tmpDir, { recursive: true });
|
|
12941
|
-
const tmpPath =
|
|
13381
|
+
const tmpPath = resolve24(tmpDir, `${uid}.part`);
|
|
12942
13382
|
const out = createWriteStream2(tmpPath);
|
|
12943
13383
|
const t0 = Date.now();
|
|
12944
13384
|
let received = 0;
|
|
@@ -13020,7 +13460,7 @@ app20.post("/upload", requireAdminSession, async (c) => {
|
|
|
13020
13460
|
});
|
|
13021
13461
|
});
|
|
13022
13462
|
async function sweepStaleUploadTemps() {
|
|
13023
|
-
const accountsRoot =
|
|
13463
|
+
const accountsRoot = resolve24(DATA_ROOT, "accounts");
|
|
13024
13464
|
let accounts;
|
|
13025
13465
|
try {
|
|
13026
13466
|
accounts = await readdir3(accountsRoot, { withFileTypes: true });
|
|
@@ -13030,7 +13470,7 @@ async function sweepStaleUploadTemps() {
|
|
|
13030
13470
|
let removed = 0;
|
|
13031
13471
|
for (const acc of accounts) {
|
|
13032
13472
|
if (!acc.isDirectory()) continue;
|
|
13033
|
-
const tmpDir =
|
|
13473
|
+
const tmpDir = resolve24(accountsRoot, acc.name, UPLOAD_TMP_DIR);
|
|
13034
13474
|
let entries;
|
|
13035
13475
|
try {
|
|
13036
13476
|
entries = await readdir3(tmpDir);
|
|
@@ -13039,7 +13479,7 @@ async function sweepStaleUploadTemps() {
|
|
|
13039
13479
|
}
|
|
13040
13480
|
for (const name of entries) {
|
|
13041
13481
|
try {
|
|
13042
|
-
await unlink2(
|
|
13482
|
+
await unlink2(resolve24(tmpDir, name));
|
|
13043
13483
|
removed++;
|
|
13044
13484
|
} catch {
|
|
13045
13485
|
}
|
|
@@ -13069,14 +13509,14 @@ app20.delete("/", requireAdminSession, async (c) => {
|
|
|
13069
13509
|
console.error(`[data] account-scope-blocked endpoint="DELETE /api/admin/files" path="${relPath}" account=${accountId.slice(0, 8)}\u2026`);
|
|
13070
13510
|
return c.json({ error: "Not found" }, 404);
|
|
13071
13511
|
}
|
|
13072
|
-
const base =
|
|
13512
|
+
const base = basename8(absolute);
|
|
13073
13513
|
const protection = isProtectedFromDeletion(relPath);
|
|
13074
13514
|
if (protection.protected) {
|
|
13075
13515
|
console.error(`[data] file-delete blocked path="${relPath}" reason="protected" rule="${protection.rule}"`);
|
|
13076
13516
|
return c.json({ error: "Protected file \u2014 refusing to delete" }, 403);
|
|
13077
13517
|
}
|
|
13078
13518
|
try {
|
|
13079
|
-
const info = await
|
|
13519
|
+
const info = await stat5(absolute);
|
|
13080
13520
|
if (info.isDirectory()) {
|
|
13081
13521
|
return c.json({ error: "Directory deletion not supported" }, 400);
|
|
13082
13522
|
}
|
|
@@ -13085,7 +13525,7 @@ app20.delete("/", requireAdminSession, async (c) => {
|
|
|
13085
13525
|
}
|
|
13086
13526
|
const dot = base.lastIndexOf(".");
|
|
13087
13527
|
const stem = dot === -1 ? base : base.slice(0, dot);
|
|
13088
|
-
const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ?
|
|
13528
|
+
const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ? join24(dirname6(absolute), `${stem}.meta.json`) : null;
|
|
13089
13529
|
await unlink2(absolute);
|
|
13090
13530
|
if (sidecarPath) {
|
|
13091
13531
|
try {
|
|
@@ -13142,10 +13582,10 @@ app20.post("/folder", requireAdminSession, async (c) => {
|
|
|
13142
13582
|
if (!isValidEntryName(name)) return c.json({ error: "Invalid folder name" }, 400);
|
|
13143
13583
|
const parent = resolveOwnAccountWrite(rawPath, accountId, "POST /api/admin/files/folder");
|
|
13144
13584
|
if (!parent.ok) return c.json({ error: parent.error }, parent.status);
|
|
13145
|
-
const newAbs =
|
|
13585
|
+
const newAbs = resolve24(parent.absolute, name);
|
|
13146
13586
|
const newRel = `${parent.relative}/${name}`;
|
|
13147
13587
|
try {
|
|
13148
|
-
const info = await
|
|
13588
|
+
const info = await stat5(parent.absolute);
|
|
13149
13589
|
if (!info.isDirectory()) return c.json({ error: "Parent is not a directory" }, 400);
|
|
13150
13590
|
await mkdir3(newAbs, { recursive: false });
|
|
13151
13591
|
} catch (err) {
|
|
@@ -13181,7 +13621,7 @@ app20.post("/rename", requireAdminSession, async (c) => {
|
|
|
13181
13621
|
console.error(`[data] file-rename blocked path="${src.relative}" reason="protected"`);
|
|
13182
13622
|
return c.json({ error: "Protected entry \u2014 refusing to rename" }, 403);
|
|
13183
13623
|
}
|
|
13184
|
-
const destAbs =
|
|
13624
|
+
const destAbs = resolve24(dirname6(src.absolute), newName);
|
|
13185
13625
|
const newRel = `${dirname6(src.relative)}/${newName}`.replace(/^\.\//, "");
|
|
13186
13626
|
if (isProtectedFromRename(newRel)) {
|
|
13187
13627
|
console.error(`[data] file-rename blocked path="${newRel}" reason="protected-target"`);
|
|
@@ -13190,7 +13630,7 @@ app20.post("/rename", requireAdminSession, async (c) => {
|
|
|
13190
13630
|
try {
|
|
13191
13631
|
let collision = true;
|
|
13192
13632
|
try {
|
|
13193
|
-
await
|
|
13633
|
+
await stat5(destAbs);
|
|
13194
13634
|
} catch (e) {
|
|
13195
13635
|
if (e.code === "ENOENT") collision = false;
|
|
13196
13636
|
}
|
|
@@ -13389,13 +13829,6 @@ function readFlags() {
|
|
|
13389
13829
|
};
|
|
13390
13830
|
}
|
|
13391
13831
|
var FULLTEXT_INDEX_ADMIN = "entity_search_admin";
|
|
13392
|
-
var FULLTEXT_INDEX_PUBLIC = "entity_search_public";
|
|
13393
|
-
function pickFulltextIndex(allowedScopes) {
|
|
13394
|
-
return isPublicOnlyScope(allowedScopes) ? FULLTEXT_INDEX_PUBLIC : FULLTEXT_INDEX_ADMIN;
|
|
13395
|
-
}
|
|
13396
|
-
function isPublicOnlyScope(allowedScopes) {
|
|
13397
|
-
return Array.isArray(allowedScopes) && allowedScopes.length === 1 && allowedScopes[0] === "public";
|
|
13398
|
-
}
|
|
13399
13832
|
function buildScopeAndSliceClause(allowedScopes, sliceToken, alias = "node") {
|
|
13400
13833
|
const hasSlice = typeof sliceToken === "string" && sliceToken.length > 0;
|
|
13401
13834
|
const hasScopes = Array.isArray(allowedScopes) && allowedScopes.length > 0;
|
|
@@ -13475,7 +13908,7 @@ async function bm25Only(session, params) {
|
|
|
13475
13908
|
ORDER BY score DESC
|
|
13476
13909
|
LIMIT $limit`,
|
|
13477
13910
|
{
|
|
13478
|
-
indexName:
|
|
13911
|
+
indexName: FULLTEXT_INDEX_ADMIN,
|
|
13479
13912
|
query: escaped,
|
|
13480
13913
|
accountId,
|
|
13481
13914
|
limit: int(limit),
|
|
@@ -14149,8 +14582,6 @@ var FILTER_TOP_LEVEL_LABELS = Object.freeze(
|
|
|
14149
14582
|
"Job",
|
|
14150
14583
|
"QuoteDocument",
|
|
14151
14584
|
"InboundInvoice",
|
|
14152
|
-
"SubContractor",
|
|
14153
|
-
"SubInvoice",
|
|
14154
14585
|
"WhatsAppGroup",
|
|
14155
14586
|
"AdminConversation",
|
|
14156
14587
|
"PublicConversation",
|
|
@@ -15218,14 +15649,14 @@ var WRITE_CYPHER = `
|
|
|
15218
15649
|
var graph_default_view_default = app26;
|
|
15219
15650
|
|
|
15220
15651
|
// server/routes/admin/sidebar-artefacts.ts
|
|
15221
|
-
import { readdir as readdir4, stat as
|
|
15222
|
-
import { resolve as
|
|
15652
|
+
import { readdir as readdir4, stat as stat6 } from "fs/promises";
|
|
15653
|
+
import { resolve as resolve25, relative as relative4, isAbsolute as isAbsolute2, sep as sep7, basename as basename9 } from "path";
|
|
15223
15654
|
import { existsSync as existsSync24 } from "fs";
|
|
15224
15655
|
var LIMIT = 50;
|
|
15225
15656
|
var ADMIN_AGENT_FILES = ["IDENTITY.md", "SOUL.md", "KNOWLEDGE.md"];
|
|
15226
15657
|
function toDataRootRelPath(absPath) {
|
|
15227
15658
|
if (absPath !== DATA_ROOT && !absPath.startsWith(DATA_ROOT + sep7)) return null;
|
|
15228
|
-
return
|
|
15659
|
+
return relative4(DATA_ROOT, absPath);
|
|
15229
15660
|
}
|
|
15230
15661
|
var app27 = new Hono();
|
|
15231
15662
|
app27.get("/", requireAdminSession, async (c) => {
|
|
@@ -15239,7 +15670,7 @@ app27.get("/", requireAdminSession, async (c) => {
|
|
|
15239
15670
|
if (accountFiles === null) {
|
|
15240
15671
|
return c.json({ error: "Failed to load artefacts" }, 500);
|
|
15241
15672
|
}
|
|
15242
|
-
const accountDir =
|
|
15673
|
+
const accountDir = resolve25(ACCOUNTS_DIR, accountId);
|
|
15243
15674
|
const agents = await fetchAgentTemplateRows(accountDir);
|
|
15244
15675
|
const artefacts = [...accountFiles, ...agents].sort(
|
|
15245
15676
|
(a, b) => (b.updatedAt ?? "").localeCompare(a.updatedAt ?? "")
|
|
@@ -15272,7 +15703,7 @@ async function fetchAccountFileArtefacts(accountId) {
|
|
|
15272
15703
|
const modifiedAt = r.get("modifiedAt") ?? "";
|
|
15273
15704
|
return {
|
|
15274
15705
|
id: `account-file:${relativePath}`,
|
|
15275
|
-
name: displayName ??
|
|
15706
|
+
name: displayName ?? basename9(relativePath),
|
|
15276
15707
|
kind: "account-file",
|
|
15277
15708
|
updatedAt: modifiedAt,
|
|
15278
15709
|
mimeType,
|
|
@@ -15291,8 +15722,8 @@ async function fetchAccountFileArtefacts(accountId) {
|
|
|
15291
15722
|
async function fetchAgentTemplateRows(accountDir) {
|
|
15292
15723
|
const rows = [];
|
|
15293
15724
|
for (const filename of ADMIN_AGENT_FILES) {
|
|
15294
|
-
const overridePath =
|
|
15295
|
-
const bundledPath =
|
|
15725
|
+
const overridePath = resolve25(accountDir, "agents", "admin", filename);
|
|
15726
|
+
const bundledPath = resolve25(PLATFORM_ROOT, "templates", "agents", "admin", filename);
|
|
15296
15727
|
const labelStem = filename.replace(/\.md$/, "");
|
|
15297
15728
|
const row = await readAgentTemplateRow({
|
|
15298
15729
|
id: `agent-template:admin:${filename}`,
|
|
@@ -15305,12 +15736,12 @@ async function fetchAgentTemplateRows(accountDir) {
|
|
|
15305
15736
|
});
|
|
15306
15737
|
if (row) rows.push(row);
|
|
15307
15738
|
}
|
|
15308
|
-
const overrideDir =
|
|
15309
|
-
const bundledDir =
|
|
15739
|
+
const overrideDir = resolve25(accountDir, "specialists", "agents");
|
|
15740
|
+
const bundledDir = resolve25(PLATFORM_ROOT, "templates", "specialists", "agents");
|
|
15310
15741
|
const specialistNames = await unionSpecialistFilenames(overrideDir, bundledDir);
|
|
15311
15742
|
for (const filename of specialistNames) {
|
|
15312
|
-
const overridePath =
|
|
15313
|
-
const bundledPath =
|
|
15743
|
+
const overridePath = resolve25(overrideDir, filename);
|
|
15744
|
+
const bundledPath = resolve25(bundledDir, filename);
|
|
15314
15745
|
const row = await readAgentTemplateRow({
|
|
15315
15746
|
id: `agent-template:specialist:${filename}`,
|
|
15316
15747
|
displayName: filename.replace(/\.md$/, ""),
|
|
@@ -15364,7 +15795,7 @@ async function readAgentTemplateRow(inp) {
|
|
|
15364
15795
|
}
|
|
15365
15796
|
if (!chosenPath) return null;
|
|
15366
15797
|
try {
|
|
15367
|
-
const st = await
|
|
15798
|
+
const st = await stat6(chosenPath);
|
|
15368
15799
|
const dlPath = toDataRootRelPath(chosenPath);
|
|
15369
15800
|
return {
|
|
15370
15801
|
id: inp.id,
|
|
@@ -15389,8 +15820,8 @@ async function readAgentTemplateRow(inp) {
|
|
|
15389
15820
|
}
|
|
15390
15821
|
}
|
|
15391
15822
|
function isWithin(target, root) {
|
|
15392
|
-
const rel =
|
|
15393
|
-
return !rel.startsWith("..") && !
|
|
15823
|
+
const rel = relative4(root, target);
|
|
15824
|
+
return !rel.startsWith("..") && !isAbsolute2(rel);
|
|
15394
15825
|
}
|
|
15395
15826
|
var sidebar_artefacts_default = app27;
|
|
15396
15827
|
|
|
@@ -15676,6 +16107,8 @@ function isSelectableModel(id) {
|
|
|
15676
16107
|
}
|
|
15677
16108
|
|
|
15678
16109
|
// server/routes/admin/session-reseat.ts
|
|
16110
|
+
var RESEAT_PERMISSION_MODES = /* @__PURE__ */ new Set(["default", "acceptEdits", "plan", "auto", "bypassPermissions"]);
|
|
16111
|
+
var RESEAT_EFFORT_LEVELS = /* @__PURE__ */ new Set(["low", "medium", "high", "xhigh"]);
|
|
15679
16112
|
function resolveReseatPlan(body, mintId = randomUUID12, adminUserId, authenticatedName) {
|
|
15680
16113
|
const sessionId = mintId();
|
|
15681
16114
|
const key = `session:${sessionId}`;
|
|
@@ -15689,6 +16122,8 @@ function resolveReseatPlan(body, mintId = randomUUID12, adminUserId, authenticat
|
|
|
15689
16122
|
webchatChannel: webchatChannelDescriptor(key)
|
|
15690
16123
|
};
|
|
15691
16124
|
if (body.name) payload.name = body.name;
|
|
16125
|
+
if (body.permissionMode) payload.permissionMode = body.permissionMode;
|
|
16126
|
+
if (body.effort) payload.effort = body.effort;
|
|
15692
16127
|
if (adminUserId) payload.adminUserId = adminUserId;
|
|
15693
16128
|
if (authenticatedName) payload.authenticatedName = authenticatedName;
|
|
15694
16129
|
return { sessionId, payload };
|
|
@@ -15706,6 +16141,10 @@ app33.post("/", requireAdminSession, async (c) => {
|
|
|
15706
16141
|
const model = typeof body.model === "string" && isSelectableModel(body.model) ? body.model : null;
|
|
15707
16142
|
if (fromSessionId === null) return c.json({ error: "fromSessionId must be a valid session id" }, 400);
|
|
15708
16143
|
if (model === null) return c.json({ error: "model must be a selectable model" }, 400);
|
|
16144
|
+
const permissionMode = typeof body.permissionMode === "string" ? RESEAT_PERMISSION_MODES.has(body.permissionMode) ? body.permissionMode : null : void 0;
|
|
16145
|
+
const effort = typeof body.effort === "string" ? RESEAT_EFFORT_LEVELS.has(body.effort) ? body.effort : null : void 0;
|
|
16146
|
+
if (permissionMode === null) return c.json({ error: "permissionMode must be a composer-writable mode" }, 400);
|
|
16147
|
+
if (effort === null) return c.json({ error: "effort must be an accepted level" }, 400);
|
|
15709
16148
|
if (reseatsInFlight.has(fromSessionId)) {
|
|
15710
16149
|
return c.json({ error: "re-seat already in progress for this session" }, 409);
|
|
15711
16150
|
}
|
|
@@ -15715,9 +16154,14 @@ app33.post("/", requireAdminSession, async (c) => {
|
|
|
15715
16154
|
const adminUserId = cacheKey ? getUserIdForSession(cacheKey) : void 0;
|
|
15716
16155
|
const accountId = cacheKey ? getAccountIdForSession(cacheKey) : void 0;
|
|
15717
16156
|
const authenticatedName = accountId ? await resolveAuthenticatedName(accountId, adminUserId) : void 0;
|
|
15718
|
-
const plan = resolveReseatPlan(
|
|
16157
|
+
const plan = resolveReseatPlan(
|
|
16158
|
+
{ fromSessionId, model, ...permissionMode ? { permissionMode } : {}, ...effort ? { effort } : {} },
|
|
16159
|
+
randomUUID12,
|
|
16160
|
+
adminUserId,
|
|
16161
|
+
authenticatedName
|
|
16162
|
+
);
|
|
15719
16163
|
const reseatReqId = randomUUID12();
|
|
15720
|
-
console.log(`[chat-reseat] op=request reseatReqId=${reseatReqId} from=${fromSessionId} to-model=${model} new=${plan.sessionId}`);
|
|
16164
|
+
console.log(`[chat-reseat] op=request reseatReqId=${reseatReqId} from=${fromSessionId} to-model=${model} to-mode=${permissionMode ?? "none"} to-effort=${effort ?? "none"} new=${plan.sessionId}`);
|
|
15721
16165
|
let res;
|
|
15722
16166
|
try {
|
|
15723
16167
|
res = await fetch(`${managerBase("session-reseat:wrapper")}/rc-spawn`, {
|
|
@@ -15769,7 +16213,7 @@ app33.post("/", requireAdminSession, async (c) => {
|
|
|
15769
16213
|
var session_reseat_default = app33;
|
|
15770
16214
|
|
|
15771
16215
|
// server/routes/admin/system-stats.ts
|
|
15772
|
-
import { readFile as
|
|
16216
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
15773
16217
|
import { cpus, loadavg, totalmem, freemem } from "os";
|
|
15774
16218
|
function parseCpuStat(blob) {
|
|
15775
16219
|
const line = blob.split("\n", 1)[0] ?? "";
|
|
@@ -15813,11 +16257,11 @@ function parseMeminfo(blob) {
|
|
|
15813
16257
|
}
|
|
15814
16258
|
var SAMPLE_DELAY_MS = 100;
|
|
15815
16259
|
async function sampleLinux() {
|
|
15816
|
-
const a = parseCpuStat(await
|
|
16260
|
+
const a = parseCpuStat(await readFile6("/proc/stat", "utf-8"));
|
|
15817
16261
|
await new Promise((r) => setTimeout(r, SAMPLE_DELAY_MS));
|
|
15818
|
-
const b = parseCpuStat(await
|
|
15819
|
-
const meminfo = parseMeminfo(await
|
|
15820
|
-
const loadavgRaw = (await
|
|
16262
|
+
const b = parseCpuStat(await readFile6("/proc/stat", "utf-8"));
|
|
16263
|
+
const meminfo = parseMeminfo(await readFile6("/proc/meminfo", "utf-8"));
|
|
16264
|
+
const loadavgRaw = (await readFile6("/proc/loadavg", "utf-8")).trim();
|
|
15821
16265
|
const [l1, l5, l15] = loadavgRaw.split(/\s+/, 3).map(Number);
|
|
15822
16266
|
if (![l1, l5, l15].every(Number.isFinite)) {
|
|
15823
16267
|
throw new Error(`malformed /proc/loadavg: ${loadavgRaw}`);
|
|
@@ -15890,24 +16334,24 @@ app34.get("/", async (c) => {
|
|
|
15890
16334
|
var system_stats_default = app34;
|
|
15891
16335
|
|
|
15892
16336
|
// server/routes/admin/health.ts
|
|
15893
|
-
import { existsSync as existsSync25, readFileSync as
|
|
15894
|
-
import { resolve as
|
|
15895
|
-
var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT ??
|
|
16337
|
+
import { existsSync as existsSync25, readFileSync as readFileSync26 } from "fs";
|
|
16338
|
+
import { resolve as resolve26, join as join25 } from "path";
|
|
16339
|
+
var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT ?? resolve26(process.cwd(), "..");
|
|
15896
16340
|
var brandHostname = "maxy";
|
|
15897
|
-
var brandJsonPath =
|
|
16341
|
+
var brandJsonPath = join25(PLATFORM_ROOT6, "config", "brand.json");
|
|
15898
16342
|
if (existsSync25(brandJsonPath)) {
|
|
15899
16343
|
try {
|
|
15900
|
-
const brand = JSON.parse(
|
|
16344
|
+
const brand = JSON.parse(readFileSync26(brandJsonPath, "utf-8"));
|
|
15901
16345
|
if (brand.hostname) brandHostname = brand.hostname;
|
|
15902
16346
|
} catch {
|
|
15903
16347
|
}
|
|
15904
16348
|
}
|
|
15905
|
-
var VERSION_FILE =
|
|
16349
|
+
var VERSION_FILE = resolve26(PLATFORM_ROOT6, `config/.${brandHostname}-version`);
|
|
15906
16350
|
var PROCESS_STARTED_AT = (/* @__PURE__ */ new Date()).toISOString();
|
|
15907
16351
|
var PROBE_TIMEOUT_MS = 1e3;
|
|
15908
16352
|
function readVersion() {
|
|
15909
16353
|
if (!existsSync25(VERSION_FILE)) return "unknown";
|
|
15910
|
-
return
|
|
16354
|
+
return readFileSync26(VERSION_FILE, "utf-8").trim() || "unknown";
|
|
15911
16355
|
}
|
|
15912
16356
|
async function probeConversationDb() {
|
|
15913
16357
|
let session;
|
|
@@ -15959,7 +16403,7 @@ var health_default2 = app35;
|
|
|
15959
16403
|
|
|
15960
16404
|
// server/routes/admin/linkedin-ingest.ts
|
|
15961
16405
|
import { randomUUID as randomUUID13 } from "crypto";
|
|
15962
|
-
var
|
|
16406
|
+
var TAG30 = "[linkedin-ingest-route]";
|
|
15963
16407
|
var UUID2 = /^[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}$/;
|
|
15964
16408
|
var ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
15965
16409
|
var LINKEDIN_URL = /^https:\/\/www\.linkedin\.com\//;
|
|
@@ -16063,29 +16507,29 @@ app36.post("/", requireAdminSession, async (c) => {
|
|
|
16063
16507
|
try {
|
|
16064
16508
|
body = await c.req.json();
|
|
16065
16509
|
} catch {
|
|
16066
|
-
console.error(
|
|
16510
|
+
console.error(TAG30 + " rejected status=400 reason=schema:body-not-json");
|
|
16067
16511
|
return c.json({ ok: false, error: "schema", reason: "body-not-json" }, 400);
|
|
16068
16512
|
}
|
|
16069
16513
|
const v = validate(body);
|
|
16070
16514
|
if (!v.ok) {
|
|
16071
|
-
console.error(
|
|
16515
|
+
console.error(TAG30 + " rejected status=" + v.status + " reason=" + v.reason + " missing=" + v.missing.join(","));
|
|
16072
16516
|
return c.json({ ok: false, error: v.error, reason: v.reason, missing: v.missing }, v.status);
|
|
16073
16517
|
}
|
|
16074
16518
|
const envelope = v.envelope;
|
|
16075
16519
|
const cacheKey = c.var.cacheKey ?? "";
|
|
16076
16520
|
const senderId = getAccountIdForSession(cacheKey) ?? "";
|
|
16077
16521
|
if (!senderId) {
|
|
16078
|
-
console.error(
|
|
16522
|
+
console.error(TAG30 + " rejected status=500 reason=admin-account-not-resolved");
|
|
16079
16523
|
return c.json({ ok: false, error: "admin-account-not-resolved" }, 500);
|
|
16080
16524
|
}
|
|
16081
16525
|
const payloadBytes = JSON.stringify(envelope).length;
|
|
16082
16526
|
console.log(
|
|
16083
|
-
|
|
16527
|
+
TAG30 + " received kind=" + envelope.kind + " account=" + senderId.slice(0, 8) + " pageUrl=" + envelope.pageUrl + " dispatchId=" + envelope.dispatchId + " payloadBytes=" + payloadBytes
|
|
16084
16528
|
);
|
|
16085
16529
|
const initialMessage = buildInitialMessage(envelope);
|
|
16086
16530
|
const spawnStart = Date.now();
|
|
16087
16531
|
const sessionId = randomUUID13();
|
|
16088
|
-
console.log(
|
|
16532
|
+
console.log(TAG30 + " route target=rc-spawn dispatchId=" + envelope.dispatchId + " sessionId=" + sessionId.slice(0, 8));
|
|
16089
16533
|
const spawned = await managerRcSpawn({
|
|
16090
16534
|
sessionId,
|
|
16091
16535
|
initialMessage,
|
|
@@ -16094,12 +16538,12 @@ app36.post("/", requireAdminSession, async (c) => {
|
|
|
16094
16538
|
});
|
|
16095
16539
|
if ("error" in spawned) {
|
|
16096
16540
|
console.error(
|
|
16097
|
-
|
|
16541
|
+
TAG30 + " dispatch-failed dispatchId=" + envelope.dispatchId + " status=" + spawned.status + " ms=" + (Date.now() - spawnStart) + " message=" + spawned.error
|
|
16098
16542
|
);
|
|
16099
16543
|
return c.json({ ok: false, error: "dispatch-failed", upstreamStatus: spawned.status, detail: spawned.error }, 502);
|
|
16100
16544
|
}
|
|
16101
16545
|
console.log(
|
|
16102
|
-
|
|
16546
|
+
TAG30 + " dispatched dispatchId=" + envelope.dispatchId + " taskId=" + spawned.sessionId + " ms=" + (Date.now() - spawnStart)
|
|
16103
16547
|
);
|
|
16104
16548
|
return c.json({ ok: true, dispatchId: envelope.dispatchId, taskId: spawned.sessionId }, 202);
|
|
16105
16549
|
});
|
|
@@ -16107,7 +16551,7 @@ var linkedin_ingest_default = app36;
|
|
|
16107
16551
|
|
|
16108
16552
|
// server/routes/admin/post-turn-context.ts
|
|
16109
16553
|
import neo4j3 from "neo4j-driver";
|
|
16110
|
-
var
|
|
16554
|
+
var TAG31 = "[post-turn-context]";
|
|
16111
16555
|
var STRIPPED_PROPERTIES2 = /* @__PURE__ */ new Set([
|
|
16112
16556
|
"embedding",
|
|
16113
16557
|
"passwordHash",
|
|
@@ -16149,7 +16593,7 @@ var app37 = new Hono();
|
|
|
16149
16593
|
app37.get("/", async (c) => {
|
|
16150
16594
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
16151
16595
|
if (!isLoopbackAddr2(remoteAddr)) {
|
|
16152
|
-
console.error(`${
|
|
16596
|
+
console.error(`${TAG31} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
16153
16597
|
return c.json({ error: "post-turn-context-loopback-only" }, 403);
|
|
16154
16598
|
}
|
|
16155
16599
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
@@ -16158,7 +16602,7 @@ app37.get("/", async (c) => {
|
|
|
16158
16602
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
16159
16603
|
const offender = tokens.find((t) => !isLoopbackAddr2(t));
|
|
16160
16604
|
if (offender !== void 0) {
|
|
16161
|
-
console.error(`${
|
|
16605
|
+
console.error(`${TAG31} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
16162
16606
|
return c.json({ error: "post-turn-context-loopback-only" }, 403);
|
|
16163
16607
|
}
|
|
16164
16608
|
}
|
|
@@ -16190,7 +16634,7 @@ app37.get("/", async (c) => {
|
|
|
16190
16634
|
writes.sort((a, b) => a.sortKey.localeCompare(b.sortKey));
|
|
16191
16635
|
const total = Date.now() - started;
|
|
16192
16636
|
console.log(
|
|
16193
|
-
`${
|
|
16637
|
+
`${TAG31} sessionId=${sessionId} accountId=${accountId} writes=${writes.length} ms=${total}`
|
|
16194
16638
|
);
|
|
16195
16639
|
return c.json({
|
|
16196
16640
|
writes: writes.map(({ elementId, labels, properties }) => ({ elementId, labels, properties }))
|
|
@@ -16199,7 +16643,7 @@ app37.get("/", async (c) => {
|
|
|
16199
16643
|
const elapsed = Date.now() - started;
|
|
16200
16644
|
const message = err instanceof Error ? err.message : String(err);
|
|
16201
16645
|
console.error(
|
|
16202
|
-
`${
|
|
16646
|
+
`${TAG31} neo4j-unreachable sessionId=${sessionId} ms=${elapsed} err="${message}"`
|
|
16203
16647
|
);
|
|
16204
16648
|
return c.json({ error: `post-turn-context unavailable: ${message}` }, 503);
|
|
16205
16649
|
} finally {
|
|
@@ -16210,7 +16654,7 @@ var post_turn_context_default = app37;
|
|
|
16210
16654
|
|
|
16211
16655
|
// server/routes/admin/public-session-context.ts
|
|
16212
16656
|
import neo4j4 from "neo4j-driver";
|
|
16213
|
-
var
|
|
16657
|
+
var TAG32 = "[public-session-context]";
|
|
16214
16658
|
var STRIPPED_PROPERTIES3 = /* @__PURE__ */ new Set([
|
|
16215
16659
|
"embedding",
|
|
16216
16660
|
"passwordHash",
|
|
@@ -16252,7 +16696,7 @@ var app38 = new Hono();
|
|
|
16252
16696
|
app38.get("/", async (c) => {
|
|
16253
16697
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
16254
16698
|
if (!isLoopbackAddr3(remoteAddr)) {
|
|
16255
|
-
console.error(`${
|
|
16699
|
+
console.error(`${TAG32} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
16256
16700
|
return c.json({ error: "public-session-context-loopback-only" }, 403);
|
|
16257
16701
|
}
|
|
16258
16702
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
@@ -16261,7 +16705,7 @@ app38.get("/", async (c) => {
|
|
|
16261
16705
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
16262
16706
|
const offender = tokens.find((t) => !isLoopbackAddr3(t));
|
|
16263
16707
|
if (offender !== void 0) {
|
|
16264
|
-
console.error(`${
|
|
16708
|
+
console.error(`${TAG32} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
16265
16709
|
return c.json({ error: "public-session-context-loopback-only" }, 403);
|
|
16266
16710
|
}
|
|
16267
16711
|
}
|
|
@@ -16292,7 +16736,7 @@ app38.get("/", async (c) => {
|
|
|
16292
16736
|
writes.sort((a, b) => a.sortKey.localeCompare(b.sortKey));
|
|
16293
16737
|
const total = Date.now() - started;
|
|
16294
16738
|
console.log(
|
|
16295
|
-
`${
|
|
16739
|
+
`${TAG32} sliceToken=${sliceToken.slice(0, 8)} writes=${writes.length} ms=${total}`
|
|
16296
16740
|
);
|
|
16297
16741
|
return c.json({
|
|
16298
16742
|
writes: writes.map(({ elementId, labels, properties }) => ({ elementId, labels, properties }))
|
|
@@ -16301,7 +16745,7 @@ app38.get("/", async (c) => {
|
|
|
16301
16745
|
const elapsed = Date.now() - started;
|
|
16302
16746
|
const message = err instanceof Error ? err.message : String(err);
|
|
16303
16747
|
console.error(
|
|
16304
|
-
`${
|
|
16748
|
+
`${TAG32} neo4j-unreachable sliceToken=${sliceToken.slice(0, 8)} ms=${elapsed} err="${message}"`
|
|
16305
16749
|
);
|
|
16306
16750
|
return c.json({ error: `public-session-context unavailable: ${message}` }, 503);
|
|
16307
16751
|
} finally {
|
|
@@ -16311,7 +16755,7 @@ app38.get("/", async (c) => {
|
|
|
16311
16755
|
var public_session_context_default = app38;
|
|
16312
16756
|
|
|
16313
16757
|
// server/routes/admin/public-session-exit.ts
|
|
16314
|
-
var
|
|
16758
|
+
var TAG33 = "[public-session-exit-route]";
|
|
16315
16759
|
function isLoopbackAddr4(addr) {
|
|
16316
16760
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
16317
16761
|
}
|
|
@@ -16319,7 +16763,7 @@ var app39 = new Hono();
|
|
|
16319
16763
|
app39.post("/", async (c) => {
|
|
16320
16764
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
16321
16765
|
if (!isLoopbackAddr4(remoteAddr)) {
|
|
16322
|
-
console.error(`${
|
|
16766
|
+
console.error(`${TAG33} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
16323
16767
|
return c.json({ error: "public-session-exit-loopback-only" }, 403);
|
|
16324
16768
|
}
|
|
16325
16769
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
@@ -16328,7 +16772,7 @@ app39.post("/", async (c) => {
|
|
|
16328
16772
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
16329
16773
|
const offender = tokens.find((t) => !isLoopbackAddr4(t));
|
|
16330
16774
|
if (offender !== void 0) {
|
|
16331
|
-
console.error(`${
|
|
16775
|
+
console.error(`${TAG33} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
16332
16776
|
return c.json({ error: "public-session-exit-loopback-only" }, 403);
|
|
16333
16777
|
}
|
|
16334
16778
|
}
|
|
@@ -16346,7 +16790,7 @@ app39.post("/", async (c) => {
|
|
|
16346
16790
|
var public_session_exit_default = app39;
|
|
16347
16791
|
|
|
16348
16792
|
// server/routes/admin/access-session-evict.ts
|
|
16349
|
-
var
|
|
16793
|
+
var TAG34 = "[access-session-evict]";
|
|
16350
16794
|
function isLoopbackAddr5(addr) {
|
|
16351
16795
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
16352
16796
|
}
|
|
@@ -16354,7 +16798,7 @@ var app40 = new Hono();
|
|
|
16354
16798
|
app40.post("/", async (c) => {
|
|
16355
16799
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
16356
16800
|
if (!isLoopbackAddr5(remoteAddr)) {
|
|
16357
|
-
console.error(`${
|
|
16801
|
+
console.error(`${TAG34} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
16358
16802
|
return c.json({ error: "access-session-evict-loopback-only" }, 403);
|
|
16359
16803
|
}
|
|
16360
16804
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
@@ -16363,7 +16807,7 @@ app40.post("/", async (c) => {
|
|
|
16363
16807
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
16364
16808
|
const offender = tokens.find((t) => !isLoopbackAddr5(t));
|
|
16365
16809
|
if (offender !== void 0) {
|
|
16366
|
-
console.error(`${
|
|
16810
|
+
console.error(`${TAG34} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
16367
16811
|
return c.json({ error: "access-session-evict-loopback-only" }, 403);
|
|
16368
16812
|
}
|
|
16369
16813
|
}
|
|
@@ -16376,13 +16820,13 @@ app40.post("/", async (c) => {
|
|
|
16376
16820
|
const grantId = typeof body.grantId === "string" ? body.grantId.trim() : "";
|
|
16377
16821
|
if (!grantId) return c.json({ error: "grantId required" }, 400);
|
|
16378
16822
|
const dropped = evictAccessSessionsByGrant(grantId);
|
|
16379
|
-
console.log(`${
|
|
16823
|
+
console.log(`${TAG34} grantId=${grantId} dropped=${dropped}`);
|
|
16380
16824
|
return c.json({ ok: true, dropped });
|
|
16381
16825
|
});
|
|
16382
16826
|
var access_session_evict_default = app40;
|
|
16383
16827
|
|
|
16384
16828
|
// server/routes/admin/enrol-person.ts
|
|
16385
|
-
var
|
|
16829
|
+
var TAG35 = "[enrol]";
|
|
16386
16830
|
function isLoopbackAddr6(addr) {
|
|
16387
16831
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
16388
16832
|
}
|
|
@@ -16391,7 +16835,7 @@ var app41 = new Hono();
|
|
|
16391
16835
|
app41.post("/", async (c) => {
|
|
16392
16836
|
const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
|
|
16393
16837
|
if (!isLoopbackAddr6(remoteAddr)) {
|
|
16394
|
-
console.error(`${
|
|
16838
|
+
console.error(`${TAG35} reject reason=non-loopback remoteAddr=${remoteAddr}`);
|
|
16395
16839
|
return c.json({ error: "enrol-person-loopback-only" }, 403);
|
|
16396
16840
|
}
|
|
16397
16841
|
const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
|
|
@@ -16400,7 +16844,7 @@ app41.post("/", async (c) => {
|
|
|
16400
16844
|
const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
16401
16845
|
const offender = tokens.find((t) => !isLoopbackAddr6(t));
|
|
16402
16846
|
if (offender !== void 0) {
|
|
16403
|
-
console.error(`${
|
|
16847
|
+
console.error(`${TAG35} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
|
|
16404
16848
|
return c.json({ error: "enrol-person-loopback-only" }, 403);
|
|
16405
16849
|
}
|
|
16406
16850
|
}
|
|
@@ -16425,7 +16869,7 @@ app41.post("/", async (c) => {
|
|
|
16425
16869
|
}
|
|
16426
16870
|
const accountId = process.env.ACCOUNT_ID ?? "";
|
|
16427
16871
|
if (!accountId) {
|
|
16428
|
-
console.error(`${
|
|
16872
|
+
console.error(`${TAG35} op=person result=no-account-id`);
|
|
16429
16873
|
return c.json({ error: "no-account-id" }, 500);
|
|
16430
16874
|
}
|
|
16431
16875
|
let personId;
|
|
@@ -16433,7 +16877,7 @@ app41.post("/", async (c) => {
|
|
|
16433
16877
|
personId = await enrolPerson({ accountId, phone, email, agentSlug });
|
|
16434
16878
|
} catch (err) {
|
|
16435
16879
|
console.error(
|
|
16436
|
-
`${
|
|
16880
|
+
`${TAG35} op=person result=write-failed agentSlug=${agentSlug} contact=${maskContact(email)} err="${err instanceof Error ? err.message : String(err)}"`
|
|
16437
16881
|
);
|
|
16438
16882
|
return c.json({ error: "enrol-failed" }, 500);
|
|
16439
16883
|
}
|
|
@@ -16443,11 +16887,11 @@ app41.post("/", async (c) => {
|
|
|
16443
16887
|
if (g) grant = { grantId: g.grantId, status: g.status, contactValue: g.contactValue };
|
|
16444
16888
|
} catch (err) {
|
|
16445
16889
|
console.error(
|
|
16446
|
-
`${
|
|
16890
|
+
`${TAG35} op=person result=grant-lookup-failed contact=${maskContact(email)} err="${err instanceof Error ? err.message : String(err)}"`
|
|
16447
16891
|
);
|
|
16448
16892
|
}
|
|
16449
16893
|
console.log(
|
|
16450
|
-
`${
|
|
16894
|
+
`${TAG35} op=person personId=${personId} account=${accountId} agentSlug=${agentSlug} contact=${maskContact(email)}`
|
|
16451
16895
|
);
|
|
16452
16896
|
return c.json({ personId, grant }, 200);
|
|
16453
16897
|
});
|
|
@@ -16523,7 +16967,7 @@ app43.route("/enrol-person", enrol_person_default);
|
|
|
16523
16967
|
var admin_default = app43;
|
|
16524
16968
|
|
|
16525
16969
|
// server/routes/access/verify-token.ts
|
|
16526
|
-
var
|
|
16970
|
+
var TAG36 = "[access-verify]";
|
|
16527
16971
|
var MINT_TAG = "[access-session-mint]";
|
|
16528
16972
|
var COOKIE_NAME = "__access_session";
|
|
16529
16973
|
var app44 = new Hono();
|
|
@@ -16542,39 +16986,39 @@ app44.post("/", async (c) => {
|
|
|
16542
16986
|
}
|
|
16543
16987
|
const rateMsg = checkAccessRateLimit(ip, agentSlug);
|
|
16544
16988
|
if (rateMsg) {
|
|
16545
|
-
console.error(`${
|
|
16989
|
+
console.error(`${TAG36} grantId=- agentSlug=${agentSlug} result=rate-limited ip=${ip}`);
|
|
16546
16990
|
return c.json({ error: rateMsg }, 429);
|
|
16547
16991
|
}
|
|
16548
16992
|
const grant = await findGrantByMagicToken(token);
|
|
16549
16993
|
if (!grant) {
|
|
16550
16994
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
16551
|
-
console.error(`${
|
|
16995
|
+
console.error(`${TAG36} grantId=- agentSlug=${agentSlug} result=notfound ip=${ip}`);
|
|
16552
16996
|
return c.json({ error: "invalid-or-expired-link" }, 401);
|
|
16553
16997
|
}
|
|
16554
16998
|
if (grant.agentSlug !== agentSlug) {
|
|
16555
16999
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
16556
17000
|
console.error(
|
|
16557
|
-
`${
|
|
17001
|
+
`${TAG36} grantId=${grant.grantId} agentSlug=${agentSlug} result=agent-mismatch grantAgent=${grant.agentSlug} ip=${ip}`
|
|
16558
17002
|
);
|
|
16559
17003
|
return c.json({ error: "invalid-or-expired-link" }, 401);
|
|
16560
17004
|
}
|
|
16561
17005
|
if (grant.status === "expired" || grant.status === "revoked") {
|
|
16562
17006
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
16563
17007
|
console.error(
|
|
16564
|
-
`${
|
|
17008
|
+
`${TAG36} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired status=${grant.status} ip=${ip}`
|
|
16565
17009
|
);
|
|
16566
17010
|
return c.json({ error: "access-no-longer-valid" }, 401);
|
|
16567
17011
|
}
|
|
16568
17012
|
if (grant.expiresAt !== null && grant.expiresAt < Date.now()) {
|
|
16569
17013
|
recordAccessFailedAttempt(ip, agentSlug);
|
|
16570
17014
|
console.error(
|
|
16571
|
-
`${
|
|
17015
|
+
`${TAG36} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired reason=expiresAt-past ip=${ip}`
|
|
16572
17016
|
);
|
|
16573
17017
|
return c.json({ error: "access-no-longer-valid" }, 401);
|
|
16574
17018
|
}
|
|
16575
17019
|
if (!grant.sliceToken) {
|
|
16576
17020
|
console.error(
|
|
16577
|
-
`${
|
|
17021
|
+
`${TAG36} grantId=${grant.grantId} agentSlug=${agentSlug} result=no-slice-token reason=schema-violation`
|
|
16578
17022
|
);
|
|
16579
17023
|
return c.json({ error: "grant-misconfigured" }, 500);
|
|
16580
17024
|
}
|
|
@@ -16590,12 +17034,12 @@ app44.post("/", async (c) => {
|
|
|
16590
17034
|
await consumeMagicTokenAndActivate(grant.grantId);
|
|
16591
17035
|
} catch (err) {
|
|
16592
17036
|
console.error(
|
|
16593
|
-
`${
|
|
17037
|
+
`${TAG36} grantId=${grant.grantId} agentSlug=${agentSlug} result=consume-failed err="${err instanceof Error ? err.message : String(err)}"`
|
|
16594
17038
|
);
|
|
16595
17039
|
return c.json({ error: "verification-failed" }, 500);
|
|
16596
17040
|
}
|
|
16597
17041
|
clearAccessRateLimit(ip, agentSlug);
|
|
16598
|
-
console.log(`${
|
|
17042
|
+
console.log(`${TAG36} grantId=${grant.grantId} agentSlug=${agentSlug} result=ok ip=${ip}`);
|
|
16599
17043
|
console.log(
|
|
16600
17044
|
`${MINT_TAG} grantId=${grant.grantId} sliceToken=${grant.sliceToken.slice(0, 8)} agentSlug=${agentSlug} personId=${grant.personId ?? "none"}`
|
|
16601
17045
|
);
|
|
@@ -16613,9 +17057,9 @@ var verify_token_default = app44;
|
|
|
16613
17057
|
|
|
16614
17058
|
// app/lib/access-email.ts
|
|
16615
17059
|
import { spawn as spawn2 } from "child_process";
|
|
16616
|
-
import { resolve as
|
|
16617
|
-
var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT ??
|
|
16618
|
-
var SEND_SCRIPT =
|
|
17060
|
+
import { resolve as resolve27 } from "path";
|
|
17061
|
+
var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT ?? resolve27(process.cwd(), "..");
|
|
17062
|
+
var SEND_SCRIPT = resolve27(
|
|
16619
17063
|
PLATFORM_ROOT7,
|
|
16620
17064
|
"plugins",
|
|
16621
17065
|
"email",
|
|
@@ -16671,7 +17115,7 @@ async function sendMagicLinkEmail(payload) {
|
|
|
16671
17115
|
}
|
|
16672
17116
|
|
|
16673
17117
|
// server/routes/access/request-magic-link.ts
|
|
16674
|
-
var
|
|
17118
|
+
var TAG37 = "[access-request-link]";
|
|
16675
17119
|
var app45 = new Hono();
|
|
16676
17120
|
var VISITOR_MESSAGE = "If that email is on the invite list, a fresh link is on the way.";
|
|
16677
17121
|
app45.post("/", async (c) => {
|
|
@@ -16688,18 +17132,18 @@ app45.post("/", async (c) => {
|
|
|
16688
17132
|
}
|
|
16689
17133
|
const rateMsg = checkRequestLinkRateLimit(contactValue);
|
|
16690
17134
|
if (rateMsg) {
|
|
16691
|
-
console.error(`${
|
|
17135
|
+
console.error(`${TAG37} contactValue=${maskContact(contactValue)} result=rate-limited`);
|
|
16692
17136
|
return c.json({ error: rateMsg }, 429);
|
|
16693
17137
|
}
|
|
16694
17138
|
recordRequestLinkAttempt(contactValue);
|
|
16695
17139
|
const accountId = process.env.ACCOUNT_ID ?? "";
|
|
16696
17140
|
if (!accountId) {
|
|
16697
|
-
console.error(`${
|
|
17141
|
+
console.error(`${TAG37} contactValue=${maskContact(contactValue)} result=no-account-id`);
|
|
16698
17142
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
16699
17143
|
}
|
|
16700
17144
|
const grant = await findActiveGrantByContact(contactValue, agentSlug, accountId);
|
|
16701
17145
|
if (!grant) {
|
|
16702
|
-
console.log(`${
|
|
17146
|
+
console.log(`${TAG37} contactValue=${maskContact(contactValue)} result=notfound`);
|
|
16703
17147
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
16704
17148
|
}
|
|
16705
17149
|
let token;
|
|
@@ -16707,7 +17151,7 @@ app45.post("/", async (c) => {
|
|
|
16707
17151
|
token = await generateNewMagicToken(grant.grantId);
|
|
16708
17152
|
} catch (err) {
|
|
16709
17153
|
console.error(
|
|
16710
|
-
`${
|
|
17154
|
+
`${TAG37} contactValue=${maskContact(contactValue)} result=mint-failed err="${err instanceof Error ? err.message : String(err)}"`
|
|
16711
17155
|
);
|
|
16712
17156
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
16713
17157
|
}
|
|
@@ -16739,12 +17183,12 @@ app45.post("/", async (c) => {
|
|
|
16739
17183
|
});
|
|
16740
17184
|
if (!sendResult.ok) {
|
|
16741
17185
|
console.error(
|
|
16742
|
-
`${
|
|
17186
|
+
`${TAG37} contactValue=${maskContact(contactValue)} result=send-failed err="${sendResult.error}"`
|
|
16743
17187
|
);
|
|
16744
17188
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
16745
17189
|
}
|
|
16746
17190
|
console.log(
|
|
16747
|
-
`${
|
|
17191
|
+
`${TAG37} contactValue=${maskContact(contactValue)} result=ok messageId=${sendResult.messageId}`
|
|
16748
17192
|
);
|
|
16749
17193
|
return c.json({ message: VISITOR_MESSAGE }, 200);
|
|
16750
17194
|
});
|
|
@@ -16757,8 +17201,8 @@ app46.route("/request-magic-link", request_magic_link_default);
|
|
|
16757
17201
|
var access_default = app46;
|
|
16758
17202
|
|
|
16759
17203
|
// server/routes/sites.ts
|
|
16760
|
-
import { existsSync as existsSync26, readFileSync as
|
|
16761
|
-
import { resolve as
|
|
17204
|
+
import { existsSync as existsSync26, readFileSync as readFileSync27, realpathSync as realpathSync7, statSync as statSync11 } from "fs";
|
|
17205
|
+
import { resolve as resolve28 } from "path";
|
|
16762
17206
|
var SAFE_SEG_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
|
|
16763
17207
|
var MIME = {
|
|
16764
17208
|
".html": "text/html; charset=utf-8",
|
|
@@ -16815,19 +17259,19 @@ app47.get("/:rel{.*}", (c) => {
|
|
|
16815
17259
|
}
|
|
16816
17260
|
segments.push(seg);
|
|
16817
17261
|
}
|
|
16818
|
-
const rootDir =
|
|
16819
|
-
let filePath = segments.length === 0 ? rootDir :
|
|
17262
|
+
const rootDir = resolve28(account.accountDir, "sites");
|
|
17263
|
+
let filePath = segments.length === 0 ? rootDir : resolve28(rootDir, ...segments);
|
|
16820
17264
|
if (filePath !== rootDir && !filePath.startsWith(rootDir + "/")) {
|
|
16821
17265
|
console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
|
|
16822
17266
|
return c.text("Forbidden", 403);
|
|
16823
17267
|
}
|
|
16824
|
-
let
|
|
17268
|
+
let stat8;
|
|
16825
17269
|
try {
|
|
16826
|
-
|
|
17270
|
+
stat8 = existsSync26(filePath) ? statSync11(filePath) : null;
|
|
16827
17271
|
} catch {
|
|
16828
|
-
|
|
17272
|
+
stat8 = null;
|
|
16829
17273
|
}
|
|
16830
|
-
if (
|
|
17274
|
+
if (stat8?.isDirectory() && !reqPath.endsWith("/")) {
|
|
16831
17275
|
const search = new URL(c.req.url).search;
|
|
16832
17276
|
const target = `${reqPath}/${search}`;
|
|
16833
17277
|
console.log(
|
|
@@ -16835,8 +17279,8 @@ app47.get("/:rel{.*}", (c) => {
|
|
|
16835
17279
|
);
|
|
16836
17280
|
return c.redirect(target, 301);
|
|
16837
17281
|
}
|
|
16838
|
-
if (
|
|
16839
|
-
filePath =
|
|
17282
|
+
if (stat8?.isDirectory()) {
|
|
17283
|
+
filePath = resolve28(filePath, "index.html");
|
|
16840
17284
|
}
|
|
16841
17285
|
if (!filePath.startsWith(rootDir + "/")) {
|
|
16842
17286
|
console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
|
|
@@ -16849,8 +17293,8 @@ app47.get("/:rel{.*}", (c) => {
|
|
|
16849
17293
|
let realPath;
|
|
16850
17294
|
let realRoot;
|
|
16851
17295
|
try {
|
|
16852
|
-
realPath =
|
|
16853
|
-
realRoot =
|
|
17296
|
+
realPath = realpathSync7(filePath);
|
|
17297
|
+
realRoot = realpathSync7(rootDir);
|
|
16854
17298
|
} catch {
|
|
16855
17299
|
console.error(`[sites] not-found path=${reqPath} status=404`);
|
|
16856
17300
|
return c.text("Not found", 404);
|
|
@@ -16861,7 +17305,7 @@ app47.get("/:rel{.*}", (c) => {
|
|
|
16861
17305
|
}
|
|
16862
17306
|
let body;
|
|
16863
17307
|
try {
|
|
16864
|
-
body =
|
|
17308
|
+
body = readFileSync27(realPath);
|
|
16865
17309
|
} catch (err) {
|
|
16866
17310
|
const code = err?.code;
|
|
16867
17311
|
if (code === "EISDIR") {
|
|
@@ -16897,7 +17341,7 @@ var sites_default = app47;
|
|
|
16897
17341
|
|
|
16898
17342
|
// app/lib/visitor-token.ts
|
|
16899
17343
|
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
16900
|
-
import { mkdirSync as mkdirSync6, readFileSync as
|
|
17344
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync28, writeFileSync as writeFileSync11 } from "fs";
|
|
16901
17345
|
import { dirname as dirname7 } from "path";
|
|
16902
17346
|
var TOKEN_PREFIX = "v1.";
|
|
16903
17347
|
var SECRET_BYTES = 32;
|
|
@@ -16906,7 +17350,7 @@ var cachedSecret = null;
|
|
|
16906
17350
|
function getSecret() {
|
|
16907
17351
|
if (cachedSecret) return cachedSecret;
|
|
16908
17352
|
try {
|
|
16909
|
-
const hex2 =
|
|
17353
|
+
const hex2 = readFileSync28(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
|
|
16910
17354
|
if (hex2.length === SECRET_BYTES * 2) {
|
|
16911
17355
|
cachedSecret = Buffer.from(hex2, "hex");
|
|
16912
17356
|
return cachedSecret;
|
|
@@ -16920,7 +17364,7 @@ function getSecret() {
|
|
|
16920
17364
|
console.log(`[visitor-token] secret minted path=${VISITOR_TOKEN_SECRET_FILE}`);
|
|
16921
17365
|
} catch {
|
|
16922
17366
|
}
|
|
16923
|
-
const hex =
|
|
17367
|
+
const hex = readFileSync28(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
|
|
16924
17368
|
cachedSecret = Buffer.from(hex, "hex");
|
|
16925
17369
|
return cachedSecret;
|
|
16926
17370
|
}
|
|
@@ -16973,19 +17417,19 @@ var VISITOR_COOKIE_NAME = "mxy_v";
|
|
|
16973
17417
|
var VISITOR_COOKIE_MAX_AGE_SECONDS = Math.floor(DEFAULT_TTL_MS / 1e3);
|
|
16974
17418
|
|
|
16975
17419
|
// app/lib/brand-config.ts
|
|
16976
|
-
import { existsSync as existsSync27, readFileSync as
|
|
16977
|
-
import { join as
|
|
17420
|
+
import { existsSync as existsSync27, readFileSync as readFileSync29 } from "fs";
|
|
17421
|
+
import { join as join26 } from "path";
|
|
16978
17422
|
var cached2 = null;
|
|
16979
17423
|
var cachedAttempted = false;
|
|
16980
17424
|
function readBrandConfig() {
|
|
16981
17425
|
if (cachedAttempted) return cached2;
|
|
16982
17426
|
cachedAttempted = true;
|
|
16983
|
-
const
|
|
16984
|
-
if (!
|
|
16985
|
-
const brandPath =
|
|
17427
|
+
const platformRoot4 = process.env.MAXY_PLATFORM_ROOT;
|
|
17428
|
+
if (!platformRoot4) return null;
|
|
17429
|
+
const brandPath = join26(platformRoot4, "config", "brand.json");
|
|
16986
17430
|
if (!existsSync27(brandPath)) return null;
|
|
16987
17431
|
try {
|
|
16988
|
-
cached2 = JSON.parse(
|
|
17432
|
+
cached2 = JSON.parse(readFileSync29(brandPath, "utf-8"));
|
|
16989
17433
|
return cached2;
|
|
16990
17434
|
} catch {
|
|
16991
17435
|
return null;
|
|
@@ -17473,19 +17917,19 @@ async function writeEvent(opts) {
|
|
|
17473
17917
|
var visitor_event_default = app50;
|
|
17474
17918
|
|
|
17475
17919
|
// server/routes/session.ts
|
|
17476
|
-
import { resolve as
|
|
17920
|
+
import { resolve as resolve29 } from "path";
|
|
17477
17921
|
import { existsSync as existsSync28, writeFileSync as writeFileSync12, mkdirSync as mkdirSync7 } from "fs";
|
|
17478
17922
|
var UUID_RE4 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
17479
17923
|
function writeBrandingCache(accountId, agentSlug, branding) {
|
|
17480
17924
|
try {
|
|
17481
|
-
const cacheDir =
|
|
17925
|
+
const cacheDir = resolve29(MAXY_DIR, "branding-cache", accountId);
|
|
17482
17926
|
mkdirSync7(cacheDir, { recursive: true });
|
|
17483
|
-
writeFileSync12(
|
|
17927
|
+
writeFileSync12(resolve29(cacheDir, `${agentSlug}.json`), JSON.stringify(branding), "utf-8");
|
|
17484
17928
|
} catch (err) {
|
|
17485
17929
|
console.error(`[branding] cache write failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
17486
17930
|
}
|
|
17487
17931
|
}
|
|
17488
|
-
function
|
|
17932
|
+
function parseVisitorCookie2(cookieHeader) {
|
|
17489
17933
|
if (!cookieHeader) return null;
|
|
17490
17934
|
const match = cookieHeader.match(/(?:^|;\s*)maxy_visitor=([^;]*)/);
|
|
17491
17935
|
if (!match) return null;
|
|
@@ -17534,7 +17978,7 @@ app51.post("/", async (c) => {
|
|
|
17534
17978
|
}
|
|
17535
17979
|
const account = resolveAccount();
|
|
17536
17980
|
const cookieHeader = c.req.header("cookie");
|
|
17537
|
-
const visitorId =
|
|
17981
|
+
const visitorId = parseVisitorCookie2(cookieHeader);
|
|
17538
17982
|
const groupSlug = body.group_slug && typeof body.group_slug === "string" ? body.group_slug : null;
|
|
17539
17983
|
let groupInfo = null;
|
|
17540
17984
|
if (groupSlug) {
|
|
@@ -17568,8 +18012,8 @@ app51.post("/", async (c) => {
|
|
|
17568
18012
|
}
|
|
17569
18013
|
let agentConfig = null;
|
|
17570
18014
|
if (account) {
|
|
17571
|
-
const agentDir =
|
|
17572
|
-
if (!existsSync28(agentDir) || !existsSync28(
|
|
18015
|
+
const agentDir = resolve29(account.accountDir, "agents", agentSlug);
|
|
18016
|
+
if (!existsSync28(agentDir) || !existsSync28(resolve29(agentDir, "config.json"))) {
|
|
17573
18017
|
return c.json({ error: "Agent not found" }, 404);
|
|
17574
18018
|
}
|
|
17575
18019
|
agentConfig = resolveAgentConfig(account.accountDir, agentSlug);
|
|
@@ -17849,7 +18293,7 @@ function startGraphHealthTimer() {
|
|
|
17849
18293
|
|
|
17850
18294
|
// app/lib/file-watcher.ts
|
|
17851
18295
|
import * as fsp2 from "fs/promises";
|
|
17852
|
-
import { resolve as
|
|
18296
|
+
import { resolve as resolve30, sep as sep8 } from "path";
|
|
17853
18297
|
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;
|
|
17854
18298
|
var DEFAULT_COALESCE_MS = 500;
|
|
17855
18299
|
var ROOTS = ["accounts"];
|
|
@@ -17868,7 +18312,7 @@ async function startFileWatcher(opts = {}) {
|
|
|
17868
18312
|
const dropFn = opts.drop ?? dropFileIndex;
|
|
17869
18313
|
for (const r of ROOTS) {
|
|
17870
18314
|
try {
|
|
17871
|
-
await fsp2.mkdir(
|
|
18315
|
+
await fsp2.mkdir(resolve30(dataRoot, r), { recursive: true });
|
|
17872
18316
|
} catch (err) {
|
|
17873
18317
|
console.error(
|
|
17874
18318
|
`[file-watcher] start-failed root="${r}" err="${err.message}" \u2014 index will be maintained by the 5-min reconcile backstop only`
|
|
@@ -17889,7 +18333,7 @@ async function startFileWatcher(opts = {}) {
|
|
|
17889
18333
|
timers.set(relativePath, t);
|
|
17890
18334
|
}
|
|
17891
18335
|
async function runHook(relativePath, accountId) {
|
|
17892
|
-
const absolute =
|
|
18336
|
+
const absolute = resolve30(dataRoot, relativePath);
|
|
17893
18337
|
let exists = false;
|
|
17894
18338
|
try {
|
|
17895
18339
|
const st = await fsp2.stat(absolute);
|
|
@@ -17913,7 +18357,7 @@ async function startFileWatcher(opts = {}) {
|
|
|
17913
18357
|
}
|
|
17914
18358
|
}
|
|
17915
18359
|
async function watchRoot(rootName) {
|
|
17916
|
-
const absRoot =
|
|
18360
|
+
const absRoot = resolve30(dataRoot, rootName);
|
|
17917
18361
|
try {
|
|
17918
18362
|
const iter = fsp2.watch(absRoot, { recursive: true, signal: controller.signal });
|
|
17919
18363
|
for await (const event of iter) {
|
|
@@ -17952,7 +18396,7 @@ async function startFileWatcher(opts = {}) {
|
|
|
17952
18396
|
|
|
17953
18397
|
// app/lib/migrate-uploads.ts
|
|
17954
18398
|
import { mkdir as mkdir5, readdir as readdir5, rename as rename2, rm as rm3 } from "fs/promises";
|
|
17955
|
-
import { dirname as dirname8, relative as
|
|
18399
|
+
import { dirname as dirname8, relative as relative5, resolve as resolve31 } from "path";
|
|
17956
18400
|
var ACCOUNT_UUID_RE3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
17957
18401
|
async function walkFiles(dir) {
|
|
17958
18402
|
const out = [];
|
|
@@ -17963,7 +18407,7 @@ async function walkFiles(dir) {
|
|
|
17963
18407
|
return out;
|
|
17964
18408
|
}
|
|
17965
18409
|
for (const e of entries) {
|
|
17966
|
-
const abs =
|
|
18410
|
+
const abs = resolve31(dir, e.name);
|
|
17967
18411
|
if (e.isDirectory()) {
|
|
17968
18412
|
out.push(...await walkFiles(abs));
|
|
17969
18413
|
} else if (e.isFile()) {
|
|
@@ -17982,13 +18426,13 @@ async function relocateTree(srcDir, destDir, dataRoot, accountId, session) {
|
|
|
17982
18426
|
const files = await walkFiles(srcDir);
|
|
17983
18427
|
let moved = 0;
|
|
17984
18428
|
for (const oldAbs of files) {
|
|
17985
|
-
const suffix =
|
|
17986
|
-
const newAbs =
|
|
18429
|
+
const suffix = relative5(srcDir, oldAbs);
|
|
18430
|
+
const newAbs = resolve31(destDir, suffix);
|
|
17987
18431
|
await mkdir5(dirname8(newAbs), { recursive: true });
|
|
17988
18432
|
await rename2(oldAbs, newAbs);
|
|
17989
18433
|
moved++;
|
|
17990
|
-
const oldRel =
|
|
17991
|
-
const newRel =
|
|
18434
|
+
const oldRel = relative5(dataRoot, oldAbs);
|
|
18435
|
+
const newRel = relative5(dataRoot, newAbs);
|
|
17992
18436
|
let linked = false;
|
|
17993
18437
|
if (accountId && session) {
|
|
17994
18438
|
const fa = await session.run(
|
|
@@ -18010,7 +18454,7 @@ async function relocateTree(srcDir, destDir, dataRoot, accountId, session) {
|
|
|
18010
18454
|
}
|
|
18011
18455
|
async function migrateUploads(opts = {}) {
|
|
18012
18456
|
const dataRoot = opts.dataRoot ?? DATA_ROOT;
|
|
18013
|
-
const oldRoot =
|
|
18457
|
+
const oldRoot = resolve31(dataRoot, "uploads");
|
|
18014
18458
|
let topEntries;
|
|
18015
18459
|
try {
|
|
18016
18460
|
topEntries = await readdir5(oldRoot, { withFileTypes: true });
|
|
@@ -18031,8 +18475,8 @@ async function migrateUploads(opts = {}) {
|
|
|
18031
18475
|
const name = entry.name;
|
|
18032
18476
|
if (ACCOUNT_UUID_RE3.test(name)) {
|
|
18033
18477
|
moved += await relocateTree(
|
|
18034
|
-
|
|
18035
|
-
|
|
18478
|
+
resolve31(oldRoot, name),
|
|
18479
|
+
resolve31(dataRoot, "accounts", name, "uploads"),
|
|
18036
18480
|
dataRoot,
|
|
18037
18481
|
name,
|
|
18038
18482
|
session
|
|
@@ -18044,8 +18488,8 @@ async function migrateUploads(opts = {}) {
|
|
|
18044
18488
|
continue;
|
|
18045
18489
|
}
|
|
18046
18490
|
moved += await relocateTree(
|
|
18047
|
-
|
|
18048
|
-
|
|
18491
|
+
resolve31(oldRoot, "public"),
|
|
18492
|
+
resolve31(dataRoot, "accounts", installAccountId, "uploads", "public"),
|
|
18049
18493
|
dataRoot,
|
|
18050
18494
|
null,
|
|
18051
18495
|
// public uploads carry no graph nodes
|
|
@@ -18074,9 +18518,9 @@ async function migrateUploads(opts = {}) {
|
|
|
18074
18518
|
}
|
|
18075
18519
|
|
|
18076
18520
|
// app/lib/migrate-admin-webchat-sidecars.ts
|
|
18077
|
-
import { readdir as readdir6, readFile as
|
|
18521
|
+
import { readdir as readdir6, readFile as readFile7, rename as rename3, writeFile as writeFile4, unlink as unlink3 } from "fs/promises";
|
|
18078
18522
|
import { existsSync as existsSync29 } from "fs";
|
|
18079
|
-
import { join as
|
|
18523
|
+
import { join as join27 } from "path";
|
|
18080
18524
|
var ADMIN_ROLE2 = "admin";
|
|
18081
18525
|
var WEBCHAT_CHANNEL2 = "webchat";
|
|
18082
18526
|
var SESSION_META_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.meta\.json$/i;
|
|
@@ -18087,7 +18531,7 @@ var defaultResolveName = async (accountId, userId) => {
|
|
|
18087
18531
|
async function readRaw(path2) {
|
|
18088
18532
|
let raw;
|
|
18089
18533
|
try {
|
|
18090
|
-
raw = await
|
|
18534
|
+
raw = await readFile7(path2, "utf8");
|
|
18091
18535
|
} catch {
|
|
18092
18536
|
return null;
|
|
18093
18537
|
}
|
|
@@ -18121,7 +18565,7 @@ async function collectLiveSidecars(projectsRoot) {
|
|
|
18121
18565
|
}
|
|
18122
18566
|
for (const slug of slugs) {
|
|
18123
18567
|
if (!slug.isDirectory()) continue;
|
|
18124
|
-
const slugDir =
|
|
18568
|
+
const slugDir = join27(projectsRoot, slug.name);
|
|
18125
18569
|
let entries;
|
|
18126
18570
|
try {
|
|
18127
18571
|
entries = await readdir6(slugDir, { withFileTypes: true });
|
|
@@ -18130,9 +18574,9 @@ async function collectLiveSidecars(projectsRoot) {
|
|
|
18130
18574
|
}
|
|
18131
18575
|
for (const entry of entries) {
|
|
18132
18576
|
if (entry.isFile() && SESSION_META_RE.test(entry.name)) {
|
|
18133
|
-
out.push(
|
|
18577
|
+
out.push(join27(slugDir, entry.name));
|
|
18134
18578
|
} else if (entry.isDirectory() && entry.name === "subagents") {
|
|
18135
|
-
const subDir =
|
|
18579
|
+
const subDir = join27(slugDir, entry.name);
|
|
18136
18580
|
let subs;
|
|
18137
18581
|
try {
|
|
18138
18582
|
subs = await readdir6(subDir, { withFileTypes: true });
|
|
@@ -18140,7 +18584,7 @@ async function collectLiveSidecars(projectsRoot) {
|
|
|
18140
18584
|
continue;
|
|
18141
18585
|
}
|
|
18142
18586
|
for (const s of subs) {
|
|
18143
|
-
if (s.isFile() && SESSION_META_RE.test(s.name)) out.push(
|
|
18587
|
+
if (s.isFile() && SESSION_META_RE.test(s.name)) out.push(join27(subDir, s.name));
|
|
18144
18588
|
}
|
|
18145
18589
|
}
|
|
18146
18590
|
}
|
|
@@ -18152,7 +18596,7 @@ function shortId(path2) {
|
|
|
18152
18596
|
return base.slice(0, 8);
|
|
18153
18597
|
}
|
|
18154
18598
|
async function migrateAdminWebchatSidecars(opts = {}) {
|
|
18155
|
-
const projectsRoot = opts.projectsRoot ?? (process.env.CLAUDE_CONFIG_DIR ?
|
|
18599
|
+
const projectsRoot = opts.projectsRoot ?? (process.env.CLAUDE_CONFIG_DIR ? join27(process.env.CLAUDE_CONFIG_DIR, "projects") : null) ?? "";
|
|
18156
18600
|
const usersFile = opts.usersFile ?? USERS_FILE;
|
|
18157
18601
|
const accountId = opts.accountId ?? resolveAccount()?.accountId ?? null;
|
|
18158
18602
|
const resolveName = opts.resolveName ?? defaultResolveName;
|
|
@@ -18959,16 +19403,16 @@ var WebchatGateway = class {
|
|
|
18959
19403
|
* The public /api/chat route awaits this to return the reply on the same SSE
|
|
18960
19404
|
* response, preserving the pre-756 POST→single-blob browser contract. */
|
|
18961
19405
|
awaitReply(key, timeoutMs) {
|
|
18962
|
-
return new Promise((
|
|
19406
|
+
return new Promise((resolve34) => {
|
|
18963
19407
|
const timer2 = setTimeout(() => {
|
|
18964
19408
|
this.replyAwaiters.delete(key);
|
|
18965
|
-
|
|
19409
|
+
resolve34({ timeout: true });
|
|
18966
19410
|
}, timeoutMs);
|
|
18967
19411
|
if (timer2.unref) timer2.unref();
|
|
18968
19412
|
this.replyAwaiters.set(key, (r) => {
|
|
18969
19413
|
clearTimeout(timer2);
|
|
18970
19414
|
this.replyAwaiters.delete(key);
|
|
18971
|
-
|
|
19415
|
+
resolve34(r);
|
|
18972
19416
|
});
|
|
18973
19417
|
});
|
|
18974
19418
|
}
|
|
@@ -19104,7 +19548,7 @@ var WebchatGateway = class {
|
|
|
19104
19548
|
|
|
19105
19549
|
// app/lib/webchat/gateway/public-spawn-request.ts
|
|
19106
19550
|
function buildPublicWebchatSpawnRequest(input) {
|
|
19107
|
-
const { agentSlug, sliceToken, personId, name } = input.public;
|
|
19551
|
+
const { agentSlug, sliceToken, personId, visitorId, name } = input.public;
|
|
19108
19552
|
return {
|
|
19109
19553
|
senderId: input.key,
|
|
19110
19554
|
role: "public",
|
|
@@ -19115,6 +19559,7 @@ function buildPublicWebchatSpawnRequest(input) {
|
|
|
19115
19559
|
// public agents carry neither (mirrors the bridge's open-mode key).
|
|
19116
19560
|
...sliceToken ? { sliceToken } : {},
|
|
19117
19561
|
...personId ? { personId } : {},
|
|
19562
|
+
...visitorId ? { visitorId } : {},
|
|
19118
19563
|
name,
|
|
19119
19564
|
webchatChannel: { key: input.key, gatewayUrl: input.gatewayUrl, serverPath: input.serverPath }
|
|
19120
19565
|
};
|
|
@@ -19138,7 +19583,9 @@ function startNativeFileFollower(input) {
|
|
|
19138
19583
|
followerRunning: false,
|
|
19139
19584
|
sliceToken: "",
|
|
19140
19585
|
personId: null,
|
|
19141
|
-
name: null
|
|
19586
|
+
name: null,
|
|
19587
|
+
// WhatsApp addresses the document to senderId; no separate reply target.
|
|
19588
|
+
replyTarget: null
|
|
19142
19589
|
};
|
|
19143
19590
|
const sid = input.senderId.slice(0, 8);
|
|
19144
19591
|
let lastComposingAt = Number.NEGATIVE_INFINITY;
|
|
@@ -19237,8 +19684,8 @@ function broadcastAdminShutdown(reason) {
|
|
|
19237
19684
|
|
|
19238
19685
|
// ../lib/entitlement/src/index.ts
|
|
19239
19686
|
import { createPublicKey, createHash as createHash5, verify as cryptoVerify } from "crypto";
|
|
19240
|
-
import { existsSync as existsSync30, readFileSync as
|
|
19241
|
-
import { resolve as
|
|
19687
|
+
import { existsSync as existsSync30, readFileSync as readFileSync30, statSync as statSync12 } from "fs";
|
|
19688
|
+
import { resolve as resolve32 } from "path";
|
|
19242
19689
|
|
|
19243
19690
|
// ../lib/entitlement/src/canonicalize.ts
|
|
19244
19691
|
function canonicalize(value) {
|
|
@@ -19273,7 +19720,7 @@ var PUBKEY_SHA256 = "8eee6bcb33545fd13b16d3199a5735ca5db5062834c7b49dfe4f23801d9
|
|
|
19273
19720
|
var GRACE_DAYS = 7;
|
|
19274
19721
|
var GRACE_MS = GRACE_DAYS * 24 * 60 * 60 * 1e3;
|
|
19275
19722
|
function pubkeyPath(brand) {
|
|
19276
|
-
return
|
|
19723
|
+
return resolve32(brand.platformRoot, "lib", "entitlement", "rubytech-pubkey.pem");
|
|
19277
19724
|
}
|
|
19278
19725
|
var memo = null;
|
|
19279
19726
|
function memoKey(mtimeMs, account) {
|
|
@@ -19285,12 +19732,12 @@ function resolveEntitlement(brand, account) {
|
|
|
19285
19732
|
if (brand.commercialMode !== true) {
|
|
19286
19733
|
return logResolved(implicitTrust(account), null);
|
|
19287
19734
|
}
|
|
19288
|
-
const entitlementPath =
|
|
19735
|
+
const entitlementPath = resolve32(brand.configDir, "entitlement.json");
|
|
19289
19736
|
if (!existsSync30(entitlementPath)) {
|
|
19290
19737
|
return logResolved(anonymousFallback("missing"), { reason: "missing" });
|
|
19291
19738
|
}
|
|
19292
|
-
const
|
|
19293
|
-
const key = memoKey(
|
|
19739
|
+
const stat8 = statSync12(entitlementPath);
|
|
19740
|
+
const key = memoKey(stat8.mtimeMs, account);
|
|
19294
19741
|
if (memo && memo.key === key) {
|
|
19295
19742
|
return memo.result;
|
|
19296
19743
|
}
|
|
@@ -19301,7 +19748,7 @@ function resolveEntitlement(brand, account) {
|
|
|
19301
19748
|
function verifyAndResolve(brand, entitlementPath, account) {
|
|
19302
19749
|
let pubkeyPem;
|
|
19303
19750
|
try {
|
|
19304
|
-
pubkeyPem =
|
|
19751
|
+
pubkeyPem = readFileSync30(pubkeyPath(brand), "utf-8");
|
|
19305
19752
|
} catch (err) {
|
|
19306
19753
|
return logResolved(anonymousFallback("pubkey-missing"), {
|
|
19307
19754
|
reason: "pubkey-missing"
|
|
@@ -19315,7 +19762,7 @@ function verifyAndResolve(brand, entitlementPath, account) {
|
|
|
19315
19762
|
}
|
|
19316
19763
|
let envelope;
|
|
19317
19764
|
try {
|
|
19318
|
-
envelope = JSON.parse(
|
|
19765
|
+
envelope = JSON.parse(readFileSync30(entitlementPath, "utf-8"));
|
|
19319
19766
|
} catch {
|
|
19320
19767
|
return logResolved(anonymousFallback("malformed"), { reason: "malformed" });
|
|
19321
19768
|
}
|
|
@@ -19476,14 +19923,14 @@ function clientFrom(c) {
|
|
|
19476
19923
|
);
|
|
19477
19924
|
}
|
|
19478
19925
|
var PLATFORM_ROOT8 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
19479
|
-
var BRAND_JSON_PATH = PLATFORM_ROOT8 ?
|
|
19926
|
+
var BRAND_JSON_PATH = PLATFORM_ROOT8 ? join28(PLATFORM_ROOT8, "config", "brand.json") : "";
|
|
19480
19927
|
var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", marketingUrl: "https://getmaxy.com" };
|
|
19481
19928
|
if (BRAND_JSON_PATH && !existsSync31(BRAND_JSON_PATH)) {
|
|
19482
19929
|
console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
|
|
19483
19930
|
}
|
|
19484
19931
|
if (BRAND_JSON_PATH && existsSync31(BRAND_JSON_PATH)) {
|
|
19485
19932
|
try {
|
|
19486
|
-
const parsed = JSON.parse(
|
|
19933
|
+
const parsed = JSON.parse(readFileSync31(BRAND_JSON_PATH, "utf-8"));
|
|
19487
19934
|
BRAND = { ...BRAND, ...parsed };
|
|
19488
19935
|
} catch (err) {
|
|
19489
19936
|
console.error(`[brand] Failed to parse brand.json: ${err.message}`);
|
|
@@ -19502,11 +19949,11 @@ var brandLoginOpts = {
|
|
|
19502
19949
|
bodyFont: BRAND.defaultFonts?.body,
|
|
19503
19950
|
logoContainsName: !!BRAND.logoContainsName
|
|
19504
19951
|
};
|
|
19505
|
-
var ALIAS_DOMAINS_PATH =
|
|
19952
|
+
var ALIAS_DOMAINS_PATH = join28(homedir3(), BRAND.configDir, "alias-domains.json");
|
|
19506
19953
|
function loadAliasDomains() {
|
|
19507
19954
|
try {
|
|
19508
19955
|
if (!existsSync31(ALIAS_DOMAINS_PATH)) return null;
|
|
19509
|
-
const parsed = JSON.parse(
|
|
19956
|
+
const parsed = JSON.parse(readFileSync31(ALIAS_DOMAINS_PATH, "utf-8"));
|
|
19510
19957
|
if (!Array.isArray(parsed)) {
|
|
19511
19958
|
console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
|
|
19512
19959
|
return null;
|
|
@@ -19534,7 +19981,7 @@ var app52 = new Hono();
|
|
|
19534
19981
|
var nativeFileFollowers = /* @__PURE__ */ new Map();
|
|
19535
19982
|
var waGateway = new WaGateway({
|
|
19536
19983
|
gatewayUrl: `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`,
|
|
19537
|
-
serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ??
|
|
19984
|
+
serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve33(process.env.MAXY_PLATFORM_ROOT ?? join28(__dirname, ".."), "services/whatsapp-channel/dist/server.js"),
|
|
19538
19985
|
// Task 751 — file delivery on the native channel: resolve the platform
|
|
19539
19986
|
// account + path validation here and funnel through the shared send core.
|
|
19540
19987
|
sendDocument: async ({ senderId, accountId, filePath, caption }) => {
|
|
@@ -19550,7 +19997,7 @@ var waGateway = new WaGateway({
|
|
|
19550
19997
|
caption,
|
|
19551
19998
|
accountId,
|
|
19552
19999
|
maxyAccountId,
|
|
19553
|
-
platformRoot:
|
|
20000
|
+
platformRoot: resolve33(process.env.MAXY_PLATFORM_ROOT ?? join28(__dirname, ".."))
|
|
19554
20001
|
});
|
|
19555
20002
|
return result.ok ? { ok: true, messageId: result.messageId } : { ok: false, error: result.error };
|
|
19556
20003
|
},
|
|
@@ -19973,8 +20420,8 @@ app52.get("/agent-assets/:slug/:filename", (c) => {
|
|
|
19973
20420
|
console.error(`[agent-assets] no-account slug=${slug} file=${filename}`);
|
|
19974
20421
|
return c.text("Not found", 404);
|
|
19975
20422
|
}
|
|
19976
|
-
const filePath =
|
|
19977
|
-
const expectedDir =
|
|
20423
|
+
const filePath = resolve33(account.accountDir, "agents", slug, "assets", filename);
|
|
20424
|
+
const expectedDir = resolve33(account.accountDir, "agents", slug, "assets");
|
|
19978
20425
|
if (!filePath.startsWith(expectedDir + "/")) {
|
|
19979
20426
|
console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
|
|
19980
20427
|
return c.text("Forbidden", 403);
|
|
@@ -19986,7 +20433,7 @@ app52.get("/agent-assets/:slug/:filename", (c) => {
|
|
|
19986
20433
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
19987
20434
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
19988
20435
|
console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
|
|
19989
|
-
const body =
|
|
20436
|
+
const body = readFileSync31(filePath);
|
|
19990
20437
|
return c.body(body, 200, {
|
|
19991
20438
|
"Content-Type": contentType,
|
|
19992
20439
|
"Cache-Control": "public, max-age=3600"
|
|
@@ -20003,8 +20450,8 @@ app52.get("/generated/:filename", (c) => {
|
|
|
20003
20450
|
console.error(`[generated] serve file=${filename} status=404`);
|
|
20004
20451
|
return c.text("Not found", 404);
|
|
20005
20452
|
}
|
|
20006
|
-
const filePath =
|
|
20007
|
-
const expectedDir =
|
|
20453
|
+
const filePath = resolve33(account.accountDir, "generated", filename);
|
|
20454
|
+
const expectedDir = resolve33(account.accountDir, "generated");
|
|
20008
20455
|
if (!filePath.startsWith(expectedDir + "/")) {
|
|
20009
20456
|
console.error(`[generated] serve file=${filename} status=403`);
|
|
20010
20457
|
return c.text("Forbidden", 403);
|
|
@@ -20016,7 +20463,7 @@ app52.get("/generated/:filename", (c) => {
|
|
|
20016
20463
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
20017
20464
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
20018
20465
|
console.log(`[generated] serve file=${filename} status=200`);
|
|
20019
|
-
const body =
|
|
20466
|
+
const body = readFileSync31(filePath);
|
|
20020
20467
|
return c.body(body, 200, {
|
|
20021
20468
|
"Content-Type": contentType,
|
|
20022
20469
|
"Cache-Control": "public, max-age=86400"
|
|
@@ -20031,7 +20478,7 @@ var brandLogoPath = "/brand/maxy-monochrome.png";
|
|
|
20031
20478
|
var brandIconPath = "/brand/maxy-monochrome.png";
|
|
20032
20479
|
if (BRAND_JSON_PATH && existsSync31(BRAND_JSON_PATH)) {
|
|
20033
20480
|
try {
|
|
20034
|
-
const fullBrand = JSON.parse(
|
|
20481
|
+
const fullBrand = JSON.parse(readFileSync31(BRAND_JSON_PATH, "utf-8"));
|
|
20035
20482
|
if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
|
|
20036
20483
|
brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
|
|
20037
20484
|
} catch {
|
|
@@ -20047,11 +20494,11 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
|
|
|
20047
20494
|
})}</script>`;
|
|
20048
20495
|
var brandThemeColor = BRAND.defaultColors?.primary ?? "#000000";
|
|
20049
20496
|
var brandBackgroundColor = BRAND.defaultColors?.background ?? "#ffffff";
|
|
20050
|
-
var brandIconFsPath =
|
|
20497
|
+
var brandIconFsPath = resolve33(process.cwd(), "public", brandIconPath.replace(/^\//, ""));
|
|
20051
20498
|
var brandIconExists = existsSync31(brandIconFsPath);
|
|
20052
20499
|
var SW_SOURCE = (() => {
|
|
20053
20500
|
try {
|
|
20054
|
-
return
|
|
20501
|
+
return readFileSync31(resolve33(process.cwd(), "public", "sw.js"), "utf-8");
|
|
20055
20502
|
} catch {
|
|
20056
20503
|
return null;
|
|
20057
20504
|
}
|
|
@@ -20059,9 +20506,9 @@ var SW_SOURCE = (() => {
|
|
|
20059
20506
|
function readInstalledVersion() {
|
|
20060
20507
|
try {
|
|
20061
20508
|
if (!PLATFORM_ROOT8) return "unknown";
|
|
20062
|
-
const versionFile =
|
|
20509
|
+
const versionFile = join28(PLATFORM_ROOT8, "config", `.${BRAND.hostname}-version`);
|
|
20063
20510
|
if (!existsSync31(versionFile)) return "unknown";
|
|
20064
|
-
const content =
|
|
20511
|
+
const content = readFileSync31(versionFile, "utf-8").trim();
|
|
20065
20512
|
return content || "unknown";
|
|
20066
20513
|
} catch {
|
|
20067
20514
|
return "unknown";
|
|
@@ -20102,7 +20549,7 @@ var clientErrorReporterScript = `<script>
|
|
|
20102
20549
|
function cachedHtml(file) {
|
|
20103
20550
|
let html = htmlCache.get(file);
|
|
20104
20551
|
if (!html) {
|
|
20105
|
-
html =
|
|
20552
|
+
html = readFileSync31(resolve33(process.cwd(), "public", file), "utf-8");
|
|
20106
20553
|
const productNameEsc = escapeHtml(BRAND.productName);
|
|
20107
20554
|
html = html.replace(/<title>([^<]*)<\/title>/, (_match, inner) => `<title>${escapeHtml(inner).replace(/Maxy/g, productNameEsc)}</title>`);
|
|
20108
20555
|
html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
|
|
@@ -20120,13 +20567,13 @@ ${clientErrorReporterScript}
|
|
|
20120
20567
|
}
|
|
20121
20568
|
var brandedHtmlCache = /* @__PURE__ */ new Map();
|
|
20122
20569
|
function loadBrandingCache(agentSlug) {
|
|
20123
|
-
const configDir2 =
|
|
20570
|
+
const configDir2 = join28(homedir3(), BRAND.configDir);
|
|
20124
20571
|
try {
|
|
20125
20572
|
const accountId = getDefaultAccountId();
|
|
20126
20573
|
if (!accountId) return null;
|
|
20127
|
-
const cachePath =
|
|
20574
|
+
const cachePath = join28(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
|
|
20128
20575
|
if (!existsSync31(cachePath)) return null;
|
|
20129
|
-
return JSON.parse(
|
|
20576
|
+
return JSON.parse(readFileSync31(cachePath, "utf-8"));
|
|
20130
20577
|
} catch {
|
|
20131
20578
|
return null;
|
|
20132
20579
|
}
|
|
@@ -20226,7 +20673,7 @@ app52.use("/vnc-popout.html", logViewerFetch);
|
|
|
20226
20673
|
app52.get("/vnc-popout.html", (c) => {
|
|
20227
20674
|
let html = htmlCache.get("vnc-popout.html");
|
|
20228
20675
|
if (!html) {
|
|
20229
|
-
html =
|
|
20676
|
+
html = readFileSync31(resolve33(process.cwd(), "public", "vnc-popout.html"), "utf-8");
|
|
20230
20677
|
const name = escapeHtml(BRAND.productName);
|
|
20231
20678
|
html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
|
|
20232
20679
|
html = html.replace("</head>", ` ${brandScript}
|
|
@@ -20438,7 +20885,7 @@ async function runAdminUserReconcileTick() {
|
|
|
20438
20885
|
console.error("[adminuser-self-heal] skip reason=no-users-file");
|
|
20439
20886
|
return;
|
|
20440
20887
|
}
|
|
20441
|
-
const usersRaw =
|
|
20888
|
+
const usersRaw = readFileSync31(USERS_FILE, "utf-8").trim();
|
|
20442
20889
|
if (!usersRaw) {
|
|
20443
20890
|
console.error("[adminuser-self-heal] skip reason=empty-users-file");
|
|
20444
20891
|
return;
|
|
@@ -20490,7 +20937,7 @@ try {
|
|
|
20490
20937
|
} catch (err) {
|
|
20491
20938
|
console.error(`[graph-health] account-enumeration unavailable reason=${err instanceof Error ? err.message : String(err)}`);
|
|
20492
20939
|
}
|
|
20493
|
-
var configDirForWhatsApp =
|
|
20940
|
+
var configDirForWhatsApp = basename10(MAXY_DIR) || ".maxy";
|
|
20494
20941
|
var bootAccount = resolveAccount();
|
|
20495
20942
|
if (bootAccount) {
|
|
20496
20943
|
migrateRemovedConfigKeys(bootAccount.accountDir);
|
|
@@ -20552,7 +20999,7 @@ if (bootAccountConfig?.whatsapp) {
|
|
|
20552
20999
|
}
|
|
20553
21000
|
init({
|
|
20554
21001
|
configDir: configDirForWhatsApp,
|
|
20555
|
-
platformRoot:
|
|
21002
|
+
platformRoot: resolve33(process.env.MAXY_PLATFORM_ROOT ?? join28(__dirname, "..")),
|
|
20556
21003
|
accountConfig: bootAccountConfig,
|
|
20557
21004
|
onMessage: async (msg) => {
|
|
20558
21005
|
if (msg.isOwnerMirror) {
|