@rubytech/create-realagent 1.0.614 → 1.0.616

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 (46) hide show
  1. package/dist/index.js +42 -8
  2. package/package.json +1 -1
  3. package/payload/platform/config/brand.json +4 -0
  4. package/payload/platform/lib/mcp-stderr-tee/dist/index.d.ts +23 -13
  5. package/payload/platform/lib/mcp-stderr-tee/dist/index.d.ts.map +1 -1
  6. package/payload/platform/lib/mcp-stderr-tee/dist/index.js +86 -89
  7. package/payload/platform/lib/mcp-stderr-tee/dist/index.js.map +1 -1
  8. package/payload/platform/lib/mcp-stderr-tee/src/index.ts +86 -101
  9. package/payload/platform/plugins/admin/mcp/dist/index.js +33 -2
  10. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  11. package/payload/platform/plugins/admin/mcp/dist/lib/review-tools.d.ts.map +1 -1
  12. package/payload/platform/plugins/admin/mcp/dist/lib/review-tools.js +2 -0
  13. package/payload/platform/plugins/admin/mcp/dist/lib/review-tools.js.map +1 -1
  14. package/payload/platform/plugins/admin/skills/stream-log-review/SKILL.md +22 -8
  15. package/payload/platform/plugins/cloudflare/PLUGIN.md +5 -4
  16. package/payload/platform/plugins/cloudflare/mcp/__tests__/auth-binding.test.ts +196 -0
  17. package/payload/platform/plugins/cloudflare/mcp/__tests__/brand-load.test.ts +81 -0
  18. package/payload/platform/plugins/cloudflare/mcp/__tests__/manifest-scope.test.ts +65 -0
  19. package/payload/platform/plugins/cloudflare/mcp/__tests__/verify-scenario-0.test.ts +70 -0
  20. package/payload/platform/plugins/cloudflare/mcp/__tests__/verify-scenario-B.test.ts +124 -0
  21. package/payload/platform/plugins/cloudflare/mcp/dist/index.js +232 -183
  22. package/payload/platform/plugins/cloudflare/mcp/dist/index.js.map +1 -1
  23. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts +181 -30
  24. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts.map +1 -1
  25. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js +938 -154
  26. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js.map +1 -1
  27. package/payload/platform/plugins/cloudflare/mcp/package.json +5 -2
  28. package/payload/platform/plugins/cloudflare/mcp/vitest.config.ts +10 -0
  29. package/payload/platform/plugins/cloudflare/references/setup-guide.md +32 -27
  30. package/payload/platform/plugins/cloudflare/skills/setup-tunnel/SKILL.md +25 -3
  31. package/payload/platform/plugins/docs/PLUGIN.md +2 -0
  32. package/payload/platform/plugins/docs/references/cloudflare.md +68 -0
  33. package/payload/platform/plugins/docs/references/plugins-guide.md +8 -6
  34. package/payload/platform/plugins/docs/references/troubleshooting.md +2 -0
  35. package/payload/platform/plugins/email/mcp/dist/lib/providers.d.ts +9 -2
  36. package/payload/platform/plugins/email/mcp/dist/lib/providers.d.ts.map +1 -1
  37. package/payload/platform/plugins/email/mcp/dist/lib/providers.js +545 -92
  38. package/payload/platform/plugins/email/mcp/dist/lib/providers.js.map +1 -1
  39. package/payload/platform/scripts/logs-read.sh +114 -54
  40. package/payload/platform/templates/agents/admin/IDENTITY.md +6 -0
  41. package/payload/platform/templates/agents/public/IDENTITY.md +1 -0
  42. package/payload/platform/templates/specialists/agents/content-producer.md +4 -0
  43. package/payload/platform/templates/specialists/agents/personal-assistant.md +16 -8
  44. package/payload/platform/templates/specialists/agents/project-manager.md +4 -0
  45. package/payload/platform/templates/specialists/agents/research-assistant.md +4 -0
  46. package/payload/server/server.js +714 -125
@@ -2898,7 +2898,7 @@ var serveStatic = (options = { root: "" }) => {
2898
2898
  // server/index.ts
2899
2899
  import { readFileSync as readFileSync26, existsSync as existsSync26, watchFile } from "fs";
2900
2900
  import { resolve as resolve27, join as join13, basename as basename6 } from "path";
2901
- import { homedir as homedir3 } from "os";
2901
+ import { homedir as homedir4 } from "os";
2902
2902
 
2903
2903
  // app/lib/vnc-logger.ts
2904
2904
  import { appendFileSync, mkdirSync } from "fs";
@@ -4162,10 +4162,14 @@ function keyFilePath() {
4162
4162
 
4163
4163
  // app/lib/claude-agent.ts
4164
4164
  import Anthropic2 from "@anthropic-ai/sdk";
4165
- import { spawn as spawn2 } from "child_process";
4165
+ import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
4166
4166
  import { randomUUID as randomUUID2 } from "crypto";
4167
4167
  import { resolve as resolve6, join as join4 } from "path";
4168
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, readdirSync as readdirSync2, existsSync as existsSync6, mkdirSync as mkdirSync4, createWriteStream, statSync as statSync2, unlinkSync, cpSync, rmSync } from "fs";
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";
4170
+ import { lookup as dnsLookup } from "dns/promises";
4171
+ import { createConnection as netConnect } from "net";
4172
+ import { StringDecoder } from "string_decoder";
4169
4173
 
4170
4174
  // ../lib/models/src/index.ts
4171
4175
  var OPUS_MODEL = "claude-opus-4-7";
@@ -4465,7 +4469,7 @@ function buildX11Env(chromiumWrapperPath, transport = "vnc") {
4465
4469
  import neo4j from "neo4j-driver";
4466
4470
  import { randomUUID } from "crypto";
4467
4471
  import { spawn } from "child_process";
4468
- import { readFileSync as readFileSync6, readdirSync, existsSync as existsSync5 } from "fs";
4472
+ import { readFileSync as readFileSync6, readdirSync, existsSync as existsSync5, openSync, readSync, closeSync, statSync as statSync2 } from "fs";
4469
4473
  import { resolve as resolve5 } from "path";
4470
4474
  var PLATFORM_ROOT3 = process.env.MAXY_PLATFORM_ROOT ?? resolve5(process.cwd(), "..");
4471
4475
  var driver = null;
@@ -5455,7 +5459,46 @@ Expertise: ${typeof profile.expertise === "string" ? profile.expertise : JSON.st
5455
5459
  var MAX_SESSION_TASKS = 20;
5456
5460
  var MAX_SESSION_REVIEW_ALERTS = 5;
5457
5461
  var MAX_SESSION_PROJECTS = 5;
5458
- async function loadSessionContext(accountId) {
5462
+ var MAX_RECENT_TOOL_FAILURES = 3;
5463
+ var RECENT_FAILURES_TAIL_BYTES = 10 * 1024;
5464
+ function readRecentToolFailures(accountId, conversationId) {
5465
+ try {
5466
+ const platformRoot3 = process.env.MAXY_PLATFORM_ROOT ?? resolve5(process.cwd(), "..");
5467
+ const logDir = resolve5(platformRoot3, "..", "data/accounts", accountId, "logs");
5468
+ const logPath2 = resolve5(logDir, `claude-agent-stream-${conversationId}.log`);
5469
+ if (!existsSync5(logPath2)) {
5470
+ console.error(`[review-tail-skip] path=${logPath2} reason=file-missing \u2014 first turn of conversation, subprocess not yet spawned, or log rotated`);
5471
+ return [];
5472
+ }
5473
+ const st = statSync2(logPath2);
5474
+ const size = st.size;
5475
+ const readBytes = Math.min(size, RECENT_FAILURES_TAIL_BYTES);
5476
+ const position = size - readBytes;
5477
+ const fd = openSync(logPath2, "r");
5478
+ try {
5479
+ const buf = Buffer.alloc(readBytes);
5480
+ readSync(fd, buf, 0, readBytes, position);
5481
+ const text = buf.toString("utf-8");
5482
+ const lines = text.split("\n");
5483
+ const matches = [];
5484
+ for (let i = lines.length - 1; i >= 0 && matches.length < MAX_RECENT_TOOL_FAILURES; i--) {
5485
+ const line = lines[i];
5486
+ if (line.includes("[tool-failure-diag]")) {
5487
+ matches.push(line);
5488
+ }
5489
+ }
5490
+ return matches.reverse();
5491
+ } finally {
5492
+ closeSync(fd);
5493
+ }
5494
+ } catch (err) {
5495
+ console.error(
5496
+ `[session-context] recent-tool-failures read failed: ${err instanceof Error ? err.message : String(err)}`
5497
+ );
5498
+ return [];
5499
+ }
5500
+ }
5501
+ async function loadSessionContext(accountId, conversationId) {
5459
5502
  const session = getSession();
5460
5503
  try {
5461
5504
  const digestResult = await session.run(
@@ -5625,9 +5668,21 @@ ${projectLines.join("\n")}`);
5625
5668
  sections.push(`## Active Review Alerts (${alertsResult.records.length})
5626
5669
  ${alertLines.join("\n")}`);
5627
5670
  }
5671
+ let recentFailuresCount = 0;
5672
+ if (conversationId) {
5673
+ const failureLines = readRecentToolFailures(accountId, conversationId);
5674
+ if (failureLines.length > 0) {
5675
+ recentFailuresCount = failureLines.length;
5676
+ const guidance = "These tool calls failed in the current conversation. Inspect the diagnostic fields before retrying the same tool against the same target \u2014 if you do retry, narrate explicitly why the diagnostic suggests a retry will now succeed. A second identical failure is evidence the path is broken, not evidence another attempt is warranted.";
5677
+ sections.push(`## Recent Tool Failures (${failureLines.length})
5678
+ ${guidance}
5679
+
5680
+ ${failureLines.map((l) => `- ${l.trim()}`).join("\n")}`);
5681
+ }
5682
+ }
5628
5683
  if (sections.length === 0) return null;
5629
5684
  console.error(
5630
- `[session-context] Loaded for ${accountId.slice(0, 8)}\u2026: digest=${digestResult.records.length > 0 ? "yes" : "no"}, tasks=${tasksResult.records.length}, projects=${projectsCount}, reviewAlerts=${alertsResult.records.length}, pendingActions=${pendingCount}`
5685
+ `[session-context] Loaded for ${accountId.slice(0, 8)}\u2026: digest=${digestResult.records.length > 0 ? "yes" : "no"}, tasks=${tasksResult.records.length}, projects=${projectsCount}, reviewAlerts=${alertsResult.records.length}, pendingActions=${pendingCount}, recentFailures=${recentFailuresCount}`
5631
5686
  );
5632
5687
  return `<previous-context>
5633
5688
  ${sections.join("\n\n")}
@@ -5991,20 +6046,264 @@ var BROWSER_TOOL_PREFIXES = [
5991
6046
  function isBrowserTool(name) {
5992
6047
  return BROWSER_TOOL_PREFIXES.some((p) => name.startsWith(p));
5993
6048
  }
5994
- function agentLogStream(name, accountDir) {
6049
+ var DIAG_HARD_CAP_MS = 5e3;
6050
+ var DIAG_DNS_TIMEOUT_MS = 2e3;
6051
+ var DIAG_TCP_TIMEOUT_MS = 3e3;
6052
+ var DIAG_HTTP_TIMEOUT_MS = 4e3;
6053
+ function quoteDiag(value) {
6054
+ return JSON.stringify(value);
6055
+ }
6056
+ function extractUrl(toolName, input) {
6057
+ if (input === null || typeof input !== "object") return void 0;
6058
+ const obj = input;
6059
+ if (toolName === "WebFetch" && typeof obj.url === "string") return obj.url;
6060
+ if (isBrowserTool(toolName) && typeof obj.url === "string") return obj.url;
6061
+ if (typeof obj.url === "string" && /^https?:\/\//.test(obj.url)) return obj.url;
6062
+ return void 0;
6063
+ }
6064
+ var FULL_REDACT_ENV_VARS = /* @__PURE__ */ new Set(["HTTPS_PROXY", "HTTP_PROXY", "NO_PROXY"]);
6065
+ function redactEnvField(name) {
6066
+ const value = process.env[name];
6067
+ if (!value) return `${name.toLowerCase()}=absent`;
6068
+ if (FULL_REDACT_ENV_VARS.has(name)) {
6069
+ return `${name.toLowerCase()}=present`;
6070
+ }
6071
+ const suffix = value.length > 40 ? value.slice(-40) : value;
6072
+ return `${name.toLowerCase()}=present suffix=${quoteDiag(suffix)}`;
6073
+ }
6074
+ async function probeDns(host, family) {
6075
+ const label = family === 4 ? "dns_a" : "dns_aaaa";
6076
+ const start = Date.now();
6077
+ let timer;
6078
+ try {
6079
+ const result = await Promise.race([
6080
+ dnsLookup(host, { family, verbatim: true }),
6081
+ new Promise((_, reject) => {
6082
+ timer = setTimeout(() => reject(new Error("timeout")), DIAG_DNS_TIMEOUT_MS);
6083
+ })
6084
+ ]);
6085
+ if (timer) clearTimeout(timer);
6086
+ const ms = Date.now() - start;
6087
+ return `${label}=${result.address} ${label}_ms=${ms}`;
6088
+ } catch (err) {
6089
+ if (timer) clearTimeout(timer);
6090
+ const ms = Date.now() - start;
6091
+ const msg = err instanceof Error ? err.message : String(err);
6092
+ return `${label}=err ${label}_err=${quoteDiag(msg.slice(0, 60))} ${label}_ms=${ms}`;
6093
+ }
6094
+ }
6095
+ async function probeTcp(host, port2) {
6096
+ const start = Date.now();
6097
+ return new Promise((resolvePromise) => {
6098
+ let settled = false;
6099
+ const sock = netConnect({ host, port: port2, family: 0 });
6100
+ const finish = (result) => {
6101
+ if (settled) return;
6102
+ settled = true;
6103
+ try {
6104
+ sock.destroy();
6105
+ } catch {
6106
+ }
6107
+ resolvePromise(result);
6108
+ };
6109
+ const timer = setTimeout(() => finish(`tcp=timeout tcp_ms=${Date.now() - start}`), DIAG_TCP_TIMEOUT_MS);
6110
+ sock.once("connect", () => {
6111
+ clearTimeout(timer);
6112
+ finish(`tcp=ok tcp_ms=${Date.now() - start}`);
6113
+ });
6114
+ sock.once("error", (err) => {
6115
+ clearTimeout(timer);
6116
+ const msg = err instanceof Error ? err.message : String(err);
6117
+ finish(`tcp=err tcp_err=${quoteDiag(msg.slice(0, 60))} tcp_ms=${Date.now() - start}`);
6118
+ });
6119
+ });
6120
+ }
6121
+ async function probeHttp(url2) {
6122
+ const start = Date.now();
6123
+ const controller = new AbortController();
6124
+ const timer = setTimeout(() => controller.abort(), DIAG_HTTP_TIMEOUT_MS);
6125
+ try {
6126
+ const res = await fetch(url2, { method: "HEAD", redirect: "manual", signal: controller.signal });
6127
+ clearTimeout(timer);
6128
+ return `http_status=${res.status} http_ms=${Date.now() - start}`;
6129
+ } catch (err) {
6130
+ clearTimeout(timer);
6131
+ const msg = err instanceof Error ? err.message : String(err);
6132
+ return `http_status=err http_err=${quoteDiag(msg.slice(0, 60))} http_ms=${Date.now() - start}`;
6133
+ }
6134
+ }
6135
+ async function runFailureDiagnostic(toolName, toolInput) {
6136
+ const inputKeys = toolInput !== null && typeof toolInput === "object" ? Object.keys(toolInput).join(",") : "";
6137
+ const envFields = [
6138
+ redactEnvField("HTTPS_PROXY"),
6139
+ redactEnvField("HTTP_PROXY"),
6140
+ redactEnvField("NO_PROXY"),
6141
+ redactEnvField("NODE_OPTIONS")
6142
+ ].join(" ");
6143
+ const url2 = extractUrl(toolName, toolInput);
6144
+ if (!url2) {
6145
+ return `diag_url=none input_keys=[${inputKeys}] ${envFields}`;
6146
+ }
6147
+ let host;
6148
+ let port2;
6149
+ try {
6150
+ const parsed = new URL(url2);
6151
+ host = parsed.hostname;
6152
+ port2 = parsed.port ? Number(parsed.port) : parsed.protocol === "https:" ? 443 : 80;
6153
+ } catch {
6154
+ return `diag_url=unparseable input_keys=[${inputKeys}] ${envFields}`;
6155
+ }
6156
+ const probes = Promise.allSettled([
6157
+ probeDns(host, 4),
6158
+ probeDns(host, 6),
6159
+ probeTcp(host, port2),
6160
+ probeHttp(url2)
6161
+ ]);
6162
+ let capTimer;
6163
+ const capped = await Promise.race([
6164
+ probes,
6165
+ new Promise((resolvePromise) => {
6166
+ capTimer = setTimeout(() => resolvePromise("__diag_timeout__"), DIAG_HARD_CAP_MS);
6167
+ })
6168
+ ]);
6169
+ if (capTimer) clearTimeout(capTimer);
6170
+ if (capped === "__diag_timeout__") {
6171
+ return `diag_host=${host} diag_port=${port2} diag_timeout=true input_keys=[${inputKeys}] ${envFields}`;
6172
+ }
6173
+ const fields = capped.map((r) => r.status === "fulfilled" ? r.value : `probe_err=${quoteDiag(String(r.reason).slice(0, 40))}`).join(" ");
6174
+ return `diag_host=${host} diag_port=${port2} ${fields} input_keys=[${inputKeys}] ${envFields}`;
6175
+ }
6176
+ function agentLogStream(name, accountDir, conversationId) {
6177
+ if (!conversationId) {
6178
+ throw new Error(`agentLogStream: conversationId is required (name=${name}) \u2014 use preConversationLogStream for pre-session events`);
6179
+ }
6180
+ const logDir = resolve6(accountDir, "logs");
6181
+ mkdirSync4(logDir, { recursive: true });
6182
+ purgeOldLogs(logDir, `${name}-`);
6183
+ return createWriteStream(resolve6(logDir, `${name}-${conversationId}.log`), { flags: "a" });
6184
+ }
6185
+ function preConversationLogStream(name, accountDir) {
5995
6186
  const logDir = resolve6(accountDir, "logs");
5996
6187
  mkdirSync4(logDir, { recursive: true });
5997
6188
  const date5 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6189
+ purgeOldLogs(logDir, `preconversation-${name}-`);
6190
+ return createWriteStream(resolve6(logDir, `preconversation-${name}-${date5}.log`), { flags: "a" });
6191
+ }
6192
+ function purgeOldLogs(logDir, prefix) {
5998
6193
  const cutoff = Date.now() - LOG_RETENTION_DAYS * 24 * 60 * 60 * 1e3;
6194
+ let entries;
5999
6195
  try {
6000
- for (const file2 of readdirSync2(logDir)) {
6001
- if (!file2.startsWith(`${name}-`)) continue;
6002
- const filePath = resolve6(logDir, file2);
6003
- if (statSync2(filePath).mtimeMs < cutoff) unlinkSync(filePath);
6196
+ entries = readdirSync2(logDir);
6197
+ } catch (err) {
6198
+ const msg = err instanceof Error ? err.message : String(err);
6199
+ console.error(`[log-purge-err] readdir dir=${logDir} prefix=${prefix} reason=${msg}`);
6200
+ return;
6201
+ }
6202
+ for (const file2 of entries) {
6203
+ if (!file2.startsWith(prefix)) continue;
6204
+ const filePath = resolve6(logDir, file2);
6205
+ try {
6206
+ if (statSync3(filePath).mtimeMs < cutoff) unlinkSync(filePath);
6207
+ } catch (err) {
6208
+ const msg = err instanceof Error ? err.message : String(err);
6209
+ console.error(`[log-purge-err] file=${file2} reason=${msg}`);
6004
6210
  }
6005
- } catch {
6006
6211
  }
6007
- return createWriteStream(resolve6(logDir, `${name}-${date5}.log`), { flags: "a" });
6212
+ }
6213
+ function teeProcStderrToStreamLog(proc, streamLog) {
6214
+ if (!proc.stderr) {
6215
+ streamLog.write(`[${isoTs()}] [subproc-stderr-skip] reason=no-stderr
6216
+ `);
6217
+ return;
6218
+ }
6219
+ const utf8 = new StringDecoder("utf8");
6220
+ let buffer = "";
6221
+ proc.stderr.on("data", (chunk) => {
6222
+ const text = typeof chunk === "string" ? chunk : utf8.write(chunk);
6223
+ buffer += text;
6224
+ let idx;
6225
+ while ((idx = buffer.indexOf("\n")) !== -1) {
6226
+ const line = buffer.slice(0, idx);
6227
+ buffer = buffer.slice(idx + 1);
6228
+ if (line.length === 0) continue;
6229
+ if (streamLog.destroyed || streamLog.writableEnded) continue;
6230
+ streamLog.write(`[${isoTs()}] [subproc-stderr] ${line}
6231
+ `);
6232
+ }
6233
+ });
6234
+ proc.stderr.on("end", () => {
6235
+ const tail = (buffer + utf8.end()).trim();
6236
+ if (tail.length > 0 && !streamLog.destroyed && !streamLog.writableEnded) {
6237
+ streamLog.write(`[${isoTs()}] [subproc-stderr] ${tail}
6238
+ `);
6239
+ }
6240
+ buffer = "";
6241
+ });
6242
+ }
6243
+ function sampleProcState(pid) {
6244
+ try {
6245
+ if (osPlatform() === "linux") {
6246
+ const fdDir = `/proc/${pid}/fd`;
6247
+ let openFds2 = 0;
6248
+ try {
6249
+ openFds2 = readdirSync2(fdDir).length;
6250
+ } catch (err) {
6251
+ const msg = err instanceof Error ? err.message : String(err);
6252
+ return `proc_err=${JSON.stringify(msg.slice(0, 60))}`;
6253
+ }
6254
+ let established2 = 0;
6255
+ let connecting2 = 0;
6256
+ let sockets2 = 0;
6257
+ for (const tcpFile of ["/proc/" + pid + "/net/tcp", "/proc/" + pid + "/net/tcp6"]) {
6258
+ try {
6259
+ const raw2 = readFileSync7(tcpFile, "utf-8");
6260
+ const lines2 = raw2.split("\n");
6261
+ for (let i = 1; i < lines2.length; i++) {
6262
+ const line = lines2[i].trim();
6263
+ if (!line) continue;
6264
+ const parts = line.split(/\s+/);
6265
+ const state = parts[3];
6266
+ sockets2 += 1;
6267
+ if (state === "01") established2 += 1;
6268
+ else if (state === "02" || state === "03") connecting2 += 1;
6269
+ }
6270
+ } catch {
6271
+ }
6272
+ }
6273
+ let rssMb = 0;
6274
+ try {
6275
+ const statm = readFileSync7(`/proc/${pid}/statm`, "utf-8").trim().split(/\s+/);
6276
+ const rssPages = parseInt(statm[1] ?? "0", 10);
6277
+ if (Number.isFinite(rssPages)) rssMb = Math.round(rssPages * 4096 / (1024 * 1024));
6278
+ } catch {
6279
+ }
6280
+ return `open_fds=${openFds2} socket_count=${sockets2} tcp_established=${established2} tcp_connecting=${connecting2} rss_mb=${rssMb}`;
6281
+ }
6282
+ const result = spawnSync2("lsof", ["-p", String(pid), "-nP"], { timeout: 500, encoding: "utf-8" });
6283
+ if (result.error || result.status !== 0) {
6284
+ const reason = result.error instanceof Error ? result.error.message : `exit=${result.status}`;
6285
+ return `proc_err=${JSON.stringify(reason.slice(0, 60))}`;
6286
+ }
6287
+ const lines = result.stdout.split("\n");
6288
+ let openFds = 0;
6289
+ let sockets = 0;
6290
+ let established = 0;
6291
+ let connecting = 0;
6292
+ for (let i = 1; i < lines.length; i++) {
6293
+ const line = lines[i];
6294
+ if (!line) continue;
6295
+ openFds += 1;
6296
+ if (line.includes("IPv4") || line.includes("IPv6") || line.includes("TCP")) {
6297
+ sockets += 1;
6298
+ if (/ESTABLISHED/.test(line)) established += 1;
6299
+ else if (/SYN_SENT|SYN_RCVD/.test(line)) connecting += 1;
6300
+ }
6301
+ }
6302
+ return `open_fds=${openFds} socket_count=${sockets} tcp_established=${established} tcp_connecting=${connecting} rss_mb=unknown`;
6303
+ } catch (err) {
6304
+ const msg = err instanceof Error ? err.message : String(err);
6305
+ return `proc_err=${JSON.stringify(msg.slice(0, 60))}`;
6306
+ }
6008
6307
  }
6009
6308
  var PLATFORM_ROOT4 = process.env.MAXY_PLATFORM_ROOT ?? resolve6(process.cwd(), "..");
6010
6309
  var ACCOUNTS_DIR = resolve6(PLATFORM_ROOT4, "..", "data/accounts");
@@ -6182,8 +6481,8 @@ function resolveAgentConfig(accountDir, agentName) {
6182
6481
  const hasKnowledge = existsSync6(knowledgePath);
6183
6482
  const hasSummary = existsSync6(summaryPath);
6184
6483
  if (hasKnowledge && hasSummary) {
6185
- const knowledgeMtime = statSync2(knowledgePath).mtimeMs;
6186
- const summaryMtime = statSync2(summaryPath).mtimeMs;
6484
+ const knowledgeMtime = statSync3(knowledgePath).mtimeMs;
6485
+ const summaryMtime = statSync3(summaryPath).mtimeMs;
6187
6486
  if (summaryMtime >= knowledgeMtime) {
6188
6487
  knowledge = readFileSync7(summaryPath, "utf-8");
6189
6488
  } else {
@@ -6862,7 +7161,7 @@ ${specialist}: ${plugins.join(", ")}`);
6862
7161
  for (const entry of readdirSync2(current)) {
6863
7162
  const full = resolve6(current, entry);
6864
7163
  try {
6865
- const stat4 = statSync2(full);
7164
+ const stat4 = statSync3(full);
6866
7165
  if (stat4.isDirectory()) {
6867
7166
  walk(full, `${rel}${entry}/`);
6868
7167
  } else if (entry.endsWith(".md")) {
@@ -6993,6 +7292,15 @@ function resolveUserAccounts(userId) {
6993
7292
  var sessionStore = /* @__PURE__ */ new Map();
6994
7293
  setSessionStoreRef(sessionStore);
6995
7294
  function registerSession(sessionKey, agentType, accountId, agentName, userId, userName) {
7295
+ const existing = sessionStore.get(sessionKey);
7296
+ if (existing) {
7297
+ existing.agentType = agentType;
7298
+ existing.accountId = accountId;
7299
+ existing.agentName = agentName ?? existing.agentName;
7300
+ existing.userId = userId ?? existing.userId;
7301
+ existing.userName = userName ?? existing.userName;
7302
+ return;
7303
+ }
6996
7304
  sessionStore.set(sessionKey, { createdAt: Date.now(), agentType, accountId, agentName, userId, userName });
6997
7305
  }
6998
7306
  function registerResumedSession(sessionKey, accountId, agentName, conversationId, messages) {
@@ -7057,6 +7365,9 @@ function storeAgentSessionId(sessionKey, agentSessionId) {
7057
7365
  const session = sessionStore.get(sessionKey);
7058
7366
  if (session) {
7059
7367
  session.agentSessionId = agentSessionId;
7368
+ console.error(`[session-store] storeAgentSessionId sessionKey=${sessionKey.slice(0, 12)}\u2026 sessionId=${agentSessionId.slice(0, 8)}\u2026`);
7369
+ } else {
7370
+ console.error(`[session-store] storeAgentSessionId SKIPPED \u2014 no session entry sessionKey=${sessionKey.slice(0, 12)}\u2026 sessionId=${agentSessionId.slice(0, 8)}\u2026`);
7060
7371
  }
7061
7372
  }
7062
7373
  function getAgentSessionId(sessionKey) {
@@ -7186,43 +7497,48 @@ function consumeStalledSubagents(sessionKey) {
7186
7497
  delete session.stalledSubagents;
7187
7498
  return stalls && stalls.length > 0 ? stalls : void 0;
7188
7499
  }
7189
- function getMcpServers(accountId, userId, enabledPlugins) {
7500
+ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
7501
+ if (!conversationId) {
7502
+ throw new Error(`getMcpServers: conversationId is required (accountId=${accountId.slice(0, 8)})`);
7503
+ }
7190
7504
  const LOG_DIR2 = resolve6(ACCOUNTS_DIR, accountId, "logs");
7505
+ const STREAM_LOG_PATH = resolve6(LOG_DIR2, `claude-agent-stream-${conversationId}.log`);
7506
+ const baseEnv = { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT4, LOG_DIR: LOG_DIR2, STREAM_LOG_PATH };
7191
7507
  const servers = {
7192
7508
  "memory": {
7193
7509
  command: "node",
7194
7510
  args: [resolve6(PLATFORM_ROOT4, "plugins/memory/mcp/dist/index.js")],
7195
- env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT4, LOG_DIR: LOG_DIR2, ...userId ? { USER_ID: userId } : {} }
7511
+ env: { ...baseEnv, ...userId ? { USER_ID: userId } : {} }
7196
7512
  },
7197
7513
  "contacts": {
7198
7514
  command: "node",
7199
7515
  args: [resolve6(PLATFORM_ROOT4, "plugins/contacts/mcp/dist/index.js")],
7200
- env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT4, LOG_DIR: LOG_DIR2 }
7516
+ env: { ...baseEnv }
7201
7517
  },
7202
7518
  "whatsapp": {
7203
7519
  command: "node",
7204
7520
  args: [resolve6(PLATFORM_ROOT4, "plugins/whatsapp/mcp/dist/index.js")],
7205
- env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT4, LOG_DIR: LOG_DIR2, PLATFORM_PORT: process.env.PORT ?? "19200" }
7521
+ env: { ...baseEnv, PLATFORM_PORT: process.env.PORT ?? "19200" }
7206
7522
  },
7207
7523
  "admin": {
7208
7524
  command: "node",
7209
7525
  args: [resolve6(PLATFORM_ROOT4, "plugins/admin/mcp/dist/index.js")],
7210
- env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT4, LOG_DIR: LOG_DIR2, PLATFORM_PORT: process.env.PORT ?? "19200", ...userId ? { USER_ID: userId } : {} }
7526
+ env: { ...baseEnv, PLATFORM_PORT: process.env.PORT ?? "19200", ...userId ? { USER_ID: userId } : {} }
7211
7527
  },
7212
7528
  "scheduling": {
7213
7529
  command: "node",
7214
7530
  args: [resolve6(PLATFORM_ROOT4, "plugins/scheduling/mcp/dist/index.js")],
7215
- env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT4, LOG_DIR: LOG_DIR2 }
7531
+ env: { ...baseEnv }
7216
7532
  },
7217
7533
  "tasks": {
7218
7534
  command: "node",
7219
7535
  args: [resolve6(PLATFORM_ROOT4, "plugins/tasks/mcp/dist/index.js")],
7220
- env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT4, LOG_DIR: LOG_DIR2 }
7536
+ env: { ...baseEnv }
7221
7537
  },
7222
7538
  "email": {
7223
7539
  command: "node",
7224
7540
  args: [resolve6(PLATFORM_ROOT4, "plugins/email/mcp/dist/index.js")],
7225
- env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT4, LOG_DIR: LOG_DIR2 }
7541
+ env: { ...baseEnv }
7226
7542
  },
7227
7543
  // Playwright MCP server — browser automation for browser-specialist.
7228
7544
  // Key matches Claude Code's plugin naming: plugin_{plugin}_{server} so tools
@@ -7241,7 +7557,7 @@ function getMcpServers(accountId, userId, enabledPlugins) {
7241
7557
  servers["telegram"] = {
7242
7558
  command: "node",
7243
7559
  args: [resolve6(PLATFORM_ROOT4, "plugins/telegram/mcp/dist/index.js")],
7244
- env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT4, LOG_DIR: LOG_DIR2, TELEGRAM_BOT_TOKEN: tgBotToken }
7560
+ env: { ...baseEnv, TELEGRAM_BOT_TOKEN: tgBotToken }
7245
7561
  };
7246
7562
  } else {
7247
7563
  console.error("[plugins] telegram MCP: skipped (no bot token in account.json telegram config)");
@@ -7249,7 +7565,7 @@ function getMcpServers(accountId, userId, enabledPlugins) {
7249
7565
  servers["cloudflare"] = {
7250
7566
  command: "node",
7251
7567
  args: [resolve6(PLATFORM_ROOT4, "plugins/cloudflare/mcp/dist/index.js")],
7252
- env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT4, LOG_DIR: LOG_DIR2, PLATFORM_PORT: process.env.PORT ?? "19200" }
7568
+ env: { ...baseEnv, PLATFORM_PORT: process.env.PORT ?? "19200" }
7253
7569
  };
7254
7570
  if (Array.isArray(enabledPlugins) && enabledPlugins.length > 0) {
7255
7571
  const pluginsDir = resolve6(PLATFORM_ROOT4, "plugins");
@@ -7280,7 +7596,7 @@ function getMcpServers(accountId, userId, enabledPlugins) {
7280
7596
  servers[dir] = {
7281
7597
  command: "node",
7282
7598
  args: [mcpEntry],
7283
- env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT4, LOG_DIR: LOG_DIR2 }
7599
+ env: { ...baseEnv }
7284
7600
  };
7285
7601
  console.log(`[plugins] optional MCP server started: ${dir}`);
7286
7602
  }
@@ -7355,13 +7671,13 @@ var ADMIN_CORE_TOOLS = [
7355
7671
  "mcp__admin__action-reject",
7356
7672
  "mcp__admin__action-edit",
7357
7673
  "mcp__cloudflare__cloudflare-setup",
7358
- "mcp__cloudflare__cf-set-token",
7359
7674
  "mcp__cloudflare__cf-add-zone",
7360
7675
  "mcp__cloudflare__cf-zone-status",
7676
+ "mcp__cloudflare__cf-verify",
7677
+ "mcp__cloudflare__cf-rebuild",
7361
7678
  "mcp__cloudflare__tunnel-status",
7362
7679
  "mcp__cloudflare__tunnel-install",
7363
7680
  "mcp__cloudflare__tunnel-login",
7364
- "mcp__cloudflare__tunnel-create",
7365
7681
  "mcp__cloudflare__tunnel-enable",
7366
7682
  "mcp__cloudflare__tunnel-disable",
7367
7683
  "mcp__cloudflare__tunnel-add-hostname",
@@ -7932,10 +8248,10 @@ var COMPACTION_PROMPT = `You are about to reach your context limit. Call session
7932
8248
 
7933
8249
  Then respond with only: [COMPACTED]`;
7934
8250
  var COMPACTION_TIMEOUT_MS = 45e3;
7935
- async function* runCompactionTurn(accountDir, accountId, systemPrompt, resumeSessionId, adminModel, enabledPlugins) {
7936
- const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, void 0, enabledPlugins) });
8251
+ async function* runCompactionTurn(accountDir, accountId, systemPrompt, resumeSessionId, adminModel, conversationId, enabledPlugins) {
8252
+ const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, conversationId, void 0, enabledPlugins) });
7937
8253
  const specialistsDir = resolve6(accountDir, "specialists");
7938
- if (!existsSync6(specialistsDir)) agentLogStream("claude-agent-compaction-stream", accountDir).write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
8254
+ if (!existsSync6(specialistsDir)) agentLogStream("claude-agent-compaction-stream", accountDir, conversationId).write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
7939
8255
  `);
7940
8256
  const args = [
7941
8257
  "--print",
@@ -7964,15 +8280,23 @@ async function* runCompactionTurn(accountDir, accountId, systemPrompt, resumeSes
7964
8280
  const proc = spawn2("claude", args, {
7965
8281
  cwd: accountDir,
7966
8282
  stdio: ["ignore", "pipe", "pipe"],
7967
- env: { ...process.env, PLATFORM_ROOT: PLATFORM_ROOT4, ACCOUNT_DIR: accountDir }
8283
+ env: {
8284
+ ...process.env,
8285
+ PLATFORM_ROOT: PLATFORM_ROOT4,
8286
+ ACCOUNT_DIR: accountDir,
8287
+ // Task 532: network traces on the compaction subprocess too — its tool
8288
+ // calls are part of the same conversation's record.
8289
+ NODE_DEBUG: "http,http2,net,tls,undici,dns"
8290
+ }
7968
8291
  });
7969
- const stderrLog = agentLogStream("claude-agent-compaction-stderr", accountDir);
8292
+ const stderrLog = agentLogStream("claude-agent-compaction-stderr", accountDir, conversationId);
7970
8293
  stderrLog.on("error", () => {
7971
8294
  });
7972
8295
  proc.stderr?.pipe(stderrLog);
7973
- const streamLog = agentLogStream("claude-agent-compaction-stream", accountDir);
8296
+ const streamLog = agentLogStream("claude-agent-compaction-stream", accountDir, conversationId);
7974
8297
  streamLog.on("error", () => {
7975
8298
  });
8299
+ teeProcStderrToStreamLog(proc, streamLog);
7976
8300
  streamLog.write(`[${isoTs()}] [compaction-start] resumeSessionId=${resumeSessionId}
7977
8301
  `);
7978
8302
  proc.on("error", (err) => {
@@ -8033,14 +8357,19 @@ async function* runCompactionTurn(accountDir, accountId, systemPrompt, resumeSes
8033
8357
  streamLog.end();
8034
8358
  return { ok: capturedSummary !== void 0 && !timedOut, summary: capturedSummary };
8035
8359
  }
8036
- function clearAgentSessionId(sessionKey) {
8360
+ function clearAgentSessionId(sessionKey, reason) {
8037
8361
  const session = sessionStore.get(sessionKey);
8038
8362
  if (session) {
8039
8363
  session.agentSessionId = void 0;
8364
+ console.error(`[session-store] clearAgentSessionId sessionKey=${sessionKey.slice(0, 12)}\u2026 reason=${reason}`);
8365
+ } else {
8366
+ console.error(`[session-store] clearAgentSessionId SKIPPED \u2014 no session entry sessionKey=${sessionKey.slice(0, 12)}\u2026 reason=${reason}`);
8040
8367
  }
8041
8368
  }
8042
- async function* parseClaudeStream(proc, streamLog, adminModel) {
8369
+ async function* parseClaudeStream(proc, streamLog, adminModel, conversationId) {
8043
8370
  const toolIdToName = /* @__PURE__ */ new Map();
8371
+ const toolIdToInput = /* @__PURE__ */ new Map();
8372
+ const convIdTag = conversationId ? ` conversationId=${conversationId.slice(0, 8)}` : "";
8044
8373
  let emittedDone = false;
8045
8374
  let buffer = "";
8046
8375
  const modelCtxWindow = contextWindow(adminModel);
@@ -8075,6 +8404,83 @@ async function* parseClaudeStream(proc, streamLog, adminModel) {
8075
8404
  apiWaitTimer = void 0;
8076
8405
  }
8077
8406
  }
8407
+ const inflightTools = /* @__PURE__ */ new Map();
8408
+ const TOOL_WAIT_TICK_MS = 5e3;
8409
+ const TOOL_WAIT_DIAG_THRESHOLDS_SEC = [15, 30, 45, 60];
8410
+ const TOOL_WAIT_PROC_SEC = 30;
8411
+ const procPid = proc.pid;
8412
+ const toolWaitTimer = setInterval(() => {
8413
+ if (streamLog.destroyed) return;
8414
+ const now = Date.now();
8415
+ for (const [toolUseId, info] of inflightTools) {
8416
+ const elapsedSec = Math.round((now - info.startedAt) / 1e3);
8417
+ streamLog.write(`[${isoTs()}] [tool-wait]${convIdTag} name=${info.name} tool_use_id=${toolUseId} elapsed=${elapsedSec}s
8418
+ `);
8419
+ let nextThreshold = 0;
8420
+ for (const t of TOOL_WAIT_DIAG_THRESHOLDS_SEC) {
8421
+ if (elapsedSec >= t && t > info.lastProbeThresholdSec) nextThreshold = t;
8422
+ }
8423
+ if (nextThreshold > 0) {
8424
+ if (info.probeInFlight) {
8425
+ streamLog.write(`[${isoTs()}] [tool-wait-diag-skip]${convIdTag} tool_use_id=${toolUseId} reason=probe-in-flight threshold=${nextThreshold}s
8426
+ `);
8427
+ } else {
8428
+ info.lastProbeThresholdSec = nextThreshold;
8429
+ info.probeInFlight = true;
8430
+ const toolInput = toolIdToInput.get(toolUseId);
8431
+ runFailureDiagnostic(info.name, toolInput).then((diag) => {
8432
+ if (!streamLog.destroyed && !streamLog.writableEnded) {
8433
+ streamLog.write(`[${isoTs()}] [tool-wait-diag]${convIdTag} name=${info.name} tool_use_id=${toolUseId} elapsed=${elapsedSec}s ${diag}
8434
+ `);
8435
+ }
8436
+ }).catch((err) => {
8437
+ const msg = err instanceof Error ? err.message : String(err);
8438
+ if (!streamLog.destroyed && !streamLog.writableEnded) {
8439
+ streamLog.write(`[${isoTs()}] [tool-wait-diag]${convIdTag} name=${info.name} tool_use_id=${toolUseId} elapsed=${elapsedSec}s diag_err=${JSON.stringify(msg.slice(0, 80))}
8440
+ `);
8441
+ }
8442
+ }).finally(() => {
8443
+ const current = inflightTools.get(toolUseId);
8444
+ if (current) current.probeInFlight = false;
8445
+ });
8446
+ }
8447
+ }
8448
+ if (!info.procSampleEmitted && elapsedSec >= TOOL_WAIT_PROC_SEC) {
8449
+ info.procSampleEmitted = true;
8450
+ if (!procPid) {
8451
+ streamLog.write(`[${isoTs()}] [tool-wait-proc-skip]${convIdTag} tool_use_id=${toolUseId} reason=no-pid
8452
+ `);
8453
+ } else {
8454
+ const procLine = sampleProcState(procPid);
8455
+ streamLog.write(`[${isoTs()}] [tool-wait-proc]${convIdTag} name=${info.name} pid=${procPid} tool_use_id=${toolUseId} elapsed=${elapsedSec}s ${procLine}
8456
+ `);
8457
+ }
8458
+ }
8459
+ }
8460
+ }, TOOL_WAIT_TICK_MS);
8461
+ function trackToolUse(toolUseId, toolName) {
8462
+ if (!toolUseId) {
8463
+ streamLog.write(`[${isoTs()}] [tool-use-no-id]${convIdTag} name=${toolName} reason=missing-tool-use-id-cannot-track-wait
8464
+ `);
8465
+ return;
8466
+ }
8467
+ inflightTools.set(toolUseId, {
8468
+ name: toolName,
8469
+ startedAt: Date.now(),
8470
+ lastProbeThresholdSec: 0,
8471
+ probeInFlight: false,
8472
+ procSampleEmitted: false
8473
+ });
8474
+ }
8475
+ function untrackToolUse(toolUseId, reason) {
8476
+ if (!toolUseId) return;
8477
+ if (!inflightTools.delete(toolUseId)) {
8478
+ if (reason === "result") {
8479
+ streamLog.write(`[${isoTs()}] [tool-wait-late]${convIdTag} tool_use_id=${toolUseId} reason=result-without-matching-tool-use
8480
+ `);
8481
+ }
8482
+ }
8483
+ }
8078
8484
  const readLines = async function* () {
8079
8485
  for await (const chunk of proc.stdout) {
8080
8486
  buffer += chunk.toString();
@@ -8236,20 +8642,24 @@ async function* parseClaudeStream(proc, streamLog, adminModel) {
8236
8642
  yield { type: "session_resume", conversationId: resumeConversationId };
8237
8643
  }
8238
8644
  } else {
8239
- if (block.id) toolIdToName.set(block.id, block.name);
8645
+ if (block.id) {
8646
+ toolIdToName.set(block.id, block.name);
8647
+ toolIdToInput.set(block.id, block.input ?? {});
8648
+ }
8240
8649
  const inputPreview = JSON.stringify(block.input ?? {}).slice(0, 200);
8241
8650
  if (block.name === "Agent") {
8242
8651
  const subType = block.input?.subagent_type ?? "general-purpose";
8243
8652
  const desc = block.input?.description ?? "";
8244
- streamLog.write(`[${isoTs()}] [agent-dispatch] subagent_type=${JSON.stringify(subType)} description=${JSON.stringify(desc)} input=${inputPreview}
8653
+ streamLog.write(`[${isoTs()}] [agent-dispatch]${convIdTag} subagent_type=${JSON.stringify(subType)} description=${JSON.stringify(desc)} input=${inputPreview}
8245
8654
  `);
8246
8655
  } else {
8247
- streamLog.write(`[${isoTs()}] [tool-use] name=${block.name} input=${inputPreview}
8656
+ streamLog.write(`[${isoTs()}] [tool-use]${convIdTag} name=${block.name} input=${inputPreview}
8248
8657
  `);
8249
8658
  if (isBrowserTool(block.name)) {
8250
8659
  vncLog("mcp-tool", { name: block.name, input_preview: inputPreview });
8251
8660
  }
8252
8661
  }
8662
+ trackToolUse(block.id, block.name);
8253
8663
  yield { type: "tool_use", name: block.name, input: block.input ?? {}, toolUseId: block.id };
8254
8664
  }
8255
8665
  }
@@ -8276,10 +8686,10 @@ async function* parseClaudeStream(proc, streamLog, adminModel) {
8276
8686
  }
8277
8687
  const outputPreview = output.slice(0, 300);
8278
8688
  if (name === "Agent") {
8279
- streamLog.write(`[${isoTs()}] [agent-return] error=${!!block.is_error} output=${JSON.stringify(outputPreview)}
8689
+ streamLog.write(`[${isoTs()}] [agent-return]${convIdTag} error=${!!block.is_error} output=${JSON.stringify(outputPreview)}
8280
8690
  `);
8281
8691
  } else {
8282
- streamLog.write(`[${isoTs()}] [tool-result] name=${name} error=${!!block.is_error} output=${JSON.stringify(outputPreview)}
8692
+ streamLog.write(`[${isoTs()}] [tool-result]${convIdTag} name=${name} error=${!!block.is_error} output=${JSON.stringify(outputPreview)}
8283
8693
  `);
8284
8694
  if (isBrowserTool(name)) {
8285
8695
  vncLog("mcp-tool-result", {
@@ -8288,7 +8698,28 @@ async function* parseClaudeStream(proc, streamLog, adminModel) {
8288
8698
  output_preview: outputPreview
8289
8699
  });
8290
8700
  }
8701
+ if (block.is_error && block.tool_use_id) {
8702
+ const toolInput = toolIdToInput.get(block.tool_use_id);
8703
+ try {
8704
+ const diag = await runFailureDiagnostic(name, toolInput);
8705
+ if (!streamLog.destroyed) {
8706
+ streamLog.write(`[${isoTs()}] [tool-failure-diag]${convIdTag} name=${name} ${diag}
8707
+ `);
8708
+ }
8709
+ } catch (err) {
8710
+ if (!streamLog.destroyed) {
8711
+ const msg2 = err instanceof Error ? err.message : String(err);
8712
+ streamLog.write(`[${isoTs()}] [tool-failure-diag]${convIdTag} name=${name} diag_err=${JSON.stringify(msg2.slice(0, 80))}
8713
+ `);
8714
+ }
8715
+ }
8716
+ } else if (block.is_error && !block.tool_use_id) {
8717
+ streamLog.write(`[${isoTs()}] [tool-failure-diag-skip]${convIdTag} name=${name} reason=no-tool-use-id-cannot-lookup-input
8718
+ `);
8719
+ }
8291
8720
  }
8721
+ if (block.tool_use_id) toolIdToInput.delete(block.tool_use_id);
8722
+ untrackToolUse(block.tool_use_id, "result");
8292
8723
  const renderDirectiveRe = /^__RENDER__(\{.+\})$/gm;
8293
8724
  let renderMatch;
8294
8725
  while ((renderMatch = renderDirectiveRe.exec(output)) !== null) {
@@ -8420,6 +8851,19 @@ async function* parseClaudeStream(proc, streamLog, adminModel) {
8420
8851
  }
8421
8852
  } finally {
8422
8853
  endApiWait();
8854
+ clearInterval(toolWaitTimer);
8855
+ for (const [toolUseId, info] of inflightTools) {
8856
+ const elapsedSec = Math.round((Date.now() - info.startedAt) / 1e3);
8857
+ if (!streamLog.destroyed && !streamLog.writableEnded) {
8858
+ streamLog.write(`[${isoTs()}] [tool-wait-late]${convIdTag} name=${info.name} tool_use_id=${toolUseId} elapsed=${elapsedSec}s reason=stream-ended-without-tool-result
8859
+ `);
8860
+ }
8861
+ }
8862
+ inflightTools.clear();
8863
+ if (buffer.length > 0 && !streamLog.destroyed && !streamLog.writableEnded) {
8864
+ streamLog.write(`[${isoTs()}] [stream-buffer-flush]${convIdTag} bytes=${buffer.length} reason=process-exit preview=${JSON.stringify(buffer.slice(0, 120))}
8865
+ `);
8866
+ }
8423
8867
  }
8424
8868
  if (!emittedDone) {
8425
8869
  }
@@ -8679,17 +9123,21 @@ function buildAttachmentMetaText(attachments) {
8679
9123
  async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, adminModel, sessionKey, maxTurns = 20, attachments = [], retryCount = 0, enabledPlugins, clientTimestamp) {
8680
9124
  const userTimestamp = clientTimestamp ?? (/* @__PURE__ */ new Date()).toISOString();
8681
9125
  const resumeSessionId = sessionKey ? getAgentSessionId(sessionKey) : void 0;
9126
+ const spawnConvId = sessionKey ? getConversationIdForSession(sessionKey) : void 0;
9127
+ if (!spawnConvId) {
9128
+ throw new Error(`invokeAdminAgent: conversationId missing for sessionKey=${sessionKey?.slice(0, 8) ?? "none"} \u2014 ensureConversation must run before invoking the agent`);
9129
+ }
8682
9130
  const cdpOk = await ensureCdp();
8683
9131
  if (!cdpOk) {
8684
- const cdpLog = agentLogStream("claude-agent-stream", accountDir);
9132
+ const cdpLog = agentLogStream("claude-agent-stream", accountDir, spawnConvId);
8685
9133
  cdpLog.write(`[${isoTs()}] [warn] ensureCdp failed \u2014 browser-specialist degraded
8686
9134
  `);
8687
9135
  cdpLog.end();
8688
9136
  }
8689
9137
  const ccUserId = sessionKey ? getUserIdForSession(sessionKey) : void 0;
8690
- const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, ccUserId, enabledPlugins) });
9138
+ const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, spawnConvId, ccUserId, enabledPlugins) });
8691
9139
  const specialistsDir = resolve6(accountDir, "specialists");
8692
- if (!existsSync6(specialistsDir)) agentLogStream("claude-agent-stream", accountDir).write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
9140
+ if (!existsSync6(specialistsDir)) agentLogStream("claude-agent-stream", accountDir, spawnConvId).write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
8693
9141
  `);
8694
9142
  const args = [
8695
9143
  "--print",
@@ -8719,15 +9167,24 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
8719
9167
  const proc = spawn2("claude", args, {
8720
9168
  cwd: accountDir,
8721
9169
  stdio: ["ignore", "pipe", "pipe"],
8722
- env: { ...process.env, PLATFORM_ROOT: PLATFORM_ROOT4, ACCOUNT_DIR: accountDir }
9170
+ env: {
9171
+ ...process.env,
9172
+ PLATFORM_ROOT: PLATFORM_ROOT4,
9173
+ ACCOUNT_DIR: accountDir,
9174
+ // Task 532: tee subprocess network activity into the per-conversation
9175
+ // stream log via the existing stderr pipe. Closes the Claude Code
9176
+ // "what was the tool actually doing?" black box during tool waits.
9177
+ NODE_DEBUG: "http,http2,net,tls,undici,dns"
9178
+ }
8723
9179
  });
8724
- const stderrLog = agentLogStream("claude-agent-stderr", accountDir);
9180
+ const stderrLog = agentLogStream("claude-agent-stderr", accountDir, spawnConvId);
8725
9181
  stderrLog.on("error", () => {
8726
9182
  });
8727
9183
  proc.stderr?.pipe(stderrLog);
8728
- const streamLog = agentLogStream("claude-agent-stream", accountDir);
9184
+ const streamLog = agentLogStream("claude-agent-stream", accountDir, spawnConvId);
8729
9185
  streamLog.on("error", () => {
8730
9186
  });
9187
+ teeProcStderrToStreamLog(proc, streamLog);
8731
9188
  if (sessionKey) {
8732
9189
  const prev = activeProcesses.get(sessionKey);
8733
9190
  if (prev) {
@@ -8736,8 +9193,7 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
8736
9193
  }
8737
9194
  activeProcesses.set(sessionKey, { pid: proc.pid, spawnedAt: Date.now() });
8738
9195
  }
8739
- const spawnConvId = sessionKey ? getConversationIdForSession(sessionKey) : void 0;
8740
- streamLog.write(`[${isoTs()}] [spawn] pid=${proc.pid} resume=${resumeSessionId ?? "none"} sessionKey=${sessionKey ?? "none"} conversationId=${spawnConvId ?? "none"} pluginDir=${specialistsDir}
9196
+ streamLog.write(`[${isoTs()}] [spawn] pid=${proc.pid} resume=${resumeSessionId ?? "none"} sessionKey=${sessionKey ?? "none"} conversationId=${spawnConvId} pluginDir=${specialistsDir}
8741
9197
  `);
8742
9198
  streamLog.write(`[${isoTs()}] [stdin] len=${fullMessage.length} preview=${JSON.stringify(fullMessage.slice(0, 80))}
8743
9199
  `);
@@ -8758,7 +9214,7 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
8758
9214
  const toolCalls = [];
8759
9215
  const pendingToolCalls = /* @__PURE__ */ new Map();
8760
9216
  let capturedTokens;
8761
- for await (const event of withSubagentHeartbeat(parseClaudeStream(proc, streamLog, adminModel), streamLog)) {
9217
+ for await (const event of withSubagentHeartbeat(parseClaudeStream(proc, streamLog, adminModel, spawnConvId), streamLog)) {
8762
9218
  if (event.type === "session_init") {
8763
9219
  currentAgentSessionId = event.sessionId;
8764
9220
  if (sessionKey) storeAgentSessionId(sessionKey, event.sessionId);
@@ -8810,7 +9266,7 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
8810
9266
  if (event.type === "usage" && sessionKey && currentAgentSessionId) {
8811
9267
  const peakReqPct = event.peak_request_pct ?? 0;
8812
9268
  if (peakReqPct >= COMPACTION_THRESHOLD) {
8813
- const compactionIter = runCompactionTurn(accountDir, accountId, systemPrompt, currentAgentSessionId, adminModel, enabledPlugins);
9269
+ const compactionIter = runCompactionTurn(accountDir, accountId, systemPrompt, currentAgentSessionId, adminModel, spawnConvId, enabledPlugins);
8814
9270
  let step = await compactionIter.next();
8815
9271
  while (!step.done) {
8816
9272
  yield { type: "status", message: step.value };
@@ -8826,7 +9282,7 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
8826
9282
  console.error(`[profile-reflection] Unhandled error: ${err instanceof Error ? err.message : String(err)}`);
8827
9283
  });
8828
9284
  }
8829
- clearAgentSessionId(sessionKey);
9285
+ clearAgentSessionId(sessionKey, "compaction-complete");
8830
9286
  const convId = sessionStore.get(sessionKey)?.conversationId;
8831
9287
  if (convId) {
8832
9288
  yield { type: "status", message: "Recovering context..." };
@@ -8890,7 +9346,7 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
8890
9346
  } else if (event.type === "subagent_stalled") {
8891
9347
  if (sessionKey) {
8892
9348
  storeStalledSubagent(sessionKey, { name: event.name, task: event.task, prompt: event.prompt, elapsed_ms: event.elapsed_ms, tool_uses: event.tool_uses, total_tokens: event.total_tokens, last_tool_name: event.last_tool_name });
8893
- clearAgentSessionId(sessionKey);
9349
+ clearAgentSessionId(sessionKey, "subagent-stalled");
8894
9350
  }
8895
9351
  const taskPreview = (event.task || "").length > 120 ? event.task.slice(0, 120) + "\u2026" : event.task || "";
8896
9352
  streamLog.write(`[${isoTs()}] [stall-recovery] name=${event.name} task=${JSON.stringify(taskPreview)} \u2014 informing user and closing stream
@@ -8905,7 +9361,7 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
8905
9361
  streamLog.write(`[${isoTs()}] [main-stream-stall-recovery] elapsed=${elapsedSec}s last_event=${event.last_event_type} \u2014 killing process and closing stream
8906
9362
  `);
8907
9363
  proc.kill("SIGTERM");
8908
- if (sessionKey) clearAgentSessionId(sessionKey);
9364
+ if (sessionKey) clearAgentSessionId(sessionKey, "main-stream-stalled");
8909
9365
  yield { type: "text", content: `The response stalled after ${elapsedSec} seconds of silence. Tap Retry to try again.` };
8910
9366
  yield { type: "component", name: "action-buttons", data: { buttons: [{ label: "Retry", value: "Please retry my last request." }] } };
8911
9367
  gotDone = true;
@@ -8915,7 +9371,7 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
8915
9371
  if (event.subtype === "error_max_turns" && event.stop_reason === "tool_use") {
8916
9372
  streamLog.write(`[${isoTs()}] [max-turns-interrupted] subtype=${event.subtype} stop_reason=${event.stop_reason} \u2014 entering recovery
8917
9373
  `);
8918
- if (sessionKey) clearAgentSessionId(sessionKey);
9374
+ if (sessionKey) clearAgentSessionId(sessionKey, "max-turns-interrupted");
8919
9375
  } else {
8920
9376
  gotDone = true;
8921
9377
  if (!sessionWasReset) {
@@ -8951,7 +9407,7 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
8951
9407
  yield event;
8952
9408
  }
8953
9409
  if (!gotDone) {
8954
- if (sessionKey) clearAgentSessionId(sessionKey);
9410
+ if (sessionKey) clearAgentSessionId(sessionKey, "context-overflow-recovery");
8955
9411
  const hasWork = responseText.length > 0 || toolCalls.length > 0;
8956
9412
  if (hasWork && retryCount === 0 && sessionKey) {
8957
9413
  streamLog.write(`[${isoTs()}] [context-overflow-recovery] detected sessionKey=${sessionKey.slice(0, 8)}\u2026 responseTextLen=${responseText.length} toolCallCount=${toolCalls.length}
@@ -8981,6 +9437,10 @@ ${summary}`;
8981
9437
  }
8982
9438
  async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accountId, adminModel, sessionKey, maxTurns = 20, attachments = [], retryCount = 0, enabledPlugins, clientTimestamp) {
8983
9439
  const userTimestamp = clientTimestamp ?? (/* @__PURE__ */ new Date()).toISOString();
9440
+ const managedConvId = getConversationIdForSession(sessionKey);
9441
+ if (!managedConvId) {
9442
+ throw new Error(`invokeManagedAdminAgent: conversationId missing for sessionKey=${sessionKey.slice(0, 8)} \u2014 ensureConversation must run first`);
9443
+ }
8984
9444
  const pendingTrimmed = consumePendingTrimmedMessages(sessionKey);
8985
9445
  if (pendingTrimmed && pendingTrimmed.length > 0) {
8986
9446
  const ok = await compactTrimmedMessages(accountId, pendingTrimmed);
@@ -8988,7 +9448,7 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
8988
9448
  storePendingTrimmedMessages(sessionKey, pendingTrimmed);
8989
9449
  }
8990
9450
  }
8991
- const streamLog = agentLogStream("claude-agent-stream", accountDir);
9451
+ const streamLog = agentLogStream("claude-agent-stream", accountDir, managedConvId);
8992
9452
  streamLog.on("error", () => {
8993
9453
  });
8994
9454
  const systemPromptTokens = estimateTokens(systemPrompt);
@@ -9019,7 +9479,7 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
9019
9479
  if (!cdpOk) streamLog.write(`[${isoTs()}] [warn] ensureCdp failed \u2014 browser-specialist degraded
9020
9480
  `);
9021
9481
  const managedUserId = getUserIdForSession(sessionKey);
9022
- const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, managedUserId, enabledPlugins) });
9482
+ const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, managedConvId, managedUserId, enabledPlugins) });
9023
9483
  const specialistsDir = resolve6(accountDir, "specialists");
9024
9484
  if (!existsSync6(specialistsDir)) streamLog.write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
9025
9485
  `);
@@ -9049,12 +9509,20 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
9049
9509
  const proc = spawn2("claude", args, {
9050
9510
  cwd: accountDir,
9051
9511
  stdio: ["ignore", "pipe", "pipe"],
9052
- env: { ...process.env, PLATFORM_ROOT: PLATFORM_ROOT4, ACCOUNT_DIR: accountDir }
9512
+ env: {
9513
+ ...process.env,
9514
+ PLATFORM_ROOT: PLATFORM_ROOT4,
9515
+ ACCOUNT_DIR: accountDir,
9516
+ // Task 532: tee subprocess network traces into the per-conversation
9517
+ // stream log via the stderr pipe + dual-consume listener.
9518
+ NODE_DEBUG: "http,http2,net,tls,undici,dns"
9519
+ }
9053
9520
  });
9054
- const stderrLog = agentLogStream("claude-agent-stderr", accountDir);
9521
+ const stderrLog = agentLogStream("claude-agent-stderr", accountDir, managedConvId);
9055
9522
  stderrLog.on("error", () => {
9056
9523
  });
9057
9524
  proc.stderr?.pipe(stderrLog);
9525
+ teeProcStderrToStreamLog(proc, streamLog);
9058
9526
  if (sessionKey) {
9059
9527
  const prev = activeProcesses.get(sessionKey);
9060
9528
  if (prev) {
@@ -9063,14 +9531,13 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
9063
9531
  }
9064
9532
  activeProcesses.set(sessionKey, { pid: proc.pid, spawnedAt: Date.now() });
9065
9533
  }
9066
- const managedConvId = sessionKey ? getConversationIdForSession(sessionKey) : void 0;
9067
- streamLog.write(`[${isoTs()}] [managed-spawn] pid=${proc.pid} sessionKey=${sessionKey} conversationId=${managedConvId ?? "none"} historyMessages=${history.length} pluginDir=${specialistsDir}
9534
+ streamLog.write(`[${isoTs()}] [managed-spawn] pid=${proc.pid} sessionKey=${sessionKey} conversationId=${managedConvId} historyMessages=${history.length} pluginDir=${specialistsDir}
9068
9535
  `);
9069
9536
  streamLog.write(`[${isoTs()}] [stdin] len=${fullMessage.length} preview=${JSON.stringify(fullMessage.slice(0, 80))}
9070
9537
  `);
9071
9538
  proc.on("exit", (code, signal) => {
9072
- console.log(`[process-exit] pid=${proc.pid} code=${code} signal=${signal} sessionKey=${sessionKey ?? "none"} conversationId=${managedConvId ?? "none"}`);
9073
- if (!streamLog.destroyed && !streamLog.writableEnded) streamLog.write(`[${isoTs()}] [process-exit] pid=${proc.pid} code=${code} signal=${signal} conversationId=${managedConvId ?? "none"}
9539
+ console.log(`[process-exit] pid=${proc.pid} code=${code} signal=${signal} sessionKey=${sessionKey ?? "none"} conversationId=${managedConvId}`);
9540
+ if (!streamLog.destroyed && !streamLog.writableEnded) streamLog.write(`[${isoTs()}] [process-exit] pid=${proc.pid} code=${code} signal=${signal} conversationId=${managedConvId}
9074
9541
  `);
9075
9542
  if (sessionKey) activeProcesses.delete(sessionKey);
9076
9543
  });
@@ -9086,7 +9553,7 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
9086
9553
  const pendingToolCalls = /* @__PURE__ */ new Map();
9087
9554
  const renderedComponents = [];
9088
9555
  let capturedTokens;
9089
- for await (const event of withSubagentHeartbeat(parseClaudeStream(proc, streamLog, adminModel), streamLog)) {
9556
+ for await (const event of withSubagentHeartbeat(parseClaudeStream(proc, streamLog, adminModel, managedConvId), streamLog)) {
9090
9557
  if (event.type === "text") {
9091
9558
  responseText += event.content;
9092
9559
  } else if (event.type === "usage") {
@@ -9291,7 +9758,11 @@ async function* invokePublicAgent(message, systemPrompt, accountId, accountDir,
9291
9758
  yield { type: "done" };
9292
9759
  return;
9293
9760
  }
9294
- const streamLog = agentLogStream("public-agent-stream", accountDir);
9761
+ const publicConvId = sessionKey ? getConversationIdForSession(sessionKey) : void 0;
9762
+ if (!publicConvId) {
9763
+ throw new Error(`invokePublicAgent: conversationId missing for sessionKey=${sessionKey?.slice(0, 8) ?? "none"} \u2014 ensureConversation must run first`);
9764
+ }
9765
+ const streamLog = agentLogStream("public-agent-stream", accountDir, publicConvId);
9295
9766
  streamLog.write(`[${isoTs()}] [public-user-message] ${JSON.stringify(message)}
9296
9767
  `);
9297
9768
  if (sessionKey) {
@@ -9564,6 +10035,8 @@ async function* compactSession(sessionKey) {
9564
10035
  }
9565
10036
  const currentSessionId = getAgentSessionId(sessionKey);
9566
10037
  if (!currentSessionId) return { ok: false, reason: "no-session" };
10038
+ const compactionConvId = getConversationIdForSession(sessionKey);
10039
+ if (!compactionConvId) return { ok: false, reason: "no-conversation" };
9567
10040
  const identity = readIdentity(account.accountDir, "admin");
9568
10041
  const defaultSystemPrompt = "You are an admin agent. Full access. Professional, concise. British English. Be proactive \u2014 tell the user what needs doing and do it.";
9569
10042
  const baseSystemPrompt = identity ?? defaultSystemPrompt;
@@ -9571,7 +10044,7 @@ async function* compactSession(sessionKey) {
9571
10044
  const systemPrompt = outputStyle === "explanatory" ? `${baseSystemPrompt}
9572
10045
 
9573
10046
  ${EXPLANATORY_STYLE_INSTRUCTIONS}` : baseSystemPrompt;
9574
- const compactionIter = runCompactionTurn(account.accountDir, account.accountId, systemPrompt, currentSessionId, account.config.adminModel, account.config.enabledPlugins);
10047
+ const compactionIter = runCompactionTurn(account.accountDir, account.accountId, systemPrompt, currentSessionId, account.config.adminModel, compactionConvId, account.config.enabledPlugins);
9575
10048
  let step = await compactionIter.next();
9576
10049
  while (!step.done) {
9577
10050
  yield step.value;
@@ -9588,7 +10061,7 @@ ${EXPLANATORY_STYLE_INSTRUCTIONS}` : baseSystemPrompt;
9588
10061
  console.error(`[profile-reflection] Unhandled error: ${err instanceof Error ? err.message : String(err)}`);
9589
10062
  });
9590
10063
  }
9591
- clearAgentSessionId(sessionKey);
10064
+ clearAgentSessionId(sessionKey, "session-compact-complete");
9592
10065
  return { ok: step.value.ok };
9593
10066
  }
9594
10067
  async function* invokeAgent(config2, message, sessionKey, attachments = [], userTimestamp, gatewayResult) {
@@ -9658,8 +10131,9 @@ $ACCOUNT_DIR is ${account.accountDir}
9658
10131
  ${profileSummary}`;
9659
10132
  }
9660
10133
  }
10134
+ const invokeConvId = sessionKey ? getConversationIdForSession(sessionKey) : void 0;
9661
10135
  const [sessionContext, onboardingStep] = await Promise.all([
9662
- loadSessionContext(accountId),
10136
+ loadSessionContext(accountId, invokeConvId),
9663
10137
  loadOnboardingStep(accountId)
9664
10138
  ]);
9665
10139
  if (sessionContext) {
@@ -9874,7 +10348,7 @@ ${block}`;
9874
10348
  import { basename as basename2 } from "path";
9875
10349
 
9876
10350
  // app/lib/review-detector/rules.ts
9877
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync7, statSync as statSync3, mkdirSync as mkdirSync5, renameSync } from "fs";
10351
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync7, statSync as statSync4, mkdirSync as mkdirSync5, renameSync } from "fs";
9878
10352
  import { resolve as resolve7, dirname as dirname2 } from "path";
9879
10353
  var DEFAULT_SCAN_INTERVAL_MS = 5e3;
9880
10354
  var RATE_LIMIT_PATTERN = "rate[- ]?limit(?:ed| reached| hit)|(?:HTTP|status)[^a-z]{0,3}429|too many requests";
@@ -9898,6 +10372,7 @@ var VALID_SOURCES = /* @__PURE__ */ new Set([
9898
10372
  "mcp",
9899
10373
  "config-dir"
9900
10374
  ]);
10375
+ var VALID_SCOPES = /* @__PURE__ */ new Set(["global", "session"]);
9901
10376
  function defaultRules() {
9902
10377
  return [
9903
10378
  {
@@ -9972,6 +10447,52 @@ function defaultRules() {
9972
10447
  thresholdCount: 0,
9973
10448
  thresholdWindowMinutes: 0,
9974
10449
  suggestedAction: "An external-facing tool executed with auto-executed approval state. Check account.json approvalPolicy \u2014 if this tool should require review, the gating hook may have been bypassed or the policy was changed."
10450
+ },
10451
+ {
10452
+ // Task 530: catches the bridgeai-style class where a single conversation
10453
+ // sees the same tool error repeatedly and the agent silently falls back.
10454
+ // Session-scoped so cross-conversation coincidence doesn't trigger it.
10455
+ id: "tool-result-recurring-errors",
10456
+ name: "Tool result errors recurring in a conversation",
10457
+ type: "repeated-error",
10458
+ logSource: "system",
10459
+ pattern: "\\[tool-result\\].*error=true",
10460
+ thresholdCount: 2,
10461
+ thresholdWindowMinutes: 5,
10462
+ scope: "session",
10463
+ suggestedAction: 'The same conversation has logged multiple tool failures. Use the admin `logs-read` MCP tool with `type: "system"` (or the `logs-read.sh` script with the conversationId) to retrieve the adjacent [tool-failure-diag] lines and identify whether the cause is DNS, TCP, HTTP, or the tool\'s internal pipeline \u2014 then adapt the next attempt to match. Never retry the same tool against the same target without diagnostic-grounded reasoning.'
10464
+ },
10465
+ {
10466
+ // Task 532: closes the "60-second black-box tool wait" class. Fires when
10467
+ // a tool hits the 30-second mark of the mid-flight heartbeat. The
10468
+ // pattern anchors on elapsed=30s specifically (the tool-wait tick emits
10469
+ // one line per 5s per tool, so matching every tick would be noisy) and
10470
+ // excludes tools whose long runtime is expected: `Task`/`Agent` subagent
10471
+ // dispatch, `Bash` with explicit long timeouts.
10472
+ id: "tool-wait-long-stall",
10473
+ name: "Tool wait exceeds 30 seconds (possible stall)",
10474
+ type: "repeated-error",
10475
+ logSource: "system",
10476
+ pattern: "\\[tool-wait\\][^\\n]*name=(?!Task\\b|Agent\\b|Bash\\b)[A-Za-z0-9_]+[^\\n]*elapsed=30s",
10477
+ thresholdCount: 0,
10478
+ thresholdWindowMinutes: 0,
10479
+ scope: "session",
10480
+ suggestedAction: "A tool call has been pending for 30 seconds without a result. Read the adjacent [tool-wait-diag] and [tool-wait-proc] lines in the conversation's stream log to determine whether the network remained healthy, the subprocess held active sockets, and the HTTP request reached the wire. If diag shows a healthy network but the subprocess has no [subproc-stderr] UNDICI/HTTP activity during the wait window, the tool's internal pipeline is stalled \u2014 do not retry the same request against the same target without a change in approach."
10481
+ },
10482
+ {
10483
+ // Task 533: surface every Cloudflare-plugin refusal. The plugin emits
10484
+ // exactly one [cloudflare:refuse] line per refusal with a structured
10485
+ // reason field; any single occurrence on a previously-clean device
10486
+ // means scope, account-binding, or post-flight FQDN drift — the
10487
+ // operator should run cf-verify before acting.
10488
+ id: "cloudflare-refuse",
10489
+ name: "Cloudflare plugin refusal",
10490
+ type: "silent-catch",
10491
+ logSource: "any",
10492
+ pattern: "\\[cloudflare:refuse\\]|\\[cloudflare:post-flight-mismatch\\]",
10493
+ thresholdCount: 0,
10494
+ thresholdWindowMinutes: 0,
10495
+ suggestedAction: "The Cloudflare plugin refused an operation or detected a post-flight FQDN mismatch. Run `cf-verify` to inspect current device state. If the reason is `account-drift` or `unbound-device`, run `tunnel-login force=true` to clear cert + binding and re-authenticate. If the reason is `scope-mismatch`, the requested hostname is outside the brand's declared zones \u2014 fix at brand.json + republish. If `post-flight-fqdn-mismatch`, run `cf-rebuild` to reconcile."
9975
10496
  }
9976
10497
  ];
9977
10498
  }
@@ -10006,7 +10527,7 @@ function loadRules(configDir2) {
10006
10527
  function rulesFileMtime(configDir2) {
10007
10528
  const path2 = rulesFilePath(configDir2);
10008
10529
  try {
10009
- return statSync3(path2).mtimeMs;
10530
+ return statSync4(path2).mtimeMs;
10010
10531
  } catch {
10011
10532
  return null;
10012
10533
  }
@@ -10117,6 +10638,12 @@ function validateRule(input, label, seenIds) {
10117
10638
  if (typeof r.watchPath === "string") rule.watchPath = r.watchPath;
10118
10639
  if (typeof r.staleHours === "number") rule.staleHours = r.staleHours;
10119
10640
  if (typeof r.suppressedUntil === "string") rule.suppressedUntil = r.suppressedUntil;
10641
+ if (r.scope !== void 0) {
10642
+ if (typeof r.scope !== "string" || !VALID_SCOPES.has(r.scope)) {
10643
+ throw new Error(`${label}: scope must be one of ${[...VALID_SCOPES].join(", ")}`);
10644
+ }
10645
+ rule.scope = r.scope;
10646
+ }
10120
10647
  if (rule.type === "file-write-storm" || rule.type === "stale-log") {
10121
10648
  if (!rule.watchPath) {
10122
10649
  throw new Error(`${label}: ${rule.type} rules require watchPath`);
@@ -10131,7 +10658,7 @@ function validateRule(input, label, seenIds) {
10131
10658
  }
10132
10659
 
10133
10660
  // app/lib/review-detector/sources.ts
10134
- import { existsSync as existsSync8, readdirSync as readdirSync3, statSync as statSync4, writeFileSync as writeFileSync7, renameSync as renameSync2, mkdirSync as mkdirSync6, openSync, readSync, closeSync, readFileSync as readFileSync9 } from "fs";
10661
+ 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";
10135
10662
  import { resolve as resolve8, join as join5, basename, dirname as dirname3 } from "path";
10136
10663
  function tailStatePath(configDir2) {
10137
10664
  return resolve8(configDir2, "review-state.json");
@@ -10181,12 +10708,31 @@ function discoverSourceFiles(configDir2, accountLogDir2, logicalSource) {
10181
10708
  }[logicalSource];
10182
10709
  if (!existsSync8(accountLogDir2)) return [];
10183
10710
  const files = [];
10711
+ let scanned = 0;
10712
+ let skippedPrefixMismatch = 0;
10713
+ let skippedNotLog = 0;
10184
10714
  for (const entry of readdirSync3(accountLogDir2)) {
10185
- if (entry.startsWith(prefix) && entry.endsWith(".log")) {
10715
+ scanned += 1;
10716
+ const matchesPrefix = entry.startsWith(prefix);
10717
+ const isLog = entry.endsWith(".log");
10718
+ if (matchesPrefix && isLog) {
10186
10719
  files.push({ logicalSource, filepath: join5(accountLogDir2, entry) });
10720
+ } else if (!matchesPrefix) {
10721
+ skippedPrefixMismatch += 1;
10722
+ } else {
10723
+ skippedNotLog += 1;
10724
+ }
10725
+ }
10726
+ files.sort((a, b) => {
10727
+ try {
10728
+ return statSync5(b.filepath).mtimeMs - statSync5(a.filepath).mtimeMs;
10729
+ } catch {
10730
+ return a.filepath.localeCompare(b.filepath);
10187
10731
  }
10732
+ });
10733
+ if (skippedPrefixMismatch > 0 || skippedNotLog > 0) {
10734
+ console.error(`[review-scan-skip] dir=${accountLogDir2} source=${logicalSource} prefix=${prefix} scanned=${scanned} matched=${files.length} skipped_prefix=${skippedPrefixMismatch} skipped_non_log=${skippedNotLog}`);
10188
10735
  }
10189
- files.sort((a, b) => a.filepath.localeCompare(b.filepath));
10190
10736
  return files;
10191
10737
  }
10192
10738
  function discoverAllSources(configDir2, accountLogDir2) {
@@ -10202,7 +10748,7 @@ function discoverAllSources(configDir2, accountLogDir2) {
10202
10748
  }
10203
10749
  function readNewLines(filepath, prev) {
10204
10750
  if (!existsSync8(filepath)) return null;
10205
- const stat4 = statSync4(filepath);
10751
+ const stat4 = statSync5(filepath);
10206
10752
  const size = stat4.size;
10207
10753
  const inode = stat4.ino;
10208
10754
  let startOffset = 0;
@@ -10227,12 +10773,12 @@ function readNewLines(filepath, prev) {
10227
10773
  truncated
10228
10774
  };
10229
10775
  }
10230
- const fd = openSync(filepath, "r");
10776
+ const fd = openSync2(filepath, "r");
10231
10777
  try {
10232
10778
  const bufSize = Math.max(0, size - startOffset);
10233
10779
  const buf = Buffer.alloc(bufSize);
10234
10780
  if (bufSize > 0) {
10235
- readSync(fd, buf, 0, bufSize, startOffset);
10781
+ readSync2(fd, buf, 0, bufSize, startOffset);
10236
10782
  }
10237
10783
  const text = buf.toString("utf-8");
10238
10784
  const lines = text.length > 0 ? text.split("\n") : [];
@@ -10250,7 +10796,7 @@ function readNewLines(filepath, prev) {
10250
10796
  truncated
10251
10797
  };
10252
10798
  } finally {
10253
- closeSync(fd);
10799
+ closeSync2(fd);
10254
10800
  }
10255
10801
  }
10256
10802
  function countRecentWrites(dir, sinceMs) {
@@ -10259,7 +10805,7 @@ function countRecentWrites(dir, sinceMs) {
10259
10805
  for (const entry of readdirSync3(dir, { withFileTypes: true })) {
10260
10806
  if (!entry.isFile()) continue;
10261
10807
  try {
10262
- const st = statSync4(join5(dir, entry.name));
10808
+ const st = statSync5(join5(dir, entry.name));
10263
10809
  if (st.mtimeMs >= sinceMs) count += 1;
10264
10810
  } catch {
10265
10811
  }
@@ -10269,7 +10815,7 @@ function countRecentWrites(dir, sinceMs) {
10269
10815
  function fileLastWriteMs(path2) {
10270
10816
  if (!existsSync8(path2)) return null;
10271
10817
  try {
10272
- return statSync4(path2).mtimeMs;
10818
+ return statSync5(path2).mtimeMs;
10273
10819
  } catch {
10274
10820
  return null;
10275
10821
  }
@@ -10282,7 +10828,7 @@ function sourceKey(file2) {
10282
10828
  }
10283
10829
 
10284
10830
  // app/lib/review-detector/writer.ts
10285
- import { appendFileSync as appendFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync8, renameSync as renameSync3, statSync as statSync5 } from "fs";
10831
+ import { appendFileSync as appendFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync8, renameSync as renameSync3, statSync as statSync6 } from "fs";
10286
10832
  import { resolve as resolve9, dirname as dirname4 } from "path";
10287
10833
  import { randomUUID as randomUUID3 } from "crypto";
10288
10834
  function reviewLogPath(configDir2) {
@@ -10548,6 +11094,12 @@ import { resolve as resolve10 } from "path";
10548
11094
 
10549
11095
  // app/lib/review-detector/evaluator.ts
10550
11096
  var SAMPLE_MAX_CHARS = 500;
11097
+ var CONV_ID_REGEX = /conversationId=([a-f0-9]{8})/;
11098
+ function scopeKeyFor(rule, line) {
11099
+ if (rule.scope !== "session") return "";
11100
+ const m = CONV_ID_REGEX.exec(line);
11101
+ return m ? m[1] : "";
11102
+ }
10551
11103
  var compiledRegexCache = /* @__PURE__ */ new Map();
10552
11104
  function compileRegex(pattern) {
10553
11105
  let re = compiledRegexCache.get(pattern);
@@ -10568,31 +11120,60 @@ function toSample(line) {
10568
11120
  return line.slice(0, SAMPLE_MAX_CHARS) + "\u2026";
10569
11121
  }
10570
11122
  function newRuleState() {
10571
- return { matchTimestamps: [], lastAlertAt: null, cumulativeSinceLastAlert: 0, lastSeenAt: null };
11123
+ return {
11124
+ matchTimestamps: [],
11125
+ matchTimestampsByScope: /* @__PURE__ */ new Map(),
11126
+ lastAlertAt: null,
11127
+ cumulativeSinceLastAlert: 0,
11128
+ lastSeenAt: null
11129
+ };
10572
11130
  }
10573
11131
  function evaluateTextRule(rule, lines, state, nowMs) {
10574
11132
  if (isSuppressed(rule, nowMs)) return { match: null, state };
10575
11133
  if (rule.pattern.length === 0) return { match: null, state };
10576
11134
  const regex = compileRegex(rule.pattern);
10577
- let sample = null;
10578
- let matchCount = 0;
11135
+ const matchesByScope = /* @__PURE__ */ new Map();
11136
+ let firstSample = null;
10579
11137
  for (const line of lines) {
10580
- if (regex.test(line)) {
10581
- matchCount += 1;
10582
- if (!sample) sample = line;
10583
- }
10584
- }
10585
- if (matchCount === 0) return { match: null, state };
11138
+ if (!regex.test(line)) continue;
11139
+ if (!firstSample) firstSample = line;
11140
+ const key = scopeKeyFor(rule, line);
11141
+ const existing = matchesByScope.get(key) ?? [];
11142
+ existing.push(line);
11143
+ matchesByScope.set(key, existing);
11144
+ }
11145
+ if (matchesByScope.size === 0) return { match: null, state };
11146
+ const windowStart = rule.thresholdWindowMinutes > 0 ? nowMs - rule.thresholdWindowMinutes * 6e4 : -Infinity;
10586
11147
  const updated = {
10587
11148
  ...state,
10588
- matchTimestamps: [...state.matchTimestamps]
10589
- };
10590
- for (let i = 0; i < matchCount; i++) updated.matchTimestamps.push(nowMs);
10591
- if (rule.thresholdWindowMinutes > 0) {
10592
- const windowStart = nowMs - rule.thresholdWindowMinutes * 6e4;
11149
+ matchTimestamps: [...state.matchTimestamps],
11150
+ matchTimestampsByScope: new Map(state.matchTimestampsByScope ?? [])
11151
+ };
11152
+ let firingSample = null;
11153
+ let fires = false;
11154
+ const isCountZero = rule.thresholdCount === 0;
11155
+ if (rule.scope === "session") {
11156
+ for (const [key, hits] of matchesByScope) {
11157
+ const prior = updated.matchTimestampsByScope.get(key) ?? [];
11158
+ const merged = [...prior, ...hits.map(() => nowMs)].filter((t) => t >= windowStart);
11159
+ if (merged.length === 0) {
11160
+ updated.matchTimestampsByScope.delete(key);
11161
+ } else {
11162
+ updated.matchTimestampsByScope.set(key, merged);
11163
+ }
11164
+ if (!fires && (isCountZero || merged.length >= rule.thresholdCount)) {
11165
+ fires = true;
11166
+ firingSample = hits[0];
11167
+ }
11168
+ }
11169
+ } else {
11170
+ for (const hits of matchesByScope.values()) {
11171
+ for (const _ of hits) updated.matchTimestamps.push(nowMs);
11172
+ }
10593
11173
  updated.matchTimestamps = updated.matchTimestamps.filter((t) => t >= windowStart);
11174
+ fires = isCountZero || updated.matchTimestamps.length >= rule.thresholdCount;
11175
+ if (fires) firingSample = firstSample;
10594
11176
  }
10595
- const fires = rule.thresholdCount === 0 ? true : updated.matchTimestamps.length >= rule.thresholdCount;
10596
11177
  if (!fires) {
10597
11178
  return { match: null, state: updated };
10598
11179
  }
@@ -10600,7 +11181,7 @@ function evaluateTextRule(rule, lines, state, nowMs) {
10600
11181
  ruleId: rule.id,
10601
11182
  ruleName: rule.name,
10602
11183
  matchedAt: nowMs,
10603
- sampleEvidence: toSample(sample ?? lines[0] ?? ""),
11184
+ sampleEvidence: toSample(firingSample ?? firstSample ?? lines[0] ?? ""),
10604
11185
  suggestedAction: rule.suggestedAction
10605
11186
  };
10606
11187
  return { match: match2, state: updated };
@@ -28035,8 +28616,9 @@ async function POST2(req) {
28035
28616
  { status: 500, headers: { "Content-Type": "application/json" } }
28036
28617
  );
28037
28618
  }
28038
- const sseLog = agentLogStream("sse-events", account.accountDir);
28039
- const sk = getConversationIdForSession(session_key)?.slice(0, 8) ?? session_key.slice(0, 8);
28619
+ const publicSseConvId = getConversationIdForSession(session_key);
28620
+ const sseLog = publicSseConvId ? agentLogStream("sse-events", account.accountDir, publicSseConvId) : preConversationLogStream("sse-events", account.accountDir);
28621
+ const sk = publicSseConvId?.slice(0, 8) ?? session_key.slice(0, 8);
28040
28622
  const agentName = getAgentNameForSession(session_key);
28041
28623
  if (!agentName) {
28042
28624
  console.log(`[chat] no agent for session=${sk} \u2014 session expired or server restarted`);
@@ -29925,7 +30507,7 @@ async function POST16(req) {
29925
30507
 
29926
30508
  // app/api/onboarding/claude-auth/route.ts
29927
30509
  import { spawn as spawn3, execFileSync as execFileSync2 } from "child_process";
29928
- import { openSync as openSync2, closeSync as closeSync2, writeFileSync as writeFileSync12, writeSync } from "fs";
30510
+ import { openSync as openSync3, closeSync as closeSync3, writeFileSync as writeFileSync12, writeSync } from "fs";
29929
30511
  function checkAuthStatus() {
29930
30512
  try {
29931
30513
  execFileSync2("claude", ["auth", "status"], { encoding: "utf-8", timeout: 5e3 });
@@ -29996,7 +30578,7 @@ async function POST17(req, remoteAddress) {
29996
30578
  const chromiumWrapper = writeChromiumWrapper();
29997
30579
  const x11Env = buildX11Env(chromiumWrapper, transport);
29998
30580
  vncLog("claude-auth", { action: "start", transport });
29999
- const claudeAuthLogFd = openSync2(logPath("claude-auth"), "a");
30581
+ const claudeAuthLogFd = openSync3(logPath("claude-auth"), "a");
30000
30582
  const claudeProc = spawn3("claude", ["auth", "login"], {
30001
30583
  env: x11Env,
30002
30584
  stdio: ["ignore", "pipe", "pipe"]
@@ -30005,7 +30587,7 @@ async function POST17(req, remoteAddress) {
30005
30587
  const onClaudeOutput = (chunk) => writeSync(claudeAuthLogFd, chunk);
30006
30588
  claudeProc.stdout?.on("data", onClaudeOutput);
30007
30589
  claudeProc.stderr?.on("data", onClaudeOutput);
30008
- claudeProc.once("close", () => closeSync2(claudeAuthLogFd));
30590
+ claudeProc.once("close", () => closeSync3(claudeAuthLogFd));
30009
30591
  await waitForAuthPage(2e4);
30010
30592
  return Response.json({ started: true, transport });
30011
30593
  }
@@ -30411,7 +30993,8 @@ async function POST21(req) {
30411
30993
  try {
30412
30994
  const parsed = JSON.parse(message);
30413
30995
  if (parsed._lifecycle) {
30414
- const lifecycleLog = agentLogStream("component-lifecycle", account.accountDir);
30996
+ const lifecycleConvId = getConversationIdForSession(session_key);
30997
+ const lifecycleLog = lifecycleConvId ? agentLogStream("component-lifecycle", account.accountDir, lifecycleConvId) : preConversationLogStream("component-lifecycle", account.accountDir);
30415
30998
  const ts = (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
30416
30999
  const detail = parsed.filePath ? ` filePath=${parsed.filePath}` : "";
30417
31000
  lifecycleLog.write(`[${ts}] [component:${parsed.component ?? "unknown"}] ${parsed.event ?? "unknown"}${detail}
@@ -30435,7 +31018,8 @@ async function POST21(req) {
30435
31018
  try {
30436
31019
  const parsed = JSON.parse(message);
30437
31020
  if (isComponentDone(parsed)) {
30438
- const componentLog = agentLogStream("component-lifecycle", account.accountDir);
31021
+ const componentConvId = getConversationIdForSession(session_key);
31022
+ const componentLog = componentConvId ? agentLogStream("component-lifecycle", account.accountDir, componentConvId) : preConversationLogStream("component-lifecycle", account.accountDir);
30439
31023
  const ts = (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
30440
31024
  message = transformComponentDone(parsed);
30441
31025
  componentLog.write(`[${ts}] [component:${parsed.component}] componentDone \u2192 transformed
@@ -30463,8 +31047,9 @@ async function POST21(req) {
30463
31047
  gatewayResult = await processInbound(message, "web-admin");
30464
31048
  }
30465
31049
  const encoder = new TextEncoder();
30466
- const sseLog = agentLogStream("sse-events", account.accountDir);
30467
- const sk = getConversationIdForSession(session_key)?.slice(0, 8) ?? session_key.slice(0, 8);
31050
+ const sseConvId = getConversationIdForSession(session_key);
31051
+ const sseLog = sseConvId ? agentLogStream("sse-events", account.accountDir, sseConvId) : preConversationLogStream("sse-events", account.accountDir);
31052
+ const sk = sseConvId?.slice(0, 8) ?? session_key.slice(0, 8);
30468
31053
  const readable = new ReadableStream({
30469
31054
  async start(controller) {
30470
31055
  try {
@@ -30573,13 +31158,14 @@ async function POST22(req) {
30573
31158
  }
30574
31159
 
30575
31160
  // app/api/admin/logs/route.ts
30576
- import { existsSync as existsSync19, readdirSync as readdirSync5, readFileSync as readFileSync20, statSync as statSync6 } from "fs";
31161
+ import { existsSync as existsSync19, readdirSync as readdirSync5, readFileSync as readFileSync20, statSync as statSync7 } from "fs";
30577
31162
  import { resolve as resolve19, basename as basename5 } from "path";
30578
31163
  var TAIL_BYTES = 8192;
30579
31164
  async function GET9(request) {
30580
31165
  const { searchParams } = new URL(request.url);
30581
31166
  const fileParam = searchParams.get("file");
30582
31167
  const typeParam = searchParams.get("type");
31168
+ const conversationIdParam = searchParams.get("conversationId");
30583
31169
  const download = searchParams.get("download") === "1";
30584
31170
  const account = resolveAccount();
30585
31171
  const accountLogDir2 = account ? resolve19(account.accountDir, "logs") : null;
@@ -30599,18 +31185,21 @@ async function GET9(request) {
30599
31185
  return Response.json({ error: `File not found: ${safe}` }, { status: 404 });
30600
31186
  }
30601
31187
  if (typeParam) {
30602
- const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
30603
- const typeMap = {
30604
- stream: `claude-agent-stream-${today}.log`,
30605
- error: `claude-agent-stderr-${today}.log`,
30606
- session: `sse-events-${today}.log`,
30607
- sse: `sse-events-${today}.log`,
30608
- public: `public-agent-stream-${today}.log`
31188
+ const prefixMap = {
31189
+ stream: "claude-agent-stream",
31190
+ error: "claude-agent-stderr",
31191
+ session: "sse-events",
31192
+ sse: "sse-events",
31193
+ public: "public-agent-stream"
30609
31194
  };
30610
- const fileName = typeMap[typeParam];
30611
- if (!fileName) {
31195
+ const prefix = prefixMap[typeParam];
31196
+ if (!prefix) {
30612
31197
  return Response.json({ error: `Unknown type: ${typeParam}. Valid: stream, error, session, sse, public` }, { status: 400 });
30613
31198
  }
31199
+ if (!conversationIdParam) {
31200
+ return Response.json({ error: `type=${typeParam} requires conversationId (per-conversation log files, no daily fallback)` }, { status: 400 });
31201
+ }
31202
+ const fileName = `${prefix}-${conversationIdParam}.log`;
30614
31203
  for (const dir of [accountLogDir2, LOG_DIR]) {
30615
31204
  if (!dir) continue;
30616
31205
  const filePath = resolve19(dir, fileName);
@@ -30634,7 +31223,7 @@ async function GET9(request) {
30634
31223
  } catch {
30635
31224
  continue;
30636
31225
  }
30637
- files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync6(resolve19(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).forEach(({ name }) => {
31226
+ 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 }) => {
30638
31227
  seen.add(name);
30639
31228
  try {
30640
31229
  const content = readFileSync20(resolve19(dir, name));
@@ -30894,7 +31483,7 @@ async function GET13() {
30894
31483
 
30895
31484
  // app/api/admin/version/upgrade/route.ts
30896
31485
  import { spawn as spawn4 } from "child_process";
30897
- import { existsSync as existsSync24, statSync as statSync7, writeFileSync as writeFileSync16, readFileSync as readFileSync24, openSync as openSync3, closeSync as closeSync3 } from "fs";
31486
+ import { existsSync as existsSync24, statSync as statSync8, writeFileSync as writeFileSync16, readFileSync as readFileSync24, openSync as openSync4, closeSync as closeSync4 } from "fs";
30898
31487
  import { resolve as resolve25, join as join11 } from "path";
30899
31488
  var PLATFORM_ROOT10 = process.env.MAXY_PLATFORM_ROOT ?? resolve25(process.cwd(), "..");
30900
31489
  var upgradePkg = "@rubytech/create-maxy";
@@ -30914,7 +31503,7 @@ var LOCK_MAX_AGE_MS = 20 * 60 * 1e3;
30914
31503
  function isLockFresh() {
30915
31504
  if (!existsSync24(LOCK_FILE)) return false;
30916
31505
  try {
30917
- const stat4 = statSync7(LOCK_FILE);
31506
+ const stat4 = statSync8(LOCK_FILE);
30918
31507
  return Date.now() - stat4.mtimeMs < LOCK_MAX_AGE_MS;
30919
31508
  } catch {
30920
31509
  return false;
@@ -30943,7 +31532,7 @@ async function POST23(req) {
30943
31532
  console.error("[admin/version/upgrade] failed to write lock file:", err);
30944
31533
  }
30945
31534
  try {
30946
- const logFd = openSync3(LOG_FILE, "w");
31535
+ const logFd = openSync4(LOG_FILE, "w");
30947
31536
  const child = spawn4("systemd-run", [
30948
31537
  "--user",
30949
31538
  "--scope",
@@ -30959,7 +31548,7 @@ async function POST23(req) {
30959
31548
  cwd: resolve25(process.cwd(), "..")
30960
31549
  });
30961
31550
  child.unref();
30962
- closeSync3(logFd);
31551
+ closeSync4(logFd);
30963
31552
  console.log(`[admin/version/upgrade] spawned upgrade process (pid ${child.pid})`);
30964
31553
  return Response.json({ ok: true, started: true });
30965
31554
  } catch (err) {
@@ -31316,7 +31905,7 @@ var brandLoginOpts = {
31316
31905
  bodyFont: BRAND.defaultFonts?.body,
31317
31906
  logoContainsName: !!BRAND.logoContainsName
31318
31907
  };
31319
- var ALIAS_DOMAINS_PATH = join13(homedir3(), BRAND.configDir, "alias-domains.json");
31908
+ var ALIAS_DOMAINS_PATH = join13(homedir4(), BRAND.configDir, "alias-domains.json");
31320
31909
  function loadAliasDomains() {
31321
31910
  try {
31322
31911
  if (!existsSync26(ALIAS_DOMAINS_PATH)) return null;
@@ -31809,7 +32398,7 @@ function cachedHtml(file2) {
31809
32398
  }
31810
32399
  var brandedHtmlCache = /* @__PURE__ */ new Map();
31811
32400
  function loadBrandingCache(agentSlug) {
31812
- const configDir2 = join13(homedir3(), BRAND.configDir);
32401
+ const configDir2 = join13(homedir4(), BRAND.configDir);
31813
32402
  try {
31814
32403
  const accountJsonPath = join13(configDir2, "account.json");
31815
32404
  if (!existsSync26(accountJsonPath)) return null;
@@ -31825,7 +32414,7 @@ function loadBrandingCache(agentSlug) {
31825
32414
  }
31826
32415
  function resolveDefaultSlug() {
31827
32416
  try {
31828
- const configDir2 = join13(homedir3(), BRAND.configDir);
32417
+ const configDir2 = join13(homedir4(), BRAND.configDir);
31829
32418
  const accountJsonPath = join13(configDir2, "account.json");
31830
32419
  if (!existsSync26(accountJsonPath)) return null;
31831
32420
  const account = JSON.parse(readFileSync26(accountJsonPath, "utf-8"));