@barivia/barsom-mcp 0.23.11 → 0.23.12

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/README.md CHANGED
@@ -194,7 +194,7 @@ The right viewer depends on **(MCP App support)** **and** **(can the human reach
194
194
  ### Migration notes
195
195
 
196
196
  - **Results explorer figure dropdown (0.23.2):** `results_explorer` puts a grouped `<select>` above the plot (no scrolling the sidebar to switch figures), matching barmesh explorer UX. Sidebar keeps Metrics + Highlights only.
197
- - **UI delivery + train auto-open (0.22.4+):** `train()` returns `training_monitor` App structured content so supporting hosts open the monitor in the same turn. App tools emit structured `ui_delivery` (`delivery`, `urls`, `hint_for_agent`) and always put the standalone viz URL first. Env: `BARIVIA_UI_DELIVERY`, `BARIVIA_VIZ_PORT`. Cyclic cos/sin component PNGs are omitted from default `summary.files` / `figures=all` (encoding for training; panels still appear in `combined` / recolor); opt in with train param `viz_upload_cyclic_components=true`.
197
+ - **UI delivery + train auto-open (0.22.4+ / 0.23.11+):** `train()` returns `training_monitor` App structured content so supporting hosts open the monitor in the same turn. App tools emit structured `ui_delivery` (`delivery`, `urls`, `hint_for_agent`) and always put the standalone viz URL first. Env: `BARIVIA_UI_DELIVERY` (`auto` \| `apps` \| `localhost` \| `inline` \| `text_only`), `BARIVIA_UI_PREFER_LOCALHOST=1` (opt-in localhost under `auto`), `BARIVIA_VIZ_PORT`. Cyclic cos/sin component PNGs are omitted from default `summary.files` / `figures=all` (encoding for training; panels still appear in `combined` / recolor); opt in with train param `viz_upload_cyclic_components=true`.
198
198
  - **Account surface (0.22.0, breaking):** `account(request_compute|compute_status|release_compute)` removed — AWS cloud burst leases are gone. Use `account(status|history|add_funds)`. Capacity is shared LOCAL/GKE pools.
199
199
  - **Structured results + train hints (0.21.x, non-breaking):**
200
200
  - **`results(action=get)`** returns **`structuredContent`** (job id/type, label, summary metrics, file list) alongside the text summary — same pattern as `training_monitor` / `results_explorer`. Agent hosts can read fields without parsing prose.
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Canonical figure taxonomy for barsom results_explorer dropdown sections.
3
+ * Labels match the prior GROUP_LABELS (Summary → Diagnostics → Components → Other).
4
+ */
5
+ export const FIGURE_SECTION_ORDER = [
6
+ "summary",
7
+ "diagnostic",
8
+ "component",
9
+ "artifact",
10
+ ];
11
+ export const FIGURE_SECTION_LABELS = {
12
+ summary: "Summary",
13
+ diagnostic: "Diagnostics",
14
+ component: "Components",
15
+ artifact: "Other",
16
+ };
17
+ /** Map a figure base key into one of four explorer sections. */
18
+ export function figureSection(key) {
19
+ if (key === "combined" || key === "coverage_overview")
20
+ return "summary";
21
+ if (key.startsWith("component_"))
22
+ return "component";
23
+ if (key === "umatrix" ||
24
+ key === "hit_histogram" ||
25
+ key === "learning_curve" ||
26
+ key === "correlation" ||
27
+ key === "divergence_kl" ||
28
+ key === "divergence_w1" ||
29
+ key.startsWith("cluster")) {
30
+ return "diagnostic";
31
+ }
32
+ if (key.startsWith("component"))
33
+ return "component";
34
+ return "artifact";
35
+ }
36
+ export const FIGURE_SORT_RANK = {
37
+ combined: 0,
38
+ coverage_overview: 1,
39
+ umatrix: 10,
40
+ hit_histogram: 11,
41
+ learning_curve: 12,
42
+ correlation: 13,
43
+ divergence_kl: 14,
44
+ divergence_w1: 15,
45
+ };
46
+ export function sortRank(key) {
47
+ if (key in FIGURE_SORT_RANK)
48
+ return FIGURE_SORT_RANK[key];
49
+ if (key.startsWith("component_")) {
50
+ const n = Number(key.replace(/^component_/, "").split("_")[0]);
51
+ return 20 + (Number.isFinite(n) ? n : 0);
52
+ }
53
+ if (key.startsWith("cluster"))
54
+ return 16;
55
+ return 50;
56
+ }
57
+ /** Sort by section order, then canonical rank, then label. */
58
+ export function sortFiguresBySection(figures) {
59
+ return [...figures].sort((a, b) => FIGURE_SECTION_ORDER.indexOf(a.kind ?? figureSection(a.key)) -
60
+ FIGURE_SECTION_ORDER.indexOf(b.kind ?? figureSection(b.key)) ||
61
+ sortRank(a.key) - sortRank(b.key) ||
62
+ a.label.localeCompare(b.label));
63
+ }
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.11";
69
+ export const CLIENT_VERSION = "0.23.12";
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). */
@@ -1096,6 +1096,11 @@ export async function fetchTrainingGuidanceFromApi() {
1096
1096
  catch (e) {
1097
1097
  if (e?.httpStatus === 401 || e?.httpStatus === 403)
1098
1098
  throw e;
1099
+ // Prefer last successful payload over a hard failure (TTL may have expired).
1100
+ if (_trainingGuidanceCache?.text) {
1101
+ return (_trainingGuidanceCache.text +
1102
+ "\n\n(Note: API unreachable — showing last cached guidance; may be stale.)");
1103
+ }
1099
1104
  return "Could not fetch parameter guidance. Check API key and connectivity.";
1100
1105
  }
1101
1106
  }
@@ -4,7 +4,9 @@ import { registerAuditedTool, runMcpToolAudit } from "../audit.js";
4
4
  import { apiCall, apiRawCall, BARSOM_VIZ_VIEWS, buildStandaloneVizPageUrl, buildVizRelatedUrls, getVizPort, getCaptionForImage, getResultsImagesToFetch, mimeForFilename, structuredTextResult, tryAttachImage, } from "../shared.js";
5
5
  import { buildVizStandaloneUrl } from "../viz_links.js";
6
6
  import { appendUiDeliveryContent, beginInlineFallback, buildStandaloneVizUrl, resolveUiDelivery, } from "../ui-delivery.js";
7
+ import { figureSection, sortFiguresBySection } from "../figure_sections.js";
7
8
  export const RESULTS_EXPLORER_URI = "ui://barsom/results-explorer";
9
+ export { FIGURE_SECTION_LABELS, FIGURE_SECTION_ORDER, figureSection } from "../figure_sections.js";
8
10
  function labelForFigure(filename) {
9
11
  const base = filename.replace(/\.(png|pdf|svg)$/i, "");
10
12
  if (base === "combined")
@@ -34,7 +36,7 @@ function buildAvailableFigures(jobId, summary) {
34
36
  // `_fetch_figure` server-tool bridge (the default figure also arrives as a
35
37
  // base64 image via the tool result). The standalone browser page builds its
36
38
  // own relative-URL payload separately (see views/results-explorer/app.ts).
37
- return figureFiles.map((filename) => {
39
+ return sortFiguresBySection(figureFiles.map((filename) => {
38
40
  const base = filename.replace(/\.(png|pdf|svg)$/i, "");
39
41
  const featureName = base.startsWith("component_")
40
42
  ? base.replace(/^component_\d+_/, "").replace(/_/g, " ")
@@ -44,11 +46,11 @@ function buildAvailableFigures(jobId, summary) {
44
46
  key: base,
45
47
  label: labelForFigure(filename),
46
48
  filename,
47
- kind: base.startsWith("component_") ? "component" : base === "combined" ? "summary" : "diagnostic",
49
+ kind: figureSection(base),
48
50
  featureName,
49
51
  caption: getCaptionForImage(filename) || (!isRaster ? "This figure is available for download, but it is not inline-displayable in this first iteration." : undefined),
50
52
  };
51
- });
53
+ }));
52
54
  }
53
55
  function buildMetrics(summary) {
54
56
  const jobType = String(summary.job_type ?? "train_som");
@@ -179,7 +181,7 @@ async function handleResultsExplorer(job_id) {
179
181
  }
180
182
  return {
181
183
  ...structuredTextResult({ ...payload, ui_delivery: uiDelivery }, undefined, content),
182
- _meta: { ui: { resourceUri: RESULTS_EXPLORER_URI } },
184
+ _meta: { ui: { resourceUri: RESULTS_EXPLORER_URI, visibility: ["app"] } },
183
185
  };
184
186
  }
185
187
  export function registerExploreMapTool(server) {
@@ -189,7 +191,7 @@ export function registerExploreMapTool(server) {
189
191
  inputSchema: {
190
192
  job_id: z.string().describe("Job ID of a completed map training job"),
191
193
  },
192
- _meta: { ui: { resourceUri: RESULTS_EXPLORER_URI } },
194
+ _meta: { ui: { resourceUri: RESULTS_EXPLORER_URI, visibility: ["app"] } },
193
195
  };
194
196
  registerAppTool(server, "results_explorer", toolConfig, async (args) => runMcpToolAudit("results_explorer", "default", args, () => handleResultsExplorer(String(args.job_id))));
195
197
  // NOTE: this MUST stay a registered tool — the Results Explorer MCP App view
@@ -100,10 +100,11 @@ async function enrichWithTrainingLog(job_id, data) {
100
100
  }
101
101
  }
102
102
  export async function buildTrainingMonitorResult(job_id, opts) {
103
- const { fetch_training_log, preamble } = opts ?? {};
103
+ const { preamble } = opts ?? {};
104
104
  let data = (await apiCall("GET", `/v1/jobs/${job_id}`));
105
105
  const jobStatus = String(data.status ?? "");
106
- if (fetch_training_log || needsTrainingLogEnrichment(data)) {
106
+ // Enrich terminal jobs for curve arrays (App refresh uses job_id only — no public flag).
107
+ if (needsTrainingLogEnrichment(data) || ["completed", "failed", "cancelled"].includes(jobStatus)) {
107
108
  data = await enrichWithTrainingLog(job_id, data);
108
109
  }
109
110
  const port = getVizPort();
@@ -178,21 +179,15 @@ export function registerTrainingMonitorTool(server) {
178
179
  description: "Optional view of training progress for a job_id. Prefer the standalone localhost URL in the tool result (always surfaced when the viz server is up) — some hosts list MCP Apps without mounting panels. When a host mounts the App, the panel auto-refreshes every 5s: QE/TE curves (≤1000 batch samples per phase) plus a hit-distribution heatmap. Set BARIVIA_VIZ_PORT for a stable port; re-run for a fresh link after proxy restart. Headless: jobs(action=status) has the same scalars — not required to finish training or read results.",
179
180
  inputSchema: {
180
181
  job_id: z.string().describe("Training job ID to monitor"),
181
- fetch_training_log: z
182
- .boolean()
183
- .optional()
184
- .describe("[INTERNAL — App/refresh path] Merge completed-job training-log arrays when live progress is empty. Agents should omit this."),
185
182
  },
186
- _meta: { ui: { resourceUri: TRAINING_MONITOR_URI } },
183
+ _meta: { ui: { resourceUri: TRAINING_MONITOR_URI, visibility: ["model", "app"] } },
187
184
  }, async (args) => runMcpToolAudit("training_monitor", "default", args, async () => {
188
185
  beginInlineFallback();
189
- const { job_id, fetch_training_log } = args;
190
- const { structuredContent, content, text } = await buildTrainingMonitorResult(job_id, {
191
- fetch_training_log,
192
- });
186
+ const { job_id } = args;
187
+ const { structuredContent, content, text } = await buildTrainingMonitorResult(job_id);
193
188
  return {
194
189
  ...structuredTextResult(structuredContent, text, content),
195
- _meta: { ui: { resourceUri: TRAINING_MONITOR_URI } },
190
+ _meta: { ui: { resourceUri: TRAINING_MONITOR_URI, visibility: ["model", "app"] } },
196
191
  };
197
192
  }));
198
193
  }