@workbench-ai/workbench 0.0.97 → 0.0.99

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/index.js CHANGED
@@ -1850,6 +1850,9 @@ async function requestDeviceAuthorization(baseUrl) {
1850
1850
  });
1851
1851
  }
1852
1852
  if (!response.ok) {
1853
+ if (isRetryableHttpStatus(response.status)) {
1854
+ throw deviceLoginUnavailableError("start", response.status, response.statusText, text);
1855
+ }
1853
1856
  const excerpt = readResponseError(text);
1854
1857
  throw new WorkbenchCodedError("login_denied", `Device login failed: ${response.status}${excerpt ? ` ${excerpt}` : response.statusText ? ` ${response.statusText}` : ""}`, {
1855
1858
  exitCode: 1,
@@ -1889,6 +1892,9 @@ async function pollDeviceToken(baseUrl, authorization, timeoutSeconds) {
1889
1892
  if (response.ok) {
1890
1893
  return JSON.parse(text);
1891
1894
  }
1895
+ if (isRetryableHttpStatus(response.status)) {
1896
+ throw deviceLoginUnavailableError("wait", response.status, response.statusText, text);
1897
+ }
1892
1898
  const error = readResponseError(text) ?? "authorization_pending";
1893
1899
  if (error === "slow_down") {
1894
1900
  intervalMs += 5000;
@@ -1913,6 +1919,18 @@ async function pollDeviceToken(baseUrl, authorization, timeoutSeconds) {
1913
1919
  exitCode: 1,
1914
1920
  });
1915
1921
  }
1922
+ function deviceLoginUnavailableError(phase, status, statusText, text) {
1923
+ const excerpt = readResponseError(text);
1924
+ const detail = `${status}${excerpt ? ` ${excerpt}` : statusText ? ` ${statusText}` : ""}`;
1925
+ const command = phase === "start"
1926
+ ? "workbench login --start-only --no-open"
1927
+ : `workbench login --wait --timeout ${LOGIN_WAIT_TIMEOUT_SECONDS}`;
1928
+ return new WorkbenchCodedError("service_unavailable", `Workbench Cloud login is temporarily unavailable: ${detail}`, {
1929
+ retryable: true,
1930
+ remediation: `Retry ${command}.`,
1931
+ exitCode: 1,
1932
+ });
1933
+ }
1916
1934
  async function fetchWorkbenchUsername(baseUrl, accessToken) {
1917
1935
  const response = await fetch(`${baseUrl}/api/workbench/profile`, {
1918
1936
  headers: { authorization: `Bearer ${accessToken}` },
@@ -2150,7 +2168,10 @@ function isTransientFetchError(error) {
2150
2168
  return /(?:fetch failed|socket hang up|ECONNRESET|EPIPE|UND_ERR_SOCKET|terminated)/iu.test(errorMessage(error));
2151
2169
  }
2152
2170
  function isTransientApiRequestError(error) {
2153
- return error instanceof WorkbenchApiRequestError && (error.status === 429 || error.status >= 500);
2171
+ return error instanceof WorkbenchApiRequestError && isRetryableHttpStatus(error.status);
2172
+ }
2173
+ function isRetryableHttpStatus(status) {
2174
+ return status === 429 || status >= 500;
2154
2175
  }
2155
2176
  function isIdempotentApiRequestMethod(method) {
2156
2177
  return method === "GET" || method === "PUT" || method === "DELETE";
@@ -2246,21 +2267,10 @@ async function collectAdapterAuthBundle(args) {
2246
2267
  });
2247
2268
  }
2248
2269
  if (args.method === "oauth") {
2249
- const profile = await requiredAuthFile(args.profileRoot, ".claude.json", {
2250
- provider: "Claude auth",
2251
- remediation: "claude setup-token && workbench login claude --method oauth",
2252
- });
2253
- const portableToken = await requiredAnyAuthFile(args.profileRoot, [
2254
- ".claude/oauth-token",
2255
- ".claude/.credentials.json",
2256
- ], {
2257
- provider: "Claude portable OAuth token",
2258
- remediation: "claude setup-token && workbench login claude --method oauth",
2259
- });
2260
2270
  return createWorkbenchAdapterAuthBundle({
2261
2271
  target: args.target,
2262
2272
  method: args.method,
2263
- files: [profile, portableToken],
2273
+ files: await collectClaudeOAuthFiles(args.profileRoot),
2264
2274
  });
2265
2275
  }
2266
2276
  if (args.method === "bedrock") {
@@ -2309,15 +2319,25 @@ async function requiredAuthFile(root, relativePath, guidance) {
2309
2319
  }
2310
2320
  return file;
2311
2321
  }
2312
- async function requiredAnyAuthFile(root, paths, guidance) {
2313
- const files = await optionalAuthFiles(root, paths);
2314
- const [file] = files;
2315
- if (file) {
2316
- return file;
2317
- }
2318
- throw new WorkbenchCodedError("provider_oauth_missing", `Missing ${guidance.provider} file: ${paths.map((entry) => path.join(root, entry)).join(" or ")}`, {
2319
- remediation: guidance.remediation,
2320
- subject: { paths: paths.map((entry) => path.join(root, entry)), relativePaths: [...paths] },
2322
+ const CLAUDE_OAUTH_PROFILE_PATH = ".claude.json";
2323
+ const CLAUDE_OAUTH_TOKEN_PATHS = [".claude/oauth-token", ".claude/.credentials.json"];
2324
+ const CLAUDE_OAUTH_REMEDIATION = "claude setup-token && workbench login claude --method oauth";
2325
+ async function collectClaudeOAuthFiles(root) {
2326
+ const [profile] = await optionalAuthFiles(root, [CLAUDE_OAUTH_PROFILE_PATH]);
2327
+ const [portableToken] = await optionalAuthFiles(root, CLAUDE_OAUTH_TOKEN_PATHS);
2328
+ if (profile && portableToken) {
2329
+ return [profile, portableToken];
2330
+ }
2331
+ const missingRelativePaths = [
2332
+ ...(!profile ? [CLAUDE_OAUTH_PROFILE_PATH] : []),
2333
+ ...(!portableToken ? [...CLAUDE_OAUTH_TOKEN_PATHS] : []),
2334
+ ];
2335
+ throw new WorkbenchCodedError("provider_oauth_missing", "Claude OAuth capture requires Claude Code's profile and portable OAuth token.", {
2336
+ remediation: CLAUDE_OAUTH_REMEDIATION,
2337
+ subject: {
2338
+ paths: missingRelativePaths.map((entry) => path.join(root, entry)),
2339
+ relativePaths: missingRelativePaths,
2340
+ },
2321
2341
  exitCode: 2,
2322
2342
  });
2323
2343
  }
@@ -3026,6 +3046,7 @@ async function statusWithCausalNext(status, auth, core, machine) {
3026
3046
  };
3027
3047
  }
3028
3048
  const snapshot = await createWorkbenchReadOnlyInspectionSnapshot(core).catch(() => null);
3049
+ const currentVersionId = status.project.currentVersionId ?? snapshot?.status.currentVersionId ?? snapshot?.refs.current;
3029
3050
  const lastRun = snapshot?.runs
3030
3051
  .slice()
3031
3052
  .sort((left, right) => right.createdAt.localeCompare(left.createdAt))[0];
@@ -3034,8 +3055,11 @@ async function statusWithCausalNext(status, auth, core, machine) {
3034
3055
  }
3035
3056
  const failedRemote = status.remotes.find((remote) => remote.sync.status === "error");
3036
3057
  const hasWorkflowCase = snapshot ? snapshotHasWorkflowCase(snapshot) : false;
3037
- const hasScoredRun = snapshot?.runs.some((run) => scoredRunValue(run) !== undefined) ?? false;
3038
- const canPublish = hasWorkflowCase && hasScoredRun;
3058
+ const hasCurrentScoredProofRun = snapshot?.runs.some((run) => currentVersionId !== undefined &&
3059
+ scoredRunValue(run) !== undefined &&
3060
+ ((run.kind === "eval" && run.versionId === currentVersionId) ||
3061
+ (run.kind === "improve" && run.outputVersionId === currentVersionId))) ?? false;
3062
+ const canPublish = hasWorkflowCase && hasCurrentScoredProofRun;
3039
3063
  const cloudAuthMissing = auth.workbenchCloud.status !== "authenticated";
3040
3064
  const cloudRemoteNeedsAuth = status.remotes.some((remote) => remote.kind === "workbench-cloud" &&
3041
3065
  (remote.sync.status !== "up_to_date" || remote.publication.status === "unpublished"));
@@ -3051,6 +3075,9 @@ async function statusWithCausalNext(status, auth, core, machine) {
3051
3075
  if (!hasWorkflowCase) {
3052
3076
  return { ...status, next: "edit .workbench/cases, then run workbench eval" };
3053
3077
  }
3078
+ if (!hasCurrentScoredProofRun) {
3079
+ return { ...status, next: "workbench eval" };
3080
+ }
3054
3081
  const cloudRemote = status.remotes.find((remote) => remote.kind === "workbench-cloud");
3055
3082
  if (canPublish && !cloudRemote) {
3056
3083
  return { ...status, next: "workbench publish" };
@@ -3061,7 +3088,6 @@ async function statusWithCausalNext(status, auth, core, machine) {
3061
3088
  if (unpublishedCloudRemote) {
3062
3089
  return { ...status, next: "workbench publish" };
3063
3090
  }
3064
- const currentVersionId = status.project.currentVersionId ?? snapshot?.status.currentVersionId ?? snapshot?.refs.current;
3065
3091
  const stalePublishedCloudRemote = status.remotes.find((remote) => remote.kind === "workbench-cloud" &&
3066
3092
  remote.publication.status === "published" &&
3067
3093
  remote.sync.status === "up_to_date" &&
@@ -45,7 +45,7 @@ export async function installSnapshotToStore(options) {
45
45
  const previous = existingHash
46
46
  ? existingHash === contentHash ? "unchanged" : canUpdateExisting ? "updated" : "overwritten"
47
47
  : "none";
48
- if (existingHash && previous === "overwritten" && !options.overwrite) {
48
+ if (!options.dryRun && existingHash && previous === "overwritten" && !options.overwrite) {
49
49
  throw new WorkbenchCodedError("install_failed", `Canonical skill already exists: ${destination}`, {
50
50
  remediation: "Pass --yes to overwrite the existing canonical store skill.",
51
51
  subject: { destination },
@@ -103,7 +103,7 @@ export async function readInstalledSkillsInventory(options = {}) {
103
103
  return {
104
104
  stores,
105
105
  skills,
106
- next: next ? `workbench install ${next}` : skills.length === 0 ? "workbench install OWNER/SKILL" : null,
106
+ next: next ? `workbench install ${next}` : null,
107
107
  };
108
108
  }
109
109
  export function installedInventoryToJson(inventory) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workbench-ai/workbench",
3
- "version": "0.0.97",
3
+ "version": "0.0.99",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/workbench-ai/workbench.git",
@@ -21,10 +21,10 @@
21
21
  ],
22
22
  "dependencies": {
23
23
  "yaml": "^2.8.2",
24
- "@workbench-ai/workbench-core": "0.0.97",
25
- "@workbench-ai/workbench-protocol": "0.0.97",
26
- "@workbench-ai/workbench-built-in-adapters": "0.0.97",
27
- "@workbench-ai/workbench-contract": "0.0.97"
24
+ "@workbench-ai/workbench-built-in-adapters": "0.0.99",
25
+ "@workbench-ai/workbench-protocol": "0.0.99",
26
+ "@workbench-ai/workbench-core": "0.0.99",
27
+ "@workbench-ai/workbench-contract": "0.0.99"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@tailwindcss/postcss": "^4.2.2",
@@ -35,7 +35,7 @@
35
35
  "react-dom": "^19.2.0",
36
36
  "typescript": "^5.9.2",
37
37
  "vitest": "^3.2.4",
38
- "@workbench-ai/workbench-ui": "0.0.97"
38
+ "@workbench-ai/workbench-ui": "0.0.99"
39
39
  },
40
40
  "scripts": {
41
41
  "build": "rm -rf dist && tsc -p tsconfig.json && chmod 755 dist/workbench.js && node ./scripts/build-dev-open-assets.mjs",