@rubytech/create-maxy 1.0.621 → 1.0.623
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/platform/lib/device-url/dist/index.d.ts +44 -0
- package/payload/platform/lib/device-url/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/device-url/dist/index.js +68 -0
- package/payload/platform/lib/device-url/dist/index.js.map +1 -0
- package/payload/platform/lib/device-url/src/index.ts +78 -0
- package/payload/platform/lib/device-url/tsconfig.json +8 -0
- package/payload/platform/package.json +2 -2
- package/payload/platform/plugins/admin/mcp/dist/index.js +12 -5
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/cloudflare/PLUGIN.md +28 -2
- package/payload/platform/plugins/cloudflare/mcp/dist/index.js +119 -29
- package/payload/platform/plugins/cloudflare/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts +30 -17
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts.map +1 -1
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js +78 -34
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js.map +1 -1
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.d.ts +90 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.d.ts.map +1 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.js +550 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.js.map +1 -0
- package/payload/platform/plugins/cloudflare/references/setup-guide.md +5 -6
- package/payload/platform/plugins/cloudflare/skills/setup-tunnel/SKILL.md +4 -56
- package/payload/platform/plugins/docs/references/cloudflare.md +18 -11
- package/payload/platform/templates/agents/admin/IDENTITY.md +8 -0
- package/payload/server/public/assets/admin-BxVuKRJZ.js +352 -0
- package/payload/server/public/assets/{public-ZM0fHAOE.js → public-Bgm9WQFZ.js} +2 -2
- package/payload/server/public/assets/useVoiceRecorder-BORuG_su.css +1 -0
- package/payload/server/public/assets/useVoiceRecorder-CiYPZu3g.js +44 -0
- package/payload/server/public/index.html +3 -3
- package/payload/server/public/public.html +3 -3
- package/payload/server/server.js +553 -202
- package/payload/server/public/assets/admin-D7LRdkYB.js +0 -352
- package/payload/server/public/assets/useVoiceRecorder-CaFVzk8y.css +0 -1
- package/payload/server/public/assets/useVoiceRecorder-OB_Gtr0e.js +0 -43
package/payload/server/server.js
CHANGED
|
@@ -2896,8 +2896,8 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
2896
2896
|
};
|
|
2897
2897
|
|
|
2898
2898
|
// server/index.ts
|
|
2899
|
-
import { readFileSync as
|
|
2900
|
-
import { resolve as resolve27, join as
|
|
2899
|
+
import { readFileSync as readFileSync27, existsSync as existsSync27, watchFile } from "fs";
|
|
2900
|
+
import { resolve as resolve27, join as join14, basename as basename6 } from "path";
|
|
2901
2901
|
import { homedir as homedir4 } from "os";
|
|
2902
2902
|
|
|
2903
2903
|
// app/lib/vnc-logger.ts
|
|
@@ -3829,7 +3829,7 @@ Content-Length: 0\r
|
|
|
3829
3829
|
}
|
|
3830
3830
|
|
|
3831
3831
|
// app/api/health/route.ts
|
|
3832
|
-
import { existsSync as
|
|
3832
|
+
import { existsSync as existsSync12, readFileSync as readFileSync13 } from "fs";
|
|
3833
3833
|
import { createConnection as createConnection3 } from "net";
|
|
3834
3834
|
|
|
3835
3835
|
// app/lib/claude-auth.ts
|
|
@@ -4164,9 +4164,9 @@ function keyFilePath() {
|
|
|
4164
4164
|
import Anthropic2 from "@anthropic-ai/sdk";
|
|
4165
4165
|
import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
|
|
4166
4166
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
4167
|
-
import { resolve as resolve6, join as
|
|
4167
|
+
import { resolve as resolve6, join as join5 } from "path";
|
|
4168
4168
|
import { platform as osPlatform } from "os";
|
|
4169
|
-
import { readFileSync as
|
|
4169
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, readdirSync as readdirSync2, existsSync as existsSync7, mkdirSync as mkdirSync4, createWriteStream, statSync as statSync3, unlinkSync, cpSync, rmSync } from "fs";
|
|
4170
4170
|
import { lookup as dnsLookup } from "dns/promises";
|
|
4171
4171
|
import { createConnection as netConnect } from "net";
|
|
4172
4172
|
import { StringDecoder } from "string_decoder";
|
|
@@ -6037,6 +6037,73 @@ ${items}
|
|
|
6037
6037
|
</commitment-detected>`;
|
|
6038
6038
|
}
|
|
6039
6039
|
|
|
6040
|
+
// app/lib/tool-surface-filter.ts
|
|
6041
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
|
|
6042
|
+
import { join as join4 } from "path";
|
|
6043
|
+
var ALLOWED_CLOUDFLARE_DURING_SETUP = /* @__PURE__ */ new Set([
|
|
6044
|
+
"mcp__cloudflare__cloudflare-setup-run",
|
|
6045
|
+
"mcp__cloudflare__cloudflare-setup-status"
|
|
6046
|
+
]);
|
|
6047
|
+
function shouldRemoveDuringCloudflareSetup(toolName) {
|
|
6048
|
+
if (toolName.startsWith("mcp__plugin_playwright_playwright__")) return true;
|
|
6049
|
+
if (toolName.startsWith("mcp__plugin_chrome-devtools-mcp_chrome-devtools__")) return true;
|
|
6050
|
+
if (toolName.startsWith("mcp__cloudflare__")) {
|
|
6051
|
+
return !ALLOWED_CLOUDFLARE_DURING_SETUP.has(toolName);
|
|
6052
|
+
}
|
|
6053
|
+
if (toolName === "Bash") return true;
|
|
6054
|
+
if (toolName === "Write") return true;
|
|
6055
|
+
if (toolName === "Edit") return true;
|
|
6056
|
+
if (toolName === "NotebookEdit") return true;
|
|
6057
|
+
return false;
|
|
6058
|
+
}
|
|
6059
|
+
function readCloudflareSetupState() {
|
|
6060
|
+
const path2 = join4(MAXY_DIR, "cloudflared", "setup.state.json");
|
|
6061
|
+
if (!existsSync6(path2)) return null;
|
|
6062
|
+
try {
|
|
6063
|
+
const raw2 = readFileSync7(path2, "utf-8");
|
|
6064
|
+
const parsed = JSON.parse(raw2);
|
|
6065
|
+
if (typeof parsed?.phase !== "string") return null;
|
|
6066
|
+
const phase = parsed.phase;
|
|
6067
|
+
const setupActive = phase !== "idle" && phase !== "healthy" && phase !== "unhealthy";
|
|
6068
|
+
return { phase, domain: parsed.domain ?? null, setupActive };
|
|
6069
|
+
} catch {
|
|
6070
|
+
return null;
|
|
6071
|
+
}
|
|
6072
|
+
}
|
|
6073
|
+
function applyToolSurfaceFilters(tools) {
|
|
6074
|
+
const cf = readCloudflareSetupState();
|
|
6075
|
+
if (cf && cf.setupActive) {
|
|
6076
|
+
const kept = [];
|
|
6077
|
+
const removed = [];
|
|
6078
|
+
for (const t of tools) {
|
|
6079
|
+
if (shouldRemoveDuringCloudflareSetup(t)) {
|
|
6080
|
+
removed.push(t);
|
|
6081
|
+
} else {
|
|
6082
|
+
kept.push(t);
|
|
6083
|
+
}
|
|
6084
|
+
}
|
|
6085
|
+
return {
|
|
6086
|
+
tools: kept,
|
|
6087
|
+
filtered: "cloudflare-setup",
|
|
6088
|
+
phase: cf.phase,
|
|
6089
|
+
removed: removed.length,
|
|
6090
|
+
removedList: [...removed].sort().join(",")
|
|
6091
|
+
};
|
|
6092
|
+
}
|
|
6093
|
+
return {
|
|
6094
|
+
tools,
|
|
6095
|
+
filtered: "none",
|
|
6096
|
+
phase: cf?.phase ?? "idle",
|
|
6097
|
+
removed: 0,
|
|
6098
|
+
removedList: ""
|
|
6099
|
+
};
|
|
6100
|
+
}
|
|
6101
|
+
function logToolSurfaceFilter(result) {
|
|
6102
|
+
console.error(
|
|
6103
|
+
`[chat-driver:tool-surface] filtered=${result.filtered} phase=${result.phase} removed=${result.removed} removed-list=${result.removedList}`
|
|
6104
|
+
);
|
|
6105
|
+
}
|
|
6106
|
+
|
|
6040
6107
|
// app/lib/claude-agent.ts
|
|
6041
6108
|
var LOG_RETENTION_DAYS = 7;
|
|
6042
6109
|
var BROWSER_TOOL_PREFIXES = [
|
|
@@ -6270,7 +6337,7 @@ function sampleProcState(pid) {
|
|
|
6270
6337
|
let sockets2 = 0;
|
|
6271
6338
|
for (const tcpFile of ["/proc/" + pid + "/net/tcp", "/proc/" + pid + "/net/tcp6"]) {
|
|
6272
6339
|
try {
|
|
6273
|
-
const raw2 =
|
|
6340
|
+
const raw2 = readFileSync8(tcpFile, "utf-8");
|
|
6274
6341
|
const lines2 = raw2.split("\n");
|
|
6275
6342
|
for (let i = 1; i < lines2.length; i++) {
|
|
6276
6343
|
const line = lines2[i].trim();
|
|
@@ -6286,7 +6353,7 @@ function sampleProcState(pid) {
|
|
|
6286
6353
|
}
|
|
6287
6354
|
let rssMb = 0;
|
|
6288
6355
|
try {
|
|
6289
|
-
const statm =
|
|
6356
|
+
const statm = readFileSync8(`/proc/${pid}/statm`, "utf-8").trim().split(/\s+/);
|
|
6290
6357
|
const rssPages = parseInt(statm[1] ?? "0", 10);
|
|
6291
6358
|
if (Number.isFinite(rssPages)) rssMb = Math.round(rssPages * 4096 / (1024 * 1024));
|
|
6292
6359
|
} catch {
|
|
@@ -6321,19 +6388,19 @@ function sampleProcState(pid) {
|
|
|
6321
6388
|
}
|
|
6322
6389
|
var PLATFORM_ROOT4 = process.env.MAXY_PLATFORM_ROOT ?? resolve6(process.cwd(), "..");
|
|
6323
6390
|
var ACCOUNTS_DIR = resolve6(PLATFORM_ROOT4, "..", "data/accounts");
|
|
6324
|
-
if (!
|
|
6391
|
+
if (!existsSync7(PLATFORM_ROOT4)) {
|
|
6325
6392
|
throw new Error(
|
|
6326
6393
|
`PLATFORM_ROOT does not exist: ${PLATFORM_ROOT4}
|
|
6327
6394
|
Set the MAXY_PLATFORM_ROOT environment variable to the absolute path of the platform directory.`
|
|
6328
6395
|
);
|
|
6329
6396
|
}
|
|
6330
6397
|
function resolveAccount() {
|
|
6331
|
-
if (!
|
|
6398
|
+
if (!existsSync7(ACCOUNTS_DIR)) return null;
|
|
6332
6399
|
const usersFilePath = resolve6(PLATFORM_ROOT4, "config", "users.json");
|
|
6333
6400
|
let usersJsonUserId = null;
|
|
6334
|
-
if (
|
|
6401
|
+
if (existsSync7(usersFilePath)) {
|
|
6335
6402
|
try {
|
|
6336
|
-
const raw2 =
|
|
6403
|
+
const raw2 = readFileSync8(usersFilePath, "utf-8").trim();
|
|
6337
6404
|
if (raw2) {
|
|
6338
6405
|
const users = JSON.parse(raw2);
|
|
6339
6406
|
if (users.length > 0) {
|
|
@@ -6348,8 +6415,8 @@ function resolveAccount() {
|
|
|
6348
6415
|
for (const entry of entries) {
|
|
6349
6416
|
if (!entry.isDirectory()) continue;
|
|
6350
6417
|
const configPath2 = resolve6(ACCOUNTS_DIR, entry.name, "account.json");
|
|
6351
|
-
if (!
|
|
6352
|
-
const raw2 =
|
|
6418
|
+
if (!existsSync7(configPath2)) continue;
|
|
6419
|
+
const raw2 = readFileSync8(configPath2, "utf-8");
|
|
6353
6420
|
let config2;
|
|
6354
6421
|
try {
|
|
6355
6422
|
config2 = JSON.parse(raw2);
|
|
@@ -6383,8 +6450,8 @@ function resolveAccount() {
|
|
|
6383
6450
|
}
|
|
6384
6451
|
function readAgentFile(accountDir, agentName, filename) {
|
|
6385
6452
|
const filePath = resolve6(accountDir, "agents", agentName, filename);
|
|
6386
|
-
if (!
|
|
6387
|
-
return
|
|
6453
|
+
if (!existsSync7(filePath)) return null;
|
|
6454
|
+
return readFileSync8(filePath, "utf-8");
|
|
6388
6455
|
}
|
|
6389
6456
|
function readIdentity(accountDir, agentName) {
|
|
6390
6457
|
return readAgentFile(accountDir, agentName, "IDENTITY.md");
|
|
@@ -6401,13 +6468,13 @@ function validateAgentSlug(slug) {
|
|
|
6401
6468
|
}
|
|
6402
6469
|
function resolveDefaultAgentSlug(accountDir) {
|
|
6403
6470
|
const configPath2 = resolve6(accountDir, "account.json");
|
|
6404
|
-
if (!
|
|
6471
|
+
if (!existsSync7(configPath2)) {
|
|
6405
6472
|
console.error("[agent-resolve] account.json not found \u2014 cannot resolve defaultAgent");
|
|
6406
6473
|
return null;
|
|
6407
6474
|
}
|
|
6408
6475
|
let config2;
|
|
6409
6476
|
try {
|
|
6410
|
-
config2 = JSON.parse(
|
|
6477
|
+
config2 = JSON.parse(readFileSync8(configPath2, "utf-8"));
|
|
6411
6478
|
} catch (err) {
|
|
6412
6479
|
console.error("[agent-resolve] failed to read account.json:", err);
|
|
6413
6480
|
return null;
|
|
@@ -6417,7 +6484,7 @@ function resolveDefaultAgentSlug(accountDir) {
|
|
|
6417
6484
|
return null;
|
|
6418
6485
|
}
|
|
6419
6486
|
const agentConfigPath = resolve6(accountDir, "agents", config2.defaultAgent, "config.json");
|
|
6420
|
-
if (!
|
|
6487
|
+
if (!existsSync7(agentConfigPath)) {
|
|
6421
6488
|
console.error(`[agent-resolve] defaultAgent="${config2.defaultAgent}" has no config.json at ${agentConfigPath}`);
|
|
6422
6489
|
return null;
|
|
6423
6490
|
}
|
|
@@ -6492,20 +6559,20 @@ function resolveAgentConfig(accountDir, agentName) {
|
|
|
6492
6559
|
const agentDir = resolve6(accountDir, "agents", agentName);
|
|
6493
6560
|
const knowledgePath = resolve6(agentDir, "KNOWLEDGE.md");
|
|
6494
6561
|
const summaryPath = resolve6(agentDir, "KNOWLEDGE-SUMMARY.md");
|
|
6495
|
-
const hasKnowledge =
|
|
6496
|
-
const hasSummary =
|
|
6562
|
+
const hasKnowledge = existsSync7(knowledgePath);
|
|
6563
|
+
const hasSummary = existsSync7(summaryPath);
|
|
6497
6564
|
if (hasKnowledge && hasSummary) {
|
|
6498
6565
|
const knowledgeMtime = statSync3(knowledgePath).mtimeMs;
|
|
6499
6566
|
const summaryMtime = statSync3(summaryPath).mtimeMs;
|
|
6500
6567
|
if (summaryMtime >= knowledgeMtime) {
|
|
6501
|
-
knowledge =
|
|
6568
|
+
knowledge = readFileSync8(summaryPath, "utf-8");
|
|
6502
6569
|
} else {
|
|
6503
6570
|
console.warn(`[agent-config] ${agentName}: KNOWLEDGE-SUMMARY.md is stale (KNOWLEDGE.md is newer) \u2014 using full knowledge`);
|
|
6504
|
-
knowledge =
|
|
6571
|
+
knowledge = readFileSync8(knowledgePath, "utf-8");
|
|
6505
6572
|
}
|
|
6506
6573
|
knowledgeBaked = true;
|
|
6507
6574
|
} else if (hasKnowledge) {
|
|
6508
|
-
knowledge =
|
|
6575
|
+
knowledge = readFileSync8(knowledgePath, "utf-8");
|
|
6509
6576
|
knowledgeBaked = true;
|
|
6510
6577
|
}
|
|
6511
6578
|
let budget = null;
|
|
@@ -6528,10 +6595,10 @@ function resolveAgentConfig(accountDir, agentName) {
|
|
|
6528
6595
|
}
|
|
6529
6596
|
function parsePluginFrontmatter(pluginDir) {
|
|
6530
6597
|
const pluginPath = resolve6(PLATFORM_ROOT4, "plugins", pluginDir, "PLUGIN.md");
|
|
6531
|
-
if (!
|
|
6598
|
+
if (!existsSync7(pluginPath)) return null;
|
|
6532
6599
|
let raw2;
|
|
6533
6600
|
try {
|
|
6534
|
-
raw2 =
|
|
6601
|
+
raw2 = readFileSync8(pluginPath, "utf-8");
|
|
6535
6602
|
} catch {
|
|
6536
6603
|
console.warn(`[plugins] cannot read ${pluginPath}`);
|
|
6537
6604
|
return null;
|
|
@@ -6592,22 +6659,22 @@ function autoDeliverPremiumPlugins(purchasedPlugins) {
|
|
|
6592
6659
|
const TAG18 = "[premium-auto-deliver]";
|
|
6593
6660
|
const stagingRoot = resolve6(PLATFORM_ROOT4, "../premium-plugins");
|
|
6594
6661
|
const pluginsDir = resolve6(PLATFORM_ROOT4, "plugins");
|
|
6595
|
-
if (!
|
|
6662
|
+
if (!existsSync7(stagingRoot)) {
|
|
6596
6663
|
console.log(`${TAG18} no staging directory \u2014 skipping`);
|
|
6597
6664
|
return;
|
|
6598
6665
|
}
|
|
6599
6666
|
for (const pluginName of purchasedPlugins) {
|
|
6600
6667
|
const stagingDir = resolve6(stagingRoot, pluginName);
|
|
6601
|
-
if (!
|
|
6668
|
+
if (!existsSync7(stagingDir)) {
|
|
6602
6669
|
console.log(`${TAG18} ${pluginName}: not in staging \u2014 skipping`);
|
|
6603
6670
|
continue;
|
|
6604
6671
|
}
|
|
6605
|
-
const bundlePath =
|
|
6606
|
-
const isBundle =
|
|
6672
|
+
const bundlePath = join5(stagingDir, "BUNDLE.md");
|
|
6673
|
+
const isBundle = existsSync7(bundlePath);
|
|
6607
6674
|
if (isBundle) {
|
|
6608
6675
|
let bundleRaw;
|
|
6609
6676
|
try {
|
|
6610
|
-
bundleRaw =
|
|
6677
|
+
bundleRaw = readFileSync8(bundlePath, "utf-8");
|
|
6611
6678
|
} catch (err) {
|
|
6612
6679
|
console.log(`${TAG18} ${pluginName}: cannot read BUNDLE.md \u2014 ${err instanceof Error ? err.message : String(err)}`);
|
|
6613
6680
|
continue;
|
|
@@ -6639,12 +6706,12 @@ function autoDeliverPremiumPlugins(purchasedPlugins) {
|
|
|
6639
6706
|
let skipped = 0;
|
|
6640
6707
|
for (const sub of subPlugins) {
|
|
6641
6708
|
const target = resolve6(pluginsDir, sub);
|
|
6642
|
-
if (
|
|
6709
|
+
if (existsSync7(resolve6(target, "PLUGIN.md"))) {
|
|
6643
6710
|
skipped++;
|
|
6644
6711
|
continue;
|
|
6645
6712
|
}
|
|
6646
6713
|
const source = resolve6(stagingDir, "plugins", sub);
|
|
6647
|
-
if (!
|
|
6714
|
+
if (!existsSync7(source)) {
|
|
6648
6715
|
console.log(`${TAG18} ${pluginName}/${sub}: source missing in staging \u2014 skipping`);
|
|
6649
6716
|
continue;
|
|
6650
6717
|
}
|
|
@@ -6658,7 +6725,7 @@ function autoDeliverPremiumPlugins(purchasedPlugins) {
|
|
|
6658
6725
|
console.log(`${TAG18} ${pluginName} (bundle): ${delivered} delivered, ${skipped} already present`);
|
|
6659
6726
|
} else {
|
|
6660
6727
|
const target = resolve6(pluginsDir, pluginName);
|
|
6661
|
-
if (
|
|
6728
|
+
if (existsSync7(resolve6(target, "PLUGIN.md"))) {
|
|
6662
6729
|
console.log(`${TAG18} ${pluginName}: already present \u2014 skipping`);
|
|
6663
6730
|
continue;
|
|
6664
6731
|
}
|
|
@@ -6700,7 +6767,7 @@ function migratePluginRenames(accountDir, config2) {
|
|
|
6700
6767
|
if (!changed) return;
|
|
6701
6768
|
const configPath2 = resolve6(accountDir, "account.json");
|
|
6702
6769
|
try {
|
|
6703
|
-
const raw2 =
|
|
6770
|
+
const raw2 = readFileSync8(configPath2, "utf-8");
|
|
6704
6771
|
const parsed = JSON.parse(raw2);
|
|
6705
6772
|
parsed.enabledPlugins = migrated;
|
|
6706
6773
|
writeFileSync5(configPath2, JSON.stringify(parsed, null, 2) + "\n");
|
|
@@ -6712,7 +6779,7 @@ function migratePluginRenames(accountDir, config2) {
|
|
|
6712
6779
|
const pluginsDir = resolve6(PLATFORM_ROOT4, "plugins");
|
|
6713
6780
|
for (const oldName of Object.keys(PLUGIN_RENAMES)) {
|
|
6714
6781
|
const orphan = resolve6(pluginsDir, oldName);
|
|
6715
|
-
if (
|
|
6782
|
+
if (existsSync7(orphan)) {
|
|
6716
6783
|
try {
|
|
6717
6784
|
rmSync(orphan, { recursive: true });
|
|
6718
6785
|
console.log(`${TAG18} removed orphan: ${oldName}`);
|
|
@@ -6727,20 +6794,20 @@ function autoDeliverBundleAgents(accountDir, purchasedPlugins) {
|
|
|
6727
6794
|
const TAG18 = "[bundle-agent-deliver]";
|
|
6728
6795
|
const stagingRoot = resolve6(PLATFORM_ROOT4, "../premium-plugins");
|
|
6729
6796
|
const specialistsDir = resolve6(accountDir, "specialists", "agents");
|
|
6730
|
-
if (!
|
|
6731
|
-
if (!
|
|
6797
|
+
if (!existsSync7(stagingRoot)) return;
|
|
6798
|
+
if (!existsSync7(specialistsDir)) {
|
|
6732
6799
|
mkdirSync4(specialistsDir, { recursive: true });
|
|
6733
6800
|
}
|
|
6734
6801
|
const agentsmdPath = resolve6(accountDir, "agents", "admin", "AGENTS.md");
|
|
6735
6802
|
let agentsmd = "";
|
|
6736
6803
|
try {
|
|
6737
|
-
agentsmd =
|
|
6804
|
+
agentsmd = existsSync7(agentsmdPath) ? readFileSync8(agentsmdPath, "utf-8") : "";
|
|
6738
6805
|
} catch {
|
|
6739
6806
|
}
|
|
6740
6807
|
let delivered = 0;
|
|
6741
6808
|
for (const pluginName of purchasedPlugins) {
|
|
6742
6809
|
const bundleAgentsDir = resolve6(stagingRoot, pluginName, "agents");
|
|
6743
|
-
if (!
|
|
6810
|
+
if (!existsSync7(bundleAgentsDir)) continue;
|
|
6744
6811
|
let entries;
|
|
6745
6812
|
try {
|
|
6746
6813
|
entries = readdirSync2(bundleAgentsDir).filter((f) => f.endsWith(".md"));
|
|
@@ -6749,7 +6816,7 @@ function autoDeliverBundleAgents(accountDir, purchasedPlugins) {
|
|
|
6749
6816
|
}
|
|
6750
6817
|
for (const filename of entries) {
|
|
6751
6818
|
const target = resolve6(specialistsDir, filename);
|
|
6752
|
-
if (
|
|
6819
|
+
if (existsSync7(target)) continue;
|
|
6753
6820
|
const source = resolve6(bundleAgentsDir, filename);
|
|
6754
6821
|
try {
|
|
6755
6822
|
cpSync(source, target);
|
|
@@ -6758,7 +6825,7 @@ function autoDeliverBundleAgents(accountDir, purchasedPlugins) {
|
|
|
6758
6825
|
continue;
|
|
6759
6826
|
}
|
|
6760
6827
|
try {
|
|
6761
|
-
const content =
|
|
6828
|
+
const content = readFileSync8(target, "utf-8");
|
|
6762
6829
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
6763
6830
|
if (fmMatch) {
|
|
6764
6831
|
const nameMatch = fmMatch[1].match(/^name:\s*(.+)/m);
|
|
@@ -6794,7 +6861,7 @@ function assemblePublicPluginContent(pluginDir) {
|
|
|
6794
6861
|
const pluginPath = resolve6(pluginRoot, "PLUGIN.md");
|
|
6795
6862
|
let raw2;
|
|
6796
6863
|
try {
|
|
6797
|
-
raw2 =
|
|
6864
|
+
raw2 = readFileSync8(pluginPath, "utf-8");
|
|
6798
6865
|
} catch {
|
|
6799
6866
|
return null;
|
|
6800
6867
|
}
|
|
@@ -6815,7 +6882,7 @@ function assemblePublicPluginContent(pluginDir) {
|
|
|
6815
6882
|
const skillMdPath = resolve6(skillDir, "SKILL.md");
|
|
6816
6883
|
let skillRaw;
|
|
6817
6884
|
try {
|
|
6818
|
-
skillRaw =
|
|
6885
|
+
skillRaw = readFileSync8(skillMdPath, "utf-8");
|
|
6819
6886
|
} catch {
|
|
6820
6887
|
continue;
|
|
6821
6888
|
}
|
|
@@ -6866,7 +6933,7 @@ function assemblePublicPluginContent(pluginDir) {
|
|
|
6866
6933
|
}
|
|
6867
6934
|
for (const refFile of refFiles) {
|
|
6868
6935
|
try {
|
|
6869
|
-
const refContent =
|
|
6936
|
+
const refContent = readFileSync8(resolve6(refsDir, refFile), "utf-8").trim();
|
|
6870
6937
|
if (refContent) {
|
|
6871
6938
|
parts.push(`
|
|
6872
6939
|
<!-- reference: ${refFile} -->`);
|
|
@@ -6940,7 +7007,7 @@ function loadEmbeddedPlugins(agentType, selectedPlugins, enabledPlugins) {
|
|
|
6940
7007
|
const pluginPath = resolve6(pluginsDir, dir, "PLUGIN.md");
|
|
6941
7008
|
let raw2;
|
|
6942
7009
|
try {
|
|
6943
|
-
raw2 =
|
|
7010
|
+
raw2 = readFileSync8(pluginPath, "utf-8");
|
|
6944
7011
|
} catch (err) {
|
|
6945
7012
|
console.warn(`[plugins] ${dir}: failed to read PLUGIN.md for ${agentType} embed: ${String(err)}`);
|
|
6946
7013
|
continue;
|
|
@@ -6965,7 +7032,7 @@ function fetchMcpToolsList(pluginDir) {
|
|
|
6965
7032
|
const cached2 = mcpToolsCache.get(pluginDir);
|
|
6966
7033
|
if (cached2) return Promise.resolve(cached2);
|
|
6967
7034
|
const serverPath = resolve6(PLATFORM_ROOT4, "plugins", pluginDir, "mcp/dist/index.js");
|
|
6968
|
-
if (!
|
|
7035
|
+
if (!existsSync7(serverPath)) return Promise.resolve([]);
|
|
6969
7036
|
const startMs = Date.now();
|
|
6970
7037
|
return new Promise((resolvePromise) => {
|
|
6971
7038
|
const proc = spawn2(process.execPath, [serverPath], {
|
|
@@ -7169,7 +7236,7 @@ ${specialist}: ${plugins.join(", ")}`);
|
|
|
7169
7236
|
const references = [];
|
|
7170
7237
|
const scanDir = (base, prefix, target) => {
|
|
7171
7238
|
const scanPath = resolve6(pluginRoot, base);
|
|
7172
|
-
if (!
|
|
7239
|
+
if (!existsSync7(scanPath)) return;
|
|
7173
7240
|
try {
|
|
7174
7241
|
const walk = (current, rel) => {
|
|
7175
7242
|
for (const entry of readdirSync2(current)) {
|
|
@@ -7202,7 +7269,7 @@ ${specialist}: ${plugins.join(", ")}`);
|
|
|
7202
7269
|
}
|
|
7203
7270
|
} else if (parsed.tools.length > 0) {
|
|
7204
7271
|
const serverPath = resolve6(PLATFORM_ROOT4, "plugins", dir, "mcp/dist/index.js");
|
|
7205
|
-
if (
|
|
7272
|
+
if (existsSync7(serverPath)) {
|
|
7206
7273
|
fallbackSourced++;
|
|
7207
7274
|
console.error(`[plugin-manifest] ${dir}: tools/list empty \u2014 fallback to frontmatter (${parsed.tools.length} tools)`);
|
|
7208
7275
|
}
|
|
@@ -7277,16 +7344,16 @@ function getDefaultAccountId() {
|
|
|
7277
7344
|
return resolveAccount()?.accountId ?? null;
|
|
7278
7345
|
}
|
|
7279
7346
|
function resolveUserAccounts(userId) {
|
|
7280
|
-
if (!
|
|
7347
|
+
if (!existsSync7(ACCOUNTS_DIR)) return [];
|
|
7281
7348
|
const results = [];
|
|
7282
7349
|
const entries = readdirSync2(ACCOUNTS_DIR, { withFileTypes: true });
|
|
7283
7350
|
for (const entry of entries) {
|
|
7284
7351
|
if (!entry.isDirectory()) continue;
|
|
7285
7352
|
const configPath2 = resolve6(ACCOUNTS_DIR, entry.name, "account.json");
|
|
7286
|
-
if (!
|
|
7353
|
+
if (!existsSync7(configPath2)) continue;
|
|
7287
7354
|
let config2;
|
|
7288
7355
|
try {
|
|
7289
|
-
config2 = JSON.parse(
|
|
7356
|
+
config2 = JSON.parse(readFileSync8(configPath2, "utf-8"));
|
|
7290
7357
|
} catch {
|
|
7291
7358
|
console.error(`[session] account.json corrupt at ${configPath2} \u2014 skipping`);
|
|
7292
7359
|
continue;
|
|
@@ -7606,7 +7673,7 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
|
|
|
7606
7673
|
}
|
|
7607
7674
|
}
|
|
7608
7675
|
const mcpEntry = resolve6(PLATFORM_ROOT4, "plugins", dir, "mcp/dist/index.js");
|
|
7609
|
-
if (!
|
|
7676
|
+
if (!existsSync7(mcpEntry)) continue;
|
|
7610
7677
|
servers[dir] = {
|
|
7611
7678
|
command: "node",
|
|
7612
7679
|
args: [mcpEntry],
|
|
@@ -7691,6 +7758,8 @@ var ADMIN_CORE_TOOLS = [
|
|
|
7691
7758
|
"mcp__cloudflare__tunnel-enable",
|
|
7692
7759
|
"mcp__cloudflare__tunnel-disable",
|
|
7693
7760
|
"mcp__cloudflare__tunnel-add-hostname",
|
|
7761
|
+
"mcp__cloudflare__cloudflare-setup-run",
|
|
7762
|
+
"mcp__cloudflare__cloudflare-setup-status",
|
|
7694
7763
|
"mcp__cloudflare__dns-lookup",
|
|
7695
7764
|
"mcp__tasks__task-create",
|
|
7696
7765
|
"mcp__tasks__task-update",
|
|
@@ -7757,6 +7826,12 @@ function getAdminAllowedTools(enabledPlugins) {
|
|
|
7757
7826
|
}
|
|
7758
7827
|
return tools;
|
|
7759
7828
|
}
|
|
7829
|
+
function assembleAllowedToolsForAdminSpawn(enabledPlugins) {
|
|
7830
|
+
const base = getAdminAllowedTools(enabledPlugins);
|
|
7831
|
+
const result = applyToolSurfaceFilters(base);
|
|
7832
|
+
logToolSurfaceFilter(result);
|
|
7833
|
+
return result.tools;
|
|
7834
|
+
}
|
|
7760
7835
|
var QUERY_CLASSIFIER_MODEL = HAIKU_MODEL2;
|
|
7761
7836
|
var QUERY_CLASSIFIER_TIMEOUT_MS = 3e3;
|
|
7762
7837
|
var QUERY_CLASSIFIER_MSG_CAP = 500;
|
|
@@ -7845,7 +7920,7 @@ ${message.slice(0, QUERY_CLASSIFIER_MSG_CAP)}`
|
|
|
7845
7920
|
}
|
|
7846
7921
|
async function fetchMemoryContext(accountId, query, sessionKey, options) {
|
|
7847
7922
|
const serverPath = resolve6(PLATFORM_ROOT4, "plugins/memory/mcp/dist/index.js");
|
|
7848
|
-
if (!
|
|
7923
|
+
if (!existsSync7(serverPath)) {
|
|
7849
7924
|
console.error(`[fetchMemoryContext] MCP server not found: ${serverPath}`);
|
|
7850
7925
|
return null;
|
|
7851
7926
|
}
|
|
@@ -7943,7 +8018,7 @@ async function fetchMemoryContext(accountId, query, sessionKey, options) {
|
|
|
7943
8018
|
async function compactTrimmedMessages(accountId, trimmedMessages) {
|
|
7944
8019
|
if (trimmedMessages.length === 0) return true;
|
|
7945
8020
|
const serverPath = resolve6(PLATFORM_ROOT4, "plugins/memory/mcp/dist/index.js");
|
|
7946
|
-
if (!
|
|
8021
|
+
if (!existsSync7(serverPath)) return false;
|
|
7947
8022
|
const briefing = trimmedMessages.map((m) => `[${m.role.toUpperCase()}] ${m.content}`).join("\n\n");
|
|
7948
8023
|
return new Promise((resolvePromise) => {
|
|
7949
8024
|
const proc = spawn2(process.execPath, [serverPath], {
|
|
@@ -8261,7 +8336,7 @@ var COMPACTION_TIMEOUT_MS = 45e3;
|
|
|
8261
8336
|
async function* runCompactionTurn(accountDir, accountId, systemPrompt, resumeSessionId, adminModel, conversationId, enabledPlugins) {
|
|
8262
8337
|
const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, conversationId, void 0, enabledPlugins) });
|
|
8263
8338
|
const specialistsDir = resolve6(accountDir, "specialists");
|
|
8264
|
-
if (!
|
|
8339
|
+
if (!existsSync7(specialistsDir)) agentLogStream("claude-agent-compaction-stream", accountDir, conversationId).write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
|
|
8265
8340
|
`);
|
|
8266
8341
|
const args = [
|
|
8267
8342
|
"--print",
|
|
@@ -8273,7 +8348,7 @@ async function* runCompactionTurn(accountDir, accountId, systemPrompt, resumeSes
|
|
|
8273
8348
|
"--mcp-config",
|
|
8274
8349
|
mcpConfig,
|
|
8275
8350
|
"--allowedTools",
|
|
8276
|
-
|
|
8351
|
+
assembleAllowedToolsForAdminSpawn(enabledPlugins).join(","),
|
|
8277
8352
|
"--permission-mode",
|
|
8278
8353
|
"dontAsk",
|
|
8279
8354
|
"--model",
|
|
@@ -9150,7 +9225,7 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
|
|
|
9150
9225
|
const ccUserId = sessionKey ? getUserIdForSession(sessionKey) : void 0;
|
|
9151
9226
|
const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, spawnConvId, ccUserId, enabledPlugins) });
|
|
9152
9227
|
const specialistsDir = resolve6(accountDir, "specialists");
|
|
9153
|
-
if (!
|
|
9228
|
+
if (!existsSync7(specialistsDir)) agentLogStream("claude-agent-stream", accountDir, spawnConvId).write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
|
|
9154
9229
|
`);
|
|
9155
9230
|
const args = [
|
|
9156
9231
|
"--print",
|
|
@@ -9162,7 +9237,7 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
|
|
|
9162
9237
|
"--mcp-config",
|
|
9163
9238
|
mcpConfig,
|
|
9164
9239
|
"--allowedTools",
|
|
9165
|
-
|
|
9240
|
+
assembleAllowedToolsForAdminSpawn(enabledPlugins).join(","),
|
|
9166
9241
|
"--permission-mode",
|
|
9167
9242
|
"dontAsk",
|
|
9168
9243
|
"--model",
|
|
@@ -9496,7 +9571,7 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
|
|
|
9496
9571
|
const managedUserId = getUserIdForSession(sessionKey);
|
|
9497
9572
|
const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, managedConvId, managedUserId, enabledPlugins) });
|
|
9498
9573
|
const specialistsDir = resolve6(accountDir, "specialists");
|
|
9499
|
-
if (!
|
|
9574
|
+
if (!existsSync7(specialistsDir)) streamLog.write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
|
|
9500
9575
|
`);
|
|
9501
9576
|
const fullMessage = attachments.length > 0 ? message + buildAttachmentMetaText(attachments) : message;
|
|
9502
9577
|
const args = [
|
|
@@ -9509,7 +9584,7 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
|
|
|
9509
9584
|
"--mcp-config",
|
|
9510
9585
|
mcpConfig,
|
|
9511
9586
|
"--allowedTools",
|
|
9512
|
-
|
|
9587
|
+
assembleAllowedToolsForAdminSpawn(enabledPlugins).join(","),
|
|
9513
9588
|
"--permission-mode",
|
|
9514
9589
|
"dontAsk",
|
|
9515
9590
|
"--model",
|
|
@@ -10166,7 +10241,7 @@ ${sessionContext}`;
|
|
|
10166
10241
|
const skillPath = resolve6(PLATFORM_ROOT4, "plugins/admin/skills/onboarding/SKILL.md");
|
|
10167
10242
|
let skillContent = "";
|
|
10168
10243
|
try {
|
|
10169
|
-
skillContent =
|
|
10244
|
+
skillContent = readFileSync8(skillPath, "utf-8");
|
|
10170
10245
|
} catch (err) {
|
|
10171
10246
|
console.error(`[onboarding-inject] accountId=${accountId.slice(0, 8)}\u2026 error=skill-read-failed path=${skillPath} reason=${err instanceof Error ? err.message : String(err)}`);
|
|
10172
10247
|
}
|
|
@@ -10366,7 +10441,7 @@ ${block}`;
|
|
|
10366
10441
|
import { basename as basename2 } from "path";
|
|
10367
10442
|
|
|
10368
10443
|
// app/lib/review-detector/rules.ts
|
|
10369
|
-
import { readFileSync as
|
|
10444
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync8, statSync as statSync4, mkdirSync as mkdirSync5, renameSync } from "fs";
|
|
10370
10445
|
import { resolve as resolve7, dirname as dirname2 } from "path";
|
|
10371
10446
|
var DEFAULT_SCAN_INTERVAL_MS = 5e3;
|
|
10372
10447
|
var RATE_LIMIT_PATTERN = "rate[- ]?limit(?:ed| reached| hit)|(?:HTTP|status)[^a-z]{0,3}429|too many requests";
|
|
@@ -10574,24 +10649,64 @@ function defaultRules() {
|
|
|
10574
10649
|
suggestedAction: "This laptop is signed into a Cloudflare account that does not own the hostnames the tunnel is configured to serve. Run `tunnel-status` to confirm, then tell the operator verbatim: 'The tunnel is running on this laptop but nothing from the internet is reaching it. The Cloudflare account this laptop is signed into doesn't own your domain. Open Cloudflare in your browser \u2014 is the account name in the top-left the one that owns your domain? If not, switch to the correct one, then tell me and I will re-sign-in.' When the operator confirms the correct account is selected, run `tunnel-login force=true`."
|
|
10575
10650
|
},
|
|
10576
10651
|
{
|
|
10577
|
-
// Task
|
|
10578
|
-
//
|
|
10579
|
-
//
|
|
10580
|
-
//
|
|
10581
|
-
//
|
|
10582
|
-
//
|
|
10583
|
-
//
|
|
10584
|
-
// that
|
|
10585
|
-
//
|
|
10586
|
-
//
|
|
10652
|
+
// Task 545: tunnel-login's terminal-failure class — cloudflared's
|
|
10653
|
+
// login process died without writing cert.pem. Covers every reason
|
|
10654
|
+
// the handler emits on the `failed` branch: either an unknown exit
|
|
10655
|
+
// (`-without-cert`), an exit preceded by the courtesy browser-launch
|
|
10656
|
+
// marker (`-with-marker`), auth URL never produced (`-timeout`), or
|
|
10657
|
+
// crashed before producing it at all. Task 541's original pattern
|
|
10658
|
+
// matched `reason=browser-launch-fetch-error` — Task 545 retired
|
|
10659
|
+
// that reason because the marker alone is no longer terminal
|
|
10660
|
+
// (cloudflared keeps its OAuth-callback loop alive after emitting
|
|
10661
|
+
// it). Use this pattern for any new terminal reason the handler
|
|
10662
|
+
// gains: extend the alternation rather than adding parallel rules.
|
|
10587
10663
|
id: "cloudflare-tunnel-login-failed",
|
|
10588
|
-
name: "Cloudflare tunnel-login
|
|
10664
|
+
name: "Cloudflare tunnel-login process terminated without writing cert",
|
|
10665
|
+
type: "silent-catch",
|
|
10666
|
+
logSource: "any",
|
|
10667
|
+
pattern: "\\[cloudflare:tunnel-login:failed\\] reason=(login-process-exited-without-cert|login-process-exited-with-marker|auth-url-timeout|process-exited-before-auth-url)",
|
|
10668
|
+
thresholdCount: 0,
|
|
10669
|
+
thresholdWindowMinutes: 0,
|
|
10670
|
+
suggestedAction: "The cloudflared login process died before cert.pem landed on disk. For `login-process-exited-*` reasons the tunnel-login tool detected the dead process on the next call and has already respawned it \u2014 relay the new sign-in URL and wait for the operator to authorize in the VNC browser. For `auth-url-timeout` / `process-exited-before-auth-url`, the tool's most recent call returned an error and did not respawn \u2014 call `tunnel-login` again to spawn a fresh attempt. Never open the Cloudflare dashboard in any other surface; the only auth path is the sign-in URL the tool produces."
|
|
10671
|
+
},
|
|
10672
|
+
{
|
|
10673
|
+
// Task 545: non-terminal advisory. cloudflared's browser-launch
|
|
10674
|
+
// subcommand failed (DISPLAY unreachable, xdg-open absent, etc.) but
|
|
10675
|
+
// its OAuth-callback listener is still running — the login can still
|
|
10676
|
+
// complete if a human opens the URL. This rule fires so the admin
|
|
10677
|
+
// agent can relay "open the URL yourself" to the operator the moment
|
|
10678
|
+
// the condition appears, rather than waiting for the operator to
|
|
10679
|
+
// notice nothing is happening in their browser. Task 546 will
|
|
10680
|
+
// obsolete the advisory by rendering auth URLs that auto-open in
|
|
10681
|
+
// the VNC browser.
|
|
10682
|
+
id: "cloudflare-tunnel-login-browser-launch-failed",
|
|
10683
|
+
name: "cloudflared couldn't open the sign-in URL (login still live)",
|
|
10589
10684
|
type: "silent-catch",
|
|
10590
10685
|
logSource: "any",
|
|
10591
|
-
pattern: "\\[cloudflare:tunnel-login:
|
|
10686
|
+
pattern: "\\[cloudflare:tunnel-login:browser-launch-failed\\]",
|
|
10592
10687
|
thresholdCount: 0,
|
|
10593
10688
|
thresholdWindowMinutes: 0,
|
|
10594
|
-
suggestedAction: "cloudflared's browser
|
|
10689
|
+
suggestedAction: "cloudflared's browser-launch courtesy failed on this laptop, but the sign-in URL is still live \u2014 the process is waiting for the OAuth callback. Tell the operator to open the sign-in URL in the VNC browser themselves and complete the authorization. The tunnel-login tool already includes the URL and the advisory in its most recent response; do not restart the login (that would invalidate the URL the operator is about to open)."
|
|
10690
|
+
},
|
|
10691
|
+
{
|
|
10692
|
+
// Task 545: raw-log surface. cloudflared-login.log is now read by
|
|
10693
|
+
// the review-detector's `cloudflared` source (see sources.ts). The
|
|
10694
|
+
// literal "Failed to fetch resource" is emitted by cloudflared
|
|
10695
|
+
// itself when its browser-launch subcommand can't reach a display.
|
|
10696
|
+
// This rule catches the cloudflared-side event even if the MCP
|
|
10697
|
+
// handler's classification drifts or the platform is restarting
|
|
10698
|
+
// mid-login and the advisory log line is not yet written. Keeping
|
|
10699
|
+
// this rule on a different source (log-line, not MCP stderr) makes
|
|
10700
|
+
// the detection redundant in the "defence in depth" sense — if the
|
|
10701
|
+
// MCP classification ever regresses, this still fires.
|
|
10702
|
+
id: "cloudflared-login-browser-launch-failed-raw",
|
|
10703
|
+
name: "cloudflared login \u2014 browser-launch fetch error in log",
|
|
10704
|
+
type: "silent-catch",
|
|
10705
|
+
logSource: "cloudflared",
|
|
10706
|
+
pattern: "Failed to fetch resource",
|
|
10707
|
+
thresholdCount: 0,
|
|
10708
|
+
thresholdWindowMinutes: 0,
|
|
10709
|
+
suggestedAction: "cloudflared-login.log shows the browser-launch courtesy failed. The sign-in URL from the last tunnel-login call is still live \u2014 the cloudflared process waits for the OAuth callback regardless of whether it could open the browser itself. Tell the operator to open the sign-in URL manually in the VNC browser on the device."
|
|
10595
10710
|
},
|
|
10596
10711
|
{
|
|
10597
10712
|
// Task 540: cloudflared.log is the one file most likely to carry the
|
|
@@ -10629,6 +10744,65 @@ function defaultRules() {
|
|
|
10629
10744
|
thresholdWindowMinutes: 60,
|
|
10630
10745
|
scope: "session",
|
|
10631
10746
|
suggestedAction: 'Agent emitted a choice-fork question ("Want me to X, or Y?") instead of a one-sided question or the prescribed action. Review Task 543 IDENTITY \xA7 Questions \u2014 the agent asked an opposing-axis question, or degraded a deterministic tool signal into a menu. The log sample shows the offending turn verbatim.'
|
|
10747
|
+
},
|
|
10748
|
+
{
|
|
10749
|
+
// Task 546: fires when the operator clicks a device-bound URL affordance
|
|
10750
|
+
// and the chat UI cannot drive the device browser — either CDP is
|
|
10751
|
+
// unreachable, the navigation timed out, or CDP returned an error. The
|
|
10752
|
+
// log line carries intent, hostname, and navigateResult so the admin
|
|
10753
|
+
// agent can name the affected flow and hostname verbatim on its next
|
|
10754
|
+
// turn. Every occurrence is worth surfacing (thresholdCount: 0) because
|
|
10755
|
+
// this is the exact class of silent failure Task 546 exists to close:
|
|
10756
|
+
// the operator clicked, nothing happened on the device, and if we don't
|
|
10757
|
+
// review the click telemetry the agent has no way to know the flow is
|
|
10758
|
+
// stuck.
|
|
10759
|
+
id: "device-url-click-failed",
|
|
10760
|
+
name: "Device-bound URL click failed to drive the VNC browser",
|
|
10761
|
+
type: "silent-catch",
|
|
10762
|
+
logSource: "server",
|
|
10763
|
+
// Enumerate the NavigateResult union explicitly rather than relying
|
|
10764
|
+
// on a (?!ok) negative lookahead anchored to a specific token order.
|
|
10765
|
+
// If a new member is added to the union in cdp-client.ts, this rule
|
|
10766
|
+
// must be updated in the same commit — the pattern is order-agnostic
|
|
10767
|
+
// (browser= and navigateResult= can appear in either order) and the
|
|
10768
|
+
// enumerated list compile-fails the source if it ever drifts from
|
|
10769
|
+
// the shared type in device-url-schema.ts.
|
|
10770
|
+
pattern: "\\[device-url:click\\][^\\n]*(?:browser=fallback|navigateResult=(?:timeout|cdp-unreachable|error))",
|
|
10771
|
+
thresholdCount: 0,
|
|
10772
|
+
thresholdWindowMinutes: 0,
|
|
10773
|
+
suggestedAction: "A device-bound URL click failed to drive Chromium on the device's VNC display. Identify the `intent` and `hostname` from the log line, then check the VNC surface: read `vnc-boot.log` and confirm Chromium on :99 is responding on CDP port 9222. If CDP is unreachable, the operator needs to restart the VNC stack; if CDP is reachable but navigation errored, the URL itself may be malformed upstream \u2014 grep the stream log for the originating `[device-url:render]` line."
|
|
10774
|
+
},
|
|
10775
|
+
{
|
|
10776
|
+
// Task 547: catches agent prose that forks or narrates during an
|
|
10777
|
+
// orchestrated flow. The tool-surface gate filters the raw tunnel-*
|
|
10778
|
+
// tools, Playwright, and Bash/Write/Edit while cloudflare-setup-run
|
|
10779
|
+
// is mid-flow, so the agent cannot *call* a deviating tool — but the
|
|
10780
|
+
// agent could still narrate ("Want me to kick off the login flow
|
|
10781
|
+
// now?", "Or would you prefer to do it manually?"). This rule catches
|
|
10782
|
+
// that narration surface.
|
|
10783
|
+
//
|
|
10784
|
+
// The task file prescribes a `preceded-by` rule type (scopes the
|
|
10785
|
+
// followup pattern to a window after a `cloudflare-setup-run` tool
|
|
10786
|
+
// result). That type is introduced by Task 544, which has not
|
|
10787
|
+
// shipped at the time of writing. The fallback named in Task 547 is
|
|
10788
|
+
// a pure `repeated-error` with the followup pattern only — accepts
|
|
10789
|
+
// the false-positive surface (other contexts where the agent asks
|
|
10790
|
+
// "Want me to X, or Y?") until 544 lands. Any such false-positive is
|
|
10791
|
+
// also caught by the pre-existing `agent-choice-fork` rule, so the
|
|
10792
|
+
// net observability cost is zero; the name and suggestedAction below
|
|
10793
|
+
// point a reader at Task 547 specifically when this rule fires
|
|
10794
|
+
// during a cloudflare setup. When 544 ships, migrate this to
|
|
10795
|
+
// `preceded-by` with triggerPattern="cloudflare-setup-run" and
|
|
10796
|
+
// followupWindow=5 lines.
|
|
10797
|
+
id: "orchestrator-mid-flow-fork",
|
|
10798
|
+
name: "Agent narration or fork during an orchestrated flow",
|
|
10799
|
+
type: "repeated-error",
|
|
10800
|
+
logSource: "any",
|
|
10801
|
+
pattern: "(Want me to|Should I|Do you want me|Or would you|Either way|Meanwhile)[^\\n]+\\?",
|
|
10802
|
+
thresholdCount: 1,
|
|
10803
|
+
thresholdWindowMinutes: 60,
|
|
10804
|
+
scope: "session",
|
|
10805
|
+
suggestedAction: "Review Task 547 \u2014 during an orchestrated flow (e.g. cloudflare-setup-run), the agent's only permitted output is relaying the tool's result verbatim and passing the operator's input through. Any forking or narrative prose is a deviation even though the tool-surface gate has filtered the deviating tools out of the menu. Check whether the offending turn occurred during a cloudflare setup (grep `[cloudflare:setup-run:` near this line); if so, the IDENTITY.md Tool-Surface Gates section may need a sharper reminder."
|
|
10632
10806
|
}
|
|
10633
10807
|
];
|
|
10634
10808
|
}
|
|
@@ -10637,7 +10811,7 @@ function rulesFilePath(configDir2) {
|
|
|
10637
10811
|
}
|
|
10638
10812
|
function ensureRulesFile(configDir2) {
|
|
10639
10813
|
const path2 = rulesFilePath(configDir2);
|
|
10640
|
-
if (
|
|
10814
|
+
if (existsSync8(path2)) return { created: false, path: path2 };
|
|
10641
10815
|
mkdirSync5(dirname2(path2), { recursive: true });
|
|
10642
10816
|
const body = {
|
|
10643
10817
|
scanIntervalMs: DEFAULT_SCAN_INTERVAL_MS,
|
|
@@ -10648,10 +10822,10 @@ function ensureRulesFile(configDir2) {
|
|
|
10648
10822
|
}
|
|
10649
10823
|
function loadRules(configDir2) {
|
|
10650
10824
|
const path2 = rulesFilePath(configDir2);
|
|
10651
|
-
if (!
|
|
10825
|
+
if (!existsSync8(path2)) {
|
|
10652
10826
|
throw new Error(`rules file missing at ${path2}`);
|
|
10653
10827
|
}
|
|
10654
|
-
const raw2 =
|
|
10828
|
+
const raw2 = readFileSync9(path2, "utf-8");
|
|
10655
10829
|
let parsed;
|
|
10656
10830
|
try {
|
|
10657
10831
|
parsed = JSON.parse(raw2);
|
|
@@ -10816,16 +10990,16 @@ function validateRule(input, label, seenIds) {
|
|
|
10816
10990
|
}
|
|
10817
10991
|
|
|
10818
10992
|
// app/lib/review-detector/sources.ts
|
|
10819
|
-
import { existsSync as
|
|
10820
|
-
import { resolve as resolve8, join as
|
|
10993
|
+
import { existsSync as existsSync9, readdirSync as readdirSync3, statSync as statSync5, writeFileSync as writeFileSync7, renameSync as renameSync2, mkdirSync as mkdirSync6, openSync as openSync2, readSync as readSync2, closeSync as closeSync2, readFileSync as readFileSync10 } from "fs";
|
|
10994
|
+
import { resolve as resolve8, join as join6, basename, dirname as dirname3 } from "path";
|
|
10821
10995
|
function tailStatePath(configDir2) {
|
|
10822
10996
|
return resolve8(configDir2, "review-state.json");
|
|
10823
10997
|
}
|
|
10824
10998
|
function loadTailState(configDir2) {
|
|
10825
10999
|
const path2 = tailStatePath(configDir2);
|
|
10826
|
-
if (!
|
|
11000
|
+
if (!existsSync9(path2)) return {};
|
|
10827
11001
|
try {
|
|
10828
|
-
const raw2 =
|
|
11002
|
+
const raw2 = readFileSync10(path2, "utf-8");
|
|
10829
11003
|
const parsed = JSON.parse(raw2);
|
|
10830
11004
|
if (!parsed || typeof parsed !== "object") return {};
|
|
10831
11005
|
const clean = {};
|
|
@@ -10851,15 +11025,19 @@ function saveTailState(configDir2, state) {
|
|
|
10851
11025
|
function discoverSourceFiles(configDir2, accountLogDir2, logicalSource) {
|
|
10852
11026
|
if (logicalSource === "server") {
|
|
10853
11027
|
const p = resolve8(configDir2, "logs", "server.log");
|
|
10854
|
-
return
|
|
11028
|
+
return existsSync9(p) ? [{ logicalSource: "server", filepath: p }] : [];
|
|
10855
11029
|
}
|
|
10856
11030
|
if (logicalSource === "vnc") {
|
|
10857
11031
|
const p = resolve8(configDir2, "logs", "vnc-boot.log");
|
|
10858
|
-
return
|
|
11032
|
+
return existsSync9(p) ? [{ logicalSource: "vnc", filepath: p }] : [];
|
|
10859
11033
|
}
|
|
10860
11034
|
if (logicalSource === "cloudflared") {
|
|
10861
|
-
const
|
|
10862
|
-
|
|
11035
|
+
const files2 = [];
|
|
11036
|
+
const daemon = resolve8(configDir2, "logs", "cloudflared.log");
|
|
11037
|
+
if (existsSync9(daemon)) files2.push({ logicalSource: "cloudflared", filepath: daemon });
|
|
11038
|
+
const login = resolve8(configDir2, "logs", "cloudflared-login.log");
|
|
11039
|
+
if (existsSync9(login)) files2.push({ logicalSource: "cloudflared", filepath: login });
|
|
11040
|
+
return files2;
|
|
10863
11041
|
}
|
|
10864
11042
|
const prefix = {
|
|
10865
11043
|
system: "claude-agent-stream-",
|
|
@@ -10868,7 +11046,7 @@ function discoverSourceFiles(configDir2, accountLogDir2, logicalSource) {
|
|
|
10868
11046
|
public: "public-agent-stream-",
|
|
10869
11047
|
mcp: "mcp-"
|
|
10870
11048
|
}[logicalSource];
|
|
10871
|
-
if (!
|
|
11049
|
+
if (!existsSync9(accountLogDir2)) return [];
|
|
10872
11050
|
const files = [];
|
|
10873
11051
|
let scanned = 0;
|
|
10874
11052
|
let skippedPrefixMismatch = 0;
|
|
@@ -10878,7 +11056,7 @@ function discoverSourceFiles(configDir2, accountLogDir2, logicalSource) {
|
|
|
10878
11056
|
const matchesPrefix = entry.startsWith(prefix);
|
|
10879
11057
|
const isLog = entry.endsWith(".log");
|
|
10880
11058
|
if (matchesPrefix && isLog) {
|
|
10881
|
-
files.push({ logicalSource, filepath:
|
|
11059
|
+
files.push({ logicalSource, filepath: join6(accountLogDir2, entry) });
|
|
10882
11060
|
} else if (!matchesPrefix) {
|
|
10883
11061
|
skippedPrefixMismatch += 1;
|
|
10884
11062
|
} else {
|
|
@@ -10910,7 +11088,7 @@ function discoverAllSources(configDir2, accountLogDir2) {
|
|
|
10910
11088
|
];
|
|
10911
11089
|
}
|
|
10912
11090
|
function readNewLines(filepath, prev) {
|
|
10913
|
-
if (!
|
|
11091
|
+
if (!existsSync9(filepath)) return null;
|
|
10914
11092
|
const stat4 = statSync5(filepath);
|
|
10915
11093
|
const size = stat4.size;
|
|
10916
11094
|
const inode = stat4.ino;
|
|
@@ -10963,12 +11141,12 @@ function readNewLines(filepath, prev) {
|
|
|
10963
11141
|
}
|
|
10964
11142
|
}
|
|
10965
11143
|
function countRecentWrites(dir, sinceMs) {
|
|
10966
|
-
if (!
|
|
11144
|
+
if (!existsSync9(dir)) return 0;
|
|
10967
11145
|
let count = 0;
|
|
10968
11146
|
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
10969
11147
|
if (!entry.isFile()) continue;
|
|
10970
11148
|
try {
|
|
10971
|
-
const st = statSync5(
|
|
11149
|
+
const st = statSync5(join6(dir, entry.name));
|
|
10972
11150
|
if (st.mtimeMs >= sinceMs) count += 1;
|
|
10973
11151
|
} catch {
|
|
10974
11152
|
}
|
|
@@ -10976,7 +11154,7 @@ function countRecentWrites(dir, sinceMs) {
|
|
|
10976
11154
|
return count;
|
|
10977
11155
|
}
|
|
10978
11156
|
function fileLastWriteMs(path2) {
|
|
10979
|
-
if (!
|
|
11157
|
+
if (!existsSync9(path2)) return null;
|
|
10980
11158
|
try {
|
|
10981
11159
|
return statSync5(path2).mtimeMs;
|
|
10982
11160
|
} catch {
|
|
@@ -10991,7 +11169,7 @@ function sourceKey(file2) {
|
|
|
10991
11169
|
}
|
|
10992
11170
|
|
|
10993
11171
|
// app/lib/review-detector/writer.ts
|
|
10994
|
-
import { appendFileSync as appendFileSync2, existsSync as
|
|
11172
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync8, renameSync as renameSync3, statSync as statSync6 } from "fs";
|
|
10995
11173
|
import { resolve as resolve9, dirname as dirname4 } from "path";
|
|
10996
11174
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
10997
11175
|
function reviewLogPath(configDir2) {
|
|
@@ -11129,8 +11307,8 @@ function queueAlert(configDir2, accountId, match2) {
|
|
|
11129
11307
|
}
|
|
11130
11308
|
async function drainPendingAlerts(configDir2) {
|
|
11131
11309
|
const path2 = pendingAlertsPath(configDir2);
|
|
11132
|
-
if (!
|
|
11133
|
-
const raw2 =
|
|
11310
|
+
if (!existsSync10(path2)) return { drained: 0, remaining: 0 };
|
|
11311
|
+
const raw2 = readFileSync11(path2, "utf-8");
|
|
11134
11312
|
const lines = raw2.split("\n").filter((l) => l.trim().length > 0);
|
|
11135
11313
|
if (lines.length === 0) return { drained: 0, remaining: 0 };
|
|
11136
11314
|
const remaining = [];
|
|
@@ -25560,16 +25738,16 @@ var WhatsAppConfigSchema = external_exports.object({
|
|
|
25560
25738
|
});
|
|
25561
25739
|
|
|
25562
25740
|
// app/lib/whatsapp/config-persist.ts
|
|
25563
|
-
import { readFileSync as
|
|
25564
|
-
import { resolve as resolve11, join as
|
|
25741
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync9, existsSync as existsSync11 } from "fs";
|
|
25742
|
+
import { resolve as resolve11, join as join7 } from "path";
|
|
25565
25743
|
var TAG2 = "[whatsapp:config]";
|
|
25566
25744
|
function configPath(accountDir) {
|
|
25567
25745
|
return resolve11(accountDir, "account.json");
|
|
25568
25746
|
}
|
|
25569
25747
|
function readConfig(accountDir) {
|
|
25570
25748
|
const path2 = configPath(accountDir);
|
|
25571
|
-
if (!
|
|
25572
|
-
return JSON.parse(
|
|
25749
|
+
if (!existsSync11(path2)) throw new Error(`account.json not found at ${path2}`);
|
|
25750
|
+
return JSON.parse(readFileSync12(path2, "utf-8"));
|
|
25573
25751
|
}
|
|
25574
25752
|
function writeConfig(accountDir, config2) {
|
|
25575
25753
|
const path2 = configPath(accountDir);
|
|
@@ -25743,8 +25921,8 @@ function setPublicAgent(accountDir, slug) {
|
|
|
25743
25921
|
if (!trimmed) {
|
|
25744
25922
|
return { ok: false, error: "Agent slug cannot be empty." };
|
|
25745
25923
|
}
|
|
25746
|
-
const agentConfigPath =
|
|
25747
|
-
if (!
|
|
25924
|
+
const agentConfigPath = join7(accountDir, "agents", trimmed, "config.json");
|
|
25925
|
+
if (!existsSync11(agentConfigPath)) {
|
|
25748
25926
|
return { ok: false, error: `Agent "${trimmed}" not found \u2014 no config.json at ${agentConfigPath}. Check the agent slug and try again.` };
|
|
25749
25927
|
}
|
|
25750
25928
|
try {
|
|
@@ -26784,7 +26962,7 @@ async function sendMediaMessage(sock, to, media, opts) {
|
|
|
26784
26962
|
// app/lib/whatsapp/inbound/media.ts
|
|
26785
26963
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
26786
26964
|
import { writeFile, mkdir } from "fs/promises";
|
|
26787
|
-
import { join as
|
|
26965
|
+
import { join as join8 } from "path";
|
|
26788
26966
|
import {
|
|
26789
26967
|
downloadMediaMessage,
|
|
26790
26968
|
downloadContentFromMessage,
|
|
@@ -26870,7 +27048,7 @@ async function downloadInboundMedia(msg, sock, opts) {
|
|
|
26870
27048
|
await mkdir(MEDIA_DIR, { recursive: true });
|
|
26871
27049
|
const ext = mimeToExt(mimetype ?? "application/octet-stream");
|
|
26872
27050
|
const filename = `${randomUUID5()}.${ext}`;
|
|
26873
|
-
const filePath =
|
|
27051
|
+
const filePath = join8(MEDIA_DIR, filename);
|
|
26874
27052
|
await writeFile(filePath, buffer);
|
|
26875
27053
|
const sizeKB = (buffer.length / 1024).toFixed(0);
|
|
26876
27054
|
console.error(`${TAG8} media downloaded type=${mimetype ?? "unknown"} size=${sizeKB}KB path=${filePath}`);
|
|
@@ -27832,8 +28010,8 @@ async function GET(req, remoteAddress) {
|
|
|
27832
28010
|
const browserTransport = resolveBrowserTransport(req, remoteAddress);
|
|
27833
28011
|
let pinConfigured = false;
|
|
27834
28012
|
try {
|
|
27835
|
-
if (
|
|
27836
|
-
const raw2 =
|
|
28013
|
+
if (existsSync12(USERS_FILE)) {
|
|
28014
|
+
const raw2 = readFileSync13(USERS_FILE, "utf-8").trim();
|
|
27837
28015
|
if (raw2) {
|
|
27838
28016
|
const users = JSON.parse(raw2);
|
|
27839
28017
|
pinConfigured = Array.isArray(users) && users.length > 0;
|
|
@@ -27852,7 +28030,7 @@ async function GET(req, remoteAddress) {
|
|
|
27852
28030
|
const vncRunning = await checkPort(6080);
|
|
27853
28031
|
let apiKeyConfigured = false;
|
|
27854
28032
|
try {
|
|
27855
|
-
apiKeyConfigured =
|
|
28033
|
+
apiKeyConfigured = existsSync12(keyFilePath());
|
|
27856
28034
|
} catch {
|
|
27857
28035
|
}
|
|
27858
28036
|
let apiKeyStatus = "missing";
|
|
@@ -27920,7 +28098,7 @@ async function GET(req, remoteAddress) {
|
|
|
27920
28098
|
|
|
27921
28099
|
// app/api/session/route.ts
|
|
27922
28100
|
import { resolve as resolve12 } from "path";
|
|
27923
|
-
import { existsSync as
|
|
28101
|
+
import { existsSync as existsSync13, writeFileSync as writeFileSync10, mkdirSync as mkdirSync8 } from "fs";
|
|
27924
28102
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
27925
28103
|
async function POST(req) {
|
|
27926
28104
|
let body;
|
|
@@ -27971,7 +28149,7 @@ async function POST(req) {
|
|
|
27971
28149
|
if (account) {
|
|
27972
28150
|
const agentDir = resolve12(account.accountDir, "agents", agentSlug);
|
|
27973
28151
|
const agentConfigPath = resolve12(agentDir, "config.json");
|
|
27974
|
-
if (!
|
|
28152
|
+
if (!existsSync13(agentDir) || !existsSync13(agentConfigPath)) {
|
|
27975
28153
|
return Response.json({ error: "Agent not found" }, { status: 404 });
|
|
27976
28154
|
}
|
|
27977
28155
|
agentConfig = resolveAgentConfig(account.accountDir, agentSlug);
|
|
@@ -28346,7 +28524,7 @@ async function storeGeneratedFile(accountId, filePath) {
|
|
|
28346
28524
|
// app/lib/stt/voice-note.ts
|
|
28347
28525
|
import { writeFile as writeFile3, mkdtemp, rm } from "fs/promises";
|
|
28348
28526
|
import { tmpdir } from "os";
|
|
28349
|
-
import { join as
|
|
28527
|
+
import { join as join9 } from "path";
|
|
28350
28528
|
var TAG13 = "[voice]";
|
|
28351
28529
|
var AUDIO_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
28352
28530
|
"audio/ogg",
|
|
@@ -28384,9 +28562,9 @@ async function transcribeVoiceNote(file2, source) {
|
|
|
28384
28562
|
let tempDir;
|
|
28385
28563
|
let tempPath;
|
|
28386
28564
|
try {
|
|
28387
|
-
tempDir = await mkdtemp(
|
|
28565
|
+
tempDir = await mkdtemp(join9(tmpdir(), "voice-"));
|
|
28388
28566
|
const ext = audioExtension(mimeType);
|
|
28389
|
-
tempPath =
|
|
28567
|
+
tempPath = join9(tempDir, `recording${ext}`);
|
|
28390
28568
|
const buffer = Buffer.from(await file2.arrayBuffer());
|
|
28391
28569
|
await writeFile3(tempPath, buffer);
|
|
28392
28570
|
} catch (err) {
|
|
@@ -28937,7 +29115,7 @@ async function POST2(req) {
|
|
|
28937
29115
|
|
|
28938
29116
|
// app/lib/access-gate.ts
|
|
28939
29117
|
import neo4j2 from "neo4j-driver";
|
|
28940
|
-
import { readFileSync as
|
|
29118
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
28941
29119
|
import { resolve as resolve14 } from "path";
|
|
28942
29120
|
import { randomUUID as randomUUID7, randomInt } from "crypto";
|
|
28943
29121
|
var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT ?? resolve14(process.cwd(), "..");
|
|
@@ -28946,7 +29124,7 @@ function readPassword2() {
|
|
|
28946
29124
|
if (process.env.NEO4J_PASSWORD) return process.env.NEO4J_PASSWORD;
|
|
28947
29125
|
const passwordFile = resolve14(PLATFORM_ROOT6, "config/.neo4j-password");
|
|
28948
29126
|
try {
|
|
28949
|
-
return
|
|
29127
|
+
return readFileSync14(passwordFile, "utf-8").trim();
|
|
28950
29128
|
} catch {
|
|
28951
29129
|
throw new Error(
|
|
28952
29130
|
`Neo4j password not found. Expected at ${passwordFile} or in NEO4J_PASSWORD env var.`
|
|
@@ -29544,7 +29722,7 @@ async function POST6(req) {
|
|
|
29544
29722
|
}
|
|
29545
29723
|
|
|
29546
29724
|
// app/lib/brevo-sms.ts
|
|
29547
|
-
import { readFileSync as
|
|
29725
|
+
import { readFileSync as readFileSync15, writeFileSync as writeFileSync11, mkdirSync as mkdirSync9, existsSync as existsSync14, chmodSync } from "fs";
|
|
29548
29726
|
import { dirname as dirname5 } from "path";
|
|
29549
29727
|
import { resolve as resolve15 } from "path";
|
|
29550
29728
|
var BREVO_API_KEY_FILE = resolve15(MAXY_DIR, ".brevo-api-key");
|
|
@@ -29555,8 +29733,8 @@ var platformRoot2 = process.env.MAXY_PLATFORM_ROOT;
|
|
|
29555
29733
|
if (platformRoot2) {
|
|
29556
29734
|
try {
|
|
29557
29735
|
const brandPath3 = resolve15(platformRoot2, "config", "brand.json");
|
|
29558
|
-
if (
|
|
29559
|
-
const brand = JSON.parse(
|
|
29736
|
+
if (existsSync14(brandPath3)) {
|
|
29737
|
+
const brand = JSON.parse(readFileSync15(brandPath3, "utf-8"));
|
|
29560
29738
|
if (brand.productName) BREVO_SENDER = brand.productName;
|
|
29561
29739
|
}
|
|
29562
29740
|
} catch {
|
|
@@ -29564,7 +29742,7 @@ if (platformRoot2) {
|
|
|
29564
29742
|
}
|
|
29565
29743
|
function readBrevoApiKey() {
|
|
29566
29744
|
try {
|
|
29567
|
-
const key =
|
|
29745
|
+
const key = readFileSync15(BREVO_API_KEY_FILE, "utf-8").trim();
|
|
29568
29746
|
if (!key) {
|
|
29569
29747
|
throw new Error(`Brevo API key file is empty: ${BREVO_API_KEY_FILE}`);
|
|
29570
29748
|
}
|
|
@@ -29579,7 +29757,7 @@ function readBrevoApiKey() {
|
|
|
29579
29757
|
}
|
|
29580
29758
|
}
|
|
29581
29759
|
function hasBrevoApiKey() {
|
|
29582
|
-
return
|
|
29760
|
+
return existsSync14(BREVO_API_KEY_FILE);
|
|
29583
29761
|
}
|
|
29584
29762
|
async function sendSms(recipient, content, opts) {
|
|
29585
29763
|
let apiKey;
|
|
@@ -29744,15 +29922,15 @@ function checkTelegramAccess(params) {
|
|
|
29744
29922
|
}
|
|
29745
29923
|
|
|
29746
29924
|
// app/api/telegram/webhook/route.ts
|
|
29747
|
-
import { existsSync as
|
|
29925
|
+
import { existsSync as existsSync15, readFileSync as readFileSync16 } from "fs";
|
|
29748
29926
|
import { timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
29749
29927
|
var TAG15 = "[telegram-webhook]";
|
|
29750
29928
|
var TELEGRAM_API = "https://api.telegram.org";
|
|
29751
29929
|
function getWebhookSecret(botType) {
|
|
29752
29930
|
const filePath = botType === "admin" ? TELEGRAM_ADMIN_WEBHOOK_SECRET_FILE : TELEGRAM_WEBHOOK_SECRET_FILE;
|
|
29753
29931
|
try {
|
|
29754
|
-
if (!
|
|
29755
|
-
const secret =
|
|
29932
|
+
if (!existsSync15(filePath)) return null;
|
|
29933
|
+
const secret = readFileSync16(filePath, "utf-8").trim();
|
|
29756
29934
|
return secret || null;
|
|
29757
29935
|
} catch {
|
|
29758
29936
|
return null;
|
|
@@ -29909,7 +30087,7 @@ async function POST8(req) {
|
|
|
29909
30087
|
}
|
|
29910
30088
|
|
|
29911
30089
|
// app/api/whatsapp/login/start/route.ts
|
|
29912
|
-
import { join as
|
|
30090
|
+
import { join as join10 } from "path";
|
|
29913
30091
|
|
|
29914
30092
|
// app/lib/whatsapp/login.ts
|
|
29915
30093
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
@@ -30139,7 +30317,7 @@ async function POST9(req) {
|
|
|
30139
30317
|
const body = await req.json().catch(() => ({}));
|
|
30140
30318
|
const accountId = validateAccountId(body.accountId);
|
|
30141
30319
|
const force = body.force ?? false;
|
|
30142
|
-
const authDir =
|
|
30320
|
+
const authDir = join10(MAXY_DIR, "credentials", "whatsapp", accountId);
|
|
30143
30321
|
const result = await startLogin({ accountId, authDir, force });
|
|
30144
30322
|
console.error(`[whatsapp:api] login/start result account=${accountId} hasQr=${!!result.qrRaw}${result.selfPhone ? ` phone=${result.selfPhone}` : ""}`);
|
|
30145
30323
|
return Response.json(result);
|
|
@@ -30353,7 +30531,7 @@ function serializeWhatsAppSchema() {
|
|
|
30353
30531
|
|
|
30354
30532
|
// app/api/whatsapp/config/route.ts
|
|
30355
30533
|
import { resolve as resolve16 } from "path";
|
|
30356
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
30534
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync17, existsSync as existsSync16 } from "fs";
|
|
30357
30535
|
async function POST14(req) {
|
|
30358
30536
|
try {
|
|
30359
30537
|
const body = await req.json().catch(() => ({}));
|
|
@@ -30403,15 +30581,15 @@ async function POST14(req) {
|
|
|
30403
30581
|
case "list-public-agents": {
|
|
30404
30582
|
const agentsDir = resolve16(account.accountDir, "agents");
|
|
30405
30583
|
const agents = [];
|
|
30406
|
-
if (
|
|
30584
|
+
if (existsSync16(agentsDir)) {
|
|
30407
30585
|
try {
|
|
30408
30586
|
const entries = readdirSync4(agentsDir, { withFileTypes: true });
|
|
30409
30587
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
30410
30588
|
if (!entry.isDirectory() || entry.name === "admin") continue;
|
|
30411
30589
|
const configPath2 = resolve16(agentsDir, entry.name, "config.json");
|
|
30412
|
-
if (!
|
|
30590
|
+
if (!existsSync16(configPath2)) continue;
|
|
30413
30591
|
try {
|
|
30414
|
-
const config2 = JSON.parse(
|
|
30592
|
+
const config2 = JSON.parse(readFileSync17(configPath2, "utf-8"));
|
|
30415
30593
|
agents.push({ slug: entry.name, displayName: config2.displayName ?? entry.name });
|
|
30416
30594
|
} catch {
|
|
30417
30595
|
console.error(`[whatsapp:api] config action=list-public-agents error="failed to parse config.json for agent ${entry.name}" \u2014 skipping`);
|
|
@@ -30827,15 +31005,15 @@ async function POST17(req, remoteAddress) {
|
|
|
30827
31005
|
}
|
|
30828
31006
|
|
|
30829
31007
|
// app/api/onboarding/set-pin/route.ts
|
|
30830
|
-
import { existsSync as
|
|
31008
|
+
import { existsSync as existsSync17, writeFileSync as writeFileSync13, mkdirSync as mkdirSync10, readFileSync as readFileSync18, unlinkSync as unlinkSync2 } from "fs";
|
|
30831
31009
|
import { createHash, randomUUID as randomUUID9 } from "crypto";
|
|
30832
31010
|
import { dirname as dirname6 } from "path";
|
|
30833
31011
|
function hashPin(pin) {
|
|
30834
31012
|
return createHash("sha256").update(pin).digest("hex");
|
|
30835
31013
|
}
|
|
30836
31014
|
function readUsersFile() {
|
|
30837
|
-
if (!
|
|
30838
|
-
const raw2 =
|
|
31015
|
+
if (!existsSync17(USERS_FILE)) return null;
|
|
31016
|
+
const raw2 = readFileSync18(USERS_FILE, "utf-8").trim();
|
|
30839
31017
|
if (!raw2) return [];
|
|
30840
31018
|
return JSON.parse(raw2);
|
|
30841
31019
|
}
|
|
@@ -30873,7 +31051,7 @@ async function POST18(req) {
|
|
|
30873
31051
|
const account = resolveAccount();
|
|
30874
31052
|
if (account) {
|
|
30875
31053
|
try {
|
|
30876
|
-
const config2 = JSON.parse(
|
|
31054
|
+
const config2 = JSON.parse(readFileSync18(`${account.accountDir}/account.json`, "utf-8"));
|
|
30877
31055
|
if (!config2.admins) config2.admins = [];
|
|
30878
31056
|
if (!config2.admins.some((a) => a.userId === userId)) {
|
|
30879
31057
|
config2.admins.push({ userId, role: "owner" });
|
|
@@ -30924,7 +31102,7 @@ async function DELETE(req) {
|
|
|
30924
31102
|
}
|
|
30925
31103
|
|
|
30926
31104
|
// app/api/onboarding/skip/route.ts
|
|
30927
|
-
import { existsSync as
|
|
31105
|
+
import { existsSync as existsSync18, writeFileSync as writeFileSync14, mkdirSync as mkdirSync11, readFileSync as readFileSync19 } from "fs";
|
|
30928
31106
|
import { resolve as resolve18, dirname as dirname7 } from "path";
|
|
30929
31107
|
var PLATFORM_ROOT8 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
30930
31108
|
async function POST19() {
|
|
@@ -30936,9 +31114,9 @@ async function POST19() {
|
|
|
30936
31114
|
const { accountId, accountDir } = account;
|
|
30937
31115
|
let agentName = "Maxy";
|
|
30938
31116
|
const brandPath3 = PLATFORM_ROOT8 ? resolve18(PLATFORM_ROOT8, "config", "brand.json") : "";
|
|
30939
|
-
if (brandPath3 &&
|
|
31117
|
+
if (brandPath3 && existsSync18(brandPath3)) {
|
|
30940
31118
|
try {
|
|
30941
|
-
const brand = JSON.parse(
|
|
31119
|
+
const brand = JSON.parse(readFileSync19(brandPath3, "utf-8"));
|
|
30942
31120
|
if (brand.productName) agentName = brand.productName;
|
|
30943
31121
|
} catch (err) {
|
|
30944
31122
|
console.error(`[onboarding-skip] brand.json read failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -30984,14 +31162,14 @@ async function POST19() {
|
|
|
30984
31162
|
}
|
|
30985
31163
|
|
|
30986
31164
|
// app/api/admin/session/route.ts
|
|
30987
|
-
import { readFileSync as
|
|
31165
|
+
import { readFileSync as readFileSync20, existsSync as existsSync19 } from "fs";
|
|
30988
31166
|
import { createHash as createHash2 } from "crypto";
|
|
30989
31167
|
function hashPin2(pin) {
|
|
30990
31168
|
return createHash2("sha256").update(pin).digest("hex");
|
|
30991
31169
|
}
|
|
30992
31170
|
function readUsersFile2() {
|
|
30993
|
-
if (!
|
|
30994
|
-
const raw2 =
|
|
31171
|
+
if (!existsSync19(USERS_FILE)) return null;
|
|
31172
|
+
const raw2 = readFileSync20(USERS_FILE, "utf-8").trim();
|
|
30995
31173
|
if (!raw2) return [];
|
|
30996
31174
|
return JSON.parse(raw2);
|
|
30997
31175
|
}
|
|
@@ -31392,7 +31570,7 @@ async function POST22(req) {
|
|
|
31392
31570
|
}
|
|
31393
31571
|
|
|
31394
31572
|
// app/api/admin/logs/route.ts
|
|
31395
|
-
import { existsSync as
|
|
31573
|
+
import { existsSync as existsSync20, readdirSync as readdirSync5, readFileSync as readFileSync21, statSync as statSync7 } from "fs";
|
|
31396
31574
|
import { resolve as resolve19, basename as basename5 } from "path";
|
|
31397
31575
|
var TAIL_BYTES = 8192;
|
|
31398
31576
|
async function GET9(request) {
|
|
@@ -31411,7 +31589,7 @@ async function GET9(request) {
|
|
|
31411
31589
|
const filePath = resolve19(dir, safe);
|
|
31412
31590
|
searched.push(filePath);
|
|
31413
31591
|
try {
|
|
31414
|
-
const content =
|
|
31592
|
+
const content = readFileSync21(filePath, "utf-8");
|
|
31415
31593
|
const headers = { "Content-Type": "text/plain; charset=utf-8" };
|
|
31416
31594
|
if (download) headers["Content-Disposition"] = `attachment; filename="${safe}"`;
|
|
31417
31595
|
return new Response(content, { headers });
|
|
@@ -31453,7 +31631,7 @@ async function GET9(request) {
|
|
|
31453
31631
|
const filePath = resolve19(dir, fileName);
|
|
31454
31632
|
searched.push(filePath);
|
|
31455
31633
|
try {
|
|
31456
|
-
const content =
|
|
31634
|
+
const content = readFileSync21(filePath, "utf-8");
|
|
31457
31635
|
const headers = { "Content-Type": "text/plain; charset=utf-8" };
|
|
31458
31636
|
if (download) headers["Content-Disposition"] = `attachment; filename="${fileName}"`;
|
|
31459
31637
|
return new Response(content, { headers });
|
|
@@ -31468,7 +31646,7 @@ async function GET9(request) {
|
|
|
31468
31646
|
const seen = /* @__PURE__ */ new Set();
|
|
31469
31647
|
const logs = {};
|
|
31470
31648
|
for (const dir of [accountLogDir2, LOG_DIR]) {
|
|
31471
|
-
if (!dir || !
|
|
31649
|
+
if (!dir || !existsSync20(dir)) continue;
|
|
31472
31650
|
let files;
|
|
31473
31651
|
try {
|
|
31474
31652
|
files = readdirSync5(dir).filter((f) => f.endsWith(".log"));
|
|
@@ -31480,7 +31658,7 @@ async function GET9(request) {
|
|
|
31480
31658
|
files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync7(resolve19(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).forEach(({ name }) => {
|
|
31481
31659
|
seen.add(name);
|
|
31482
31660
|
try {
|
|
31483
|
-
const content =
|
|
31661
|
+
const content = readFileSync21(resolve19(dir, name));
|
|
31484
31662
|
const tail = content.length > TAIL_BYTES ? content.subarray(content.length - TAIL_BYTES).toString("utf-8") : content.toString("utf-8");
|
|
31485
31663
|
logs[name] = tail.trim() || "(empty)";
|
|
31486
31664
|
} catch (err) {
|
|
@@ -31517,7 +31695,7 @@ async function GET10() {
|
|
|
31517
31695
|
|
|
31518
31696
|
// app/api/admin/attachment/[attachmentId]/route.ts
|
|
31519
31697
|
import { readFile as readFile3, readdir } from "fs/promises";
|
|
31520
|
-
import { existsSync as
|
|
31698
|
+
import { existsSync as existsSync21 } from "fs";
|
|
31521
31699
|
import { resolve as resolve20 } from "path";
|
|
31522
31700
|
async function GET11(req, attachmentId) {
|
|
31523
31701
|
const sessionKey = new URL(req.url).searchParams.get("session_key") ?? "";
|
|
@@ -31532,11 +31710,11 @@ async function GET11(req, attachmentId) {
|
|
|
31532
31710
|
return new Response("Not found", { status: 404 });
|
|
31533
31711
|
}
|
|
31534
31712
|
const dir = resolve20(ATTACHMENTS_ROOT, accountId, attachmentId);
|
|
31535
|
-
if (!
|
|
31713
|
+
if (!existsSync21(dir)) {
|
|
31536
31714
|
return new Response("Not found", { status: 404 });
|
|
31537
31715
|
}
|
|
31538
31716
|
const metaPath = resolve20(dir, `${attachmentId}.meta.json`);
|
|
31539
|
-
if (!
|
|
31717
|
+
if (!existsSync21(metaPath)) {
|
|
31540
31718
|
return new Response("Not found", { status: 404 });
|
|
31541
31719
|
}
|
|
31542
31720
|
let meta3;
|
|
@@ -31562,7 +31740,7 @@ async function GET11(req, attachmentId) {
|
|
|
31562
31740
|
}
|
|
31563
31741
|
|
|
31564
31742
|
// app/api/admin/account/route.ts
|
|
31565
|
-
import { readFileSync as
|
|
31743
|
+
import { readFileSync as readFileSync22, writeFileSync as writeFileSync15 } from "fs";
|
|
31566
31744
|
import { resolve as resolve21 } from "path";
|
|
31567
31745
|
var VALID_CONTEXT_MODES = ["managed", "claude-code"];
|
|
31568
31746
|
async function PATCH(req) {
|
|
@@ -31588,7 +31766,7 @@ async function PATCH(req) {
|
|
|
31588
31766
|
}
|
|
31589
31767
|
const configPath2 = resolve21(account.accountDir, "account.json");
|
|
31590
31768
|
try {
|
|
31591
|
-
const raw2 =
|
|
31769
|
+
const raw2 = readFileSync22(configPath2, "utf-8");
|
|
31592
31770
|
const config2 = JSON.parse(raw2);
|
|
31593
31771
|
config2.contextMode = contextMode;
|
|
31594
31772
|
writeFileSync15(configPath2, JSON.stringify(config2, null, 2) + "\n", "utf-8");
|
|
@@ -31602,14 +31780,14 @@ async function PATCH(req) {
|
|
|
31602
31780
|
|
|
31603
31781
|
// app/api/admin/agents/route.ts
|
|
31604
31782
|
import { resolve as resolve22 } from "path";
|
|
31605
|
-
import { readdirSync as readdirSync6, readFileSync as
|
|
31783
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync23, existsSync as existsSync22 } from "fs";
|
|
31606
31784
|
async function GET12() {
|
|
31607
31785
|
const account = resolveAccount();
|
|
31608
31786
|
if (!account) {
|
|
31609
31787
|
return Response.json({ agents: [] });
|
|
31610
31788
|
}
|
|
31611
31789
|
const agentsDir = resolve22(account.accountDir, "agents");
|
|
31612
|
-
if (!
|
|
31790
|
+
if (!existsSync22(agentsDir)) {
|
|
31613
31791
|
return Response.json({ agents: [] });
|
|
31614
31792
|
}
|
|
31615
31793
|
const agents = [];
|
|
@@ -31619,9 +31797,9 @@ async function GET12() {
|
|
|
31619
31797
|
if (!entry.isDirectory()) continue;
|
|
31620
31798
|
if (entry.name === "admin") continue;
|
|
31621
31799
|
const configPath2 = resolve22(agentsDir, entry.name, "config.json");
|
|
31622
|
-
if (!
|
|
31800
|
+
if (!existsSync22(configPath2)) continue;
|
|
31623
31801
|
try {
|
|
31624
|
-
const config2 = JSON.parse(
|
|
31802
|
+
const config2 = JSON.parse(readFileSync23(configPath2, "utf-8"));
|
|
31625
31803
|
agents.push({
|
|
31626
31804
|
slug: entry.name,
|
|
31627
31805
|
displayName: config2.displayName ?? entry.name,
|
|
@@ -31640,7 +31818,7 @@ async function GET12() {
|
|
|
31640
31818
|
|
|
31641
31819
|
// app/api/admin/agents/[slug]/route.ts
|
|
31642
31820
|
import { resolve as resolve23 } from "path";
|
|
31643
|
-
import { existsSync as
|
|
31821
|
+
import { existsSync as existsSync23, rmSync as rmSync2 } from "fs";
|
|
31644
31822
|
async function DELETE2(_req, { params }) {
|
|
31645
31823
|
const { slug } = await params;
|
|
31646
31824
|
const account = resolveAccount();
|
|
@@ -31654,7 +31832,7 @@ async function DELETE2(_req, { params }) {
|
|
|
31654
31832
|
return Response.json({ error: "Invalid agent slug" }, { status: 400 });
|
|
31655
31833
|
}
|
|
31656
31834
|
const agentDir = resolve23(account.accountDir, "agents", slug);
|
|
31657
|
-
if (!
|
|
31835
|
+
if (!existsSync23(agentDir)) {
|
|
31658
31836
|
return Response.json({ error: "Agent not found" }, { status: 404 });
|
|
31659
31837
|
}
|
|
31660
31838
|
try {
|
|
@@ -31668,15 +31846,15 @@ async function DELETE2(_req, { params }) {
|
|
|
31668
31846
|
}
|
|
31669
31847
|
|
|
31670
31848
|
// app/api/admin/version/route.ts
|
|
31671
|
-
import { readFileSync as
|
|
31672
|
-
import { resolve as resolve24, join as
|
|
31849
|
+
import { readFileSync as readFileSync24, existsSync as existsSync24 } from "fs";
|
|
31850
|
+
import { resolve as resolve24, join as join11 } from "path";
|
|
31673
31851
|
var PLATFORM_ROOT9 = process.env.MAXY_PLATFORM_ROOT ?? resolve24(process.cwd(), "..");
|
|
31674
31852
|
var brandHostname = "maxy";
|
|
31675
31853
|
var brandNpmPackage = "@rubytech/create-maxy";
|
|
31676
|
-
var brandJsonPath =
|
|
31677
|
-
if (
|
|
31854
|
+
var brandJsonPath = join11(PLATFORM_ROOT9, "config", "brand.json");
|
|
31855
|
+
if (existsSync24(brandJsonPath)) {
|
|
31678
31856
|
try {
|
|
31679
|
-
const brand = JSON.parse(
|
|
31857
|
+
const brand = JSON.parse(readFileSync24(brandJsonPath, "utf-8"));
|
|
31680
31858
|
if (brand.hostname) brandHostname = brand.hostname;
|
|
31681
31859
|
if (brand.npm?.packageName) brandNpmPackage = brand.npm.packageName;
|
|
31682
31860
|
} catch {
|
|
@@ -31687,8 +31865,8 @@ var NPM_PACKAGE = brandNpmPackage;
|
|
|
31687
31865
|
var REGISTRY_URL = `https://registry.npmjs.org/${NPM_PACKAGE}/latest`;
|
|
31688
31866
|
var FETCH_TIMEOUT_MS = 5e3;
|
|
31689
31867
|
function readInstalled() {
|
|
31690
|
-
if (!
|
|
31691
|
-
const content =
|
|
31868
|
+
if (!existsSync24(VERSION_FILE)) return "unknown";
|
|
31869
|
+
const content = readFileSync24(VERSION_FILE, "utf-8").trim();
|
|
31692
31870
|
return content || "unknown";
|
|
31693
31871
|
}
|
|
31694
31872
|
async function fetchLatest() {
|
|
@@ -31739,15 +31917,15 @@ async function GET13() {
|
|
|
31739
31917
|
|
|
31740
31918
|
// app/api/admin/version/upgrade/route.ts
|
|
31741
31919
|
import { spawn as spawn4 } from "child_process";
|
|
31742
|
-
import { existsSync as
|
|
31743
|
-
import { resolve as resolve25, join as
|
|
31920
|
+
import { existsSync as existsSync25, statSync as statSync8, writeFileSync as writeFileSync16, readFileSync as readFileSync25, openSync as openSync4, closeSync as closeSync4 } from "fs";
|
|
31921
|
+
import { resolve as resolve25, join as join12 } from "path";
|
|
31744
31922
|
var PLATFORM_ROOT10 = process.env.MAXY_PLATFORM_ROOT ?? resolve25(process.cwd(), "..");
|
|
31745
31923
|
var upgradePkg = "@rubytech/create-maxy";
|
|
31746
31924
|
var upgradeHostname = "maxy";
|
|
31747
|
-
var brandPath =
|
|
31748
|
-
if (
|
|
31925
|
+
var brandPath = join12(PLATFORM_ROOT10, "config", "brand.json");
|
|
31926
|
+
if (existsSync25(brandPath)) {
|
|
31749
31927
|
try {
|
|
31750
|
-
const brand = JSON.parse(
|
|
31928
|
+
const brand = JSON.parse(readFileSync25(brandPath, "utf-8"));
|
|
31751
31929
|
if (brand.npm?.packageName) upgradePkg = brand.npm.packageName;
|
|
31752
31930
|
if (brand.hostname) upgradeHostname = brand.hostname;
|
|
31753
31931
|
} catch {
|
|
@@ -31757,7 +31935,7 @@ var LOCK_FILE = `/tmp/${upgradeHostname}-upgrade.lock`;
|
|
|
31757
31935
|
var LOG_FILE = `/tmp/${upgradeHostname}-upgrade.log`;
|
|
31758
31936
|
var LOCK_MAX_AGE_MS = 20 * 60 * 1e3;
|
|
31759
31937
|
function isLockFresh() {
|
|
31760
|
-
if (!
|
|
31938
|
+
if (!existsSync25(LOCK_FILE)) return false;
|
|
31761
31939
|
try {
|
|
31762
31940
|
const stat4 = statSync8(LOCK_FILE);
|
|
31763
31941
|
return Date.now() - stat4.mtimeMs < LOCK_MAX_AGE_MS;
|
|
@@ -31817,14 +31995,14 @@ async function POST23(req) {
|
|
|
31817
31995
|
}
|
|
31818
31996
|
|
|
31819
31997
|
// app/api/admin/version/upgrade/progress/route.ts
|
|
31820
|
-
import { existsSync as
|
|
31821
|
-
import { resolve as resolve26, join as
|
|
31998
|
+
import { existsSync as existsSync26, readFileSync as readFileSync26 } from "fs";
|
|
31999
|
+
import { resolve as resolve26, join as join13 } from "path";
|
|
31822
32000
|
var PLATFORM_ROOT11 = process.env.MAXY_PLATFORM_ROOT ?? resolve26(process.cwd(), "..");
|
|
31823
32001
|
var upgradeHostname2 = "maxy";
|
|
31824
|
-
var brandPath2 =
|
|
31825
|
-
if (
|
|
32002
|
+
var brandPath2 = join13(PLATFORM_ROOT11, "config", "brand.json");
|
|
32003
|
+
if (existsSync26(brandPath2)) {
|
|
31826
32004
|
try {
|
|
31827
|
-
const brand = JSON.parse(
|
|
32005
|
+
const brand = JSON.parse(readFileSync26(brandPath2, "utf-8"));
|
|
31828
32006
|
if (brand.hostname) upgradeHostname2 = brand.hostname;
|
|
31829
32007
|
} catch {
|
|
31830
32008
|
}
|
|
@@ -31832,12 +32010,12 @@ if (existsSync25(brandPath2)) {
|
|
|
31832
32010
|
var LOG_FILE2 = `/tmp/${upgradeHostname2}-upgrade.log`;
|
|
31833
32011
|
var STEP_RE = /\[(\d+)\/(\d+)\]\s+(.+)/;
|
|
31834
32012
|
async function GET14() {
|
|
31835
|
-
if (!
|
|
32013
|
+
if (!existsSync26(LOG_FILE2)) {
|
|
31836
32014
|
return Response.json({ step: 0, total: 0, label: "", started: false });
|
|
31837
32015
|
}
|
|
31838
32016
|
let content;
|
|
31839
32017
|
try {
|
|
31840
|
-
content =
|
|
32018
|
+
content = readFileSync26(LOG_FILE2, "utf-8");
|
|
31841
32019
|
} catch {
|
|
31842
32020
|
return Response.json({ step: 0, total: 0, label: "", started: false });
|
|
31843
32021
|
}
|
|
@@ -32133,16 +32311,184 @@ async function POST27(req, remoteAddress) {
|
|
|
32133
32311
|
}
|
|
32134
32312
|
}
|
|
32135
32313
|
|
|
32314
|
+
// app/lib/cdp-client.ts
|
|
32315
|
+
var CDP_HOST = "127.0.0.1";
|
|
32316
|
+
var CDP_PORT = 9222;
|
|
32317
|
+
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
32318
|
+
async function cdpNavigateNewTab(url2, opts = {}) {
|
|
32319
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
32320
|
+
const endpoint = `http://${CDP_HOST}:${CDP_PORT}/json/new?${encodeURIComponent(url2)}`;
|
|
32321
|
+
let res;
|
|
32322
|
+
try {
|
|
32323
|
+
res = await fetch(endpoint, {
|
|
32324
|
+
method: "PUT",
|
|
32325
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
32326
|
+
});
|
|
32327
|
+
} catch (err) {
|
|
32328
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
32329
|
+
if (msg.includes("ECONNREFUSED") || msg.includes("fetch failed") || msg.includes("ENOTFOUND")) {
|
|
32330
|
+
return { result: "cdp-unreachable", detail: msg };
|
|
32331
|
+
}
|
|
32332
|
+
if (err instanceof Error && err.name === "TimeoutError") {
|
|
32333
|
+
return { result: "timeout", detail: msg };
|
|
32334
|
+
}
|
|
32335
|
+
return { result: "error", detail: msg };
|
|
32336
|
+
}
|
|
32337
|
+
if (!res.ok) {
|
|
32338
|
+
const body = await res.text().catch(() => "<unreadable>");
|
|
32339
|
+
return {
|
|
32340
|
+
result: "error",
|
|
32341
|
+
detail: `CDP /json/new returned ${res.status}: ${body.slice(0, 200)}`
|
|
32342
|
+
};
|
|
32343
|
+
}
|
|
32344
|
+
let target;
|
|
32345
|
+
try {
|
|
32346
|
+
target = await res.json();
|
|
32347
|
+
} catch (err) {
|
|
32348
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
32349
|
+
return { result: "error", detail: `CDP /json/new response not JSON: ${msg}` };
|
|
32350
|
+
}
|
|
32351
|
+
return { result: "ok", targetId: target?.id };
|
|
32352
|
+
}
|
|
32353
|
+
|
|
32354
|
+
// app/api/admin/device-browser/navigate/route.ts
|
|
32355
|
+
async function POST28(req, remoteAddress) {
|
|
32356
|
+
const TAG18 = "[device-url:click]";
|
|
32357
|
+
let body;
|
|
32358
|
+
try {
|
|
32359
|
+
body = await req.json();
|
|
32360
|
+
} catch (err) {
|
|
32361
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
32362
|
+
console.error(`${TAG18} reject reason=body-not-json detail=${detail} browser=fallback navigateResult=error`);
|
|
32363
|
+
return Response.json(
|
|
32364
|
+
{ ok: false, navigateResult: "error", browser: "fallback", detail: "Request body was not valid JSON" },
|
|
32365
|
+
{ status: 400 }
|
|
32366
|
+
);
|
|
32367
|
+
}
|
|
32368
|
+
const url2 = typeof body.url === "string" ? body.url : "";
|
|
32369
|
+
const intent = typeof body.intent === "string" ? body.intent : "";
|
|
32370
|
+
const hostname4 = typeof body.hostname === "string" ? body.hostname : "";
|
|
32371
|
+
if (!url2) {
|
|
32372
|
+
console.error(`${TAG18} reject reason=missing-url intent=${JSON.stringify(intent)} browser=fallback navigateResult=error`);
|
|
32373
|
+
return Response.json(
|
|
32374
|
+
{ ok: false, navigateResult: "error", browser: "fallback", detail: "url field is required" },
|
|
32375
|
+
{ status: 400 }
|
|
32376
|
+
);
|
|
32377
|
+
}
|
|
32378
|
+
let parsed;
|
|
32379
|
+
try {
|
|
32380
|
+
parsed = new URL(url2);
|
|
32381
|
+
} catch {
|
|
32382
|
+
console.error(`${TAG18} reject reason=url-malformed intent=${JSON.stringify(intent)} url=${url2} browser=fallback navigateResult=error`);
|
|
32383
|
+
return Response.json(
|
|
32384
|
+
{ ok: false, navigateResult: "error", browser: "fallback", detail: "url is not a valid URL" },
|
|
32385
|
+
{ status: 400 }
|
|
32386
|
+
);
|
|
32387
|
+
}
|
|
32388
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
32389
|
+
console.error(
|
|
32390
|
+
`${TAG18} reject reason=scheme-not-allowed scheme=${parsed.protocol} intent=${JSON.stringify(intent)} browser=fallback navigateResult=error`
|
|
32391
|
+
);
|
|
32392
|
+
return Response.json(
|
|
32393
|
+
{
|
|
32394
|
+
ok: false,
|
|
32395
|
+
navigateResult: "error",
|
|
32396
|
+
browser: "fallback",
|
|
32397
|
+
detail: `URL scheme ${parsed.protocol} is not permitted; only http and https are accepted`
|
|
32398
|
+
},
|
|
32399
|
+
{ status: 400 }
|
|
32400
|
+
);
|
|
32401
|
+
}
|
|
32402
|
+
const transport = resolveBrowserTransport(req, remoteAddress);
|
|
32403
|
+
const cdpOk = await ensureCdp(transport);
|
|
32404
|
+
if (!cdpOk) {
|
|
32405
|
+
console.error(
|
|
32406
|
+
`${TAG18} intent=${JSON.stringify(intent)} browser=fallback navigateResult=cdp-unreachable hostname=${JSON.stringify(hostname4)}`
|
|
32407
|
+
);
|
|
32408
|
+
return Response.json(
|
|
32409
|
+
{
|
|
32410
|
+
ok: false,
|
|
32411
|
+
navigateResult: "cdp-unreachable",
|
|
32412
|
+
browser: "fallback",
|
|
32413
|
+
detail: "Device browser (Chromium/CDP on :9222) did not start"
|
|
32414
|
+
},
|
|
32415
|
+
{ status: 502 }
|
|
32416
|
+
);
|
|
32417
|
+
}
|
|
32418
|
+
const outcome = await cdpNavigateNewTab(parsed.toString());
|
|
32419
|
+
const browser = outcome.result === "ok" ? "vnc" : "fallback";
|
|
32420
|
+
const detailStr = outcome.detail ? ` detail=${JSON.stringify(outcome.detail.length > 230 ? outcome.detail.slice(0, 227) + "..." : outcome.detail)}` : "";
|
|
32421
|
+
console.error(
|
|
32422
|
+
`${TAG18} intent=${JSON.stringify(intent)} browser=${browser} navigateResult=${outcome.result} hostname=${JSON.stringify(hostname4)} targetId=${outcome.targetId ?? "none"}${detailStr}`
|
|
32423
|
+
);
|
|
32424
|
+
if (outcome.result !== "ok") {
|
|
32425
|
+
return Response.json(
|
|
32426
|
+
{
|
|
32427
|
+
ok: false,
|
|
32428
|
+
navigateResult: outcome.result,
|
|
32429
|
+
browser: "fallback",
|
|
32430
|
+
detail: outcome.detail
|
|
32431
|
+
},
|
|
32432
|
+
{ status: 502 }
|
|
32433
|
+
);
|
|
32434
|
+
}
|
|
32435
|
+
return Response.json({
|
|
32436
|
+
ok: true,
|
|
32437
|
+
navigateResult: "ok",
|
|
32438
|
+
browser: "vnc",
|
|
32439
|
+
targetId: outcome.targetId
|
|
32440
|
+
});
|
|
32441
|
+
}
|
|
32442
|
+
|
|
32443
|
+
// app/api/admin/events/route.ts
|
|
32444
|
+
var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
|
|
32445
|
+
"device-url:render",
|
|
32446
|
+
"device-url:fallback-shown",
|
|
32447
|
+
"device-url:vnc-surface-shown",
|
|
32448
|
+
"device-url:malformed"
|
|
32449
|
+
]);
|
|
32450
|
+
async function POST29(req) {
|
|
32451
|
+
const TAG18 = "[admin:events]";
|
|
32452
|
+
let body;
|
|
32453
|
+
try {
|
|
32454
|
+
body = await req.json();
|
|
32455
|
+
} catch (err) {
|
|
32456
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
32457
|
+
console.error(`${TAG18} reject reason=body-not-json detail=${detail}`);
|
|
32458
|
+
return Response.json({ ok: false, detail: "Request body was not valid JSON" }, { status: 400 });
|
|
32459
|
+
}
|
|
32460
|
+
const event = typeof body.event === "string" ? body.event : "";
|
|
32461
|
+
if (!ALLOWED_EVENTS.has(event)) {
|
|
32462
|
+
console.error(`${TAG18} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
|
|
32463
|
+
return Response.json({ ok: false, detail: `Event "${event}" is not allowed` }, { status: 400 });
|
|
32464
|
+
}
|
|
32465
|
+
const rawFields = body.fields && typeof body.fields === "object" ? body.fields : {};
|
|
32466
|
+
const safeFields = {};
|
|
32467
|
+
for (const [key, value] of Object.entries(rawFields)) {
|
|
32468
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_-]{0,39}$/.test(key)) continue;
|
|
32469
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
32470
|
+
safeFields[key] = value;
|
|
32471
|
+
}
|
|
32472
|
+
}
|
|
32473
|
+
const formatted = Object.entries(safeFields).map(([k, v]) => {
|
|
32474
|
+
if (typeof v !== "string") return `${k}=${String(v)}`;
|
|
32475
|
+
const trunc = v.length > 230 ? v.slice(0, 227) + "..." : v;
|
|
32476
|
+
return `${k}=${JSON.stringify(trunc)}`;
|
|
32477
|
+
}).join(" ");
|
|
32478
|
+
console.error(`[${event}] ${formatted}`);
|
|
32479
|
+
return Response.json({ ok: true });
|
|
32480
|
+
}
|
|
32481
|
+
|
|
32136
32482
|
// server/index.ts
|
|
32137
32483
|
var PLATFORM_ROOT12 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
32138
|
-
var BRAND_JSON_PATH = PLATFORM_ROOT12 ?
|
|
32484
|
+
var BRAND_JSON_PATH = PLATFORM_ROOT12 ? join14(PLATFORM_ROOT12, "config", "brand.json") : "";
|
|
32139
32485
|
var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", domain: "getmaxy.com" };
|
|
32140
|
-
if (BRAND_JSON_PATH && !
|
|
32486
|
+
if (BRAND_JSON_PATH && !existsSync27(BRAND_JSON_PATH)) {
|
|
32141
32487
|
console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
|
|
32142
32488
|
}
|
|
32143
|
-
if (BRAND_JSON_PATH &&
|
|
32489
|
+
if (BRAND_JSON_PATH && existsSync27(BRAND_JSON_PATH)) {
|
|
32144
32490
|
try {
|
|
32145
|
-
const parsed = JSON.parse(
|
|
32491
|
+
const parsed = JSON.parse(readFileSync27(BRAND_JSON_PATH, "utf-8"));
|
|
32146
32492
|
BRAND = { ...BRAND, ...parsed };
|
|
32147
32493
|
} catch (err) {
|
|
32148
32494
|
console.error(`[brand] Failed to parse brand.json: ${err.message}`);
|
|
@@ -32161,11 +32507,11 @@ var brandLoginOpts = {
|
|
|
32161
32507
|
bodyFont: BRAND.defaultFonts?.body,
|
|
32162
32508
|
logoContainsName: !!BRAND.logoContainsName
|
|
32163
32509
|
};
|
|
32164
|
-
var ALIAS_DOMAINS_PATH =
|
|
32510
|
+
var ALIAS_DOMAINS_PATH = join14(homedir4(), BRAND.configDir, "alias-domains.json");
|
|
32165
32511
|
function loadAliasDomains() {
|
|
32166
32512
|
try {
|
|
32167
|
-
if (!
|
|
32168
|
-
const parsed = JSON.parse(
|
|
32513
|
+
if (!existsSync27(ALIAS_DOMAINS_PATH)) return null;
|
|
32514
|
+
const parsed = JSON.parse(readFileSync27(ALIAS_DOMAINS_PATH, "utf-8"));
|
|
32169
32515
|
if (!Array.isArray(parsed)) {
|
|
32170
32516
|
console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
|
|
32171
32517
|
return null;
|
|
@@ -32523,6 +32869,11 @@ app.delete(
|
|
|
32523
32869
|
(c) => DELETE2(c.req.raw, { params: Promise.resolve({ slug: c.req.param("slug") }) })
|
|
32524
32870
|
);
|
|
32525
32871
|
app.post("/api/admin/browser/launch", (c) => POST27(c.req.raw, c.env?.incoming?.socket?.remoteAddress));
|
|
32872
|
+
app.post(
|
|
32873
|
+
"/api/admin/device-browser/navigate",
|
|
32874
|
+
(c) => POST28(c.req.raw, c.env?.incoming?.socket?.remoteAddress)
|
|
32875
|
+
);
|
|
32876
|
+
app.post("/api/admin/events", (c) => POST29(c.req.raw));
|
|
32526
32877
|
app.get("/api/admin/sessions", (c) => GET15(c.req.raw));
|
|
32527
32878
|
app.post("/api/admin/sessions/new", (c) => POST24(c.req.raw));
|
|
32528
32879
|
app.delete(
|
|
@@ -32578,14 +32929,14 @@ app.get("/agent-assets/:slug/:filename", (c) => {
|
|
|
32578
32929
|
console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
|
|
32579
32930
|
return c.text("Forbidden", 403);
|
|
32580
32931
|
}
|
|
32581
|
-
if (!
|
|
32932
|
+
if (!existsSync27(filePath)) {
|
|
32582
32933
|
console.error(`[agent-assets] serve slug=${slug} file=${filename} status=404`);
|
|
32583
32934
|
return c.text("Not found", 404);
|
|
32584
32935
|
}
|
|
32585
32936
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
32586
32937
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
32587
32938
|
console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
|
|
32588
|
-
const body =
|
|
32939
|
+
const body = readFileSync27(filePath);
|
|
32589
32940
|
return c.body(body, 200, {
|
|
32590
32941
|
"Content-Type": contentType,
|
|
32591
32942
|
"Cache-Control": "public, max-age=3600"
|
|
@@ -32608,14 +32959,14 @@ app.get("/generated/:filename", (c) => {
|
|
|
32608
32959
|
console.error(`[generated] serve file=${filename} status=403`);
|
|
32609
32960
|
return c.text("Forbidden", 403);
|
|
32610
32961
|
}
|
|
32611
|
-
if (!
|
|
32962
|
+
if (!existsSync27(filePath)) {
|
|
32612
32963
|
console.error(`[generated] serve file=${filename} status=404`);
|
|
32613
32964
|
return c.text("Not found", 404);
|
|
32614
32965
|
}
|
|
32615
32966
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
32616
32967
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
32617
32968
|
console.log(`[generated] serve file=${filename} status=200`);
|
|
32618
|
-
const body =
|
|
32969
|
+
const body = readFileSync27(filePath);
|
|
32619
32970
|
return c.body(body, 200, {
|
|
32620
32971
|
"Content-Type": contentType,
|
|
32621
32972
|
"Cache-Control": "public, max-age=86400"
|
|
@@ -32624,9 +32975,9 @@ app.get("/generated/:filename", (c) => {
|
|
|
32624
32975
|
var htmlCache = /* @__PURE__ */ new Map();
|
|
32625
32976
|
var brandLogoPath = "/brand/maxy-monochrome.png";
|
|
32626
32977
|
var brandIconPath = "/brand/maxy-monochrome.png";
|
|
32627
|
-
if (BRAND_JSON_PATH &&
|
|
32978
|
+
if (BRAND_JSON_PATH && existsSync27(BRAND_JSON_PATH)) {
|
|
32628
32979
|
try {
|
|
32629
|
-
const fullBrand = JSON.parse(
|
|
32980
|
+
const fullBrand = JSON.parse(readFileSync27(BRAND_JSON_PATH, "utf-8"));
|
|
32630
32981
|
if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
|
|
32631
32982
|
brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
|
|
32632
32983
|
} catch {
|
|
@@ -32643,7 +32994,7 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
|
|
|
32643
32994
|
function cachedHtml(file2) {
|
|
32644
32995
|
let html = htmlCache.get(file2);
|
|
32645
32996
|
if (!html) {
|
|
32646
|
-
html =
|
|
32997
|
+
html = readFileSync27(resolve27(process.cwd(), "public", file2), "utf-8");
|
|
32647
32998
|
html = html.replace("<title>Maxy</title>", `<title>${escapeHtml2(BRAND.productName)}</title>`);
|
|
32648
32999
|
html = html.replace('href="/favicon.ico"', `href="${escapeHtml2(brandFaviconPath)}"`);
|
|
32649
33000
|
html = html.replace("</head>", `${brandScript}
|
|
@@ -32654,26 +33005,26 @@ function cachedHtml(file2) {
|
|
|
32654
33005
|
}
|
|
32655
33006
|
var brandedHtmlCache = /* @__PURE__ */ new Map();
|
|
32656
33007
|
function loadBrandingCache(agentSlug) {
|
|
32657
|
-
const configDir2 =
|
|
33008
|
+
const configDir2 = join14(homedir4(), BRAND.configDir);
|
|
32658
33009
|
try {
|
|
32659
|
-
const accountJsonPath =
|
|
32660
|
-
if (!
|
|
32661
|
-
const account = JSON.parse(
|
|
33010
|
+
const accountJsonPath = join14(configDir2, "account.json");
|
|
33011
|
+
if (!existsSync27(accountJsonPath)) return null;
|
|
33012
|
+
const account = JSON.parse(readFileSync27(accountJsonPath, "utf-8"));
|
|
32662
33013
|
const accountId = account.accountId;
|
|
32663
33014
|
if (!accountId) return null;
|
|
32664
|
-
const cachePath =
|
|
32665
|
-
if (!
|
|
32666
|
-
return JSON.parse(
|
|
33015
|
+
const cachePath = join14(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
|
|
33016
|
+
if (!existsSync27(cachePath)) return null;
|
|
33017
|
+
return JSON.parse(readFileSync27(cachePath, "utf-8"));
|
|
32667
33018
|
} catch {
|
|
32668
33019
|
return null;
|
|
32669
33020
|
}
|
|
32670
33021
|
}
|
|
32671
33022
|
function resolveDefaultSlug() {
|
|
32672
33023
|
try {
|
|
32673
|
-
const configDir2 =
|
|
32674
|
-
const accountJsonPath =
|
|
32675
|
-
if (!
|
|
32676
|
-
const account = JSON.parse(
|
|
33024
|
+
const configDir2 = join14(homedir4(), BRAND.configDir);
|
|
33025
|
+
const accountJsonPath = join14(configDir2, "account.json");
|
|
33026
|
+
if (!existsSync27(accountJsonPath)) return null;
|
|
33027
|
+
const account = JSON.parse(readFileSync27(accountJsonPath, "utf-8"));
|
|
32677
33028
|
return account.defaultAgent || null;
|
|
32678
33029
|
} catch {
|
|
32679
33030
|
return null;
|
|
@@ -32746,7 +33097,7 @@ app.use("/vnc-popout.html", logViewerFetch);
|
|
|
32746
33097
|
app.get("/vnc-popout.html", (c) => {
|
|
32747
33098
|
let html = htmlCache.get("vnc-popout.html");
|
|
32748
33099
|
if (!html) {
|
|
32749
|
-
html =
|
|
33100
|
+
html = readFileSync27(resolve27(process.cwd(), "public", "vnc-popout.html"), "utf-8");
|
|
32750
33101
|
const name = escapeHtml2(BRAND.productName);
|
|
32751
33102
|
html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
|
|
32752
33103
|
html = html.replace("</head>", ` ${brandScript}
|
|
@@ -32800,8 +33151,8 @@ console.log(`${BRAND.productName} listening on http://${hostname3}:${port}`);
|
|
|
32800
33151
|
(async () => {
|
|
32801
33152
|
try {
|
|
32802
33153
|
let userId = "";
|
|
32803
|
-
if (
|
|
32804
|
-
const users = JSON.parse(
|
|
33154
|
+
if (existsSync27(USERS_FILE)) {
|
|
33155
|
+
const users = JSON.parse(readFileSync27(USERS_FILE, "utf-8").trim() || "[]");
|
|
32805
33156
|
userId = users[0]?.userId ?? "";
|
|
32806
33157
|
}
|
|
32807
33158
|
await backfillNullUserIdConversations(userId);
|
|
@@ -32827,7 +33178,7 @@ if (bootAccountConfig?.whatsapp) {
|
|
|
32827
33178
|
}
|
|
32828
33179
|
init({
|
|
32829
33180
|
configDir: configDirForWhatsApp,
|
|
32830
|
-
platformRoot: resolve27(process.env.MAXY_PLATFORM_ROOT ??
|
|
33181
|
+
platformRoot: resolve27(process.env.MAXY_PLATFORM_ROOT ?? join14(__dirname, "..")),
|
|
32831
33182
|
accountConfig: bootAccountConfig,
|
|
32832
33183
|
onMessage: async (msg) => {
|
|
32833
33184
|
try {
|