severally-mcp 2.3.1 → 2.4.0

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/src/store.mjs CHANGED
@@ -12,7 +12,11 @@ export function ensureDirs() {
12
12
  fs.mkdirSync(path.join(home, 'history'), { recursive: true, mode: 0o700 });
13
13
  fs.mkdirSync(path.join(home, 'jobs'), { recursive: true, mode: 0o700 });
14
14
  fs.mkdirSync(path.join(home, 'homes'), { recursive: true, mode: 0o700 });
15
- try { fs.chmodSync(home, 0o700); } catch { /* best effort */ }
15
+ try {
16
+ fs.chmodSync(home, 0o700);
17
+ } catch {
18
+ /* best effort */
19
+ }
16
20
  return home;
17
21
  }
18
22
 
@@ -45,7 +49,11 @@ export function writeJobArtifact(jobId, name, content) {
45
49
  }
46
50
 
47
51
  export function readIfExists(p) {
48
- try { return fs.readFileSync(p, 'utf8'); } catch { return ''; }
52
+ try {
53
+ return fs.readFileSync(p, 'utf8');
54
+ } catch {
55
+ return '';
56
+ }
49
57
  }
50
58
 
51
59
  // A consultation from an earlier server session, found through the index and
@@ -58,24 +66,34 @@ function indexRow(jobId) {
58
66
  try {
59
67
  const parsed = JSON.parse(line);
60
68
  if (parsed.job_id === jobId) return parsed;
61
- } catch { /* a damaged line is skipped, not fatal */ }
69
+ } catch {
70
+ /* a damaged line is skipped, not fatal */
71
+ }
62
72
  }
63
73
  return null;
64
74
  }
65
75
 
66
76
  /** Every row of the history index, in the order written; a damaged line is skipped. */
67
77
  export function indexRows() {
68
- return readIfExists(path.join(POLICY.home, 'history', 'index.jsonl')).split('\n').flatMap((line) => {
69
- try {
70
- const row = line.trim() ? JSON.parse(line) : null;
71
- return row && typeof row === 'object' && JOB_ID.test(row.job_id) ? [row] : [];
72
- } catch { return []; }
73
- });
78
+ return readIfExists(path.join(POLICY.home, 'history', 'index.jsonl'))
79
+ .split('\n')
80
+ .flatMap((line) => {
81
+ try {
82
+ const row = line.trim() ? JSON.parse(line) : null;
83
+ return row && typeof row === 'object' && JOB_ID.test(row.job_id) ? [row] : [];
84
+ } catch {
85
+ return [];
86
+ }
87
+ });
74
88
  }
75
89
 
76
90
  /** The round file an index row points at, or null. */
77
91
  export function loadRoundAt(row) {
78
- try { return JSON.parse(fs.readFileSync(roundPath(row.chain_id, row.round), 'utf8')); } catch { return null; }
92
+ try {
93
+ return JSON.parse(fs.readFileSync(roundPath(row.chain_id, row.round), 'utf8'));
94
+ } catch {
95
+ return null;
96
+ }
79
97
  }
80
98
 
81
99
  export const roundPath = (chainId, round) =>
@@ -92,7 +110,11 @@ export function writeDiagnostics(chainId, round, text) {
92
110
  export function loadRound(jobId) {
93
111
  const row = indexRow(jobId);
94
112
  if (!row) return null;
95
- try { return JSON.parse(fs.readFileSync(roundPath(row.chain_id, row.round), 'utf8')); } catch { return null; }
113
+ try {
114
+ return JSON.parse(fs.readFileSync(roundPath(row.chain_id, row.round), 'utf8'));
115
+ } catch {
116
+ return null;
117
+ }
96
118
  }
97
119
 
98
120
  // Every round file of one chain, oldest first: a follow-up to a consultation
@@ -100,9 +122,20 @@ export function loadRound(jobId) {
100
122
  export function loadChain(chainId) {
101
123
  const dir = path.join(POLICY.home, 'history', chainId);
102
124
  let names = [];
103
- try { names = fs.readdirSync(dir).filter((n) => /^round-\d+\.json$/.test(n)).sort(); } catch { return []; }
125
+ try {
126
+ names = fs
127
+ .readdirSync(dir)
128
+ .filter((n) => /^round-\d+\.json$/.test(n))
129
+ .sort();
130
+ } catch {
131
+ return [];
132
+ }
104
133
  return names.flatMap((n) => {
105
- try { return [JSON.parse(fs.readFileSync(path.join(dir, n), 'utf8'))]; } catch { return []; }
134
+ try {
135
+ return [JSON.parse(fs.readFileSync(path.join(dir, n), 'utf8'))];
136
+ } catch {
137
+ return [];
138
+ }
106
139
  });
107
140
  }
108
141
 
@@ -169,18 +202,31 @@ export function writeRunning(record) {
169
202
 
170
203
  export function loadRunning(jobId) {
171
204
  if (!JOB_ID.test(jobId)) return null;
172
- try { return JSON.parse(fs.readFileSync(runningFile(jobId), 'utf8')); } catch { return null; }
205
+ try {
206
+ return JSON.parse(fs.readFileSync(runningFile(jobId), 'utf8'));
207
+ } catch {
208
+ return null;
209
+ }
173
210
  }
174
211
 
175
212
  export function listRunning() {
176
213
  try {
177
- return fs.readdirSync(runningDir()).map((n) => n.replace(/\.json$/, '')).filter((id) => JOB_ID.test(id));
178
- } catch { return []; }
214
+ return fs
215
+ .readdirSync(runningDir())
216
+ .map((n) => n.replace(/\.json$/, ''))
217
+ .filter((id) => JOB_ID.test(id));
218
+ } catch {
219
+ return [];
220
+ }
179
221
  }
180
222
 
181
223
  export function removeRunning(jobId) {
182
224
  if (!JOB_ID.test(jobId)) return;
183
- try { fs.rmSync(runningFile(jobId), { force: true }); } catch { /* best effort */ }
225
+ try {
226
+ fs.rmSync(runningFile(jobId), { force: true });
227
+ } catch {
228
+ /* best effort */
229
+ }
184
230
  }
185
231
 
186
232
  // Seats: the concurrency cap is shared by every severally process on one
@@ -198,16 +244,31 @@ function withSeatLock(fn) {
198
244
  fs.mkdirSync(seatsDir(), { recursive: true, mode: 0o700 });
199
245
  const until = Date.now() + LOCK_WAIT_MS;
200
246
  for (;;) {
201
- try { fs.mkdirSync(lock); break; } catch (err) {
247
+ try {
248
+ fs.mkdirSync(lock);
249
+ break;
250
+ } catch (err) {
202
251
  if (err.code !== 'EEXIST') throw err;
203
252
  // Held for a few file operations at most: one this old was left by a
204
253
  // process that died holding it.
205
- try { if (Date.now() - fs.statSync(lock).mtimeMs > LOCK_STALE_MS) fs.rmdirSync(lock); } catch { /* gone already */ }
254
+ try {
255
+ if (Date.now() - fs.statSync(lock).mtimeMs > LOCK_STALE_MS) fs.rmdirSync(lock);
256
+ } catch {
257
+ /* gone already */
258
+ }
206
259
  if (Date.now() > until) throw new Error('the seat lock stayed taken; try again');
207
260
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
208
261
  }
209
262
  }
210
- try { return fn(); } finally { try { fs.rmdirSync(lock); } catch { /* best effort */ } }
263
+ try {
264
+ return fn();
265
+ } finally {
266
+ try {
267
+ fs.rmdirSync(lock);
268
+ } catch {
269
+ /* best effort */
270
+ }
271
+ }
211
272
  }
212
273
 
213
274
  /** Takes one seat per entry of `seats` ({ job_id, owner }), all or none. */
@@ -217,14 +278,28 @@ export function takeSeats(seats, cap, held) {
217
278
  const names = fs.readdirSync(seatsDir());
218
279
  // A cancel asked for after its consultation ended has no seat left to go with.
219
280
  for (const name of names.filter((n) => n.endsWith('.cancel'))) {
220
- if (!names.includes(name.replace(/\.cancel$/, '.json'))) try { fs.rmSync(path.join(seatsDir(), name), { force: true }); } catch { /* best effort */ }
281
+ if (!names.includes(name.replace(/\.cancel$/, '.json')))
282
+ try {
283
+ fs.rmSync(path.join(seatsDir(), name), { force: true });
284
+ } catch {
285
+ /* best effort */
286
+ }
221
287
  }
222
288
  for (const name of names) {
223
289
  if (!name.endsWith('.json')) continue;
224
290
  let seat = null;
225
- try { seat = JSON.parse(fs.readFileSync(path.join(seatsDir(), name), 'utf8')); } catch { /* half-written or damaged: free */ }
291
+ try {
292
+ seat = JSON.parse(fs.readFileSync(path.join(seatsDir(), name), 'utf8'));
293
+ } catch {
294
+ /* half-written or damaged: free */
295
+ }
226
296
  if (seat && held(seat)) live.push(seat);
227
- else try { fs.rmSync(path.join(seatsDir(), name), { force: true }); } catch { /* best effort */ }
297
+ else
298
+ try {
299
+ fs.rmSync(path.join(seatsDir(), name), { force: true });
300
+ } catch {
301
+ /* best effort */
302
+ }
228
303
  }
229
304
  // A chain runs one round at a time, across processes: a live seat on the
230
305
  // same chain, or a round file already at the number chosen, means another
@@ -232,7 +307,11 @@ export function takeSeats(seats, cap, held) {
232
307
  for (const mine of seats.filter((x) => x.chain_id)) {
233
308
  let other = live.find((x) => x.chain_id === mine.chain_id);
234
309
  if (!other && fs.existsSync(roundPath(mine.chain_id, mine.round))) {
235
- try { other = { job_id: JSON.parse(fs.readFileSync(roundPath(mine.chain_id, mine.round), 'utf8')).job_id, round: mine.round }; } catch { other = { job_id: '(unreadable)', round: mine.round }; }
310
+ try {
311
+ other = { job_id: JSON.parse(fs.readFileSync(roundPath(mine.chain_id, mine.round), 'utf8')).job_id, round: mine.round };
312
+ } catch {
313
+ other = { job_id: '(unreadable)', round: mine.round };
314
+ }
236
315
  }
237
316
  if (other) return { ok: false, chainBusy: { live: other, mine } };
238
317
  }
@@ -249,7 +328,11 @@ export function takeSeats(seats, cap, held) {
249
328
 
250
329
  export function releaseSeat(jobId) {
251
330
  if (!JOB_ID.test(jobId)) return;
252
- try { fs.rmSync(seatFile(jobId), { force: true }); } catch { /* best effort */ }
331
+ try {
332
+ fs.rmSync(seatFile(jobId), { force: true });
333
+ } catch {
334
+ /* best effort */
335
+ }
253
336
  clearCancel(jobId);
254
337
  }
255
338
 
@@ -258,16 +341,31 @@ export function releaseSeat(jobId) {
258
341
  const cancelFile = (jobId) => path.join(seatsDir(), `${jobId}.cancel`);
259
342
  export function requestCancel(jobId) {
260
343
  if (!JOB_ID.test(jobId)) return false;
261
- try { fs.mkdirSync(seatsDir(), { recursive: true, mode: 0o700 }); fs.writeFileSync(cancelFile(jobId), '', { mode: 0o600 }); return true; } catch { return false; }
344
+ try {
345
+ fs.mkdirSync(seatsDir(), { recursive: true, mode: 0o700 });
346
+ fs.writeFileSync(cancelFile(jobId), '', { mode: 0o600 });
347
+ return true;
348
+ } catch {
349
+ return false;
350
+ }
262
351
  }
263
352
  export const cancelRequested = (jobId) => JOB_ID.test(jobId) && fs.existsSync(cancelFile(jobId));
264
353
  export function clearCancel(jobId) {
265
- if (JOB_ID.test(jobId)) try { fs.rmSync(cancelFile(jobId), { force: true }); } catch { /* best effort */ }
354
+ if (JOB_ID.test(jobId))
355
+ try {
356
+ fs.rmSync(cancelFile(jobId), { force: true });
357
+ } catch {
358
+ /* best effort */
359
+ }
266
360
  }
267
361
 
268
362
  export function loadGroup(groupId) {
269
363
  if (!GROUP_ID.test(groupId)) return null;
270
- try { return JSON.parse(fs.readFileSync(path.join(POLICY.home, 'history', 'groups', `${groupId}.json`), 'utf8')); } catch { return null; }
364
+ try {
365
+ return JSON.parse(fs.readFileSync(path.join(POLICY.home, 'history', 'groups', `${groupId}.json`), 'utf8'));
366
+ } catch {
367
+ return null;
368
+ }
271
369
  }
272
370
 
273
371
  // A fan-out is recorded as its own record so a later reader can tell which
@@ -281,6 +379,10 @@ export function persistGroup(group) {
281
379
 
282
380
  export function cleanupJobDir(jobId) {
283
381
  for (const dir of [jobDir(jobId), sandboxHome(jobId)]) {
284
- try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* best effort */ }
382
+ try {
383
+ fs.rmSync(dir, { recursive: true, force: true });
384
+ } catch {
385
+ /* best effort */
386
+ }
285
387
  }
286
388
  }