@workbench-ai/workbench 0.0.79 → 0.0.80

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":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAkEA,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
@@ -4,7 +4,7 @@ import { createRequire } from "node:module";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
6
  import { gzipSync } from "node:zlib";
7
- import { addWorkbenchRemote, addWorkbenchAgent, compareWorkbench, createWorkbenchInspectionSnapshot, createWorkbenchAdapterAuthBundle, createWorkbenchReadOnlyInspectionSnapshot, diffWorkbenchVersions, evalWorkbenchSkill, improveWorkbenchSkill, initWorkbenchSkill, listWorkbenchAgents, listWorkbenchVersions, localWorkbenchAdapterAuthStore, parseWorkbenchAdapterAuthTarget, prepareWorkbenchCloudEvalRequest, prepareWorkbenchCloudImproveRequest, publishWorkbenchVersion, removeWorkbenchAgent, showWorkbenchRef, switchWorkbenchVersion, syncWorkbenchRemote, workbenchJobEvidenceForSnapshot, workbenchStatusSnapshot, WorkbenchCodedError, WorkbenchUserError, } from "@workbench-ai/workbench-core";
7
+ import { addWorkbenchRemote, addWorkbenchAgent, compareWorkbench, createWorkbenchVersionRuntimeSnapshot, createWorkbenchInspectionSnapshot, createWorkbenchAdapterAuthBundle, createWorkbenchReadOnlyInspectionSnapshot, diffWorkbenchVersions, evalWorkbenchSkill, improveWorkbenchSkill, initWorkbenchSkill, listWorkbenchAgents, listWorkbenchVersions, localWorkbenchAdapterAuthStore, parseWorkbenchAdapterAuthTarget, prepareWorkbenchCloudEvalRequest, prepareWorkbenchCloudImproveRequest, publishWorkbenchVersion, removeWorkbenchAgent, showWorkbenchRef, switchWorkbenchVersion, syncWorkbenchRemote, workbenchJobEvidenceForSnapshot, workbenchStatusSnapshot, WorkbenchCodedError, WorkbenchUserError, } from "@workbench-ai/workbench-core";
8
8
  import { normalizeWorkbenchSkillName } from "@workbench-ai/workbench-contract";
9
9
  import { emitError, emitResult } from "./output.js";
10
10
  import { fanOutSkill, manualFanOutCommand } from "./fanout.js";
@@ -582,13 +582,15 @@ async function handleShow(parsed, io) {
582
582
  if (artifact) {
583
583
  return output(fileListing("artifact", artifact.id, artifact.files), parsed, io, () => formatFileListing("artifact", artifact.id, artifact.files));
584
584
  }
585
- const details = evidenceDetailsForRunOrJob(snapshot, objectRef);
586
- const evidenceFiles = evidenceFilesForRunOrJob(snapshot, objectRef);
587
- if (details.length > 0 || evidenceFiles.length > 0) {
585
+ const selection = runOrJobEvidenceSelection(snapshot, objectRef);
586
+ const details = evidenceDetailsForSelection(snapshot, selection);
587
+ const evidenceFiles = evidenceFilesForSelection(snapshot, selection);
588
+ if (selection.run || selection.jobs.length > 0 || details.length > 0 || evidenceFiles.length > 0) {
588
589
  return output({
590
+ jobs: selection.jobs.map(jobEvidenceSummary),
589
591
  details: details.map(evidenceDetailSummary),
590
592
  files: evidenceFiles.map(fileSummary),
591
- }, parsed, io, () => formatRunOrJobEvidence(details, evidenceFiles));
593
+ }, parsed, io, () => formatRunOrJobEvidence(selection.jobs, details, evidenceFiles));
592
594
  }
593
595
  const value = await showWorkbenchRef(ref, core);
594
596
  return output(value, parsed, io, () => formatShow(value));
@@ -744,7 +746,8 @@ async function handleLogin(parsed, io) {
744
746
  exitCode: 2,
745
747
  });
746
748
  }
747
- const startOnly = parsed.flags["start-only"] === true;
749
+ const startOnly = parsed.flags["start-only"] === true ||
750
+ (parsed.flags["no-open"] === true && parsed.flags.wait !== true && parsed.flags.timeout === undefined);
748
751
  const waitOnly = parsed.flags.wait === true;
749
752
  const timeoutSeconds = intFlag(parsed, "timeout");
750
753
  if (startOnly && timeoutSeconds !== undefined) {
@@ -1019,6 +1022,9 @@ function installNextCommand(fanout) {
1019
1022
  }
1020
1023
  function formatInstallOutcome(result, dryRun) {
1021
1024
  if (dryRun) {
1025
+ if (result.result === "unchanged") {
1026
+ return `Already installed ${result.directoryName} at ${result.destination} (unchanged; dry run made no changes).`;
1027
+ }
1022
1028
  return `Would install ${result.directoryName} to ${result.destination} (${formatFileCount(result.filesCopied)}).`;
1023
1029
  }
1024
1030
  if (result.result === "unchanged") {
@@ -1082,7 +1088,7 @@ function withTimeout(promise, timeoutMs) {
1082
1088
  }
1083
1089
  async function startCloudExecution(command, parsed, io) {
1084
1090
  const root = dirFlag(parsed) ?? process.cwd();
1085
- const showProgress = parsed.flags.json !== true;
1091
+ const showProgress = true;
1086
1092
  const interrupt = createCloudInterruptController(command, io, showProgress);
1087
1093
  try {
1088
1094
  writeCloudProgress(io, `workbench cloud: preparing hosted ${command}.`, showProgress);
@@ -1118,6 +1124,14 @@ async function startCloudExecution(command, parsed, io) {
1118
1124
  samples: intFlag(parsed, "samples"),
1119
1125
  budget: intFlag(parsed, "budget"),
1120
1126
  }));
1127
+ writeCloudProgress(io, "workbench cloud: checking provider auth.", showProgress);
1128
+ await cloudPreScheduleStep(command, interrupt, preflightCloudAdapterAuth({
1129
+ root,
1130
+ versionId: request.versionId,
1131
+ parsed,
1132
+ baseUrl: source.baseUrl,
1133
+ authToken: token,
1134
+ }));
1121
1135
  writeCloudProgress(io, "workbench cloud: syncing source to cloud.", showProgress);
1122
1136
  const syncBefore = await cloudPreScheduleStep(command, interrupt, syncWorkbenchRemote({ ...core, remote: remote.name }));
1123
1137
  writeCloudProgress(io, `workbench cloud: scheduling hosted ${command}.`, showProgress);
@@ -1216,6 +1230,79 @@ function cloudCanceledBeforeRunIdError(command) {
1216
1230
  exitCode: 130,
1217
1231
  });
1218
1232
  }
1233
+ async function preflightCloudAdapterAuth(input) {
1234
+ const snapshot = await createWorkbenchReadOnlyInspectionSnapshot({ dir: input.root, authToken: input.authToken });
1235
+ const version = snapshotVersionByRef(snapshot, input.versionId);
1236
+ if (!version) {
1237
+ throw new WorkbenchCodedError("version_not_found", `Version not found: ${input.versionId}`, {
1238
+ remediation: "Run workbench status.",
1239
+ subject: { versionId: input.versionId },
1240
+ exitCode: 1,
1241
+ });
1242
+ }
1243
+ const runtime = await createWorkbenchVersionRuntimeSnapshot(version, {
1244
+ skill: stringFlag(input.parsed, "skills"),
1245
+ agent: stringFlag(input.parsed, "agents"),
1246
+ authToken: input.authToken,
1247
+ });
1248
+ const targets = uniqueAdapterAuthTargets(runtime.selectedAgents.flatMap(cloudAdapterAuthTargetsForAgent));
1249
+ if (targets.length === 0) {
1250
+ return;
1251
+ }
1252
+ const statuses = await fetchCloudAdapterAuthStatuses(input.baseUrl);
1253
+ const missing = targets.find((target) => !statuses.some((status) => adapterAuthStatusMatchesTarget(status, target)));
1254
+ if (!missing) {
1255
+ return;
1256
+ }
1257
+ throw new WorkbenchCodedError("adapter_auth_required", `${formatCloudAdapterAuthTarget(missing)} disconnected. Run workbench login ${missing.adapterId}.`, {
1258
+ remediation: `Run workbench login ${missing.adapterId}.`,
1259
+ subject: {
1260
+ adapterId: missing.adapterId,
1261
+ profile: missing.profile,
1262
+ ...(missing.slot ? { slot: missing.slot } : {}),
1263
+ },
1264
+ exitCode: 1,
1265
+ });
1266
+ }
1267
+ function cloudAdapterAuthTargetsForAgent(agent) {
1268
+ const adapterId = agent.adapter.trim().toLowerCase();
1269
+ if (adapterId !== "codex" && adapterId !== "claude") {
1270
+ return [];
1271
+ }
1272
+ const auth = agent.config.auth;
1273
+ if (typeof auth === "string" && auth.trim()) {
1274
+ return [{ adapterId, profile: auth.trim() }];
1275
+ }
1276
+ if (auth && typeof auth === "object" && !Array.isArray(auth)) {
1277
+ return Object.entries(auth)
1278
+ .filter((entry) => typeof entry[1] === "string" && entry[1].trim().length > 0)
1279
+ .map(([slot, profile]) => ({ adapterId, slot, profile: profile.trim() }));
1280
+ }
1281
+ return [{ adapterId, profile: "default" }];
1282
+ }
1283
+ function uniqueAdapterAuthTargets(targets) {
1284
+ const byKey = new Map();
1285
+ for (const target of targets) {
1286
+ byKey.set(adapterAuthTargetKey(target), target);
1287
+ }
1288
+ return [...byKey.values()].sort((left, right) => adapterAuthTargetKey(left).localeCompare(adapterAuthTargetKey(right)));
1289
+ }
1290
+ async function fetchCloudAdapterAuthStatuses(baseUrl) {
1291
+ const response = await apiRequest("/api/workbench/auth/adapters", {}, baseUrl);
1292
+ return response.adapters ?? [];
1293
+ }
1294
+ function adapterAuthStatusMatchesTarget(status, target) {
1295
+ return status.status === "connected" &&
1296
+ status.adapterId === target.adapterId &&
1297
+ status.profile === target.profile &&
1298
+ (status.slot ?? undefined) === (target.slot ?? undefined);
1299
+ }
1300
+ function adapterAuthTargetKey(target) {
1301
+ return `${target.adapterId}/${target.slot ?? "_"}/${target.profile}`;
1302
+ }
1303
+ function formatCloudAdapterAuthTarget(target) {
1304
+ return `${target.adapterId}${target.slot ? `/${target.slot}` : ""}`;
1305
+ }
1219
1306
  async function waitForCloudRuns(input) {
1220
1307
  const runIds = input.runs
1221
1308
  .map((run) => run.id)
@@ -2876,6 +2963,16 @@ function displayRef(id) {
2876
2963
  function shortenCommandRefs(command) {
2877
2964
  return command.replace(/\b(?:v_[0-9a-f]{8,}|(?:run|job|trace|artifact)_[a-z0-9_-]+)/giu, (match) => displayRef(match));
2878
2965
  }
2966
+ function displayCandidateRefs(ids) {
2967
+ const uniqueIds = [...ids];
2968
+ for (let length = 8; length <= 32; length += 1) {
2969
+ const refs = uniqueIds.map((id) => id.length > length ? id.slice(0, length) : id);
2970
+ if (new Set(refs).size === refs.length) {
2971
+ return refs;
2972
+ }
2973
+ }
2974
+ return uniqueIds;
2975
+ }
2879
2976
  function snapshotVersionByRef(snapshot, ref) {
2880
2977
  const requested = ref.trim();
2881
2978
  const normalized = requested === "current" ? snapshot.refs.current ?? "" : requested;
@@ -2884,7 +2981,7 @@ function snapshotVersionByRef(snapshot, ref) {
2884
2981
  }
2885
2982
  const candidates = snapshot.versions.filter((version) => snapshotVersionRefMatches(version, normalized));
2886
2983
  if (candidates.length > 1) {
2887
- throw new WorkbenchCodedError("ref_ambiguous", `Version ref is ambiguous: ${ref}. Candidates: ${candidates.map((version) => displayRef(version.id)).join(", ")}.`, {
2984
+ throw new WorkbenchCodedError("ref_ambiguous", `Version ref is ambiguous: ${ref}. Candidates: ${displayCandidateRefs(candidates.map((version) => version.id)).join(", ")}.`, {
2888
2985
  subject: { ref, candidates: candidates.map((version) => version.id) },
2889
2986
  exitCode: 2,
2890
2987
  });
@@ -2907,7 +3004,7 @@ function snapshotObjectByRef(entries, ref, kind) {
2907
3004
  }
2908
3005
  const candidates = entries.filter((entry) => objectRefMatches(entry.id, normalized));
2909
3006
  if (candidates.length > 1) {
2910
- throw new WorkbenchCodedError("ref_ambiguous", `${capitalize(kind)} ref is ambiguous: ${ref}. Candidates: ${candidates.map((entry) => displayRef(entry.id)).slice(0, 8).join(", ")}.`, {
3007
+ throw new WorkbenchCodedError("ref_ambiguous", `${capitalize(kind)} ref is ambiguous: ${ref}. Candidates: ${displayCandidateRefs(candidates.map((entry) => entry.id)).slice(0, 8).join(", ")}.`, {
2911
3008
  subject: { ref, candidates: candidates.map((entry) => entry.id).slice(0, 20) },
2912
3009
  exitCode: 2,
2913
3010
  });
@@ -2928,7 +3025,7 @@ function runOrJobEvidenceSelection(snapshot, ref) {
2928
3025
  const run = snapshotObjectByRef(snapshot.runs, ref, "run");
2929
3026
  const job = snapshotObjectByRef(snapshot.jobs, ref, "job");
2930
3027
  if (run && job) {
2931
- throw new WorkbenchCodedError("ref_ambiguous", `Run/job ref is ambiguous: ${ref}. Candidates: ${displayRef(run.id)}, ${displayRef(job.id)}.`, {
3028
+ throw new WorkbenchCodedError("ref_ambiguous", `Run/job ref is ambiguous: ${ref}. Candidates: ${displayCandidateRefs([run.id, job.id]).join(", ")}.`, {
2932
3029
  subject: { ref, candidates: [run.id, job.id] },
2933
3030
  exitCode: 2,
2934
3031
  });
@@ -2943,6 +3040,9 @@ function runOrJobEvidenceSelection(snapshot, ref) {
2943
3040
  }
2944
3041
  function evidenceFilesForRunOrJob(snapshot, ref) {
2945
3042
  const selection = runOrJobEvidenceSelection(snapshot, ref);
3043
+ return evidenceFilesForSelection(snapshot, selection);
3044
+ }
3045
+ function evidenceFilesForSelection(snapshot, selection) {
2946
3046
  if (!selection.run && selection.jobs.length === 0) {
2947
3047
  return [];
2948
3048
  }
@@ -3024,10 +3124,32 @@ function isUserFacingTraceEvidenceFile(file) {
3024
3124
  function evidencePathSegment(value) {
3025
3125
  return value.replace(/[^A-Za-z0-9._-]+/gu, "-") || "_";
3026
3126
  }
3027
- function formatRunOrJobEvidence(details, files) {
3127
+ function formatRunOrJobEvidence(jobs, details, files) {
3128
+ const jobLines = jobs.length > 0 ? ["Jobs:", ...jobs.map(formatJobEvidenceSummary)] : [];
3028
3129
  const detailLines = details.map(formatTraceDetail).filter(Boolean);
3029
3130
  const fileLines = files.length > 0 ? ["Files:", ...files.map((file) => file.path)] : [];
3030
- return [...detailLines, ...fileLines].join("\n") || "No evidence.";
3131
+ return [...jobLines, ...detailLines, ...fileLines].join("\n") || "No evidence.";
3132
+ }
3133
+ function jobEvidenceSummary(job) {
3134
+ return {
3135
+ id: job.id,
3136
+ runId: job.runId,
3137
+ caseId: job.caseId,
3138
+ sample: job.sample,
3139
+ status: job.status,
3140
+ ...(job.score !== undefined ? { score: job.score } : {}),
3141
+ ...(job.error ? { error: job.error } : {}),
3142
+ };
3143
+ }
3144
+ function formatJobEvidenceSummary(job) {
3145
+ return [
3146
+ displayRef(job.id),
3147
+ `case=${job.caseId}`,
3148
+ `sample=${job.sample}`,
3149
+ job.status,
3150
+ job.score !== undefined ? `score=${job.score.toFixed(3)}` : undefined,
3151
+ job.error ? `error=${singleLine(job.error)}` : undefined,
3152
+ ].filter(Boolean).join("\t");
3031
3153
  }
3032
3154
  function evidenceDetailSummary(detail) {
3033
3155
  return {
@@ -3103,6 +3225,9 @@ async function fileForRunOrJobRef(core, objectRef, requestedPath) {
3103
3225
  }
3104
3226
  function evidenceDetailsForRunOrJob(snapshot, ref) {
3105
3227
  const selection = runOrJobEvidenceSelection(snapshot, ref);
3228
+ return evidenceDetailsForSelection(snapshot, selection);
3229
+ }
3230
+ function evidenceDetailsForSelection(snapshot, selection) {
3106
3231
  return selection.jobs.flatMap((entry) => {
3107
3232
  const detail = workbenchJobEvidenceForSnapshot(snapshot, {
3108
3233
  runId: entry.runId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workbench-ai/workbench",
3
- "version": "0.0.79",
3
+ "version": "0.0.80",
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.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"
25
+ "@workbench-ai/workbench-built-in-adapters": "0.0.80",
26
+ "@workbench-ai/workbench-protocol": "0.0.80",
27
+ "@workbench-ai/workbench-contract": "0.0.80",
28
+ "@workbench-ai/workbench-core": "0.0.80"
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.79"
39
+ "@workbench-ai/workbench-ui": "0.0.80"
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",