@barivia/barsom-mcp 0.23.13 → 0.23.14
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/job_status_format.js +4 -0
- package/dist/shared.js +22 -1
- package/dist/tools/results.js +34 -10
- package/package.json +1 -1
|
@@ -64,6 +64,10 @@ export function formatJobStatusText(job_id, data) {
|
|
|
64
64
|
else if (prepareJobId) {
|
|
65
65
|
parts.push(`phase: waiting_for_prepare (prepare_job_id=${prepareJobId})`);
|
|
66
66
|
}
|
|
67
|
+
else if (progress >= 5) {
|
|
68
|
+
// Progress advanced but phase not yet stamped — do not imply early ingest.
|
|
69
|
+
parts.push(`phase: running`);
|
|
70
|
+
}
|
|
67
71
|
else {
|
|
68
72
|
parts.push(`phase: preprocessing (not set yet — often download/parse before first progress tick)`);
|
|
69
73
|
}
|
package/dist/shared.js
CHANGED
|
@@ -66,7 +66,7 @@ function newRequestId() {
|
|
|
66
66
|
* X-Barsom-Client-Version so the server can annotate tool guidance with the
|
|
67
67
|
* wrapper version each action requires. Keep in sync with package.json on bump.
|
|
68
68
|
*/
|
|
69
|
-
export const CLIENT_VERSION = "0.23.
|
|
69
|
+
export const CLIENT_VERSION = "0.23.14";
|
|
70
70
|
/** User-facing links; keep aligned with barivia.se / api.barivia.se. */
|
|
71
71
|
export const PUBLIC_SITE_ORIGIN = "https://barivia.se";
|
|
72
72
|
/** Self-serve account dashboard (manage plan, billing, and API keys). */
|
|
@@ -1218,6 +1218,27 @@ export function coerceFiguresArg(v) {
|
|
|
1218
1218
|
}
|
|
1219
1219
|
return val;
|
|
1220
1220
|
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Images to inline after results(action=feature_divergences).
|
|
1223
|
+
* Default is text-only (omit / none / default / combined_only) so headless LLM hosts
|
|
1224
|
+
* (e.g. Ornith) are not rejected for image MCP content. Opt in with "all"/"images" or a filename array.
|
|
1225
|
+
*/
|
|
1226
|
+
export function getFeatureDivergenceImagesToFetch(files, figures) {
|
|
1227
|
+
const isRasterOrSvg = (f) => /\.(png|svg)$/i.test(f);
|
|
1228
|
+
if (figures === "all" || figures === "images") {
|
|
1229
|
+
return files.filter(isRasterOrSvg);
|
|
1230
|
+
}
|
|
1231
|
+
if (Array.isArray(figures)) {
|
|
1232
|
+
return figures.map((name) => {
|
|
1233
|
+
const n = String(name);
|
|
1234
|
+
if (/\.(png|svg|pdf)$/i.test(n))
|
|
1235
|
+
return n;
|
|
1236
|
+
return `${n}.png`;
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
// omit, "none", "default", "combined_only" → no inline images
|
|
1240
|
+
return [];
|
|
1241
|
+
}
|
|
1221
1242
|
/** Attach result figures with captions; tracks inlined names for dedup. */
|
|
1222
1243
|
export async function attachResultsImages(content, jobId, jobType, summary, figures, includeIndividual, inlinedImages) {
|
|
1223
1244
|
for (const name of getResultsImagesToFetch(jobType, summary, figures, includeIndividual)) {
|
package/dist/tools/results.js
CHANGED
|
@@ -2,7 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { registerAuditedTool } from "../audit.js";
|
|
5
|
-
import { apiCall, apiRawCall, getWorkspaceRootAsync, sandboxPath, pollUntilComplete, tryAttachImage, resetInlineAttachBudget, attachResultsImages, resolveOutputDpi, POLL_RECOLOR_MAX_MS, fmtDensityDiffNode, getCaptionForImage, shouldFetchAllRemainingFigures, mimeForFilename, structuredTextResult, coerceFiguresArg, } from "../shared.js";
|
|
5
|
+
import { apiCall, apiRawCall, getWorkspaceRootAsync, sandboxPath, pollUntilComplete, tryAttachImage, resetInlineAttachBudget, attachResultsImages, resolveOutputDpi, POLL_RECOLOR_MAX_MS, fmtDensityDiffNode, getCaptionForImage, shouldFetchAllRemainingFigures, getFeatureDivergenceImagesToFetch, mimeForFilename, structuredTextResult, coerceFiguresArg, } from "../shared.js";
|
|
6
6
|
export function registerResultsTool(server) {
|
|
7
7
|
registerAuditedTool(server, "results", `Retrieve, recolor, download, or export figures and metrics for a completed map job. Presentation and artifact access only — operations on the frozen map (predict, compare, project, impute_column, transition_flow) live in **inference**.
|
|
8
8
|
|
|
@@ -33,14 +33,14 @@ action=export: Structured data exports (not the same as download). Use export_ty
|
|
|
33
33
|
|
|
34
34
|
action=download: Save figures/artifacts to disk (deduped file list). Use so user can open, share, or version files locally.
|
|
35
35
|
- folder: e.g. "." or "./results". Relative to the client's working directory (or MCP workspace). Each job lands in a dedicated subfolder under folder: {job_type}_{label} when label was set at train time, otherwise {job_type}_{job_id_prefix} — so parallel downloads never overwrite summary.json or figure names.
|
|
36
|
-
- figures: "all" (default) downloads published image files; OR a string array of exact artifact filenames from the
|
|
36
|
+
- figures: "all" (default) downloads published image files; OR a string array of **exact** artifact filenames from summary.files / the Available list (e.g. ["combined.png","component_1_alcohol.png"]). Do not invent bare component_1.png — published planes are component_{i}_{feature}.png. Logical tokens like component_1 are for action=get/recolor only. Enum "none" is for get/feature_divergences, not download.
|
|
37
37
|
- include_json: when true, also saves summary.json and other JSON artifacts (use this when you need CSVs listed in summary.files without naming each figure).
|
|
38
38
|
|
|
39
39
|
action=recolor: Change colormap or output format — no retraining. Returns a new job_id; auto-polls 60s.
|
|
40
40
|
AFTER: use results(action=get, job_id=NEW_JOB_ID).
|
|
41
41
|
Colormaps: viridis, plasma, inferno, magma, cividis, turbo, coolwarm, balance, hsv, twilight, RdBu, Spectral. Also: thermal, hot, grays, RdYlBu, delta, curl, phase, rainbow, tol_bright, tol_muted.
|
|
42
42
|
|
|
43
|
-
action=feature_divergences: Recompute component-plane divergences (min-shift sum-normalized denormalized planes). Finalize already writes directed KL heatmap (divergence_kl: cell (i,j)=KL(pᵢ‖pⱼ); upper/lower triangles opposite directions) plus SKL pair tops and mean-outgoing ranking in JSON; use this for Wasserstein or a refresh. metrics default ["symmetric_kl","directed_kl"]; add "wasserstein" for W1. Auto-polls then
|
|
43
|
+
action=feature_divergences: Recompute component-plane divergences (min-shift sum-normalized denormalized planes). Finalize already writes directed KL heatmap (divergence_kl: cell (i,j)=KL(pᵢ‖pⱼ); upper/lower triangles opposite directions) plus SKL pair tops and mean-outgoing ranking in JSON; use this for Wasserstein or a refresh. metrics default ["symmetric_kl","directed_kl"]; add "wasserstein" for W1. Auto-polls then returns text + structuredContent (tops/metrics/files). Default is **text-only** (no inline images) for headless LLM hosts; opt in with figures="all" or figures=["divergence_kl.png"]. Prefer results(download) for heatmaps on text-only clients.
|
|
44
44
|
|
|
45
45
|
NOT FOR: Jobs that haven't completed (use jobs(action=status) first), or temporal flow (use inference(action=transition_flow)).`, {
|
|
46
46
|
action: z
|
|
@@ -50,7 +50,7 @@ NOT FOR: Jobs that haven't completed (use jobs(action=status) first), or tempora
|
|
|
50
50
|
figures: z.preprocess(coerceFiguresArg, z
|
|
51
51
|
.union([z.enum(["default", "combined_only", "all", "images", "none"]), z.array(z.string())])
|
|
52
52
|
.optional()
|
|
53
|
-
.describe("Prefer string 'all'/'none'. action=get: omit=combined only; 'all'=all plots; 'none'=metrics text only; array=logical names (combined,umatrix,…). action=download: 'all'=image files; array=exact filenames (e.g.
|
|
53
|
+
.describe("Prefer string 'all'/'none'. action=get: omit=combined only; 'all'=all plots; 'none'=metrics text only; array=logical names (combined,umatrix,…). action=feature_divergences: omit/'none'=text-only (default); 'all'/'images'=inline heatmaps; array=exact heatmap filenames. action=download: 'all'=image files; array=exact filenames from summary.files (e.g. component_1_alcohol.png — not bare component_1.png). Stringified JSON arrays from hosts are accepted.")),
|
|
54
54
|
include_individual: z
|
|
55
55
|
.boolean()
|
|
56
56
|
.optional()
|
|
@@ -664,12 +664,16 @@ NOT FOR: Jobs that haven't completed (use jobs(action=status) first), or tempora
|
|
|
664
664
|
}
|
|
665
665
|
}
|
|
666
666
|
const savedDir = path.join(folder, jobSubfolder);
|
|
667
|
+
const availCap = 40;
|
|
668
|
+
const availableNote = missing.length > 0 && files.length > 0
|
|
669
|
+
? ` Available (${Math.min(files.length, availCap)}${files.length > availCap ? ` of ${files.length}` : ""}): ${files.slice(0, availCap).join(", ")}${files.length > availCap ? ", …" : ""}. Use exact names from this list (component planes are component_{i}_{feature}.png, not bare component_1.png).`
|
|
670
|
+
: "";
|
|
667
671
|
if (saved.length === 0) {
|
|
668
672
|
const missNote = missing.length > 0 ? ` Missing/failed: ${missing.join(", ")}.` : "";
|
|
669
673
|
return {
|
|
670
674
|
content: [{
|
|
671
675
|
type: "text",
|
|
672
|
-
text: `No files saved.${missNote} Check job_id and that the job is completed.`,
|
|
676
|
+
text: `No files saved.${missNote}${availableNote} Check job_id and that the job is completed.`,
|
|
673
677
|
}],
|
|
674
678
|
};
|
|
675
679
|
}
|
|
@@ -679,7 +683,7 @@ NOT FOR: Jobs that haven't completed (use jobs(action=status) first), or tempora
|
|
|
679
683
|
return {
|
|
680
684
|
content: [{
|
|
681
685
|
type: "text",
|
|
682
|
-
text: `Saved ${saved.length} file(s) to ${savedDir}: ${saved.join(", ")}.${missNote}`,
|
|
686
|
+
text: `Saved ${saved.length} file(s) to ${savedDir}: ${saved.join(", ")}.${missNote}${availableNote}`,
|
|
683
687
|
}],
|
|
684
688
|
};
|
|
685
689
|
}
|
|
@@ -707,6 +711,7 @@ NOT FOR: Jobs that haven't completed (use jobs(action=status) first), or tempora
|
|
|
707
711
|
return { content: [{ type: "text", text: `Recolor job ${newJobId} submitted. Poll with jobs(action=status, job_id="${newJobId}"), then results(action=get, job_id="${newJobId}").` }] };
|
|
708
712
|
}
|
|
709
713
|
if (action === "feature_divergences") {
|
|
714
|
+
resetInlineAttachBudget();
|
|
710
715
|
const body = {
|
|
711
716
|
metrics: divergenceMetrics ?? ["symmetric_kl", "directed_kl"],
|
|
712
717
|
output_format: output_format ?? "png",
|
|
@@ -721,7 +726,7 @@ NOT FOR: Jobs that haven't completed (use jobs(action=status) first), or tempora
|
|
|
721
726
|
return {
|
|
722
727
|
content: [{
|
|
723
728
|
type: "text",
|
|
724
|
-
text: `Feature divergences job ${newJobId} submitted (parent ${job_id}). Poll with jobs(action=status, job_id="${newJobId}"), then results(action=get, job_id="${newJobId}").`,
|
|
729
|
+
text: `Feature divergences job ${newJobId} submitted (parent ${job_id}). Poll with jobs(action=status, job_id="${newJobId}"), then results(action=get, job_id="${newJobId}", figures="none").`,
|
|
725
730
|
}],
|
|
726
731
|
};
|
|
727
732
|
}
|
|
@@ -730,6 +735,8 @@ NOT FOR: Jobs that haven't completed (use jobs(action=status) first), or tempora
|
|
|
730
735
|
const topDivs = sum.top_divergences ?? [];
|
|
731
736
|
const klComp = sum.feature_kl_complexity ?? [];
|
|
732
737
|
const topW1 = sum.top_wasserstein ?? null;
|
|
738
|
+
const files = sum.files ?? [];
|
|
739
|
+
const toInline = getFeatureDivergenceImagesToFetch(files, figuresArg);
|
|
733
740
|
const lines = [
|
|
734
741
|
`Feature divergences complete — job_id: ${newJobId} (parent training: ${job_id})`,
|
|
735
742
|
`Method: ${sum.divergence_method ?? "kl_min_shift_sum_norm_denormalized_component_planes"}`,
|
|
@@ -744,14 +751,31 @@ NOT FOR: Jobs that haven't completed (use jobs(action=status) first), or tempora
|
|
|
744
751
|
topW1 && topW1.length
|
|
745
752
|
? `Top Wasserstein pairs: ${topW1.map((t) => `${t.f1}&${t.f2}=${typeof t.wasserstein === "number" ? Number(t.wasserstein).toFixed(3) : t.wasserstein}`).join(", ")}`
|
|
746
753
|
: "",
|
|
754
|
+
files.length ? `Artifacts: ${files.join(", ")}` : "",
|
|
755
|
+
toInline.length === 0
|
|
756
|
+
? `Text-only response (default). Heatmaps: results(download, job_id="${job_id}" or "${newJobId}") or re-call with figures="all" on vision-capable hosts.`
|
|
757
|
+
: "",
|
|
747
758
|
`Artifacts refreshed on parent result (divergence_kl = directed KL heatmap, upper/lower triangles opposite directions; feature_divergences.json has both directed_kl and symmetric_kl). Compare with Pearson top_correlations from results(get) on the training job.`,
|
|
748
759
|
].filter(Boolean);
|
|
749
760
|
const content = [{ type: "text", text: lines.join("\n") }];
|
|
750
|
-
const
|
|
751
|
-
for (const f of
|
|
761
|
+
const inlinedImages = new Set();
|
|
762
|
+
for (const f of toInline) {
|
|
752
763
|
await tryAttachImage(content, newJobId, f);
|
|
764
|
+
inlinedImages.add(f);
|
|
753
765
|
}
|
|
754
|
-
|
|
766
|
+
const structuredPayload = {
|
|
767
|
+
job_id: newJobId,
|
|
768
|
+
parent_job_id: job_id,
|
|
769
|
+
job_type: "feature_divergences",
|
|
770
|
+
metrics: sum.metrics ?? body.metrics,
|
|
771
|
+
top_divergences: topDivs,
|
|
772
|
+
feature_kl_complexity: klComp,
|
|
773
|
+
top_wasserstein: topW1,
|
|
774
|
+
files,
|
|
775
|
+
figures_inlined: [...inlinedImages],
|
|
776
|
+
};
|
|
777
|
+
const summaryText = content.filter((c) => c.type === "text").map((c) => c.text).join("\n\n");
|
|
778
|
+
return structuredTextResult(structuredPayload, summaryText, content);
|
|
755
779
|
}
|
|
756
780
|
throw new Error("Invalid action");
|
|
757
781
|
});
|