labgate 0.5.37 → 0.5.38
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 +2 -0
- package/dist/cli.js +4 -0
- package/dist/cli.js.map +1 -1
- package/dist/lib/automation-engine.d.ts +91 -0
- package/dist/lib/automation-engine.js +401 -0
- package/dist/lib/automation-engine.js.map +1 -0
- package/dist/lib/config.d.ts +33 -0
- package/dist/lib/config.js +137 -1
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/container.d.ts +4 -1
- package/dist/lib/container.js +26 -10
- package/dist/lib/container.js.map +1 -1
- package/dist/lib/feedback.d.ts +13 -1
- package/dist/lib/feedback.js +9 -4
- package/dist/lib/feedback.js.map +1 -1
- package/dist/lib/init.js +5 -1
- package/dist/lib/init.js.map +1 -1
- package/dist/lib/results-mcp.js +390 -4
- package/dist/lib/results-mcp.js.map +1 -1
- package/dist/lib/results-store.d.ts +66 -0
- package/dist/lib/results-store.js +324 -6
- package/dist/lib/results-store.js.map +1 -1
- package/dist/lib/ui.d.ts +2 -0
- package/dist/lib/ui.html +16870 -8381
- package/dist/lib/ui.js +1650 -120
- package/dist/lib/ui.js.map +1 -1
- package/dist/lib/web-terminal.d.ts +4 -1
- package/dist/lib/web-terminal.js +33 -3
- package/dist/lib/web-terminal.js.map +1 -1
- package/dist/mcp-bundles/dataset-mcp.bundle.mjs +120 -3
- package/dist/mcp-bundles/display-mcp.bundle.mjs +94 -0
- package/dist/mcp-bundles/explorer-mcp.bundle.mjs +123 -4
- package/dist/mcp-bundles/results-mcp.bundle.mjs +850 -53
- package/dist/mcp-bundles/slurm-mcp.bundle.mjs +94 -0
- package/package.json +1 -1
|
@@ -2979,7 +2979,7 @@ var require_compile = __commonJS({
|
|
|
2979
2979
|
const schOrFunc = root.refs[ref];
|
|
2980
2980
|
if (schOrFunc)
|
|
2981
2981
|
return schOrFunc;
|
|
2982
|
-
let _sch =
|
|
2982
|
+
let _sch = resolve2.call(this, root, ref);
|
|
2983
2983
|
if (_sch === void 0) {
|
|
2984
2984
|
const schema = (_a2 = root.localRefs) === null || _a2 === void 0 ? void 0 : _a2[ref];
|
|
2985
2985
|
const { schemaId } = this.opts;
|
|
@@ -3006,7 +3006,7 @@ var require_compile = __commonJS({
|
|
|
3006
3006
|
function sameSchemaEnv(s1, s2) {
|
|
3007
3007
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
3008
3008
|
}
|
|
3009
|
-
function
|
|
3009
|
+
function resolve2(root, ref) {
|
|
3010
3010
|
let sch;
|
|
3011
3011
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
3012
3012
|
ref = sch;
|
|
@@ -3581,55 +3581,55 @@ var require_fast_uri = __commonJS({
|
|
|
3581
3581
|
}
|
|
3582
3582
|
return uri;
|
|
3583
3583
|
}
|
|
3584
|
-
function
|
|
3584
|
+
function resolve2(baseURI, relativeURI, options) {
|
|
3585
3585
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
3586
3586
|
const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
3587
3587
|
schemelessOptions.skipEscape = true;
|
|
3588
3588
|
return serialize(resolved, schemelessOptions);
|
|
3589
3589
|
}
|
|
3590
|
-
function resolveComponent(base,
|
|
3590
|
+
function resolveComponent(base, relative2, options, skipNormalization) {
|
|
3591
3591
|
const target = {};
|
|
3592
3592
|
if (!skipNormalization) {
|
|
3593
3593
|
base = parse3(serialize(base, options), options);
|
|
3594
|
-
|
|
3594
|
+
relative2 = parse3(serialize(relative2, options), options);
|
|
3595
3595
|
}
|
|
3596
3596
|
options = options || {};
|
|
3597
|
-
if (!options.tolerant &&
|
|
3598
|
-
target.scheme =
|
|
3599
|
-
target.userinfo =
|
|
3600
|
-
target.host =
|
|
3601
|
-
target.port =
|
|
3602
|
-
target.path = removeDotSegments(
|
|
3603
|
-
target.query =
|
|
3597
|
+
if (!options.tolerant && relative2.scheme) {
|
|
3598
|
+
target.scheme = relative2.scheme;
|
|
3599
|
+
target.userinfo = relative2.userinfo;
|
|
3600
|
+
target.host = relative2.host;
|
|
3601
|
+
target.port = relative2.port;
|
|
3602
|
+
target.path = removeDotSegments(relative2.path || "");
|
|
3603
|
+
target.query = relative2.query;
|
|
3604
3604
|
} else {
|
|
3605
|
-
if (
|
|
3606
|
-
target.userinfo =
|
|
3607
|
-
target.host =
|
|
3608
|
-
target.port =
|
|
3609
|
-
target.path = removeDotSegments(
|
|
3610
|
-
target.query =
|
|
3605
|
+
if (relative2.userinfo !== void 0 || relative2.host !== void 0 || relative2.port !== void 0) {
|
|
3606
|
+
target.userinfo = relative2.userinfo;
|
|
3607
|
+
target.host = relative2.host;
|
|
3608
|
+
target.port = relative2.port;
|
|
3609
|
+
target.path = removeDotSegments(relative2.path || "");
|
|
3610
|
+
target.query = relative2.query;
|
|
3611
3611
|
} else {
|
|
3612
|
-
if (!
|
|
3612
|
+
if (!relative2.path) {
|
|
3613
3613
|
target.path = base.path;
|
|
3614
|
-
if (
|
|
3615
|
-
target.query =
|
|
3614
|
+
if (relative2.query !== void 0) {
|
|
3615
|
+
target.query = relative2.query;
|
|
3616
3616
|
} else {
|
|
3617
3617
|
target.query = base.query;
|
|
3618
3618
|
}
|
|
3619
3619
|
} else {
|
|
3620
|
-
if (
|
|
3621
|
-
target.path = removeDotSegments(
|
|
3620
|
+
if (relative2.path[0] === "/") {
|
|
3621
|
+
target.path = removeDotSegments(relative2.path);
|
|
3622
3622
|
} else {
|
|
3623
3623
|
if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
|
|
3624
|
-
target.path = "/" +
|
|
3624
|
+
target.path = "/" + relative2.path;
|
|
3625
3625
|
} else if (!base.path) {
|
|
3626
|
-
target.path =
|
|
3626
|
+
target.path = relative2.path;
|
|
3627
3627
|
} else {
|
|
3628
|
-
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) +
|
|
3628
|
+
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative2.path;
|
|
3629
3629
|
}
|
|
3630
3630
|
target.path = removeDotSegments(target.path);
|
|
3631
3631
|
}
|
|
3632
|
-
target.query =
|
|
3632
|
+
target.query = relative2.query;
|
|
3633
3633
|
}
|
|
3634
3634
|
target.userinfo = base.userinfo;
|
|
3635
3635
|
target.host = base.host;
|
|
@@ -3637,7 +3637,7 @@ var require_fast_uri = __commonJS({
|
|
|
3637
3637
|
}
|
|
3638
3638
|
target.scheme = base.scheme;
|
|
3639
3639
|
}
|
|
3640
|
-
target.fragment =
|
|
3640
|
+
target.fragment = relative2.fragment;
|
|
3641
3641
|
return target;
|
|
3642
3642
|
}
|
|
3643
3643
|
function equal(uriA, uriB, options) {
|
|
@@ -3808,7 +3808,7 @@ var require_fast_uri = __commonJS({
|
|
|
3808
3808
|
var fastUri = {
|
|
3809
3809
|
SCHEMES,
|
|
3810
3810
|
normalize,
|
|
3811
|
-
resolve,
|
|
3811
|
+
resolve: resolve2,
|
|
3812
3812
|
resolveComponent,
|
|
3813
3813
|
equal,
|
|
3814
3814
|
serialize,
|
|
@@ -27964,7 +27964,7 @@ var Protocol = class {
|
|
|
27964
27964
|
return;
|
|
27965
27965
|
}
|
|
27966
27966
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
27967
|
-
await new Promise((
|
|
27967
|
+
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
|
|
27968
27968
|
options?.signal?.throwIfAborted();
|
|
27969
27969
|
}
|
|
27970
27970
|
} catch (error48) {
|
|
@@ -27981,7 +27981,7 @@ var Protocol = class {
|
|
|
27981
27981
|
*/
|
|
27982
27982
|
request(request, resultSchema, options) {
|
|
27983
27983
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
27984
|
-
return new Promise((
|
|
27984
|
+
return new Promise((resolve2, reject) => {
|
|
27985
27985
|
const earlyReject = (error48) => {
|
|
27986
27986
|
reject(error48);
|
|
27987
27987
|
};
|
|
@@ -28059,7 +28059,7 @@ var Protocol = class {
|
|
|
28059
28059
|
if (!parseResult.success) {
|
|
28060
28060
|
reject(parseResult.error);
|
|
28061
28061
|
} else {
|
|
28062
|
-
|
|
28062
|
+
resolve2(parseResult.data);
|
|
28063
28063
|
}
|
|
28064
28064
|
} catch (error48) {
|
|
28065
28065
|
reject(error48);
|
|
@@ -28320,12 +28320,12 @@ var Protocol = class {
|
|
|
28320
28320
|
}
|
|
28321
28321
|
} catch {
|
|
28322
28322
|
}
|
|
28323
|
-
return new Promise((
|
|
28323
|
+
return new Promise((resolve2, reject) => {
|
|
28324
28324
|
if (signal.aborted) {
|
|
28325
28325
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
28326
28326
|
return;
|
|
28327
28327
|
}
|
|
28328
|
-
const timeoutId = setTimeout(
|
|
28328
|
+
const timeoutId = setTimeout(resolve2, interval);
|
|
28329
28329
|
signal.addEventListener("abort", () => {
|
|
28330
28330
|
clearTimeout(timeoutId);
|
|
28331
28331
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -29284,7 +29284,7 @@ var McpServer = class {
|
|
|
29284
29284
|
let task = createTaskResult.task;
|
|
29285
29285
|
const pollInterval = task.pollInterval ?? 5e3;
|
|
29286
29286
|
while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
|
|
29287
|
-
await new Promise((
|
|
29287
|
+
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
|
|
29288
29288
|
const updatedTask = await extra.taskStore.getTask(taskId);
|
|
29289
29289
|
if (!updatedTask) {
|
|
29290
29290
|
throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
|
|
@@ -29927,17 +29927,24 @@ var StdioServerTransport = class {
|
|
|
29927
29927
|
this.onclose?.();
|
|
29928
29928
|
}
|
|
29929
29929
|
send(message) {
|
|
29930
|
-
return new Promise((
|
|
29930
|
+
return new Promise((resolve2) => {
|
|
29931
29931
|
const json2 = serializeMessage(message);
|
|
29932
29932
|
if (this._stdout.write(json2)) {
|
|
29933
|
-
|
|
29933
|
+
resolve2();
|
|
29934
29934
|
} else {
|
|
29935
|
-
this._stdout.once("drain",
|
|
29935
|
+
this._stdout.once("drain", resolve2);
|
|
29936
29936
|
}
|
|
29937
29937
|
});
|
|
29938
29938
|
}
|
|
29939
29939
|
};
|
|
29940
29940
|
|
|
29941
|
+
// src/lib/results-mcp.ts
|
|
29942
|
+
import { chmodSync as chmodSync2, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
29943
|
+
import { dirname as dirname2, isAbsolute, relative, resolve } from "path";
|
|
29944
|
+
import { createHash } from "crypto";
|
|
29945
|
+
import { execFile } from "child_process";
|
|
29946
|
+
import { promisify } from "util";
|
|
29947
|
+
|
|
29941
29948
|
// src/lib/results-store.ts
|
|
29942
29949
|
import { randomUUID } from "crypto";
|
|
29943
29950
|
import { existsSync as existsSync2, readFileSync as readFileSync2, renameSync, unlinkSync, writeFileSync } from "fs";
|
|
@@ -29947,6 +29954,100 @@ import { dirname, join as join2 } from "path";
|
|
|
29947
29954
|
import { readFileSync, existsSync, chmodSync, mkdirSync } from "fs";
|
|
29948
29955
|
import { join } from "path";
|
|
29949
29956
|
import { homedir } from "os";
|
|
29957
|
+
var DEFAULT_CONFIG = {
|
|
29958
|
+
runtime: "auto",
|
|
29959
|
+
// Default sandbox image: includes python3 + basic build tools (git/make/g++).
|
|
29960
|
+
// Note: pip/venv are not included by default in Debian; users may still need a
|
|
29961
|
+
// custom image for richer Python workflows.
|
|
29962
|
+
image: "docker.io/library/node:20-bookworm",
|
|
29963
|
+
session_timeout_hours: 8,
|
|
29964
|
+
filesystem: {
|
|
29965
|
+
extra_paths: [],
|
|
29966
|
+
blocked_patterns: [
|
|
29967
|
+
"**/.ssh",
|
|
29968
|
+
"**/.gnupg",
|
|
29969
|
+
"**/.aws",
|
|
29970
|
+
"**/.config/gcloud",
|
|
29971
|
+
"**/.azure",
|
|
29972
|
+
"**/.env",
|
|
29973
|
+
"**/.netrc",
|
|
29974
|
+
"**/.git-credentials",
|
|
29975
|
+
"**/*.pem",
|
|
29976
|
+
"**/*.key",
|
|
29977
|
+
"**/id_rsa*",
|
|
29978
|
+
"**/id_ed25519*",
|
|
29979
|
+
"**/credentials*",
|
|
29980
|
+
"**/secrets*"
|
|
29981
|
+
]
|
|
29982
|
+
},
|
|
29983
|
+
datasets: [],
|
|
29984
|
+
commands: {
|
|
29985
|
+
blacklist: [
|
|
29986
|
+
"mount",
|
|
29987
|
+
"umount",
|
|
29988
|
+
"mkfs",
|
|
29989
|
+
"reboot",
|
|
29990
|
+
"shutdown"
|
|
29991
|
+
]
|
|
29992
|
+
},
|
|
29993
|
+
network: {
|
|
29994
|
+
mode: "host",
|
|
29995
|
+
allowed_domains: [
|
|
29996
|
+
"api.anthropic.com",
|
|
29997
|
+
"api.openai.com",
|
|
29998
|
+
"pypi.org",
|
|
29999
|
+
"files.pythonhosted.org",
|
|
30000
|
+
"conda.anaconda.org",
|
|
30001
|
+
"registry.npmjs.org",
|
|
30002
|
+
"github.com"
|
|
30003
|
+
]
|
|
30004
|
+
},
|
|
30005
|
+
slurm: {
|
|
30006
|
+
enabled: true,
|
|
30007
|
+
poll_interval_seconds: 5,
|
|
30008
|
+
sacct_lookback_hours: 24,
|
|
30009
|
+
mcp_server: true
|
|
30010
|
+
},
|
|
30011
|
+
audit: {
|
|
30012
|
+
enabled: true,
|
|
30013
|
+
log_dir: "~/.labgate/logs"
|
|
30014
|
+
},
|
|
30015
|
+
plugins: {
|
|
30016
|
+
files: true,
|
|
30017
|
+
datasets: false,
|
|
30018
|
+
results: false,
|
|
30019
|
+
charting: true,
|
|
30020
|
+
molecular: true,
|
|
30021
|
+
genomics: true,
|
|
30022
|
+
phylogenetics: true,
|
|
30023
|
+
network: true,
|
|
30024
|
+
slurm: true,
|
|
30025
|
+
explorer: true,
|
|
30026
|
+
automation: false
|
|
30027
|
+
},
|
|
30028
|
+
automation: {
|
|
30029
|
+
model: "claude-sonnet-4-5-20250929",
|
|
30030
|
+
system_prompt: "You are an automation assistant helping guide a coding agent. When the agent asks a question or waits for input, provide a concise, helpful response based on the context.",
|
|
30031
|
+
trigger_patterns: [
|
|
30032
|
+
"\\?\\s*$",
|
|
30033
|
+
"^>\\s*$",
|
|
30034
|
+
"Do you want to proceed",
|
|
30035
|
+
"Press Enter to continue",
|
|
30036
|
+
"Y/n\\]?\\s*$"
|
|
30037
|
+
],
|
|
30038
|
+
context_lines: 100,
|
|
30039
|
+
delay_ms: 3e3,
|
|
30040
|
+
max_turns: 20,
|
|
30041
|
+
max_tokens: 1024
|
|
30042
|
+
},
|
|
30043
|
+
headless: {
|
|
30044
|
+
claude_run_with_allowed_permissions: true,
|
|
30045
|
+
continuation_in_other_terminals: true,
|
|
30046
|
+
git_integration: false
|
|
30047
|
+
}
|
|
30048
|
+
};
|
|
30049
|
+
var KNOWN_PLUGIN_IDS = Object.freeze(Object.keys(DEFAULT_CONFIG.plugins));
|
|
30050
|
+
var KNOWN_PLUGIN_ID_SET = new Set(KNOWN_PLUGIN_IDS);
|
|
29950
30051
|
var LABGATE_DIR = process.env.LABGATE_DIR ?? join(homedir(), ".labgate");
|
|
29951
30052
|
var PRIVATE_DIR_MODE = 448;
|
|
29952
30053
|
var PRIVATE_FILE_MODE = 384;
|
|
@@ -29995,6 +30096,10 @@ function sanitizeSource(value) {
|
|
|
29995
30096
|
if (!/^[a-z0-9._-]+$/.test(source)) return "unknown";
|
|
29996
30097
|
return source;
|
|
29997
30098
|
}
|
|
30099
|
+
function sanitizeBoolean(value, fallback = false) {
|
|
30100
|
+
if (typeof value === "boolean") return value;
|
|
30101
|
+
return fallback;
|
|
30102
|
+
}
|
|
29998
30103
|
function sanitizeStringArray(value, maxItemLen, maxItems) {
|
|
29999
30104
|
if (!Array.isArray(value)) return [];
|
|
30000
30105
|
const out = [];
|
|
@@ -30023,6 +30128,139 @@ function sanitizeMetadata(value) {
|
|
|
30023
30128
|
}
|
|
30024
30129
|
return Object.keys(out).length > 0 ? out : null;
|
|
30025
30130
|
}
|
|
30131
|
+
function sanitizePositiveInt(value, fallback, min, max) {
|
|
30132
|
+
if (!Number.isFinite(value)) return fallback;
|
|
30133
|
+
const intVal = Math.floor(value);
|
|
30134
|
+
return Math.min(max, Math.max(min, intVal));
|
|
30135
|
+
}
|
|
30136
|
+
function sanitizeScriptKind(value) {
|
|
30137
|
+
const normalized = sanitizeString(value, 16).toLowerCase();
|
|
30138
|
+
if (normalized === "bash") return "bash";
|
|
30139
|
+
if (normalized === "python") return "python";
|
|
30140
|
+
if (normalized === "slurm") return "slurm";
|
|
30141
|
+
return "other";
|
|
30142
|
+
}
|
|
30143
|
+
function sanitizeReproStrategy(value) {
|
|
30144
|
+
const normalized = sanitizeString(value, 16).toLowerCase();
|
|
30145
|
+
if (normalized === "precomputed") return "precomputed";
|
|
30146
|
+
return "run";
|
|
30147
|
+
}
|
|
30148
|
+
function sanitizeExecutionStatus(value) {
|
|
30149
|
+
const normalized = sanitizeString(value, 24).toLowerCase();
|
|
30150
|
+
if (normalized === "succeeded") return "succeeded";
|
|
30151
|
+
if (normalized === "failed") return "failed";
|
|
30152
|
+
if (normalized === "submitted") return "submitted";
|
|
30153
|
+
if (normalized === "precomputed") return "precomputed";
|
|
30154
|
+
return "not_run";
|
|
30155
|
+
}
|
|
30156
|
+
function sanitizeReproExecution(value) {
|
|
30157
|
+
if (value === null || value === void 0) return null;
|
|
30158
|
+
if (typeof value !== "object" || Array.isArray(value)) return null;
|
|
30159
|
+
const src = value;
|
|
30160
|
+
return {
|
|
30161
|
+
status: sanitizeExecutionStatus(src.status),
|
|
30162
|
+
command: sanitizeNullableString(src.command, 4096),
|
|
30163
|
+
ran_at: sanitizeNullableString(src.ran_at, 64),
|
|
30164
|
+
duration_ms: src.duration_ms === null || src.duration_ms === void 0 ? null : sanitizePositiveInt(src.duration_ms, 0, 0, 7 * 24 * 60 * 60 * 1e3),
|
|
30165
|
+
exit_code: src.exit_code === null || src.exit_code === void 0 ? null : sanitizePositiveInt(src.exit_code, 0, 0, 65535),
|
|
30166
|
+
signal: sanitizeNullableString(src.signal, 64),
|
|
30167
|
+
stdout_tail: sanitizeNullableString(src.stdout_tail, 2e4),
|
|
30168
|
+
stderr_tail: sanitizeNullableString(src.stderr_tail, 2e4),
|
|
30169
|
+
slurm_job_id: sanitizeNullableString(src.slurm_job_id, 128)
|
|
30170
|
+
};
|
|
30171
|
+
}
|
|
30172
|
+
function sanitizeReproducibility(value) {
|
|
30173
|
+
if (value === null || value === void 0) return null;
|
|
30174
|
+
if (typeof value !== "object" || Array.isArray(value)) return null;
|
|
30175
|
+
const src = value;
|
|
30176
|
+
return {
|
|
30177
|
+
script_path: sanitizeNullableString(src.script_path, 4096),
|
|
30178
|
+
script_kind: sanitizeScriptKind(src.script_kind),
|
|
30179
|
+
script_sha256: sanitizeNullableString(src.script_sha256, 128),
|
|
30180
|
+
input_files: sanitizeStringArray(src.input_files, 4096, 500),
|
|
30181
|
+
runtime: sanitizeNullableString(src.runtime, 2048),
|
|
30182
|
+
requirements: sanitizeStringArray(src.requirements, 512, 200),
|
|
30183
|
+
strategy: sanitizeReproStrategy(src.strategy),
|
|
30184
|
+
precomputed_reason: sanitizeNullableString(src.precomputed_reason, 4e3),
|
|
30185
|
+
execution: sanitizeReproExecution(src.execution)
|
|
30186
|
+
};
|
|
30187
|
+
}
|
|
30188
|
+
function mergeReproExecutionPatch(current, patch) {
|
|
30189
|
+
if (patch === void 0) return current;
|
|
30190
|
+
if (patch === null) return null;
|
|
30191
|
+
if (typeof patch !== "object" || Array.isArray(patch)) return current;
|
|
30192
|
+
const src = patch;
|
|
30193
|
+
const merged = { ...current || {} };
|
|
30194
|
+
const fields = [
|
|
30195
|
+
"status",
|
|
30196
|
+
"command",
|
|
30197
|
+
"ran_at",
|
|
30198
|
+
"duration_ms",
|
|
30199
|
+
"exit_code",
|
|
30200
|
+
"signal",
|
|
30201
|
+
"stdout_tail",
|
|
30202
|
+
"stderr_tail",
|
|
30203
|
+
"slurm_job_id"
|
|
30204
|
+
];
|
|
30205
|
+
for (const field of fields) {
|
|
30206
|
+
if (Object.prototype.hasOwnProperty.call(src, field)) {
|
|
30207
|
+
merged[field] = src[field];
|
|
30208
|
+
}
|
|
30209
|
+
}
|
|
30210
|
+
return sanitizeReproExecution(merged);
|
|
30211
|
+
}
|
|
30212
|
+
function mergeReproducibilityPatch(current, patch) {
|
|
30213
|
+
if (patch === void 0) return current;
|
|
30214
|
+
if (patch === null) return null;
|
|
30215
|
+
if (typeof patch !== "object" || Array.isArray(patch)) return current;
|
|
30216
|
+
const src = patch;
|
|
30217
|
+
const merged = { ...current || {} };
|
|
30218
|
+
const fields = [
|
|
30219
|
+
"script_path",
|
|
30220
|
+
"script_kind",
|
|
30221
|
+
"script_sha256",
|
|
30222
|
+
"input_files",
|
|
30223
|
+
"runtime",
|
|
30224
|
+
"requirements",
|
|
30225
|
+
"strategy",
|
|
30226
|
+
"precomputed_reason"
|
|
30227
|
+
];
|
|
30228
|
+
for (const field of fields) {
|
|
30229
|
+
if (Object.prototype.hasOwnProperty.call(src, field)) {
|
|
30230
|
+
merged[field] = src[field];
|
|
30231
|
+
}
|
|
30232
|
+
}
|
|
30233
|
+
if (Object.prototype.hasOwnProperty.call(src, "execution")) {
|
|
30234
|
+
merged.execution = mergeReproExecutionPatch(current?.execution || null, src.execution);
|
|
30235
|
+
} else if (current?.execution !== void 0) {
|
|
30236
|
+
merged.execution = current.execution;
|
|
30237
|
+
}
|
|
30238
|
+
return sanitizeReproducibility(merged);
|
|
30239
|
+
}
|
|
30240
|
+
function normalizeLineageId(value, fallback) {
|
|
30241
|
+
const lineage = sanitizeString(value, 128);
|
|
30242
|
+
return lineage || fallback;
|
|
30243
|
+
}
|
|
30244
|
+
function sanitizeDerivedIdComponent(value) {
|
|
30245
|
+
const cleaned = value.replace(/[^a-zA-Z0-9._-]/g, "-").replace(/-+/g, "-").replace(/^-/, "").replace(/-$/, "");
|
|
30246
|
+
return cleaned || "result";
|
|
30247
|
+
}
|
|
30248
|
+
function formatVersionLabel(version2) {
|
|
30249
|
+
return `v${String(version2).padStart(2, "0")}`;
|
|
30250
|
+
}
|
|
30251
|
+
function deriveVersionedResultId(lineageId, version2, existing) {
|
|
30252
|
+
const base = sanitizeDerivedIdComponent(sanitizeString(lineageId, 112) || "result");
|
|
30253
|
+
const stemRaw = `${base}-${formatVersionLabel(version2)}`;
|
|
30254
|
+
const stem = stemRaw.length > 128 ? stemRaw.slice(0, 128) : stemRaw;
|
|
30255
|
+
let candidate = stem;
|
|
30256
|
+
let suffix = 2;
|
|
30257
|
+
while (existing.has(candidate)) {
|
|
30258
|
+
const extra = `-${suffix}`;
|
|
30259
|
+
candidate = `${stem.slice(0, Math.max(1, 128 - extra.length))}${extra}`;
|
|
30260
|
+
suffix += 1;
|
|
30261
|
+
}
|
|
30262
|
+
return candidate;
|
|
30263
|
+
}
|
|
30026
30264
|
function clampInt(value, fallback, min, max) {
|
|
30027
30265
|
if (!Number.isFinite(value)) return fallback;
|
|
30028
30266
|
return Math.min(max, Math.max(min, Math.floor(value)));
|
|
@@ -30030,18 +30268,36 @@ function clampInt(value, fallback, min, max) {
|
|
|
30030
30268
|
function buildSearchText(result) {
|
|
30031
30269
|
return [
|
|
30032
30270
|
result.id,
|
|
30271
|
+
result.lineage_id,
|
|
30272
|
+
`v${result.version}`,
|
|
30033
30273
|
result.title,
|
|
30034
30274
|
result.summary,
|
|
30035
30275
|
result.details || "",
|
|
30036
30276
|
result.source,
|
|
30277
|
+
result.starred ? "starred" : "",
|
|
30278
|
+
result.previous_result_id || "",
|
|
30037
30279
|
result.session_id || "",
|
|
30038
30280
|
result.workdir || "",
|
|
30039
30281
|
result.tags.join(" "),
|
|
30040
|
-
result.artifacts.join(" ")
|
|
30282
|
+
result.artifacts.join(" "),
|
|
30283
|
+
result.reproducibility?.script_path || "",
|
|
30284
|
+
result.reproducibility?.script_kind || "",
|
|
30285
|
+
result.reproducibility?.runtime || "",
|
|
30286
|
+
result.reproducibility?.input_files.join(" ") || "",
|
|
30287
|
+
result.reproducibility?.requirements.join(" ") || "",
|
|
30288
|
+
result.reproducibility?.strategy || "",
|
|
30289
|
+
result.reproducibility?.precomputed_reason || "",
|
|
30290
|
+
result.reproducibility?.execution?.command || "",
|
|
30291
|
+
result.reproducibility?.execution?.stdout_tail || "",
|
|
30292
|
+
result.reproducibility?.execution?.stderr_tail || "",
|
|
30293
|
+
result.reproducibility?.execution?.slurm_job_id || ""
|
|
30041
30294
|
].join("\n").toLowerCase();
|
|
30042
30295
|
}
|
|
30043
|
-
function
|
|
30044
|
-
return [...items].sort((a, b) =>
|
|
30296
|
+
function sortByPriority(items) {
|
|
30297
|
+
return [...items].sort((a, b) => {
|
|
30298
|
+
if (a.starred !== b.starred) return (b.starred ? 1 : 0) - (a.starred ? 1 : 0);
|
|
30299
|
+
return b.updated_at.localeCompare(a.updated_at);
|
|
30300
|
+
});
|
|
30045
30301
|
}
|
|
30046
30302
|
function countParsedResults(raw) {
|
|
30047
30303
|
try {
|
|
@@ -30069,7 +30325,9 @@ var ResultsStore = class {
|
|
|
30069
30325
|
const search = sanitizeString(opts?.search, 256).toLowerCase();
|
|
30070
30326
|
const source = sanitizeString(opts?.source, 64).toLowerCase();
|
|
30071
30327
|
const tag = sanitizeString(opts?.tag, 64).toLowerCase();
|
|
30072
|
-
|
|
30328
|
+
const lineage = sanitizeString(opts?.lineage, 128);
|
|
30329
|
+
const starred = typeof opts?.starred === "boolean" ? opts.starred : void 0;
|
|
30330
|
+
let rows = sortByPriority(this.readFile().results);
|
|
30073
30331
|
if (search) {
|
|
30074
30332
|
rows = rows.filter((r) => buildSearchText(r).includes(search));
|
|
30075
30333
|
}
|
|
@@ -30079,6 +30337,12 @@ var ResultsStore = class {
|
|
|
30079
30337
|
if (tag) {
|
|
30080
30338
|
rows = rows.filter((r) => r.tags.some((t) => t.toLowerCase() === tag));
|
|
30081
30339
|
}
|
|
30340
|
+
if (lineage) {
|
|
30341
|
+
rows = rows.filter((r) => r.lineage_id === lineage);
|
|
30342
|
+
}
|
|
30343
|
+
if (starred !== void 0) {
|
|
30344
|
+
rows = rows.filter((r) => r.starred === starred);
|
|
30345
|
+
}
|
|
30082
30346
|
const total = rows.length;
|
|
30083
30347
|
rows = rows.slice(safeOffset, safeOffset + safeLimit);
|
|
30084
30348
|
return { results: rows, total };
|
|
@@ -30088,14 +30352,74 @@ var ResultsStore = class {
|
|
|
30088
30352
|
if (!normalized) return null;
|
|
30089
30353
|
return this.readFile().results.find((r) => r.id === normalized) || null;
|
|
30090
30354
|
}
|
|
30355
|
+
listResultVersions(idOrLineage) {
|
|
30356
|
+
const normalized = sanitizeString(idOrLineage, 128);
|
|
30357
|
+
if (!normalized) return [];
|
|
30358
|
+
const rows = this.readFile().results;
|
|
30359
|
+
const byId = rows.find((r) => r.id === normalized) || null;
|
|
30360
|
+
const lineage = byId ? byId.lineage_id : normalized;
|
|
30361
|
+
return rows.filter((r) => r.lineage_id === lineage).sort((a, b) => {
|
|
30362
|
+
if (a.version !== b.version) return b.version - a.version;
|
|
30363
|
+
return b.updated_at.localeCompare(a.updated_at);
|
|
30364
|
+
});
|
|
30365
|
+
}
|
|
30091
30366
|
createResult(input) {
|
|
30092
|
-
const title = sanitizeString(input.title, 240);
|
|
30093
|
-
if (!title) throw new Error("title is required");
|
|
30094
30367
|
const file2 = this.readFile();
|
|
30095
|
-
const
|
|
30368
|
+
const existingIds = new Set(file2.results.map((r) => r.id));
|
|
30369
|
+
let title = sanitizeString(input.title, 240);
|
|
30370
|
+
let lineageId = sanitizeString(input.lineage_id, 128);
|
|
30371
|
+
let version2 = sanitizePositiveInt(input.version, 1, 1, 1e4);
|
|
30372
|
+
let previousResultId = sanitizeNullableString(input.previous_result_id, 128);
|
|
30373
|
+
const versionOf = sanitizeString(input.version_of, 128);
|
|
30374
|
+
if (versionOf) {
|
|
30375
|
+
const base = file2.results.find((r) => r.id === versionOf);
|
|
30376
|
+
if (!base) throw new Error(`version_of result not found: ${versionOf}`);
|
|
30377
|
+
lineageId = base.lineage_id;
|
|
30378
|
+
const lineageRows = file2.results.filter((r) => r.lineage_id === lineageId);
|
|
30379
|
+
const latest = lineageRows.sort((a, b) => {
|
|
30380
|
+
if (a.version !== b.version) return b.version - a.version;
|
|
30381
|
+
return b.updated_at.localeCompare(a.updated_at);
|
|
30382
|
+
})[0] || base;
|
|
30383
|
+
version2 = latest.version + 1;
|
|
30384
|
+
previousResultId = latest.id;
|
|
30385
|
+
if (!title) title = base.title;
|
|
30386
|
+
}
|
|
30387
|
+
if (!title) throw new Error("title is required");
|
|
30388
|
+
if (lineageId) {
|
|
30389
|
+
const lineageRows = file2.results.filter((r) => r.lineage_id === lineageId);
|
|
30390
|
+
if (lineageRows.length > 0) {
|
|
30391
|
+
const latest = lineageRows.sort((a, b) => {
|
|
30392
|
+
if (a.version !== b.version) return b.version - a.version;
|
|
30393
|
+
return b.updated_at.localeCompare(a.updated_at);
|
|
30394
|
+
})[0];
|
|
30395
|
+
const hasVersionConflict = lineageRows.some((r) => r.version === version2);
|
|
30396
|
+
if ((input.version === void 0 || input.version === null) && (!versionOf || hasVersionConflict)) {
|
|
30397
|
+
version2 = (latest?.version || 0) + 1;
|
|
30398
|
+
if (!previousResultId && latest) previousResultId = latest.id;
|
|
30399
|
+
} else if (hasVersionConflict) {
|
|
30400
|
+
throw new Error(`version ${version2} already exists for lineage ${lineageId}`);
|
|
30401
|
+
}
|
|
30402
|
+
}
|
|
30403
|
+
}
|
|
30404
|
+
let id = sanitizeString(input.id, 128);
|
|
30405
|
+
if (!id) {
|
|
30406
|
+
if (versionOf) {
|
|
30407
|
+
const nextLineage = lineageId || randomUUID();
|
|
30408
|
+
id = deriveVersionedResultId(nextLineage, version2, existingIds);
|
|
30409
|
+
} else {
|
|
30410
|
+
id = randomUUID();
|
|
30411
|
+
}
|
|
30412
|
+
}
|
|
30096
30413
|
if (file2.results.some((r) => r.id === id)) {
|
|
30097
30414
|
throw new Error(`Result id already exists: ${id}`);
|
|
30098
30415
|
}
|
|
30416
|
+
const effectiveLineageId = normalizeLineageId(lineageId, id);
|
|
30417
|
+
if (file2.results.some((r) => r.lineage_id === effectiveLineageId && r.version === version2)) {
|
|
30418
|
+
throw new Error(`version ${version2} already exists for lineage ${effectiveLineageId}`);
|
|
30419
|
+
}
|
|
30420
|
+
if (previousResultId && previousResultId === id) {
|
|
30421
|
+
throw new Error("previous_result_id cannot equal result id");
|
|
30422
|
+
}
|
|
30099
30423
|
const ts = nowIso();
|
|
30100
30424
|
const created = {
|
|
30101
30425
|
id,
|
|
@@ -30103,11 +30427,16 @@ var ResultsStore = class {
|
|
|
30103
30427
|
summary: sanitizeString(input.summary, 1e3),
|
|
30104
30428
|
details: sanitizeNullableString(input.details, 2e4),
|
|
30105
30429
|
source: sanitizeSource(input.source),
|
|
30430
|
+
starred: sanitizeBoolean(input.starred, false),
|
|
30106
30431
|
session_id: sanitizeNullableString(input.session_id, 128),
|
|
30107
30432
|
workdir: sanitizeNullableString(input.workdir, 4096),
|
|
30433
|
+
lineage_id: effectiveLineageId,
|
|
30434
|
+
version: version2,
|
|
30435
|
+
previous_result_id: sanitizeNullableString(previousResultId, 128),
|
|
30108
30436
|
tags: sanitizeStringArray(input.tags, 64, 50),
|
|
30109
30437
|
artifacts: sanitizeStringArray(input.artifacts, 4096, 100),
|
|
30110
30438
|
metadata: sanitizeMetadata(input.metadata),
|
|
30439
|
+
reproducibility: sanitizeReproducibility(input.reproducibility),
|
|
30111
30440
|
created_at: ts,
|
|
30112
30441
|
updated_at: ts
|
|
30113
30442
|
};
|
|
@@ -30153,6 +30482,13 @@ var ResultsStore = class {
|
|
|
30153
30482
|
changed = true;
|
|
30154
30483
|
}
|
|
30155
30484
|
}
|
|
30485
|
+
if (patch.starred !== void 0) {
|
|
30486
|
+
const starred = sanitizeBoolean(patch.starred, false);
|
|
30487
|
+
if (starred !== next.starred) {
|
|
30488
|
+
next.starred = starred;
|
|
30489
|
+
changed = true;
|
|
30490
|
+
}
|
|
30491
|
+
}
|
|
30156
30492
|
if (patch.session_id !== void 0) {
|
|
30157
30493
|
const sessionId = sanitizeNullableString(patch.session_id, 128);
|
|
30158
30494
|
if (sessionId !== next.session_id) {
|
|
@@ -30167,6 +30503,28 @@ var ResultsStore = class {
|
|
|
30167
30503
|
changed = true;
|
|
30168
30504
|
}
|
|
30169
30505
|
}
|
|
30506
|
+
if (patch.lineage_id !== void 0) {
|
|
30507
|
+
const lineageId = sanitizeString(patch.lineage_id, 128);
|
|
30508
|
+
if (!lineageId) throw new Error("lineage_id cannot be empty");
|
|
30509
|
+
if (lineageId !== next.lineage_id) {
|
|
30510
|
+
next.lineage_id = lineageId;
|
|
30511
|
+
changed = true;
|
|
30512
|
+
}
|
|
30513
|
+
}
|
|
30514
|
+
if (patch.version !== void 0) {
|
|
30515
|
+
const version2 = sanitizePositiveInt(patch.version, next.version, 1, 1e4);
|
|
30516
|
+
if (version2 !== next.version) {
|
|
30517
|
+
next.version = version2;
|
|
30518
|
+
changed = true;
|
|
30519
|
+
}
|
|
30520
|
+
}
|
|
30521
|
+
if (patch.previous_result_id !== void 0) {
|
|
30522
|
+
const previousResultId = sanitizeNullableString(patch.previous_result_id, 128);
|
|
30523
|
+
if (previousResultId !== next.previous_result_id) {
|
|
30524
|
+
next.previous_result_id = previousResultId;
|
|
30525
|
+
changed = true;
|
|
30526
|
+
}
|
|
30527
|
+
}
|
|
30170
30528
|
if (patch.tags !== void 0) {
|
|
30171
30529
|
const tags = sanitizeStringArray(patch.tags, 64, 50);
|
|
30172
30530
|
if (JSON.stringify(tags) !== JSON.stringify(next.tags)) {
|
|
@@ -30188,7 +30546,20 @@ var ResultsStore = class {
|
|
|
30188
30546
|
changed = true;
|
|
30189
30547
|
}
|
|
30190
30548
|
}
|
|
30549
|
+
if (patch.reproducibility !== void 0) {
|
|
30550
|
+
const reproducibility = mergeReproducibilityPatch(next.reproducibility, patch.reproducibility);
|
|
30551
|
+
if (JSON.stringify(reproducibility) !== JSON.stringify(next.reproducibility)) {
|
|
30552
|
+
next.reproducibility = reproducibility;
|
|
30553
|
+
changed = true;
|
|
30554
|
+
}
|
|
30555
|
+
}
|
|
30191
30556
|
if (!changed) return current;
|
|
30557
|
+
if (next.previous_result_id && next.previous_result_id === next.id) {
|
|
30558
|
+
throw new Error("previous_result_id cannot equal result id");
|
|
30559
|
+
}
|
|
30560
|
+
if (file2.results.some((r, i) => i !== idx && r.lineage_id === next.lineage_id && r.version === next.version)) {
|
|
30561
|
+
throw new Error(`version ${next.version} already exists for lineage ${next.lineage_id}`);
|
|
30562
|
+
}
|
|
30192
30563
|
next.updated_at = nowIso();
|
|
30193
30564
|
file2.results[idx] = next;
|
|
30194
30565
|
this.writeFile(file2);
|
|
@@ -30271,11 +30642,16 @@ var ResultsStore = class {
|
|
|
30271
30642
|
summary: sanitizeString(r.summary, 1e3),
|
|
30272
30643
|
details: sanitizeNullableString(r.details, 2e4),
|
|
30273
30644
|
source: sanitizeSource(r.source),
|
|
30645
|
+
starred: sanitizeBoolean(r.starred, false),
|
|
30274
30646
|
session_id: sanitizeNullableString(r.session_id, 128),
|
|
30275
30647
|
workdir: sanitizeNullableString(r.workdir, 4096),
|
|
30648
|
+
lineage_id: normalizeLineageId(r.lineage_id, id),
|
|
30649
|
+
version: sanitizePositiveInt(r.version, 1, 1, 1e4),
|
|
30650
|
+
previous_result_id: sanitizeNullableString(r.previous_result_id, 128),
|
|
30276
30651
|
tags: sanitizeStringArray(r.tags, 64, 50),
|
|
30277
30652
|
artifacts: sanitizeStringArray(r.artifacts, 4096, 100),
|
|
30278
30653
|
metadata: sanitizeMetadata(r.metadata),
|
|
30654
|
+
reproducibility: sanitizeReproducibility(r.reproducibility),
|
|
30279
30655
|
created_at: created,
|
|
30280
30656
|
updated_at: updated
|
|
30281
30657
|
};
|
|
@@ -30322,6 +30698,9 @@ var ResultsStore = class {
|
|
|
30322
30698
|
};
|
|
30323
30699
|
|
|
30324
30700
|
// src/lib/results-mcp.ts
|
|
30701
|
+
var execFileAsync = promisify(execFile);
|
|
30702
|
+
var DEFAULT_REPRO_TIMEOUT_SECONDS = 120;
|
|
30703
|
+
var MAX_REPRO_OUTPUT_CHARS = 2e4;
|
|
30325
30704
|
function clampInt2(value, fallback, min, max) {
|
|
30326
30705
|
if (!Number.isFinite(value)) return fallback;
|
|
30327
30706
|
return Math.min(max, Math.max(min, Math.floor(value)));
|
|
@@ -30361,6 +30740,193 @@ function parseDbPathFromArgs(args) {
|
|
|
30361
30740
|
}
|
|
30362
30741
|
return dbPath;
|
|
30363
30742
|
}
|
|
30743
|
+
function trimTail(value, maxLen = MAX_REPRO_OUTPUT_CHARS) {
|
|
30744
|
+
let text = null;
|
|
30745
|
+
if (typeof value === "string") {
|
|
30746
|
+
text = value;
|
|
30747
|
+
} else if (Buffer.isBuffer(value)) {
|
|
30748
|
+
text = value.toString("utf-8");
|
|
30749
|
+
} else if (value !== void 0 && value !== null) {
|
|
30750
|
+
text = String(value);
|
|
30751
|
+
}
|
|
30752
|
+
if (text === null) return null;
|
|
30753
|
+
if (text.length <= maxLen) return text;
|
|
30754
|
+
return text.slice(text.length - maxLen);
|
|
30755
|
+
}
|
|
30756
|
+
function isPathInside(rootPath, targetPath) {
|
|
30757
|
+
const rel = relative(rootPath, targetPath);
|
|
30758
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
30759
|
+
}
|
|
30760
|
+
function buildAllowedRoots(input) {
|
|
30761
|
+
const roots = [
|
|
30762
|
+
input.executionWorkdir ? resolve(input.executionWorkdir) : "",
|
|
30763
|
+
input.workdir ? resolve(input.workdir) : "",
|
|
30764
|
+
resolve(process.cwd())
|
|
30765
|
+
].filter(Boolean);
|
|
30766
|
+
return [...new Set(roots)];
|
|
30767
|
+
}
|
|
30768
|
+
function ensurePathAllowed(path, allowedRoots) {
|
|
30769
|
+
const allowed = allowedRoots.some((root) => isPathInside(root, path));
|
|
30770
|
+
if (allowed) return;
|
|
30771
|
+
throw new Error(
|
|
30772
|
+
`Script path "${path}" is outside allowed roots: ${allowedRoots.join(", ")}. Set workdir/working_directory or use a path inside your workspace.`
|
|
30773
|
+
);
|
|
30774
|
+
}
|
|
30775
|
+
function ensureScriptOnDisk(scriptPath, allowedRoots, scriptContent) {
|
|
30776
|
+
const resolvedPath = resolve(String(scriptPath || "").trim());
|
|
30777
|
+
if (!resolvedPath) {
|
|
30778
|
+
throw new Error("reproducibility.script_path is required.");
|
|
30779
|
+
}
|
|
30780
|
+
ensurePathAllowed(resolvedPath, allowedRoots);
|
|
30781
|
+
if (typeof scriptContent === "string") {
|
|
30782
|
+
mkdirSync2(dirname2(resolvedPath), { recursive: true });
|
|
30783
|
+
const normalized = scriptContent.endsWith("\n") ? scriptContent : `${scriptContent}
|
|
30784
|
+
`;
|
|
30785
|
+
writeFileSync2(resolvedPath, normalized, "utf-8");
|
|
30786
|
+
try {
|
|
30787
|
+
chmodSync2(resolvedPath, 493);
|
|
30788
|
+
} catch {
|
|
30789
|
+
}
|
|
30790
|
+
}
|
|
30791
|
+
if (!existsSync3(resolvedPath)) {
|
|
30792
|
+
throw new Error(`Reproducibility script not found: ${resolvedPath}`);
|
|
30793
|
+
}
|
|
30794
|
+
const raw = readFileSync3(resolvedPath);
|
|
30795
|
+
const scriptSha256 = createHash("sha256").update(raw).digest("hex");
|
|
30796
|
+
return { scriptPath: resolvedPath, scriptSha256 };
|
|
30797
|
+
}
|
|
30798
|
+
function parseSlurmJobId(text) {
|
|
30799
|
+
const match = text.match(/Submitted batch job\s+([0-9]+)/i);
|
|
30800
|
+
return match?.[1] || null;
|
|
30801
|
+
}
|
|
30802
|
+
function makeDefaultExecution(strategy, runNow) {
|
|
30803
|
+
if (String(strategy || "").toLowerCase() === "precomputed") {
|
|
30804
|
+
return { status: "precomputed" };
|
|
30805
|
+
}
|
|
30806
|
+
if (runNow === false) return { status: "not_run" };
|
|
30807
|
+
return { status: "not_run" };
|
|
30808
|
+
}
|
|
30809
|
+
async function executeLocalReproScript(input) {
|
|
30810
|
+
const cwd = input.workdir ? resolve(input.workdir) : dirname2(input.scriptPath);
|
|
30811
|
+
const timeoutMs = clampInt2(input.timeoutSeconds, DEFAULT_REPRO_TIMEOUT_SECONDS, 10, 86400) * 1e3;
|
|
30812
|
+
const start = Date.now();
|
|
30813
|
+
const scriptKind = String(input.scriptKind || "").toLowerCase();
|
|
30814
|
+
const useCustomCommand = typeof input.commandOverride === "string" && input.commandOverride.trim().length > 0;
|
|
30815
|
+
const displayCommand = useCustomCommand ? input.commandOverride.trim() : scriptKind === "python" ? `python3 ${input.scriptPath}` : `bash ${input.scriptPath}`;
|
|
30816
|
+
try {
|
|
30817
|
+
if (useCustomCommand) {
|
|
30818
|
+
const ok2 = await execFileAsync("bash", ["-lc", input.commandOverride.trim()], {
|
|
30819
|
+
cwd,
|
|
30820
|
+
timeout: timeoutMs,
|
|
30821
|
+
maxBuffer: 5 * 1024 * 1024
|
|
30822
|
+
});
|
|
30823
|
+
return {
|
|
30824
|
+
status: "succeeded",
|
|
30825
|
+
command: displayCommand,
|
|
30826
|
+
ran_at: new Date(start).toISOString(),
|
|
30827
|
+
duration_ms: Date.now() - start,
|
|
30828
|
+
exit_code: 0,
|
|
30829
|
+
signal: null,
|
|
30830
|
+
stdout_tail: trimTail(ok2.stdout),
|
|
30831
|
+
stderr_tail: trimTail(ok2.stderr),
|
|
30832
|
+
slurm_job_id: null
|
|
30833
|
+
};
|
|
30834
|
+
}
|
|
30835
|
+
if (scriptKind === "python") {
|
|
30836
|
+
const ok2 = await execFileAsync("python3", [input.scriptPath], {
|
|
30837
|
+
cwd,
|
|
30838
|
+
timeout: timeoutMs,
|
|
30839
|
+
maxBuffer: 5 * 1024 * 1024
|
|
30840
|
+
});
|
|
30841
|
+
return {
|
|
30842
|
+
status: "succeeded",
|
|
30843
|
+
command: displayCommand,
|
|
30844
|
+
ran_at: new Date(start).toISOString(),
|
|
30845
|
+
duration_ms: Date.now() - start,
|
|
30846
|
+
exit_code: 0,
|
|
30847
|
+
signal: null,
|
|
30848
|
+
stdout_tail: trimTail(ok2.stdout),
|
|
30849
|
+
stderr_tail: trimTail(ok2.stderr),
|
|
30850
|
+
slurm_job_id: null
|
|
30851
|
+
};
|
|
30852
|
+
}
|
|
30853
|
+
const ok = await execFileAsync("bash", [input.scriptPath], {
|
|
30854
|
+
cwd,
|
|
30855
|
+
timeout: timeoutMs,
|
|
30856
|
+
maxBuffer: 5 * 1024 * 1024
|
|
30857
|
+
});
|
|
30858
|
+
return {
|
|
30859
|
+
status: "succeeded",
|
|
30860
|
+
command: displayCommand,
|
|
30861
|
+
ran_at: new Date(start).toISOString(),
|
|
30862
|
+
duration_ms: Date.now() - start,
|
|
30863
|
+
exit_code: 0,
|
|
30864
|
+
signal: null,
|
|
30865
|
+
stdout_tail: trimTail(ok.stdout),
|
|
30866
|
+
stderr_tail: trimTail(ok.stderr),
|
|
30867
|
+
slurm_job_id: null
|
|
30868
|
+
};
|
|
30869
|
+
} catch (err) {
|
|
30870
|
+
const code = typeof err?.code === "number" ? err.code : null;
|
|
30871
|
+
const signal = typeof err?.signal === "string" ? err.signal : null;
|
|
30872
|
+
return {
|
|
30873
|
+
status: "failed",
|
|
30874
|
+
command: displayCommand,
|
|
30875
|
+
ran_at: new Date(start).toISOString(),
|
|
30876
|
+
duration_ms: Date.now() - start,
|
|
30877
|
+
exit_code: code,
|
|
30878
|
+
signal,
|
|
30879
|
+
stdout_tail: trimTail(err?.stdout),
|
|
30880
|
+
stderr_tail: trimTail(err?.stderr || err?.message),
|
|
30881
|
+
slurm_job_id: null
|
|
30882
|
+
};
|
|
30883
|
+
}
|
|
30884
|
+
}
|
|
30885
|
+
async function executeSlurmReproScript(input) {
|
|
30886
|
+
const cwd = input.workdir ? resolve(input.workdir) : dirname2(input.scriptPath);
|
|
30887
|
+
const timeoutMs = clampInt2(input.timeoutSeconds, DEFAULT_REPRO_TIMEOUT_SECONDS, 10, 86400) * 1e3;
|
|
30888
|
+
const start = Date.now();
|
|
30889
|
+
const useCustomCommand = typeof input.commandOverride === "string" && input.commandOverride.trim().length > 0;
|
|
30890
|
+
const displayCommand = useCustomCommand ? input.commandOverride.trim() : `sbatch ${input.scriptPath}`;
|
|
30891
|
+
try {
|
|
30892
|
+
const ok = useCustomCommand ? await execFileAsync("bash", ["-lc", input.commandOverride.trim()], {
|
|
30893
|
+
cwd,
|
|
30894
|
+
timeout: timeoutMs,
|
|
30895
|
+
maxBuffer: 5 * 1024 * 1024
|
|
30896
|
+
}) : await execFileAsync("sbatch", [input.scriptPath], {
|
|
30897
|
+
cwd,
|
|
30898
|
+
timeout: timeoutMs,
|
|
30899
|
+
maxBuffer: 5 * 1024 * 1024
|
|
30900
|
+
});
|
|
30901
|
+
const combined = `${ok.stdout || ""}
|
|
30902
|
+
${ok.stderr || ""}`;
|
|
30903
|
+
return {
|
|
30904
|
+
status: "submitted",
|
|
30905
|
+
command: displayCommand,
|
|
30906
|
+
ran_at: new Date(start).toISOString(),
|
|
30907
|
+
duration_ms: Date.now() - start,
|
|
30908
|
+
exit_code: 0,
|
|
30909
|
+
signal: null,
|
|
30910
|
+
stdout_tail: trimTail(ok.stdout),
|
|
30911
|
+
stderr_tail: trimTail(ok.stderr),
|
|
30912
|
+
slurm_job_id: parseSlurmJobId(combined)
|
|
30913
|
+
};
|
|
30914
|
+
} catch (err) {
|
|
30915
|
+
const code = typeof err?.code === "number" ? err.code : null;
|
|
30916
|
+
const signal = typeof err?.signal === "string" ? err.signal : null;
|
|
30917
|
+
return {
|
|
30918
|
+
status: "failed",
|
|
30919
|
+
command: displayCommand,
|
|
30920
|
+
ran_at: new Date(start).toISOString(),
|
|
30921
|
+
duration_ms: Date.now() - start,
|
|
30922
|
+
exit_code: code,
|
|
30923
|
+
signal,
|
|
30924
|
+
stdout_tail: trimTail(err?.stdout),
|
|
30925
|
+
stderr_tail: trimTail(err?.stderr || err?.message),
|
|
30926
|
+
slurm_job_id: null
|
|
30927
|
+
};
|
|
30928
|
+
}
|
|
30929
|
+
}
|
|
30364
30930
|
async function main(args = process.argv.slice(2)) {
|
|
30365
30931
|
const dbPath = parseDbPathFromArgs(args);
|
|
30366
30932
|
const store = dbPath ? new ResultsStore(dbPath) : new ResultsStore();
|
|
@@ -30376,6 +30942,36 @@ async function main(args = process.argv.slice(2)) {
|
|
|
30376
30942
|
instructions: "LabGate results registry. Use these tools to record important findings, retrieve previously recorded outcomes, and keep results structured for later review."
|
|
30377
30943
|
}
|
|
30378
30944
|
);
|
|
30945
|
+
const reproducibilityExecutionSchema = external_exports3.object({
|
|
30946
|
+
status: external_exports3.enum(["not_run", "succeeded", "failed", "submitted", "precomputed"]).optional(),
|
|
30947
|
+
command: external_exports3.string().nullable().optional(),
|
|
30948
|
+
ran_at: external_exports3.string().nullable().optional(),
|
|
30949
|
+
duration_ms: external_exports3.number().int().min(0).nullable().optional(),
|
|
30950
|
+
exit_code: external_exports3.number().int().nullable().optional(),
|
|
30951
|
+
signal: external_exports3.string().nullable().optional(),
|
|
30952
|
+
stdout_tail: external_exports3.string().nullable().optional(),
|
|
30953
|
+
stderr_tail: external_exports3.string().nullable().optional(),
|
|
30954
|
+
slurm_job_id: external_exports3.string().nullable().optional()
|
|
30955
|
+
});
|
|
30956
|
+
const reproducibilitySchema = external_exports3.object({
|
|
30957
|
+
script_path: external_exports3.string().nullable().optional(),
|
|
30958
|
+
script_kind: external_exports3.enum(["bash", "python", "slurm", "other"]).optional(),
|
|
30959
|
+
script_sha256: external_exports3.string().nullable().optional(),
|
|
30960
|
+
input_files: external_exports3.array(external_exports3.string()).optional(),
|
|
30961
|
+
runtime: external_exports3.string().nullable().optional(),
|
|
30962
|
+
requirements: external_exports3.array(external_exports3.string()).optional(),
|
|
30963
|
+
strategy: external_exports3.enum(["run", "precomputed"]).optional(),
|
|
30964
|
+
precomputed_reason: external_exports3.string().nullable().optional(),
|
|
30965
|
+
execution: reproducibilityExecutionSchema.nullable().optional()
|
|
30966
|
+
});
|
|
30967
|
+
const reproducibilityRegistrationSchema = reproducibilitySchema.extend({
|
|
30968
|
+
script_path: external_exports3.string().describe("Path to the script that reproduces this result"),
|
|
30969
|
+
script_content: external_exports3.string().optional().describe("Optional script body to write at script_path"),
|
|
30970
|
+
run_now: external_exports3.boolean().optional().default(true).describe("Execute or submit the script immediately"),
|
|
30971
|
+
timeout_seconds: external_exports3.number().int().min(10).max(86400).optional().default(DEFAULT_REPRO_TIMEOUT_SECONDS),
|
|
30972
|
+
command_override: external_exports3.string().optional().describe("Optional shell command used instead of the default runner"),
|
|
30973
|
+
working_directory: external_exports3.string().nullable().optional().describe("Working directory used for execution/submission")
|
|
30974
|
+
});
|
|
30379
30975
|
server.registerTool(
|
|
30380
30976
|
"list_results",
|
|
30381
30977
|
{
|
|
@@ -30385,17 +30981,21 @@ async function main(args = process.argv.slice(2)) {
|
|
|
30385
30981
|
search: external_exports3.string().optional().describe("Search text across title, summary, details, tags, and metadata"),
|
|
30386
30982
|
tag: external_exports3.string().optional().describe("Filter by exact tag"),
|
|
30387
30983
|
source: external_exports3.string().optional().describe("Filter by source (e.g. claude, codex)"),
|
|
30984
|
+
lineage: external_exports3.string().optional().describe("Filter by lineage id (all versions of a result thread)"),
|
|
30985
|
+
starred: external_exports3.boolean().optional().describe("Filter by starred status"),
|
|
30388
30986
|
limit: external_exports3.number().int().min(1).max(500).optional().default(50).describe("Maximum results to return"),
|
|
30389
30987
|
offset: external_exports3.number().int().min(0).optional().default(0).describe("Offset for pagination")
|
|
30390
30988
|
}
|
|
30391
30989
|
},
|
|
30392
|
-
async ({ search, tag, source, limit, offset }) => {
|
|
30990
|
+
async ({ search, tag, source, lineage, starred, limit, offset }) => {
|
|
30393
30991
|
const safeLimit = clampInt2(limit, 50, 1, 500);
|
|
30394
30992
|
const safeOffset = clampInt2(offset, 0, 0, 1e5);
|
|
30395
30993
|
const listed = store.listResults({
|
|
30396
30994
|
search: search || void 0,
|
|
30397
30995
|
tag: tag || void 0,
|
|
30398
30996
|
source: source || void 0,
|
|
30997
|
+
lineage: lineage || void 0,
|
|
30998
|
+
starred: typeof starred === "boolean" ? starred : void 0,
|
|
30399
30999
|
limit: safeLimit,
|
|
30400
31000
|
offset: safeOffset
|
|
30401
31001
|
});
|
|
@@ -30414,29 +31014,60 @@ async function main(args = process.argv.slice(2)) {
|
|
|
30414
31014
|
title: "Register Result",
|
|
30415
31015
|
description: "Register a new structured result (finding/outcome) so it can be reviewed later in the LabGate UI.",
|
|
30416
31016
|
inputSchema: {
|
|
31017
|
+
id: external_exports3.string().optional().describe("Optional explicit id (normally auto-generated)"),
|
|
30417
31018
|
title: external_exports3.string().describe("Short title of the result"),
|
|
30418
31019
|
summary: external_exports3.string().optional().describe("One-line summary"),
|
|
30419
31020
|
details: external_exports3.string().nullable().optional().describe("Long-form details"),
|
|
30420
31021
|
source: external_exports3.string().optional().default("claude").describe("Result source label, e.g. claude or codex"),
|
|
31022
|
+
starred: external_exports3.boolean().optional().default(false).describe("Whether the result is starred for quick access"),
|
|
30421
31023
|
session_id: external_exports3.string().nullable().optional().describe("Optional LabGate session id"),
|
|
30422
31024
|
workdir: external_exports3.string().nullable().optional().describe("Optional work directory path"),
|
|
31025
|
+
lineage_id: external_exports3.string().optional().describe("Optional lineage id used to group versioned results"),
|
|
31026
|
+
version: external_exports3.number().int().min(1).optional().describe("Explicit version number within the lineage"),
|
|
31027
|
+
previous_result_id: external_exports3.string().nullable().optional().describe("Optional previous version id in this lineage"),
|
|
31028
|
+
version_of: external_exports3.string().optional().describe("Create a next version based on an existing result id"),
|
|
30423
31029
|
tags: external_exports3.array(external_exports3.string()).optional().describe("Tags for filtering"),
|
|
30424
31030
|
artifacts: external_exports3.array(external_exports3.string()).optional().describe("Related file paths or artifact references"),
|
|
30425
|
-
metadata: external_exports3.record(external_exports3.string(), external_exports3.unknown()).nullable().optional().describe("Optional flat metadata map")
|
|
31031
|
+
metadata: external_exports3.record(external_exports3.string(), external_exports3.unknown()).nullable().optional().describe("Optional flat metadata map"),
|
|
31032
|
+
reproducibility: reproducibilitySchema.nullable().optional().describe("Reproducibility specification for this result")
|
|
30426
31033
|
}
|
|
30427
31034
|
},
|
|
30428
|
-
async ({
|
|
31035
|
+
async ({
|
|
31036
|
+
id,
|
|
31037
|
+
title,
|
|
31038
|
+
summary,
|
|
31039
|
+
details,
|
|
31040
|
+
source,
|
|
31041
|
+
starred,
|
|
31042
|
+
session_id,
|
|
31043
|
+
workdir,
|
|
31044
|
+
lineage_id,
|
|
31045
|
+
version: version2,
|
|
31046
|
+
previous_result_id,
|
|
31047
|
+
version_of,
|
|
31048
|
+
tags,
|
|
31049
|
+
artifacts,
|
|
31050
|
+
metadata,
|
|
31051
|
+
reproducibility
|
|
31052
|
+
}) => {
|
|
30429
31053
|
try {
|
|
30430
31054
|
const created = store.createResult({
|
|
31055
|
+
id,
|
|
30431
31056
|
title,
|
|
30432
31057
|
summary,
|
|
30433
31058
|
details: details === void 0 ? null : details,
|
|
30434
31059
|
source,
|
|
31060
|
+
starred,
|
|
30435
31061
|
session_id,
|
|
30436
31062
|
workdir,
|
|
31063
|
+
lineage_id,
|
|
31064
|
+
version: version2,
|
|
31065
|
+
previous_result_id,
|
|
31066
|
+
version_of,
|
|
30437
31067
|
tags,
|
|
30438
31068
|
artifacts,
|
|
30439
|
-
metadata
|
|
31069
|
+
metadata,
|
|
31070
|
+
reproducibility
|
|
30440
31071
|
});
|
|
30441
31072
|
return asText(`Result "${created.title}" recorded with id ${created.id}.`);
|
|
30442
31073
|
} catch (err) {
|
|
@@ -30444,6 +31075,125 @@ async function main(args = process.argv.slice(2)) {
|
|
|
30444
31075
|
}
|
|
30445
31076
|
}
|
|
30446
31077
|
);
|
|
31078
|
+
server.registerTool(
|
|
31079
|
+
"register_reproducible_result",
|
|
31080
|
+
{
|
|
31081
|
+
title: "Register Reproducible Result",
|
|
31082
|
+
description: "Register a result with reproducibility guarantees: script path/hash, explicit inputs/requirements, and optional immediate execution (or SLURM submission).",
|
|
31083
|
+
inputSchema: {
|
|
31084
|
+
id: external_exports3.string().optional().describe("Optional explicit id (normally auto-generated)"),
|
|
31085
|
+
title: external_exports3.string().describe("Short title of the result"),
|
|
31086
|
+
summary: external_exports3.string().optional().describe("One-line summary"),
|
|
31087
|
+
details: external_exports3.string().nullable().optional().describe("Long-form details"),
|
|
31088
|
+
source: external_exports3.string().optional().default("claude").describe("Result source label, e.g. claude or codex"),
|
|
31089
|
+
starred: external_exports3.boolean().optional().default(false).describe("Whether the result is starred for quick access"),
|
|
31090
|
+
session_id: external_exports3.string().nullable().optional().describe("Optional LabGate session id"),
|
|
31091
|
+
workdir: external_exports3.string().nullable().optional().describe("Optional result workdir (also default run cwd)"),
|
|
31092
|
+
lineage_id: external_exports3.string().optional().describe("Optional lineage id used to group versioned results"),
|
|
31093
|
+
version: external_exports3.number().int().min(1).optional().describe("Explicit version number within the lineage"),
|
|
31094
|
+
previous_result_id: external_exports3.string().nullable().optional().describe("Optional previous version id in this lineage"),
|
|
31095
|
+
version_of: external_exports3.string().optional().describe("Create a next version based on an existing result id"),
|
|
31096
|
+
tags: external_exports3.array(external_exports3.string()).optional().describe("Tags for filtering"),
|
|
31097
|
+
artifacts: external_exports3.array(external_exports3.string()).optional().describe("Related file paths or artifact references"),
|
|
31098
|
+
metadata: external_exports3.record(external_exports3.string(), external_exports3.unknown()).nullable().optional().describe("Optional flat metadata map"),
|
|
31099
|
+
reproducibility: reproducibilityRegistrationSchema
|
|
31100
|
+
}
|
|
31101
|
+
},
|
|
31102
|
+
async ({
|
|
31103
|
+
id,
|
|
31104
|
+
title,
|
|
31105
|
+
summary,
|
|
31106
|
+
details,
|
|
31107
|
+
source,
|
|
31108
|
+
starred,
|
|
31109
|
+
session_id,
|
|
31110
|
+
workdir,
|
|
31111
|
+
lineage_id,
|
|
31112
|
+
version: version2,
|
|
31113
|
+
previous_result_id,
|
|
31114
|
+
version_of,
|
|
31115
|
+
tags,
|
|
31116
|
+
artifacts,
|
|
31117
|
+
metadata,
|
|
31118
|
+
reproducibility
|
|
31119
|
+
}) => {
|
|
31120
|
+
try {
|
|
31121
|
+
const repro = reproducibility || {};
|
|
31122
|
+
const executionWorkdir = typeof repro.working_directory === "string" && repro.working_directory.trim() ? String(repro.working_directory) : null;
|
|
31123
|
+
const allowedRoots = buildAllowedRoots({
|
|
31124
|
+
workdir: typeof workdir === "string" ? workdir : null,
|
|
31125
|
+
executionWorkdir
|
|
31126
|
+
});
|
|
31127
|
+
const scriptSpec = ensureScriptOnDisk(
|
|
31128
|
+
String(repro.script_path || ""),
|
|
31129
|
+
allowedRoots,
|
|
31130
|
+
typeof repro.script_content === "string" ? repro.script_content : void 0
|
|
31131
|
+
);
|
|
31132
|
+
const strategy = String(repro.strategy || "run").toLowerCase() === "precomputed" ? "precomputed" : "run";
|
|
31133
|
+
const runNow = repro.run_now === void 0 ? true : !!repro.run_now;
|
|
31134
|
+
const scriptKind = String(repro.script_kind || "bash").toLowerCase();
|
|
31135
|
+
const timeoutSeconds = clampInt2(
|
|
31136
|
+
typeof repro.timeout_seconds === "number" ? repro.timeout_seconds : void 0,
|
|
31137
|
+
DEFAULT_REPRO_TIMEOUT_SECONDS,
|
|
31138
|
+
10,
|
|
31139
|
+
86400
|
|
31140
|
+
);
|
|
31141
|
+
const commandOverride = typeof repro.command_override === "string" ? repro.command_override : void 0;
|
|
31142
|
+
const runCwd = executionWorkdir && executionWorkdir.trim() || typeof workdir === "string" && workdir.trim() || null;
|
|
31143
|
+
let execution = makeDefaultExecution(strategy, runNow);
|
|
31144
|
+
if (strategy !== "precomputed" && runNow) {
|
|
31145
|
+
execution = scriptKind === "slurm" ? await executeSlurmReproScript({
|
|
31146
|
+
scriptPath: scriptSpec.scriptPath,
|
|
31147
|
+
timeoutSeconds,
|
|
31148
|
+
workdir: runCwd,
|
|
31149
|
+
commandOverride
|
|
31150
|
+
}) : await executeLocalReproScript({
|
|
31151
|
+
scriptPath: scriptSpec.scriptPath,
|
|
31152
|
+
scriptKind,
|
|
31153
|
+
timeoutSeconds,
|
|
31154
|
+
workdir: runCwd,
|
|
31155
|
+
commandOverride
|
|
31156
|
+
});
|
|
31157
|
+
}
|
|
31158
|
+
const reproducibilityPayload = {
|
|
31159
|
+
script_path: scriptSpec.scriptPath,
|
|
31160
|
+
script_kind: scriptKind,
|
|
31161
|
+
script_sha256: scriptSpec.scriptSha256,
|
|
31162
|
+
input_files: Array.isArray(repro.input_files) ? repro.input_files : void 0,
|
|
31163
|
+
runtime: typeof repro.runtime === "string" ? repro.runtime : null,
|
|
31164
|
+
requirements: Array.isArray(repro.requirements) ? repro.requirements : void 0,
|
|
31165
|
+
strategy,
|
|
31166
|
+
precomputed_reason: typeof repro.precomputed_reason === "string" ? repro.precomputed_reason : null,
|
|
31167
|
+
execution
|
|
31168
|
+
};
|
|
31169
|
+
const created = store.createResult({
|
|
31170
|
+
id,
|
|
31171
|
+
title,
|
|
31172
|
+
summary,
|
|
31173
|
+
details: details === void 0 ? null : details,
|
|
31174
|
+
source,
|
|
31175
|
+
starred,
|
|
31176
|
+
session_id,
|
|
31177
|
+
workdir,
|
|
31178
|
+
lineage_id,
|
|
31179
|
+
version: version2,
|
|
31180
|
+
previous_result_id,
|
|
31181
|
+
version_of,
|
|
31182
|
+
tags,
|
|
31183
|
+
artifacts,
|
|
31184
|
+
metadata,
|
|
31185
|
+
reproducibility: reproducibilityPayload
|
|
31186
|
+
});
|
|
31187
|
+
return asJson({
|
|
31188
|
+
ok: true,
|
|
31189
|
+
result: created,
|
|
31190
|
+
reproducibility_execution: created.reproducibility?.execution || null
|
|
31191
|
+
});
|
|
31192
|
+
} catch (err) {
|
|
31193
|
+
return asError(`Failed to register reproducible result: ${err.message}`);
|
|
31194
|
+
}
|
|
31195
|
+
}
|
|
31196
|
+
);
|
|
30447
31197
|
server.registerTool(
|
|
30448
31198
|
"get_result",
|
|
30449
31199
|
{
|
|
@@ -30461,6 +31211,27 @@ async function main(args = process.argv.slice(2)) {
|
|
|
30461
31211
|
return asJson(result);
|
|
30462
31212
|
}
|
|
30463
31213
|
);
|
|
31214
|
+
server.registerTool(
|
|
31215
|
+
"list_result_versions",
|
|
31216
|
+
{
|
|
31217
|
+
title: "List Result Versions",
|
|
31218
|
+
description: "List all versions in a result lineage. Accepts either a result id or a lineage id.",
|
|
31219
|
+
inputSchema: {
|
|
31220
|
+
id_or_lineage: external_exports3.string().describe("Result id or lineage id")
|
|
31221
|
+
}
|
|
31222
|
+
},
|
|
31223
|
+
async ({ id_or_lineage }) => {
|
|
31224
|
+
const versions = store.listResultVersions(id_or_lineage);
|
|
31225
|
+
if (versions.length === 0) {
|
|
31226
|
+
return asError(`No versions found for "${id_or_lineage}".`);
|
|
31227
|
+
}
|
|
31228
|
+
return asJson({
|
|
31229
|
+
lineage_id: versions[0].lineage_id,
|
|
31230
|
+
count: versions.length,
|
|
31231
|
+
versions
|
|
31232
|
+
});
|
|
31233
|
+
}
|
|
31234
|
+
);
|
|
30464
31235
|
server.registerTool(
|
|
30465
31236
|
"update_result",
|
|
30466
31237
|
{
|
|
@@ -30472,15 +31243,36 @@ async function main(args = process.argv.slice(2)) {
|
|
|
30472
31243
|
summary: external_exports3.string().optional().describe("New summary"),
|
|
30473
31244
|
details: external_exports3.string().nullable().optional().describe("New details, null clears"),
|
|
30474
31245
|
source: external_exports3.string().optional().describe("New source"),
|
|
31246
|
+
starred: external_exports3.boolean().optional().describe("Set whether this result is starred"),
|
|
30475
31247
|
session_id: external_exports3.string().nullable().optional().describe("New session id, null clears"),
|
|
30476
31248
|
workdir: external_exports3.string().nullable().optional().describe("New workdir, null clears"),
|
|
31249
|
+
lineage_id: external_exports3.string().optional().describe("Set lineage id"),
|
|
31250
|
+
version: external_exports3.number().int().min(1).optional().describe("Set version number"),
|
|
31251
|
+
previous_result_id: external_exports3.string().nullable().optional().describe("Set previous version id"),
|
|
30477
31252
|
tags: external_exports3.array(external_exports3.string()).optional().describe("Replace tags"),
|
|
30478
31253
|
artifacts: external_exports3.array(external_exports3.string()).optional().describe("Replace artifacts"),
|
|
30479
|
-
metadata: external_exports3.record(external_exports3.string(), external_exports3.unknown()).nullable().optional().describe("Replace metadata, null clears")
|
|
31254
|
+
metadata: external_exports3.record(external_exports3.string(), external_exports3.unknown()).nullable().optional().describe("Replace metadata, null clears"),
|
|
31255
|
+
reproducibility: reproducibilitySchema.nullable().optional().describe("Replace reproducibility metadata, null clears")
|
|
30480
31256
|
}
|
|
30481
31257
|
},
|
|
30482
|
-
async ({
|
|
30483
|
-
|
|
31258
|
+
async ({
|
|
31259
|
+
id,
|
|
31260
|
+
title,
|
|
31261
|
+
summary,
|
|
31262
|
+
details,
|
|
31263
|
+
source,
|
|
31264
|
+
starred,
|
|
31265
|
+
session_id,
|
|
31266
|
+
workdir,
|
|
31267
|
+
lineage_id,
|
|
31268
|
+
version: version2,
|
|
31269
|
+
previous_result_id,
|
|
31270
|
+
tags,
|
|
31271
|
+
artifacts,
|
|
31272
|
+
metadata,
|
|
31273
|
+
reproducibility
|
|
31274
|
+
}) => {
|
|
31275
|
+
const hasPatch = title !== void 0 || summary !== void 0 || details !== void 0 || source !== void 0 || starred !== void 0 || session_id !== void 0 || workdir !== void 0 || lineage_id !== void 0 || version2 !== void 0 || previous_result_id !== void 0 || tags !== void 0 || artifacts !== void 0 || metadata !== void 0 || reproducibility !== void 0;
|
|
30484
31276
|
if (!hasPatch) {
|
|
30485
31277
|
return asError("No update fields provided.");
|
|
30486
31278
|
}
|
|
@@ -30490,11 +31282,16 @@ async function main(args = process.argv.slice(2)) {
|
|
|
30490
31282
|
summary,
|
|
30491
31283
|
details,
|
|
30492
31284
|
source,
|
|
31285
|
+
starred,
|
|
30493
31286
|
session_id,
|
|
30494
31287
|
workdir,
|
|
31288
|
+
lineage_id,
|
|
31289
|
+
version: version2,
|
|
31290
|
+
previous_result_id,
|
|
30495
31291
|
tags,
|
|
30496
31292
|
artifacts,
|
|
30497
|
-
metadata
|
|
31293
|
+
metadata,
|
|
31294
|
+
reproducibility
|
|
30498
31295
|
});
|
|
30499
31296
|
if (!updated) {
|
|
30500
31297
|
return asError(`Result "${id}" not found.`);
|