@gleanwork/mcp-server-tester 2.0.0-beta.2 → 2.0.0-beta.3

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/cli/index.js CHANGED
@@ -13,6 +13,7 @@ import { createRequire } from 'module';
13
13
  import os, { homedir, tmpdir, userInfo } from 'os';
14
14
  import { promisify } from 'util';
15
15
  import crypto, { randomUUID, createHash } from 'crypto';
16
+ import { setTimeout as setTimeout$1 } from 'timers/promises';
16
17
  import { Command } from 'commander';
17
18
  import { render, useApp, useInput, Box, Text } from 'ink';
18
19
  import React, { useState, useEffect, useCallback, useRef } from 'react';
@@ -942,11 +943,11 @@ function isNodeDefaultFetch(fetch2) {
942
943
  return source.includes("internal/deps/undici") || source.includes("lazy loading of undici");
943
944
  }
944
945
  async function createSafeNodeFetch() {
945
- const [{ createRequire: createRequire3 }, { lookup }] = await Promise.all([
946
+ const [{ createRequire: createRequire4 }, { lookup }] = await Promise.all([
946
947
  loadNodeModule("node:module"),
947
948
  loadNodeModule("node:dns")
948
949
  ]);
949
- const { Agent, fetch: fetch2 } = createRequire3(getCurrentModulePath())(
950
+ const { Agent, fetch: fetch2 } = createRequire4(getCurrentModulePath())(
950
951
  "undici"
951
952
  );
952
953
  const dispatcher = new Agent({
@@ -2454,7 +2455,7 @@ function createProviderToolFactory({
2454
2455
  inputSchema
2455
2456
  }) {
2456
2457
  return ({
2457
- execute,
2458
+ execute: execute2,
2458
2459
  outputSchema,
2459
2460
  needsApproval,
2460
2461
  toModelOutput,
@@ -2468,7 +2469,7 @@ function createProviderToolFactory({
2468
2469
  args,
2469
2470
  inputSchema,
2470
2471
  outputSchema,
2471
- execute,
2472
+ execute: execute2,
2472
2473
  needsApproval,
2473
2474
  toModelOutput,
2474
2475
  onInputStart,
@@ -2483,7 +2484,7 @@ function createProviderToolFactoryWithOutputSchema({
2483
2484
  supportsDeferredResults
2484
2485
  }) {
2485
2486
  return ({
2486
- execute,
2487
+ execute: execute2,
2487
2488
  needsApproval,
2488
2489
  toModelOutput,
2489
2490
  onInputStart,
@@ -2496,7 +2497,7 @@ function createProviderToolFactoryWithOutputSchema({
2496
2497
  args,
2497
2498
  inputSchema,
2498
2499
  outputSchema,
2499
- execute,
2500
+ execute: execute2,
2500
2501
  needsApproval,
2501
2502
  toModelOutput,
2502
2503
  onInputStart,
@@ -2654,11 +2655,11 @@ function isAsyncIterable(obj) {
2654
2655
  return obj != null && typeof obj[Symbol.asyncIterator] === "function";
2655
2656
  }
2656
2657
  async function* executeTool({
2657
- execute,
2658
+ execute: execute2,
2658
2659
  input,
2659
2660
  options
2660
2661
  }) {
2661
- const result = execute(input, options);
2662
+ const result = execute2(input, options);
2662
2663
  if (isAsyncIterable(result)) {
2663
2664
  let lastOutput;
2664
2665
  for await (const output of result) {
@@ -3563,6 +3564,30 @@ var init_pythonRuntime = __esm({
3563
3564
  exec = promisify(execFile);
3564
3565
  }
3565
3566
  });
3567
+
3568
+ // src/evals/cowork/driver.ts
3569
+ var COMPUTER_USE_TOKEN_FIELDS, CoworkDriverError, CoworkHitlBudgetError;
3570
+ var init_driver = __esm({
3571
+ "src/evals/cowork/driver.ts"() {
3572
+ init_esm_shims();
3573
+ COMPUTER_USE_TOKEN_FIELDS = [
3574
+ "input_tokens",
3575
+ "output_tokens",
3576
+ "cache_creation_input_tokens",
3577
+ "cache_read_input_tokens"
3578
+ ];
3579
+ CoworkDriverError = class extends Error {
3580
+ constructor(message, telemetry2) {
3581
+ super(message);
3582
+ this.telemetry = telemetry2;
3583
+ }
3584
+ kind = "failed";
3585
+ };
3586
+ CoworkHitlBudgetError = class extends CoworkDriverError {
3587
+ kind = "hitl-budget-exhausted";
3588
+ };
3589
+ }
3590
+ });
3566
3591
  function resolveDriverPath(env) {
3567
3592
  const configuredRoot = env.MST_COWORK_DRIVER_ROOT;
3568
3593
  if (configuredRoot) {
@@ -3616,7 +3641,7 @@ async function runComputerUseDriver(query2, options, mode, label) {
3616
3641
  const childError = error;
3617
3642
  stdout = String(childError.stdout ?? "");
3618
3643
  const reported = parseLastJsonLine(stdout);
3619
- const telemetry2 = parseTelemetry(reported?.telemetry, env, "partial");
3644
+ const telemetry3 = parseTelemetry(reported?.telemetry, env, "partial");
3620
3645
  const details = error.killed === true ? "driver terminated before completion; not retrying" : "driver exited unsuccessfully; not retrying";
3621
3646
  if (mode === "hitl" && reported?.status === "failed" && typeof reported.error === "string" && /^Computer Use HITL check exceeded \d+ actions after attempting a visible prompt$/.test(
3622
3647
  reported.error
@@ -3626,13 +3651,13 @@ async function runComputerUseDriver(query2, options, mode, label) {
3626
3651
  );
3627
3652
  throw new ComputerUseHitlBudgetError(
3628
3653
  `HITL inspection reached its ${maxActions}-action budget.`,
3629
- telemetry2
3654
+ telemetry3
3630
3655
  );
3631
3656
  }
3632
3657
  diagnostic(`Computer Use ${label} failed: ${details}`);
3633
3658
  throw new ComputerUseDriverError(
3634
3659
  `Computer Use ${label} failed: ${details}`,
3635
- telemetry2
3660
+ telemetry3
3636
3661
  );
3637
3662
  }
3638
3663
  diagnostic(
@@ -3652,11 +3677,11 @@ async function runComputerUseDriver(query2, options, mode, label) {
3652
3677
  parseTelemetry(record.telemetry, env, "partial")
3653
3678
  );
3654
3679
  }
3655
- const telemetry = parseTelemetry(record.telemetry, env, "complete");
3680
+ const telemetry2 = parseTelemetry(record.telemetry, env, "complete");
3656
3681
  const common = {
3657
3682
  action_count: record.action_count,
3658
3683
  model: record.model,
3659
- ...telemetry ? { telemetry } : {}
3684
+ ...telemetry2 ? { telemetry: telemetry2 } : {}
3660
3685
  };
3661
3686
  diagnostic(
3662
3687
  `Computer Use ${label} completed (actions=${common.action_count})`
@@ -3666,7 +3691,7 @@ async function runComputerUseDriver(query2, options, mode, label) {
3666
3691
  if (submission?.action !== "key" || submission.text !== "enter") {
3667
3692
  throw new ComputerUseDriverError(
3668
3693
  "Computer Use submission returned an invalid submission boundary.",
3669
- telemetry ? { ...telemetry, accounting: "partial" } : void 0
3694
+ telemetry2 ? { ...telemetry2, accounting: "partial" } : void 0
3670
3695
  );
3671
3696
  }
3672
3697
  return {
@@ -3726,7 +3751,7 @@ function parseTelemetry(value, env, accounting) {
3726
3751
  if (!usage || !coverage) return void 0;
3727
3752
  const safeUsage = {};
3728
3753
  const safeCoverage = {};
3729
- for (const field of TOKEN_FIELDS) {
3754
+ for (const field of COMPUTER_USE_TOKEN_FIELDS) {
3730
3755
  const count = coverage[field];
3731
3756
  if (!isCount(count) || count > record.planner_response_count)
3732
3757
  return void 0;
@@ -3756,26 +3781,22 @@ function parseTelemetry(value, env, accounting) {
3756
3781
  cost: { status: "unavailable" }
3757
3782
  };
3758
3783
  }
3759
- var execFileAsync, TOKEN_FIELDS, ComputerUseDriverError, ComputerUseHitlBudgetError;
3784
+ var execFileAsync, ComputerUseDriverError, ComputerUseHitlBudgetError;
3760
3785
  var init_anthropicComputerUse = __esm({
3761
3786
  "src/evals/cowork/anthropicComputerUse.ts"() {
3762
3787
  init_esm_shims();
3763
3788
  init_pythonRuntime();
3789
+ init_driver();
3764
3790
  execFileAsync = promisify(execFile);
3765
- TOKEN_FIELDS = [
3766
- "input_tokens",
3767
- "output_tokens",
3768
- "cache_creation_input_tokens",
3769
- "cache_read_input_tokens"
3770
- ];
3771
- ComputerUseDriverError = class extends Error {
3772
- constructor(message, telemetry) {
3773
- super(message);
3774
- this.telemetry = telemetry;
3791
+ ComputerUseDriverError = class extends CoworkDriverError {
3792
+ constructor(message, telemetry2) {
3793
+ super(message, telemetry2);
3794
+ this.telemetry = telemetry2;
3775
3795
  }
3776
3796
  };
3777
3797
  ComputerUseHitlBudgetError = class extends ComputerUseDriverError {
3778
3798
  name = "ComputerUseHitlBudgetError";
3799
+ kind = "hitl-budget-exhausted";
3779
3800
  };
3780
3801
  }
3781
3802
  });
@@ -5011,7 +5032,7 @@ async function prepareMacCoworkSession(options) {
5011
5032
  const { running: wasRunning } = await controller.state();
5012
5033
  await mkdir(lease, { mode: 448 });
5013
5034
  const receiptPath = join(lease, "session.json");
5014
- const receipt = Buffer.from(
5035
+ const receipt2 = Buffer.from(
5015
5036
  JSON.stringify({
5016
5037
  version: 1,
5017
5038
  nonce: randomUUID(),
@@ -5024,7 +5045,7 @@ async function prepareMacCoworkSession(options) {
5024
5045
  let leaseInfo;
5025
5046
  try {
5026
5047
  leaseInfo = await lstat(lease);
5027
- await writeFile(receiptPath, receipt, { mode: 384, flag: "wx" });
5048
+ await writeFile(receiptPath, receipt2, { mode: 384, flag: "wx" });
5028
5049
  } catch {
5029
5050
  try {
5030
5051
  await rmdir(lease);
@@ -5036,7 +5057,7 @@ async function prepareMacCoworkSession(options) {
5036
5057
  let installAttempted = false;
5037
5058
  const ownsLease = async () => {
5038
5059
  const current = await lstat(lease);
5039
- if (!current.isDirectory() || current.isSymbolicLink() || current.dev !== leaseInfo.dev || current.ino !== leaseInfo.ino || current.uid !== process.getuid?.() || (current.mode & 63) !== 0 || !(await privateBytes(receiptPath)).equals(receipt))
5060
+ if (!current.isDirectory() || current.isSymbolicLink() || current.dev !== leaseInfo.dev || current.ino !== leaseInfo.ino || current.uid !== process.getuid?.() || (current.mode & 63) !== 0 || !(await privateBytes(receiptPath)).equals(receipt2))
5040
5061
  throw new Error(ERROR3);
5041
5062
  const names = await readdir(lease);
5042
5063
  if (names.length !== 1 || names[0] !== "session.json")
@@ -5088,7 +5109,7 @@ async function prepareMacCoworkSession(options) {
5088
5109
  } catch {
5089
5110
  const after = await lstat(lease);
5090
5111
  if (after.isDirectory() && after.dev === leaseInfo.dev && after.ino === leaseInfo.ino)
5091
- await writeFile(receiptPath, receipt, { mode: 384, flag: "wx" });
5112
+ await writeFile(receiptPath, receipt2, { mode: 384, flag: "wx" });
5092
5113
  throw new Error(ERROR3);
5093
5114
  }
5094
5115
  } catch {
@@ -5193,16 +5214,16 @@ async function recoverMacCoworkSession() {
5193
5214
  if (!leaseInfo.isDirectory() || leaseInfo.isSymbolicLink() || leaseInfo.uid !== process.getuid?.() || leaseInfo.mode & 63)
5194
5215
  throw new Error("Recovery lease is not a private owned directory.");
5195
5216
  const bytes = await privateFile(receiptPath);
5196
- const receipt = Receipt.parse(JSON.parse(bytes.toString("utf8")));
5197
- if (receipt.profileDirectory !== profile)
5217
+ const receipt2 = Receipt.parse(JSON.parse(bytes.toString("utf8")));
5218
+ if (receipt2.profileDirectory !== profile)
5198
5219
  throw new Error("Recovery profile mismatch.");
5199
- assertStoppedOwner(receipt.pid);
5220
+ assertStoppedOwner(receipt2.pid);
5200
5221
  await mkdir(gate, { mode: 448 });
5201
5222
  const verifyLease = async () => {
5202
5223
  const current = await lstat(lease);
5203
5224
  if (current.dev !== leaseInfo.dev || current.ino !== leaseInfo.ino || !(await privateFile(receiptPath)).equals(bytes) || (await readdir(lease)).join() !== "session.json")
5204
5225
  throw new Error("Recovery lease changed; refusing cleanup.");
5205
- assertStoppedOwner(receipt.pid);
5226
+ assertStoppedOwner(receipt2.pid);
5206
5227
  };
5207
5228
  try {
5208
5229
  await verifyLease();
@@ -5211,9 +5232,9 @@ async function recoverMacCoworkSession() {
5211
5232
  const journal = JSON.parse(
5212
5233
  (await privateFile(join(transaction, "journal.json"))).toString("utf8")
5213
5234
  );
5214
- if (journal.directory !== receipt.stagingDirectory)
5235
+ if (journal.directory !== receipt2.stagingDirectory)
5215
5236
  throw new Error("Recovery staging mismatch.");
5216
- } else if (await exists3(receipt.stagingDirectory))
5237
+ } else if (await exists3(receipt2.stagingDirectory))
5217
5238
  throw new Error(
5218
5239
  "Staging exists without its transaction; recovery refused."
5219
5240
  );
@@ -5223,10 +5244,10 @@ async function recoverMacCoworkSession() {
5223
5244
  throw new Error("Claude did not stop; recovery refused.");
5224
5245
  await verifyLease();
5225
5246
  if (hasTransaction) await restoreMacCoworkSettings(profile);
5226
- if (await exists3(transaction) || await exists3(receipt.stagingDirectory))
5247
+ if (await exists3(transaction) || await exists3(receipt2.stagingDirectory))
5227
5248
  throw new Error("Recovery state remains; lease retained.");
5228
- if (receipt.wasRunning) await controller.start();
5229
- if ((await controller.state()).running !== receipt.wasRunning)
5249
+ if (receipt2.wasRunning) await controller.start();
5250
+ if ((await controller.state()).running !== receipt2.wasRunning)
5230
5251
  throw new Error(
5231
5252
  "Could not restore prior Claude running state; lease retained."
5232
5253
  );
@@ -5294,6 +5315,240 @@ var init_macos = __esm({
5294
5315
  }
5295
5316
  });
5296
5317
 
5318
+ // src/evals/cowork/linux.ts
5319
+ var linux_exports = {};
5320
+ __export(linux_exports, {
5321
+ linuxCoworkPlatform: () => linuxCoworkPlatform
5322
+ });
5323
+ async function readSettings(file) {
5324
+ const handle = await open$1(
5325
+ file,
5326
+ constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK
5327
+ );
5328
+ try {
5329
+ const metadata2 = await handle.stat();
5330
+ if (!metadata2.isFile() || metadata2.size > 1024 * 1024)
5331
+ throw new Error("Invalid prepared settings file.");
5332
+ return JSON.parse(await handle.readFile("utf8"));
5333
+ } finally {
5334
+ await handle.close();
5335
+ }
5336
+ }
5337
+ function telemetry(started, actions, accounting) {
5338
+ return {
5339
+ driver: "linux-desktop",
5340
+ accounting,
5341
+ duration_ms: Math.max(0, Date.now() - started),
5342
+ action_count: actions,
5343
+ planner: { status: "not-applicable" },
5344
+ cost: { status: "not-applicable" }
5345
+ };
5346
+ }
5347
+ function receipt(stdout) {
5348
+ try {
5349
+ const parsed = JSON.parse(stdout);
5350
+ if (!parsed || typeof parsed !== "object") return void 0;
5351
+ const r = parsed;
5352
+ if (!["ready", "submitted", "hitl_checked", "failed"].includes(
5353
+ String(r.status)
5354
+ ) || typeof r.action_count !== "number" || !Number.isSafeInteger(r.action_count) || r.action_count < 0 || typeof r.duration_ms !== "number" || !Number.isFinite(r.duration_ms) || r.duration_ms < 0)
5355
+ return void 0;
5356
+ return {
5357
+ status: r.status,
5358
+ action_count: r.action_count,
5359
+ duration_ms: r.duration_ms,
5360
+ errorCode: typeof r.error === "string" && /^[a-z_]{1,64}$/.test(r.error) ? r.error : void 0
5361
+ };
5362
+ } catch {
5363
+ return void 0;
5364
+ }
5365
+ }
5366
+ async function execute(mode, payload, options) {
5367
+ const started = Date.now();
5368
+ const remaining = options.deadlineAt - started;
5369
+ if (remaining <= 0)
5370
+ throw new CoworkDriverError(
5371
+ "Linux desktop deadline exceeded; no action attempted."
5372
+ );
5373
+ const env = { ...process.env, ...options.env };
5374
+ if (!env.DISPLAY || !env.DBUS_SESSION_BUS_ADDRESS)
5375
+ throw new CoworkDriverError(
5376
+ "Linux Cowork requires a prepared DISPLAY and D-Bus desktop session."
5377
+ );
5378
+ if (env.MST_COWORK_URL_OPENER !== void 0 && !isAbsolute(env.MST_COWORK_URL_OPENER))
5379
+ throw new CoworkDriverError(
5380
+ "MST_COWORK_URL_OPENER must be an absolute executable path."
5381
+ );
5382
+ const script = createRequire(
5383
+ typeof __filename$1 === "string" ? __filename$1 : import.meta.url
5384
+ ).resolve("@gleanwork/mcp-server-tester/cowork-linux-runtime");
5385
+ const timeout = Math.min(remaining, mode === "submit" ? 6e4 : 1e4);
5386
+ const driverTimeout = Math.max(
5387
+ 1,
5388
+ Math.floor(timeout - Math.min(1e3, timeout / 10))
5389
+ );
5390
+ const result = await new Promise(
5391
+ (resolve10) => {
5392
+ const child = execFile(
5393
+ env.MST_COWORK_PYTHON ?? "python3",
5394
+ [
5395
+ script,
5396
+ "--mode",
5397
+ mode,
5398
+ "--timeout-ms",
5399
+ String(driverTimeout),
5400
+ "--max-actions",
5401
+ String(options.maxActions ?? 24)
5402
+ ],
5403
+ {
5404
+ timeout,
5405
+ killSignal: "SIGKILL",
5406
+ maxBuffer: 64 * 1024,
5407
+ env: {
5408
+ ...Object.fromEntries(
5409
+ SESSION_ENV.flatMap((k) => env[k] ? [[k, env[k]]] : [])
5410
+ ),
5411
+ NO_AT_BRIDGE: "0"
5412
+ }
5413
+ },
5414
+ (error, stdout) => resolve10({ failed: error !== null, stdout: String(stdout) })
5415
+ );
5416
+ child.stdin?.on("error", () => {
5417
+ });
5418
+ child.stdin?.end(JSON.stringify(payload));
5419
+ }
5420
+ );
5421
+ const record = receipt(result.stdout);
5422
+ const expected = mode === "probe" ? "ready" : mode === "submit" ? "submitted" : "hitl_checked";
5423
+ if (result.failed || record?.status !== expected || record.action_count > (options.maxActions ?? 24))
5424
+ throw new CoworkDriverError(
5425
+ `Linux desktop ${mode} failed or its receipt was uncertain (${record?.errorCode ?? "missing_or_invalid_receipt"}); no retry attempted.`,
5426
+ record ? telemetry(started, record.action_count, "partial") : void 0
5427
+ );
5428
+ return record;
5429
+ }
5430
+ var SESSION_ENV, linuxCoworkPlatform;
5431
+ var init_linux = __esm({
5432
+ "src/evals/cowork/linux.ts"() {
5433
+ init_esm_shims();
5434
+ init_driver();
5435
+ SESSION_ENV = [
5436
+ "PATH",
5437
+ "HOME",
5438
+ "DISPLAY",
5439
+ "XAUTHORITY",
5440
+ "DBUS_SESSION_BUS_ADDRESS",
5441
+ "AT_SPI_BUS_ADDRESS",
5442
+ "XDG_RUNTIME_DIR",
5443
+ "XDG_CONFIG_HOME",
5444
+ "MST_COWORK_URL_OPENER",
5445
+ "LANG",
5446
+ "LC_ALL"
5447
+ ];
5448
+ linuxCoworkPlatform = {
5449
+ dataDirectory: (options) => options.dataDir ?? join(
5450
+ process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"),
5451
+ "Claude-3p",
5452
+ "local-agent-mode-sessions"
5453
+ ),
5454
+ async prepare({ manifest, env, model }) {
5455
+ const settingsFile = env.MST_COWORK_SETTINGS_FILE ?? "/etc/claude-desktop/managed-settings.json";
5456
+ if (!isAbsolute(settingsFile))
5457
+ throw new Error("Linux Cowork settings path must be absolute.");
5458
+ try {
5459
+ const settings = await readSettings(settingsFile);
5460
+ const models = settings.inferenceModels;
5461
+ if (model && !models?.some((m) => m.name === model))
5462
+ throw new Error("model");
5463
+ const expected = manifest.servers ?? [];
5464
+ const actual = settings.managedMcpServers;
5465
+ if (!Array.isArray(actual) || actual.length !== expected.length || settings.allowManagedMcpServersOnly !== true)
5466
+ throw new Error("servers");
5467
+ for (const [index, server] of expected.entries()) {
5468
+ if (server.transport !== "http") throw new Error("transport");
5469
+ const observed = actual.find(
5470
+ (s) => s.name === (server.label ?? `server-${index + 1}`)
5471
+ );
5472
+ if (!observed || observed.transport !== "http" || observed.url !== server.serverUrl)
5473
+ throw new Error("server");
5474
+ if (observed.toolPolicy?.["*"] === "allow" && manifest.coworkSetup?.approveWriteTools !== true)
5475
+ throw new Error("policy");
5476
+ }
5477
+ } catch {
5478
+ throw new Error(
5479
+ "Prepared Linux desktop settings do not match the eval model, MCP servers, or approval policy."
5480
+ );
5481
+ }
5482
+ await execute(
5483
+ "probe",
5484
+ {},
5485
+ { deadlineAt: Date.now() + 15e3, maxActions: 1, env }
5486
+ );
5487
+ return {
5488
+ async dispose() {
5489
+ }
5490
+ };
5491
+ },
5492
+ async recover() {
5493
+ throw new Error(
5494
+ "Linux desktop recovery belongs to the runtime owner, not the MST driver."
5495
+ );
5496
+ },
5497
+ async submit(query2, options) {
5498
+ const started = Date.now();
5499
+ const result = await execute("submit", { prompt: query2 }, options);
5500
+ return {
5501
+ status: "submitted",
5502
+ action_count: result.action_count,
5503
+ telemetry: telemetry(started, result.action_count, "complete")
5504
+ };
5505
+ },
5506
+ async handleHitl(options) {
5507
+ if (!options.isComplete)
5508
+ throw new Error("Linux HITL requires a bound native completion check.");
5509
+ const started = Date.now();
5510
+ let actions = 0;
5511
+ const budget = options.maxActions ?? 12;
5512
+ try {
5513
+ while (Date.now() < options.deadlineAt) {
5514
+ if (await options.isComplete())
5515
+ return {
5516
+ status: "hitl_checked",
5517
+ action_count: actions,
5518
+ telemetry: telemetry(started, actions, "complete")
5519
+ };
5520
+ if (actions >= budget)
5521
+ throw new CoworkHitlBudgetError(
5522
+ "Linux HITL action budget exhausted.",
5523
+ telemetry(started, actions, "partial")
5524
+ );
5525
+ const result = await execute(
5526
+ "hitl",
5527
+ { approveWriteTools: options.approveWriteTools === true },
5528
+ { ...options, maxActions: budget - actions }
5529
+ );
5530
+ actions += result.action_count;
5531
+ await setTimeout$1(
5532
+ Math.min(500, Math.max(0, options.deadlineAt - Date.now()))
5533
+ );
5534
+ }
5535
+ } catch (error) {
5536
+ if (error instanceof CoworkHitlBudgetError) throw error;
5537
+ const partialActions = error instanceof CoworkDriverError ? error.telemetry?.action_count ?? 0 : 0;
5538
+ throw new CoworkDriverError(
5539
+ "Linux HITL failed for the bound native session.",
5540
+ telemetry(started, actions + partialActions, "partial")
5541
+ );
5542
+ }
5543
+ throw new CoworkDriverError(
5544
+ "Linux HITL deadline exceeded.",
5545
+ telemetry(started, actions, "partial")
5546
+ );
5547
+ }
5548
+ };
5549
+ }
5550
+ });
5551
+
5297
5552
  // src/cli/index.ts
5298
5553
  init_esm_shims();
5299
5554
 
@@ -5375,7 +5630,7 @@ init_esm_shims();
5375
5630
 
5376
5631
  // package.json
5377
5632
  var package_default = {
5378
- version: "2.0.0-beta.2"};
5633
+ version: "2.0.0-beta.3"};
5379
5634
 
5380
5635
  // src/cli/templates/index.ts
5381
5636
  function getPlaywrightConfigTemplate(answers) {
@@ -7116,8 +7371,8 @@ ${errorText}`
7116
7371
  return;
7117
7372
  }
7118
7373
  try {
7119
- const open5 = await import('open');
7120
- await open5.default(url.toString());
7374
+ const open6 = await import('open');
7375
+ await open6.default(url.toString());
7121
7376
  debug2("Opened browser for authentication");
7122
7377
  } catch (error) {
7123
7378
  debug2("Failed to open browser:", error);
@@ -10565,11 +10820,13 @@ init_esm_shims();
10565
10820
 
10566
10821
  // src/evals/cowork/platform.ts
10567
10822
  init_esm_shims();
10568
- async function getCoworkPlatform() {
10569
- if (process.platform === "darwin")
10823
+ async function getCoworkPlatform(provider) {
10824
+ if (process.platform === "darwin" && provider === "anthropic-computer-use")
10570
10825
  return (await Promise.resolve().then(() => (init_macos(), macos_exports))).macCoworkPlatform;
10826
+ if (process.platform === "linux" && provider === "linux-desktop")
10827
+ return (await Promise.resolve().then(() => (init_linux(), linux_exports))).linuxCoworkPlatform;
10571
10828
  throw new Error(
10572
- `Cowork has no qualified ${process.platform} desktop adapter. The shared runner is portable; native execution is currently macOS-only.`
10829
+ `Cowork driver ${provider} is not supported on ${process.platform}.`
10573
10830
  );
10574
10831
  }
10575
10832
 
@@ -11155,7 +11412,7 @@ async function waitForClaudeMatch(options, requireCompletion) {
11155
11412
  }
11156
11413
  lastPending = trace;
11157
11414
  }
11158
- await delay2(POLL_INTERVAL_MS);
11415
+ await delay3(POLL_INTERVAL_MS);
11159
11416
  }
11160
11417
  if (lastPending) {
11161
11418
  throw new Error(
@@ -11318,7 +11575,7 @@ async function waitForAccessibilityTrace(options) {
11318
11575
  if (fallback) {
11319
11576
  return fallback;
11320
11577
  }
11321
- await delay2(POLL_INTERVAL_MS);
11578
+ await delay3(POLL_INTERVAL_MS);
11322
11579
  }
11323
11580
  throw new Error(
11324
11581
  `Timed out waiting for Claude Chat Desktop visible response for marker ${options.context.marker}`
@@ -11407,6 +11664,7 @@ async function parseClaudeTrace(candidate, marker16) {
11407
11664
  auditParsed,
11408
11665
  transcriptParsed,
11409
11666
  usageAvailable: usage !== void 0,
11667
+ knownUsageFields: knownUsageFields(resultEvents),
11410
11668
  costAvailable: resultEvents.some(
11411
11669
  (event) => typeof event.total_cost_usd === "number"
11412
11670
  ),
@@ -11918,6 +12176,35 @@ function extractAggregatedUsage(events) {
11918
12176
  cacheCreationInputTokens: (total.cacheCreationInputTokens ?? 0) + (value.cacheCreationInputTokens ?? 0)
11919
12177
  }));
11920
12178
  }
12179
+ function knownUsageFields(events) {
12180
+ const fields = [
12181
+ [
12182
+ "inputTokens",
12183
+ (event) => event.usage?.input_tokens ?? event.usage?.inputTokens
12184
+ ],
12185
+ [
12186
+ "outputTokens",
12187
+ (event) => event.usage?.output_tokens ?? event.usage?.outputTokens
12188
+ ],
12189
+ [
12190
+ "cacheReadInputTokens",
12191
+ (event) => event.usage?.cache_read_input_tokens ?? event.usage?.cacheReadInputTokens
12192
+ ],
12193
+ [
12194
+ "cacheCreationInputTokens",
12195
+ (event) => event.usage?.cache_creation_input_tokens ?? event.usage?.cacheCreationInputTokens
12196
+ ],
12197
+ ["totalCostUsd", (event) => event.total_cost_usd],
12198
+ ["durationMs", (event) => event.duration_ms],
12199
+ ["durationApiMs", (event) => event.duration_api_ms]
12200
+ ];
12201
+ return fields.filter(
12202
+ ([key, value]) => events.length > 0 && events.every((event) => {
12203
+ const number = value(event);
12204
+ return typeof number === "number" && Number.isFinite(number) && number >= 0 && (key === "totalCostUsd" || Number.isSafeInteger(number));
12205
+ })
12206
+ ).map(([key]) => key);
12207
+ }
11921
12208
  function extractUsage(event) {
11922
12209
  const usage = event.usage;
11923
12210
  const inputTokens = getNumber(usage, "input_tokens") ?? getNumber(usage, "inputTokens");
@@ -12014,19 +12301,28 @@ function classifyTraceFailure(message) {
12014
12301
  function formatError2(err) {
12015
12302
  return err instanceof Error ? err.message : String(err);
12016
12303
  }
12017
- function delay2(ms) {
12304
+ function delay3(ms) {
12018
12305
  return new Promise((resolve10) => setTimeout(resolve10, ms));
12019
12306
  }
12020
12307
 
12021
12308
  // src/evals/coworkHost.ts
12022
- init_anthropicComputerUse();
12309
+ init_driver();
12023
12310
  var OptionsSchema = z.object({
12024
- computerUseProvider: z.literal("anthropic-computer-use").default("anthropic-computer-use"),
12311
+ computerUseProvider: z.enum(["anthropic-computer-use", "linux-desktop"]).default(
12312
+ () => process.platform === "linux" ? "linux-desktop" : "anthropic-computer-use"
12313
+ ),
12025
12314
  computerUseMaxActions: z.number().int().min(1).max(64).default(24),
12026
12315
  hitlMaxActions: z.number().int().min(1).max(24).default(12),
12027
12316
  computerUseModel: z.string().regex(/^[A-Za-z0-9._:-]+$/).optional(),
12028
12317
  dataDir: z.string().min(1).optional()
12029
- }).strict();
12318
+ }).strict().superRefine((options, context) => {
12319
+ if (options.computerUseProvider === "linux-desktop" && options.computerUseModel !== void 0)
12320
+ context.addIssue({
12321
+ code: "custom",
12322
+ path: ["computerUseModel"],
12323
+ message: "linux-desktop does not use a planner model."
12324
+ });
12325
+ });
12030
12326
  var CoworkSchema = z.object({
12031
12327
  type: z.string(),
12032
12328
  options: OptionsSchema.default(() => OptionsSchema.parse({})),
@@ -12052,9 +12348,9 @@ async function runBatch(requests, context, selectedPlatform) {
12052
12348
  );
12053
12349
  const config = configs[0];
12054
12350
  const env = { ...process.env, ...context.env, ...config.env };
12055
- if (!env.ANTHROPIC_API_KEY)
12351
+ if (config.options.computerUseProvider === "anthropic-computer-use" && !env.ANTHROPIC_API_KEY)
12056
12352
  throw new Error("ANTHROPIC_API_KEY is required for Cowork Computer Use.");
12057
- const platform = await getCoworkPlatform();
12353
+ const platform = await getCoworkPlatform(config.options.computerUseProvider);
12058
12354
  const dataDir = platform.dataDirectory(config.options);
12059
12355
  const servers = requests[0].input.servers;
12060
12356
  const { arms: _arms, ...manifest } = context.manifest;
@@ -12085,7 +12381,7 @@ async function runBatch(requests, context, selectedPlatform) {
12085
12381
  active = true;
12086
12382
  try {
12087
12383
  if (env.MST_COWORK_RECOVER === "1") await platform.recover();
12088
- if (servers.length || config.model)
12384
+ if (config.options.computerUseProvider === "linux-desktop" || servers.length || config.model)
12089
12385
  session = await platform.prepare({
12090
12386
  manifest: managedManifest,
12091
12387
  env,
@@ -12145,7 +12441,7 @@ async function runBatch(requests, context, selectedPlatform) {
12145
12441
  if (computerUse.submission.status !== "completed") {
12146
12442
  computerUse.submission = {
12147
12443
  status: "failed",
12148
- ...error instanceof ComputerUseDriverError && error.telemetry ? { telemetry: error.telemetry } : {}
12444
+ ...error instanceof CoworkDriverError && error.telemetry ? { telemetry: error.telemetry } : {}
12149
12445
  };
12150
12446
  }
12151
12447
  results[index] = failure(safeError(error));
@@ -12184,6 +12480,18 @@ async function runBatch(requests, context, selectedPlatform) {
12184
12480
  maxActions: config.options.hitlMaxActions,
12185
12481
  model: config.options.computerUseModel,
12186
12482
  env,
12483
+ approveWriteTools: context.manifest.coworkSetup?.approveWriteTools === true,
12484
+ isComplete: async () => {
12485
+ const current = await findMatchingClaudeSessions({
12486
+ ...match,
12487
+ sessionPath
12488
+ });
12489
+ if (current.length !== 1)
12490
+ throw new Error(
12491
+ "Bound native session is missing or ambiguous."
12492
+ );
12493
+ return current[0].isComplete;
12494
+ },
12187
12495
  task: `Handle only the currently open Cowork task just submitted with this exact query: ${request.input.scenario}. Do not switch tasks. Never create, type, or resubmit a task. If the current task cannot be identified uniquely, stop without an action.`
12188
12496
  });
12189
12497
  computerUse.hitl = {
@@ -12193,11 +12501,11 @@ async function runBatch(requests, context, selectedPlatform) {
12193
12501
  }
12194
12502
  } catch (error) {
12195
12503
  computerUse.hitl = {
12196
- status: error instanceof ComputerUseHitlBudgetError ? "budget-exhausted" : "failed",
12197
- ...error instanceof ComputerUseDriverError && error.telemetry ? { telemetry: error.telemetry } : {}
12504
+ status: error instanceof CoworkDriverError && error.kind === "hitl-budget-exhausted" ? "budget-exhausted" : "failed",
12505
+ ...error instanceof CoworkDriverError && error.telemetry ? { telemetry: error.telemetry } : {}
12198
12506
  };
12199
12507
  const message = safeError(error);
12200
- if (error instanceof ComputerUseHitlBudgetError) {
12508
+ if (error instanceof CoworkDriverError && error.kind === "hitl-budget-exhausted") {
12201
12509
  hitlWarning = message;
12202
12510
  } else {
12203
12511
  hitlError = message;
@@ -12350,7 +12658,7 @@ async function runBuiltinHost(input, host, context, factory) {
12350
12658
  }
12351
12659
  controller.signal.throwIfAborted();
12352
12660
  }
12353
- async function execute() {
12661
+ async function execute2() {
12354
12662
  try {
12355
12663
  checkDeadline();
12356
12664
  if (config.hostType !== "cli") {
@@ -12428,9 +12736,9 @@ async function runBuiltinHost(input, host, context, factory) {
12428
12736
  if (config.hostType === "cli" && config.cli?.claudeMcpServers !== void 0) {
12429
12737
  void expired.catch(() => {
12430
12738
  });
12431
- return await execute();
12739
+ return await execute2();
12432
12740
  }
12433
- const result = await Promise.race([execute(), expired]);
12741
+ const result = await Promise.race([execute2(), expired]);
12434
12742
  checkDeadline();
12435
12743
  return result;
12436
12744
  } catch (error) {
@@ -16325,7 +16633,7 @@ async function runEvalSuite(options) {
16325
16633
  for (const arm of armResults) {
16326
16634
  totalHostUsage = sumUsage(totalHostUsage, arm.result?.totalHostUsage);
16327
16635
  }
16328
- const telemetry = {
16636
+ const telemetry2 = {
16329
16637
  cases: allResults.length,
16330
16638
  toolCalls: countToolCalls(allResults),
16331
16639
  failedCases: allResults.filter((result) => !result.pass).length,
@@ -16345,7 +16653,7 @@ async function runEvalSuite(options) {
16345
16653
  passRate: allResults.length > 0 ? allResults.filter((result) => result.pass).length / allResults.length : 0,
16346
16654
  ...computedMetrics
16347
16655
  },
16348
- telemetry,
16656
+ telemetry: telemetry2,
16349
16657
  armDeltas: buildArmDeltas(armResults),
16350
16658
  results: allResults
16351
16659
  };