negotium 0.1.25 → 0.1.26

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/cron.js CHANGED
@@ -3468,7 +3468,7 @@ var init_claude_provider = __esm(async () => {
3468
3468
  });
3469
3469
 
3470
3470
  // ../../packages/core/src/version.ts
3471
- var NEGOTIUM_VERSION = "0.1.25";
3471
+ var NEGOTIUM_VERSION = "0.1.26";
3472
3472
 
3473
3473
  // ../../packages/core/src/agents/codex-native-multi-agent.ts
3474
3474
  import { spawn as spawn3 } from "child_process";
@@ -5790,6 +5790,38 @@ var init_self_schedules = __esm(async () => {
5790
5790
  `);
5791
5791
  });
5792
5792
 
5793
+ // ../../packages/core/src/platform/playwright/headed-launch.ts
5794
+ import { accessSync, constants } from "fs";
5795
+ import { delimiter, isAbsolute, resolve as resolve5 } from "path";
5796
+ function findExecutableOnPath(command, environment = process.env) {
5797
+ const candidates = isAbsolute(command) ? [command] : (environment.PATH ?? "").split(delimiter).filter(Boolean).map((directory) => resolve5(directory, command));
5798
+ for (const candidate of candidates) {
5799
+ try {
5800
+ accessSync(candidate, constants.X_OK);
5801
+ return candidate;
5802
+ } catch {}
5803
+ }
5804
+ return null;
5805
+ }
5806
+ function resolveHeadedPlaywrightSpawn(command, args, options = {}) {
5807
+ const platform2 = options.platform ?? process.platform;
5808
+ const environment = options.environment ?? process.env;
5809
+ const hasDisplay = Boolean(environment.DISPLAY?.trim() || environment.WAYLAND_DISPLAY?.trim());
5810
+ if (platform2 !== "linux" || hasDisplay) {
5811
+ return { command, args: [...args], virtualDisplay: false };
5812
+ }
5813
+ const xvfbRun = (options.findExecutable ?? findExecutableOnPath)("xvfb-run", environment);
5814
+ if (!xvfbRun) {
5815
+ throw new Error("Headed Playwright on Linux requires DISPLAY/WAYLAND_DISPLAY or xvfb-run; install Xvfb and ensure xvfb-run is on PATH");
5816
+ }
5817
+ return {
5818
+ command: xvfbRun,
5819
+ args: ["-a", "-s", "-screen 0 1440x1000x24", command, ...args],
5820
+ virtualDisplay: true
5821
+ };
5822
+ }
5823
+ var init_headed_launch = () => {};
5824
+
5793
5825
  // ../../packages/core/src/storage/browser-profiles.ts
5794
5826
  function normalizeBrowserProfileName(name) {
5795
5827
  const value = name.trim().toLowerCase();
@@ -5894,7 +5926,7 @@ import {
5894
5926
  unlinkSync as unlinkSync9,
5895
5927
  writeFileSync as writeFileSync7
5896
5928
  } from "fs";
5897
- import { dirname as dirname8, join as join13, resolve as resolve5 } from "path";
5929
+ import { dirname as dirname8, join as join13, resolve as resolve6 } from "path";
5898
5930
  function makeInstanceKey(userId, topic) {
5899
5931
  if (!topic)
5900
5932
  return makeBrowserProfileInstanceKey(userId, "default");
@@ -5912,7 +5944,7 @@ function migrateLegacyTopicProfile(ownerId, topic) {
5912
5944
  const current2 = getTopicBrowserProfile(topic);
5913
5945
  if (current2 !== "default" || !hasBrowserProfileTopic(topic))
5914
5946
  return current2;
5915
- const legacyDir = resolve5(BROWSER_PROFILES_DIR, sanitizeTopicName(topic));
5947
+ const legacyDir = resolve6(BROWSER_PROFILES_DIR, sanitizeTopicName(topic));
5916
5948
  if (!existsSync13(legacyDir))
5917
5949
  return current2;
5918
5950
  const profile = legacyBrowserProfileName(topic);
@@ -6091,11 +6123,11 @@ async function killPlaywrightOnPort(port, expectedUserDataDir) {
6091
6123
  }
6092
6124
  function browserProcessMatchesExpectedProfile(cmdline, expectedUserDataDir) {
6093
6125
  const actualUserDataDir = extractUserDataDirArg(cmdline);
6094
- return actualUserDataDir !== null && resolve5(actualUserDataDir) === resolve5(expectedUserDataDir);
6126
+ return actualUserDataDir !== null && resolve6(actualUserDataDir) === resolve6(expectedUserDataDir);
6095
6127
  }
6096
6128
  function killBrowserProcsForUserDataDir(userDataDir) {
6097
- const target = resolve5(userDataDir);
6098
- if (!target.startsWith(resolve5(BROWSER_PROFILES_DIR)))
6129
+ const target = resolve6(userDataDir);
6130
+ if (!target.startsWith(resolve6(BROWSER_PROFILES_DIR)))
6099
6131
  return;
6100
6132
  let pids;
6101
6133
  try {
@@ -6115,7 +6147,7 @@ function killBrowserProcsForUserDataDir(userDataDir) {
6115
6147
  stdio: "pipe"
6116
6148
  }).toString().trim();
6117
6149
  const argDir = extractUserDataDirArg(cmdline);
6118
- if (!argDir || resolve5(argDir) !== target)
6150
+ if (!argDir || resolve6(argDir) !== target)
6119
6151
  continue;
6120
6152
  killProcessTreeChildren(pidNum);
6121
6153
  process.kill(pidNum, "SIGKILL");
@@ -6126,13 +6158,13 @@ function killBrowserProcsForUserDataDir(userDataDir) {
6126
6158
  }
6127
6159
  }
6128
6160
  function selectOrphanBrowserPids(procs, liveUserDataDirs, profileRoot, selfPid) {
6129
- const root = resolve5(profileRoot);
6130
- const live = new Set([...liveUserDataDirs].map((d) => resolve5(d)));
6161
+ const root = resolve6(profileRoot);
6162
+ const live = new Set([...liveUserDataDirs].map((d) => resolve6(d)));
6131
6163
  const out = [];
6132
6164
  for (const { pid, userDataDir } of procs) {
6133
6165
  if (pid === selfPid || !userDataDir)
6134
6166
  continue;
6135
- const dir = resolve5(userDataDir);
6167
+ const dir = resolve6(userDataDir);
6136
6168
  if (!dir.startsWith(root))
6137
6169
  continue;
6138
6170
  if (live.has(dir))
@@ -6148,7 +6180,7 @@ function reapOrphanBrowsers() {
6148
6180
  const daemonLease = getRuntimeProcessLease("node-daemon", Date.now(), Number.POSITIVE_INFINITY);
6149
6181
  if (!isBrowserJanitorOwner(daemonLease?.pid ?? null, process.pid))
6150
6182
  return;
6151
- const profileRoot = resolve5(BROWSER_PROFILES_DIR);
6183
+ const profileRoot = resolve6(BROWSER_PROFILES_DIR);
6152
6184
  let pids;
6153
6185
  try {
6154
6186
  pids = execFileSync4("pgrep", ["-f", "--", profileRoot], { stdio: "pipe" }).toString().trim();
@@ -6201,7 +6233,7 @@ function cleanSingletonFiles(userDataDir) {
6201
6233
  for (const f of files) {
6202
6234
  if (f.startsWith("Singleton")) {
6203
6235
  try {
6204
- unlinkSync9(resolve5(userDataDir, f));
6236
+ unlinkSync9(resolve6(userDataDir, f));
6205
6237
  logger.info({ file: f, userDataDir }, "Removed stale Singleton file");
6206
6238
  } catch (e) {
6207
6239
  logger.warn({ err: e, file: f }, "Failed to remove stale Chrome Singleton file");
@@ -6237,7 +6269,7 @@ function ownerDirectory(ownerId) {
6237
6269
  return `${sanitizeTopicName(ownerId).slice(0, 24)}_${digest}`;
6238
6270
  }
6239
6271
  function resolveProfileDir(ownerId, profile) {
6240
- return resolve5(BROWSER_PROFILES_DIR, "profiles", ownerDirectory(ownerId), profile);
6272
+ return resolve6(BROWSER_PROFILES_DIR, "profiles", ownerDirectory(ownerId), profile);
6241
6273
  }
6242
6274
  function resolveUserDataDir(instanceKey) {
6243
6275
  const { ownerId, profile } = parseInstanceKey(instanceKey);
@@ -6295,11 +6327,25 @@ async function spawnPlaywright(instanceKey, userId, reservedPort, browserBin = P
6295
6327
  }
6296
6328
  const command = browserBin.endsWith(".mjs") ? process.execPath : browserBin;
6297
6329
  const args = browserBin.endsWith(".mjs") ? [browserBin, ...mcpArgs] : mcpArgs;
6298
- const proc = spawn4(command, args, {
6299
- stdio: "ignore",
6300
- detached: false,
6301
- env: childEnv
6302
- });
6330
+ let spawnSpec;
6331
+ try {
6332
+ spawnSpec = resolveHeadedPlaywrightSpawn(command, args, { environment: childEnv });
6333
+ } catch (err) {
6334
+ releasePort(port);
6335
+ throw err;
6336
+ }
6337
+ let proc;
6338
+ try {
6339
+ proc = spawn4(spawnSpec.command, spawnSpec.args, {
6340
+ stdio: "ignore",
6341
+ detached: false,
6342
+ env: childEnv
6343
+ });
6344
+ } catch (err) {
6345
+ releasePort(port);
6346
+ throw err;
6347
+ }
6348
+ const spawnError = waitForChildProcessSpawnError(proc);
6303
6349
  const reapCrashedBrowser = () => {
6304
6350
  const userDataDir2 = resolveUserDataDir(instanceKey);
6305
6351
  killBrowserProcsForUserDataDir(userDataDir2);
@@ -6332,7 +6378,10 @@ async function spawnPlaywright(instanceKey, userId, reservedPort, browserBin = P
6332
6378
  lastUsedAt: now,
6333
6379
  capability
6334
6380
  });
6335
- const ready = await waitForServer(port, 1e4) && await supportsOwnerCleanup(port, capability);
6381
+ const ready = await Promise.race([
6382
+ (async () => await waitForServer(port, 1e4) && await supportsOwnerCleanup(port, capability))(),
6383
+ spawnError
6384
+ ]);
6336
6385
  if (!ready) {
6337
6386
  const exitCode = proc.exitCode;
6338
6387
  killInstance(instanceKey);
@@ -6343,7 +6392,7 @@ async function spawnPlaywright(instanceKey, userId, reservedPort, browserBin = P
6343
6392
  throw new Error(`Playwright MCP failed health check after spawn on port ${port}` + (exitCode === null ? "" : ` (exitCode=${exitCode})`));
6344
6393
  }
6345
6394
  writePortFile(instanceKey, port);
6346
- logger.info({ instanceKey, port, pid: proc.pid, ready }, "Playwright MCP started");
6395
+ logger.info({ instanceKey, port, pid: proc.pid, ready, virtualDisplay: spawnSpec.virtualDisplay }, "Playwright MCP started");
6347
6396
  return port;
6348
6397
  }
6349
6398
  async function supportsOwnerCleanup(port, capability) {
@@ -6411,10 +6460,16 @@ async function waitForServer(port, timeoutMs) {
6411
6460
  logger.warn({ port, timeoutMs }, "Playwright MCP not ready before timeout");
6412
6461
  return false;
6413
6462
  }
6463
+ function waitForChildProcessSpawnError(proc) {
6464
+ return new Promise((_resolve, reject) => {
6465
+ proc.once("error", reject);
6466
+ });
6467
+ }
6414
6468
  var BASE_PORT, MAX_PORT, MAX_IDLE_MS, instances, usedPorts, spawning, pinnedInstances, _onPlaywrightExit = null;
6415
6469
  var init_manager2 = __esm(async () => {
6416
6470
  init_config();
6417
6471
  init_logger();
6472
+ init_headed_launch();
6418
6473
  await init_browser_profiles();
6419
6474
  await init_runtime_process_leases();
6420
6475
  BASE_PORT = PLAYWRIGHT_BASE_PORT;
@@ -7039,9 +7094,9 @@ var init_runtime_turn_requests = __esm(async () => {
7039
7094
 
7040
7095
  // ../../packages/core/src/prompts/builders.ts
7041
7096
  import { readFileSync as readFileSync10 } from "fs";
7042
- import { resolve as resolve6 } from "path";
7097
+ import { resolve as resolve7 } from "path";
7043
7098
  function loadPrompt(filename, dir = SESSIONS_DIR) {
7044
- const raw = readFileSync10(resolve6(dir, filename), "utf-8");
7099
+ const raw = readFileSync10(resolve7(dir, filename), "utf-8");
7045
7100
  return raw.replace(/\{\{RESOURCES_DIR\}\}/g, RESOURCES_DIR);
7046
7101
  }
7047
7102
  function replaceVars(template, vars) {
@@ -7084,7 +7139,7 @@ function visualDesignGuide() {
7084
7139
  return _visualDesignGuide;
7085
7140
  }
7086
7141
  function loadAgentPrompt(filename) {
7087
- const raw = readFileSync10(resolve6(AGENTS_PROMPTS_DIR, filename), "utf-8");
7142
+ const raw = readFileSync10(resolve7(AGENTS_PROMPTS_DIR, filename), "utf-8");
7088
7143
  const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
7089
7144
  if (!match)
7090
7145
  throw new Error(`Agent prompt ${filename} is missing frontmatter`);
@@ -7284,8 +7339,8 @@ var init_builders = __esm(() => {
7284
7339
  init_model_catalog();
7285
7340
  init_config();
7286
7341
  init_logger();
7287
- PROMPTS_DIR = resolve6(PROJECT_ROOT, "src/prompts");
7288
- SESSIONS_DIR = resolve6(PROMPTS_DIR, "sessions");
7342
+ PROMPTS_DIR = resolve7(PROJECT_ROOT, "src/prompts");
7343
+ SESSIONS_DIR = resolve7(PROMPTS_DIR, "sessions");
7289
7344
  });
7290
7345
 
7291
7346
  // ../../packages/core/src/storage/api-topic-brief.ts
@@ -8646,12 +8701,12 @@ var init_errors = __esm(() => {
8646
8701
 
8647
8702
  // ../../packages/core/src/runtime/event-heartbeat.ts
8648
8703
  function nextOrHeartbeat(pending, intervalMs) {
8649
- return new Promise((resolve7, reject) => {
8650
- const timer = setTimeout(() => resolve7({ kind: "heartbeat" }), intervalMs);
8704
+ return new Promise((resolve8, reject) => {
8705
+ const timer = setTimeout(() => resolve8({ kind: "heartbeat" }), intervalMs);
8651
8706
  timer.unref?.();
8652
8707
  pending.then((result) => {
8653
8708
  clearTimeout(timer);
8654
- resolve7({ kind: "event", result });
8709
+ resolve8({ kind: "event", result });
8655
8710
  }, (error) => {
8656
8711
  clearTimeout(timer);
8657
8712
  reject(error);
@@ -9078,7 +9133,7 @@ var init_visual_html = __esm(() => {
9078
9133
 
9079
9134
  // ../../packages/core/src/runtime/visuals.ts
9080
9135
  import { realpathSync as realpathSync2 } from "fs";
9081
- import { isAbsolute, resolve as resolve7 } from "path";
9136
+ import { isAbsolute as isAbsolute2, resolve as resolve8 } from "path";
9082
9137
  function activeVisualHtmlForPrompt(html) {
9083
9138
  if (html.length <= ACTIVE_VISUAL_PROMPT_MAX_CHARS) {
9084
9139
  return { html, omittedChars: 0 };
@@ -9129,8 +9184,8 @@ function topicAllowsVisualFileId(topicId, fileId) {
9129
9184
  return topicHasAttachmentFileId(topicId, fileId) || topicHasVisualFileId(topicId, fileId);
9130
9185
  }
9131
9186
  function isPathInside(baseDir, filePath) {
9132
- const base = resolve7(baseDir);
9133
- const normalized = resolve7(filePath);
9187
+ const base = resolve8(baseDir);
9188
+ const normalized = resolve8(filePath);
9134
9189
  try {
9135
9190
  const realBase = realpathSync2(base);
9136
9191
  const real = realpathSync2(normalized);
@@ -9192,7 +9247,7 @@ function resolveVisualMediaInput(topicId, input) {
9192
9247
  }
9193
9248
  const rawPath = input.file_path.trim();
9194
9249
  const cwd = workspaceCwdFor(topicId);
9195
- const candidate = isAbsolute(rawPath) ? rawPath : resolve7(cwd, rawPath);
9250
+ const candidate = isAbsolute2(rawPath) ? rawPath : resolve8(cwd, rawPath);
9196
9251
  if (!isPathInside(cwd, candidate)) {
9197
9252
  return { error: "file_path must be inside the topic workspace" };
9198
9253
  }
@@ -9906,7 +9961,7 @@ __export(exports_turn_runner, {
9906
9961
  });
9907
9962
  import { randomUUID as randomUUID9 } from "crypto";
9908
9963
  import { mkdirSync as mkdirSync16, realpathSync as realpathSync3, statSync as statSync6 } from "fs";
9909
- import { isAbsolute as isAbsolute2, resolve as resolve8 } from "path";
9964
+ import { isAbsolute as isAbsolute3, resolve as resolve9 } from "path";
9910
9965
  function withDefaultPlaywright(configuredMcp, isManager) {
9911
9966
  if (isManager)
9912
9967
  return configuredMcp;
@@ -10250,7 +10305,7 @@ async function streamAgentEvents(topicId, topicTitle, queryId, events, control,
10250
10305
  case "file":
10251
10306
  if (!silent && peerBridge) {
10252
10307
  const cwd = workspaceCwdFor(topicId);
10253
- const path = isAbsolute2(event.path) ? event.path : resolve8(cwd, event.path);
10308
+ const path = isAbsolute3(event.path) ? event.path : resolve9(cwd, event.path);
10254
10309
  if (!isPathInside(cwd, path)) {
10255
10310
  logger.warn({ topicId, path }, "peer output file is outside the topic workspace");
10256
10311
  break;
@@ -12530,8 +12585,8 @@ function computeNextCronRun(expression, after = new Date, timezone) {
12530
12585
  // ../../packages/module-cron/src/scripts.ts
12531
12586
  import { spawn as spawn5 } from "child_process";
12532
12587
  import { existsSync as existsSync16, mkdirSync as mkdirSync19, readdirSync as readdirSync6 } from "fs";
12533
- import { resolve as resolve9, sep } from "path";
12534
- var CRON_JOBS_DIR = resolve9(process.env.NEGOTIUM_CRON_JOBS_DIR?.trim() || resolve9(WORKSPACE_DIR, "cron", "jobs"));
12588
+ import { resolve as resolve10, sep } from "path";
12589
+ var CRON_JOBS_DIR = resolve10(process.env.NEGOTIUM_CRON_JOBS_DIR?.trim() || resolve10(WORKSPACE_DIR, "cron", "jobs"));
12535
12590
  var DEFAULT_SCRIPT_TIMEOUT_MS = 10 * 60000;
12536
12591
  var DEFAULT_OUTPUT_LIMIT_BYTES = 1024 * 1024;
12537
12592
  var KILL_GRACE_MS = 2500;
@@ -12554,7 +12609,7 @@ function resolveCronScriptPath(script) {
12554
12609
  const valid = validateCronScriptName(script);
12555
12610
  if (!valid.ok)
12556
12611
  throw new Error(valid.error);
12557
- const path = resolve9(CRON_JOBS_DIR, script);
12612
+ const path = resolve10(CRON_JOBS_DIR, script);
12558
12613
  if (!path.startsWith(`${CRON_JOBS_DIR}${sep}`))
12559
12614
  throw new Error("script path escaped jobs directory");
12560
12615
  return path;
@@ -14012,4 +14067,4 @@ export {
14012
14067
  CRON_CONTEXT_RETAIN_TURNS
14013
14068
  };
14014
14069
 
14015
- //# debugId=33EAC29DD9B345CC64756E2164756E21
14070
+ //# debugId=8A97E8B383252AFE64756E2164756E21