@rubytech/create-realagent 1.0.613 → 1.0.615

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 (26) hide show
  1. package/dist/index.js +42 -8
  2. package/package.json +1 -1
  3. package/payload/platform/plugins/admin/mcp/dist/lib/review-tools.d.ts.map +1 -1
  4. package/payload/platform/plugins/admin/mcp/dist/lib/review-tools.js +2 -0
  5. package/payload/platform/plugins/admin/mcp/dist/lib/review-tools.js.map +1 -1
  6. package/payload/platform/plugins/cloudflare/mcp/dist/index.js +32 -4
  7. package/payload/platform/plugins/cloudflare/mcp/dist/index.js.map +1 -1
  8. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts +18 -2
  9. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts.map +1 -1
  10. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js +106 -19
  11. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js.map +1 -1
  12. package/payload/platform/plugins/cloudflare/references/setup-guide.md +6 -0
  13. package/payload/platform/plugins/docs/references/troubleshooting.md +2 -0
  14. package/payload/platform/plugins/email/mcp/dist/lib/providers.d.ts +9 -2
  15. package/payload/platform/plugins/email/mcp/dist/lib/providers.d.ts.map +1 -1
  16. package/payload/platform/plugins/email/mcp/dist/lib/providers.js +545 -92
  17. package/payload/platform/plugins/email/mcp/dist/lib/providers.js.map +1 -1
  18. package/payload/platform/templates/agents/admin/IDENTITY.md +6 -0
  19. package/payload/platform/templates/agents/admin/SOUL.md +20 -0
  20. package/payload/platform/templates/agents/public/IDENTITY.md +1 -0
  21. package/payload/platform/templates/agents/public/SOUL.md +12 -0
  22. package/payload/platform/templates/specialists/agents/content-producer.md +4 -0
  23. package/payload/platform/templates/specialists/agents/personal-assistant.md +4 -0
  24. package/payload/platform/templates/specialists/agents/project-manager.md +4 -0
  25. package/payload/platform/templates/specialists/agents/research-assistant.md +4 -0
  26. package/payload/server/server.js +332 -59
@@ -4165,7 +4165,9 @@ import Anthropic2 from "@anthropic-ai/sdk";
4165
4165
  import { spawn as spawn2 } 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 { readFileSync as readFileSync7, writeFileSync as writeFileSync5, readdirSync as readdirSync2, existsSync as existsSync6, mkdirSync as mkdirSync4, createWriteStream, statSync as statSync3, unlinkSync, cpSync, rmSync } from "fs";
4169
+ import { lookup as dnsLookup } from "dns/promises";
4170
+ import { createConnection as netConnect } from "net";
4169
4171
 
4170
4172
  // ../lib/models/src/index.ts
4171
4173
  var OPUS_MODEL = "claude-opus-4-7";
@@ -4465,7 +4467,7 @@ function buildX11Env(chromiumWrapperPath, transport = "vnc") {
4465
4467
  import neo4j from "neo4j-driver";
4466
4468
  import { randomUUID } from "crypto";
4467
4469
  import { spawn } from "child_process";
4468
- import { readFileSync as readFileSync6, readdirSync, existsSync as existsSync5 } from "fs";
4470
+ import { readFileSync as readFileSync6, readdirSync, existsSync as existsSync5, openSync, readSync, closeSync, statSync as statSync2 } from "fs";
4469
4471
  import { resolve as resolve5 } from "path";
4470
4472
  var PLATFORM_ROOT3 = process.env.MAXY_PLATFORM_ROOT ?? resolve5(process.cwd(), "..");
4471
4473
  var driver = null;
@@ -5455,7 +5457,45 @@ Expertise: ${typeof profile.expertise === "string" ? profile.expertise : JSON.st
5455
5457
  var MAX_SESSION_TASKS = 20;
5456
5458
  var MAX_SESSION_REVIEW_ALERTS = 5;
5457
5459
  var MAX_SESSION_PROJECTS = 5;
5458
- async function loadSessionContext(accountId) {
5460
+ var MAX_RECENT_TOOL_FAILURES = 3;
5461
+ var RECENT_FAILURES_TAIL_BYTES = 10 * 1024;
5462
+ function readRecentToolFailures(accountId, conversationId) {
5463
+ try {
5464
+ const platformRoot3 = process.env.MAXY_PLATFORM_ROOT ?? resolve5(process.cwd(), "..");
5465
+ 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 [];
5469
+ const st = statSync2(logPath2);
5470
+ const size = st.size;
5471
+ const readBytes = Math.min(size, RECENT_FAILURES_TAIL_BYTES);
5472
+ const position = size - readBytes;
5473
+ const fd = openSync(logPath2, "r");
5474
+ try {
5475
+ const buf = Buffer.alloc(readBytes);
5476
+ readSync(fd, buf, 0, readBytes, position);
5477
+ const text = buf.toString("utf-8");
5478
+ const convPrefix = conversationId.slice(0, 8);
5479
+ const lines = text.split("\n");
5480
+ const matches = [];
5481
+ for (let i = lines.length - 1; i >= 0 && matches.length < MAX_RECENT_TOOL_FAILURES; i--) {
5482
+ const line = lines[i];
5483
+ if (line.includes("[tool-failure-diag]") && line.includes(`conversationId=${convPrefix}`)) {
5484
+ matches.push(line);
5485
+ }
5486
+ }
5487
+ return matches.reverse();
5488
+ } finally {
5489
+ closeSync(fd);
5490
+ }
5491
+ } catch (err) {
5492
+ console.error(
5493
+ `[session-context] recent-tool-failures read failed: ${err instanceof Error ? err.message : String(err)}`
5494
+ );
5495
+ return [];
5496
+ }
5497
+ }
5498
+ async function loadSessionContext(accountId, conversationId) {
5459
5499
  const session = getSession();
5460
5500
  try {
5461
5501
  const digestResult = await session.run(
@@ -5625,9 +5665,21 @@ ${projectLines.join("\n")}`);
5625
5665
  sections.push(`## Active Review Alerts (${alertsResult.records.length})
5626
5666
  ${alertLines.join("\n")}`);
5627
5667
  }
5668
+ let recentFailuresCount = 0;
5669
+ if (conversationId) {
5670
+ const failureLines = readRecentToolFailures(accountId, conversationId);
5671
+ if (failureLines.length > 0) {
5672
+ recentFailuresCount = failureLines.length;
5673
+ 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.";
5674
+ sections.push(`## Recent Tool Failures (${failureLines.length})
5675
+ ${guidance}
5676
+
5677
+ ${failureLines.map((l) => `- ${l.trim()}`).join("\n")}`);
5678
+ }
5679
+ }
5628
5680
  if (sections.length === 0) return null;
5629
5681
  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}`
5682
+ `[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
5683
  );
5632
5684
  return `<previous-context>
5633
5685
  ${sections.join("\n\n")}
@@ -5991,6 +6043,133 @@ var BROWSER_TOOL_PREFIXES = [
5991
6043
  function isBrowserTool(name) {
5992
6044
  return BROWSER_TOOL_PREFIXES.some((p) => name.startsWith(p));
5993
6045
  }
6046
+ var DIAG_HARD_CAP_MS = 5e3;
6047
+ var DIAG_DNS_TIMEOUT_MS = 2e3;
6048
+ var DIAG_TCP_TIMEOUT_MS = 3e3;
6049
+ var DIAG_HTTP_TIMEOUT_MS = 4e3;
6050
+ function quoteDiag(value) {
6051
+ return JSON.stringify(value);
6052
+ }
6053
+ function extractUrl(toolName, input) {
6054
+ if (input === null || typeof input !== "object") return void 0;
6055
+ const obj = input;
6056
+ if (toolName === "WebFetch" && typeof obj.url === "string") return obj.url;
6057
+ if (isBrowserTool(toolName) && typeof obj.url === "string") return obj.url;
6058
+ if (typeof obj.url === "string" && /^https?:\/\//.test(obj.url)) return obj.url;
6059
+ return void 0;
6060
+ }
6061
+ var FULL_REDACT_ENV_VARS = /* @__PURE__ */ new Set(["HTTPS_PROXY", "HTTP_PROXY", "NO_PROXY"]);
6062
+ function redactEnvField(name) {
6063
+ const value = process.env[name];
6064
+ if (!value) return `${name.toLowerCase()}=absent`;
6065
+ if (FULL_REDACT_ENV_VARS.has(name)) {
6066
+ return `${name.toLowerCase()}=present`;
6067
+ }
6068
+ const suffix = value.length > 40 ? value.slice(-40) : value;
6069
+ return `${name.toLowerCase()}=present suffix=${quoteDiag(suffix)}`;
6070
+ }
6071
+ async function probeDns(host, family) {
6072
+ const label = family === 4 ? "dns_a" : "dns_aaaa";
6073
+ const start = Date.now();
6074
+ let timer;
6075
+ try {
6076
+ const result = await Promise.race([
6077
+ dnsLookup(host, { family, verbatim: true }),
6078
+ new Promise((_, reject) => {
6079
+ timer = setTimeout(() => reject(new Error("timeout")), DIAG_DNS_TIMEOUT_MS);
6080
+ })
6081
+ ]);
6082
+ if (timer) clearTimeout(timer);
6083
+ const ms = Date.now() - start;
6084
+ return `${label}=${result.address} ${label}_ms=${ms}`;
6085
+ } catch (err) {
6086
+ if (timer) clearTimeout(timer);
6087
+ const ms = Date.now() - start;
6088
+ const msg = err instanceof Error ? err.message : String(err);
6089
+ return `${label}=err ${label}_err=${quoteDiag(msg.slice(0, 60))} ${label}_ms=${ms}`;
6090
+ }
6091
+ }
6092
+ async function probeTcp(host, port2) {
6093
+ const start = Date.now();
6094
+ return new Promise((resolvePromise) => {
6095
+ let settled = false;
6096
+ const sock = netConnect({ host, port: port2, family: 0 });
6097
+ const finish = (result) => {
6098
+ if (settled) return;
6099
+ settled = true;
6100
+ try {
6101
+ sock.destroy();
6102
+ } catch {
6103
+ }
6104
+ resolvePromise(result);
6105
+ };
6106
+ const timer = setTimeout(() => finish(`tcp=timeout tcp_ms=${Date.now() - start}`), DIAG_TCP_TIMEOUT_MS);
6107
+ sock.once("connect", () => {
6108
+ clearTimeout(timer);
6109
+ finish(`tcp=ok tcp_ms=${Date.now() - start}`);
6110
+ });
6111
+ sock.once("error", (err) => {
6112
+ clearTimeout(timer);
6113
+ const msg = err instanceof Error ? err.message : String(err);
6114
+ finish(`tcp=err tcp_err=${quoteDiag(msg.slice(0, 60))} tcp_ms=${Date.now() - start}`);
6115
+ });
6116
+ });
6117
+ }
6118
+ async function probeHttp(url2) {
6119
+ const start = Date.now();
6120
+ const controller = new AbortController();
6121
+ const timer = setTimeout(() => controller.abort(), DIAG_HTTP_TIMEOUT_MS);
6122
+ try {
6123
+ const res = await fetch(url2, { method: "HEAD", redirect: "manual", signal: controller.signal });
6124
+ clearTimeout(timer);
6125
+ return `http_status=${res.status} http_ms=${Date.now() - start}`;
6126
+ } catch (err) {
6127
+ clearTimeout(timer);
6128
+ const msg = err instanceof Error ? err.message : String(err);
6129
+ return `http_status=err http_err=${quoteDiag(msg.slice(0, 60))} http_ms=${Date.now() - start}`;
6130
+ }
6131
+ }
6132
+ async function runFailureDiagnostic(toolName, toolInput) {
6133
+ const inputKeys = toolInput !== null && typeof toolInput === "object" ? Object.keys(toolInput).join(",") : "";
6134
+ const envFields = [
6135
+ redactEnvField("HTTPS_PROXY"),
6136
+ redactEnvField("HTTP_PROXY"),
6137
+ redactEnvField("NO_PROXY"),
6138
+ redactEnvField("NODE_OPTIONS")
6139
+ ].join(" ");
6140
+ const url2 = extractUrl(toolName, toolInput);
6141
+ if (!url2) {
6142
+ return `diag_url=none input_keys=[${inputKeys}] ${envFields}`;
6143
+ }
6144
+ let host;
6145
+ let port2;
6146
+ try {
6147
+ const parsed = new URL(url2);
6148
+ host = parsed.hostname;
6149
+ port2 = parsed.port ? Number(parsed.port) : parsed.protocol === "https:" ? 443 : 80;
6150
+ } catch {
6151
+ return `diag_url=unparseable input_keys=[${inputKeys}] ${envFields}`;
6152
+ }
6153
+ const probes = Promise.allSettled([
6154
+ probeDns(host, 4),
6155
+ probeDns(host, 6),
6156
+ probeTcp(host, port2),
6157
+ probeHttp(url2)
6158
+ ]);
6159
+ let capTimer;
6160
+ const capped = await Promise.race([
6161
+ probes,
6162
+ new Promise((resolvePromise) => {
6163
+ capTimer = setTimeout(() => resolvePromise("__diag_timeout__"), DIAG_HARD_CAP_MS);
6164
+ })
6165
+ ]);
6166
+ if (capTimer) clearTimeout(capTimer);
6167
+ if (capped === "__diag_timeout__") {
6168
+ return `diag_host=${host} diag_port=${port2} diag_timeout=true input_keys=[${inputKeys}] ${envFields}`;
6169
+ }
6170
+ const fields = capped.map((r) => r.status === "fulfilled" ? r.value : `probe_err=${quoteDiag(String(r.reason).slice(0, 40))}`).join(" ");
6171
+ return `diag_host=${host} diag_port=${port2} ${fields} input_keys=[${inputKeys}] ${envFields}`;
6172
+ }
5994
6173
  function agentLogStream(name, accountDir) {
5995
6174
  const logDir = resolve6(accountDir, "logs");
5996
6175
  mkdirSync4(logDir, { recursive: true });
@@ -6000,7 +6179,7 @@ function agentLogStream(name, accountDir) {
6000
6179
  for (const file2 of readdirSync2(logDir)) {
6001
6180
  if (!file2.startsWith(`${name}-`)) continue;
6002
6181
  const filePath = resolve6(logDir, file2);
6003
- if (statSync2(filePath).mtimeMs < cutoff) unlinkSync(filePath);
6182
+ if (statSync3(filePath).mtimeMs < cutoff) unlinkSync(filePath);
6004
6183
  }
6005
6184
  } catch {
6006
6185
  }
@@ -6182,8 +6361,8 @@ function resolveAgentConfig(accountDir, agentName) {
6182
6361
  const hasKnowledge = existsSync6(knowledgePath);
6183
6362
  const hasSummary = existsSync6(summaryPath);
6184
6363
  if (hasKnowledge && hasSummary) {
6185
- const knowledgeMtime = statSync2(knowledgePath).mtimeMs;
6186
- const summaryMtime = statSync2(summaryPath).mtimeMs;
6364
+ const knowledgeMtime = statSync3(knowledgePath).mtimeMs;
6365
+ const summaryMtime = statSync3(summaryPath).mtimeMs;
6187
6366
  if (summaryMtime >= knowledgeMtime) {
6188
6367
  knowledge = readFileSync7(summaryPath, "utf-8");
6189
6368
  } else {
@@ -6862,7 +7041,7 @@ ${specialist}: ${plugins.join(", ")}`);
6862
7041
  for (const entry of readdirSync2(current)) {
6863
7042
  const full = resolve6(current, entry);
6864
7043
  try {
6865
- const stat4 = statSync2(full);
7044
+ const stat4 = statSync3(full);
6866
7045
  if (stat4.isDirectory()) {
6867
7046
  walk(full, `${rel}${entry}/`);
6868
7047
  } else if (entry.endsWith(".md")) {
@@ -6993,6 +7172,15 @@ function resolveUserAccounts(userId) {
6993
7172
  var sessionStore = /* @__PURE__ */ new Map();
6994
7173
  setSessionStoreRef(sessionStore);
6995
7174
  function registerSession(sessionKey, agentType, accountId, agentName, userId, userName) {
7175
+ const existing = sessionStore.get(sessionKey);
7176
+ if (existing) {
7177
+ existing.agentType = agentType;
7178
+ existing.accountId = accountId;
7179
+ existing.agentName = agentName ?? existing.agentName;
7180
+ existing.userId = userId ?? existing.userId;
7181
+ existing.userName = userName ?? existing.userName;
7182
+ return;
7183
+ }
6996
7184
  sessionStore.set(sessionKey, { createdAt: Date.now(), agentType, accountId, agentName, userId, userName });
6997
7185
  }
6998
7186
  function registerResumedSession(sessionKey, accountId, agentName, conversationId, messages) {
@@ -7057,6 +7245,9 @@ function storeAgentSessionId(sessionKey, agentSessionId) {
7057
7245
  const session = sessionStore.get(sessionKey);
7058
7246
  if (session) {
7059
7247
  session.agentSessionId = agentSessionId;
7248
+ console.error(`[session-store] storeAgentSessionId sessionKey=${sessionKey.slice(0, 12)}\u2026 sessionId=${agentSessionId.slice(0, 8)}\u2026`);
7249
+ } else {
7250
+ console.error(`[session-store] storeAgentSessionId SKIPPED \u2014 no session entry sessionKey=${sessionKey.slice(0, 12)}\u2026 sessionId=${agentSessionId.slice(0, 8)}\u2026`);
7060
7251
  }
7061
7252
  }
7062
7253
  function getAgentSessionId(sessionKey) {
@@ -8033,14 +8224,19 @@ async function* runCompactionTurn(accountDir, accountId, systemPrompt, resumeSes
8033
8224
  streamLog.end();
8034
8225
  return { ok: capturedSummary !== void 0 && !timedOut, summary: capturedSummary };
8035
8226
  }
8036
- function clearAgentSessionId(sessionKey) {
8227
+ function clearAgentSessionId(sessionKey, reason) {
8037
8228
  const session = sessionStore.get(sessionKey);
8038
8229
  if (session) {
8039
8230
  session.agentSessionId = void 0;
8231
+ console.error(`[session-store] clearAgentSessionId sessionKey=${sessionKey.slice(0, 12)}\u2026 reason=${reason}`);
8232
+ } else {
8233
+ console.error(`[session-store] clearAgentSessionId SKIPPED \u2014 no session entry sessionKey=${sessionKey.slice(0, 12)}\u2026 reason=${reason}`);
8040
8234
  }
8041
8235
  }
8042
- async function* parseClaudeStream(proc, streamLog, adminModel) {
8236
+ async function* parseClaudeStream(proc, streamLog, adminModel, conversationId) {
8043
8237
  const toolIdToName = /* @__PURE__ */ new Map();
8238
+ const toolIdToInput = /* @__PURE__ */ new Map();
8239
+ const convIdTag = conversationId ? ` conversationId=${conversationId.slice(0, 8)}` : "";
8044
8240
  let emittedDone = false;
8045
8241
  let buffer = "";
8046
8242
  const modelCtxWindow = contextWindow(adminModel);
@@ -8236,15 +8432,18 @@ async function* parseClaudeStream(proc, streamLog, adminModel) {
8236
8432
  yield { type: "session_resume", conversationId: resumeConversationId };
8237
8433
  }
8238
8434
  } else {
8239
- if (block.id) toolIdToName.set(block.id, block.name);
8435
+ if (block.id) {
8436
+ toolIdToName.set(block.id, block.name);
8437
+ toolIdToInput.set(block.id, block.input ?? {});
8438
+ }
8240
8439
  const inputPreview = JSON.stringify(block.input ?? {}).slice(0, 200);
8241
8440
  if (block.name === "Agent") {
8242
8441
  const subType = block.input?.subagent_type ?? "general-purpose";
8243
8442
  const desc = block.input?.description ?? "";
8244
- streamLog.write(`[${isoTs()}] [agent-dispatch] subagent_type=${JSON.stringify(subType)} description=${JSON.stringify(desc)} input=${inputPreview}
8443
+ streamLog.write(`[${isoTs()}] [agent-dispatch]${convIdTag} subagent_type=${JSON.stringify(subType)} description=${JSON.stringify(desc)} input=${inputPreview}
8245
8444
  `);
8246
8445
  } else {
8247
- streamLog.write(`[${isoTs()}] [tool-use] name=${block.name} input=${inputPreview}
8446
+ streamLog.write(`[${isoTs()}] [tool-use]${convIdTag} name=${block.name} input=${inputPreview}
8248
8447
  `);
8249
8448
  if (isBrowserTool(block.name)) {
8250
8449
  vncLog("mcp-tool", { name: block.name, input_preview: inputPreview });
@@ -8276,10 +8475,10 @@ async function* parseClaudeStream(proc, streamLog, adminModel) {
8276
8475
  }
8277
8476
  const outputPreview = output.slice(0, 300);
8278
8477
  if (name === "Agent") {
8279
- streamLog.write(`[${isoTs()}] [agent-return] error=${!!block.is_error} output=${JSON.stringify(outputPreview)}
8478
+ streamLog.write(`[${isoTs()}] [agent-return]${convIdTag} error=${!!block.is_error} output=${JSON.stringify(outputPreview)}
8280
8479
  `);
8281
8480
  } else {
8282
- streamLog.write(`[${isoTs()}] [tool-result] name=${name} error=${!!block.is_error} output=${JSON.stringify(outputPreview)}
8481
+ streamLog.write(`[${isoTs()}] [tool-result]${convIdTag} name=${name} error=${!!block.is_error} output=${JSON.stringify(outputPreview)}
8283
8482
  `);
8284
8483
  if (isBrowserTool(name)) {
8285
8484
  vncLog("mcp-tool-result", {
@@ -8288,7 +8487,24 @@ async function* parseClaudeStream(proc, streamLog, adminModel) {
8288
8487
  output_preview: outputPreview
8289
8488
  });
8290
8489
  }
8490
+ if (block.is_error && block.tool_use_id) {
8491
+ const toolInput = toolIdToInput.get(block.tool_use_id);
8492
+ try {
8493
+ const diag = await runFailureDiagnostic(name, toolInput);
8494
+ if (!streamLog.destroyed) {
8495
+ streamLog.write(`[${isoTs()}] [tool-failure-diag]${convIdTag} name=${name} ${diag}
8496
+ `);
8497
+ }
8498
+ } catch (err) {
8499
+ if (!streamLog.destroyed) {
8500
+ const msg2 = err instanceof Error ? err.message : String(err);
8501
+ streamLog.write(`[${isoTs()}] [tool-failure-diag]${convIdTag} name=${name} diag_err=${JSON.stringify(msg2.slice(0, 80))}
8502
+ `);
8503
+ }
8504
+ }
8505
+ }
8291
8506
  }
8507
+ if (block.tool_use_id) toolIdToInput.delete(block.tool_use_id);
8292
8508
  const renderDirectiveRe = /^__RENDER__(\{.+\})$/gm;
8293
8509
  let renderMatch;
8294
8510
  while ((renderMatch = renderDirectiveRe.exec(output)) !== null) {
@@ -8758,7 +8974,7 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
8758
8974
  const toolCalls = [];
8759
8975
  const pendingToolCalls = /* @__PURE__ */ new Map();
8760
8976
  let capturedTokens;
8761
- for await (const event of withSubagentHeartbeat(parseClaudeStream(proc, streamLog, adminModel), streamLog)) {
8977
+ for await (const event of withSubagentHeartbeat(parseClaudeStream(proc, streamLog, adminModel, spawnConvId), streamLog)) {
8762
8978
  if (event.type === "session_init") {
8763
8979
  currentAgentSessionId = event.sessionId;
8764
8980
  if (sessionKey) storeAgentSessionId(sessionKey, event.sessionId);
@@ -8826,7 +9042,7 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
8826
9042
  console.error(`[profile-reflection] Unhandled error: ${err instanceof Error ? err.message : String(err)}`);
8827
9043
  });
8828
9044
  }
8829
- clearAgentSessionId(sessionKey);
9045
+ clearAgentSessionId(sessionKey, "compaction-complete");
8830
9046
  const convId = sessionStore.get(sessionKey)?.conversationId;
8831
9047
  if (convId) {
8832
9048
  yield { type: "status", message: "Recovering context..." };
@@ -8890,7 +9106,7 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
8890
9106
  } else if (event.type === "subagent_stalled") {
8891
9107
  if (sessionKey) {
8892
9108
  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);
9109
+ clearAgentSessionId(sessionKey, "subagent-stalled");
8894
9110
  }
8895
9111
  const taskPreview = (event.task || "").length > 120 ? event.task.slice(0, 120) + "\u2026" : event.task || "";
8896
9112
  streamLog.write(`[${isoTs()}] [stall-recovery] name=${event.name} task=${JSON.stringify(taskPreview)} \u2014 informing user and closing stream
@@ -8905,7 +9121,7 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
8905
9121
  streamLog.write(`[${isoTs()}] [main-stream-stall-recovery] elapsed=${elapsedSec}s last_event=${event.last_event_type} \u2014 killing process and closing stream
8906
9122
  `);
8907
9123
  proc.kill("SIGTERM");
8908
- if (sessionKey) clearAgentSessionId(sessionKey);
9124
+ if (sessionKey) clearAgentSessionId(sessionKey, "main-stream-stalled");
8909
9125
  yield { type: "text", content: `The response stalled after ${elapsedSec} seconds of silence. Tap Retry to try again.` };
8910
9126
  yield { type: "component", name: "action-buttons", data: { buttons: [{ label: "Retry", value: "Please retry my last request." }] } };
8911
9127
  gotDone = true;
@@ -8915,7 +9131,7 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
8915
9131
  if (event.subtype === "error_max_turns" && event.stop_reason === "tool_use") {
8916
9132
  streamLog.write(`[${isoTs()}] [max-turns-interrupted] subtype=${event.subtype} stop_reason=${event.stop_reason} \u2014 entering recovery
8917
9133
  `);
8918
- if (sessionKey) clearAgentSessionId(sessionKey);
9134
+ if (sessionKey) clearAgentSessionId(sessionKey, "max-turns-interrupted");
8919
9135
  } else {
8920
9136
  gotDone = true;
8921
9137
  if (!sessionWasReset) {
@@ -8951,7 +9167,7 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
8951
9167
  yield event;
8952
9168
  }
8953
9169
  if (!gotDone) {
8954
- if (sessionKey) clearAgentSessionId(sessionKey);
9170
+ if (sessionKey) clearAgentSessionId(sessionKey, "context-overflow-recovery");
8955
9171
  const hasWork = responseText.length > 0 || toolCalls.length > 0;
8956
9172
  if (hasWork && retryCount === 0 && sessionKey) {
8957
9173
  streamLog.write(`[${isoTs()}] [context-overflow-recovery] detected sessionKey=${sessionKey.slice(0, 8)}\u2026 responseTextLen=${responseText.length} toolCallCount=${toolCalls.length}
@@ -9086,7 +9302,7 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
9086
9302
  const pendingToolCalls = /* @__PURE__ */ new Map();
9087
9303
  const renderedComponents = [];
9088
9304
  let capturedTokens;
9089
- for await (const event of withSubagentHeartbeat(parseClaudeStream(proc, streamLog, adminModel), streamLog)) {
9305
+ for await (const event of withSubagentHeartbeat(parseClaudeStream(proc, streamLog, adminModel, managedConvId), streamLog)) {
9090
9306
  if (event.type === "text") {
9091
9307
  responseText += event.content;
9092
9308
  } else if (event.type === "usage") {
@@ -9588,7 +9804,7 @@ ${EXPLANATORY_STYLE_INSTRUCTIONS}` : baseSystemPrompt;
9588
9804
  console.error(`[profile-reflection] Unhandled error: ${err instanceof Error ? err.message : String(err)}`);
9589
9805
  });
9590
9806
  }
9591
- clearAgentSessionId(sessionKey);
9807
+ clearAgentSessionId(sessionKey, "session-compact-complete");
9592
9808
  return { ok: step.value.ok };
9593
9809
  }
9594
9810
  async function* invokeAgent(config2, message, sessionKey, attachments = [], userTimestamp, gatewayResult) {
@@ -9658,8 +9874,9 @@ $ACCOUNT_DIR is ${account.accountDir}
9658
9874
  ${profileSummary}`;
9659
9875
  }
9660
9876
  }
9877
+ const invokeConvId = sessionKey ? getConversationIdForSession(sessionKey) : void 0;
9661
9878
  const [sessionContext, onboardingStep] = await Promise.all([
9662
- loadSessionContext(accountId),
9879
+ loadSessionContext(accountId, invokeConvId),
9663
9880
  loadOnboardingStep(accountId)
9664
9881
  ]);
9665
9882
  if (sessionContext) {
@@ -9874,7 +10091,7 @@ ${block}`;
9874
10091
  import { basename as basename2 } from "path";
9875
10092
 
9876
10093
  // 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";
10094
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync7, statSync as statSync4, mkdirSync as mkdirSync5, renameSync } from "fs";
9878
10095
  import { resolve as resolve7, dirname as dirname2 } from "path";
9879
10096
  var DEFAULT_SCAN_INTERVAL_MS = 5e3;
9880
10097
  var RATE_LIMIT_PATTERN = "rate[- ]?limit(?:ed| reached| hit)|(?:HTTP|status)[^a-z]{0,3}429|too many requests";
@@ -9898,6 +10115,7 @@ var VALID_SOURCES = /* @__PURE__ */ new Set([
9898
10115
  "mcp",
9899
10116
  "config-dir"
9900
10117
  ]);
10118
+ var VALID_SCOPES = /* @__PURE__ */ new Set(["global", "session"]);
9901
10119
  function defaultRules() {
9902
10120
  return [
9903
10121
  {
@@ -9972,6 +10190,20 @@ function defaultRules() {
9972
10190
  thresholdCount: 0,
9973
10191
  thresholdWindowMinutes: 0,
9974
10192
  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."
10193
+ },
10194
+ {
10195
+ // Task 530: catches the bridgeai-style class where a single conversation
10196
+ // sees the same tool error repeatedly and the agent silently falls back.
10197
+ // Session-scoped so cross-conversation coincidence doesn't trigger it.
10198
+ id: "tool-result-recurring-errors",
10199
+ name: "Tool result errors recurring in a conversation",
10200
+ type: "repeated-error",
10201
+ logSource: "system",
10202
+ pattern: "\\[tool-result\\].*error=true",
10203
+ thresholdCount: 2,
10204
+ thresholdWindowMinutes: 5,
10205
+ scope: "session",
10206
+ 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.'
9975
10207
  }
9976
10208
  ];
9977
10209
  }
@@ -10006,7 +10238,7 @@ function loadRules(configDir2) {
10006
10238
  function rulesFileMtime(configDir2) {
10007
10239
  const path2 = rulesFilePath(configDir2);
10008
10240
  try {
10009
- return statSync3(path2).mtimeMs;
10241
+ return statSync4(path2).mtimeMs;
10010
10242
  } catch {
10011
10243
  return null;
10012
10244
  }
@@ -10117,6 +10349,12 @@ function validateRule(input, label, seenIds) {
10117
10349
  if (typeof r.watchPath === "string") rule.watchPath = r.watchPath;
10118
10350
  if (typeof r.staleHours === "number") rule.staleHours = r.staleHours;
10119
10351
  if (typeof r.suppressedUntil === "string") rule.suppressedUntil = r.suppressedUntil;
10352
+ if (r.scope !== void 0) {
10353
+ if (typeof r.scope !== "string" || !VALID_SCOPES.has(r.scope)) {
10354
+ throw new Error(`${label}: scope must be one of ${[...VALID_SCOPES].join(", ")}`);
10355
+ }
10356
+ rule.scope = r.scope;
10357
+ }
10120
10358
  if (rule.type === "file-write-storm" || rule.type === "stale-log") {
10121
10359
  if (!rule.watchPath) {
10122
10360
  throw new Error(`${label}: ${rule.type} rules require watchPath`);
@@ -10131,7 +10369,7 @@ function validateRule(input, label, seenIds) {
10131
10369
  }
10132
10370
 
10133
10371
  // 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";
10372
+ 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
10373
  import { resolve as resolve8, join as join5, basename, dirname as dirname3 } from "path";
10136
10374
  function tailStatePath(configDir2) {
10137
10375
  return resolve8(configDir2, "review-state.json");
@@ -10202,7 +10440,7 @@ function discoverAllSources(configDir2, accountLogDir2) {
10202
10440
  }
10203
10441
  function readNewLines(filepath, prev) {
10204
10442
  if (!existsSync8(filepath)) return null;
10205
- const stat4 = statSync4(filepath);
10443
+ const stat4 = statSync5(filepath);
10206
10444
  const size = stat4.size;
10207
10445
  const inode = stat4.ino;
10208
10446
  let startOffset = 0;
@@ -10227,12 +10465,12 @@ function readNewLines(filepath, prev) {
10227
10465
  truncated
10228
10466
  };
10229
10467
  }
10230
- const fd = openSync(filepath, "r");
10468
+ const fd = openSync2(filepath, "r");
10231
10469
  try {
10232
10470
  const bufSize = Math.max(0, size - startOffset);
10233
10471
  const buf = Buffer.alloc(bufSize);
10234
10472
  if (bufSize > 0) {
10235
- readSync(fd, buf, 0, bufSize, startOffset);
10473
+ readSync2(fd, buf, 0, bufSize, startOffset);
10236
10474
  }
10237
10475
  const text = buf.toString("utf-8");
10238
10476
  const lines = text.length > 0 ? text.split("\n") : [];
@@ -10250,7 +10488,7 @@ function readNewLines(filepath, prev) {
10250
10488
  truncated
10251
10489
  };
10252
10490
  } finally {
10253
- closeSync(fd);
10491
+ closeSync2(fd);
10254
10492
  }
10255
10493
  }
10256
10494
  function countRecentWrites(dir, sinceMs) {
@@ -10259,7 +10497,7 @@ function countRecentWrites(dir, sinceMs) {
10259
10497
  for (const entry of readdirSync3(dir, { withFileTypes: true })) {
10260
10498
  if (!entry.isFile()) continue;
10261
10499
  try {
10262
- const st = statSync4(join5(dir, entry.name));
10500
+ const st = statSync5(join5(dir, entry.name));
10263
10501
  if (st.mtimeMs >= sinceMs) count += 1;
10264
10502
  } catch {
10265
10503
  }
@@ -10269,7 +10507,7 @@ function countRecentWrites(dir, sinceMs) {
10269
10507
  function fileLastWriteMs(path2) {
10270
10508
  if (!existsSync8(path2)) return null;
10271
10509
  try {
10272
- return statSync4(path2).mtimeMs;
10510
+ return statSync5(path2).mtimeMs;
10273
10511
  } catch {
10274
10512
  return null;
10275
10513
  }
@@ -10282,7 +10520,7 @@ function sourceKey(file2) {
10282
10520
  }
10283
10521
 
10284
10522
  // 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";
10523
+ 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
10524
  import { resolve as resolve9, dirname as dirname4 } from "path";
10287
10525
  import { randomUUID as randomUUID3 } from "crypto";
10288
10526
  function reviewLogPath(configDir2) {
@@ -10548,6 +10786,12 @@ import { resolve as resolve10 } from "path";
10548
10786
 
10549
10787
  // app/lib/review-detector/evaluator.ts
10550
10788
  var SAMPLE_MAX_CHARS = 500;
10789
+ var CONV_ID_REGEX = /conversationId=([a-f0-9]{8})/;
10790
+ function scopeKeyFor(rule, line) {
10791
+ if (rule.scope !== "session") return "";
10792
+ const m = CONV_ID_REGEX.exec(line);
10793
+ return m ? m[1] : "";
10794
+ }
10551
10795
  var compiledRegexCache = /* @__PURE__ */ new Map();
10552
10796
  function compileRegex(pattern) {
10553
10797
  let re = compiledRegexCache.get(pattern);
@@ -10568,31 +10812,60 @@ function toSample(line) {
10568
10812
  return line.slice(0, SAMPLE_MAX_CHARS) + "\u2026";
10569
10813
  }
10570
10814
  function newRuleState() {
10571
- return { matchTimestamps: [], lastAlertAt: null, cumulativeSinceLastAlert: 0, lastSeenAt: null };
10815
+ return {
10816
+ matchTimestamps: [],
10817
+ matchTimestampsByScope: /* @__PURE__ */ new Map(),
10818
+ lastAlertAt: null,
10819
+ cumulativeSinceLastAlert: 0,
10820
+ lastSeenAt: null
10821
+ };
10572
10822
  }
10573
10823
  function evaluateTextRule(rule, lines, state, nowMs) {
10574
10824
  if (isSuppressed(rule, nowMs)) return { match: null, state };
10575
10825
  if (rule.pattern.length === 0) return { match: null, state };
10576
10826
  const regex = compileRegex(rule.pattern);
10577
- let sample = null;
10578
- let matchCount = 0;
10827
+ const matchesByScope = /* @__PURE__ */ new Map();
10828
+ let firstSample = null;
10579
10829
  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 };
10830
+ if (!regex.test(line)) continue;
10831
+ if (!firstSample) firstSample = line;
10832
+ const key = scopeKeyFor(rule, line);
10833
+ const existing = matchesByScope.get(key) ?? [];
10834
+ existing.push(line);
10835
+ matchesByScope.set(key, existing);
10836
+ }
10837
+ if (matchesByScope.size === 0) return { match: null, state };
10838
+ const windowStart = rule.thresholdWindowMinutes > 0 ? nowMs - rule.thresholdWindowMinutes * 6e4 : -Infinity;
10586
10839
  const updated = {
10587
10840
  ...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;
10841
+ matchTimestamps: [...state.matchTimestamps],
10842
+ matchTimestampsByScope: new Map(state.matchTimestampsByScope ?? [])
10843
+ };
10844
+ let firingSample = null;
10845
+ let fires = false;
10846
+ const isCountZero = rule.thresholdCount === 0;
10847
+ if (rule.scope === "session") {
10848
+ for (const [key, hits] of matchesByScope) {
10849
+ const prior = updated.matchTimestampsByScope.get(key) ?? [];
10850
+ const merged = [...prior, ...hits.map(() => nowMs)].filter((t) => t >= windowStart);
10851
+ if (merged.length === 0) {
10852
+ updated.matchTimestampsByScope.delete(key);
10853
+ } else {
10854
+ updated.matchTimestampsByScope.set(key, merged);
10855
+ }
10856
+ if (!fires && (isCountZero || merged.length >= rule.thresholdCount)) {
10857
+ fires = true;
10858
+ firingSample = hits[0];
10859
+ }
10860
+ }
10861
+ } else {
10862
+ for (const hits of matchesByScope.values()) {
10863
+ for (const _ of hits) updated.matchTimestamps.push(nowMs);
10864
+ }
10593
10865
  updated.matchTimestamps = updated.matchTimestamps.filter((t) => t >= windowStart);
10866
+ fires = isCountZero || updated.matchTimestamps.length >= rule.thresholdCount;
10867
+ if (fires) firingSample = firstSample;
10594
10868
  }
10595
- const fires = rule.thresholdCount === 0 ? true : updated.matchTimestamps.length >= rule.thresholdCount;
10596
10869
  if (!fires) {
10597
10870
  return { match: null, state: updated };
10598
10871
  }
@@ -10600,7 +10873,7 @@ function evaluateTextRule(rule, lines, state, nowMs) {
10600
10873
  ruleId: rule.id,
10601
10874
  ruleName: rule.name,
10602
10875
  matchedAt: nowMs,
10603
- sampleEvidence: toSample(sample ?? lines[0] ?? ""),
10876
+ sampleEvidence: toSample(firingSample ?? firstSample ?? lines[0] ?? ""),
10604
10877
  suggestedAction: rule.suggestedAction
10605
10878
  };
10606
10879
  return { match: match2, state: updated };
@@ -29925,7 +30198,7 @@ async function POST16(req) {
29925
30198
 
29926
30199
  // app/api/onboarding/claude-auth/route.ts
29927
30200
  import { spawn as spawn3, execFileSync as execFileSync2 } from "child_process";
29928
- import { openSync as openSync2, closeSync as closeSync2, writeFileSync as writeFileSync12, writeSync } from "fs";
30201
+ import { openSync as openSync3, closeSync as closeSync3, writeFileSync as writeFileSync12, writeSync } from "fs";
29929
30202
  function checkAuthStatus() {
29930
30203
  try {
29931
30204
  execFileSync2("claude", ["auth", "status"], { encoding: "utf-8", timeout: 5e3 });
@@ -29996,7 +30269,7 @@ async function POST17(req, remoteAddress) {
29996
30269
  const chromiumWrapper = writeChromiumWrapper();
29997
30270
  const x11Env = buildX11Env(chromiumWrapper, transport);
29998
30271
  vncLog("claude-auth", { action: "start", transport });
29999
- const claudeAuthLogFd = openSync2(logPath("claude-auth"), "a");
30272
+ const claudeAuthLogFd = openSync3(logPath("claude-auth"), "a");
30000
30273
  const claudeProc = spawn3("claude", ["auth", "login"], {
30001
30274
  env: x11Env,
30002
30275
  stdio: ["ignore", "pipe", "pipe"]
@@ -30005,7 +30278,7 @@ async function POST17(req, remoteAddress) {
30005
30278
  const onClaudeOutput = (chunk) => writeSync(claudeAuthLogFd, chunk);
30006
30279
  claudeProc.stdout?.on("data", onClaudeOutput);
30007
30280
  claudeProc.stderr?.on("data", onClaudeOutput);
30008
- claudeProc.once("close", () => closeSync2(claudeAuthLogFd));
30281
+ claudeProc.once("close", () => closeSync3(claudeAuthLogFd));
30009
30282
  await waitForAuthPage(2e4);
30010
30283
  return Response.json({ started: true, transport });
30011
30284
  }
@@ -30573,7 +30846,7 @@ async function POST22(req) {
30573
30846
  }
30574
30847
 
30575
30848
  // app/api/admin/logs/route.ts
30576
- import { existsSync as existsSync19, readdirSync as readdirSync5, readFileSync as readFileSync20, statSync as statSync6 } from "fs";
30849
+ import { existsSync as existsSync19, readdirSync as readdirSync5, readFileSync as readFileSync20, statSync as statSync7 } from "fs";
30577
30850
  import { resolve as resolve19, basename as basename5 } from "path";
30578
30851
  var TAIL_BYTES = 8192;
30579
30852
  async function GET9(request) {
@@ -30634,7 +30907,7 @@ async function GET9(request) {
30634
30907
  } catch {
30635
30908
  continue;
30636
30909
  }
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 }) => {
30910
+ 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
30911
  seen.add(name);
30639
30912
  try {
30640
30913
  const content = readFileSync20(resolve19(dir, name));
@@ -30894,7 +31167,7 @@ async function GET13() {
30894
31167
 
30895
31168
  // app/api/admin/version/upgrade/route.ts
30896
31169
  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";
31170
+ import { existsSync as existsSync24, statSync as statSync8, writeFileSync as writeFileSync16, readFileSync as readFileSync24, openSync as openSync4, closeSync as closeSync4 } from "fs";
30898
31171
  import { resolve as resolve25, join as join11 } from "path";
30899
31172
  var PLATFORM_ROOT10 = process.env.MAXY_PLATFORM_ROOT ?? resolve25(process.cwd(), "..");
30900
31173
  var upgradePkg = "@rubytech/create-maxy";
@@ -30914,7 +31187,7 @@ var LOCK_MAX_AGE_MS = 20 * 60 * 1e3;
30914
31187
  function isLockFresh() {
30915
31188
  if (!existsSync24(LOCK_FILE)) return false;
30916
31189
  try {
30917
- const stat4 = statSync7(LOCK_FILE);
31190
+ const stat4 = statSync8(LOCK_FILE);
30918
31191
  return Date.now() - stat4.mtimeMs < LOCK_MAX_AGE_MS;
30919
31192
  } catch {
30920
31193
  return false;
@@ -30943,7 +31216,7 @@ async function POST23(req) {
30943
31216
  console.error("[admin/version/upgrade] failed to write lock file:", err);
30944
31217
  }
30945
31218
  try {
30946
- const logFd = openSync3(LOG_FILE, "w");
31219
+ const logFd = openSync4(LOG_FILE, "w");
30947
31220
  const child = spawn4("systemd-run", [
30948
31221
  "--user",
30949
31222
  "--scope",
@@ -30959,7 +31232,7 @@ async function POST23(req) {
30959
31232
  cwd: resolve25(process.cwd(), "..")
30960
31233
  });
30961
31234
  child.unref();
30962
- closeSync3(logFd);
31235
+ closeSync4(logFd);
30963
31236
  console.log(`[admin/version/upgrade] spawned upgrade process (pid ${child.pid})`);
30964
31237
  return Response.json({ ok: true, started: true });
30965
31238
  } catch (err) {