@rubytech/create-realagent 1.0.617 → 1.0.619

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 (24) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/plugins/admin/PLUGIN.md +1 -0
  3. package/payload/platform/plugins/admin/hooks/webfetch-preflight.mjs +363 -0
  4. package/payload/platform/plugins/admin/mcp/dist/lib/review-tools.d.ts.map +1 -1
  5. package/payload/platform/plugins/admin/mcp/dist/lib/review-tools.js +55 -6
  6. package/payload/platform/plugins/admin/mcp/dist/lib/review-tools.js.map +1 -1
  7. package/payload/platform/plugins/admin/skills/stream-log-review/SKILL.md +4 -1
  8. package/payload/platform/plugins/cloudflare/PLUGIN.md +1 -1
  9. package/payload/platform/plugins/cloudflare/mcp/dist/index.js +135 -1
  10. package/payload/platform/plugins/cloudflare/mcp/dist/index.js.map +1 -1
  11. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts +17 -10
  12. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts.map +1 -1
  13. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js +65 -25
  14. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js.map +1 -1
  15. package/payload/platform/plugins/cloudflare/references/setup-guide.md +2 -2
  16. package/payload/platform/plugins/cloudflare/skills/setup-tunnel/SKILL.md +4 -2
  17. package/payload/platform/plugins/docs/references/cloudflare.md +8 -8
  18. package/payload/platform/plugins/docs/references/plugins-guide.md +2 -0
  19. package/payload/platform/scripts/seed-neo4j.sh +12 -0
  20. package/payload/platform/templates/agents/admin/IDENTITY.md +2 -0
  21. package/payload/platform/templates/specialists/agents/personal-assistant.md +5 -5
  22. package/payload/server/public/assets/{admin-Df1liz4Y.js → admin-D7LRdkYB.js} +30 -30
  23. package/payload/server/public/index.html +1 -1
  24. package/payload/server/server.js +204 -25
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Real Agent</title>
7
7
  <link rel="icon" href="/favicon.ico">
8
- <script type="module" crossorigin src="/assets/admin-Df1liz4Y.js"></script>
8
+ <script type="module" crossorigin src="/assets/admin-D7LRdkYB.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/chunk-Be6NvmcD.js">
10
10
  <link rel="modulepreload" crossorigin href="/assets/preload-helper-rov5CBGT.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/useVoiceRecorder-OB_Gtr0e.js">
@@ -6211,14 +6211,22 @@ function purgeOldLogs(logDir, prefix) {
6211
6211
  }
6212
6212
  }
6213
6213
  function teeProcStderrToStreamLog(proc, streamLog) {
6214
+ const pid = proc.pid;
6214
6215
  if (!proc.stderr) {
6215
- streamLog.write(`[${isoTs()}] [subproc-stderr-skip] reason=no-stderr
6216
+ streamLog.write(`[${isoTs()}] [subproc-stderr-skip] reason=no-stderr pid=${pid}
6216
6217
  `);
6217
6218
  return;
6218
6219
  }
6220
+ if (!streamLog.destroyed && !streamLog.writableEnded) {
6221
+ streamLog.write(`[${isoTs()}] [subproc-stderr-tee-attached] pid=${pid}
6222
+ `);
6223
+ }
6219
6224
  const utf8 = new StringDecoder("utf8");
6220
6225
  let buffer = "";
6226
+ let bytesSeen = 0;
6227
+ let linesEmitted = 0;
6221
6228
  proc.stderr.on("data", (chunk) => {
6229
+ bytesSeen += typeof chunk === "string" ? Buffer.byteLength(chunk, "utf8") : chunk.length;
6222
6230
  const text = typeof chunk === "string" ? chunk : utf8.write(chunk);
6223
6231
  buffer += text;
6224
6232
  let idx;
@@ -6229,12 +6237,18 @@ function teeProcStderrToStreamLog(proc, streamLog) {
6229
6237
  if (streamLog.destroyed || streamLog.writableEnded) continue;
6230
6238
  streamLog.write(`[${isoTs()}] [subproc-stderr] ${line}
6231
6239
  `);
6240
+ linesEmitted++;
6232
6241
  }
6233
6242
  });
6234
6243
  proc.stderr.on("end", () => {
6235
6244
  const tail = (buffer + utf8.end()).trim();
6236
6245
  if (tail.length > 0 && !streamLog.destroyed && !streamLog.writableEnded) {
6237
6246
  streamLog.write(`[${isoTs()}] [subproc-stderr] ${tail}
6247
+ `);
6248
+ linesEmitted++;
6249
+ }
6250
+ if (!streamLog.destroyed && !streamLog.writableEnded) {
6251
+ streamLog.write(`[${isoTs()}] [subproc-stderr-tee-detached] pid=${pid} bytes=${bytesSeen} lines=${linesEmitted}
6238
6252
  `);
6239
6253
  }
6240
6254
  buffer = "";
@@ -8284,10 +8298,11 @@ async function* runCompactionTurn(accountDir, accountId, systemPrompt, resumeSes
8284
8298
  env: {
8285
8299
  ...process.env,
8286
8300
  PLATFORM_ROOT: PLATFORM_ROOT4,
8287
- ACCOUNT_DIR: accountDir,
8288
- // Task 532: network traces on the compaction subprocess too its tool
8289
- // calls are part of the same conversation's record.
8290
- NODE_DEBUG: "http,http2,net,tls,undici,dns"
8301
+ ACCOUNT_DIR: accountDir
8302
+ // Task 535: NODE_DEBUG removed. The Claude Code CLI is a bundled Bun
8303
+ // binary and Bun ignores Node's NODE_DEBUG flag, so setting it here was
8304
+ // a no-op that misled future readers. The [subproc-debug-unavailable]
8305
+ // line below records the source-of-silence explicitly.
8291
8306
  }
8292
8307
  });
8293
8308
  const stderrLog = agentLogStream("claude-agent-compaction-stderr", accountDir, conversationId);
@@ -8298,6 +8313,8 @@ async function* runCompactionTurn(accountDir, accountId, systemPrompt, resumeSes
8298
8313
  streamLog.on("error", () => {
8299
8314
  });
8300
8315
  teeProcStderrToStreamLog(proc, streamLog);
8316
+ streamLog.write(`[${isoTs()}] [subproc-debug-unavailable] reason=bundled-bun-binary-ignores-node-debug pid=${proc.pid} cli=claude
8317
+ `);
8301
8318
  streamLog.write(`[${isoTs()}] [compaction-start] resumeSessionId=${resumeSessionId}
8302
8319
  `);
8303
8320
  proc.on("error", (err) => {
@@ -9171,11 +9188,11 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
9171
9188
  env: {
9172
9189
  ...process.env,
9173
9190
  PLATFORM_ROOT: PLATFORM_ROOT4,
9174
- ACCOUNT_DIR: accountDir,
9175
- // Task 532: tee subprocess network activity into the per-conversation
9176
- // stream log via the existing stderr pipe. Closes the Claude Code
9177
- // "what was the tool actually doing?" black box during tool waits.
9178
- NODE_DEBUG: "http,http2,net,tls,undici,dns"
9191
+ ACCOUNT_DIR: accountDir
9192
+ // Task 535: NODE_DEBUG removed. The Claude Code CLI is a bundled Bun
9193
+ // binary and Bun ignores Node's NODE_DEBUG flag, so setting it here was
9194
+ // a no-op that misled future readers. The [subproc-debug-unavailable]
9195
+ // line below records the source-of-silence explicitly.
9179
9196
  }
9180
9197
  });
9181
9198
  const stderrLog = agentLogStream("claude-agent-stderr", accountDir, spawnConvId);
@@ -9186,6 +9203,8 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
9186
9203
  streamLog.on("error", () => {
9187
9204
  });
9188
9205
  teeProcStderrToStreamLog(proc, streamLog);
9206
+ streamLog.write(`[${isoTs()}] [subproc-debug-unavailable] reason=bundled-bun-binary-ignores-node-debug pid=${proc.pid} cli=claude
9207
+ `);
9189
9208
  if (sessionKey) {
9190
9209
  const prev = activeProcesses.get(sessionKey);
9191
9210
  if (prev) {
@@ -9513,10 +9532,11 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
9513
9532
  env: {
9514
9533
  ...process.env,
9515
9534
  PLATFORM_ROOT: PLATFORM_ROOT4,
9516
- ACCOUNT_DIR: accountDir,
9517
- // Task 532: tee subprocess network traces into the per-conversation
9518
- // stream log via the stderr pipe + dual-consume listener.
9519
- NODE_DEBUG: "http,http2,net,tls,undici,dns"
9535
+ ACCOUNT_DIR: accountDir
9536
+ // Task 535: NODE_DEBUG removed. The Claude Code CLI is a bundled Bun
9537
+ // binary and Bun ignores Node's NODE_DEBUG flag, so setting it here was
9538
+ // a no-op that misled future readers. The [subproc-debug-unavailable]
9539
+ // line below records the source-of-silence explicitly.
9520
9540
  }
9521
9541
  });
9522
9542
  const stderrLog = agentLogStream("claude-agent-stderr", accountDir, managedConvId);
@@ -9524,6 +9544,8 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
9524
9544
  });
9525
9545
  proc.stderr?.pipe(stderrLog);
9526
9546
  teeProcStderrToStreamLog(proc, streamLog);
9547
+ streamLog.write(`[${isoTs()}] [subproc-debug-unavailable] reason=bundled-bun-binary-ignores-node-debug pid=${proc.pid} cli=claude
9548
+ `);
9527
9549
  if (sessionKey) {
9528
9550
  const prev = activeProcesses.get(sessionKey);
9529
9551
  if (prev) {
@@ -10360,8 +10382,10 @@ var VALID_TYPES = /* @__PURE__ */ new Set([
10360
10382
  "silent-catch",
10361
10383
  "file-write-storm",
10362
10384
  "stale-log",
10363
- "rate-limit"
10385
+ "rate-limit",
10386
+ "absent-followup"
10364
10387
  ]);
10388
+ var MAX_FOLLOWUP_WINDOW_MS = 6e5;
10365
10389
  var VALID_SOURCES = /* @__PURE__ */ new Set([
10366
10390
  "any",
10367
10391
  "server",
@@ -10480,6 +10504,46 @@ function defaultRules() {
10480
10504
  scope: "session",
10481
10505
  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."
10482
10506
  },
10507
+ {
10508
+ // Task 536: detect agents ignoring the WEBFETCH_CANNOT_READ_JS_SPA
10509
+ // structured failure. A single SPA short-circuit per conversation is
10510
+ // expected — the hook is doing its job. Two or more in the same
10511
+ // conversation within 5 minutes means either (a) the agent retried
10512
+ // WebFetch on the same SPA URL despite the directive, or (b) the
10513
+ // owner is asking about multiple SPA URLs in one session and the
10514
+ // pattern needs surfacing as a recurring class. Both signal that the
10515
+ // IDENTITY.md "Tool Failure Discipline" guidance is not landing in the
10516
+ // prompt — revise the copy rather than add mechanical enforcement.
10517
+ id: "webfetch-spa-short-circuit-recurring",
10518
+ name: "WebFetch JS-SPA short-circuit fired repeatedly in conversation",
10519
+ type: "repeated-error",
10520
+ logSource: "system",
10521
+ pattern: "WEBFETCH_CANNOT_READ_JS_SPA",
10522
+ thresholdCount: 2,
10523
+ thresholdWindowMinutes: 5,
10524
+ scope: "session",
10525
+ suggestedAction: "The WebFetch SPA preflight has fired more than once in this conversation. Either the agent is ignoring the loud-failure directive (retrying WebFetch after seeing WEBFETCH_CANNOT_READ_JS_SPA), or multiple SPA URLs are being asked about. Read the conversation's stream log for the [tool-use] / [tool-result] sequence around each occurrence \u2014 if the agent dispatched WebFetch on the same URL or substituted Playwright silently, revisit the IDENTITY.md `Tool Failure Discipline` paragraph that names structured-error handling."
10526
+ },
10527
+ {
10528
+ // Task 538: fires when a [spawn] line appears in a conversation's stream
10529
+ // log but no subprocess-lifecycle marker follows within 10s. The three
10530
+ // acceptable followups are Task 535's contract — at least one must be
10531
+ // emitted immediately at every spawn site. Their absence means
10532
+ // `teeProcStderrToStreamLog` regressed, the markers drifted, or the
10533
+ // spawn site was added without wiring them up. Session scope so a single
10534
+ // broken conversation fires exactly once, not N times for every spawn.
10535
+ id: "subproc-tee-silent-spawn",
10536
+ name: "Subprocess spawn without a stderr-tee lifecycle marker",
10537
+ type: "absent-followup",
10538
+ logSource: "system",
10539
+ pattern: "\\[spawn\\] pid=\\d+",
10540
+ followupPattern: "\\[subproc-stderr-tee-attached\\]|\\[subproc-debug-unavailable\\]|\\[subproc-stderr-skip\\]",
10541
+ followupWindowMs: 1e4,
10542
+ thresholdCount: 0,
10543
+ thresholdWindowMinutes: 0,
10544
+ scope: "session",
10545
+ suggestedAction: "The main-subprocess tee infrastructure has regressed \u2014 a spawn produced no lifecycle marker. Re-check `teeProcStderrToStreamLog` is invoked at the spawn site and that `[subproc-debug-unavailable]` or `[subproc-stderr-skip]` is written immediately when the tee cannot attach (Task 535 contract)."
10546
+ },
10483
10547
  {
10484
10548
  // Task 533: surface every Cloudflare-plugin refusal. The plugin emits
10485
10549
  // exactly one [cloudflare:refuse] line per refusal with a structured
@@ -10638,6 +10702,17 @@ function validateRule(input, label, seenIds) {
10638
10702
  };
10639
10703
  if (typeof r.watchPath === "string") rule.watchPath = r.watchPath;
10640
10704
  if (typeof r.staleHours === "number") rule.staleHours = r.staleHours;
10705
+ if (typeof r.followupPattern === "string") {
10706
+ if (r.followupPattern.length > 0) {
10707
+ try {
10708
+ new RegExp(r.followupPattern);
10709
+ } catch (err) {
10710
+ throw new Error(`${label}: followupPattern is not a valid regex: ${err instanceof Error ? err.message : String(err)}`);
10711
+ }
10712
+ }
10713
+ rule.followupPattern = r.followupPattern;
10714
+ }
10715
+ if (typeof r.followupWindowMs === "number") rule.followupWindowMs = r.followupWindowMs;
10641
10716
  if (typeof r.suppressedUntil === "string") rule.suppressedUntil = r.suppressedUntil;
10642
10717
  if (r.scope !== void 0) {
10643
10718
  if (typeof r.scope !== "string" || !VALID_SCOPES.has(r.scope)) {
@@ -10655,6 +10730,17 @@ function validateRule(input, label, seenIds) {
10655
10730
  throw new Error(`${label}: stale-log rules require a positive staleHours`);
10656
10731
  }
10657
10732
  }
10733
+ if (rule.type === "absent-followup") {
10734
+ if (rule.pattern.length === 0) {
10735
+ throw new Error(`${label}: absent-followup rules require a non-empty pattern`);
10736
+ }
10737
+ if (typeof rule.followupPattern !== "string" || rule.followupPattern.length === 0) {
10738
+ throw new Error(`${label}: absent-followup rules require a non-empty followupPattern`);
10739
+ }
10740
+ if (typeof rule.followupWindowMs !== "number" || rule.followupWindowMs <= 0 || rule.followupWindowMs > MAX_FOLLOWUP_WINDOW_MS) {
10741
+ throw new Error(`${label}: absent-followup rules require followupWindowMs in (0, ${MAX_FOLLOWUP_WINDOW_MS}]`);
10742
+ }
10743
+ }
10658
10744
  return rule;
10659
10745
  }
10660
10746
 
@@ -11126,7 +11212,8 @@ function newRuleState() {
11126
11212
  matchTimestampsByScope: /* @__PURE__ */ new Map(),
11127
11213
  lastAlertAt: null,
11128
11214
  cumulativeSinceLastAlert: 0,
11129
- lastSeenAt: null
11215
+ lastSeenAt: null,
11216
+ pendingFollowups: []
11130
11217
  };
11131
11218
  }
11132
11219
  function evaluateTextRule(rule, lines, state, nowMs) {
@@ -11224,6 +11311,58 @@ function evaluateStaleLogRule(rule, lastMtimeMs, state, nowMs) {
11224
11311
  };
11225
11312
  return { match: match2, state: updated };
11226
11313
  }
11314
+ function evaluateAbsentFollowupRule(rule, lines, state, nowMs) {
11315
+ if (isSuppressed(rule, nowMs)) return { matches: [], state };
11316
+ if (!rule.pattern || !rule.followupPattern || !rule.followupWindowMs) {
11317
+ return { matches: [], state };
11318
+ }
11319
+ const triggerRegex = compileRegex(rule.pattern);
11320
+ const followupRegex = compileRegex(rule.followupPattern);
11321
+ const pending = [...state.pendingFollowups ?? []];
11322
+ for (const line of lines) {
11323
+ if (triggerRegex.test(line)) {
11324
+ pending.push({
11325
+ scope: scopeKeyFor(rule, line),
11326
+ timestamp: nowMs,
11327
+ line,
11328
+ fulfilled: false
11329
+ });
11330
+ continue;
11331
+ }
11332
+ if (followupRegex.test(line)) {
11333
+ const scope = scopeKeyFor(rule, line);
11334
+ for (const entry of pending) {
11335
+ if (!entry.fulfilled && entry.scope === scope) {
11336
+ entry.fulfilled = true;
11337
+ break;
11338
+ }
11339
+ }
11340
+ }
11341
+ }
11342
+ const matches = [];
11343
+ const kept = [];
11344
+ for (const entry of pending) {
11345
+ const age = nowMs - entry.timestamp;
11346
+ if (age >= rule.followupWindowMs) {
11347
+ if (!entry.fulfilled) {
11348
+ matches.push({
11349
+ ruleId: rule.id,
11350
+ ruleName: rule.name,
11351
+ matchedAt: nowMs,
11352
+ sampleEvidence: toSample(entry.line),
11353
+ suggestedAction: rule.suggestedAction,
11354
+ missedForMs: age
11355
+ });
11356
+ }
11357
+ continue;
11358
+ }
11359
+ kept.push(entry);
11360
+ }
11361
+ return {
11362
+ matches,
11363
+ state: { ...state, pendingFollowups: kept }
11364
+ };
11365
+ }
11227
11366
  var ALERT_WINDOW_MS = 60 * 60 * 1e3;
11228
11367
  function rateLimitDecision(state, nowMs) {
11229
11368
  const since = state.lastAlertAt === null ? Infinity : nowMs - state.lastAlertAt;
@@ -11344,6 +11483,24 @@ async function runScanCycle(runtime) {
11344
11483
  const result = evaluateStaleLogRule(rule, lastMs, state, cycleStart);
11345
11484
  state = result.state;
11346
11485
  match2 = result.match;
11486
+ } else if (rule.type === "absent-followup") {
11487
+ let inputLines = [];
11488
+ if (rule.logSource === "any") {
11489
+ for (const [src, lines] of linesBySource.entries()) {
11490
+ if (src !== "config-dir") inputLines.push(...lines);
11491
+ }
11492
+ } else {
11493
+ inputLines = linesBySource.get(rule.logSource) ?? [];
11494
+ }
11495
+ const result = evaluateAbsentFollowupRule(rule, inputLines, state, cycleStart);
11496
+ state = result.state;
11497
+ for (const m of result.matches) {
11498
+ const safeTrigger = m.sampleEvidence.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
11499
+ console.error(
11500
+ `[review-detector] absent-followup rule=${rule.id} trigger="${safeTrigger}" missed_for_ms=${m.missedForMs ?? ""}`
11501
+ );
11502
+ matches.push(m);
11503
+ }
11347
11504
  }
11348
11505
  runtime.ruleState.set(rule.id, state);
11349
11506
  if (match2) matches.push(match2);
@@ -31172,18 +31329,23 @@ async function GET9(request) {
31172
31329
  const accountLogDir2 = account ? resolve19(account.accountDir, "logs") : null;
31173
31330
  if (fileParam) {
31174
31331
  const safe = basename5(fileParam);
31332
+ const searched = [];
31175
31333
  for (const dir of [accountLogDir2, LOG_DIR]) {
31176
31334
  if (!dir) continue;
31177
31335
  const filePath = resolve19(dir, safe);
31336
+ searched.push(filePath);
31178
31337
  try {
31179
31338
  const content = readFileSync20(filePath, "utf-8");
31180
31339
  const headers = { "Content-Type": "text/plain; charset=utf-8" };
31181
31340
  if (download) headers["Content-Disposition"] = `attachment; filename="${safe}"`;
31182
31341
  return new Response(content, { headers });
31183
- } catch {
31342
+ } catch (err) {
31343
+ const reason = err instanceof Error ? err.message : String(err);
31344
+ console.debug(`[admin/logs] miss dir=${dir} name=${safe} reason=${reason}`);
31184
31345
  }
31185
31346
  }
31186
- return Response.json({ error: `File not found: ${safe}` }, { status: 404 });
31347
+ console.warn(`[admin/logs] not-found name=${safe} searched=[${searched.join(",")}]`);
31348
+ return Response.json({ error: `File not found: ${safe}`, code: "NOT_FOUND" }, { status: 404 });
31187
31349
  }
31188
31350
  if (typeParam) {
31189
31351
  const prefixMap = {
@@ -31195,24 +31357,37 @@ async function GET9(request) {
31195
31357
  };
31196
31358
  const prefix = prefixMap[typeParam];
31197
31359
  if (!prefix) {
31198
- return Response.json({ error: `Unknown type: ${typeParam}. Valid: stream, error, session, sse, public` }, { status: 400 });
31360
+ console.warn(`[admin/logs] rejected reason=unknown-type type=${typeParam}`);
31361
+ return Response.json(
31362
+ { error: `Unknown type: ${typeParam}. Valid: stream, error, session, sse, public`, code: "UNKNOWN_TYPE" },
31363
+ { status: 400 }
31364
+ );
31199
31365
  }
31200
31366
  if (!conversationIdParam) {
31201
- return Response.json({ error: `type=${typeParam} requires conversationId (per-conversation log files, no daily fallback)` }, { status: 400 });
31367
+ console.warn(`[admin/logs] rejected type=${typeParam} reason=no-conversationId`);
31368
+ return Response.json(
31369
+ { error: `type=${typeParam} requires conversationId (per-conversation log files, no daily fallback)`, code: "CONVERSATION_ID_REQUIRED" },
31370
+ { status: 400 }
31371
+ );
31202
31372
  }
31203
31373
  const fileName = `${prefix}-${conversationIdParam}.log`;
31374
+ const searched = [];
31204
31375
  for (const dir of [accountLogDir2, LOG_DIR]) {
31205
31376
  if (!dir) continue;
31206
31377
  const filePath = resolve19(dir, fileName);
31378
+ searched.push(filePath);
31207
31379
  try {
31208
31380
  const content = readFileSync20(filePath, "utf-8");
31209
31381
  const headers = { "Content-Type": "text/plain; charset=utf-8" };
31210
31382
  if (download) headers["Content-Disposition"] = `attachment; filename="${fileName}"`;
31211
31383
  return new Response(content, { headers });
31212
- } catch {
31384
+ } catch (err) {
31385
+ const reason = err instanceof Error ? err.message : String(err);
31386
+ console.debug(`[admin/logs] miss dir=${dir} name=${fileName} reason=${reason}`);
31213
31387
  }
31214
31388
  }
31215
- return Response.json({ error: `Log not found: ${fileName}` }, { status: 404 });
31389
+ console.warn(`[admin/logs] not-found name=${fileName} searched=[${searched.join(",")}]`);
31390
+ return Response.json({ error: `Log not found: ${fileName}`, code: "NOT_FOUND" }, { status: 404 });
31216
31391
  }
31217
31392
  const seen = /* @__PURE__ */ new Set();
31218
31393
  const logs = {};
@@ -31221,7 +31396,9 @@ async function GET9(request) {
31221
31396
  let files;
31222
31397
  try {
31223
31398
  files = readdirSync5(dir).filter((f) => f.endsWith(".log"));
31224
- } catch {
31399
+ } catch (err) {
31400
+ const reason = err instanceof Error ? err.message : String(err);
31401
+ console.warn(`[admin/logs] readdir-fail dir=${dir} reason=${reason}`);
31225
31402
  continue;
31226
31403
  }
31227
31404
  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 }) => {
@@ -31230,8 +31407,10 @@ async function GET9(request) {
31230
31407
  const content = readFileSync20(resolve19(dir, name));
31231
31408
  const tail = content.length > TAIL_BYTES ? content.subarray(content.length - TAIL_BYTES).toString("utf-8") : content.toString("utf-8");
31232
31409
  logs[name] = tail.trim() || "(empty)";
31233
- } catch {
31234
- logs[name] = "(unreadable)";
31410
+ } catch (err) {
31411
+ const reason = err instanceof Error ? err.message : String(err);
31412
+ console.debug(`[admin/logs] read-fail name=${name} reason=${reason}`);
31413
+ logs[name] = `(unreadable: ${reason})`;
31235
31414
  }
31236
31415
  });
31237
31416
  }