@rubytech/create-realagent 1.0.627 → 1.0.630

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 (27) hide show
  1. package/dist/index.js +34 -0
  2. package/package.json +1 -1
  3. package/payload/platform/lib/graph-mcp/dist/index.d.ts +26 -0
  4. package/payload/platform/lib/graph-mcp/dist/index.d.ts.map +1 -0
  5. package/payload/platform/lib/graph-mcp/dist/index.js +193 -0
  6. package/payload/platform/lib/graph-mcp/dist/index.js.map +1 -0
  7. package/payload/platform/lib/graph-mcp/src/index.ts +225 -0
  8. package/payload/platform/lib/graph-mcp/tsconfig.json +8 -0
  9. package/payload/platform/package.json +2 -2
  10. package/payload/platform/plugins/cloudflare/scripts/_stream-log.sh +124 -0
  11. package/payload/platform/plugins/cloudflare/scripts/reset-tunnel.sh +45 -3
  12. package/payload/platform/plugins/cloudflare/scripts/setup-tunnel.sh +151 -10
  13. package/payload/platform/plugins/docs/references/memory-guide.md +8 -0
  14. package/payload/platform/plugins/docs/references/plugins-guide.md +2 -0
  15. package/payload/platform/plugins/memory/mcp/scripts/graph/accept.sh +129 -0
  16. package/payload/platform/plugins/memory/mcp/scripts/graph/fixture.cypher +59 -0
  17. package/payload/platform/plugins/memory/references/graph-primitives.md +195 -0
  18. package/payload/server/public/assets/admin-DirN63aF.js +352 -0
  19. package/payload/server/public/assets/public-Cizdj15i.js +5 -0
  20. package/payload/server/public/assets/useVoiceRecorder-DIV9KAk_.css +1 -0
  21. package/payload/server/public/assets/{useVoiceRecorder-CiYPZu3g.js → useVoiceRecorder-tbj4tUsl.js} +1 -1
  22. package/payload/server/public/index.html +3 -3
  23. package/payload/server/public/public.html +3 -3
  24. package/payload/server/server.js +481 -102
  25. package/payload/server/public/assets/admin-BxVuKRJZ.js +0 -352
  26. package/payload/server/public/assets/public-Bgm9WQFZ.js +0 -5
  27. package/payload/server/public/assets/useVoiceRecorder-BORuG_su.css +0 -1
@@ -2530,7 +2530,7 @@ var responseViaResponseObject = async (res, outgoing, options = {}) => {
2530
2530
  });
2531
2531
  if (!chunk) {
2532
2532
  if (i === 1) {
2533
- await new Promise((resolve28) => setTimeout(resolve28));
2533
+ await new Promise((resolve29) => setTimeout(resolve29));
2534
2534
  maxReadCount = 3;
2535
2535
  continue;
2536
2536
  }
@@ -2897,7 +2897,7 @@ var serveStatic = (options = { root: "" }) => {
2897
2897
 
2898
2898
  // server/index.ts
2899
2899
  import { readFileSync as readFileSync26, existsSync as existsSync26, watchFile } from "fs";
2900
- import { resolve as resolve27, join as join13, basename as basename6 } from "path";
2900
+ import { resolve as resolve28, join as join13, basename as basename6 } from "path";
2901
2901
  import { homedir as homedir4 } from "os";
2902
2902
 
2903
2903
  // app/lib/vnc-logger.ts
@@ -2971,10 +2971,10 @@ var SCRYPT_R = 8;
2971
2971
  var SCRYPT_P = 1;
2972
2972
  var SCRYPT_KEYLEN = 64;
2973
2973
  function scryptAsync(password, salt) {
2974
- return new Promise((resolve28, reject) => {
2974
+ return new Promise((resolve29, reject) => {
2975
2975
  scrypt(password, salt, SCRYPT_KEYLEN, { N: SCRYPT_N, r: SCRYPT_R, p: SCRYPT_P }, (err, key) => {
2976
2976
  if (err) reject(err);
2977
- else resolve28(key);
2977
+ else resolve29(key);
2978
2978
  });
2979
2979
  });
2980
2980
  }
@@ -3610,8 +3610,6 @@ function handleUpgrade(req, clientSocket, head, opts) {
3610
3610
  const qsIndex = url2.indexOf("?");
3611
3611
  const pathname = qsIndex === -1 ? url2 : url2.slice(0, qsIndex);
3612
3612
  if (pathname !== WS_PATH) {
3613
- vncLog("ws-upgrade", { decision: "rejected", reason: "wrong-path", path: pathname });
3614
- clientSocket.destroy();
3615
3613
  return;
3616
3614
  }
3617
3615
  const corrId = newCorrId();
@@ -3828,9 +3826,203 @@ Content-Length: 0\r
3828
3826
  socket.destroy();
3829
3827
  }
3830
3828
 
3829
+ // server/graph-proxy.ts
3830
+ import { createConnection as createConnection2 } from "net";
3831
+ var GRAPH_PREFIX = "/graph";
3832
+ var UPSTREAM_TIMEOUT_MS2 = 5e3;
3833
+ var HOP_BY_HOP2 = /* @__PURE__ */ new Set([
3834
+ "connection",
3835
+ "keep-alive",
3836
+ "proxy-authenticate",
3837
+ "proxy-authorization",
3838
+ "te",
3839
+ "trailer",
3840
+ "transfer-encoding",
3841
+ "upgrade"
3842
+ ]);
3843
+ function resolveUpstreamPort() {
3844
+ const uri = process.env.NEO4J_URI ?? "bolt://localhost:7687";
3845
+ const m = uri.match(/:(\d+)$/);
3846
+ const boltPort = m ? parseInt(m[1], 10) : 7687;
3847
+ return boltPort - 213;
3848
+ }
3849
+ var UPSTREAM_HOST = "127.0.0.1";
3850
+ var UPSTREAM_PORT = resolveUpstreamPort();
3851
+ function attachGraphHttpRoutes(app2) {
3852
+ const handler = async (c) => {
3853
+ const raw2 = c.req.raw;
3854
+ const url2 = new URL(raw2.url);
3855
+ const pathAfterPrefix = url2.pathname.slice(GRAPH_PREFIX.length) || "/";
3856
+ const upstreamUrl = `http://${UPSTREAM_HOST}:${UPSTREAM_PORT}${pathAfterPrefix}${url2.search}`;
3857
+ const upstreamHeaders = new Headers(raw2.headers);
3858
+ for (const h of HOP_BY_HOP2) upstreamHeaders.delete(h);
3859
+ upstreamHeaders.set("host", `${UPSTREAM_HOST}:${UPSTREAM_PORT}`);
3860
+ try {
3861
+ const upstream = await fetch(upstreamUrl, {
3862
+ method: raw2.method,
3863
+ headers: upstreamHeaders,
3864
+ body: raw2.body,
3865
+ // `duplex: 'half'` is required when forwarding a streaming body; TS
3866
+ // typings do not yet expose it but the undici runtime supports it.
3867
+ ...raw2.body ? { duplex: "half" } : {},
3868
+ redirect: "manual"
3869
+ });
3870
+ const resHeaders = new Headers(upstream.headers);
3871
+ for (const h of HOP_BY_HOP2) resHeaders.delete(h);
3872
+ return new Response(upstream.body, {
3873
+ status: upstream.status,
3874
+ statusText: upstream.statusText,
3875
+ headers: resHeaders
3876
+ });
3877
+ } catch (err) {
3878
+ const msg = err instanceof Error ? err.message : String(err);
3879
+ console.error(`[graph-proxy] upstream fetch failed for ${upstreamUrl}: ${msg}`);
3880
+ return c.text(`Graph proxy upstream unreachable: ${msg}`, 502);
3881
+ }
3882
+ };
3883
+ app2.all(GRAPH_PREFIX, handler);
3884
+ app2.all(`${GRAPH_PREFIX}/*`, handler);
3885
+ }
3886
+ function attachGraphWsProxy(server, opts) {
3887
+ server.on("upgrade", (req, clientSocket, head) => {
3888
+ try {
3889
+ const url2 = req.url ?? "";
3890
+ const qsIndex = url2.indexOf("?");
3891
+ const pathname = qsIndex === -1 ? url2 : url2.slice(0, qsIndex);
3892
+ const isGraphPath = pathname === GRAPH_PREFIX || pathname.startsWith(`${GRAPH_PREFIX}/`);
3893
+ if (!isGraphPath) {
3894
+ if (pathname !== "/websockify") {
3895
+ clientSocket.destroy();
3896
+ }
3897
+ return;
3898
+ }
3899
+ const hostHeader = (req.headers.host ?? "").split(":")[0];
3900
+ const remote = req.socket.remoteAddress;
3901
+ const xff = headerString2(req.headers["x-forwarded-for"]);
3902
+ const cookie = headerString2(req.headers.cookie);
3903
+ const decision = opts.canAccessAdmin({
3904
+ host: hostHeader,
3905
+ remoteAddress: remote,
3906
+ xForwardedFor: xff,
3907
+ cookieHeader: cookie,
3908
+ isPublicHost: opts.isPublicHost
3909
+ });
3910
+ if (!decision.allow) {
3911
+ const status = decision.reason === "public-host" ? 404 : 401;
3912
+ writeStatusAndDestroy2(clientSocket, status, decision.reason === "public-host" ? "Not Found" : "Unauthorized");
3913
+ return;
3914
+ }
3915
+ const originHeader = headerString2(req.headers.origin);
3916
+ const originHost = parseOriginHost2(originHeader);
3917
+ if (!originHost || originHost !== hostHeader || opts.isPublicHost(originHost)) {
3918
+ writeStatusAndDestroy2(clientSocket, 403, "Forbidden");
3919
+ return;
3920
+ }
3921
+ const rewrittenPath = pathname === GRAPH_PREFIX ? "/" : pathname.slice(GRAPH_PREFIX.length);
3922
+ const rewrittenUrl = qsIndex === -1 ? rewrittenPath : rewrittenPath + url2.slice(qsIndex);
3923
+ const upstream = createConnection2({ host: UPSTREAM_HOST, port: UPSTREAM_PORT });
3924
+ upstream.setTimeout(UPSTREAM_TIMEOUT_MS2);
3925
+ upstream.once("connect", () => {
3926
+ upstream.setTimeout(0);
3927
+ const lines = [];
3928
+ lines.push(`${req.method ?? "GET"} ${rewrittenUrl} HTTP/${req.httpVersion}`);
3929
+ lines.push(`host: ${UPSTREAM_HOST}:${UPSTREAM_PORT}`);
3930
+ for (const [name, value] of Object.entries(req.headers)) {
3931
+ if (name === "host") continue;
3932
+ if (HOP_BY_HOP2.has(name)) continue;
3933
+ if (value == null) continue;
3934
+ if (Array.isArray(value)) {
3935
+ for (const v of value) lines.push(`${name}: ${v}`);
3936
+ } else {
3937
+ lines.push(`${name}: ${value}`);
3938
+ }
3939
+ }
3940
+ const upgradeHeader = headerString2(req.headers.upgrade);
3941
+ const connectionHeader = headerString2(req.headers.connection);
3942
+ if (upgradeHeader) lines.push(`upgrade: ${upgradeHeader}`);
3943
+ if (connectionHeader) lines.push(`connection: ${connectionHeader}`);
3944
+ upstream.write(lines.join("\r\n") + "\r\n\r\n");
3945
+ if (head && head.length > 0) upstream.write(head);
3946
+ clientSocket.pipe(upstream);
3947
+ upstream.pipe(clientSocket);
3948
+ const teardown = () => {
3949
+ clientSocket.destroy();
3950
+ upstream.destroy();
3951
+ };
3952
+ clientSocket.once("close", teardown);
3953
+ upstream.once("close", teardown);
3954
+ clientSocket.once("error", teardown);
3955
+ upstream.once("error", teardown);
3956
+ });
3957
+ upstream.once("timeout", () => {
3958
+ writeStatusAndDestroy2(clientSocket, 504, "Gateway Timeout");
3959
+ upstream.destroy();
3960
+ });
3961
+ upstream.once("error", (err) => {
3962
+ console.error(`[graph-proxy] ws upstream error: ${err.message}`);
3963
+ writeStatusAndDestroy2(clientSocket, 502, "Bad Gateway");
3964
+ upstream.destroy();
3965
+ });
3966
+ } catch (err) {
3967
+ console.error(`[graph-proxy] upgrade handler exception: ${err.message}`);
3968
+ clientSocket.destroy();
3969
+ }
3970
+ });
3971
+ }
3972
+ function graphAuthMiddleware(opts) {
3973
+ return async (c, next) => {
3974
+ const url2 = new URL(c.req.raw.url);
3975
+ if (!url2.pathname.startsWith(GRAPH_PREFIX)) return next();
3976
+ const hostHeader = (c.req.header("host") ?? "").split(":")[0];
3977
+ const incoming = c.env?.incoming;
3978
+ const remote = incoming?.socket?.remoteAddress;
3979
+ const xff = c.req.header("x-forwarded-for");
3980
+ const cookie = c.req.header("cookie");
3981
+ const decision = opts.canAccessAdmin({
3982
+ host: hostHeader,
3983
+ remoteAddress: remote,
3984
+ xForwardedFor: xff,
3985
+ cookieHeader: cookie,
3986
+ isPublicHost: opts.isPublicHost
3987
+ });
3988
+ if (!decision.allow) {
3989
+ return c.text(
3990
+ decision.reason === "public-host" ? "Not Found" : "Unauthorized",
3991
+ decision.reason === "public-host" ? 404 : 401
3992
+ );
3993
+ }
3994
+ return next();
3995
+ };
3996
+ }
3997
+ function headerString2(value) {
3998
+ if (value == null) return void 0;
3999
+ return Array.isArray(value) ? value[0] : value;
4000
+ }
4001
+ function parseOriginHost2(origin) {
4002
+ if (!origin) return null;
4003
+ try {
4004
+ return new URL(origin).hostname;
4005
+ } catch {
4006
+ return null;
4007
+ }
4008
+ }
4009
+ function writeStatusAndDestroy2(socket, status, statusText) {
4010
+ try {
4011
+ socket.write(
4012
+ `HTTP/1.1 ${status} ${statusText}\r
4013
+ Connection: close\r
4014
+ Content-Length: 0\r
4015
+ \r
4016
+ `
4017
+ );
4018
+ } catch {
4019
+ }
4020
+ socket.destroy();
4021
+ }
4022
+
3831
4023
  // app/api/health/route.ts
3832
4024
  import { existsSync as existsSync11, readFileSync as readFileSync12 } from "fs";
3833
- import { createConnection as createConnection3 } from "net";
4025
+ import { createConnection as createConnection4 } from "net";
3834
4026
 
3835
4027
  // app/lib/claude-auth.ts
3836
4028
  import { readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
@@ -4187,7 +4379,7 @@ function contextWindow(model) {
4187
4379
 
4188
4380
  // app/lib/vnc.ts
4189
4381
  import { spawnSync, execFileSync } from "child_process";
4190
- import { createConnection as createConnection2 } from "net";
4382
+ import { createConnection as createConnection3 } from "net";
4191
4383
  import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
4192
4384
  import { resolve as resolve4 } from "path";
4193
4385
  var PLATFORM_ROOT2 = process.env.MAXY_PLATFORM_ROOT ?? resolve4(process.cwd(), "..");
@@ -4282,7 +4474,7 @@ async function waitForPort(port2, timeoutMs = 12e3) {
4282
4474
  const deadline = Date.now() + timeoutMs;
4283
4475
  while (Date.now() < deadline) {
4284
4476
  const ready = await new Promise((res) => {
4285
- const socket = createConnection2(port2, "127.0.0.1");
4477
+ const socket = createConnection3(port2, "127.0.0.1");
4286
4478
  socket.setTimeout(500);
4287
4479
  socket.once("connect", () => {
4288
4480
  socket.destroy();
@@ -5146,7 +5338,7 @@ ${userContent}`;
5146
5338
  "dontAsk",
5147
5339
  prompt
5148
5340
  ];
5149
- return new Promise((resolve28) => {
5341
+ return new Promise((resolve29) => {
5150
5342
  let stdout = "";
5151
5343
  let stderr = "";
5152
5344
  const spawnFn = _spawnOverride ?? spawn;
@@ -5164,35 +5356,35 @@ ${userContent}`;
5164
5356
  const timer = setTimeout(() => {
5165
5357
  proc.kill("SIGTERM");
5166
5358
  console.error("[persist] autoLabel: haiku subprocess timed out");
5167
- resolve28(null);
5359
+ resolve29(null);
5168
5360
  }, SESSION_LABEL_TIMEOUT_MS);
5169
5361
  proc.on("error", (err) => {
5170
5362
  clearTimeout(timer);
5171
5363
  console.error(`[persist] autoLabel: subprocess error \u2014 ${err.message}`);
5172
- resolve28(null);
5364
+ resolve29(null);
5173
5365
  });
5174
5366
  proc.on("close", (code) => {
5175
5367
  clearTimeout(timer);
5176
5368
  if (code !== 0) {
5177
5369
  console.error(`[persist] autoLabel: subprocess exited code=${code}${stderr ? ` stderr=${stderr.trim().slice(0, 200)}` : ""}`);
5178
- resolve28(null);
5370
+ resolve29(null);
5179
5371
  return;
5180
5372
  }
5181
5373
  const text = stdout.trim();
5182
5374
  if (!text) {
5183
5375
  console.error("[persist] autoLabel: haiku returned empty response");
5184
- resolve28(null);
5376
+ resolve29(null);
5185
5377
  return;
5186
5378
  }
5187
5379
  if (text === "SKIP") {
5188
5380
  console.error("[persist] autoLabel: haiku returned SKIP \u2014 messages too vague");
5189
- resolve28(null);
5381
+ resolve29(null);
5190
5382
  return;
5191
5383
  }
5192
5384
  const words = text.split(/\s+/).slice(0, SESSION_LABEL_MAX_WORDS);
5193
5385
  const label = words.join(" ");
5194
5386
  console.error(`[persist] autoLabel: haiku response="${label}"`);
5195
- resolve28(label);
5387
+ resolve29(label);
5196
5388
  });
5197
5389
  });
5198
5390
  }
@@ -7512,12 +7704,42 @@ function consumeStalledSubagents(sessionKey) {
7512
7704
  delete session.stalledSubagents;
7513
7705
  return stalls && stalls.length > 0 ? stalls : void 0;
7514
7706
  }
7707
+ function streamLogPathFor(accountId, conversationId) {
7708
+ const logDir = resolve6(ACCOUNTS_DIR, accountId, "logs");
7709
+ const streamLogPath = resolve6(logDir, `claude-agent-stream-${conversationId}.log`);
7710
+ return { logDir, streamLogPath };
7711
+ }
7712
+ function buildSpawnEnv(accountId, accountDir, conversationId) {
7713
+ if (!conversationId) {
7714
+ throw new Error(`buildSpawnEnv: conversationId is required (accountId=${accountId.slice(0, 8)})`);
7715
+ }
7716
+ const { logDir, streamLogPath } = streamLogPathFor(accountId, conversationId);
7717
+ return {
7718
+ ...process.env,
7719
+ PLATFORM_ROOT: PLATFORM_ROOT4,
7720
+ ACCOUNT_DIR: accountDir,
7721
+ ACCOUNT_ID: accountId,
7722
+ LOG_DIR: logDir,
7723
+ STREAM_LOG_PATH: streamLogPath
7724
+ };
7725
+ }
7726
+ var cachedBrandHostname = null;
7727
+ function readBrandHostname() {
7728
+ if (cachedBrandHostname !== null) return cachedBrandHostname;
7729
+ try {
7730
+ const brandPath3 = resolve6(PLATFORM_ROOT4, "config", "brand.json");
7731
+ const parsed = JSON.parse(readFileSync7(brandPath3, "utf-8"));
7732
+ cachedBrandHostname = typeof parsed.hostname === "string" && parsed.hostname.length > 0 ? parsed.hostname : "maxy";
7733
+ } catch {
7734
+ cachedBrandHostname = "maxy";
7735
+ }
7736
+ return cachedBrandHostname;
7737
+ }
7515
7738
  function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
7516
7739
  if (!conversationId) {
7517
7740
  throw new Error(`getMcpServers: conversationId is required (accountId=${accountId.slice(0, 8)})`);
7518
7741
  }
7519
- const LOG_DIR2 = resolve6(ACCOUNTS_DIR, accountId, "logs");
7520
- const STREAM_LOG_PATH = resolve6(LOG_DIR2, `claude-agent-stream-${conversationId}.log`);
7742
+ const { logDir: LOG_DIR2, streamLogPath: STREAM_LOG_PATH } = streamLogPathFor(accountId, conversationId);
7521
7743
  const baseEnv = { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT4, LOG_DIR: LOG_DIR2, STREAM_LOG_PATH };
7522
7744
  const servers = {
7523
7745
  "memory": {
@@ -7564,6 +7786,26 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
7564
7786
  "plugin_playwright_playwright": {
7565
7787
  command: "npx",
7566
7788
  args: ["-y", "@playwright/mcp@latest", "--cdp-endpoint", "http://127.0.0.1:9222", "--caps", "pdf"]
7789
+ },
7790
+ // Graph MCP shim (Task 557) — spawns upstream `uvx mcp-neo4j-cypher` locked
7791
+ // into read-only mode and namespaced `maxy-graph`, wraps stderr with our
7792
+ // tee, and emits one `[graph-query]` line per tool call. Credentials flow
7793
+ // from the brand's own NEO4J_URI + config/.neo4j-password so a per-brand
7794
+ // admin session only ever sees its own Neo4j instance (isolation is
7795
+ // enforced upstream by the installer's process/port boundary per
7796
+ // MAXY-PRD.md:627, not in any application-layer filter).
7797
+ "graph": {
7798
+ command: "node",
7799
+ args: [resolve6(PLATFORM_ROOT4, "lib/graph-mcp/dist/index.js")],
7800
+ env: {
7801
+ ...baseEnv,
7802
+ BRAND: readBrandHostname(),
7803
+ NEO4J_URI: process.env.NEO4J_URI ?? "bolt://localhost:7687",
7804
+ NEO4J_USERNAME: process.env.NEO4J_USERNAME ?? process.env.NEO4J_USER ?? "neo4j",
7805
+ NEO4J_NAMESPACE: "maxy-graph",
7806
+ NEO4J_READ_ONLY: "true",
7807
+ NEO4J_RESPONSE_TOKEN_LIMIT: "20000"
7808
+ }
7567
7809
  }
7568
7810
  };
7569
7811
  const tgConfig = resolveAccount()?.config?.telegram;
@@ -7627,6 +7869,12 @@ var ADMIN_CORE_TOOLS = [
7627
7869
  "Glob",
7628
7870
  "Grep",
7629
7871
  "Agent",
7872
+ // Task 557 — upstream mcp-neo4j-cypher (namespaced maxy-graph, read-only).
7873
+ // maxy-graph_write_neo4j_cypher is intentionally absent: writes go through
7874
+ // the schema-aware memory-write tool, which validates labels, properties,
7875
+ // and embeddings. Admin-only by virtue of not being in the public allow list.
7876
+ "mcp__graph__maxy-graph_read_neo4j_cypher",
7877
+ "mcp__graph__maxy-graph_get_neo4j_schema",
7630
7878
  "mcp__memory__memory-search",
7631
7879
  "mcp__memory__memory-rank",
7632
7880
  "mcp__memory__memory-write",
@@ -7846,7 +8094,7 @@ async function fetchMemoryContext(accountId, query, sessionKey, options) {
7846
8094
  return null;
7847
8095
  }
7848
8096
  const startMs = Date.now();
7849
- return new Promise((resolve28) => {
8097
+ return new Promise((resolve29) => {
7850
8098
  const proc = spawn2(process.execPath, [serverPath], {
7851
8099
  env: {
7852
8100
  ...process.env,
@@ -7875,7 +8123,7 @@ async function fetchMemoryContext(accountId, query, sessionKey, options) {
7875
8123
  } else {
7876
8124
  console.error(`[fetchMemoryContext] failed: ${reason} (${elapsed}ms)${stderrBuf ? ` stderr: ${stderrBuf.slice(0, 500)}` : ""}`);
7877
8125
  }
7878
- resolve28(value);
8126
+ resolve29(value);
7879
8127
  };
7880
8128
  proc.stdout.on("data", (chunk) => {
7881
8129
  buffer += chunk.toString();
@@ -8286,15 +8534,10 @@ async function* runCompactionTurn(accountDir, accountId, systemPrompt, resumeSes
8286
8534
  const proc = spawn2("claude", args, {
8287
8535
  cwd: accountDir,
8288
8536
  stdio: ["ignore", "pipe", "pipe"],
8289
- env: {
8290
- ...process.env,
8291
- PLATFORM_ROOT: PLATFORM_ROOT4,
8292
- ACCOUNT_DIR: accountDir
8293
- // Task 535: NODE_DEBUG removed. The Claude Code CLI is a bundled Bun
8294
- // binary and Bun ignores Node's NODE_DEBUG flag, so setting it here was
8295
- // a no-op that misled future readers. The [subproc-debug-unavailable]
8296
- // line below records the source-of-silence explicitly.
8297
- }
8537
+ // Task 556: STREAM_LOG_PATH is inherited by every Bash-tool subprocess
8538
+ // the Claude CLI spawns; opt-in shell scripts tee their cloudflared/etc.
8539
+ // output into the same per-conversation stream log the agent writes to.
8540
+ env: buildSpawnEnv(accountId, accountDir, conversationId)
8298
8541
  });
8299
8542
  const stderrLog = agentLogStream("claude-agent-compaction-stderr", accountDir, conversationId);
8300
8543
  stderrLog.on("error", () => {
@@ -8305,6 +8548,8 @@ async function* runCompactionTurn(accountDir, accountId, systemPrompt, resumeSes
8305
8548
  });
8306
8549
  teeProcStderrToStreamLog(proc, streamLog);
8307
8550
  streamLog.write(`[${isoTs()}] [subproc-debug-unavailable] reason=bundled-bun-binary-ignores-node-debug pid=${proc.pid} cli=claude
8551
+ `);
8552
+ streamLog.write(`[${isoTs()}] [spawn-env] STREAM_LOG_PATH=set pid=${proc.pid} conversationId=${conversationId} site=compaction
8308
8553
  `);
8309
8554
  streamLog.write(`[${isoTs()}] [compaction-start] resumeSessionId=${resumeSessionId}
8310
8555
  `);
@@ -9176,15 +9421,8 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
9176
9421
  const proc = spawn2("claude", args, {
9177
9422
  cwd: accountDir,
9178
9423
  stdio: ["ignore", "pipe", "pipe"],
9179
- env: {
9180
- ...process.env,
9181
- PLATFORM_ROOT: PLATFORM_ROOT4,
9182
- ACCOUNT_DIR: accountDir
9183
- // Task 535: NODE_DEBUG removed. The Claude Code CLI is a bundled Bun
9184
- // binary and Bun ignores Node's NODE_DEBUG flag, so setting it here was
9185
- // a no-op that misled future readers. The [subproc-debug-unavailable]
9186
- // line below records the source-of-silence explicitly.
9187
- }
9424
+ // Task 556: STREAM_LOG_PATH inherited by Bash-tool subprocesses.
9425
+ env: buildSpawnEnv(accountId, accountDir, spawnConvId)
9188
9426
  });
9189
9427
  const stderrLog = agentLogStream("claude-agent-stderr", accountDir, spawnConvId);
9190
9428
  stderrLog.on("error", () => {
@@ -9195,6 +9433,8 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
9195
9433
  });
9196
9434
  teeProcStderrToStreamLog(proc, streamLog);
9197
9435
  streamLog.write(`[${isoTs()}] [subproc-debug-unavailable] reason=bundled-bun-binary-ignores-node-debug pid=${proc.pid} cli=claude
9436
+ `);
9437
+ streamLog.write(`[${isoTs()}] [spawn-env] STREAM_LOG_PATH=set pid=${proc.pid} conversationId=${spawnConvId} site=admin
9198
9438
  `);
9199
9439
  if (sessionKey) {
9200
9440
  const prev = activeProcesses.get(sessionKey);
@@ -9520,15 +9760,8 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
9520
9760
  const proc = spawn2("claude", args, {
9521
9761
  cwd: accountDir,
9522
9762
  stdio: ["ignore", "pipe", "pipe"],
9523
- env: {
9524
- ...process.env,
9525
- PLATFORM_ROOT: PLATFORM_ROOT4,
9526
- ACCOUNT_DIR: accountDir
9527
- // Task 535: NODE_DEBUG removed. The Claude Code CLI is a bundled Bun
9528
- // binary and Bun ignores Node's NODE_DEBUG flag, so setting it here was
9529
- // a no-op that misled future readers. The [subproc-debug-unavailable]
9530
- // line below records the source-of-silence explicitly.
9531
- }
9763
+ // Task 556: STREAM_LOG_PATH inherited by Bash-tool subprocesses.
9764
+ env: buildSpawnEnv(accountId, accountDir, managedConvId)
9532
9765
  });
9533
9766
  const stderrLog = agentLogStream("claude-agent-stderr", accountDir, managedConvId);
9534
9767
  stderrLog.on("error", () => {
@@ -9536,6 +9769,8 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
9536
9769
  proc.stderr?.pipe(stderrLog);
9537
9770
  teeProcStderrToStreamLog(proc, streamLog);
9538
9771
  streamLog.write(`[${isoTs()}] [subproc-debug-unavailable] reason=bundled-bun-binary-ignores-node-debug pid=${proc.pid} cli=claude
9772
+ `);
9773
+ streamLog.write(`[${isoTs()}] [spawn-env] STREAM_LOG_PATH=set pid=${proc.pid} conversationId=${managedConvId} site=managed
9539
9774
  `);
9540
9775
  if (sessionKey) {
9541
9776
  const prev = activeProcesses.get(sessionKey);
@@ -10212,6 +10447,15 @@ ${body}`;
10212
10447
 
10213
10448
  ${manifest}`;
10214
10449
  }
10450
+ const graphRefPath = resolve6(PLATFORM_ROOT4, "plugins/memory/references/graph-primitives.md");
10451
+ try {
10452
+ const graphRef = readFileSync7(graphRefPath, "utf-8");
10453
+ baseSystemPrompt += `
10454
+
10455
+ ${graphRef}`;
10456
+ } catch (err) {
10457
+ console.error(`[graph-primitives] reference missing at ${graphRefPath} \u2014 admin session will have no Cypher cookbook: ${err instanceof Error ? err.message : String(err)}`);
10458
+ }
10215
10459
  }
10216
10460
  if (agentConfig?.budget) {
10217
10461
  const pluginTokens = embeddedPlugins.reduce((sum, p) => sum + estimateTokens(p.body), 0);
@@ -26125,7 +26369,7 @@ var credsSaveQueue = Promise.resolve();
26125
26369
  async function drainCredsSaveQueue(timeoutMs = 5e3) {
26126
26370
  console.error(`${TAG4} draining credential save queue\u2026`);
26127
26371
  const timer = new Promise(
26128
- (resolve28) => setTimeout(() => resolve28("timeout"), timeoutMs)
26372
+ (resolve29) => setTimeout(() => resolve29("timeout"), timeoutMs)
26129
26373
  );
26130
26374
  const result = await Promise.race([
26131
26375
  credsSaveQueue.then(() => "drained"),
@@ -26253,11 +26497,11 @@ async function createWaSocket(opts) {
26253
26497
  return sock;
26254
26498
  }
26255
26499
  async function waitForConnection(sock) {
26256
- return new Promise((resolve28, reject) => {
26500
+ return new Promise((resolve29, reject) => {
26257
26501
  const handler = (update) => {
26258
26502
  if (update.connection === "open") {
26259
26503
  sock.ev.off("connection.update", handler);
26260
- resolve28();
26504
+ resolve29();
26261
26505
  }
26262
26506
  if (update.connection === "close") {
26263
26507
  sock.ev.off("connection.update", handler);
@@ -26371,14 +26615,14 @@ ${inspected}`;
26371
26615
  return inspect2(err, INSPECT_OPTS2);
26372
26616
  }
26373
26617
  function withTimeout(label, promise2, timeoutMs) {
26374
- return new Promise((resolve28, reject) => {
26618
+ return new Promise((resolve29, reject) => {
26375
26619
  const timer = setTimeout(() => {
26376
26620
  reject(new Error(`${label} timed out after ${timeoutMs}ms`));
26377
26621
  }, timeoutMs);
26378
26622
  promise2.then(
26379
26623
  (value) => {
26380
26624
  clearTimeout(timer);
26381
- resolve28(value);
26625
+ resolve29(value);
26382
26626
  },
26383
26627
  (err) => {
26384
26628
  clearTimeout(timer);
@@ -27578,11 +27822,11 @@ async function connectWithReconnect(conn) {
27578
27822
  console.error(
27579
27823
  `${TAG12} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
27580
27824
  );
27581
- await new Promise((resolve28) => {
27582
- const timer = setTimeout(resolve28, delay);
27825
+ await new Promise((resolve29) => {
27826
+ const timer = setTimeout(resolve29, delay);
27583
27827
  conn.abortController.signal.addEventListener("abort", () => {
27584
27828
  clearTimeout(timer);
27585
- resolve28();
27829
+ resolve29();
27586
27830
  }, { once: true });
27587
27831
  });
27588
27832
  }
@@ -27590,16 +27834,16 @@ async function connectWithReconnect(conn) {
27590
27834
  }
27591
27835
  }
27592
27836
  function waitForDisconnectEvent(conn) {
27593
- return new Promise((resolve28) => {
27837
+ return new Promise((resolve29) => {
27594
27838
  if (!conn.sock) {
27595
- resolve28();
27839
+ resolve29();
27596
27840
  return;
27597
27841
  }
27598
27842
  const sock = conn.sock;
27599
27843
  const handler = (update) => {
27600
27844
  if (update.connection === "close") {
27601
27845
  sock.ev.off("connection.update", handler);
27602
- resolve28();
27846
+ resolve29();
27603
27847
  }
27604
27848
  };
27605
27849
  sock.ev.on("connection.update", handler);
@@ -27809,8 +28053,8 @@ async function handleInboundMessage(conn, msg) {
27809
28053
  const conversationKey = isGroup ? remoteJid : senderPhone;
27810
28054
  const debounceKey = `${conn.accountId}:${conversationKey}:${senderPhone}`;
27811
28055
  let resolvePending;
27812
- const sttPending = new Promise((resolve28) => {
27813
- resolvePending = resolve28;
28056
+ const sttPending = new Promise((resolve29) => {
28057
+ resolvePending = resolve29;
27814
28058
  });
27815
28059
  if (conn.debouncer) conn.debouncer.registerPending(debounceKey, sttPending);
27816
28060
  try {
@@ -27917,20 +28161,20 @@ async function probeApiKey() {
27917
28161
  return result.status;
27918
28162
  }
27919
28163
  function checkPort(port2, timeoutMs = 500) {
27920
- return new Promise((resolve28) => {
27921
- const socket = createConnection3(port2, "127.0.0.1");
28164
+ return new Promise((resolve29) => {
28165
+ const socket = createConnection4(port2, "127.0.0.1");
27922
28166
  socket.setTimeout(timeoutMs);
27923
28167
  socket.once("connect", () => {
27924
28168
  socket.destroy();
27925
- resolve28(true);
28169
+ resolve29(true);
27926
28170
  });
27927
28171
  socket.once("error", () => {
27928
28172
  socket.destroy();
27929
- resolve28(false);
28173
+ resolve29(false);
27930
28174
  });
27931
28175
  socket.once("timeout", () => {
27932
28176
  socket.destroy();
27933
- resolve28(false);
28177
+ resolve29(false);
27934
28178
  });
27935
28179
  });
27936
28180
  }
@@ -30121,8 +30365,8 @@ async function startLogin(opts) {
30121
30365
  resetActiveLogin(accountId);
30122
30366
  let resolveQr = null;
30123
30367
  let rejectQr = null;
30124
- const qrPromise = new Promise((resolve28, reject) => {
30125
- resolveQr = resolve28;
30368
+ const qrPromise = new Promise((resolve29, reject) => {
30369
+ resolveQr = resolve29;
30126
30370
  rejectQr = reject;
30127
30371
  });
30128
30372
  const qrTimer = setTimeout(
@@ -31199,6 +31443,106 @@ async function createAdminSession(accountId, thinkingView, userId, userName) {
31199
31443
  });
31200
31444
  }
31201
31445
 
31446
+ // app/api/admin/chat/route.ts
31447
+ import { resolve as resolve19 } from "path";
31448
+
31449
+ // app/lib/script-stream-tailer.ts
31450
+ import { createReadStream as createReadStream2, statSync as statSync7 } from "fs";
31451
+ import { StringDecoder as StringDecoder2 } from "string_decoder";
31452
+ var SCRIPT_STREAM_RE = /^\[([^\]]+)\] \[(setup-tunnel|reset-tunnel)((?::[^\]]+)?)\] (.*)$/;
31453
+ function parseLine(line) {
31454
+ const m = line.match(SCRIPT_STREAM_RE);
31455
+ if (!m) return void 0;
31456
+ const [, timestamp, scope, tagSuffix, rest] = m;
31457
+ return {
31458
+ type: "script_stream",
31459
+ source: scope + (tagSuffix ?? ""),
31460
+ timestamp,
31461
+ line: rest
31462
+ };
31463
+ }
31464
+ function startScriptStreamTailer(opts) {
31465
+ const { path: path2, onEvent, onError } = opts;
31466
+ let offset;
31467
+ try {
31468
+ offset = statSync7(path2).size;
31469
+ } catch {
31470
+ offset = 0;
31471
+ }
31472
+ const utf8 = new StringDecoder2("utf8");
31473
+ let buffer = "";
31474
+ let stopped = false;
31475
+ let pendingRead = false;
31476
+ let timer;
31477
+ const processLine = (line) => {
31478
+ const event = parseLine(line);
31479
+ if (event) onEvent(event);
31480
+ };
31481
+ const readDelta = async () => {
31482
+ if (pendingRead) return;
31483
+ pendingRead = true;
31484
+ try {
31485
+ let size;
31486
+ try {
31487
+ size = statSync7(path2).size;
31488
+ } catch {
31489
+ return;
31490
+ }
31491
+ if (size === offset) return;
31492
+ if (size < offset) {
31493
+ offset = 0;
31494
+ buffer = "";
31495
+ }
31496
+ await new Promise((res, rej) => {
31497
+ const stream = createReadStream2(path2, { start: offset, end: size - 1 });
31498
+ stream.on("data", (chunk) => {
31499
+ buffer += typeof chunk === "string" ? chunk : utf8.write(chunk);
31500
+ let idx;
31501
+ while ((idx = buffer.indexOf("\n")) !== -1) {
31502
+ const line = buffer.slice(0, idx);
31503
+ buffer = buffer.slice(idx + 1);
31504
+ if (line.length > 0) processLine(line);
31505
+ }
31506
+ });
31507
+ stream.on("end", () => {
31508
+ offset = size;
31509
+ res();
31510
+ });
31511
+ stream.on("error", rej);
31512
+ });
31513
+ } catch (err) {
31514
+ if (onError) onError(err instanceof Error ? err : new Error(String(err)));
31515
+ } finally {
31516
+ pendingRead = false;
31517
+ }
31518
+ };
31519
+ const tick = () => {
31520
+ if (stopped) return;
31521
+ readDelta().catch(() => {
31522
+ }).finally(() => {
31523
+ if (!stopped) timer = setTimeout(tick, 200);
31524
+ });
31525
+ };
31526
+ timer = setTimeout(tick, 0);
31527
+ return {
31528
+ async stop() {
31529
+ if (stopped) return;
31530
+ stopped = true;
31531
+ if (timer) clearTimeout(timer);
31532
+ while (pendingRead) {
31533
+ await new Promise((r) => setImmediate(r));
31534
+ }
31535
+ await readDelta();
31536
+ buffer += utf8.end();
31537
+ if (buffer.length > 0) {
31538
+ const event = parseLine(buffer);
31539
+ if (event) onEvent(event);
31540
+ buffer = "";
31541
+ }
31542
+ }
31543
+ };
31544
+ }
31545
+
31202
31546
  // app/api/admin/chat/route.ts
31203
31547
  function isComponentDone(parsed) {
31204
31548
  return typeof parsed === "object" && parsed !== null && parsed._componentDone === true && typeof parsed.component === "string" && typeof parsed.payload === "string";
@@ -31390,8 +31734,32 @@ async function POST21(req) {
31390
31734
  const sseConvId = getConversationIdForSession(session_key);
31391
31735
  const sseLog = sseConvId ? agentLogStream("sse-events", account.accountDir, sseConvId) : preConversationLogStream("sse-events", account.accountDir);
31392
31736
  const sk = sseConvId?.slice(0, 8) ?? session_key.slice(0, 8);
31737
+ let tailer = null;
31393
31738
  const readable = new ReadableStream({
31394
31739
  async start(controller) {
31740
+ let controllerOpen = true;
31741
+ if (sseConvId) {
31742
+ const streamLogPath = resolve19(account.accountDir, "logs", `claude-agent-stream-${sseConvId}.log`);
31743
+ tailer = startScriptStreamTailer({
31744
+ path: streamLogPath,
31745
+ onEvent: (event) => {
31746
+ if (!controllerOpen) return;
31747
+ const ts = (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
31748
+ sseLog.write(`[${ts}] [${sk}] admin: ${JSON.stringify(event)}
31749
+ `);
31750
+ try {
31751
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}
31752
+
31753
+ `));
31754
+ } catch {
31755
+ controllerOpen = false;
31756
+ }
31757
+ },
31758
+ onError: (err) => {
31759
+ console.error(`[script-stream-tailer] ${streamLogPath}: ${err.message}`);
31760
+ }
31761
+ });
31762
+ }
31395
31763
  try {
31396
31764
  for await (const event of invokeAgent(
31397
31765
  { type: "admin", skipTopicCheck },
@@ -31439,6 +31807,13 @@ async function POST21(req) {
31439
31807
  }
31440
31808
  }
31441
31809
  } finally {
31810
+ if (tailer) {
31811
+ try {
31812
+ await tailer.stop();
31813
+ } catch {
31814
+ }
31815
+ }
31816
+ controllerOpen = false;
31442
31817
  sseLog.end();
31443
31818
  try {
31444
31819
  controller.close();
@@ -31498,8 +31873,8 @@ async function POST22(req) {
31498
31873
  }
31499
31874
 
31500
31875
  // app/api/admin/logs/route.ts
31501
- import { existsSync as existsSync19, readdirSync as readdirSync5, readFileSync as readFileSync20, statSync as statSync7 } from "fs";
31502
- import { resolve as resolve19, basename as basename5 } from "path";
31876
+ import { existsSync as existsSync19, readdirSync as readdirSync5, readFileSync as readFileSync20, statSync as statSync8 } from "fs";
31877
+ import { resolve as resolve20, basename as basename5 } from "path";
31503
31878
  var TAIL_BYTES = 8192;
31504
31879
  async function GET9(request) {
31505
31880
  const { searchParams } = new URL(request.url);
@@ -31508,13 +31883,13 @@ async function GET9(request) {
31508
31883
  const conversationIdParam = searchParams.get("conversationId");
31509
31884
  const download = searchParams.get("download") === "1";
31510
31885
  const account = resolveAccount();
31511
- const accountLogDir2 = account ? resolve19(account.accountDir, "logs") : null;
31886
+ const accountLogDir2 = account ? resolve20(account.accountDir, "logs") : null;
31512
31887
  if (fileParam) {
31513
31888
  const safe = basename5(fileParam);
31514
31889
  const searched = [];
31515
31890
  for (const dir of [accountLogDir2, LOG_DIR]) {
31516
31891
  if (!dir) continue;
31517
- const filePath = resolve19(dir, safe);
31892
+ const filePath = resolve20(dir, safe);
31518
31893
  searched.push(filePath);
31519
31894
  try {
31520
31895
  const content = readFileSync20(filePath, "utf-8");
@@ -31556,7 +31931,7 @@ async function GET9(request) {
31556
31931
  const searched = [];
31557
31932
  for (const dir of [accountLogDir2, LOG_DIR]) {
31558
31933
  if (!dir) continue;
31559
- const filePath = resolve19(dir, fileName);
31934
+ const filePath = resolve20(dir, fileName);
31560
31935
  searched.push(filePath);
31561
31936
  try {
31562
31937
  const content = readFileSync20(filePath, "utf-8");
@@ -31583,10 +31958,10 @@ async function GET9(request) {
31583
31958
  console.warn(`[admin/logs] readdir-fail dir=${dir} reason=${reason}`);
31584
31959
  continue;
31585
31960
  }
31586
- 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 }) => {
31961
+ files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync8(resolve20(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).forEach(({ name }) => {
31587
31962
  seen.add(name);
31588
31963
  try {
31589
- const content = readFileSync20(resolve19(dir, name));
31964
+ const content = readFileSync20(resolve20(dir, name));
31590
31965
  const tail = content.length > TAIL_BYTES ? content.subarray(content.length - TAIL_BYTES).toString("utf-8") : content.toString("utf-8");
31591
31966
  logs[name] = tail.trim() || "(empty)";
31592
31967
  } catch (err) {
@@ -31624,7 +31999,7 @@ async function GET10() {
31624
31999
  // app/api/admin/attachment/[attachmentId]/route.ts
31625
32000
  import { readFile as readFile3, readdir } from "fs/promises";
31626
32001
  import { existsSync as existsSync20 } from "fs";
31627
- import { resolve as resolve20 } from "path";
32002
+ import { resolve as resolve21 } from "path";
31628
32003
  async function GET11(req, attachmentId) {
31629
32004
  const sessionKey = new URL(req.url).searchParams.get("session_key") ?? "";
31630
32005
  if (!validateSession(sessionKey, "admin")) {
@@ -31637,11 +32012,11 @@ async function GET11(req, attachmentId) {
31637
32012
  if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(attachmentId)) {
31638
32013
  return new Response("Not found", { status: 404 });
31639
32014
  }
31640
- const dir = resolve20(ATTACHMENTS_ROOT, accountId, attachmentId);
32015
+ const dir = resolve21(ATTACHMENTS_ROOT, accountId, attachmentId);
31641
32016
  if (!existsSync20(dir)) {
31642
32017
  return new Response("Not found", { status: 404 });
31643
32018
  }
31644
- const metaPath = resolve20(dir, `${attachmentId}.meta.json`);
32019
+ const metaPath = resolve21(dir, `${attachmentId}.meta.json`);
31645
32020
  if (!existsSync20(metaPath)) {
31646
32021
  return new Response("Not found", { status: 404 });
31647
32022
  }
@@ -31656,7 +32031,7 @@ async function GET11(req, attachmentId) {
31656
32031
  if (!dataFile) {
31657
32032
  return new Response("Not found", { status: 404 });
31658
32033
  }
31659
- const filePath = resolve20(dir, dataFile);
32034
+ const filePath = resolve21(dir, dataFile);
31660
32035
  const buffer = await readFile3(filePath);
31661
32036
  return new Response(buffer, {
31662
32037
  headers: {
@@ -31669,7 +32044,7 @@ async function GET11(req, attachmentId) {
31669
32044
 
31670
32045
  // app/api/admin/account/route.ts
31671
32046
  import { readFileSync as readFileSync21, writeFileSync as writeFileSync15 } from "fs";
31672
- import { resolve as resolve21 } from "path";
32047
+ import { resolve as resolve22 } from "path";
31673
32048
  var VALID_CONTEXT_MODES = ["managed", "claude-code"];
31674
32049
  async function PATCH(req) {
31675
32050
  let body;
@@ -31692,7 +32067,7 @@ async function PATCH(req) {
31692
32067
  if (!account) {
31693
32068
  return Response.json({ error: "No account configured" }, { status: 500 });
31694
32069
  }
31695
- const configPath2 = resolve21(account.accountDir, "account.json");
32070
+ const configPath2 = resolve22(account.accountDir, "account.json");
31696
32071
  try {
31697
32072
  const raw2 = readFileSync21(configPath2, "utf-8");
31698
32073
  const config2 = JSON.parse(raw2);
@@ -31707,14 +32082,14 @@ async function PATCH(req) {
31707
32082
  }
31708
32083
 
31709
32084
  // app/api/admin/agents/route.ts
31710
- import { resolve as resolve22 } from "path";
32085
+ import { resolve as resolve23 } from "path";
31711
32086
  import { readdirSync as readdirSync6, readFileSync as readFileSync22, existsSync as existsSync21 } from "fs";
31712
32087
  async function GET12() {
31713
32088
  const account = resolveAccount();
31714
32089
  if (!account) {
31715
32090
  return Response.json({ agents: [] });
31716
32091
  }
31717
- const agentsDir = resolve22(account.accountDir, "agents");
32092
+ const agentsDir = resolve23(account.accountDir, "agents");
31718
32093
  if (!existsSync21(agentsDir)) {
31719
32094
  return Response.json({ agents: [] });
31720
32095
  }
@@ -31724,7 +32099,7 @@ async function GET12() {
31724
32099
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
31725
32100
  if (!entry.isDirectory()) continue;
31726
32101
  if (entry.name === "admin") continue;
31727
- const configPath2 = resolve22(agentsDir, entry.name, "config.json");
32102
+ const configPath2 = resolve23(agentsDir, entry.name, "config.json");
31728
32103
  if (!existsSync21(configPath2)) continue;
31729
32104
  try {
31730
32105
  const config2 = JSON.parse(readFileSync22(configPath2, "utf-8"));
@@ -31745,7 +32120,7 @@ async function GET12() {
31745
32120
  }
31746
32121
 
31747
32122
  // app/api/admin/agents/[slug]/route.ts
31748
- import { resolve as resolve23 } from "path";
32123
+ import { resolve as resolve24 } from "path";
31749
32124
  import { existsSync as existsSync22, rmSync as rmSync2 } from "fs";
31750
32125
  async function DELETE2(_req, { params }) {
31751
32126
  const { slug } = await params;
@@ -31759,7 +32134,7 @@ async function DELETE2(_req, { params }) {
31759
32134
  if (slug.includes("/") || slug.includes("..") || slug.includes("\\")) {
31760
32135
  return Response.json({ error: "Invalid agent slug" }, { status: 400 });
31761
32136
  }
31762
- const agentDir = resolve23(account.accountDir, "agents", slug);
32137
+ const agentDir = resolve24(account.accountDir, "agents", slug);
31763
32138
  if (!existsSync22(agentDir)) {
31764
32139
  return Response.json({ error: "Agent not found" }, { status: 404 });
31765
32140
  }
@@ -31775,8 +32150,8 @@ async function DELETE2(_req, { params }) {
31775
32150
 
31776
32151
  // app/api/admin/version/route.ts
31777
32152
  import { readFileSync as readFileSync23, existsSync as existsSync23 } from "fs";
31778
- import { resolve as resolve24, join as join10 } from "path";
31779
- var PLATFORM_ROOT9 = process.env.MAXY_PLATFORM_ROOT ?? resolve24(process.cwd(), "..");
32153
+ import { resolve as resolve25, join as join10 } from "path";
32154
+ var PLATFORM_ROOT9 = process.env.MAXY_PLATFORM_ROOT ?? resolve25(process.cwd(), "..");
31780
32155
  var brandHostname = "maxy";
31781
32156
  var brandNpmPackage = "@rubytech/create-maxy";
31782
32157
  var brandJsonPath = join10(PLATFORM_ROOT9, "config", "brand.json");
@@ -31788,7 +32163,7 @@ if (existsSync23(brandJsonPath)) {
31788
32163
  } catch {
31789
32164
  }
31790
32165
  }
31791
- var VERSION_FILE = resolve24(PLATFORM_ROOT9, `config/.${brandHostname}-version`);
32166
+ var VERSION_FILE = resolve25(PLATFORM_ROOT9, `config/.${brandHostname}-version`);
31792
32167
  var NPM_PACKAGE = brandNpmPackage;
31793
32168
  var REGISTRY_URL = `https://registry.npmjs.org/${NPM_PACKAGE}/latest`;
31794
32169
  var FETCH_TIMEOUT_MS = 5e3;
@@ -31845,9 +32220,9 @@ async function GET13() {
31845
32220
 
31846
32221
  // app/api/admin/version/upgrade/route.ts
31847
32222
  import { spawn as spawn4 } from "child_process";
31848
- import { existsSync as existsSync24, statSync as statSync8, writeFileSync as writeFileSync16, readFileSync as readFileSync24, openSync as openSync4, closeSync as closeSync4 } from "fs";
31849
- import { resolve as resolve25, join as join11 } from "path";
31850
- var PLATFORM_ROOT10 = process.env.MAXY_PLATFORM_ROOT ?? resolve25(process.cwd(), "..");
32223
+ import { existsSync as existsSync24, statSync as statSync9, writeFileSync as writeFileSync16, readFileSync as readFileSync24, openSync as openSync4, closeSync as closeSync4 } from "fs";
32224
+ import { resolve as resolve26, join as join11 } from "path";
32225
+ var PLATFORM_ROOT10 = process.env.MAXY_PLATFORM_ROOT ?? resolve26(process.cwd(), "..");
31851
32226
  var upgradePkg = "@rubytech/create-maxy";
31852
32227
  var upgradeHostname = "maxy";
31853
32228
  var brandPath = join11(PLATFORM_ROOT10, "config", "brand.json");
@@ -31865,7 +32240,7 @@ var LOCK_MAX_AGE_MS = 20 * 60 * 1e3;
31865
32240
  function isLockFresh() {
31866
32241
  if (!existsSync24(LOCK_FILE)) return false;
31867
32242
  try {
31868
- const stat4 = statSync8(LOCK_FILE);
32243
+ const stat4 = statSync9(LOCK_FILE);
31869
32244
  return Date.now() - stat4.mtimeMs < LOCK_MAX_AGE_MS;
31870
32245
  } catch {
31871
32246
  return false;
@@ -31907,7 +32282,7 @@ async function POST23(req) {
31907
32282
  detached: true,
31908
32283
  stdio: ["ignore", logFd, logFd],
31909
32284
  env: { ...process.env, npm_config_yes: "true" },
31910
- cwd: resolve25(process.cwd(), "..")
32285
+ cwd: resolve26(process.cwd(), "..")
31911
32286
  });
31912
32287
  child.unref();
31913
32288
  closeSync4(logFd);
@@ -31924,8 +32299,8 @@ async function POST23(req) {
31924
32299
 
31925
32300
  // app/api/admin/version/upgrade/progress/route.ts
31926
32301
  import { existsSync as existsSync25, readFileSync as readFileSync25 } from "fs";
31927
- import { resolve as resolve26, join as join12 } from "path";
31928
- var PLATFORM_ROOT11 = process.env.MAXY_PLATFORM_ROOT ?? resolve26(process.cwd(), "..");
32302
+ import { resolve as resolve27, join as join12 } from "path";
32303
+ var PLATFORM_ROOT11 = process.env.MAXY_PLATFORM_ROOT ?? resolve27(process.cwd(), "..");
31929
32304
  var upgradeHostname2 = "maxy";
31930
32305
  var brandPath2 = join12(PLATFORM_ROOT11, "config", "brand.json");
31931
32306
  if (existsSync25(brandPath2)) {
@@ -32851,8 +33226,8 @@ app.get("/agent-assets/:slug/:filename", (c) => {
32851
33226
  console.error(`[agent-assets] no-account slug=${slug} file=${filename}`);
32852
33227
  return c.text("Not found", 404);
32853
33228
  }
32854
- const filePath = resolve27(account.accountDir, "agents", slug, "assets", filename);
32855
- const expectedDir = resolve27(account.accountDir, "agents", slug, "assets");
33229
+ const filePath = resolve28(account.accountDir, "agents", slug, "assets", filename);
33230
+ const expectedDir = resolve28(account.accountDir, "agents", slug, "assets");
32856
33231
  if (!filePath.startsWith(expectedDir + "/")) {
32857
33232
  console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
32858
33233
  return c.text("Forbidden", 403);
@@ -32881,8 +33256,8 @@ app.get("/generated/:filename", (c) => {
32881
33256
  console.error(`[generated] serve file=${filename} status=404`);
32882
33257
  return c.text("Not found", 404);
32883
33258
  }
32884
- const filePath = resolve27(account.accountDir, "generated", filename);
32885
- const expectedDir = resolve27(account.accountDir, "generated");
33259
+ const filePath = resolve28(account.accountDir, "generated", filename);
33260
+ const expectedDir = resolve28(account.accountDir, "generated");
32886
33261
  if (!filePath.startsWith(expectedDir + "/")) {
32887
33262
  console.error(`[generated] serve file=${filename} status=403`);
32888
33263
  return c.text("Forbidden", 403);
@@ -32922,7 +33297,7 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
32922
33297
  function cachedHtml(file2) {
32923
33298
  let html = htmlCache.get(file2);
32924
33299
  if (!html) {
32925
- html = readFileSync26(resolve27(process.cwd(), "public", file2), "utf-8");
33300
+ html = readFileSync26(resolve28(process.cwd(), "public", file2), "utf-8");
32926
33301
  html = html.replace("<title>Maxy</title>", `<title>${escapeHtml2(BRAND.productName)}</title>`);
32927
33302
  html = html.replace('href="/favicon.ico"', `href="${escapeHtml2(brandFaviconPath)}"`);
32928
33303
  html = html.replace("</head>", `${brandScript}
@@ -33025,7 +33400,7 @@ app.use("/vnc-popout.html", logViewerFetch);
33025
33400
  app.get("/vnc-popout.html", (c) => {
33026
33401
  let html = htmlCache.get("vnc-popout.html");
33027
33402
  if (!html) {
33028
- html = readFileSync26(resolve27(process.cwd(), "public", "vnc-popout.html"), "utf-8");
33403
+ html = readFileSync26(resolve28(process.cwd(), "public", "vnc-popout.html"), "utf-8");
33029
33404
  const name = escapeHtml2(BRAND.productName);
33030
33405
  html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
33031
33406
  html = html.replace("</head>", ` ${brandScript}
@@ -33066,6 +33441,9 @@ app.get("/:slug", async (c, next) => {
33066
33441
  }
33067
33442
  await next();
33068
33443
  });
33444
+ app.use("/graph/*", graphAuthMiddleware({ isPublicHost, canAccessAdmin }));
33445
+ app.use("/graph", graphAuthMiddleware({ isPublicHost, canAccessAdmin }));
33446
+ attachGraphHttpRoutes(app);
33069
33447
  app.use("/*", serveStatic({ root: "./public" }));
33070
33448
  var port = parseInt(process.env.PORT ?? "19200", 10);
33071
33449
  var hostname3 = process.env.HOSTNAME ?? "0.0.0.0";
@@ -33075,6 +33453,7 @@ attachVncWsProxy(httpServer, {
33075
33453
  upstreamHost: "127.0.0.1",
33076
33454
  upstreamPort: 6080
33077
33455
  });
33456
+ attachGraphWsProxy(httpServer, { isPublicHost, canAccessAdmin });
33078
33457
  console.log(`${BRAND.productName} listening on http://${hostname3}:${port}`);
33079
33458
  (async () => {
33080
33459
  try {
@@ -33106,7 +33485,7 @@ if (bootAccountConfig?.whatsapp) {
33106
33485
  }
33107
33486
  init({
33108
33487
  configDir: configDirForWhatsApp,
33109
- platformRoot: resolve27(process.env.MAXY_PLATFORM_ROOT ?? join13(__dirname, "..")),
33488
+ platformRoot: resolve28(process.env.MAXY_PLATFORM_ROOT ?? join13(__dirname, "..")),
33110
33489
  accountConfig: bootAccountConfig,
33111
33490
  onMessage: async (msg) => {
33112
33491
  try {