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