@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.
Files changed (35) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/device-url/dist/index.d.ts +44 -0
  3. package/payload/platform/lib/device-url/dist/index.d.ts.map +1 -0
  4. package/payload/platform/lib/device-url/dist/index.js +68 -0
  5. package/payload/platform/lib/device-url/dist/index.js.map +1 -0
  6. package/payload/platform/lib/device-url/src/index.ts +78 -0
  7. package/payload/platform/lib/device-url/tsconfig.json +8 -0
  8. package/payload/platform/package.json +2 -2
  9. package/payload/platform/plugins/admin/mcp/dist/index.js +12 -5
  10. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  11. package/payload/platform/plugins/cloudflare/PLUGIN.md +28 -2
  12. package/payload/platform/plugins/cloudflare/mcp/dist/index.js +119 -29
  13. package/payload/platform/plugins/cloudflare/mcp/dist/index.js.map +1 -1
  14. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts +30 -17
  15. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts.map +1 -1
  16. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js +78 -34
  17. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js.map +1 -1
  18. package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.d.ts +90 -0
  19. package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.d.ts.map +1 -0
  20. package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.js +550 -0
  21. package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.js.map +1 -0
  22. package/payload/platform/plugins/cloudflare/references/setup-guide.md +5 -6
  23. package/payload/platform/plugins/cloudflare/skills/setup-tunnel/SKILL.md +4 -56
  24. package/payload/platform/plugins/docs/references/cloudflare.md +18 -11
  25. package/payload/platform/templates/agents/admin/IDENTITY.md +8 -0
  26. package/payload/server/public/assets/admin-BxVuKRJZ.js +352 -0
  27. package/payload/server/public/assets/{public-ZM0fHAOE.js → public-Bgm9WQFZ.js} +2 -2
  28. package/payload/server/public/assets/useVoiceRecorder-BORuG_su.css +1 -0
  29. package/payload/server/public/assets/useVoiceRecorder-CiYPZu3g.js +44 -0
  30. package/payload/server/public/index.html +3 -3
  31. package/payload/server/public/public.html +3 -3
  32. package/payload/server/server.js +553 -202
  33. package/payload/server/public/assets/admin-D7LRdkYB.js +0 -352
  34. package/payload/server/public/assets/useVoiceRecorder-CaFVzk8y.css +0 -1
  35. package/payload/server/public/assets/useVoiceRecorder-OB_Gtr0e.js +0 -43
@@ -2896,8 +2896,8 @@ var serveStatic = (options = { root: "" }) => {
2896
2896
  };
2897
2897
 
2898
2898
  // server/index.ts
2899
- import { readFileSync as readFileSync26, existsSync as existsSync26, watchFile } from "fs";
2900
- import { resolve as resolve27, join as join13, basename as basename6 } from "path";
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 existsSync11, readFileSync as readFileSync12 } from "fs";
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 join4 } from "path";
4167
+ import { resolve as resolve6, join as join5 } from "path";
4168
4168
  import { platform as osPlatform } from "os";
4169
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, readdirSync as readdirSync2, existsSync as existsSync6, mkdirSync as mkdirSync4, createWriteStream, statSync as statSync3, unlinkSync, cpSync, rmSync } from "fs";
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 = readFileSync7(tcpFile, "utf-8");
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 = readFileSync7(`/proc/${pid}/statm`, "utf-8").trim().split(/\s+/);
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 (!existsSync6(PLATFORM_ROOT4)) {
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 (!existsSync6(ACCOUNTS_DIR)) return null;
6398
+ if (!existsSync7(ACCOUNTS_DIR)) return null;
6332
6399
  const usersFilePath = resolve6(PLATFORM_ROOT4, "config", "users.json");
6333
6400
  let usersJsonUserId = null;
6334
- if (existsSync6(usersFilePath)) {
6401
+ if (existsSync7(usersFilePath)) {
6335
6402
  try {
6336
- const raw2 = readFileSync7(usersFilePath, "utf-8").trim();
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 (!existsSync6(configPath2)) continue;
6352
- const raw2 = readFileSync7(configPath2, "utf-8");
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 (!existsSync6(filePath)) return null;
6387
- return readFileSync7(filePath, "utf-8");
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 (!existsSync6(configPath2)) {
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(readFileSync7(configPath2, "utf-8"));
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 (!existsSync6(agentConfigPath)) {
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 = existsSync6(knowledgePath);
6496
- const hasSummary = existsSync6(summaryPath);
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 = readFileSync7(summaryPath, "utf-8");
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 = readFileSync7(knowledgePath, "utf-8");
6571
+ knowledge = readFileSync8(knowledgePath, "utf-8");
6505
6572
  }
6506
6573
  knowledgeBaked = true;
6507
6574
  } else if (hasKnowledge) {
6508
- knowledge = readFileSync7(knowledgePath, "utf-8");
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 (!existsSync6(pluginPath)) return null;
6598
+ if (!existsSync7(pluginPath)) return null;
6532
6599
  let raw2;
6533
6600
  try {
6534
- raw2 = readFileSync7(pluginPath, "utf-8");
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 (!existsSync6(stagingRoot)) {
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 (!existsSync6(stagingDir)) {
6668
+ if (!existsSync7(stagingDir)) {
6602
6669
  console.log(`${TAG18} ${pluginName}: not in staging \u2014 skipping`);
6603
6670
  continue;
6604
6671
  }
6605
- const bundlePath = join4(stagingDir, "BUNDLE.md");
6606
- const isBundle = existsSync6(bundlePath);
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 = readFileSync7(bundlePath, "utf-8");
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 (existsSync6(resolve6(target, "PLUGIN.md"))) {
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 (!existsSync6(source)) {
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 (existsSync6(resolve6(target, "PLUGIN.md"))) {
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 = readFileSync7(configPath2, "utf-8");
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 (existsSync6(orphan)) {
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 (!existsSync6(stagingRoot)) return;
6731
- if (!existsSync6(specialistsDir)) {
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 = existsSync6(agentsmdPath) ? readFileSync7(agentsmdPath, "utf-8") : "";
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 (!existsSync6(bundleAgentsDir)) continue;
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 (existsSync6(target)) continue;
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 = readFileSync7(target, "utf-8");
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 = readFileSync7(pluginPath, "utf-8");
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 = readFileSync7(skillMdPath, "utf-8");
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 = readFileSync7(resolve6(refsDir, refFile), "utf-8").trim();
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 = readFileSync7(pluginPath, "utf-8");
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 (!existsSync6(serverPath)) return Promise.resolve([]);
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 (!existsSync6(scanPath)) return;
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 (existsSync6(serverPath)) {
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 (!existsSync6(ACCOUNTS_DIR)) return [];
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 (!existsSync6(configPath2)) continue;
7353
+ if (!existsSync7(configPath2)) continue;
7287
7354
  let config2;
7288
7355
  try {
7289
- config2 = JSON.parse(readFileSync7(configPath2, "utf-8"));
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 (!existsSync6(mcpEntry)) continue;
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 (!existsSync6(serverPath)) {
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 (!existsSync6(serverPath)) return false;
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 (!existsSync6(specialistsDir)) agentLogStream("claude-agent-compaction-stream", accountDir, conversationId).write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
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
- getAdminAllowedTools(enabledPlugins).join(","),
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 (!existsSync6(specialistsDir)) agentLogStream("claude-agent-stream", accountDir, spawnConvId).write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
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
- getAdminAllowedTools(enabledPlugins).join(","),
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 (!existsSync6(specialistsDir)) streamLog.write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
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
- getAdminAllowedTools(enabledPlugins).join(","),
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 = readFileSync7(skillPath, "utf-8");
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 readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync7, statSync as statSync4, mkdirSync as mkdirSync5, renameSync } from "fs";
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 541: tunnel-login's browser-launch failure class — cloudflared
10578
- // reports "Failed to fetch resource" into cloudflared-login.log when
10579
- // its browser launcher cannot reach the default browser, and the login
10580
- // process lingers with a live PID holding the log open. Prior to Task
10581
- // 541, `tunnel-login` inferred liveness from PID alone and kept
10582
- // reporting "Sign-in in progress" against a dead auth flow. The MCP
10583
- // handler now parses the log and emits this line on the first call
10584
- // that detects the failure; the rule surfaces it to the admin agent on
10585
- // the next turn so the 44-tool-call self-discovery loop (session
10586
- // 0e30b69e) cannot reopen.
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 failed (browser launcher fetch error)",
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:failed\\] reason=browser-launch-fetch-error",
10686
+ pattern: "\\[cloudflare:tunnel-login:browser-launch-failed\\]",
10592
10687
  thresholdCount: 0,
10593
10688
  thresholdWindowMinutes: 0,
10594
- suggestedAction: "cloudflared's browser launcher failed to open the sign-in URL on the laptop. The tunnel-login tool has already detected this and restarted the login \u2014 relay the new sign-in URL to the operator and wait for them to confirm authorization in the VNC browser. Do not open the Cloudflare dashboard in any other surface; the only path is the sign-in URL the tool produces."
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 (existsSync7(path2)) return { created: false, path: path2 };
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 (!existsSync7(path2)) {
10825
+ if (!existsSync8(path2)) {
10652
10826
  throw new Error(`rules file missing at ${path2}`);
10653
10827
  }
10654
- const raw2 = readFileSync8(path2, "utf-8");
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 existsSync8, 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 readFileSync9 } from "fs";
10820
- import { resolve as resolve8, join as join5, basename, dirname as dirname3 } from "path";
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 (!existsSync8(path2)) return {};
11000
+ if (!existsSync9(path2)) return {};
10827
11001
  try {
10828
- const raw2 = readFileSync9(path2, "utf-8");
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 existsSync8(p) ? [{ logicalSource: "server", filepath: p }] : [];
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 existsSync8(p) ? [{ logicalSource: "vnc", filepath: p }] : [];
11032
+ return existsSync9(p) ? [{ logicalSource: "vnc", filepath: p }] : [];
10859
11033
  }
10860
11034
  if (logicalSource === "cloudflared") {
10861
- const p = resolve8(configDir2, "logs", "cloudflared.log");
10862
- return existsSync8(p) ? [{ logicalSource: "cloudflared", filepath: p }] : [];
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 (!existsSync8(accountLogDir2)) return [];
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: join5(accountLogDir2, entry) });
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 (!existsSync8(filepath)) return null;
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 (!existsSync8(dir)) return 0;
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(join5(dir, entry.name));
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 (!existsSync8(path2)) return null;
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 existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync8, renameSync as renameSync3, statSync as statSync6 } from "fs";
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 (!existsSync9(path2)) return { drained: 0, remaining: 0 };
11133
- const raw2 = readFileSync10(path2, "utf-8");
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 readFileSync11, writeFileSync as writeFileSync9, existsSync as existsSync10 } from "fs";
25564
- import { resolve as resolve11, join as join6 } from "path";
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 (!existsSync10(path2)) throw new Error(`account.json not found at ${path2}`);
25572
- return JSON.parse(readFileSync11(path2, "utf-8"));
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 = join6(accountDir, "agents", trimmed, "config.json");
25747
- if (!existsSync10(agentConfigPath)) {
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 join7 } from "path";
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 = join7(MEDIA_DIR, filename);
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 (existsSync11(USERS_FILE)) {
27836
- const raw2 = readFileSync12(USERS_FILE, "utf-8").trim();
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 = existsSync11(keyFilePath());
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 existsSync12, writeFileSync as writeFileSync10, mkdirSync as mkdirSync8 } from "fs";
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 (!existsSync12(agentDir) || !existsSync12(agentConfigPath)) {
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 join8 } from "path";
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(join8(tmpdir(), "voice-"));
28565
+ tempDir = await mkdtemp(join9(tmpdir(), "voice-"));
28388
28566
  const ext = audioExtension(mimeType);
28389
- tempPath = join8(tempDir, `recording${ext}`);
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 readFileSync13 } from "fs";
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 readFileSync13(passwordFile, "utf-8").trim();
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 readFileSync14, writeFileSync as writeFileSync11, mkdirSync as mkdirSync9, existsSync as existsSync13, chmodSync } from "fs";
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 (existsSync13(brandPath3)) {
29559
- const brand = JSON.parse(readFileSync14(brandPath3, "utf-8"));
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 = readFileSync14(BREVO_API_KEY_FILE, "utf-8").trim();
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 existsSync13(BREVO_API_KEY_FILE);
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 existsSync14, readFileSync as readFileSync15 } from "fs";
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 (!existsSync14(filePath)) return null;
29755
- const secret = readFileSync15(filePath, "utf-8").trim();
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 join9 } from "path";
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 = join9(MAXY_DIR, "credentials", "whatsapp", accountId);
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 readFileSync16, existsSync as existsSync15 } from "fs";
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 (existsSync15(agentsDir)) {
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 (!existsSync15(configPath2)) continue;
30590
+ if (!existsSync16(configPath2)) continue;
30413
30591
  try {
30414
- const config2 = JSON.parse(readFileSync16(configPath2, "utf-8"));
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 existsSync16, writeFileSync as writeFileSync13, mkdirSync as mkdirSync10, readFileSync as readFileSync17, unlinkSync as unlinkSync2 } from "fs";
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 (!existsSync16(USERS_FILE)) return null;
30838
- const raw2 = readFileSync17(USERS_FILE, "utf-8").trim();
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(readFileSync17(`${account.accountDir}/account.json`, "utf-8"));
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 existsSync17, writeFileSync as writeFileSync14, mkdirSync as mkdirSync11, readFileSync as readFileSync18 } from "fs";
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 && existsSync17(brandPath3)) {
31117
+ if (brandPath3 && existsSync18(brandPath3)) {
30940
31118
  try {
30941
- const brand = JSON.parse(readFileSync18(brandPath3, "utf-8"));
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 readFileSync19, existsSync as existsSync18 } from "fs";
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 (!existsSync18(USERS_FILE)) return null;
30994
- const raw2 = readFileSync19(USERS_FILE, "utf-8").trim();
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 existsSync19, readdirSync as readdirSync5, readFileSync as readFileSync20, statSync as statSync7 } from "fs";
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 = readFileSync20(filePath, "utf-8");
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 = readFileSync20(filePath, "utf-8");
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 || !existsSync19(dir)) continue;
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 = readFileSync20(resolve19(dir, name));
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 existsSync20 } from "fs";
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 (!existsSync20(dir)) {
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 (!existsSync20(metaPath)) {
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 readFileSync21, writeFileSync as writeFileSync15 } from "fs";
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 = readFileSync21(configPath2, "utf-8");
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 readFileSync22, existsSync as existsSync21 } from "fs";
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 (!existsSync21(agentsDir)) {
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 (!existsSync21(configPath2)) continue;
31800
+ if (!existsSync22(configPath2)) continue;
31623
31801
  try {
31624
- const config2 = JSON.parse(readFileSync22(configPath2, "utf-8"));
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 existsSync22, rmSync as rmSync2 } from "fs";
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 (!existsSync22(agentDir)) {
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 readFileSync23, existsSync as existsSync23 } from "fs";
31672
- import { resolve as resolve24, join as join10 } from "path";
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 = join10(PLATFORM_ROOT9, "config", "brand.json");
31677
- if (existsSync23(brandJsonPath)) {
31854
+ var brandJsonPath = join11(PLATFORM_ROOT9, "config", "brand.json");
31855
+ if (existsSync24(brandJsonPath)) {
31678
31856
  try {
31679
- const brand = JSON.parse(readFileSync23(brandJsonPath, "utf-8"));
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 (!existsSync23(VERSION_FILE)) return "unknown";
31691
- const content = readFileSync23(VERSION_FILE, "utf-8").trim();
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 existsSync24, statSync as statSync8, writeFileSync as writeFileSync16, readFileSync as readFileSync24, openSync as openSync4, closeSync as closeSync4 } from "fs";
31743
- import { resolve as resolve25, join as join11 } from "path";
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 = join11(PLATFORM_ROOT10, "config", "brand.json");
31748
- if (existsSync24(brandPath)) {
31925
+ var brandPath = join12(PLATFORM_ROOT10, "config", "brand.json");
31926
+ if (existsSync25(brandPath)) {
31749
31927
  try {
31750
- const brand = JSON.parse(readFileSync24(brandPath, "utf-8"));
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 (!existsSync24(LOCK_FILE)) return false;
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 existsSync25, readFileSync as readFileSync25 } from "fs";
31821
- import { resolve as resolve26, join as join12 } from "path";
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 = join12(PLATFORM_ROOT11, "config", "brand.json");
31825
- if (existsSync25(brandPath2)) {
32002
+ var brandPath2 = join13(PLATFORM_ROOT11, "config", "brand.json");
32003
+ if (existsSync26(brandPath2)) {
31826
32004
  try {
31827
- const brand = JSON.parse(readFileSync25(brandPath2, "utf-8"));
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 (!existsSync25(LOG_FILE2)) {
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 = readFileSync25(LOG_FILE2, "utf-8");
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 ? join13(PLATFORM_ROOT12, "config", "brand.json") : "";
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 && !existsSync26(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 && existsSync26(BRAND_JSON_PATH)) {
32489
+ if (BRAND_JSON_PATH && existsSync27(BRAND_JSON_PATH)) {
32144
32490
  try {
32145
- const parsed = JSON.parse(readFileSync26(BRAND_JSON_PATH, "utf-8"));
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 = join13(homedir4(), BRAND.configDir, "alias-domains.json");
32510
+ var ALIAS_DOMAINS_PATH = join14(homedir4(), BRAND.configDir, "alias-domains.json");
32165
32511
  function loadAliasDomains() {
32166
32512
  try {
32167
- if (!existsSync26(ALIAS_DOMAINS_PATH)) return null;
32168
- const parsed = JSON.parse(readFileSync26(ALIAS_DOMAINS_PATH, "utf-8"));
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 (!existsSync26(filePath)) {
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 = readFileSync26(filePath);
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 (!existsSync26(filePath)) {
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 = readFileSync26(filePath);
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 && existsSync26(BRAND_JSON_PATH)) {
32978
+ if (BRAND_JSON_PATH && existsSync27(BRAND_JSON_PATH)) {
32628
32979
  try {
32629
- const fullBrand = JSON.parse(readFileSync26(BRAND_JSON_PATH, "utf-8"));
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 = readFileSync26(resolve27(process.cwd(), "public", file2), "utf-8");
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 = join13(homedir4(), BRAND.configDir);
33008
+ const configDir2 = join14(homedir4(), BRAND.configDir);
32658
33009
  try {
32659
- const accountJsonPath = join13(configDir2, "account.json");
32660
- if (!existsSync26(accountJsonPath)) return null;
32661
- const account = JSON.parse(readFileSync26(accountJsonPath, "utf-8"));
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 = join13(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
32665
- if (!existsSync26(cachePath)) return null;
32666
- return JSON.parse(readFileSync26(cachePath, "utf-8"));
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 = join13(homedir4(), BRAND.configDir);
32674
- const accountJsonPath = join13(configDir2, "account.json");
32675
- if (!existsSync26(accountJsonPath)) return null;
32676
- const account = JSON.parse(readFileSync26(accountJsonPath, "utf-8"));
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 = readFileSync26(resolve27(process.cwd(), "public", "vnc-popout.html"), "utf-8");
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 (existsSync26(USERS_FILE)) {
32804
- const users = JSON.parse(readFileSync26(USERS_FILE, "utf-8").trim() || "[]");
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 ?? join13(__dirname, "..")),
33181
+ platformRoot: resolve27(process.env.MAXY_PLATFORM_ROOT ?? join14(__dirname, "..")),
32831
33182
  accountConfig: bootAccountConfig,
32832
33183
  onMessage: async (msg) => {
32833
33184
  try {