labgate 0.5.21 → 0.5.23
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 +6 -4
- package/dist/cli.js +12 -16
- package/dist/cli.js.map +1 -1
- package/dist/lib/config.js +1 -1
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/container.js +9 -3
- package/dist/lib/container.js.map +1 -1
- package/dist/lib/init.js +4 -4
- package/dist/lib/init.js.map +1 -1
- package/dist/lib/slurm-db.d.ts +6 -0
- package/dist/lib/slurm-db.js +181 -18
- package/dist/lib/slurm-db.js.map +1 -1
- package/dist/lib/ui.html +82 -5
- package/dist/lib/ui.js +12 -3
- package/dist/lib/ui.js.map +1 -1
- package/dist/mcp-bundles/dataset-mcp.bundle.mjs +1 -1
- package/dist/mcp-bundles/slurm-mcp.bundle.mjs +173 -17
- package/package.json +1 -1
|
@@ -29945,7 +29945,7 @@ var StdioServerTransport = class {
|
|
|
29945
29945
|
};
|
|
29946
29946
|
|
|
29947
29947
|
// src/lib/slurm-db.ts
|
|
29948
|
-
import { existsSync, mkdirSync, chmodSync } from "fs";
|
|
29948
|
+
import { existsSync, mkdirSync, chmodSync, readFileSync, writeFileSync } from "fs";
|
|
29949
29949
|
import { dirname } from "path";
|
|
29950
29950
|
var TERMINAL_STATES = /* @__PURE__ */ new Set([
|
|
29951
29951
|
"COMPLETED",
|
|
@@ -29998,30 +29998,43 @@ CREATE INDEX IF NOT EXISTS idx_slurm_jobs_submit_time ON slurm_jobs(submit_time)
|
|
|
29998
29998
|
`;
|
|
29999
29999
|
var SlurmJobDB = class {
|
|
30000
30000
|
db;
|
|
30001
|
+
useJsonFallback = false;
|
|
30002
|
+
jsonStorePath = null;
|
|
30003
|
+
jsonJobs = /* @__PURE__ */ new Map();
|
|
30001
30004
|
constructor(dbPath) {
|
|
30002
|
-
if (!Database) {
|
|
30003
|
-
throw new Error(
|
|
30004
|
-
'SLURM tracking requires the "better-sqlite3" package. Install it with: npm install better-sqlite3'
|
|
30005
|
-
);
|
|
30006
|
-
}
|
|
30007
30005
|
const dir = dirname(dbPath);
|
|
30008
30006
|
if (!existsSync(dir)) {
|
|
30009
30007
|
mkdirSync(dir, { recursive: true });
|
|
30010
30008
|
}
|
|
30011
|
-
|
|
30012
|
-
|
|
30013
|
-
|
|
30014
|
-
|
|
30015
|
-
|
|
30016
|
-
|
|
30017
|
-
|
|
30018
|
-
|
|
30019
|
-
|
|
30020
|
-
|
|
30021
|
-
|
|
30009
|
+
const forceJson = process.env.LABGATE_SLURM_DB_FORCE_JSON === "1";
|
|
30010
|
+
if (Database && !forceJson) {
|
|
30011
|
+
try {
|
|
30012
|
+
this.db = new Database(dbPath);
|
|
30013
|
+
try {
|
|
30014
|
+
chmodSync(dbPath, 384);
|
|
30015
|
+
} catch {
|
|
30016
|
+
}
|
|
30017
|
+
this.db.pragma("journal_mode = WAL");
|
|
30018
|
+
this.db.pragma("busy_timeout = 5000");
|
|
30019
|
+
this.db.exec(SCHEMA);
|
|
30020
|
+
try {
|
|
30021
|
+
this.db.exec("ALTER TABLE slurm_jobs ADD COLUMN notes TEXT");
|
|
30022
|
+
} catch {
|
|
30023
|
+
}
|
|
30024
|
+
return;
|
|
30025
|
+
} catch {
|
|
30026
|
+
}
|
|
30022
30027
|
}
|
|
30028
|
+
this.useJsonFallback = true;
|
|
30029
|
+
this.jsonStorePath = `${dbPath}.json`;
|
|
30030
|
+
this.loadJsonStore();
|
|
30031
|
+
this.flushJsonStore();
|
|
30023
30032
|
}
|
|
30024
30033
|
upsertJob(job) {
|
|
30034
|
+
if (this.useJsonFallback) {
|
|
30035
|
+
this.upsertJobJson(job);
|
|
30036
|
+
return;
|
|
30037
|
+
}
|
|
30025
30038
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
30026
30039
|
const existing = this.getJob(job.job_id);
|
|
30027
30040
|
if (existing) {
|
|
@@ -30110,14 +30123,37 @@ var SlurmJobDB = class {
|
|
|
30110
30123
|
}
|
|
30111
30124
|
}
|
|
30112
30125
|
updateJobNotes(jobId, notes) {
|
|
30126
|
+
if (this.useJsonFallback) {
|
|
30127
|
+
const existing = this.jsonJobs.get(jobId);
|
|
30128
|
+
if (!existing) return false;
|
|
30129
|
+
this.jsonJobs.set(jobId, { ...existing, notes, updated_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
30130
|
+
this.flushJsonStore();
|
|
30131
|
+
return true;
|
|
30132
|
+
}
|
|
30113
30133
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
30114
30134
|
const result = this.db.prepare("UPDATE slurm_jobs SET notes = ?, updated_at = ? WHERE job_id = ?").run(notes, now, jobId);
|
|
30115
30135
|
return result.changes > 0;
|
|
30116
30136
|
}
|
|
30117
30137
|
getJob(jobId) {
|
|
30138
|
+
if (this.useJsonFallback) {
|
|
30139
|
+
const job = this.jsonJobs.get(jobId);
|
|
30140
|
+
return job ? { ...job } : null;
|
|
30141
|
+
}
|
|
30118
30142
|
return this.db.prepare("SELECT * FROM slurm_jobs WHERE job_id = ?").get(jobId) ?? null;
|
|
30119
30143
|
}
|
|
30120
30144
|
listJobs(opts) {
|
|
30145
|
+
if (this.useJsonFallback) {
|
|
30146
|
+
const limitRaw2 = opts?.limit ?? 100;
|
|
30147
|
+
const offsetRaw2 = opts?.offset ?? 0;
|
|
30148
|
+
const limit2 = Number.isFinite(limitRaw2) ? Math.min(1e3, Math.max(1, Math.floor(limitRaw2))) : 100;
|
|
30149
|
+
const offset2 = Number.isFinite(offsetRaw2) ? Math.max(0, Math.floor(offsetRaw2)) : 0;
|
|
30150
|
+
const search = opts?.search?.toLowerCase();
|
|
30151
|
+
const rows = [...this.jsonJobs.values()].filter((j) => !opts?.state || j.state === opts.state).filter((j) => !opts?.session_id || j.session_id === opts.session_id).filter((j) => {
|
|
30152
|
+
if (!search) return true;
|
|
30153
|
+
return j.job_id.toLowerCase().includes(search) || (j.name || "").toLowerCase().includes(search) || (j.command || "").toLowerCase().includes(search) || (j.notes || "").toLowerCase().includes(search);
|
|
30154
|
+
}).sort((a, b) => a.submit_time < b.submit_time ? 1 : a.submit_time > b.submit_time ? -1 : 0).slice(offset2, offset2 + limit2);
|
|
30155
|
+
return rows.map((r) => ({ ...r }));
|
|
30156
|
+
}
|
|
30121
30157
|
const conditions = [];
|
|
30122
30158
|
const values = [];
|
|
30123
30159
|
if (opts?.state) {
|
|
@@ -30141,18 +30177,138 @@ var SlurmJobDB = class {
|
|
|
30141
30177
|
return this.db.prepare(`SELECT * FROM slurm_jobs ${where} ORDER BY submit_time DESC LIMIT ? OFFSET ?`).all(...values, limit, offset);
|
|
30142
30178
|
}
|
|
30143
30179
|
getActiveJobIds() {
|
|
30180
|
+
if (this.useJsonFallback) {
|
|
30181
|
+
return [...this.jsonJobs.values()].filter((j) => ["PENDING", "RUNNING", "COMPLETING", "SUSPENDED"].includes(j.state)).map((j) => j.job_id);
|
|
30182
|
+
}
|
|
30144
30183
|
const rows = this.db.prepare("SELECT job_id FROM slurm_jobs WHERE state IN ('PENDING', 'RUNNING', 'COMPLETING', 'SUSPENDED')").all();
|
|
30145
30184
|
return rows.map((r) => r.job_id);
|
|
30146
30185
|
}
|
|
30147
30186
|
getJobCount(state) {
|
|
30187
|
+
if (this.useJsonFallback) {
|
|
30188
|
+
if (state) {
|
|
30189
|
+
let count = 0;
|
|
30190
|
+
for (const job of this.jsonJobs.values()) {
|
|
30191
|
+
if (job.state === state) count++;
|
|
30192
|
+
}
|
|
30193
|
+
return count;
|
|
30194
|
+
}
|
|
30195
|
+
return this.jsonJobs.size;
|
|
30196
|
+
}
|
|
30148
30197
|
if (state) {
|
|
30149
30198
|
return this.db.prepare("SELECT COUNT(*) as count FROM slurm_jobs WHERE state = ?").get(state).count;
|
|
30150
30199
|
}
|
|
30151
30200
|
return this.db.prepare("SELECT COUNT(*) as count FROM slurm_jobs").get().count;
|
|
30152
30201
|
}
|
|
30153
30202
|
close() {
|
|
30203
|
+
if (this.useJsonFallback) {
|
|
30204
|
+
this.flushJsonStore();
|
|
30205
|
+
return;
|
|
30206
|
+
}
|
|
30154
30207
|
this.db.close();
|
|
30155
30208
|
}
|
|
30209
|
+
loadJsonStore() {
|
|
30210
|
+
if (!this.jsonStorePath) return;
|
|
30211
|
+
if (!existsSync(this.jsonStorePath)) return;
|
|
30212
|
+
try {
|
|
30213
|
+
const raw = readFileSync(this.jsonStorePath, "utf-8");
|
|
30214
|
+
const parsed = JSON.parse(raw || "{}");
|
|
30215
|
+
const jobs = Array.isArray(parsed?.jobs) ? parsed.jobs : [];
|
|
30216
|
+
for (const row of jobs) {
|
|
30217
|
+
const job = row;
|
|
30218
|
+
if (!job?.job_id) continue;
|
|
30219
|
+
this.jsonJobs.set(job.job_id, job);
|
|
30220
|
+
}
|
|
30221
|
+
} catch {
|
|
30222
|
+
this.jsonJobs.clear();
|
|
30223
|
+
}
|
|
30224
|
+
}
|
|
30225
|
+
flushJsonStore() {
|
|
30226
|
+
if (!this.jsonStorePath) return;
|
|
30227
|
+
try {
|
|
30228
|
+
const payload = {
|
|
30229
|
+
version: 1,
|
|
30230
|
+
jobs: [...this.jsonJobs.values()]
|
|
30231
|
+
};
|
|
30232
|
+
writeFileSync(this.jsonStorePath, JSON.stringify(payload, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
|
|
30233
|
+
try {
|
|
30234
|
+
chmodSync(this.jsonStorePath, 384);
|
|
30235
|
+
} catch {
|
|
30236
|
+
}
|
|
30237
|
+
} catch {
|
|
30238
|
+
}
|
|
30239
|
+
}
|
|
30240
|
+
upsertJobJson(job) {
|
|
30241
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
30242
|
+
const existing = this.jsonJobs.get(job.job_id);
|
|
30243
|
+
if (existing) {
|
|
30244
|
+
const fields = [
|
|
30245
|
+
"name",
|
|
30246
|
+
"state",
|
|
30247
|
+
"submit_time",
|
|
30248
|
+
"start_time",
|
|
30249
|
+
"end_time",
|
|
30250
|
+
"partition",
|
|
30251
|
+
"nodes",
|
|
30252
|
+
"num_nodes",
|
|
30253
|
+
"num_tasks",
|
|
30254
|
+
"cpus_per_task",
|
|
30255
|
+
"mem",
|
|
30256
|
+
"stdout_path",
|
|
30257
|
+
"stderr_path",
|
|
30258
|
+
"stdout_size",
|
|
30259
|
+
"stderr_size",
|
|
30260
|
+
"command",
|
|
30261
|
+
"script_path",
|
|
30262
|
+
"session_id",
|
|
30263
|
+
"workdir",
|
|
30264
|
+
"exit_code",
|
|
30265
|
+
"account",
|
|
30266
|
+
"qos",
|
|
30267
|
+
"time_limit",
|
|
30268
|
+
"notes"
|
|
30269
|
+
];
|
|
30270
|
+
const updated = { ...existing, updated_at: now };
|
|
30271
|
+
for (const field of fields) {
|
|
30272
|
+
if (job[field] !== void 0) {
|
|
30273
|
+
updated[field] = job[field];
|
|
30274
|
+
}
|
|
30275
|
+
}
|
|
30276
|
+
this.jsonJobs.set(job.job_id, updated);
|
|
30277
|
+
this.flushJsonStore();
|
|
30278
|
+
return;
|
|
30279
|
+
}
|
|
30280
|
+
const inserted = {
|
|
30281
|
+
job_id: job.job_id,
|
|
30282
|
+
name: job.name ?? null,
|
|
30283
|
+
state: job.state ?? "UNKNOWN",
|
|
30284
|
+
submit_time: job.submit_time ?? now,
|
|
30285
|
+
start_time: job.start_time ?? null,
|
|
30286
|
+
end_time: job.end_time ?? null,
|
|
30287
|
+
partition: job.partition ?? null,
|
|
30288
|
+
nodes: job.nodes ?? null,
|
|
30289
|
+
num_nodes: job.num_nodes ?? null,
|
|
30290
|
+
num_tasks: job.num_tasks ?? null,
|
|
30291
|
+
cpus_per_task: job.cpus_per_task ?? null,
|
|
30292
|
+
mem: job.mem ?? null,
|
|
30293
|
+
stdout_path: job.stdout_path ?? null,
|
|
30294
|
+
stderr_path: job.stderr_path ?? null,
|
|
30295
|
+
stdout_size: job.stdout_size ?? null,
|
|
30296
|
+
stderr_size: job.stderr_size ?? null,
|
|
30297
|
+
command: job.command ?? null,
|
|
30298
|
+
script_path: job.script_path ?? null,
|
|
30299
|
+
session_id: job.session_id ?? null,
|
|
30300
|
+
workdir: job.workdir ?? null,
|
|
30301
|
+
exit_code: job.exit_code ?? null,
|
|
30302
|
+
account: job.account ?? null,
|
|
30303
|
+
qos: job.qos ?? null,
|
|
30304
|
+
time_limit: job.time_limit ?? null,
|
|
30305
|
+
notes: job.notes ?? null,
|
|
30306
|
+
discovered_at: now,
|
|
30307
|
+
updated_at: now
|
|
30308
|
+
};
|
|
30309
|
+
this.jsonJobs.set(job.job_id, inserted);
|
|
30310
|
+
this.flushJsonStore();
|
|
30311
|
+
}
|
|
30156
30312
|
};
|
|
30157
30313
|
|
|
30158
30314
|
// src/lib/config.ts
|