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.
Files changed (47) hide show
  1. package/dist/{chunk-a9y2rwxf.js → chunk-38pf4yqx.js} +1 -2
  2. package/dist/{chunk-a9y2rwxf.js.map → chunk-38pf4yqx.js.map} +3 -3
  3. package/dist/cron.js +273 -265
  4. package/dist/cron.js.map +17 -15
  5. package/dist/hosted-agent.js +47 -23
  6. package/dist/hosted-agent.js.map +10 -8
  7. package/dist/main.js +947 -348
  8. package/dist/main.js.map +34 -31
  9. package/dist/mcp-servers.js +1 -3
  10. package/dist/mcp-servers.js.map +3 -3
  11. package/dist/prompts.js +1 -2
  12. package/dist/prompts.js.map +3 -3
  13. package/dist/registry.js +1 -1
  14. package/dist/rollout.js +1 -1
  15. package/dist/runtime/src/agents/claude-provider.ts +2 -2
  16. package/dist/runtime/src/agents/codex-provider.ts +1 -8
  17. package/dist/runtime/src/application/switch-topic-access-mode.ts +37 -0
  18. package/dist/runtime/src/index.ts +5 -0
  19. package/dist/runtime/src/mcp/background-bash-server.ts +120 -25
  20. package/dist/runtime/src/platform/background-bash/context.ts +9 -0
  21. package/dist/runtime/src/platform/background-bash/manager.ts +64 -94
  22. package/dist/runtime/src/platform/config.ts +0 -2
  23. package/dist/runtime/src/platform/mcp-config.ts +30 -13
  24. package/dist/runtime/src/prompts/sessions/manager-system.md +3 -3
  25. package/dist/runtime/src/query/active-rooms.ts +1 -0
  26. package/dist/runtime/src/runtime/turn-runner.ts +33 -7
  27. package/dist/runtime/src/storage/api-messages.ts +16 -2
  28. package/dist/runtime/src/topics/derive.ts +1 -0
  29. package/dist/runtime/src/types/api.ts +3 -0
  30. package/dist/runtime/src/version.ts +1 -1
  31. package/dist/runtime-helpers.js +9 -2
  32. package/dist/runtime-helpers.js.map +4 -4
  33. package/dist/storage.js +14 -4
  34. package/dist/storage.js.map +3 -3
  35. package/dist/types/apps/negotium/src/mcp-servers.d.ts +1 -1
  36. package/dist/types/packages/core/src/platform/background-bash/context.d.ts +1 -0
  37. package/dist/types/packages/core/src/platform/background-bash/manager.d.ts +11 -0
  38. package/dist/types/packages/core/src/platform/config.d.ts +0 -1
  39. package/dist/types/packages/core/src/platform/delay.d.ts +1 -0
  40. package/dist/types/packages/core/src/platform/mcp-config.d.ts +3 -3
  41. package/dist/types/packages/core/src/storage/api-messages.d.ts +2 -0
  42. package/dist/types/packages/core/src/types/api.d.ts +3 -0
  43. package/dist/types/packages/core/src/version.d.ts +1 -1
  44. package/dist/vault.js +1 -2
  45. package/dist/vault.js.map +3 -3
  46. package/package.json +1 -1
  47. 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, PADDLEOCR_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;
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/mcp-config.ts
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 createHmac2("sha256", capability).update(owner).digest("hex");
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
- build({ agent, bgBashPort }) {
2487
- if (bgBashPort === undefined)
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 longLivedHttpMcp(agent, bgBashPort);
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 = spawn(options.command, options.args, {
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 ??= process.env.PADDLEOCR_TIMEOUT_MS ?? "300000";
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.14";
3217
+ var NEGOTIUM_VERSION = "0.1.16";
3058
3218
 
3059
3219
  // ../../packages/core/src/agents/codex-native-multi-agent.ts
3060
- import { spawn as spawn2 } from "child_process";
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 = spawn2(process.execPath, [codexCliScriptPath(), "app-server", "--stdio"], {
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(name, server) {
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 execFileSync2, spawn as spawn3 } from "child_process";
5356
- import { createHash as createHash2, randomBytes as randomBytes4 } from "crypto";
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 instances.get(instanceKey)?.capability;
5598
+ return instances2.get(instanceKey)?.capability;
5439
5599
  }
5440
5600
  function evictIdleInstance() {
5441
5601
  const now = Date.now();
5442
- const oldestKey = selectIdleEvictionKey(instances, pinnedInstances.keys(), spawning.keys(), now, MAX_IDLE_MS);
5602
+ const oldestKey = selectIdleEvictionKey(instances2, pinnedInstances.keys(), spawning2.keys(), now, MAX_IDLE_MS);
5443
5603
  if (oldestKey) {
5444
- const instance = instances.get(oldestKey);
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 allocatePort(expectedUserDataDir) {
5629
+ async function allocatePort2(expectedUserDataDir) {
5470
5630
  for (let port = BASE_PORT;port <= MAX_PORT; port++) {
5471
- if (usedPorts.has(port))
5631
+ if (usedPorts2.has(port))
5472
5632
  continue;
5473
- usedPorts.add(port);
5474
- if (isPortInUse(port)) {
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 (isPortInUse(port)) {
5478
- usedPorts.delete(port);
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, usedPorts, isPortInUse);
5647
+ const reusablePort = selectReusablePort(BASE_PORT, MAX_PORT, usedPorts2, isPortInUse2);
5488
5648
  if (reusablePort !== null) {
5489
- usedPorts.add(reusablePort);
5649
+ usedPorts2.add(reusablePort);
5490
5650
  return reusablePort;
5491
5651
  }
5492
5652
  }
5493
- throw new Error(`No available ports for Playwright MCP (${instances.size} active instances, range ${BASE_PORT}-${MAX_PORT})`);
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
- usedPorts.delete(port);
5664
+ usedPorts2.delete(port);
5505
5665
  }
5506
- function isPortInUse(port) {
5666
+ function isPortInUse2(port) {
5507
5667
  try {
5508
- execFileSync2("lsof", ["-i", `:${port}`, "-t"], { stdio: "pipe" });
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 = execFileSync2("lsof", ["-i", `:${port}`, "-t"], { stdio: "pipe" }).toString().trim();
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 = execFileSync2("ps", ["-p", pid, "-o", "command="], { stdio: "pipe" }).toString().trim();
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 (!isPortInUse(port))
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 = execFileSync2("pgrep", ["-f", "--", userDataDir], { stdio: "pipe" }).toString().trim();
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 = execFileSync2("ps", ["-p", String(pidNum), "-o", "command="], {
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 = execFileSync2("pgrep", ["-f", "--", profileRoot], { stdio: "pipe" }).toString().trim();
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 = execFileSync2("ps", ["-p", String(pidNum), "-o", "command="], {
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 = [...instances.keys()].map(resolveUserDataDir);
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 isHealthy(port) {
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 = execFileSync2("pgrep", ["-P", String(pid)], { stdio: "pipe" }).toString().trim();
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 = instances.get(instanceKey);
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
- instances.delete(instanceKey);
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 allocatePort(userDataDir);
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 = randomBytes4(32).toString("hex");
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 = spawn3(command, args, {
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 (instances.get(instanceKey)?.process === proc) {
5933
+ if (instances2.get(instanceKey)?.process === proc) {
5774
5934
  releasePort(port);
5775
- instances.delete(instanceKey);
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 = instances.get(instanceKey)?.process === proc;
5941
+ const wasOurs = instances2.get(instanceKey)?.process === proc;
5782
5942
  if (wasOurs) {
5783
5943
  releasePort(port);
5784
- instances.delete(instanceKey);
5944
+ instances2.delete(instanceKey);
5785
5945
  reapCrashedBrowser();
5786
5946
  _onPlaywrightExit?.(instanceKey, code);
5787
5947
  }
5788
5948
  });
5789
5949
  const now = Date.now();
5790
- instances.set(instanceKey, {
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 = spawning.get(instanceKey);
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 = instances.get(instanceKey);
5998
+ const existing = instances2.get(instanceKey);
5839
5999
  if (existing && !existing.process.killed && existing.process.exitCode === null) {
5840
- if (await isHealthy(existing.port)) {
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
- instances.delete(instanceKey);
6011
+ instances2.delete(instanceKey);
5852
6012
  cleanSingletonFiles(resolveUserDataDir(instanceKey));
5853
6013
  }
5854
6014
  return spawnPlaywright(instanceKey, userId);
5855
- })().finally(() => spawning.delete(instanceKey));
5856
- spawning.set(instanceKey, promise);
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 (!isPortInUse(port))
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 isHealthy(port))
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, instances, usedPorts, spawning, pinnedInstances, _onPlaywrightExit = null;
5878
- var init_manager = __esm(async () => {
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
- instances = new Map;
5886
- usedPorts = new Set;
5887
- spawning = new Map;
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 init_manager();
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 enabledMcp = override?.mcp ?? [];
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
- init_manager2();
10862
+ init_manager();
10858
10863
  init_constants();
10859
10864
  init_logger();
10860
10865
  init_mcp_config();
10861
- await init_manager();
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
- init_manager2();
11181
+ init_manager();
11177
11182
  init_config();
11178
11183
  init_constants();
11179
11184
  init_logger();
11180
- await init_manager();
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
- init_manager2();
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 init_manager();
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=FF137F4E4189580A64756E2164756E21
13228
+ //# debugId=A44DB415735252A864756E2164756E21