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.
@@ -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
- this.db = new Database(dbPath);
30012
- try {
30013
- chmodSync(dbPath, 384);
30014
- } catch {
30015
- }
30016
- this.db.pragma("journal_mode = WAL");
30017
- this.db.pragma("busy_timeout = 5000");
30018
- this.db.exec(SCHEMA);
30019
- try {
30020
- this.db.exec("ALTER TABLE slurm_jobs ADD COLUMN notes TEXT");
30021
- } catch {
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "labgate",
3
- "version": "0.5.21",
3
+ "version": "0.5.23",
4
4
  "description": "Policy-controlled sandboxes for AI coding agents — https://labgate.dev",
5
5
  "homepage": "https://labgate.dev",
6
6
  "keywords": [