@workbench-ai/workbench 0.0.77 → 0.0.79

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAgEA,MAAM,WAAW,KAAK;IACpB,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC;CAC/B;AAuTD,wBAAsB,MAAM,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,EAAE,GAAE,KAGzD,GAAG,OAAO,CAAC,MAAM,CAAC,CAoMlB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAiEA,MAAM,WAAW,KAAK;IACpB,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC;CAC/B;AAuTD,wBAAsB,MAAM,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,EAAE,GAAE,KAGzD,GAAG,OAAO,CAAC,MAAM,CAAC,CAoMlB"}
package/dist/index.js CHANGED
@@ -404,7 +404,7 @@ export async function runCli(argv, io = {
404
404
  if (command === "diff") {
405
405
  const range = optionalPositional(parsed, 1) ?? await defaultDiffRange(core);
406
406
  const diffs = await diffWorkbenchVersions(range, core);
407
- return output(diffs, parsed, io, () => diffs.map((entry) => `${entry.status}\t${entry.path}`).join("\n") || "No diff.");
407
+ return output(diffs, parsed, io, () => formatDiff(diffs));
408
408
  }
409
409
  if (command === "show") {
410
410
  return await handleShow(parsed, io);
@@ -723,6 +723,7 @@ const API_REQUEST_MAX_ATTEMPTS = 3;
723
723
  const API_REQUEST_GZIP_THRESHOLD_BYTES = 1024 * 1024;
724
724
  const CLOUD_RUN_TIMEOUT_MS = 30 * 60 * 1000;
725
725
  const CLOUD_RUN_POLL_INTERVAL_MS = 3000;
726
+ const LOGIN_WAIT_TIMEOUT_SECONDS = 120;
726
727
  async function handleLogin(parsed, io) {
727
728
  const provider = optionalPositional(parsed, 1);
728
729
  if (provider) {
@@ -739,7 +740,7 @@ async function handleLogin(parsed, io) {
739
740
  }
740
741
  if (parsed.flags["start-only"] === true && parsed.flags.wait === true) {
741
742
  throw new WorkbenchCodedError("usage", "workbench login accepts only one of --start-only or --wait.", {
742
- remediation: "Run workbench login --start-only or workbench login --wait.",
743
+ remediation: `Run workbench login --start-only or workbench login --wait --timeout ${LOGIN_WAIT_TIMEOUT_SECONDS}.`,
743
744
  exitCode: 2,
744
745
  });
745
746
  }
@@ -748,7 +749,13 @@ async function handleLogin(parsed, io) {
748
749
  const timeoutSeconds = intFlag(parsed, "timeout");
749
750
  if (startOnly && timeoutSeconds !== undefined) {
750
751
  throw new WorkbenchCodedError("usage", "workbench login --timeout only applies with --wait.", {
751
- remediation: "Run workbench login --start-only, then workbench login --wait.",
752
+ remediation: `Run workbench login --start-only, then workbench login --wait --timeout ${LOGIN_WAIT_TIMEOUT_SECONDS}.`,
753
+ exitCode: 2,
754
+ });
755
+ }
756
+ if (waitOnly && timeoutSeconds === undefined) {
757
+ throw new WorkbenchCodedError("usage", "workbench login --wait requires --timeout N.", {
758
+ remediation: `Run workbench login --wait --timeout ${LOGIN_WAIT_TIMEOUT_SECONDS}.`,
752
759
  exitCode: 2,
753
760
  });
754
761
  }
@@ -773,8 +780,8 @@ async function handleLogin(parsed, io) {
773
780
  verificationUriComplete: record.verification_uri_complete,
774
781
  userCode: record.user_code,
775
782
  expiresAt: record.expiresAt,
776
- resume: "workbench login --wait",
777
- }, parsed, io, () => `Open ${record.verification_uri_complete}\nCode: ${record.user_code}\nResume: workbench login --wait`);
783
+ resume: `workbench login --wait --timeout ${LOGIN_WAIT_TIMEOUT_SECONDS}`,
784
+ }, parsed, io, () => `Open ${record.verification_uri_complete}\nCode: ${record.user_code}\nResume: workbench login --wait --timeout ${LOGIN_WAIT_TIMEOUT_SECONDS}`);
778
785
  }
779
786
  await writePendingDeviceAuthorization(record);
780
787
  if (freshAuthorization && !parsed.flags.json) {
@@ -1047,8 +1054,7 @@ function formatFanOut(fanout) {
1047
1054
  if (fanout.linkedAgents.length === 0) {
1048
1055
  return "fanout: completed";
1049
1056
  }
1050
- const count = fanout.linkedAgents.length + (fanout.additionalAgents ?? 0);
1051
- return `fanout: linked ${count} ${count === 1 ? "agent" : "agents"}`;
1057
+ return "fanout: completed";
1052
1058
  }
1053
1059
  async function latestInstallVersion(record) {
1054
1060
  const handle = normalizedOwnerSkillHandle(record.handle);
@@ -1727,7 +1733,7 @@ async function pollDeviceToken(baseUrl, authorization, timeoutSeconds) {
1727
1733
  }
1728
1734
  throw new WorkbenchCodedError("login_pending", "Device login is still waiting for browser authorization.", {
1729
1735
  retryable: true,
1730
- remediation: "Authorize the device in the browser, then run workbench login --wait.",
1736
+ remediation: `Authorize the device in the browser, then run workbench login --wait --timeout ${LOGIN_WAIT_TIMEOUT_SECONDS}.`,
1731
1737
  subject: {
1732
1738
  retryAfterSeconds: Math.max(1, Math.ceil(intervalMs / 1000)),
1733
1739
  verificationUri: authorization.verification_uri,
@@ -1802,7 +1808,7 @@ async function apiRequest(apiPath, options = {}, baseUrlOverride) {
1802
1808
  : selectWorkbenchBaseUrl({ configBaseUrl: config.baseUrl });
1803
1809
  const token = await workbenchCloudToken({ baseUrl });
1804
1810
  const method = options.method ?? "GET";
1805
- const canRetry = method === "GET";
1811
+ const canRetry = isIdempotentApiRequestMethod(method);
1806
1812
  const requestBody = encodeJsonRequestBody(options.body);
1807
1813
  let lastError = null;
1808
1814
  for (let attempt = 1; attempt <= API_REQUEST_MAX_ATTEMPTS; attempt += 1) {
@@ -1977,6 +1983,9 @@ function isTransientFetchError(error) {
1977
1983
  function isTransientApiRequestError(error) {
1978
1984
  return error instanceof WorkbenchApiRequestError && (error.status === 429 || error.status >= 500);
1979
1985
  }
1986
+ function isIdempotentApiRequestMethod(method) {
1987
+ return method === "GET" || method === "PUT" || method === "DELETE";
1988
+ }
1980
1989
  function errorMessage(error) {
1981
1990
  return error instanceof Error ? error.message : String(error);
1982
1991
  }
@@ -2748,11 +2757,6 @@ function snapshotHasWorkflowCase(snapshot) {
2748
2757
  /^\.workbench\/cases\/[^/]+\/case\.ya?ml$/u.test(file.path)) ?? [];
2749
2758
  return caseFiles.some((file) => file.kind === "text" && !/\n\s*smoke:\s*true(?:\s|$)/u.test(`\n${file.content}`));
2750
2759
  }
2751
- function installHandleFromStatusRemote(remote) {
2752
- const publicationUrl = remote.publication.status === "published" ? remote.publication.installUrl : undefined;
2753
- const source = parseWorkbenchInstallSource(publicationUrl ?? remote.url);
2754
- return source ? `${source.owner}/${source.skill}` : publicationUrl ?? remote.url;
2755
- }
2756
2760
  async function statusWithCausalNext(status, auth, core, machine) {
2757
2761
  if (!status.project.initialized) {
2758
2762
  return {
@@ -2803,19 +2807,59 @@ async function statusWithCausalNext(status, auth, core, machine) {
2803
2807
  currentVersionId !== undefined &&
2804
2808
  remote.publication.versionId !== currentVersionId);
2805
2809
  if (canPublish && stalePublishedCloudRemote) {
2810
+ const publishedVersionId = stalePublishedCloudRemote.publication.versionId;
2811
+ if (snapshot && publishedVersionId && currentVersionId) {
2812
+ if (versionHasAncestor(snapshot, currentVersionId, publishedVersionId)) {
2813
+ return { ...status, next: "workbench publish" };
2814
+ }
2815
+ if (versionHasAncestor(snapshot, publishedVersionId, currentVersionId)) {
2816
+ return { ...status, next: `workbench switch ${displayRef(publishedVersionId)}` };
2817
+ }
2818
+ }
2806
2819
  return { ...status, next: "workbench publish" };
2807
2820
  }
2808
- const publishedCloudRemote = status.remotes.find((remote) => remote.kind === "workbench-cloud" &&
2809
- remote.publication.status === "published" &&
2810
- Boolean(remote.publication.installUrl));
2811
- if (publishedCloudRemote) {
2812
- return { ...status, next: `workbench install ${installHandleFromStatusRemote(publishedCloudRemote)}` };
2813
- }
2814
2821
  return {
2815
2822
  ...status,
2816
2823
  next: null,
2817
2824
  };
2818
2825
  }
2826
+ function versionHasAncestor(snapshot, versionId, ancestorId) {
2827
+ if (versionId === ancestorId) {
2828
+ return true;
2829
+ }
2830
+ const parentsByChild = new Map();
2831
+ for (const edge of snapshot.lineage) {
2832
+ const parents = parentsByChild.get(edge.childId) ?? [];
2833
+ parents.push(edge.parentId);
2834
+ parentsByChild.set(edge.childId, parents);
2835
+ }
2836
+ for (const version of snapshot.versions) {
2837
+ if (version.parentIds.length === 0) {
2838
+ continue;
2839
+ }
2840
+ const parents = parentsByChild.get(version.id) ?? [];
2841
+ for (const parentId of version.parentIds) {
2842
+ if (!parents.includes(parentId)) {
2843
+ parents.push(parentId);
2844
+ }
2845
+ }
2846
+ parentsByChild.set(version.id, parents);
2847
+ }
2848
+ const seen = new Set();
2849
+ const stack = [...(parentsByChild.get(versionId) ?? [])];
2850
+ while (stack.length > 0) {
2851
+ const next = stack.pop();
2852
+ if (next === ancestorId) {
2853
+ return true;
2854
+ }
2855
+ if (seen.has(next)) {
2856
+ continue;
2857
+ }
2858
+ seen.add(next);
2859
+ stack.push(...(parentsByChild.get(next) ?? []));
2860
+ }
2861
+ return false;
2862
+ }
2819
2863
  function displayRef(id) {
2820
2864
  const version = /^v_([0-9a-f]{8,})$/iu.exec(id);
2821
2865
  if (version?.[1]) {
@@ -3206,7 +3250,21 @@ async function evalSuccessNextCommand(core, runs) {
3206
3250
  return "edit .workbench/cases, then run workbench eval";
3207
3251
  }
3208
3252
  const snapshot = await createWorkbenchReadOnlyInspectionSnapshot(core);
3209
- return snapshotHasWorkflowCase(snapshot) ? "workbench publish" : "edit .workbench/cases, then run workbench eval";
3253
+ if (!snapshotHasWorkflowCase(snapshot)) {
3254
+ return "edit .workbench/cases, then run workbench eval";
3255
+ }
3256
+ const auth = await workbenchCliAuthStatus();
3257
+ if (auth.workbenchCloud.status !== "authenticated") {
3258
+ return "workbench login";
3259
+ }
3260
+ const status = await workbenchStatusSnapshot(core);
3261
+ return statusHasPublishedCurrentCloudSource(status) ? null : "workbench publish";
3262
+ }
3263
+ function statusHasPublishedCurrentCloudSource(status) {
3264
+ const currentVersionId = status.project.currentVersionId;
3265
+ return Boolean(currentVersionId && status.remotes.some((remote) => remote.kind === "workbench-cloud" &&
3266
+ remote.publication.status === "published" &&
3267
+ remote.publication.versionId === currentVersionId));
3210
3268
  }
3211
3269
  function formatStatusSnapshot(status) {
3212
3270
  const lines = [
@@ -3315,7 +3373,8 @@ function formatJob(job) {
3315
3373
  }
3316
3374
  function formatComparison(comparison) {
3317
3375
  const lines = ["version\tskill\tagent\tstatus\tscore\tcost\tlatency\trun"];
3318
- for (const cell of comparison.cells) {
3376
+ const evidenceCells = comparison.cells.filter((cell) => cell.runId || cell.status);
3377
+ for (const cell of evidenceCells) {
3319
3378
  lines.push([
3320
3379
  displayRef(cell.versionId),
3321
3380
  cell.skillName,
@@ -3327,7 +3386,73 @@ function formatComparison(comparison) {
3327
3386
  cell.runId ? displayRef(cell.runId) : "n/a",
3328
3387
  ].join("\t"));
3329
3388
  }
3330
- return lines.join("\n");
3389
+ return lines.length === 1 ? "No comparable runs." : lines.join("\n");
3390
+ }
3391
+ function formatDiff(entries) {
3392
+ if (entries.length === 0) {
3393
+ return "No diff.";
3394
+ }
3395
+ return entries.map(formatDiffEntry).join("\n");
3396
+ }
3397
+ function formatDiffEntry(entry) {
3398
+ const before = entry.before ?? "";
3399
+ const after = entry.after ?? "";
3400
+ if (entry.status === "modified" || entry.status === "added" || entry.status === "removed") {
3401
+ return [
3402
+ `diff --workbench ${entry.path}`,
3403
+ `--- ${entry.status === "added" ? "/dev/null" : `a/${entry.path}`}`,
3404
+ `+++ ${entry.status === "removed" ? "/dev/null" : `b/${entry.path}`}`,
3405
+ ...unifiedLineDiff(before, after),
3406
+ ].join("\n");
3407
+ }
3408
+ return `${entry.status}\t${entry.path}`;
3409
+ }
3410
+ function unifiedLineDiff(before, after) {
3411
+ const beforeLines = splitDiffLines(before);
3412
+ const afterLines = splitDiffLines(after);
3413
+ const table = longestCommonSubsequenceTable(beforeLines, afterLines);
3414
+ const lines = [];
3415
+ let left = 0;
3416
+ let right = 0;
3417
+ while (left < beforeLines.length && right < afterLines.length) {
3418
+ if (beforeLines[left] === afterLines[right]) {
3419
+ lines.push(` ${beforeLines[left]}`);
3420
+ left += 1;
3421
+ right += 1;
3422
+ }
3423
+ else if (table[left + 1][right] >= table[left][right + 1]) {
3424
+ lines.push(`-${beforeLines[left]}`);
3425
+ left += 1;
3426
+ }
3427
+ else {
3428
+ lines.push(`+${afterLines[right]}`);
3429
+ right += 1;
3430
+ }
3431
+ }
3432
+ while (left < beforeLines.length) {
3433
+ lines.push(`-${beforeLines[left]}`);
3434
+ left += 1;
3435
+ }
3436
+ while (right < afterLines.length) {
3437
+ lines.push(`+${afterLines[right]}`);
3438
+ right += 1;
3439
+ }
3440
+ return lines.length > 0 ? lines : [" "];
3441
+ }
3442
+ function splitDiffLines(value) {
3443
+ const withoutFinalNewline = value.endsWith("\n") ? value.slice(0, -1) : value;
3444
+ return withoutFinalNewline ? withoutFinalNewline.split(/\r?\n/u) : [];
3445
+ }
3446
+ function longestCommonSubsequenceTable(left, right) {
3447
+ const table = Array.from({ length: left.length + 1 }, () => Array.from({ length: right.length + 1 }, () => 0));
3448
+ for (let i = left.length - 1; i >= 0; i -= 1) {
3449
+ for (let j = right.length - 1; j >= 0; j -= 1) {
3450
+ table[i][j] = left[i] === right[j]
3451
+ ? table[i + 1][j + 1] + 1
3452
+ : Math.max(table[i + 1][j], table[i][j + 1]);
3453
+ }
3454
+ }
3455
+ return table;
3331
3456
  }
3332
3457
  function shortObjectId(id) {
3333
3458
  return id.length > 8 ? id.slice(0, 8) : id;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workbench-ai/workbench",
3
- "version": "0.0.77",
3
+ "version": "0.0.79",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/workbench-ai/workbench.git",
@@ -22,10 +22,10 @@
22
22
  "dependencies": {
23
23
  "skills": "1.5.11",
24
24
  "yaml": "^2.8.2",
25
- "@workbench-ai/workbench-built-in-adapters": "0.0.77",
26
- "@workbench-ai/workbench-core": "0.0.77",
27
- "@workbench-ai/workbench-contract": "0.0.77",
28
- "@workbench-ai/workbench-protocol": "0.0.77"
25
+ "@workbench-ai/workbench-built-in-adapters": "0.0.79",
26
+ "@workbench-ai/workbench-core": "0.0.79",
27
+ "@workbench-ai/workbench-contract": "0.0.79",
28
+ "@workbench-ai/workbench-protocol": "0.0.79"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@tailwindcss/postcss": "^4.2.2",
@@ -36,7 +36,7 @@
36
36
  "react-dom": "^19.2.0",
37
37
  "typescript": "^5.9.2",
38
38
  "vitest": "^3.2.4",
39
- "@workbench-ai/workbench-ui": "0.0.77"
39
+ "@workbench-ai/workbench-ui": "0.0.79"
40
40
  },
41
41
  "scripts": {
42
42
  "build": "rm -rf dist && tsc -p tsconfig.json && chmod 755 dist/workbench.js && node ./scripts/build-dev-open-assets.mjs",