@barivia/barmesh-mcp 0.7.0 → 0.7.1
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_monitor.js +38 -2
- package/dist/logger.js +3 -0
- package/dist/shared.js +77 -12
- package/dist/tools/barmesh_results_explorer.js +9 -4
- package/dist/tools/cfd.js +3 -3
- package/dist/tools/datasets.js +11 -9
- package/dist/tools/guide.js +13 -3
- package/dist/tools/results.js +2 -2
- package/dist/training_monitor_curve.js +1 -1
- package/dist/views/src/views/barmesh-training-monitor/index.html +24 -24
- package/package.json +1 -1
package/dist/job_monitor.js
CHANGED
|
@@ -55,6 +55,14 @@ export function snapshotFromJob(data, elapsedSec, note) {
|
|
|
55
55
|
snap.qe = Math.round(qe * 10_000) / 10_000;
|
|
56
56
|
if (te != null)
|
|
57
57
|
snap.te = Math.round(te * 10_000) / 10_000;
|
|
58
|
+
const panelTe = num(data, "panel_topographic_error");
|
|
59
|
+
if (panelTe != null)
|
|
60
|
+
snap.panel_te = Math.round(panelTe * 10_000) / 10_000;
|
|
61
|
+
if (data.kernel_complete === true)
|
|
62
|
+
snap.kernel_complete = true;
|
|
63
|
+
const failureStage = str(data, "failure_stage");
|
|
64
|
+
if (failureStage)
|
|
65
|
+
snap.failure_stage = failureStage;
|
|
58
66
|
const eta = num(data, "training_eta_sec");
|
|
59
67
|
if (eta != null && eta > 0)
|
|
60
68
|
snap.eta_sec = Math.round(eta);
|
|
@@ -79,7 +87,11 @@ export function shouldRecordSnapshot(prev, next) {
|
|
|
79
87
|
return true;
|
|
80
88
|
if (Math.abs(prev.progress_pct - next.progress_pct) >= 1)
|
|
81
89
|
return true;
|
|
82
|
-
if (prev.qe !== next.qe || prev.te !== next.te)
|
|
90
|
+
if (prev.qe !== next.qe || prev.te !== next.te || prev.panel_te !== next.panel_te)
|
|
91
|
+
return true;
|
|
92
|
+
if (prev.kernel_complete !== next.kernel_complete)
|
|
93
|
+
return true;
|
|
94
|
+
if (prev.failure_stage !== next.failure_stage)
|
|
83
95
|
return true;
|
|
84
96
|
return false;
|
|
85
97
|
}
|
|
@@ -93,6 +105,12 @@ export function formatSnapshotLine(s) {
|
|
|
93
105
|
parts.push(`QE ${s.qe.toFixed(4)}`);
|
|
94
106
|
if (s.te != null)
|
|
95
107
|
parts.push(`Epoch TE ${s.te.toFixed(4)}`);
|
|
108
|
+
if (s.panel_te != null)
|
|
109
|
+
parts.push(`Panel TE ${s.panel_te.toFixed(4)}`);
|
|
110
|
+
if (s.kernel_complete)
|
|
111
|
+
parts.push("kernel complete");
|
|
112
|
+
if (s.failure_stage)
|
|
113
|
+
parts.push(`failure_stage ${s.failure_stage}`);
|
|
96
114
|
if (s.eta_sec != null)
|
|
97
115
|
parts.push(`ETA ~${s.eta_sec}s`);
|
|
98
116
|
if (s.ordering_errors_tail?.length) {
|
|
@@ -119,6 +137,22 @@ export function formatMonitorText(result, opts) {
|
|
|
119
137
|
lines.push(result.suggested_next_step);
|
|
120
138
|
return lines.join("\n");
|
|
121
139
|
}
|
|
140
|
+
function failureStageHint(stage, error) {
|
|
141
|
+
const err = (error ?? "").toLowerCase();
|
|
142
|
+
if (stage === "preprocessing") {
|
|
143
|
+
return "Check barmesh_datasets(action=preview) and prepare job status.";
|
|
144
|
+
}
|
|
145
|
+
if (stage === "training") {
|
|
146
|
+
if (err.includes("memory") || err.includes("readonlymemory")) {
|
|
147
|
+
return "Reduce grid size or batch_size and retrain.";
|
|
148
|
+
}
|
|
149
|
+
return "Review mesh convergence parameters (grid, batch_size, features).";
|
|
150
|
+
}
|
|
151
|
+
if (stage === "visualization" || stage === "upload" || stage === "metrics") {
|
|
152
|
+
return "Re-run barmesh_training_monitor with wait_finalize=true or barmesh_results(action=render) for figures.";
|
|
153
|
+
}
|
|
154
|
+
return "Read the error above before retrying.";
|
|
155
|
+
}
|
|
122
156
|
function suggestedNextStep(job_id, data) {
|
|
123
157
|
const status = String(data.status ?? "");
|
|
124
158
|
if (status === "completed") {
|
|
@@ -130,7 +164,9 @@ function suggestedNextStep(job_id, data) {
|
|
|
130
164
|
}
|
|
131
165
|
if (status === "failed") {
|
|
132
166
|
const stage = str(data, "failure_stage");
|
|
133
|
-
|
|
167
|
+
const err = str(data, "error");
|
|
168
|
+
const hint = stage ? failureStageHint(stage, err) : "Read the error above before retrying.";
|
|
169
|
+
return `Job failed${stage ? ` at ${stage}` : ""}. ${hint}`;
|
|
134
170
|
}
|
|
135
171
|
if (status === "cancelled")
|
|
136
172
|
return `Job cancelled. Confirm with barmesh_jobs(action=status, job_id="${job_id}").`;
|
package/dist/logger.js
CHANGED
|
@@ -29,6 +29,9 @@ function emit(fields) {
|
|
|
29
29
|
export function logInfo(msg, fields = {}) {
|
|
30
30
|
emit({ ...fields, level: "info", msg });
|
|
31
31
|
}
|
|
32
|
+
export function logWarn(msg, fields = {}) {
|
|
33
|
+
emit({ ...fields, level: "warn", msg });
|
|
34
|
+
}
|
|
32
35
|
export function logAudit(fields) {
|
|
33
36
|
emit({ ...fields, event: "mcp_tool_call", level: "info", msg: "mcp_tool_call" });
|
|
34
37
|
}
|
package/dist/shared.js
CHANGED
|
@@ -13,17 +13,25 @@ import { pipeline } from "node:stream/promises";
|
|
|
13
13
|
import os from "node:os";
|
|
14
14
|
import path from "node:path";
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
|
-
import { logInfo } from "./logger.js";
|
|
16
|
+
import { logInfo, logWarn } from "./logger.js";
|
|
17
17
|
// ---------------------------------------------------------------------------
|
|
18
18
|
// Config
|
|
19
19
|
// ---------------------------------------------------------------------------
|
|
20
|
-
export const API_URL = process.env.BARIVIA_API_URL ??
|
|
21
|
-
export const API_KEY = process.env.BARIVIA_API_KEY ??
|
|
20
|
+
export const API_URL = process.env.BARIVIA_API_URL ?? "https://api.barivia.se";
|
|
21
|
+
export const API_KEY = process.env.BARIVIA_API_KEY ?? "";
|
|
22
22
|
export const FETCH_TIMEOUT_MS = parseInt(process.env.BARIVIA_FETCH_TIMEOUT_MS ?? "60000", 10);
|
|
23
23
|
export const MAX_RETRIES = 2;
|
|
24
|
+
export const RETRY_BASE_MS = parseInt(process.env.BARIVIA_RETRY_BASE_MS ?? "1000", 10);
|
|
24
25
|
export const RETRYABLE_STATUS = new Set([502, 503, 504]);
|
|
26
|
+
/** Exponential backoff for transient API retries (attempt 0 → base, 1 → 2×, …). */
|
|
27
|
+
export function retryDelayMs(attempt) {
|
|
28
|
+
return RETRY_BASE_MS * 2 ** attempt;
|
|
29
|
+
}
|
|
30
|
+
function newRequestId() {
|
|
31
|
+
return randomUUID();
|
|
32
|
+
}
|
|
25
33
|
/** Single source of truth for the proxy version. Keep in sync with package.json on bump. */
|
|
26
|
-
export const CLIENT_VERSION = "0.7.
|
|
34
|
+
export const CLIENT_VERSION = "0.7.1";
|
|
27
35
|
export const PUBLIC_SITE_ORIGIN = "https://barivia.se";
|
|
28
36
|
/** Large per-cell CSV uploads may exceed the default fetch timeout. */
|
|
29
37
|
export const UPLOAD_DATASET_TIMEOUT_MS = 180_000;
|
|
@@ -33,6 +41,21 @@ export const LARGE_UPLOAD_BYTES = 64 * 1024 * 1024; // 64 MB
|
|
|
33
41
|
export const PRESIGNED_PUT_TIMEOUT_MS = 30 * 60_000; // 30 min
|
|
34
42
|
/** Poll window for the async stage_dataset job. */
|
|
35
43
|
export const POLL_STAGE_MAX_MS = 30 * 60_000; // 30 min
|
|
44
|
+
/** Map a local upload path to the API Content-Type (csv vs tsv; ignores .gz suffix). */
|
|
45
|
+
export function resolveUploadContentType(filePath) {
|
|
46
|
+
const lower = filePath.toLowerCase();
|
|
47
|
+
const base = lower.endsWith(".gz") ? lower.slice(0, -3) : lower;
|
|
48
|
+
return path.extname(base) === ".tsv" ? "text/tab-separated-values" : "text/csv";
|
|
49
|
+
}
|
|
50
|
+
export function suggestMeshDatasetPreview(datasetId) {
|
|
51
|
+
return `barmesh_datasets(action=preview, dataset_id=${datasetId}) to verify mesh, feature, and volume columns.`;
|
|
52
|
+
}
|
|
53
|
+
export function suggestAfterCfdSubmit(jobId, prepSuffix = "") {
|
|
54
|
+
return `Run barmesh_training_monitor(job_id="${jobId}") or barmesh_jobs(action=monitor, job_id="${jobId}")${prepSuffix}; on completion call barmesh_results(action=get, job_id="${jobId}").`;
|
|
55
|
+
}
|
|
56
|
+
export function suggestAfterRender(parentJobId, fmt) {
|
|
57
|
+
return `Figures rendered as ${fmt}. Download with barmesh_results(action=download, job_id="${parentJobId}") or barmesh_results(action=image, job_id="${parentJobId}", filename=<figure>.${fmt}).`;
|
|
58
|
+
}
|
|
36
59
|
/** Streaming SHA-256 of a file (idempotency key) without reading it into memory. */
|
|
37
60
|
export async function streamFileSha256(srcPath) {
|
|
38
61
|
return new Promise((resolve, reject) => {
|
|
@@ -80,7 +103,11 @@ export async function putPresignedStream(url, srcPath, contentType, timeoutMs =
|
|
|
80
103
|
const resp = await fetch(url, {
|
|
81
104
|
method: "PUT",
|
|
82
105
|
body: webStream,
|
|
83
|
-
headers: {
|
|
106
|
+
headers: {
|
|
107
|
+
"Content-Type": contentType,
|
|
108
|
+
"Content-Length": String(contentLength),
|
|
109
|
+
"Content-Encoding": "gzip",
|
|
110
|
+
},
|
|
84
111
|
duplex: "half",
|
|
85
112
|
signal: controller.signal,
|
|
86
113
|
});
|
|
@@ -147,7 +174,7 @@ export async function fetchWithTimeout(url, init, timeoutMs = FETCH_TIMEOUT_MS)
|
|
|
147
174
|
// Path / sandbox helpers
|
|
148
175
|
// ---------------------------------------------------------------------------
|
|
149
176
|
export function getSandboxRoot() {
|
|
150
|
-
const raw = process.env.BARIVIA_WORKSPACE_ROOT
|
|
177
|
+
const raw = process.env.BARIVIA_WORKSPACE_ROOT;
|
|
151
178
|
if (raw)
|
|
152
179
|
return path.resolve(process.cwd(), raw);
|
|
153
180
|
const workspaceFolder = process.env.CURSOR_WORKSPACE_FOLDER ?? process.env.WORKSPACE_FOLDER;
|
|
@@ -282,7 +309,7 @@ function _traceparentHeader() {
|
|
|
282
309
|
export async function apiCall(method, pathPart, body, extraHeaders, requestTimeoutMs) {
|
|
283
310
|
const url = `${API_URL}${pathPart}`;
|
|
284
311
|
const contentType = extraHeaders?.["Content-Type"] ?? "application/json";
|
|
285
|
-
const requestId =
|
|
312
|
+
const requestId = newRequestId();
|
|
286
313
|
const headers = {
|
|
287
314
|
Authorization: `Bearer ${API_KEY}`,
|
|
288
315
|
"Content-Type": contentType,
|
|
@@ -310,7 +337,17 @@ export async function apiCall(method, pathPart, body, extraHeaders, requestTimeo
|
|
|
310
337
|
const text = await resp.text();
|
|
311
338
|
if (!resp.ok) {
|
|
312
339
|
if (attempt < MAX_RETRIES && isTransientError(null, resp.status)) {
|
|
313
|
-
|
|
340
|
+
const delayMs = retryDelayMs(attempt);
|
|
341
|
+
logWarn("API retry", {
|
|
342
|
+
rid: requestId,
|
|
343
|
+
action: method,
|
|
344
|
+
path: pathPart,
|
|
345
|
+
attempt: attempt + 1,
|
|
346
|
+
max_retries: MAX_RETRIES,
|
|
347
|
+
delay_ms: delayMs,
|
|
348
|
+
error_code: `http_${resp.status}`,
|
|
349
|
+
});
|
|
350
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
314
351
|
continue;
|
|
315
352
|
}
|
|
316
353
|
logInfo("API response", { rid: requestId, outcome: "error", duration_ms: Date.now() - t0, error_code: `http_${resp.status}` });
|
|
@@ -322,7 +359,17 @@ export async function apiCall(method, pathPart, body, extraHeaders, requestTimeo
|
|
|
322
359
|
catch (err) {
|
|
323
360
|
lastError = err;
|
|
324
361
|
if (attempt < MAX_RETRIES && isTransientError(err)) {
|
|
325
|
-
|
|
362
|
+
const delayMs = retryDelayMs(attempt);
|
|
363
|
+
logWarn("API retry", {
|
|
364
|
+
rid: requestId,
|
|
365
|
+
action: method,
|
|
366
|
+
path: pathPart,
|
|
367
|
+
attempt: attempt + 1,
|
|
368
|
+
max_retries: MAX_RETRIES,
|
|
369
|
+
delay_ms: delayMs,
|
|
370
|
+
error_code: err instanceof Error ? err.name : "fetch_error",
|
|
371
|
+
});
|
|
372
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
326
373
|
continue;
|
|
327
374
|
}
|
|
328
375
|
if (err instanceof DOMException && err.name === "AbortError" && !err.httpStatus) {
|
|
@@ -336,7 +383,7 @@ export async function apiCall(method, pathPart, body, extraHeaders, requestTimeo
|
|
|
336
383
|
/** Fetch raw bytes from the API (for image downloads). */
|
|
337
384
|
export async function apiRawCall(pathPart, requestTimeoutMs) {
|
|
338
385
|
const url = `${API_URL}${pathPart}`;
|
|
339
|
-
const requestId =
|
|
386
|
+
const requestId = newRequestId();
|
|
340
387
|
const effectiveTimeout = requestTimeoutMs ?? FETCH_TIMEOUT_MS;
|
|
341
388
|
let lastError;
|
|
342
389
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
@@ -344,7 +391,16 @@ export async function apiRawCall(pathPart, requestTimeoutMs) {
|
|
|
344
391
|
const resp = await fetchWithTimeout(url, { method: "GET", headers: { Authorization: `Bearer ${API_KEY}`, "X-Request-ID": requestId, traceparent: _traceparentHeader() } }, effectiveTimeout);
|
|
345
392
|
if (!resp.ok) {
|
|
346
393
|
if (attempt < MAX_RETRIES && isTransientError(null, resp.status)) {
|
|
347
|
-
|
|
394
|
+
const delayMs = retryDelayMs(attempt);
|
|
395
|
+
logWarn("API retry", {
|
|
396
|
+
rid: requestId,
|
|
397
|
+
path: pathPart,
|
|
398
|
+
attempt: attempt + 1,
|
|
399
|
+
max_retries: MAX_RETRIES,
|
|
400
|
+
delay_ms: delayMs,
|
|
401
|
+
error_code: `http_${resp.status}`,
|
|
402
|
+
});
|
|
403
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
348
404
|
continue;
|
|
349
405
|
}
|
|
350
406
|
const text = await resp.text();
|
|
@@ -359,7 +415,16 @@ export async function apiRawCall(pathPart, requestTimeoutMs) {
|
|
|
359
415
|
catch (err) {
|
|
360
416
|
lastError = err;
|
|
361
417
|
if (attempt < MAX_RETRIES && isTransientError(err)) {
|
|
362
|
-
|
|
418
|
+
const delayMs = retryDelayMs(attempt);
|
|
419
|
+
logWarn("API retry", {
|
|
420
|
+
rid: requestId,
|
|
421
|
+
path: pathPart,
|
|
422
|
+
attempt: attempt + 1,
|
|
423
|
+
max_retries: MAX_RETRIES,
|
|
424
|
+
delay_ms: delayMs,
|
|
425
|
+
error_code: err instanceof Error ? err.name : "fetch_error",
|
|
426
|
+
});
|
|
427
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
363
428
|
continue;
|
|
364
429
|
}
|
|
365
430
|
if (err instanceof DOMException && err.name === "AbortError" && !err.httpStatus) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
|
|
3
|
-
import {
|
|
3
|
+
import { runMcpToolAudit } from "../audit.js";
|
|
4
4
|
import { apiCall, apiRawCall, getClientSupportsMcpApps, getVizPort, getCaptionForImage, mimeForFilename, structuredTextResult, tryAttachImage, } from "../shared.js";
|
|
5
5
|
import { FIGURE_SECTION_LABELS, FIGURE_SECTION_ORDER, figureSection, sortRank, } from "../figure_sections.js";
|
|
6
6
|
import { buildMetadataStrip } from "../results_metadata.js";
|
|
@@ -178,9 +178,14 @@ export function registerResultsExplorerTool(server) {
|
|
|
178
178
|
_meta: { ui: { resourceUri: RESULTS_EXPLORER_URI } },
|
|
179
179
|
};
|
|
180
180
|
registerAppTool(server, "barmesh_results_explorer", toolConfig, async (args) => runMcpToolAudit("barmesh_results_explorer", "default", args, () => handleResultsExplorer(String(args.job_id))));
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
181
|
+
registerAppTool(server, "_barmesh_fetch_figure", {
|
|
182
|
+
title: "Fetch figure (internal)",
|
|
183
|
+
description: "[INTERNAL — MCP App only; agents MUST NOT call.] Lazy-loads one raster figure as base64 for the Mesh Convergence Results Explorer.",
|
|
184
|
+
inputSchema: {
|
|
185
|
+
job_id: z.string(),
|
|
186
|
+
filename: z.string(),
|
|
187
|
+
},
|
|
188
|
+
_meta: { ui: { resourceUri: RESULTS_EXPLORER_URI, visibility: ["app"] } },
|
|
184
189
|
}, async ({ job_id, filename }) => {
|
|
185
190
|
const safeFilename = filename.replace(/[^a-zA-Z0-9._-]/g, "");
|
|
186
191
|
const mime = mimeForFilename(safeFilename);
|
package/dist/tools/cfd.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { registerAuditedTool } from "../audit.js";
|
|
3
|
-
import { apiCall, textResult } from "../shared.js";
|
|
3
|
+
import { apiCall, textResult, suggestAfterCfdSubmit } from "../shared.js";
|
|
4
4
|
import { pollCfdPrepareIfPresent } from "../cfd_prepare.js";
|
|
5
5
|
export function registerCfdTools(server) {
|
|
6
6
|
registerAuditedTool(server, "barmesh_mesh_convergence", `Run SOM-based mesh-convergence analysis on an uploaded combined per-cell CSV.
|
|
@@ -52,7 +52,7 @@ COMMON MISTAKES: omitting feature_columns (required); choosing a reference_mesh
|
|
|
52
52
|
const id = data.id;
|
|
53
53
|
if (id != null) {
|
|
54
54
|
const prep = data.prepare_job_id != null ? " (dataset prepare complete)" : "";
|
|
55
|
-
data.suggested_next_step =
|
|
55
|
+
data.suggested_next_step = suggestAfterCfdSubmit(String(id), prep);
|
|
56
56
|
}
|
|
57
57
|
return textResult(data);
|
|
58
58
|
});
|
|
@@ -85,7 +85,7 @@ COMMON MISTAKES: not providing h_column or n_cells_column; mixing QoIs with diff
|
|
|
85
85
|
const data = (await apiCall("POST", "/v1/cfd/richardson", body));
|
|
86
86
|
const id = data.id;
|
|
87
87
|
if (id != null)
|
|
88
|
-
data.suggested_next_step =
|
|
88
|
+
data.suggested_next_step = suggestAfterCfdSubmit(String(id));
|
|
89
89
|
return textResult(data);
|
|
90
90
|
});
|
|
91
91
|
}
|
package/dist/tools/datasets.js
CHANGED
|
@@ -4,7 +4,7 @@ import { z } from "zod";
|
|
|
4
4
|
import fs from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { registerAuditedTool } from "../audit.js";
|
|
7
|
-
import { apiCall, getWorkspaceRootAsync, resolveFilePathForUpload, textResult, pollUntilComplete, UPLOAD_DATASET_TIMEOUT_MS, LARGE_UPLOAD_BYTES, PRESIGNED_PUT_TIMEOUT_MS, POLL_STAGE_MAX_MS, streamFileSha256, putPresignedStream, } from "../shared.js";
|
|
7
|
+
import { apiCall, getWorkspaceRootAsync, resolveFilePathForUpload, textResult, pollUntilComplete, UPLOAD_DATASET_TIMEOUT_MS, LARGE_UPLOAD_BYTES, PRESIGNED_PUT_TIMEOUT_MS, POLL_STAGE_MAX_MS, streamFileSha256, putPresignedStream, resolveUploadContentType, suggestMeshDatasetPreview, } from "../shared.js";
|
|
8
8
|
import { GZIP_UPLOAD_HINT } from "../upload_hints.js";
|
|
9
9
|
/**
|
|
10
10
|
* Normalize a nullable string field from the API. Returns "" for absent values,
|
|
@@ -91,9 +91,11 @@ ESCALATION: If preview shows a feature column as non-numeric, fix the extraction
|
|
|
91
91
|
if (!name)
|
|
92
92
|
throw new Error("barmesh_datasets(upload) requires name.");
|
|
93
93
|
let body;
|
|
94
|
+
let uploadContentType = "text/csv";
|
|
94
95
|
if (file_path && file_path.length > 0) {
|
|
95
96
|
await apiCall("GET", "/v1/system/info");
|
|
96
97
|
const resolved = await resolveFilePathForUpload(file_path, server);
|
|
98
|
+
uploadContentType = resolveUploadContentType(resolved);
|
|
97
99
|
const lower = resolved.toLowerCase();
|
|
98
100
|
const isGzipInput = lower.endsWith(".gz");
|
|
99
101
|
const baseExt = path.extname(isGzipInput ? lower.slice(0, -3) : lower);
|
|
@@ -134,10 +136,10 @@ ESCALATION: If preview shows a feature column as non-numeric, fix the extraction
|
|
|
134
136
|
id: datasetId,
|
|
135
137
|
status: init.status,
|
|
136
138
|
idempotent_replay: true,
|
|
137
|
-
suggested_next_step:
|
|
139
|
+
suggested_next_step: suggestMeshDatasetPreview(datasetId),
|
|
138
140
|
});
|
|
139
141
|
}
|
|
140
|
-
await putPresignedStream(init.upload_url, resolved, init.content_type ??
|
|
142
|
+
await putPresignedStream(init.upload_url, resolved, init.content_type ?? uploadContentType, PRESIGNED_PUT_TIMEOUT_MS, isGzipInput);
|
|
141
143
|
const fin = (await apiCall("POST", `/v1/datasets/${datasetId}/finalize`, {}));
|
|
142
144
|
const jobId = (fin.id ?? fin.job_id);
|
|
143
145
|
const poll = await pollUntilComplete(jobId, POLL_STAGE_MAX_MS);
|
|
@@ -150,21 +152,21 @@ ESCALATION: If preview shows a feature column as non-numeric, fix the extraction
|
|
|
150
152
|
status: ready ? "ready" : "staging",
|
|
151
153
|
job_id: jobId,
|
|
152
154
|
suggested_next_step: ready
|
|
153
|
-
?
|
|
154
|
-
: `Still staging; poll barmesh_jobs(action=status, job_id="${jobId}") then
|
|
155
|
+
? suggestMeshDatasetPreview(datasetId)
|
|
156
|
+
: `Still staging; poll barmesh_jobs(action=status, job_id="${jobId}") then ${suggestMeshDatasetPreview(datasetId)}.`,
|
|
155
157
|
});
|
|
156
158
|
}
|
|
157
159
|
if (isGzipInput) {
|
|
158
160
|
const gzBytes = await fs.readFile(resolved);
|
|
159
161
|
const data = (await apiCall("POST", "/v1/datasets", gzBytes, {
|
|
160
162
|
"X-Dataset-Name": name,
|
|
161
|
-
"Content-Type":
|
|
163
|
+
"Content-Type": uploadContentType,
|
|
162
164
|
"Content-Encoding": "gzip",
|
|
163
165
|
"Idempotency-Key": createHash("sha256").update(`${name}\n`).update(gzBytes).digest("hex"),
|
|
164
166
|
}, UPLOAD_DATASET_TIMEOUT_MS));
|
|
165
167
|
const gid = data.id ?? data.dataset_id;
|
|
166
168
|
if (gid != null) {
|
|
167
|
-
data.suggested_next_step =
|
|
169
|
+
data.suggested_next_step = suggestMeshDatasetPreview(String(gid));
|
|
168
170
|
}
|
|
169
171
|
return textResult(data);
|
|
170
172
|
}
|
|
@@ -179,7 +181,7 @@ ESCALATION: If preview shows a feature column as non-numeric, fix the extraction
|
|
|
179
181
|
const GZIP_THRESHOLD = 1024 * 1024;
|
|
180
182
|
const uploadHeaders = {
|
|
181
183
|
"X-Dataset-Name": name,
|
|
182
|
-
"Content-Type":
|
|
184
|
+
"Content-Type": uploadContentType,
|
|
183
185
|
"Idempotency-Key": createHash("sha256").update(`${name}\n`).update(body).digest("hex"),
|
|
184
186
|
};
|
|
185
187
|
let uploadBody = body;
|
|
@@ -190,7 +192,7 @@ ESCALATION: If preview shows a feature column as non-numeric, fix the extraction
|
|
|
190
192
|
const data = (await apiCall("POST", "/v1/datasets", uploadBody, uploadHeaders, UPLOAD_DATASET_TIMEOUT_MS));
|
|
191
193
|
const id = data.id ?? data.dataset_id;
|
|
192
194
|
if (id != null) {
|
|
193
|
-
data.suggested_next_step =
|
|
195
|
+
data.suggested_next_step = suggestMeshDatasetPreview(String(id));
|
|
194
196
|
}
|
|
195
197
|
return textResult(data);
|
|
196
198
|
}
|
package/dist/tools/guide.js
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import { registerAuditedTool } from "../audit.js";
|
|
2
2
|
import { apiCall, textResult } from "../shared.js";
|
|
3
3
|
const OFFLINE_GUIDE = `barmesh: CFD mesh-convergence on the Barivia API.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
|
|
5
|
+
Two tracks:
|
|
6
|
+
- barmesh_mesh_convergence — SOM volume-fingerprint distances (symmetric KL + Wasserstein-1 vs reference and stepwise)
|
|
7
|
+
- barmesh_richardson — classical Richardson / GCI on scalar QoIs
|
|
8
|
+
|
|
9
|
+
Workflow:
|
|
10
|
+
1. barmesh_prepare_mesh_data — recipe for the combined per-cell CSV (mesh_id + features + cell volume V)
|
|
11
|
+
2. barmesh_datasets(upload) → barmesh_datasets(preview)
|
|
12
|
+
3. barmesh_mesh_convergence and/or barmesh_richardson — returns job_id
|
|
13
|
+
4. barmesh_training_monitor (visual MCP App) or barmesh_jobs(action=monitor) headless — poll until complete
|
|
14
|
+
5. barmesh_results(action=get) → barmesh_results_explorer for figures
|
|
15
|
+
|
|
16
|
+
(API unreachable; offline summary. Set BARIVIA_API_KEY / BARIVIA_API_URL.)`;
|
|
7
17
|
const OFFLINE_PREP = `barmesh mesh-data prep (offline summary; API unreachable):
|
|
8
18
|
Build ONE combined per-cell CSV across all meshes of the refinement study:
|
|
9
19
|
- one row per cell; a mesh label column (mesh_id); the physical channels you want compared (e.g. p, U_mag, k, log_epsilon — log-compress turbulence quantities); and a cell-volume column (V) for fingerprint weighting.
|
package/dist/tools/results.js
CHANGED
|
@@ -2,7 +2,7 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { registerAuditedTool } from "../audit.js";
|
|
5
|
-
import { apiCall, apiRawCall, getWorkspaceRootAsync, sandboxPath, textResult, tryAttachImage, resetInlineAttachBudget, pollUntilComplete, POLL_STAGE_MAX_MS, } from "../shared.js";
|
|
5
|
+
import { apiCall, apiRawCall, getWorkspaceRootAsync, sandboxPath, textResult, tryAttachImage, resetInlineAttachBudget, pollUntilComplete, POLL_STAGE_MAX_MS, suggestAfterRender, } from "../shared.js";
|
|
6
6
|
import { formatConvergenceReading } from "../convergence_reading.js";
|
|
7
7
|
const MESH_DEFAULT_FIGURES = ["combined", "overview_distances", "plot_vol_all_meshes", "plot_vol_steps", "learning_curve"];
|
|
8
8
|
function formatMeshResultsSummary(jobId, data, summary) {
|
|
@@ -84,7 +84,7 @@ NOT FOR: Submitting jobs.`, {
|
|
|
84
84
|
status: "completed",
|
|
85
85
|
render_job_id: renderId,
|
|
86
86
|
format: fmt,
|
|
87
|
-
suggested_next_step:
|
|
87
|
+
suggested_next_step: suggestAfterRender(job_id, fmt),
|
|
88
88
|
});
|
|
89
89
|
}
|
|
90
90
|
if (action === "image") {
|
|
@@ -16,7 +16,7 @@ export function isKernelTrainingComplete(data, status) {
|
|
|
16
16
|
return true;
|
|
17
17
|
return status === "completed";
|
|
18
18
|
}
|
|
19
|
-
export function teCurveLabel(base
|
|
19
|
+
export function teCurveLabel(base) {
|
|
20
20
|
return `${base} (panel)`;
|
|
21
21
|
}
|
|
22
22
|
export function formatCurveSourceNote(data) {
|