@workbench-ai/workbench 0.0.83 → 0.0.85

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
@@ -908,7 +908,7 @@ async function handleInstall(parsed, io) {
908
908
  },
909
909
  });
910
910
  const fanout = parsed.flags["dry-run"] === true
911
- ? skippedFanOut(result.directoryName, result.destination)
911
+ ? skippedFanOut(result.directoryName, result.destination, result.result === "unchanged" ? "unchanged" : "dry-run")
912
912
  : await fanOutSkill(result.directoryName, { skillDir: result.destination });
913
913
  const next = installNextCommand(fanout);
914
914
  return emitResult("workbench.cli.install.v1", {
@@ -1008,16 +1008,16 @@ async function handleCloudImprove(parsed, io) {
1008
1008
  ...(next ? [`next: ${next}`] : []),
1009
1009
  ].filter(Boolean).join("\n"));
1010
1010
  }
1011
- function skippedFanOut(name, destination) {
1011
+ function skippedFanOut(name, destination, reason) {
1012
1012
  return {
1013
1013
  status: "skipped",
1014
1014
  command: manualFanOutCommand(destination, name),
1015
1015
  linkedAgents: [],
1016
- reason: "dry-run",
1016
+ reason,
1017
1017
  };
1018
1018
  }
1019
1019
  function installNextCommand(fanout) {
1020
- return fanout.status === "failed" || (fanout.status === "skipped" && fanout.reason !== "dry-run")
1020
+ return fanout.status === "failed" || (fanout.status === "skipped" && fanout.reason !== "dry-run" && fanout.reason !== "unchanged")
1021
1021
  ? fanout.command
1022
1022
  : null;
1023
1023
  }
@@ -1026,11 +1026,20 @@ function formatInstallOutcome(result, dryRun) {
1026
1026
  if (result.result === "unchanged") {
1027
1027
  return `Already installed ${result.directoryName} at ${result.destination} (unchanged; dry run made no changes).`;
1028
1028
  }
1029
+ if (result.previous === "updated") {
1030
+ return `Would update ${result.directoryName} at ${result.destination} (${formatFileCount(result.filesCopied)}).`;
1031
+ }
1032
+ if (result.previous === "overwritten") {
1033
+ return `Would overwrite ${result.directoryName} at ${result.destination} (${formatFileCount(result.filesCopied)}).`;
1034
+ }
1029
1035
  return `Would install ${result.directoryName} to ${result.destination} (${formatFileCount(result.filesCopied)}).`;
1030
1036
  }
1031
1037
  if (result.result === "unchanged") {
1032
1038
  return `Already installed ${result.directoryName} at ${result.destination} (unchanged).`;
1033
1039
  }
1040
+ if (result.previous === "updated") {
1041
+ return `Updated ${result.directoryName} at ${result.destination} (${formatFileCount(result.filesCopied)}).`;
1042
+ }
1034
1043
  const detail = result.previous === "overwritten"
1035
1044
  ? `overwrote existing copy, ${formatFileCount(result.filesCopied)}`
1036
1045
  : formatFileCount(result.filesCopied);
@@ -1051,9 +1060,13 @@ function fanOutToJson(fanout) {
1051
1060
  }
1052
1061
  function formatFanOut(fanout) {
1053
1062
  if (fanout.status === "skipped") {
1054
- return fanout.reason === "dry-run"
1055
- ? "fanout: planned"
1056
- : `fanout skipped: ${fanout.reason ?? "not available"}`;
1063
+ if (fanout.reason === "dry-run") {
1064
+ return "fanout: planned";
1065
+ }
1066
+ if (fanout.reason === "unchanged") {
1067
+ return "fanout: unchanged";
1068
+ }
1069
+ return `fanout skipped: ${fanout.reason ?? "not available"}`;
1057
1070
  }
1058
1071
  if (fanout.status === "failed") {
1059
1072
  return `fanout failed: ${fanout.reason ?? "unknown failure"}`;
@@ -2990,17 +3003,30 @@ function versionHasAncestor(snapshot, versionId, ancestorId) {
2990
3003
  return false;
2991
3004
  }
2992
3005
  function displayRef(id) {
3006
+ return displayRefWithMinLength(id, 8);
3007
+ }
3008
+ function displayRefWithMinLength(id, minLength) {
2993
3009
  const version = /^v_([0-9a-f]{8,})$/iu.exec(id);
2994
3010
  if (version?.[1]) {
2995
- return version[1].slice(0, 8);
3011
+ return version[1].slice(0, minLength);
2996
3012
  }
2997
3013
  const separator = id.indexOf("_");
2998
3014
  if (separator > 0 && separator < id.length - 1) {
2999
3015
  const prefix = id.slice(0, separator);
3000
3016
  const suffix = id.slice(separator + 1);
3001
- return `${prefix}_${suffix.slice(0, 8)}`;
3017
+ return `${prefix}_${suffix.slice(0, minLength)}`;
3002
3018
  }
3003
- return id.length > 8 ? id.slice(0, 8) : id;
3019
+ return id.length > minLength ? id.slice(0, minLength) : id;
3020
+ }
3021
+ function displayRefsForIds(ids) {
3022
+ const uniqueIds = [...new Set(ids)];
3023
+ for (let length = 8; length <= 32; length += 1) {
3024
+ const refs = uniqueIds.map((id) => displayRefWithMinLength(id, length));
3025
+ if (new Set(refs).size === refs.length) {
3026
+ return new Map(uniqueIds.map((id, index) => [id, refs[index]]));
3027
+ }
3028
+ }
3029
+ return new Map(uniqueIds.map((id) => [id, id]));
3004
3030
  }
3005
3031
  function shortenCommandRefs(command) {
3006
3032
  return command.replace(/\b(?:v_[0-9a-f]{8,}|(?:run|job|trace|artifact)_[a-z0-9_-]+)/giu, (match) => displayRef(match));
@@ -3167,8 +3193,16 @@ function evidencePathSegment(value) {
3167
3193
  return value.replace(/[^A-Za-z0-9._-]+/gu, "-") || "_";
3168
3194
  }
3169
3195
  function formatRunOrJobEvidence(jobs, details, files) {
3170
- const jobLines = jobs.length > 0 ? ["Jobs:", ...jobs.map(formatJobEvidenceSummary)] : [];
3171
- const detailLines = details.map(formatTraceDetail).filter(Boolean);
3196
+ const jobRefs = displayRefsForIds([
3197
+ ...jobs.map((job) => job.id),
3198
+ ...details.flatMap((detail) => detail.executions.flatMap((execution) => execution.jobIds)),
3199
+ ]);
3200
+ const runRefs = displayRefsForIds([
3201
+ ...jobs.map((job) => job.runId),
3202
+ ...details.map((detail) => detail.runId),
3203
+ ]);
3204
+ const jobLines = jobs.length > 0 ? ["Jobs:", ...jobs.map((job) => formatJobEvidenceSummary(job, jobRefs))] : [];
3205
+ const detailLines = details.map((detail) => formatTraceDetail(detail, { jobRefs, runRefs })).filter(Boolean);
3172
3206
  const fileLines = files.length > 0 ? ["Files:", ...files.map((file) => file.path)] : [];
3173
3207
  return [...jobLines, ...detailLines, ...fileLines].join("\n") || "No evidence.";
3174
3208
  }
@@ -3183,9 +3217,9 @@ function jobEvidenceSummary(job) {
3183
3217
  ...(job.error ? { error: job.error } : {}),
3184
3218
  };
3185
3219
  }
3186
- function formatJobEvidenceSummary(job) {
3220
+ function formatJobEvidenceSummary(job, refs = new Map()) {
3187
3221
  return [
3188
- displayRef(job.id),
3222
+ refs.get(job.id) ?? displayRef(job.id),
3189
3223
  `case=${job.caseId}`,
3190
3224
  `sample=${job.sample}`,
3191
3225
  job.status,
@@ -3304,17 +3338,9 @@ function findShowFile(files, requestedPath, objectRef) {
3304
3338
  if (candidates.length === 1) {
3305
3339
  return candidates[0];
3306
3340
  }
3307
- const equivalentCandidate = singleEquivalentShowFile(candidates);
3308
- if (equivalentCandidate) {
3309
- return equivalentCandidate;
3310
- }
3311
3341
  if (candidates.length === 0 && suffixCandidates.length === 1) {
3312
3342
  return suffixCandidates[0];
3313
3343
  }
3314
- const equivalentSuffixCandidate = singleEquivalentShowFile(suffixCandidates);
3315
- if (equivalentSuffixCandidate) {
3316
- return equivalentSuffixCandidate;
3317
- }
3318
3344
  throw ambiguousShowPath(objectRef, requestedPath, candidates.length > 0 ? candidates : suffixCandidates);
3319
3345
  }
3320
3346
  function singleEquivalentShowFile(files) {
@@ -3672,11 +3698,11 @@ function traceSummary(trace) {
3672
3698
  files: trace.files.map(fileSummary),
3673
3699
  };
3674
3700
  }
3675
- function formatTraceDetail(detail) {
3701
+ function formatTraceDetail(detail, refs = {}) {
3676
3702
  return detail.executions.map((execution) => {
3677
3703
  const sessionLabels = execution.sessions.map((session) => session.label).join(",");
3678
3704
  return [
3679
- `${execution.id}\trun=${displayRef(detail.runId)}\tjobs=${execution.jobIds.map(displayRef).join(",")}\tstatus=${execution.status}`,
3705
+ `${execution.id}\trun=${refs.runRefs?.get(detail.runId) ?? displayRef(detail.runId)}\tjobs=${execution.jobIds.map((id) => refs.jobRefs?.get(id) ?? displayRef(id)).join(",")}\tstatus=${execution.status}`,
3680
3706
  `events=${execution.trace.events.length}`,
3681
3707
  `spans=${execution.trace.spans.length}`,
3682
3708
  `summaries=${execution.trace.summaries.length}`,
@@ -18,7 +18,7 @@ export interface WorkbenchInstallStoreResult {
18
18
  store: StoreKind;
19
19
  directoryName: string;
20
20
  destination: string;
21
- previous: "none" | "overwritten" | "unchanged";
21
+ previous: "none" | "updated" | "overwritten" | "unchanged";
22
22
  filesCopied: number;
23
23
  contentHash: string;
24
24
  provenancePath: string;
@@ -1 +1 @@
1
- {"version":3,"file":"install-targets.d.ts","sourceRoot":"","sources":["../src/install-targets.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"install-targets.d.ts","sourceRoot":"","sources":["../src/install-targets.ts"],"names":[],"mappings":"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"}
@@ -13,12 +13,14 @@ export function provenancePathForStore(storeRoot = canonicalSkillsStore()) {
13
13
  }
14
14
  export async function installSnapshotToStore(options) {
15
15
  const skillName = canonicalSkillDirectoryName(options.snapshot);
16
- const destination = path.join(canonicalSkillsStore(), skillName);
16
+ const storeRoot = canonicalSkillsStore();
17
+ const destination = path.join(storeRoot, skillName);
17
18
  const normalizedFiles = options.snapshot.files.map((file) => ({
18
19
  ...file,
19
20
  path: normalizeInstallSnapshotPath(file.path),
20
21
  }));
21
22
  const contentHash = contentHashForFiles(normalizedFiles);
23
+ const provenance = await readProvenanceFile(storeRoot);
22
24
  const existingHash = await readExistingTreeHash(destination).catch((error) => {
23
25
  const code = error.code;
24
26
  if (code === "ENOENT") {
@@ -34,10 +36,16 @@ export async function installSnapshotToStore(options) {
34
36
  }
35
37
  throw error;
36
38
  });
39
+ const existingRecord = provenance.skills[skillName];
40
+ const canUpdateExisting = Boolean(existingHash &&
41
+ existingRecord &&
42
+ existingHash === existingRecord.contentHash &&
43
+ existingRecord.handle === options.provenance.handle &&
44
+ existingRecord.baseUrl === options.provenance.baseUrl);
37
45
  const previous = existingHash
38
- ? existingHash === contentHash ? "unchanged" : "overwritten"
46
+ ? existingHash === contentHash ? "unchanged" : canUpdateExisting ? "updated" : "overwritten"
39
47
  : "none";
40
- if (existingHash && previous !== "unchanged" && !options.overwrite) {
48
+ if (existingHash && previous === "overwritten" && !options.overwrite) {
41
49
  throw new WorkbenchCodedError("install_failed", `Canonical skill already exists: ${destination}`, {
42
50
  remediation: "Pass --yes to overwrite the existing canonical store skill.",
43
51
  subject: { destination },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workbench-ai/workbench",
3
- "version": "0.0.83",
3
+ "version": "0.0.85",
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-core": "0.0.83",
26
- "@workbench-ai/workbench-built-in-adapters": "0.0.83",
27
- "@workbench-ai/workbench-protocol": "0.0.83",
28
- "@workbench-ai/workbench-contract": "0.0.83"
25
+ "@workbench-ai/workbench-built-in-adapters": "0.0.85",
26
+ "@workbench-ai/workbench-protocol": "0.0.85",
27
+ "@workbench-ai/workbench-core": "0.0.85",
28
+ "@workbench-ai/workbench-contract": "0.0.85"
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.83"
39
+ "@workbench-ai/workbench-ui": "0.0.85"
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",