@rubytech/create-realagent 1.0.615 → 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 (32) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/config/brand.json +4 -0
  3. package/payload/platform/lib/mcp-stderr-tee/dist/index.d.ts +23 -13
  4. package/payload/platform/lib/mcp-stderr-tee/dist/index.d.ts.map +1 -1
  5. package/payload/platform/lib/mcp-stderr-tee/dist/index.js +86 -89
  6. package/payload/platform/lib/mcp-stderr-tee/dist/index.js.map +1 -1
  7. package/payload/platform/lib/mcp-stderr-tee/src/index.ts +86 -101
  8. package/payload/platform/plugins/admin/mcp/dist/index.js +33 -2
  9. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  10. package/payload/platform/plugins/admin/skills/stream-log-review/SKILL.md +22 -8
  11. package/payload/platform/plugins/cloudflare/PLUGIN.md +5 -4
  12. package/payload/platform/plugins/cloudflare/mcp/__tests__/auth-binding.test.ts +196 -0
  13. package/payload/platform/plugins/cloudflare/mcp/__tests__/brand-load.test.ts +81 -0
  14. package/payload/platform/plugins/cloudflare/mcp/__tests__/manifest-scope.test.ts +65 -0
  15. package/payload/platform/plugins/cloudflare/mcp/__tests__/verify-scenario-0.test.ts +70 -0
  16. package/payload/platform/plugins/cloudflare/mcp/__tests__/verify-scenario-B.test.ts +124 -0
  17. package/payload/platform/plugins/cloudflare/mcp/dist/index.js +221 -200
  18. package/payload/platform/plugins/cloudflare/mcp/dist/index.js.map +1 -1
  19. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts +174 -39
  20. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts.map +1 -1
  21. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js +891 -194
  22. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js.map +1 -1
  23. package/payload/platform/plugins/cloudflare/mcp/package.json +5 -2
  24. package/payload/platform/plugins/cloudflare/mcp/vitest.config.ts +10 -0
  25. package/payload/platform/plugins/cloudflare/references/setup-guide.md +31 -32
  26. package/payload/platform/plugins/cloudflare/skills/setup-tunnel/SKILL.md +25 -3
  27. package/payload/platform/plugins/docs/PLUGIN.md +2 -0
  28. package/payload/platform/plugins/docs/references/cloudflare.md +68 -0
  29. package/payload/platform/plugins/docs/references/plugins-guide.md +8 -6
  30. package/payload/platform/scripts/logs-read.sh +114 -54
  31. package/payload/platform/templates/specialists/agents/personal-assistant.md +12 -8
  32. package/payload/server/server.js +387 -71
@@ -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,12 +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 { platform as osPlatform } from "os";
4168
4169
  import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, readdirSync as readdirSync2, existsSync as existsSync6, mkdirSync as mkdirSync4, createWriteStream, statSync as statSync3, unlinkSync, cpSync, rmSync } from "fs";
4169
4170
  import { lookup as dnsLookup } from "dns/promises";
4170
4171
  import { createConnection as netConnect } from "net";
4172
+ import { StringDecoder } from "string_decoder";
4171
4173
 
4172
4174
  // ../lib/models/src/index.ts
4173
4175
  var OPUS_MODEL = "claude-opus-4-7";
@@ -5463,9 +5465,11 @@ function readRecentToolFailures(accountId, conversationId) {
5463
5465
  try {
5464
5466
  const platformRoot3 = process.env.MAXY_PLATFORM_ROOT ?? resolve5(process.cwd(), "..");
5465
5467
  const logDir = resolve5(platformRoot3, "..", "data/accounts", accountId, "logs");
5466
- const date5 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5467
- const logPath2 = resolve5(logDir, `claude-agent-stream-${date5}.log`);
5468
- if (!existsSync5(logPath2)) return [];
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
+ }
5469
5473
  const st = statSync2(logPath2);
5470
5474
  const size = st.size;
5471
5475
  const readBytes = Math.min(size, RECENT_FAILURES_TAIL_BYTES);
@@ -5475,12 +5479,11 @@ function readRecentToolFailures(accountId, conversationId) {
5475
5479
  const buf = Buffer.alloc(readBytes);
5476
5480
  readSync(fd, buf, 0, readBytes, position);
5477
5481
  const text = buf.toString("utf-8");
5478
- const convPrefix = conversationId.slice(0, 8);
5479
5482
  const lines = text.split("\n");
5480
5483
  const matches = [];
5481
5484
  for (let i = lines.length - 1; i >= 0 && matches.length < MAX_RECENT_TOOL_FAILURES; i--) {
5482
5485
  const line = lines[i];
5483
- if (line.includes("[tool-failure-diag]") && line.includes(`conversationId=${convPrefix}`)) {
5486
+ if (line.includes("[tool-failure-diag]")) {
5484
5487
  matches.push(line);
5485
5488
  }
5486
5489
  }
@@ -6170,20 +6173,137 @@ async function runFailureDiagnostic(toolName, toolInput) {
6170
6173
  const fields = capped.map((r) => r.status === "fulfilled" ? r.value : `probe_err=${quoteDiag(String(r.reason).slice(0, 40))}`).join(" ");
6171
6174
  return `diag_host=${host} diag_port=${port2} ${fields} input_keys=[${inputKeys}] ${envFields}`;
6172
6175
  }
6173
- function agentLogStream(name, accountDir) {
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) {
6174
6186
  const logDir = resolve6(accountDir, "logs");
6175
6187
  mkdirSync4(logDir, { recursive: true });
6176
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) {
6177
6193
  const cutoff = Date.now() - LOG_RETENTION_DAYS * 24 * 60 * 60 * 1e3;
6194
+ let entries;
6178
6195
  try {
6179
- for (const file2 of readdirSync2(logDir)) {
6180
- if (!file2.startsWith(`${name}-`)) continue;
6181
- const filePath = resolve6(logDir, file2);
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 {
6182
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}`);
6183
6210
  }
6184
- } catch {
6185
6211
  }
6186
- 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
+ }
6187
6307
  }
6188
6308
  var PLATFORM_ROOT4 = process.env.MAXY_PLATFORM_ROOT ?? resolve6(process.cwd(), "..");
6189
6309
  var ACCOUNTS_DIR = resolve6(PLATFORM_ROOT4, "..", "data/accounts");
@@ -7377,43 +7497,48 @@ function consumeStalledSubagents(sessionKey) {
7377
7497
  delete session.stalledSubagents;
7378
7498
  return stalls && stalls.length > 0 ? stalls : void 0;
7379
7499
  }
7380
- 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
+ }
7381
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 };
7382
7507
  const servers = {
7383
7508
  "memory": {
7384
7509
  command: "node",
7385
7510
  args: [resolve6(PLATFORM_ROOT4, "plugins/memory/mcp/dist/index.js")],
7386
- env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT4, LOG_DIR: LOG_DIR2, ...userId ? { USER_ID: userId } : {} }
7511
+ env: { ...baseEnv, ...userId ? { USER_ID: userId } : {} }
7387
7512
  },
7388
7513
  "contacts": {
7389
7514
  command: "node",
7390
7515
  args: [resolve6(PLATFORM_ROOT4, "plugins/contacts/mcp/dist/index.js")],
7391
- env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT4, LOG_DIR: LOG_DIR2 }
7516
+ env: { ...baseEnv }
7392
7517
  },
7393
7518
  "whatsapp": {
7394
7519
  command: "node",
7395
7520
  args: [resolve6(PLATFORM_ROOT4, "plugins/whatsapp/mcp/dist/index.js")],
7396
- 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" }
7397
7522
  },
7398
7523
  "admin": {
7399
7524
  command: "node",
7400
7525
  args: [resolve6(PLATFORM_ROOT4, "plugins/admin/mcp/dist/index.js")],
7401
- 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 } : {} }
7402
7527
  },
7403
7528
  "scheduling": {
7404
7529
  command: "node",
7405
7530
  args: [resolve6(PLATFORM_ROOT4, "plugins/scheduling/mcp/dist/index.js")],
7406
- env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT4, LOG_DIR: LOG_DIR2 }
7531
+ env: { ...baseEnv }
7407
7532
  },
7408
7533
  "tasks": {
7409
7534
  command: "node",
7410
7535
  args: [resolve6(PLATFORM_ROOT4, "plugins/tasks/mcp/dist/index.js")],
7411
- env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT4, LOG_DIR: LOG_DIR2 }
7536
+ env: { ...baseEnv }
7412
7537
  },
7413
7538
  "email": {
7414
7539
  command: "node",
7415
7540
  args: [resolve6(PLATFORM_ROOT4, "plugins/email/mcp/dist/index.js")],
7416
- env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT4, LOG_DIR: LOG_DIR2 }
7541
+ env: { ...baseEnv }
7417
7542
  },
7418
7543
  // Playwright MCP server — browser automation for browser-specialist.
7419
7544
  // Key matches Claude Code's plugin naming: plugin_{plugin}_{server} so tools
@@ -7432,7 +7557,7 @@ function getMcpServers(accountId, userId, enabledPlugins) {
7432
7557
  servers["telegram"] = {
7433
7558
  command: "node",
7434
7559
  args: [resolve6(PLATFORM_ROOT4, "plugins/telegram/mcp/dist/index.js")],
7435
- env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT4, LOG_DIR: LOG_DIR2, TELEGRAM_BOT_TOKEN: tgBotToken }
7560
+ env: { ...baseEnv, TELEGRAM_BOT_TOKEN: tgBotToken }
7436
7561
  };
7437
7562
  } else {
7438
7563
  console.error("[plugins] telegram MCP: skipped (no bot token in account.json telegram config)");
@@ -7440,7 +7565,7 @@ function getMcpServers(accountId, userId, enabledPlugins) {
7440
7565
  servers["cloudflare"] = {
7441
7566
  command: "node",
7442
7567
  args: [resolve6(PLATFORM_ROOT4, "plugins/cloudflare/mcp/dist/index.js")],
7443
- 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" }
7444
7569
  };
7445
7570
  if (Array.isArray(enabledPlugins) && enabledPlugins.length > 0) {
7446
7571
  const pluginsDir = resolve6(PLATFORM_ROOT4, "plugins");
@@ -7471,7 +7596,7 @@ function getMcpServers(accountId, userId, enabledPlugins) {
7471
7596
  servers[dir] = {
7472
7597
  command: "node",
7473
7598
  args: [mcpEntry],
7474
- env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT4, LOG_DIR: LOG_DIR2 }
7599
+ env: { ...baseEnv }
7475
7600
  };
7476
7601
  console.log(`[plugins] optional MCP server started: ${dir}`);
7477
7602
  }
@@ -7546,13 +7671,13 @@ var ADMIN_CORE_TOOLS = [
7546
7671
  "mcp__admin__action-reject",
7547
7672
  "mcp__admin__action-edit",
7548
7673
  "mcp__cloudflare__cloudflare-setup",
7549
- "mcp__cloudflare__cf-set-token",
7550
7674
  "mcp__cloudflare__cf-add-zone",
7551
7675
  "mcp__cloudflare__cf-zone-status",
7676
+ "mcp__cloudflare__cf-verify",
7677
+ "mcp__cloudflare__cf-rebuild",
7552
7678
  "mcp__cloudflare__tunnel-status",
7553
7679
  "mcp__cloudflare__tunnel-install",
7554
7680
  "mcp__cloudflare__tunnel-login",
7555
- "mcp__cloudflare__tunnel-create",
7556
7681
  "mcp__cloudflare__tunnel-enable",
7557
7682
  "mcp__cloudflare__tunnel-disable",
7558
7683
  "mcp__cloudflare__tunnel-add-hostname",
@@ -8123,10 +8248,10 @@ var COMPACTION_PROMPT = `You are about to reach your context limit. Call session
8123
8248
 
8124
8249
  Then respond with only: [COMPACTED]`;
8125
8250
  var COMPACTION_TIMEOUT_MS = 45e3;
8126
- async function* runCompactionTurn(accountDir, accountId, systemPrompt, resumeSessionId, adminModel, enabledPlugins) {
8127
- 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) });
8128
8253
  const specialistsDir = resolve6(accountDir, "specialists");
8129
- 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}
8130
8255
  `);
8131
8256
  const args = [
8132
8257
  "--print",
@@ -8155,15 +8280,23 @@ async function* runCompactionTurn(accountDir, accountId, systemPrompt, resumeSes
8155
8280
  const proc = spawn2("claude", args, {
8156
8281
  cwd: accountDir,
8157
8282
  stdio: ["ignore", "pipe", "pipe"],
8158
- 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
+ }
8159
8291
  });
8160
- const stderrLog = agentLogStream("claude-agent-compaction-stderr", accountDir);
8292
+ const stderrLog = agentLogStream("claude-agent-compaction-stderr", accountDir, conversationId);
8161
8293
  stderrLog.on("error", () => {
8162
8294
  });
8163
8295
  proc.stderr?.pipe(stderrLog);
8164
- const streamLog = agentLogStream("claude-agent-compaction-stream", accountDir);
8296
+ const streamLog = agentLogStream("claude-agent-compaction-stream", accountDir, conversationId);
8165
8297
  streamLog.on("error", () => {
8166
8298
  });
8299
+ teeProcStderrToStreamLog(proc, streamLog);
8167
8300
  streamLog.write(`[${isoTs()}] [compaction-start] resumeSessionId=${resumeSessionId}
8168
8301
  `);
8169
8302
  proc.on("error", (err) => {
@@ -8271,6 +8404,83 @@ async function* parseClaudeStream(proc, streamLog, adminModel, conversationId) {
8271
8404
  apiWaitTimer = void 0;
8272
8405
  }
8273
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
+ }
8274
8484
  const readLines = async function* () {
8275
8485
  for await (const chunk of proc.stdout) {
8276
8486
  buffer += chunk.toString();
@@ -8449,6 +8659,7 @@ async function* parseClaudeStream(proc, streamLog, adminModel, conversationId) {
8449
8659
  vncLog("mcp-tool", { name: block.name, input_preview: inputPreview });
8450
8660
  }
8451
8661
  }
8662
+ trackToolUse(block.id, block.name);
8452
8663
  yield { type: "tool_use", name: block.name, input: block.input ?? {}, toolUseId: block.id };
8453
8664
  }
8454
8665
  }
@@ -8502,9 +8713,13 @@ async function* parseClaudeStream(proc, streamLog, adminModel, conversationId) {
8502
8713
  `);
8503
8714
  }
8504
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
+ `);
8505
8719
  }
8506
8720
  }
8507
8721
  if (block.tool_use_id) toolIdToInput.delete(block.tool_use_id);
8722
+ untrackToolUse(block.tool_use_id, "result");
8508
8723
  const renderDirectiveRe = /^__RENDER__(\{.+\})$/gm;
8509
8724
  let renderMatch;
8510
8725
  while ((renderMatch = renderDirectiveRe.exec(output)) !== null) {
@@ -8636,6 +8851,19 @@ async function* parseClaudeStream(proc, streamLog, adminModel, conversationId) {
8636
8851
  }
8637
8852
  } finally {
8638
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
+ }
8639
8867
  }
8640
8868
  if (!emittedDone) {
8641
8869
  }
@@ -8895,17 +9123,21 @@ function buildAttachmentMetaText(attachments) {
8895
9123
  async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, adminModel, sessionKey, maxTurns = 20, attachments = [], retryCount = 0, enabledPlugins, clientTimestamp) {
8896
9124
  const userTimestamp = clientTimestamp ?? (/* @__PURE__ */ new Date()).toISOString();
8897
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
+ }
8898
9130
  const cdpOk = await ensureCdp();
8899
9131
  if (!cdpOk) {
8900
- const cdpLog = agentLogStream("claude-agent-stream", accountDir);
9132
+ const cdpLog = agentLogStream("claude-agent-stream", accountDir, spawnConvId);
8901
9133
  cdpLog.write(`[${isoTs()}] [warn] ensureCdp failed \u2014 browser-specialist degraded
8902
9134
  `);
8903
9135
  cdpLog.end();
8904
9136
  }
8905
9137
  const ccUserId = sessionKey ? getUserIdForSession(sessionKey) : void 0;
8906
- const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, ccUserId, enabledPlugins) });
9138
+ const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, spawnConvId, ccUserId, enabledPlugins) });
8907
9139
  const specialistsDir = resolve6(accountDir, "specialists");
8908
- 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}
8909
9141
  `);
8910
9142
  const args = [
8911
9143
  "--print",
@@ -8935,15 +9167,24 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
8935
9167
  const proc = spawn2("claude", args, {
8936
9168
  cwd: accountDir,
8937
9169
  stdio: ["ignore", "pipe", "pipe"],
8938
- 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
+ }
8939
9179
  });
8940
- const stderrLog = agentLogStream("claude-agent-stderr", accountDir);
9180
+ const stderrLog = agentLogStream("claude-agent-stderr", accountDir, spawnConvId);
8941
9181
  stderrLog.on("error", () => {
8942
9182
  });
8943
9183
  proc.stderr?.pipe(stderrLog);
8944
- const streamLog = agentLogStream("claude-agent-stream", accountDir);
9184
+ const streamLog = agentLogStream("claude-agent-stream", accountDir, spawnConvId);
8945
9185
  streamLog.on("error", () => {
8946
9186
  });
9187
+ teeProcStderrToStreamLog(proc, streamLog);
8947
9188
  if (sessionKey) {
8948
9189
  const prev = activeProcesses.get(sessionKey);
8949
9190
  if (prev) {
@@ -8952,8 +9193,7 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
8952
9193
  }
8953
9194
  activeProcesses.set(sessionKey, { pid: proc.pid, spawnedAt: Date.now() });
8954
9195
  }
8955
- const spawnConvId = sessionKey ? getConversationIdForSession(sessionKey) : void 0;
8956
- 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}
8957
9197
  `);
8958
9198
  streamLog.write(`[${isoTs()}] [stdin] len=${fullMessage.length} preview=${JSON.stringify(fullMessage.slice(0, 80))}
8959
9199
  `);
@@ -9026,7 +9266,7 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
9026
9266
  if (event.type === "usage" && sessionKey && currentAgentSessionId) {
9027
9267
  const peakReqPct = event.peak_request_pct ?? 0;
9028
9268
  if (peakReqPct >= COMPACTION_THRESHOLD) {
9029
- const compactionIter = runCompactionTurn(accountDir, accountId, systemPrompt, currentAgentSessionId, adminModel, enabledPlugins);
9269
+ const compactionIter = runCompactionTurn(accountDir, accountId, systemPrompt, currentAgentSessionId, adminModel, spawnConvId, enabledPlugins);
9030
9270
  let step = await compactionIter.next();
9031
9271
  while (!step.done) {
9032
9272
  yield { type: "status", message: step.value };
@@ -9197,6 +9437,10 @@ ${summary}`;
9197
9437
  }
9198
9438
  async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accountId, adminModel, sessionKey, maxTurns = 20, attachments = [], retryCount = 0, enabledPlugins, clientTimestamp) {
9199
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
+ }
9200
9444
  const pendingTrimmed = consumePendingTrimmedMessages(sessionKey);
9201
9445
  if (pendingTrimmed && pendingTrimmed.length > 0) {
9202
9446
  const ok = await compactTrimmedMessages(accountId, pendingTrimmed);
@@ -9204,7 +9448,7 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
9204
9448
  storePendingTrimmedMessages(sessionKey, pendingTrimmed);
9205
9449
  }
9206
9450
  }
9207
- const streamLog = agentLogStream("claude-agent-stream", accountDir);
9451
+ const streamLog = agentLogStream("claude-agent-stream", accountDir, managedConvId);
9208
9452
  streamLog.on("error", () => {
9209
9453
  });
9210
9454
  const systemPromptTokens = estimateTokens(systemPrompt);
@@ -9235,7 +9479,7 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
9235
9479
  if (!cdpOk) streamLog.write(`[${isoTs()}] [warn] ensureCdp failed \u2014 browser-specialist degraded
9236
9480
  `);
9237
9481
  const managedUserId = getUserIdForSession(sessionKey);
9238
- const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, managedUserId, enabledPlugins) });
9482
+ const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, managedConvId, managedUserId, enabledPlugins) });
9239
9483
  const specialistsDir = resolve6(accountDir, "specialists");
9240
9484
  if (!existsSync6(specialistsDir)) streamLog.write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
9241
9485
  `);
@@ -9265,12 +9509,20 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
9265
9509
  const proc = spawn2("claude", args, {
9266
9510
  cwd: accountDir,
9267
9511
  stdio: ["ignore", "pipe", "pipe"],
9268
- 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
+ }
9269
9520
  });
9270
- const stderrLog = agentLogStream("claude-agent-stderr", accountDir);
9521
+ const stderrLog = agentLogStream("claude-agent-stderr", accountDir, managedConvId);
9271
9522
  stderrLog.on("error", () => {
9272
9523
  });
9273
9524
  proc.stderr?.pipe(stderrLog);
9525
+ teeProcStderrToStreamLog(proc, streamLog);
9274
9526
  if (sessionKey) {
9275
9527
  const prev = activeProcesses.get(sessionKey);
9276
9528
  if (prev) {
@@ -9279,14 +9531,13 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
9279
9531
  }
9280
9532
  activeProcesses.set(sessionKey, { pid: proc.pid, spawnedAt: Date.now() });
9281
9533
  }
9282
- const managedConvId = sessionKey ? getConversationIdForSession(sessionKey) : void 0;
9283
- 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}
9284
9535
  `);
9285
9536
  streamLog.write(`[${isoTs()}] [stdin] len=${fullMessage.length} preview=${JSON.stringify(fullMessage.slice(0, 80))}
9286
9537
  `);
9287
9538
  proc.on("exit", (code, signal) => {
9288
- console.log(`[process-exit] pid=${proc.pid} code=${code} signal=${signal} sessionKey=${sessionKey ?? "none"} conversationId=${managedConvId ?? "none"}`);
9289
- 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}
9290
9541
  `);
9291
9542
  if (sessionKey) activeProcesses.delete(sessionKey);
9292
9543
  });
@@ -9507,7 +9758,11 @@ async function* invokePublicAgent(message, systemPrompt, accountId, accountDir,
9507
9758
  yield { type: "done" };
9508
9759
  return;
9509
9760
  }
9510
- 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);
9511
9766
  streamLog.write(`[${isoTs()}] [public-user-message] ${JSON.stringify(message)}
9512
9767
  `);
9513
9768
  if (sessionKey) {
@@ -9780,6 +10035,8 @@ async function* compactSession(sessionKey) {
9780
10035
  }
9781
10036
  const currentSessionId = getAgentSessionId(sessionKey);
9782
10037
  if (!currentSessionId) return { ok: false, reason: "no-session" };
10038
+ const compactionConvId = getConversationIdForSession(sessionKey);
10039
+ if (!compactionConvId) return { ok: false, reason: "no-conversation" };
9783
10040
  const identity = readIdentity(account.accountDir, "admin");
9784
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.";
9785
10042
  const baseSystemPrompt = identity ?? defaultSystemPrompt;
@@ -9787,7 +10044,7 @@ async function* compactSession(sessionKey) {
9787
10044
  const systemPrompt = outputStyle === "explanatory" ? `${baseSystemPrompt}
9788
10045
 
9789
10046
  ${EXPLANATORY_STYLE_INSTRUCTIONS}` : baseSystemPrompt;
9790
- 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);
9791
10048
  let step = await compactionIter.next();
9792
10049
  while (!step.done) {
9793
10050
  yield step.value;
@@ -10204,6 +10461,38 @@ function defaultRules() {
10204
10461
  thresholdWindowMinutes: 5,
10205
10462
  scope: "session",
10206
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."
10207
10496
  }
10208
10497
  ];
10209
10498
  }
@@ -10419,12 +10708,31 @@ function discoverSourceFiles(configDir2, accountLogDir2, logicalSource) {
10419
10708
  }[logicalSource];
10420
10709
  if (!existsSync8(accountLogDir2)) return [];
10421
10710
  const files = [];
10711
+ let scanned = 0;
10712
+ let skippedPrefixMismatch = 0;
10713
+ let skippedNotLog = 0;
10422
10714
  for (const entry of readdirSync3(accountLogDir2)) {
10423
- 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) {
10424
10719
  files.push({ logicalSource, filepath: join5(accountLogDir2, entry) });
10720
+ } else if (!matchesPrefix) {
10721
+ skippedPrefixMismatch += 1;
10722
+ } else {
10723
+ skippedNotLog += 1;
10425
10724
  }
10426
10725
  }
10427
- files.sort((a, b) => a.filepath.localeCompare(b.filepath));
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);
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}`);
10735
+ }
10428
10736
  return files;
10429
10737
  }
10430
10738
  function discoverAllSources(configDir2, accountLogDir2) {
@@ -28308,8 +28616,9 @@ async function POST2(req) {
28308
28616
  { status: 500, headers: { "Content-Type": "application/json" } }
28309
28617
  );
28310
28618
  }
28311
- const sseLog = agentLogStream("sse-events", account.accountDir);
28312
- 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);
28313
28622
  const agentName = getAgentNameForSession(session_key);
28314
28623
  if (!agentName) {
28315
28624
  console.log(`[chat] no agent for session=${sk} \u2014 session expired or server restarted`);
@@ -30684,7 +30993,8 @@ async function POST21(req) {
30684
30993
  try {
30685
30994
  const parsed = JSON.parse(message);
30686
30995
  if (parsed._lifecycle) {
30687
- 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);
30688
30998
  const ts = (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
30689
30999
  const detail = parsed.filePath ? ` filePath=${parsed.filePath}` : "";
30690
31000
  lifecycleLog.write(`[${ts}] [component:${parsed.component ?? "unknown"}] ${parsed.event ?? "unknown"}${detail}
@@ -30708,7 +31018,8 @@ async function POST21(req) {
30708
31018
  try {
30709
31019
  const parsed = JSON.parse(message);
30710
31020
  if (isComponentDone(parsed)) {
30711
- 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);
30712
31023
  const ts = (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
30713
31024
  message = transformComponentDone(parsed);
30714
31025
  componentLog.write(`[${ts}] [component:${parsed.component}] componentDone \u2192 transformed
@@ -30736,8 +31047,9 @@ async function POST21(req) {
30736
31047
  gatewayResult = await processInbound(message, "web-admin");
30737
31048
  }
30738
31049
  const encoder = new TextEncoder();
30739
- const sseLog = agentLogStream("sse-events", account.accountDir);
30740
- 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);
30741
31053
  const readable = new ReadableStream({
30742
31054
  async start(controller) {
30743
31055
  try {
@@ -30853,6 +31165,7 @@ async function GET9(request) {
30853
31165
  const { searchParams } = new URL(request.url);
30854
31166
  const fileParam = searchParams.get("file");
30855
31167
  const typeParam = searchParams.get("type");
31168
+ const conversationIdParam = searchParams.get("conversationId");
30856
31169
  const download = searchParams.get("download") === "1";
30857
31170
  const account = resolveAccount();
30858
31171
  const accountLogDir2 = account ? resolve19(account.accountDir, "logs") : null;
@@ -30872,18 +31185,21 @@ async function GET9(request) {
30872
31185
  return Response.json({ error: `File not found: ${safe}` }, { status: 404 });
30873
31186
  }
30874
31187
  if (typeParam) {
30875
- const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
30876
- const typeMap = {
30877
- stream: `claude-agent-stream-${today}.log`,
30878
- error: `claude-agent-stderr-${today}.log`,
30879
- session: `sse-events-${today}.log`,
30880
- sse: `sse-events-${today}.log`,
30881
- 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"
30882
31194
  };
30883
- const fileName = typeMap[typeParam];
30884
- if (!fileName) {
31195
+ const prefix = prefixMap[typeParam];
31196
+ if (!prefix) {
30885
31197
  return Response.json({ error: `Unknown type: ${typeParam}. Valid: stream, error, session, sse, public` }, { status: 400 });
30886
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`;
30887
31203
  for (const dir of [accountLogDir2, LOG_DIR]) {
30888
31204
  if (!dir) continue;
30889
31205
  const filePath = resolve19(dir, fileName);
@@ -31589,7 +31905,7 @@ var brandLoginOpts = {
31589
31905
  bodyFont: BRAND.defaultFonts?.body,
31590
31906
  logoContainsName: !!BRAND.logoContainsName
31591
31907
  };
31592
- var ALIAS_DOMAINS_PATH = join13(homedir3(), BRAND.configDir, "alias-domains.json");
31908
+ var ALIAS_DOMAINS_PATH = join13(homedir4(), BRAND.configDir, "alias-domains.json");
31593
31909
  function loadAliasDomains() {
31594
31910
  try {
31595
31911
  if (!existsSync26(ALIAS_DOMAINS_PATH)) return null;
@@ -32082,7 +32398,7 @@ function cachedHtml(file2) {
32082
32398
  }
32083
32399
  var brandedHtmlCache = /* @__PURE__ */ new Map();
32084
32400
  function loadBrandingCache(agentSlug) {
32085
- const configDir2 = join13(homedir3(), BRAND.configDir);
32401
+ const configDir2 = join13(homedir4(), BRAND.configDir);
32086
32402
  try {
32087
32403
  const accountJsonPath = join13(configDir2, "account.json");
32088
32404
  if (!existsSync26(accountJsonPath)) return null;
@@ -32098,7 +32414,7 @@ function loadBrandingCache(agentSlug) {
32098
32414
  }
32099
32415
  function resolveDefaultSlug() {
32100
32416
  try {
32101
- const configDir2 = join13(homedir3(), BRAND.configDir);
32417
+ const configDir2 = join13(homedir4(), BRAND.configDir);
32102
32418
  const accountJsonPath = join13(configDir2, "account.json");
32103
32419
  if (!existsSync26(accountJsonPath)) return null;
32104
32420
  const account = JSON.parse(readFileSync26(accountJsonPath, "utf-8"));