negotium 0.1.14 → 0.1.16
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/{chunk-a9y2rwxf.js → chunk-38pf4yqx.js} +1 -2
- package/dist/{chunk-a9y2rwxf.js.map → chunk-38pf4yqx.js.map} +3 -3
- package/dist/cron.js +273 -265
- package/dist/cron.js.map +17 -15
- package/dist/hosted-agent.js +47 -23
- package/dist/hosted-agent.js.map +10 -8
- package/dist/main.js +947 -348
- package/dist/main.js.map +34 -31
- package/dist/mcp-servers.js +1 -3
- package/dist/mcp-servers.js.map +3 -3
- package/dist/prompts.js +1 -2
- package/dist/prompts.js.map +3 -3
- package/dist/registry.js +1 -1
- package/dist/rollout.js +1 -1
- package/dist/runtime/src/agents/claude-provider.ts +2 -2
- package/dist/runtime/src/agents/codex-provider.ts +1 -8
- package/dist/runtime/src/application/switch-topic-access-mode.ts +37 -0
- package/dist/runtime/src/index.ts +5 -0
- package/dist/runtime/src/mcp/background-bash-server.ts +120 -25
- package/dist/runtime/src/platform/background-bash/context.ts +9 -0
- package/dist/runtime/src/platform/background-bash/manager.ts +64 -94
- package/dist/runtime/src/platform/config.ts +0 -2
- package/dist/runtime/src/platform/mcp-config.ts +30 -13
- package/dist/runtime/src/prompts/sessions/manager-system.md +3 -3
- package/dist/runtime/src/query/active-rooms.ts +1 -0
- package/dist/runtime/src/runtime/turn-runner.ts +33 -7
- package/dist/runtime/src/storage/api-messages.ts +16 -2
- package/dist/runtime/src/topics/derive.ts +1 -0
- package/dist/runtime/src/types/api.ts +3 -0
- package/dist/runtime/src/version.ts +1 -1
- package/dist/runtime-helpers.js +9 -2
- package/dist/runtime-helpers.js.map +4 -4
- package/dist/storage.js +14 -4
- package/dist/storage.js.map +3 -3
- package/dist/types/apps/negotium/src/mcp-servers.d.ts +1 -1
- package/dist/types/packages/core/src/platform/background-bash/context.d.ts +1 -0
- package/dist/types/packages/core/src/platform/background-bash/manager.d.ts +11 -0
- package/dist/types/packages/core/src/platform/config.d.ts +0 -1
- package/dist/types/packages/core/src/platform/delay.d.ts +1 -0
- package/dist/types/packages/core/src/platform/mcp-config.d.ts +3 -3
- package/dist/types/packages/core/src/storage/api-messages.d.ts +2 -0
- package/dist/types/packages/core/src/types/api.d.ts +3 -0
- package/dist/types/packages/core/src/version.d.ts +1 -1
- package/dist/vault.js +1 -2
- package/dist/vault.js.map +3 -3
- package/package.json +1 -1
- package/dist/runtime/src/mcp/paddleocr-server.ts +0 -105
package/dist/cron.js
CHANGED
|
@@ -187,7 +187,7 @@ function resolveDefaultModel(agent, registryDefaultModel) {
|
|
|
187
187
|
function codexAuthFilePath() {
|
|
188
188
|
return process.env.NEGOTIUM_CODEX_AUTH_FILE || join(homedir(), ".codex", "auth.json");
|
|
189
189
|
}
|
|
190
|
-
var HOME, PROJECT_ROOT, STATE_DIR_ENV, STATE_DIR, WORKSPACE_DIR, TOPIC_WORKSPACE_DIR, SHARED_WIKI_DIR, CONTEXTS_DIR, BROWSER_PROFILES_DIR, DM_WORKSPACE_DIR, SESSION_WORKSPACE_DIR, CLAUDE_EXECUTABLE, PATCHRIGHT_MCP_BIN, PLAYWRIGHT_MCP_BIN, TSX_BIN, TSCONFIG_PATH, SESSION_COMM_SERVER, TASK_SERVER, CANONICAL_MCP_PROXY_SERVER, WIKI_SERVER,
|
|
190
|
+
var HOME, PROJECT_ROOT, STATE_DIR_ENV, STATE_DIR, WORKSPACE_DIR, TOPIC_WORKSPACE_DIR, SHARED_WIKI_DIR, CONTEXTS_DIR, BROWSER_PROFILES_DIR, DM_WORKSPACE_DIR, SESSION_WORKSPACE_DIR, CLAUDE_EXECUTABLE, PATCHRIGHT_MCP_BIN, PLAYWRIGHT_MCP_BIN, TSX_BIN, TSCONFIG_PATH, SESSION_COMM_SERVER, TASK_SERVER, CANONICAL_MCP_PROXY_SERVER, WIKI_SERVER, TOKEN_STATS_SERVER, SYSTEM_HEALTH_SERVER, AGENT_HEALTH_SERVER, BACKGROUND_BASH_SERVER, VAULT_SERVER, BG_BASH_BASE_PORT, BG_BASH_MAX_PORT, RUNTIME_MCP_SECRET, NODE_CONTROL_TOKEN, VAULT_MASTER_KEY, NEGOTIUM_PORT, hostname, DATA_DIR, LOG_DIR, SESSIONS_DB, DEBUG_FILE, USERS_LOG_DIR, RUN_DIR, PROGRESS_DIR, DM_CMD_DIR, DM_RESP_DIR, SESSION_INBOX_DIR, SESSION_ASKS_DIR, PLAYWRIGHT_BASE_PORT, PLAYWRIGHT_MAX_PORT, PLAYWRIGHT_PORTS_DIR, ACTIVE_QUERY_STALE_MS, AGENTS_PROMPTS_DIR, RESOURCES_DIR, FILE_TAG_REGEX, MODEL_SONNET = "claude-sonnet-5", MODEL_OPUS = "claude-opus-4-8", MODEL_FABLE = "claude-fable-5", FALLBACK_AGENT, SESSION_AGENT, GATEWAY_AGENT, FALLBACK_MODEL, SESSION_MODEL, GATEWAY_MODEL, FFMPEG_BIN, FFPROBE_BIN, PYTHON_BIN, FASTER_WHISPER_WRAPPER, WHISPER_MODEL, TESSERACT_BIN, PDFTOTEXT_BIN, _envMaxTellDepth, MAX_TELL_DEPTH;
|
|
191
191
|
var init_config = __esm(() => {
|
|
192
192
|
init_logger();
|
|
193
193
|
init_types();
|
|
@@ -211,7 +211,6 @@ var init_config = __esm(() => {
|
|
|
211
211
|
TASK_SERVER = resolve(PROJECT_ROOT, "src/mcp/task-server.ts");
|
|
212
212
|
CANONICAL_MCP_PROXY_SERVER = resolve(PROJECT_ROOT, "src/mcp/canonical-proxy-server.ts");
|
|
213
213
|
WIKI_SERVER = resolve(PROJECT_ROOT, "src/mcp/wiki-server.ts");
|
|
214
|
-
PADDLEOCR_SERVER = resolve(PROJECT_ROOT, "src/mcp/paddleocr-server.ts");
|
|
215
214
|
TOKEN_STATS_SERVER = resolve(PROJECT_ROOT, "src/mcp/token-stats-server.ts");
|
|
216
215
|
SYSTEM_HEALTH_SERVER = resolve(PROJECT_ROOT, "src/mcp/system-health-server.ts");
|
|
217
216
|
AGENT_HEALTH_SERVER = resolve(PROJECT_ROOT, "src/mcp/agent-health-server.ts");
|
|
@@ -2019,8 +2018,156 @@ var init_bridge_ipc_config = __esm(() => {
|
|
|
2019
2018
|
registrations2 = [];
|
|
2020
2019
|
});
|
|
2021
2020
|
|
|
2022
|
-
// ../../packages/core/src/platform/
|
|
2021
|
+
// ../../packages/core/src/platform/delay.ts
|
|
2022
|
+
var delay = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2023
|
+
|
|
2024
|
+
// ../../packages/core/src/platform/background-bash/context.ts
|
|
2023
2025
|
import { createHmac as createHmac2 } from "crypto";
|
|
2026
|
+
function deriveBgBashContextCapability(runtimeCapability, userId, topic) {
|
|
2027
|
+
return createHmac2("sha256", runtimeCapability).update(`${userId}\x00${topic}`).digest("hex");
|
|
2028
|
+
}
|
|
2029
|
+
var init_context = () => {};
|
|
2030
|
+
|
|
2031
|
+
// ../../packages/core/src/platform/background-bash/manager.ts
|
|
2032
|
+
import { execFileSync as execFileSync2, spawn } from "child_process";
|
|
2033
|
+
import { randomBytes as randomBytes4 } from "crypto";
|
|
2034
|
+
function makeBgBashKey(_userId, _topic) {
|
|
2035
|
+
return "runtime";
|
|
2036
|
+
}
|
|
2037
|
+
function contextKey(userId, topic) {
|
|
2038
|
+
return `${userId}\x00${topic}`;
|
|
2039
|
+
}
|
|
2040
|
+
function bgBashContextCapability(userId, topic) {
|
|
2041
|
+
return deriveBgBashContextCapability(runtimeCapability, userId, topic);
|
|
2042
|
+
}
|
|
2043
|
+
function pidsOnPort(port) {
|
|
2044
|
+
try {
|
|
2045
|
+
return execFileSync2("lsof", ["-i", `:${port}`, "-t"], { stdio: "pipe" }).toString().trim().split(`
|
|
2046
|
+
`).map((pid) => parseInt(pid, 10)).filter((pid) => !Number.isNaN(pid));
|
|
2047
|
+
} catch {
|
|
2048
|
+
return [];
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
function isPortInUse(port) {
|
|
2052
|
+
return pidsOnPort(port).length > 0;
|
|
2053
|
+
}
|
|
2054
|
+
async function allocatePort(excludedPorts = new Set) {
|
|
2055
|
+
for (let port = BG_BASH_BASE_PORT;port <= BG_BASH_MAX_PORT; port++) {
|
|
2056
|
+
if (usedPorts.has(port) || excludedPorts.has(port))
|
|
2057
|
+
continue;
|
|
2058
|
+
usedPorts.add(port);
|
|
2059
|
+
if (isPortInUse(port)) {
|
|
2060
|
+
usedPorts.delete(port);
|
|
2061
|
+
continue;
|
|
2062
|
+
}
|
|
2063
|
+
return port;
|
|
2064
|
+
}
|
|
2065
|
+
throw new Error(`No available ports for background-bash (range ${BG_BASH_BASE_PORT}-${BG_BASH_MAX_PORT}, ${instances.size} active)`);
|
|
2066
|
+
}
|
|
2067
|
+
async function isHealthy(port) {
|
|
2068
|
+
try {
|
|
2069
|
+
const res = await fetch(`http://127.0.0.1:${port}/health`, {
|
|
2070
|
+
signal: AbortSignal.timeout(2000)
|
|
2071
|
+
});
|
|
2072
|
+
return res.ok && await res.text() === runtimeServerId;
|
|
2073
|
+
} catch {
|
|
2074
|
+
return false;
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
async function spawnBgBash(key, reservedPort, excludedPorts = new Set) {
|
|
2078
|
+
const port = reservedPort ?? await allocatePort(excludedPorts);
|
|
2079
|
+
const serverArgs = ["run", BACKGROUND_BASH_SERVER, `--port=${port}`];
|
|
2080
|
+
const proc = spawn("bun", serverArgs, {
|
|
2081
|
+
stdio: "ignore",
|
|
2082
|
+
detached: false,
|
|
2083
|
+
env: {
|
|
2084
|
+
...process.env,
|
|
2085
|
+
NEGOTIUM_BG_BASH_CAPABILITY: runtimeCapability,
|
|
2086
|
+
NEGOTIUM_BG_BASH_SERVER_ID: runtimeServerId
|
|
2087
|
+
}
|
|
2088
|
+
});
|
|
2089
|
+
proc.once("error", (err) => {
|
|
2090
|
+
logger.error({ err, key }, "background-bash server error");
|
|
2091
|
+
if (instances.get(key)?.process === proc) {
|
|
2092
|
+
usedPorts.delete(port);
|
|
2093
|
+
instances.delete(key);
|
|
2094
|
+
}
|
|
2095
|
+
});
|
|
2096
|
+
proc.once("exit", (code) => {
|
|
2097
|
+
logger.info({ key, code }, "background-bash server exited");
|
|
2098
|
+
if (instances.get(key)?.process === proc) {
|
|
2099
|
+
usedPorts.delete(port);
|
|
2100
|
+
instances.delete(key);
|
|
2101
|
+
}
|
|
2102
|
+
});
|
|
2103
|
+
const now = Date.now();
|
|
2104
|
+
instances.set(key, { process: proc, port, startedAt: now, lastUsedAt: now });
|
|
2105
|
+
const start = Date.now();
|
|
2106
|
+
while (Date.now() - start < 8000) {
|
|
2107
|
+
if (proc.exitCode !== null) {
|
|
2108
|
+
const nextExcludedPorts = new Set(excludedPorts);
|
|
2109
|
+
nextExcludedPorts.add(port);
|
|
2110
|
+
return spawnBgBash(key, undefined, nextExcludedPorts);
|
|
2111
|
+
}
|
|
2112
|
+
if (await isHealthy(port)) {
|
|
2113
|
+
logger.info({ key, port, pid: proc.pid }, "background-bash server ready");
|
|
2114
|
+
return port;
|
|
2115
|
+
}
|
|
2116
|
+
await delay(200);
|
|
2117
|
+
}
|
|
2118
|
+
killRuntimeBgBash();
|
|
2119
|
+
throw new Error(`background-bash server failed health check after spawn on port ${port}`);
|
|
2120
|
+
}
|
|
2121
|
+
async function ensureBgBash(userId, topic) {
|
|
2122
|
+
const key = makeBgBashKey(userId, topic);
|
|
2123
|
+
knownContexts.set(contextKey(userId, topic), { userId, topic });
|
|
2124
|
+
const inProgress = spawning.get(key);
|
|
2125
|
+
if (inProgress)
|
|
2126
|
+
return inProgress;
|
|
2127
|
+
const promise = (async () => {
|
|
2128
|
+
const existing = instances.get(key);
|
|
2129
|
+
if (existing && !existing.process.killed && existing.process.exitCode === null) {
|
|
2130
|
+
if (await isHealthy(existing.port)) {
|
|
2131
|
+
existing.lastUsedAt = Date.now();
|
|
2132
|
+
return existing.port;
|
|
2133
|
+
}
|
|
2134
|
+
killRuntimeBgBash();
|
|
2135
|
+
} else if (existing) {
|
|
2136
|
+
usedPorts.delete(existing.port);
|
|
2137
|
+
instances.delete(key);
|
|
2138
|
+
}
|
|
2139
|
+
return spawnBgBash(key);
|
|
2140
|
+
})().finally(() => spawning.delete(key));
|
|
2141
|
+
spawning.set(key, promise);
|
|
2142
|
+
return promise;
|
|
2143
|
+
}
|
|
2144
|
+
function killRuntimeBgBash() {
|
|
2145
|
+
const key = "runtime";
|
|
2146
|
+
const inst = instances.get(key);
|
|
2147
|
+
if (!inst)
|
|
2148
|
+
return;
|
|
2149
|
+
try {
|
|
2150
|
+
inst.process.kill("SIGTERM");
|
|
2151
|
+
} catch {}
|
|
2152
|
+
usedPorts.delete(inst.port);
|
|
2153
|
+
instances.delete(key);
|
|
2154
|
+
logger.info({ key, port: inst.port }, "background-bash server killed");
|
|
2155
|
+
}
|
|
2156
|
+
var instances, usedPorts, spawning, runtimeCapability, runtimeServerId, knownContexts;
|
|
2157
|
+
var init_manager = __esm(() => {
|
|
2158
|
+
init_config();
|
|
2159
|
+
init_logger();
|
|
2160
|
+
init_context();
|
|
2161
|
+
instances = new Map;
|
|
2162
|
+
usedPorts = new Set;
|
|
2163
|
+
spawning = new Map;
|
|
2164
|
+
runtimeCapability = randomBytes4(32).toString("hex");
|
|
2165
|
+
runtimeServerId = randomBytes4(16).toString("hex");
|
|
2166
|
+
knownContexts = new Map;
|
|
2167
|
+
});
|
|
2168
|
+
|
|
2169
|
+
// ../../packages/core/src/platform/mcp-config.ts
|
|
2170
|
+
import { createHmac as createHmac3 } from "crypto";
|
|
2024
2171
|
function buildStdioMcpServer(agent, serverFile, serverArgs, env) {
|
|
2025
2172
|
if (agent === "codex") {
|
|
2026
2173
|
return {
|
|
@@ -2065,7 +2212,7 @@ function consumePlaywrightUnavailable(userId, topic) {
|
|
|
2065
2212
|
return true;
|
|
2066
2213
|
}
|
|
2067
2214
|
function browserOwnerCapability(capability, owner) {
|
|
2068
|
-
return
|
|
2215
|
+
return createHmac3("sha256", capability).update(owner).digest("hex");
|
|
2069
2216
|
}
|
|
2070
2217
|
function browserOwnerForContext(ctx) {
|
|
2071
2218
|
if (ctx.topicId)
|
|
@@ -2099,6 +2246,21 @@ function playwrightTransport(port, owner, capability, agent) {
|
|
|
2099
2246
|
function longLivedHttpMcp(agent, port) {
|
|
2100
2247
|
return agent === "codex" ? { url: `http://127.0.0.1:${port}/mcp` } : { type: "sse", url: `http://127.0.0.1:${port}/sse` };
|
|
2101
2248
|
}
|
|
2249
|
+
function backgroundBashTransport(agent, port, userId, topic) {
|
|
2250
|
+
const capability = bgBashContextCapability(userId, topic);
|
|
2251
|
+
const headers = {
|
|
2252
|
+
"X-Background-Bash-User": userId,
|
|
2253
|
+
"X-Background-Bash-Topic": topic,
|
|
2254
|
+
"X-Background-Bash-Capability": capability
|
|
2255
|
+
};
|
|
2256
|
+
if (agent === "codex")
|
|
2257
|
+
return { url: `http://127.0.0.1:${port}/mcp`, http_headers: headers };
|
|
2258
|
+
if (agent === "maestro") {
|
|
2259
|
+
const query = new URLSearchParams({ user: userId, topic, capability });
|
|
2260
|
+
return { type: "sse", url: `http://127.0.0.1:${port}/sse?${query}` };
|
|
2261
|
+
}
|
|
2262
|
+
return { type: "sse", url: `http://127.0.0.1:${port}/sse`, headers };
|
|
2263
|
+
}
|
|
2102
2264
|
function namesInScope(scope) {
|
|
2103
2265
|
return Object.entries(MCP_CATALOG).filter(([, e]) => e.scopes.includes(scope)).map(([name]) => name);
|
|
2104
2266
|
}
|
|
@@ -2330,6 +2492,7 @@ var init_mcp_config = __esm(() => {
|
|
|
2330
2492
|
init_canonical_bridge_config();
|
|
2331
2493
|
init_runtime_spec();
|
|
2332
2494
|
init_bridge_ipc_config();
|
|
2495
|
+
init_manager();
|
|
2333
2496
|
init_config();
|
|
2334
2497
|
init_logger();
|
|
2335
2498
|
_playwrightUnavailableLastNotifiedAt = new Map;
|
|
@@ -2338,6 +2501,7 @@ var init_mcp_config = __esm(() => {
|
|
|
2338
2501
|
MCP_CATALOG = {
|
|
2339
2502
|
playwright: {
|
|
2340
2503
|
scopes: ["dm", "forum", "fork", "cron"],
|
|
2504
|
+
forumRequired: true,
|
|
2341
2505
|
build({ userId, session, topicId, playwrightPort, playwrightCapability, agent }) {
|
|
2342
2506
|
if (playwrightPort && playwrightCapability) {
|
|
2343
2507
|
const owner = browserOwnerForContext({ userId, session, topicId });
|
|
@@ -2388,12 +2552,6 @@ var init_mcp_config = __esm(() => {
|
|
|
2388
2552
|
});
|
|
2389
2553
|
}
|
|
2390
2554
|
},
|
|
2391
|
-
paddleocr: {
|
|
2392
|
-
scopes: ["dm", "forum", "fork", "cron"],
|
|
2393
|
-
build({ agent }) {
|
|
2394
|
-
return buildStdioMcpServer(agent, PADDLEOCR_SERVER, []);
|
|
2395
|
-
}
|
|
2396
|
-
},
|
|
2397
2555
|
"token-stats": {
|
|
2398
2556
|
scopes: ["dm", "forum", "manager", "cron"],
|
|
2399
2557
|
forumRequired: true,
|
|
@@ -2483,10 +2641,12 @@ var init_mcp_config = __esm(() => {
|
|
|
2483
2641
|
},
|
|
2484
2642
|
"background-bash": {
|
|
2485
2643
|
scopes: ["forum"],
|
|
2486
|
-
|
|
2487
|
-
|
|
2644
|
+
forumRequired: true,
|
|
2645
|
+
build({ agent, bgBashPort, userId, topicId, session }) {
|
|
2646
|
+
const topic = topicId ?? session;
|
|
2647
|
+
if (bgBashPort === undefined || !topic)
|
|
2488
2648
|
return null;
|
|
2489
|
-
return
|
|
2649
|
+
return backgroundBashTransport(agent, bgBashPort, userId, topic);
|
|
2490
2650
|
}
|
|
2491
2651
|
},
|
|
2492
2652
|
"agent-health": {
|
|
@@ -2584,7 +2744,7 @@ function errMsg(e, fallback) {
|
|
|
2584
2744
|
}
|
|
2585
2745
|
|
|
2586
2746
|
// ../../packages/core/src/agents/claude-provider.ts
|
|
2587
|
-
import { spawn } from "child_process";
|
|
2747
|
+
import { spawn as spawn2 } from "child_process";
|
|
2588
2748
|
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
|
|
2589
2749
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
2590
2750
|
function buildClaudeDisallowedTools(extra = undefined) {
|
|
@@ -2662,7 +2822,7 @@ function signalProcessTree(pid, signal) {
|
|
|
2662
2822
|
}
|
|
2663
2823
|
}
|
|
2664
2824
|
function spawnClaudeCodeProcessWithTreeKill(options) {
|
|
2665
|
-
const child =
|
|
2825
|
+
const child = spawn2(options.command, options.args, {
|
|
2666
2826
|
cwd: options.cwd,
|
|
2667
2827
|
detached: true,
|
|
2668
2828
|
env: options.env,
|
|
@@ -2763,7 +2923,7 @@ async function* claudeProvider(opts) {
|
|
|
2763
2923
|
delete cleanEnv.NEGOTIUM_PEER_SESSION_BRIDGE_URL;
|
|
2764
2924
|
delete cleanEnv.NEGOTIUM_PEER_SESSION_BRIDGE_TOKEN;
|
|
2765
2925
|
delete cleanEnv.CLAUDECODE;
|
|
2766
|
-
cleanEnv.CLAUDE_CODE_STREAM_CLOSE_TIMEOUT ??=
|
|
2926
|
+
cleanEnv.CLAUDE_CODE_STREAM_CLOSE_TIMEOUT ??= "300000";
|
|
2767
2927
|
cleanEnv.CLAUDE_CODE_DISABLE_WORKFLOWS = "1";
|
|
2768
2928
|
const queryOptions = {
|
|
2769
2929
|
...claudeExecutable ? { pathToClaudeCodeExecutable: claudeExecutable } : {},
|
|
@@ -3054,10 +3214,10 @@ var init_claude_provider = __esm(async () => {
|
|
|
3054
3214
|
});
|
|
3055
3215
|
|
|
3056
3216
|
// ../../packages/core/src/version.ts
|
|
3057
|
-
var NEGOTIUM_VERSION = "0.1.
|
|
3217
|
+
var NEGOTIUM_VERSION = "0.1.16";
|
|
3058
3218
|
|
|
3059
3219
|
// ../../packages/core/src/agents/codex-native-multi-agent.ts
|
|
3060
|
-
import { spawn as
|
|
3220
|
+
import { spawn as spawn3 } from "child_process";
|
|
3061
3221
|
import {
|
|
3062
3222
|
chmodSync as chmodSync3,
|
|
3063
3223
|
existsSync as existsSync8,
|
|
@@ -3074,7 +3234,7 @@ function codexCliScriptPath() {
|
|
|
3074
3234
|
return join10(dirname6(sdkRequire.resolve("@openai/codex/package.json")), "bin", "codex.js");
|
|
3075
3235
|
}
|
|
3076
3236
|
async function bootstrapCodexModelCache(codexHome, cachePath) {
|
|
3077
|
-
const child =
|
|
3237
|
+
const child = spawn3(process.execPath, [codexCliScriptPath(), "app-server", "--stdio"], {
|
|
3078
3238
|
env: { ...process.env, CODEX_HOME: codexHome },
|
|
3079
3239
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3080
3240
|
});
|
|
@@ -3222,14 +3382,7 @@ function parsePositiveInt(value, fallback) {
|
|
|
3222
3382
|
const parsed = Number.parseInt(value, 10);
|
|
3223
3383
|
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
3224
3384
|
}
|
|
3225
|
-
function withCodexMcpServerOverrides(
|
|
3226
|
-
if (name === "paddleocr") {
|
|
3227
|
-
return {
|
|
3228
|
-
...server,
|
|
3229
|
-
startup_timeout_sec: parsePositiveInt(process.env.PADDLEOCR_MCP_STARTUP_TIMEOUT_SEC, 30),
|
|
3230
|
-
tool_timeout_sec: parsePositiveInt(process.env.PADDLEOCR_MCP_TOOL_TIMEOUT_SEC, 300)
|
|
3231
|
-
};
|
|
3232
|
-
}
|
|
3385
|
+
function withCodexMcpServerOverrides(_name, server) {
|
|
3233
3386
|
return {
|
|
3234
3387
|
startup_timeout_sec: parsePositiveInt(process.env.CODEX_MCP_STARTUP_TIMEOUT_SEC, 30),
|
|
3235
3388
|
...server
|
|
@@ -4247,6 +4400,8 @@ function initializeApiMessagesSchema() {
|
|
|
4247
4400
|
parent_id TEXT,
|
|
4248
4401
|
author_id TEXT NOT NULL,
|
|
4249
4402
|
source_adapter TEXT,
|
|
4403
|
+
source_node TEXT,
|
|
4404
|
+
source_message_id TEXT,
|
|
4250
4405
|
text TEXT NOT NULL,
|
|
4251
4406
|
query_id TEXT,
|
|
4252
4407
|
agent_type TEXT,
|
|
@@ -4266,6 +4421,12 @@ function initializeApiMessagesSchema() {
|
|
|
4266
4421
|
try {
|
|
4267
4422
|
db.exec("ALTER TABLE api_messages ADD COLUMN source_adapter TEXT");
|
|
4268
4423
|
} catch {}
|
|
4424
|
+
try {
|
|
4425
|
+
db.exec("ALTER TABLE api_messages ADD COLUMN source_node TEXT");
|
|
4426
|
+
} catch {}
|
|
4427
|
+
try {
|
|
4428
|
+
db.exec("ALTER TABLE api_messages ADD COLUMN source_message_id TEXT");
|
|
4429
|
+
} catch {}
|
|
4269
4430
|
try {
|
|
4270
4431
|
db.exec("ALTER TABLE api_messages ADD COLUMN deleted INTEGER NOT NULL DEFAULT 0");
|
|
4271
4432
|
} catch {}
|
|
@@ -4311,6 +4472,8 @@ function rowToDto(r) {
|
|
|
4311
4472
|
parentId: r.parent_id ?? undefined,
|
|
4312
4473
|
authorId: r.author_id,
|
|
4313
4474
|
sourceAdapter: r.source_adapter ?? undefined,
|
|
4475
|
+
sourceNode: r.source_node ?? undefined,
|
|
4476
|
+
sourceMessageId: r.source_message_id ?? undefined,
|
|
4314
4477
|
text: r.text,
|
|
4315
4478
|
queryId: r.query_id ?? undefined,
|
|
4316
4479
|
agentType: r.agent_type ?? undefined,
|
|
@@ -4344,9 +4507,9 @@ function appendApiMessage(msg, options = {}) {
|
|
|
4344
4507
|
let inserted = false;
|
|
4345
4508
|
db.transaction(() => {
|
|
4346
4509
|
const result = db.query(`INSERT INTO api_messages
|
|
4347
|
-
(id, topic_id, parent_id, author_id, source_adapter, text, query_id, agent_type, model, attachments, usage, deleted, edited_at, reactions, kind, ask_user_question, subagent_card, mentions, thread_root_id, created_at)
|
|
4348
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
4349
|
-
ON CONFLICT(id) DO NOTHING`).run(msg.id, msg.topicId, msg.parentId ?? null, msg.authorId, msg.sourceAdapter ?? null, msg.text, msg.queryId ?? null, msg.agentType ?? null, msg.model ?? null, msg.attachments ? JSON.stringify(msg.attachments) : null, msg.usage ? JSON.stringify(msg.usage) : null, msg.deleted ? 1 : 0, msg.editedAt ?? null, msg.reactions?.length ? JSON.stringify(msg.reactions) : null, msg.kind ?? null, msg.askUserQuestion ? JSON.stringify(msg.askUserQuestion) : null, msg.subagentCard ? JSON.stringify(msg.subagentCard) : null, msg.mentions?.length ? JSON.stringify(msg.mentions) : null, msg.threadRootId ?? null, msg.createdAt);
|
|
4510
|
+
(id, topic_id, parent_id, author_id, source_adapter, source_node, source_message_id, text, query_id, agent_type, model, attachments, usage, deleted, edited_at, reactions, kind, ask_user_question, subagent_card, mentions, thread_root_id, created_at)
|
|
4511
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
4512
|
+
ON CONFLICT(id) DO NOTHING`).run(msg.id, msg.topicId, msg.parentId ?? null, msg.authorId, msg.sourceAdapter ?? null, msg.sourceNode ?? null, msg.sourceMessageId ?? null, msg.text, msg.queryId ?? null, msg.agentType ?? null, msg.model ?? null, msg.attachments ? JSON.stringify(msg.attachments) : null, msg.usage ? JSON.stringify(msg.usage) : null, msg.deleted ? 1 : 0, msg.editedAt ?? null, msg.reactions?.length ? JSON.stringify(msg.reactions) : null, msg.kind ?? null, msg.askUserQuestion ? JSON.stringify(msg.askUserQuestion) : null, msg.subagentCard ? JSON.stringify(msg.subagentCard) : null, msg.mentions?.length ? JSON.stringify(msg.mentions) : null, msg.threadRootId ?? null, msg.createdAt);
|
|
4350
4513
|
inserted = Number(result.changes ?? 0) > 0;
|
|
4351
4514
|
if (inserted && updateTopicLastMessageAt && !msg.deleted) {
|
|
4352
4515
|
db.query(`UPDATE api_topics
|
|
@@ -5291,9 +5454,6 @@ var init_self_schedules = __esm(async () => {
|
|
|
5291
5454
|
`);
|
|
5292
5455
|
});
|
|
5293
5456
|
|
|
5294
|
-
// ../../packages/core/src/platform/delay.ts
|
|
5295
|
-
var delay = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
5296
|
-
|
|
5297
5457
|
// ../../packages/core/src/storage/browser-profiles.ts
|
|
5298
5458
|
function normalizeBrowserProfileName(name) {
|
|
5299
5459
|
const value = name.trim().toLowerCase();
|
|
@@ -5352,8 +5512,8 @@ var init_browser_profiles = __esm(async () => {
|
|
|
5352
5512
|
});
|
|
5353
5513
|
|
|
5354
5514
|
// ../../packages/core/src/platform/playwright/manager.ts
|
|
5355
|
-
import { execFileSync as
|
|
5356
|
-
import { createHash as createHash2, randomBytes as
|
|
5515
|
+
import { execFileSync as execFileSync3, spawn as spawn4 } from "child_process";
|
|
5516
|
+
import { createHash as createHash2, randomBytes as randomBytes5 } from "crypto";
|
|
5357
5517
|
import {
|
|
5358
5518
|
cpSync,
|
|
5359
5519
|
existsSync as existsSync12,
|
|
@@ -5435,13 +5595,13 @@ function unpinPlaywrightInstance(instanceKey) {
|
|
|
5435
5595
|
pinnedInstances.set(instanceKey, count - 1);
|
|
5436
5596
|
}
|
|
5437
5597
|
function getPlaywrightCapability(instanceKey) {
|
|
5438
|
-
return
|
|
5598
|
+
return instances2.get(instanceKey)?.capability;
|
|
5439
5599
|
}
|
|
5440
5600
|
function evictIdleInstance() {
|
|
5441
5601
|
const now = Date.now();
|
|
5442
|
-
const oldestKey = selectIdleEvictionKey(
|
|
5602
|
+
const oldestKey = selectIdleEvictionKey(instances2, pinnedInstances.keys(), spawning2.keys(), now, MAX_IDLE_MS);
|
|
5443
5603
|
if (oldestKey) {
|
|
5444
|
-
const instance =
|
|
5604
|
+
const instance = instances2.get(oldestKey);
|
|
5445
5605
|
if (!instance)
|
|
5446
5606
|
return null;
|
|
5447
5607
|
const idleMin = ((now - instance.lastUsedAt) / 60000).toFixed(0);
|
|
@@ -5466,16 +5626,16 @@ function selectIdleEvictionKey(candidates, pinnedKeys, busyKeys, now, maxIdleMs)
|
|
|
5466
5626
|
}
|
|
5467
5627
|
return oldest?.key ?? null;
|
|
5468
5628
|
}
|
|
5469
|
-
async function
|
|
5629
|
+
async function allocatePort2(expectedUserDataDir) {
|
|
5470
5630
|
for (let port = BASE_PORT;port <= MAX_PORT; port++) {
|
|
5471
|
-
if (
|
|
5631
|
+
if (usedPorts2.has(port))
|
|
5472
5632
|
continue;
|
|
5473
|
-
|
|
5474
|
-
if (
|
|
5633
|
+
usedPorts2.add(port);
|
|
5634
|
+
if (isPortInUse2(port)) {
|
|
5475
5635
|
logger.warn({ port }, "Port occupied by external process, attempting cleanup");
|
|
5476
5636
|
await killPlaywrightOnPort(port, expectedUserDataDir);
|
|
5477
|
-
if (
|
|
5478
|
-
|
|
5637
|
+
if (isPortInUse2(port)) {
|
|
5638
|
+
usedPorts2.delete(port);
|
|
5479
5639
|
continue;
|
|
5480
5640
|
}
|
|
5481
5641
|
}
|
|
@@ -5484,13 +5644,13 @@ async function allocatePort(expectedUserDataDir) {
|
|
|
5484
5644
|
const evictedPort = evictIdleInstance();
|
|
5485
5645
|
if (evictedPort !== null) {
|
|
5486
5646
|
await waitForPortRelease(evictedPort);
|
|
5487
|
-
const reusablePort = selectReusablePort(BASE_PORT, MAX_PORT,
|
|
5647
|
+
const reusablePort = selectReusablePort(BASE_PORT, MAX_PORT, usedPorts2, isPortInUse2);
|
|
5488
5648
|
if (reusablePort !== null) {
|
|
5489
|
-
|
|
5649
|
+
usedPorts2.add(reusablePort);
|
|
5490
5650
|
return reusablePort;
|
|
5491
5651
|
}
|
|
5492
5652
|
}
|
|
5493
|
-
throw new Error(`No available ports for Playwright MCP (${
|
|
5653
|
+
throw new Error(`No available ports for Playwright MCP (${instances2.size} active instances, range ${BASE_PORT}-${MAX_PORT})`);
|
|
5494
5654
|
}
|
|
5495
5655
|
function selectReusablePort(minPort, maxPort, reservedPorts, isOccupied) {
|
|
5496
5656
|
for (let port = minPort;port <= maxPort; port++) {
|
|
@@ -5501,11 +5661,11 @@ function selectReusablePort(minPort, maxPort, reservedPorts, isOccupied) {
|
|
|
5501
5661
|
return null;
|
|
5502
5662
|
}
|
|
5503
5663
|
function releasePort(port) {
|
|
5504
|
-
|
|
5664
|
+
usedPorts2.delete(port);
|
|
5505
5665
|
}
|
|
5506
|
-
function
|
|
5666
|
+
function isPortInUse2(port) {
|
|
5507
5667
|
try {
|
|
5508
|
-
|
|
5668
|
+
execFileSync3("lsof", ["-i", `:${port}`, "-t"], { stdio: "pipe" });
|
|
5509
5669
|
return true;
|
|
5510
5670
|
} catch {
|
|
5511
5671
|
return false;
|
|
@@ -5517,13 +5677,13 @@ function extractUserDataDirArg(cmdline) {
|
|
|
5517
5677
|
}
|
|
5518
5678
|
async function killPlaywrightOnPort(port, expectedUserDataDir) {
|
|
5519
5679
|
try {
|
|
5520
|
-
const pids =
|
|
5680
|
+
const pids = execFileSync3("lsof", ["-i", `:${port}`, "-t"], { stdio: "pipe" }).toString().trim();
|
|
5521
5681
|
if (!pids)
|
|
5522
5682
|
return;
|
|
5523
5683
|
for (const pid of pids.split(`
|
|
5524
5684
|
`)) {
|
|
5525
5685
|
try {
|
|
5526
|
-
const cmdline =
|
|
5686
|
+
const cmdline = execFileSync3("ps", ["-p", pid, "-o", "command="], { stdio: "pipe" }).toString().trim();
|
|
5527
5687
|
if (!cmdline.includes("mcp-patchright")) {
|
|
5528
5688
|
logger.warn({ port, pid, cmdline: cmdline.slice(0, 80) }, "Port occupied by non-browser-MCP process, skipping");
|
|
5529
5689
|
continue;
|
|
@@ -5555,7 +5715,7 @@ async function killPlaywrightOnPort(port, expectedUserDataDir) {
|
|
|
5555
5715
|
}
|
|
5556
5716
|
const start = Date.now();
|
|
5557
5717
|
while (Date.now() - start < 3000) {
|
|
5558
|
-
if (!
|
|
5718
|
+
if (!isPortInUse2(port))
|
|
5559
5719
|
return;
|
|
5560
5720
|
await delay(200);
|
|
5561
5721
|
}
|
|
@@ -5570,7 +5730,7 @@ function killBrowserProcsForUserDataDir(userDataDir) {
|
|
|
5570
5730
|
return;
|
|
5571
5731
|
let pids;
|
|
5572
5732
|
try {
|
|
5573
|
-
pids =
|
|
5733
|
+
pids = execFileSync3("pgrep", ["-f", "--", userDataDir], { stdio: "pipe" }).toString().trim();
|
|
5574
5734
|
} catch {
|
|
5575
5735
|
return;
|
|
5576
5736
|
}
|
|
@@ -5582,7 +5742,7 @@ function killBrowserProcsForUserDataDir(userDataDir) {
|
|
|
5582
5742
|
if (Number.isNaN(pidNum) || pidNum === process.pid)
|
|
5583
5743
|
continue;
|
|
5584
5744
|
try {
|
|
5585
|
-
const cmdline =
|
|
5745
|
+
const cmdline = execFileSync3("ps", ["-p", String(pidNum), "-o", "command="], {
|
|
5586
5746
|
stdio: "pipe"
|
|
5587
5747
|
}).toString().trim();
|
|
5588
5748
|
const argDir = extractUserDataDirArg(cmdline);
|
|
@@ -5616,7 +5776,7 @@ function reapOrphanBrowsers() {
|
|
|
5616
5776
|
const profileRoot = resolve5(BROWSER_PROFILES_DIR);
|
|
5617
5777
|
let pids;
|
|
5618
5778
|
try {
|
|
5619
|
-
pids =
|
|
5779
|
+
pids = execFileSync3("pgrep", ["-f", "--", profileRoot], { stdio: "pipe" }).toString().trim();
|
|
5620
5780
|
} catch {
|
|
5621
5781
|
return;
|
|
5622
5782
|
}
|
|
@@ -5629,13 +5789,13 @@ function reapOrphanBrowsers() {
|
|
|
5629
5789
|
if (Number.isNaN(pidNum))
|
|
5630
5790
|
continue;
|
|
5631
5791
|
try {
|
|
5632
|
-
const cmdline =
|
|
5792
|
+
const cmdline = execFileSync3("ps", ["-p", String(pidNum), "-o", "command="], {
|
|
5633
5793
|
stdio: "pipe"
|
|
5634
5794
|
}).toString().trim();
|
|
5635
5795
|
procs.push({ pid: pidNum, userDataDir: extractUserDataDirArg(cmdline) });
|
|
5636
5796
|
} catch {}
|
|
5637
5797
|
}
|
|
5638
|
-
const liveDirs = [...
|
|
5798
|
+
const liveDirs = [...instances2.keys()].map(resolveUserDataDir);
|
|
5639
5799
|
const orphanPids = selectOrphanBrowserPids(procs, liveDirs, profileRoot, process.pid);
|
|
5640
5800
|
for (const pid of orphanPids) {
|
|
5641
5801
|
try {
|
|
@@ -5647,7 +5807,7 @@ function reapOrphanBrowsers() {
|
|
|
5647
5807
|
}
|
|
5648
5808
|
}
|
|
5649
5809
|
}
|
|
5650
|
-
async function
|
|
5810
|
+
async function isHealthy2(port) {
|
|
5651
5811
|
for (const path of ["/health", "/sse?owner=__negotium_health__"]) {
|
|
5652
5812
|
try {
|
|
5653
5813
|
const res = await fetch(`http://127.0.0.1:${port}${path}`, {
|
|
@@ -5677,7 +5837,7 @@ function cleanSingletonFiles(userDataDir) {
|
|
|
5677
5837
|
}
|
|
5678
5838
|
function killProcessTreeChildren(pid) {
|
|
5679
5839
|
try {
|
|
5680
|
-
const children =
|
|
5840
|
+
const children = execFileSync3("pgrep", ["-P", String(pid)], { stdio: "pipe" }).toString().trim();
|
|
5681
5841
|
if (!children)
|
|
5682
5842
|
return;
|
|
5683
5843
|
const childPids = children.split(`
|
|
@@ -5709,7 +5869,7 @@ function resolveUserDataDir(instanceKey) {
|
|
|
5709
5869
|
return resolveProfileDir(ownerId, profile);
|
|
5710
5870
|
}
|
|
5711
5871
|
function killInstance(instanceKey, opts) {
|
|
5712
|
-
const inst =
|
|
5872
|
+
const inst = instances2.get(instanceKey);
|
|
5713
5873
|
if (!inst)
|
|
5714
5874
|
return;
|
|
5715
5875
|
if (inst.process.pid) {
|
|
@@ -5725,13 +5885,13 @@ function killInstance(instanceKey, opts) {
|
|
|
5725
5885
|
cleanSingletonFiles(resolveUserDataDir(instanceKey));
|
|
5726
5886
|
if (!opts?.keepPort)
|
|
5727
5887
|
releasePort(inst.port);
|
|
5728
|
-
|
|
5888
|
+
instances2.delete(instanceKey);
|
|
5729
5889
|
deletePortFile(instanceKey);
|
|
5730
5890
|
logger.info({ instanceKey, port: inst.port, keepPort: !!opts?.keepPort }, "Killed Playwright MCP (with cleanup)");
|
|
5731
5891
|
}
|
|
5732
5892
|
async function spawnPlaywright(instanceKey, userId, reservedPort, browserBin = PLAYWRIGHT_MCP_BIN, allowFallback = true) {
|
|
5733
5893
|
const userDataDir = resolveUserDataDir(instanceKey);
|
|
5734
|
-
const port = reservedPort ?? await
|
|
5894
|
+
const port = reservedPort ?? await allocatePort2(userDataDir);
|
|
5735
5895
|
mkdirSync9(userDataDir, { recursive: true });
|
|
5736
5896
|
const mcpArgs = [
|
|
5737
5897
|
"--port",
|
|
@@ -5742,7 +5902,7 @@ async function spawnPlaywright(instanceKey, userId, reservedPort, browserBin = P
|
|
|
5742
5902
|
userDataDir
|
|
5743
5903
|
];
|
|
5744
5904
|
const proxy = resolveBrowserProxy();
|
|
5745
|
-
const capability =
|
|
5905
|
+
const capability = randomBytes5(32).toString("hex");
|
|
5746
5906
|
const childEnv = {
|
|
5747
5907
|
...process.env,
|
|
5748
5908
|
NEGOTIUM_BROWSER_CAPABILITY: capability,
|
|
@@ -5758,7 +5918,7 @@ async function spawnPlaywright(instanceKey, userId, reservedPort, browserBin = P
|
|
|
5758
5918
|
}
|
|
5759
5919
|
const command = browserBin.endsWith(".mjs") ? process.execPath : browserBin;
|
|
5760
5920
|
const args = browserBin.endsWith(".mjs") ? [browserBin, ...mcpArgs] : mcpArgs;
|
|
5761
|
-
const proc =
|
|
5921
|
+
const proc = spawn4(command, args, {
|
|
5762
5922
|
stdio: "ignore",
|
|
5763
5923
|
detached: false,
|
|
5764
5924
|
env: childEnv
|
|
@@ -5770,24 +5930,24 @@ async function spawnPlaywright(instanceKey, userId, reservedPort, browserBin = P
|
|
|
5770
5930
|
};
|
|
5771
5931
|
proc.once("error", (err) => {
|
|
5772
5932
|
logger.error({ err, instanceKey }, "Playwright MCP error");
|
|
5773
|
-
if (
|
|
5933
|
+
if (instances2.get(instanceKey)?.process === proc) {
|
|
5774
5934
|
releasePort(port);
|
|
5775
|
-
|
|
5935
|
+
instances2.delete(instanceKey);
|
|
5776
5936
|
reapCrashedBrowser();
|
|
5777
5937
|
}
|
|
5778
5938
|
});
|
|
5779
5939
|
proc.once("exit", (code) => {
|
|
5780
5940
|
logger.info({ instanceKey, code }, "Playwright MCP exited");
|
|
5781
|
-
const wasOurs =
|
|
5941
|
+
const wasOurs = instances2.get(instanceKey)?.process === proc;
|
|
5782
5942
|
if (wasOurs) {
|
|
5783
5943
|
releasePort(port);
|
|
5784
|
-
|
|
5944
|
+
instances2.delete(instanceKey);
|
|
5785
5945
|
reapCrashedBrowser();
|
|
5786
5946
|
_onPlaywrightExit?.(instanceKey, code);
|
|
5787
5947
|
}
|
|
5788
5948
|
});
|
|
5789
5949
|
const now = Date.now();
|
|
5790
|
-
|
|
5950
|
+
instances2.set(instanceKey, {
|
|
5791
5951
|
process: proc,
|
|
5792
5952
|
port,
|
|
5793
5953
|
userId,
|
|
@@ -5827,7 +5987,7 @@ async function supportsOwnerCleanup(port, capability) {
|
|
|
5827
5987
|
}
|
|
5828
5988
|
async function ensurePlaywright(userId, topic) {
|
|
5829
5989
|
const instanceKey = makeInstanceKey(userId, topic);
|
|
5830
|
-
const inProgress =
|
|
5990
|
+
const inProgress = spawning2.get(instanceKey);
|
|
5831
5991
|
if (inProgress) {
|
|
5832
5992
|
const port = await inProgress;
|
|
5833
5993
|
if (port !== null)
|
|
@@ -5835,9 +5995,9 @@ async function ensurePlaywright(userId, topic) {
|
|
|
5835
5995
|
return ensurePlaywright(userId, topic);
|
|
5836
5996
|
}
|
|
5837
5997
|
const promise = (async () => {
|
|
5838
|
-
const existing =
|
|
5998
|
+
const existing = instances2.get(instanceKey);
|
|
5839
5999
|
if (existing && !existing.process.killed && existing.process.exitCode === null) {
|
|
5840
|
-
if (await
|
|
6000
|
+
if (await isHealthy2(existing.port)) {
|
|
5841
6001
|
existing.lastUsedAt = Date.now();
|
|
5842
6002
|
return existing.port;
|
|
5843
6003
|
}
|
|
@@ -5848,18 +6008,18 @@ async function ensurePlaywright(userId, topic) {
|
|
|
5848
6008
|
cleanSingletonFiles(resolveUserDataDir(instanceKey));
|
|
5849
6009
|
} else if (existing) {
|
|
5850
6010
|
releasePort(existing.port);
|
|
5851
|
-
|
|
6011
|
+
instances2.delete(instanceKey);
|
|
5852
6012
|
cleanSingletonFiles(resolveUserDataDir(instanceKey));
|
|
5853
6013
|
}
|
|
5854
6014
|
return spawnPlaywright(instanceKey, userId);
|
|
5855
|
-
})().finally(() =>
|
|
5856
|
-
|
|
6015
|
+
})().finally(() => spawning2.delete(instanceKey));
|
|
6016
|
+
spawning2.set(instanceKey, promise);
|
|
5857
6017
|
return promise;
|
|
5858
6018
|
}
|
|
5859
6019
|
async function waitForPortRelease(port, timeoutMs = 3000) {
|
|
5860
6020
|
const start = Date.now();
|
|
5861
6021
|
while (Date.now() - start < timeoutMs) {
|
|
5862
|
-
if (!
|
|
6022
|
+
if (!isPortInUse2(port))
|
|
5863
6023
|
return;
|
|
5864
6024
|
await delay(200);
|
|
5865
6025
|
}
|
|
@@ -5867,24 +6027,24 @@ async function waitForPortRelease(port, timeoutMs = 3000) {
|
|
|
5867
6027
|
async function waitForServer(port, timeoutMs) {
|
|
5868
6028
|
const start = Date.now();
|
|
5869
6029
|
while (Date.now() - start < timeoutMs) {
|
|
5870
|
-
if (await
|
|
6030
|
+
if (await isHealthy2(port))
|
|
5871
6031
|
return true;
|
|
5872
6032
|
await delay(300);
|
|
5873
6033
|
}
|
|
5874
6034
|
logger.warn({ port, timeoutMs }, "Playwright MCP not ready before timeout");
|
|
5875
6035
|
return false;
|
|
5876
6036
|
}
|
|
5877
|
-
var BASE_PORT, MAX_PORT, MAX_IDLE_MS,
|
|
5878
|
-
var
|
|
6037
|
+
var BASE_PORT, MAX_PORT, MAX_IDLE_MS, instances2, usedPorts2, spawning2, pinnedInstances, _onPlaywrightExit = null;
|
|
6038
|
+
var init_manager2 = __esm(async () => {
|
|
5879
6039
|
init_config();
|
|
5880
6040
|
init_logger();
|
|
5881
6041
|
await init_browser_profiles();
|
|
5882
6042
|
BASE_PORT = PLAYWRIGHT_BASE_PORT;
|
|
5883
6043
|
MAX_PORT = PLAYWRIGHT_MAX_PORT;
|
|
5884
6044
|
MAX_IDLE_MS = 2 * 60 * 60 * 1000;
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
6045
|
+
instances2 = new Map;
|
|
6046
|
+
usedPorts2 = new Set;
|
|
6047
|
+
spawning2 = new Map;
|
|
5888
6048
|
pinnedInstances = new Map;
|
|
5889
6049
|
setInterval(() => {
|
|
5890
6050
|
while (evictIdleInstance() !== null) {}
|
|
@@ -6263,7 +6423,7 @@ var init_derive = __esm(async () => {
|
|
|
6263
6423
|
await init_bus();
|
|
6264
6424
|
init_config();
|
|
6265
6425
|
init_logger();
|
|
6266
|
-
await
|
|
6426
|
+
await init_manager2();
|
|
6267
6427
|
await init_active_rooms();
|
|
6268
6428
|
await init_api_messages();
|
|
6269
6429
|
await init_api_topic_config();
|
|
@@ -7543,178 +7703,6 @@ function dispatchPeerRuntimeFile(request) {
|
|
|
7543
7703
|
}
|
|
7544
7704
|
var activeBridge = null;
|
|
7545
7705
|
|
|
7546
|
-
// ../../packages/core/src/platform/background-bash/manager.ts
|
|
7547
|
-
import { execFileSync as execFileSync3, spawn as spawn4 } from "child_process";
|
|
7548
|
-
function makeBgBashKey(_userId, topic) {
|
|
7549
|
-
return `topic:${topic}`;
|
|
7550
|
-
}
|
|
7551
|
-
function pidsOnPort(port) {
|
|
7552
|
-
try {
|
|
7553
|
-
return execFileSync3("lsof", ["-i", `:${port}`, "-t"], { stdio: "pipe" }).toString().trim().split(`
|
|
7554
|
-
`).map((pid) => parseInt(pid, 10)).filter((pid) => !Number.isNaN(pid));
|
|
7555
|
-
} catch {
|
|
7556
|
-
return [];
|
|
7557
|
-
}
|
|
7558
|
-
}
|
|
7559
|
-
function isPortInUse2(port) {
|
|
7560
|
-
return pidsOnPort(port).length > 0;
|
|
7561
|
-
}
|
|
7562
|
-
function processCommand(pid) {
|
|
7563
|
-
try {
|
|
7564
|
-
return execFileSync3("ps", ["-p", String(pid), "-o", "command="], { stdio: "pipe" }).toString().trim();
|
|
7565
|
-
} catch {
|
|
7566
|
-
return "";
|
|
7567
|
-
}
|
|
7568
|
-
}
|
|
7569
|
-
function isBackgroundBashServerProcess(cmdline, port) {
|
|
7570
|
-
return cmdline.includes("background-bash-server.ts") && cmdline.includes(`--port=${port}`);
|
|
7571
|
-
}
|
|
7572
|
-
async function killBackgroundBashOnPort(port) {
|
|
7573
|
-
for (const pid of pidsOnPort(port)) {
|
|
7574
|
-
const cmdline = processCommand(pid);
|
|
7575
|
-
if (!isBackgroundBashServerProcess(cmdline, port)) {
|
|
7576
|
-
logger.warn({ port, pid, cmdline: cmdline.slice(0, 120) }, "background-bash port occupied by non-background-bash process; skipping");
|
|
7577
|
-
continue;
|
|
7578
|
-
}
|
|
7579
|
-
try {
|
|
7580
|
-
process.kill(pid, "SIGKILL");
|
|
7581
|
-
logger.info({ port, pid }, "Killed zombie background-bash server");
|
|
7582
|
-
} catch (e) {
|
|
7583
|
-
logger.warn({ err: e, port, pid }, "Failed to kill zombie background-bash server");
|
|
7584
|
-
}
|
|
7585
|
-
}
|
|
7586
|
-
const deadline = Date.now() + 2000;
|
|
7587
|
-
while (Date.now() < deadline) {
|
|
7588
|
-
if (!isPortInUse2(port))
|
|
7589
|
-
return;
|
|
7590
|
-
await delay(100);
|
|
7591
|
-
}
|
|
7592
|
-
}
|
|
7593
|
-
async function allocatePort2() {
|
|
7594
|
-
for (let port = BG_BASH_BASE_PORT;port <= BG_BASH_MAX_PORT; port++) {
|
|
7595
|
-
if (usedPorts2.has(port))
|
|
7596
|
-
continue;
|
|
7597
|
-
usedPorts2.add(port);
|
|
7598
|
-
if (isPortInUse2(port)) {
|
|
7599
|
-
await killBackgroundBashOnPort(port);
|
|
7600
|
-
if (isPortInUse2(port)) {
|
|
7601
|
-
usedPorts2.delete(port);
|
|
7602
|
-
continue;
|
|
7603
|
-
}
|
|
7604
|
-
}
|
|
7605
|
-
return port;
|
|
7606
|
-
}
|
|
7607
|
-
throw new Error(`No available ports for background-bash (range ${BG_BASH_BASE_PORT}-${BG_BASH_MAX_PORT}, ${instances2.size} active)`);
|
|
7608
|
-
}
|
|
7609
|
-
async function isHealthy2(port) {
|
|
7610
|
-
try {
|
|
7611
|
-
const res = await fetch(`http://127.0.0.1:${port}/health`, {
|
|
7612
|
-
signal: AbortSignal.timeout(2000)
|
|
7613
|
-
});
|
|
7614
|
-
res.body?.cancel();
|
|
7615
|
-
return res.ok;
|
|
7616
|
-
} catch {
|
|
7617
|
-
return false;
|
|
7618
|
-
}
|
|
7619
|
-
}
|
|
7620
|
-
async function spawnBgBash(key, userId, topic, reservedPort) {
|
|
7621
|
-
const port = reservedPort ?? await allocatePort2();
|
|
7622
|
-
const serverArgs = [
|
|
7623
|
-
"run",
|
|
7624
|
-
BACKGROUND_BASH_SERVER,
|
|
7625
|
-
`--user-id=${userId}`,
|
|
7626
|
-
`--topic=${topic}`,
|
|
7627
|
-
`--port=${port}`
|
|
7628
|
-
];
|
|
7629
|
-
const proc = spawn4("bun", serverArgs, {
|
|
7630
|
-
stdio: "ignore",
|
|
7631
|
-
detached: false,
|
|
7632
|
-
env: { ...process.env }
|
|
7633
|
-
});
|
|
7634
|
-
proc.once("error", (err) => {
|
|
7635
|
-
logger.error({ err, key }, "background-bash server error");
|
|
7636
|
-
if (instances2.get(key)?.process === proc) {
|
|
7637
|
-
usedPorts2.delete(port);
|
|
7638
|
-
instances2.delete(key);
|
|
7639
|
-
}
|
|
7640
|
-
});
|
|
7641
|
-
proc.once("exit", (code) => {
|
|
7642
|
-
logger.info({ key, code }, "background-bash server exited");
|
|
7643
|
-
if (instances2.get(key)?.process === proc) {
|
|
7644
|
-
usedPorts2.delete(port);
|
|
7645
|
-
instances2.delete(key);
|
|
7646
|
-
}
|
|
7647
|
-
});
|
|
7648
|
-
const now = Date.now();
|
|
7649
|
-
instances2.set(key, { process: proc, port, startedAt: now, lastUsedAt: now });
|
|
7650
|
-
const start = Date.now();
|
|
7651
|
-
while (Date.now() - start < 8000) {
|
|
7652
|
-
if (await isHealthy2(port)) {
|
|
7653
|
-
logger.info({ key, port, pid: proc.pid }, "background-bash server ready");
|
|
7654
|
-
return port;
|
|
7655
|
-
}
|
|
7656
|
-
await delay(200);
|
|
7657
|
-
}
|
|
7658
|
-
killBgBash(userId, topic);
|
|
7659
|
-
throw new Error(`background-bash server failed health check after spawn on port ${port}`);
|
|
7660
|
-
}
|
|
7661
|
-
async function ensureBgBash(userId, topic) {
|
|
7662
|
-
const key = makeBgBashKey(userId, topic);
|
|
7663
|
-
const inProgress = spawning2.get(key);
|
|
7664
|
-
if (inProgress)
|
|
7665
|
-
return inProgress;
|
|
7666
|
-
const promise = (async () => {
|
|
7667
|
-
const existing = instances2.get(key);
|
|
7668
|
-
if (existing && !existing.process.killed && existing.process.exitCode === null) {
|
|
7669
|
-
if (await isHealthy2(existing.port)) {
|
|
7670
|
-
existing.lastUsedAt = Date.now();
|
|
7671
|
-
return existing.port;
|
|
7672
|
-
}
|
|
7673
|
-
killBgBash(userId, topic);
|
|
7674
|
-
} else if (existing) {
|
|
7675
|
-
usedPorts2.delete(existing.port);
|
|
7676
|
-
instances2.delete(key);
|
|
7677
|
-
}
|
|
7678
|
-
return spawnBgBash(key, userId, topic);
|
|
7679
|
-
})().finally(() => spawning2.delete(key));
|
|
7680
|
-
spawning2.set(key, promise);
|
|
7681
|
-
return promise;
|
|
7682
|
-
}
|
|
7683
|
-
function killBgBash(userId, topic) {
|
|
7684
|
-
const key = makeBgBashKey(userId, topic);
|
|
7685
|
-
const inst = instances2.get(key);
|
|
7686
|
-
if (!inst)
|
|
7687
|
-
return;
|
|
7688
|
-
try {
|
|
7689
|
-
inst.process.kill("SIGTERM");
|
|
7690
|
-
} catch {}
|
|
7691
|
-
usedPorts2.delete(inst.port);
|
|
7692
|
-
instances2.delete(key);
|
|
7693
|
-
logger.info({ key, port: inst.port }, "background-bash server killed");
|
|
7694
|
-
}
|
|
7695
|
-
var instances2, usedPorts2, spawning2, MAX_IDLE_MS2;
|
|
7696
|
-
var init_manager2 = __esm(() => {
|
|
7697
|
-
init_config();
|
|
7698
|
-
init_logger();
|
|
7699
|
-
instances2 = new Map;
|
|
7700
|
-
usedPorts2 = new Set;
|
|
7701
|
-
spawning2 = new Map;
|
|
7702
|
-
MAX_IDLE_MS2 = 2 * 60 * 60 * 1000;
|
|
7703
|
-
setInterval(() => {
|
|
7704
|
-
const now = Date.now();
|
|
7705
|
-
for (const [key, inst] of instances2) {
|
|
7706
|
-
if (now - inst.lastUsedAt > MAX_IDLE_MS2) {
|
|
7707
|
-
logger.info({ key }, "evicting idle background-bash server");
|
|
7708
|
-
try {
|
|
7709
|
-
inst.process.kill("SIGTERM");
|
|
7710
|
-
} catch {}
|
|
7711
|
-
usedPorts2.delete(inst.port);
|
|
7712
|
-
instances2.delete(key);
|
|
7713
|
-
}
|
|
7714
|
-
}
|
|
7715
|
-
}, 30 * 60 * 1000).unref();
|
|
7716
|
-
});
|
|
7717
|
-
|
|
7718
7706
|
// ../../packages/core/src/query/state.ts
|
|
7719
7707
|
import { mkdirSync as mkdirSync11, readdirSync as readdirSync4, renameSync as renameSync7, unlinkSync as unlinkSync10, writeFileSync as writeFileSync9 } from "fs";
|
|
7720
7708
|
import { join as join17 } from "path";
|
|
@@ -9241,6 +9229,7 @@ var init_ask_callbacks = __esm(async () => {
|
|
|
9241
9229
|
var exports_turn_runner = {};
|
|
9242
9230
|
__export(exports_turn_runner, {
|
|
9243
9231
|
workspaceCwdFor: () => workspaceCwdFor,
|
|
9232
|
+
withDefaultPlaywright: () => withDefaultPlaywright,
|
|
9244
9233
|
wasLocallyRequeuedAfterUserPreemption: () => wasLocallyRequeuedAfterUserPreemption,
|
|
9245
9234
|
upsertTaskPanelMessage: () => upsertTaskPanelMessage,
|
|
9246
9235
|
triggerTopicAiTurn: () => triggerTopicAiTurn,
|
|
@@ -9299,6 +9288,14 @@ __export(exports_turn_runner, {
|
|
|
9299
9288
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
9300
9289
|
import { mkdirSync as mkdirSync16, realpathSync as realpathSync3, statSync as statSync6 } from "fs";
|
|
9301
9290
|
import { isAbsolute as isAbsolute2, resolve as resolve8 } from "path";
|
|
9291
|
+
function withDefaultPlaywright(configuredMcp, isManager) {
|
|
9292
|
+
if (isManager)
|
|
9293
|
+
return configuredMcp;
|
|
9294
|
+
const enabled = new Set(configuredMcp);
|
|
9295
|
+
enabled.add("playwright");
|
|
9296
|
+
enabled.add("background-bash");
|
|
9297
|
+
return [...enabled];
|
|
9298
|
+
}
|
|
9302
9299
|
function appendSystemMessage(topicId, text) {
|
|
9303
9300
|
const message = {
|
|
9304
9301
|
id: randomUUID9(),
|
|
@@ -9413,6 +9410,7 @@ async function streamAgentEvents(topicId, topicTitle, queryId, events, control,
|
|
|
9413
9410
|
queryId,
|
|
9414
9411
|
agentType,
|
|
9415
9412
|
model,
|
|
9413
|
+
sourceNode: execution?.sourceNode,
|
|
9416
9414
|
usage,
|
|
9417
9415
|
createdAt: new Date().toISOString()
|
|
9418
9416
|
};
|
|
@@ -9889,6 +9887,7 @@ function redispatchInject(inject) {
|
|
|
9889
9887
|
bridgeSessionFromHistory: inject.bridgeSessionFromHistory,
|
|
9890
9888
|
onSettled: inject.onSettled,
|
|
9891
9889
|
peerBridge: inject.peerBridge,
|
|
9890
|
+
sourceNode: inject.sourceNode,
|
|
9892
9891
|
askReplySources: inject.askReplySources,
|
|
9893
9892
|
_runtimeEpoch: inject.runtimeEpoch,
|
|
9894
9893
|
_sessionRetried: inject._sessionRetried,
|
|
@@ -10215,6 +10214,7 @@ function startAiTurn(params) {
|
|
|
10215
10214
|
let sessionId = sessionResolution.sessionId;
|
|
10216
10215
|
const deferredSessionId = params.sessionId === undefined && !sessionResolution.isolated ? undefined : sessionId;
|
|
10217
10216
|
const origin = params.origin ?? "user";
|
|
10217
|
+
const sourceNode = params.sourceNode;
|
|
10218
10218
|
const topicId = topic.id;
|
|
10219
10219
|
const requestId = params.requestId;
|
|
10220
10220
|
const depth = params.depth;
|
|
@@ -10264,6 +10264,7 @@ function startAiTurn(params) {
|
|
|
10264
10264
|
userId,
|
|
10265
10265
|
prompt,
|
|
10266
10266
|
origin,
|
|
10267
|
+
sourceNode,
|
|
10267
10268
|
requestId,
|
|
10268
10269
|
depth,
|
|
10269
10270
|
silent,
|
|
@@ -10547,7 +10548,8 @@ function startAiTurn(params) {
|
|
|
10547
10548
|
content: agentPrompt
|
|
10548
10549
|
});
|
|
10549
10550
|
}
|
|
10550
|
-
const
|
|
10551
|
+
const configuredMcp = override?.mcp ?? [];
|
|
10552
|
+
const enabledMcp = withDefaultPlaywright(configuredMcp, isManager);
|
|
10551
10553
|
const playwrightRequested = !isManager && enabledMcp.includes("playwright");
|
|
10552
10554
|
const browserProfileOwner = isTopicBrowserProfileOwner(topic.id, userId);
|
|
10553
10555
|
const wantsPlaywright = playwrightRequested && browserProfileOwner;
|
|
@@ -10655,7 +10657,7 @@ ${playwrightNote}`;
|
|
|
10655
10657
|
WsHub.get().broadcastTyping(topicId, "ai");
|
|
10656
10658
|
WsHub.get().broadcastAiActive(topicId, queryId);
|
|
10657
10659
|
}
|
|
10658
|
-
streamAgentEvents(topicId, topic.title, queryId, events, control, agentKind, resolvedModel, resolvedEffort, userId, !sessionRetried, onSessionId, { silent, peerBridge }).then(async (outcome) => {
|
|
10660
|
+
streamAgentEvents(topicId, topic.title, queryId, events, control, agentKind, resolvedModel, resolvedEffort, userId, !sessionRetried, onSessionId, { silent, peerBridge, sourceNode }).then(async (outcome) => {
|
|
10659
10661
|
if (outcome.kind === "session-expired") {
|
|
10660
10662
|
if (!silent)
|
|
10661
10663
|
WsHub.get().broadcastAborted(topicId, queryId, "stopped");
|
|
@@ -10800,6 +10802,8 @@ function triggerTopicAiTurn(topicId, userId, prompt, agentType, opts) {
|
|
|
10800
10802
|
topicId,
|
|
10801
10803
|
authorId: opts?.injectAuthorId ?? userId,
|
|
10802
10804
|
sourceAdapter: opts?.injectSourceAdapter,
|
|
10805
|
+
sourceNode: opts?.injectSourceNode,
|
|
10806
|
+
sourceMessageId: opts?.injectSourceMessageId,
|
|
10803
10807
|
authorName: execution.agent,
|
|
10804
10808
|
text: prompt,
|
|
10805
10809
|
agentType: execution.agent,
|
|
@@ -10839,6 +10843,7 @@ function triggerTopicAiTurn(topicId, userId, prompt, agentType, opts) {
|
|
|
10839
10843
|
bridgeSessionFromHistory: opts?.bridgeSessionFromHistory,
|
|
10840
10844
|
onSettled: opts?.onSettled,
|
|
10841
10845
|
peerBridge: opts?.peerBridge,
|
|
10846
|
+
sourceNode: opts?.injectSourceNode,
|
|
10842
10847
|
askReplySources: opts?.askReplySources,
|
|
10843
10848
|
from: opts?.from
|
|
10844
10849
|
});
|
|
@@ -10854,11 +10859,11 @@ var init_turn_runner = __esm(async () => {
|
|
|
10854
10859
|
await init_registry();
|
|
10855
10860
|
init_tool_format();
|
|
10856
10861
|
await init_bus();
|
|
10857
|
-
|
|
10862
|
+
init_manager();
|
|
10858
10863
|
init_constants();
|
|
10859
10864
|
init_logger();
|
|
10860
10865
|
init_mcp_config();
|
|
10861
|
-
await
|
|
10866
|
+
await init_manager2();
|
|
10862
10867
|
init_builders();
|
|
10863
10868
|
await init_active_rooms();
|
|
10864
10869
|
init_state();
|
|
@@ -11173,11 +11178,11 @@ var init_lifecycle = __esm(async () => {
|
|
|
11173
11178
|
await init_spawn_subagent();
|
|
11174
11179
|
await init_topic_cleanup();
|
|
11175
11180
|
await init_bus();
|
|
11176
|
-
|
|
11181
|
+
init_manager();
|
|
11177
11182
|
init_config();
|
|
11178
11183
|
init_constants();
|
|
11179
11184
|
init_logger();
|
|
11180
|
-
await
|
|
11185
|
+
await init_manager2();
|
|
11181
11186
|
await init_active_rooms();
|
|
11182
11187
|
init_session_inbox_cleanup();
|
|
11183
11188
|
init_state();
|
|
@@ -11303,6 +11308,9 @@ await init_turn_runner();
|
|
|
11303
11308
|
await init_bus();
|
|
11304
11309
|
await init_turn_runner();
|
|
11305
11310
|
await init_api_messages();
|
|
11311
|
+
// ../../packages/core/src/application/switch-topic-access-mode.ts
|
|
11312
|
+
await init_bus();
|
|
11313
|
+
await init_api_topics();
|
|
11306
11314
|
// ../../packages/core/src/application/switch-topic-effort.ts
|
|
11307
11315
|
await init_registry();
|
|
11308
11316
|
await init_bus();
|
|
@@ -11434,7 +11442,7 @@ var TEXT_EXTENSIONS = new Set([
|
|
|
11434
11442
|
var IMAGE_EXTENSIONS = new Set([".jpg", ".jpeg", ".png", ".bmp", ".tiff", ".tif", ".webp"]);
|
|
11435
11443
|
|
|
11436
11444
|
// ../../packages/core/src/index.ts
|
|
11437
|
-
|
|
11445
|
+
init_manager();
|
|
11438
11446
|
init_config();
|
|
11439
11447
|
init_constants();
|
|
11440
11448
|
init_jsonl();
|
|
@@ -11464,7 +11472,7 @@ db.exec(`
|
|
|
11464
11472
|
db.exec("CREATE INDEX IF NOT EXISTS idx_runtime_process_leases_heartbeat ON runtime_process_leases(heartbeat_at)");
|
|
11465
11473
|
|
|
11466
11474
|
// ../../packages/core/src/index.ts
|
|
11467
|
-
await
|
|
11475
|
+
await init_manager2();
|
|
11468
11476
|
await init_active_rooms();
|
|
11469
11477
|
init_session_inbox_path();
|
|
11470
11478
|
init_types2();
|
|
@@ -13217,4 +13225,4 @@ export {
|
|
|
13217
13225
|
CRON_CONTEXT_RETAIN_TURNS
|
|
13218
13226
|
};
|
|
13219
13227
|
|
|
13220
|
-
//# debugId=
|
|
13228
|
+
//# debugId=A44DB415735252A864756E2164756E21
|