antechamber 1.0.4 → 1.0.5

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/CHANGELOG.md CHANGED
@@ -7,6 +7,13 @@ and this project uses [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.0.5] - 2026-10-20
11
+
12
+ ### Fixed
13
+
14
+ - Reduce startup CPU usage and boot delays for large session histories by committing restoration metadata
15
+ in one registry write, while preserving restored costs, child sessions and independent registry writers.
16
+
10
17
  ## [1.0.4] - 2026-10-20
11
18
 
12
19
  ### Fixed
@@ -91,12 +91,15 @@ export declare class SessionStore {
91
91
  readonly stateDir: string;
92
92
  private readonly file;
93
93
  private entries;
94
+ private batchState;
94
95
  constructor(stateDir?: string);
95
96
  private load;
96
97
  private readEntries;
97
98
  private withRegistryLock;
98
99
  private persist;
99
100
  private mutate;
101
+ /** Synchronous restoration reads its own changes, then merges them under one short-lived write lock. */
102
+ batch<T>(action: () => T): T;
100
103
  get(ocSessionId: string): SessionRegistryEntry | undefined;
101
104
  idFor(piSessionId: string): string | undefined;
102
105
  childIds(): string[];
@@ -378,12 +378,15 @@ export class SessionStore {
378
378
  stateDir;
379
379
  file;
380
380
  entries;
381
+ batchState;
381
382
  constructor(stateDir) {
382
383
  const dir = stateDir ?? envSetting("STATE_DIR") ?? defaultStateDir();
383
384
  this.stateDir = dir;
384
385
  this.file = join(dir, "sessions.json");
385
386
  }
386
387
  load() {
388
+ if (this.batchState !== undefined)
389
+ return this.batchState.entries;
387
390
  if (this.entries !== undefined)
388
391
  return this.entries;
389
392
  this.entries = this.readEntries();
@@ -452,6 +455,11 @@ export class SessionStore {
452
455
  renameSync(tmp, this.file);
453
456
  }
454
457
  mutate(change) {
458
+ if (this.batchState !== undefined) {
459
+ this.batchState.changes.push(change);
460
+ change(this.batchState.entries);
461
+ return;
462
+ }
455
463
  this.withRegistryLock(() => {
456
464
  const entries = this.readEntries();
457
465
  if (change(entries))
@@ -459,6 +467,31 @@ export class SessionStore {
459
467
  this.entries = entries;
460
468
  });
461
469
  }
470
+ /** Synchronous restoration reads its own changes, then merges them under one short-lived write lock. */
471
+ batch(action) {
472
+ if (this.batchState !== undefined)
473
+ return action();
474
+ const changes = [];
475
+ const batch = { entries: structuredClone(this.load()), changes };
476
+ this.batchState = batch;
477
+ try {
478
+ const result = action();
479
+ this.batchState = undefined;
480
+ if (batch.changes.length !== 0) {
481
+ this.mutate((entries) => {
482
+ let changed = false;
483
+ for (const change of batch.changes)
484
+ if (change(entries))
485
+ changed = true;
486
+ return changed;
487
+ });
488
+ }
489
+ return result;
490
+ }
491
+ finally {
492
+ this.batchState = undefined;
493
+ }
494
+ }
462
495
  get(ocSessionId) {
463
496
  return this.load().get(ocSessionId);
464
497
  }
@@ -471,18 +504,23 @@ export class SessionStore {
471
504
  .map(([id]) => id);
472
505
  }
473
506
  set(ocSessionId, entry) {
507
+ const saved = this.batchState === undefined ? entry : structuredClone(entry);
474
508
  this.mutate((entries) => {
475
- entries.set(ocSessionId, entry);
509
+ entries.set(ocSessionId, this.batchState === undefined ? saved : structuredClone(saved));
476
510
  return true;
477
511
  });
478
512
  }
479
513
  /** Merge a partial update (e.g. title override, updated timestamp). */
480
514
  update(ocSessionId, patch) {
515
+ const saved = this.batchState === undefined ? patch : structuredClone(patch);
481
516
  this.mutate((entries) => {
482
517
  const previous = entries.get(ocSessionId);
483
518
  if (previous === undefined)
484
519
  return false;
485
- entries.set(ocSessionId, { ...previous, ...patch });
520
+ entries.set(ocSessionId, {
521
+ ...previous,
522
+ ...(this.batchState === undefined ? saved : structuredClone(saved)),
523
+ });
486
524
  return true;
487
525
  });
488
526
  }
package/dist/sessions.js CHANGED
@@ -308,14 +308,18 @@ export class SessionManager {
308
308
  .sort((a, b) => this.lifecycle.lastUsed(b) - this.lifecycle.lastUsed(a));
309
309
  }
310
310
  list(directory) {
311
- this.refreshNativeTitles();
312
- this.subagents.restoreAll();
313
- if (directory !== undefined) {
314
- this.ensureScanned(directory);
315
- return [...this.sessions.values()].filter((s) => s.info.directory === directory).map((s) => this.sessionInfo(s));
316
- }
317
- // Unfiltered: keep the old semantics — live sessions only.
318
- return [...this.sessions.values()].filter((s) => s.restored === undefined).map((s) => this.sessionInfo(s));
311
+ return this.store.batch(() => {
312
+ this.refreshNativeTitles();
313
+ this.subagents.restoreAll();
314
+ if (directory !== undefined) {
315
+ this.ensureScanned(directory);
316
+ return [...this.sessions.values()]
317
+ .filter((s) => s.info.directory === directory)
318
+ .map((s) => this.sessionInfo(s));
319
+ }
320
+ // Unfiltered: keep the old semantics — live sessions only.
321
+ return [...this.sessions.values()].filter((s) => s.restored === undefined).map((s) => this.sessionInfo(s));
322
+ });
319
323
  }
320
324
  /**
321
325
  * Every restorable session on disk (all directories) plus live ones. Backs the
@@ -323,10 +327,12 @@ export class SessionManager {
323
327
  * global/recent view; `list(directory)` stays the per-project source of truth.
324
328
  */
325
329
  listAll() {
326
- this.refreshNativeTitles();
327
- this.ensureAllScanned();
328
- this.subagents.restoreAll();
329
- return [...this.sessions.values()].map((s) => this.sessionInfo(s));
330
+ return this.store.batch(() => {
331
+ this.refreshNativeTitles();
332
+ this.ensureAllScanned();
333
+ this.subagents.restoreAll();
334
+ return [...this.sessions.values()].map((s) => this.sessionInfo(s));
335
+ });
330
336
  }
331
337
  sessionInfo(s) {
332
338
  if (s.restored !== undefined)
@@ -334,18 +340,20 @@ export class SessionManager {
334
340
  return s.info;
335
341
  }
336
342
  get(sessionId) {
337
- if (this.disposed)
338
- return undefined;
339
- const live = this.sessions.get(sessionId);
340
- if (live !== undefined) {
341
- this.refreshNativeTitles();
342
- this.sessionInfo(live);
343
- return live;
344
- }
345
- const restored = this.subagents.restore(sessionId) ?? this.resolveRestored(sessionId);
346
- if (restored !== undefined)
347
- this.sessionInfo(restored);
348
- return restored;
343
+ return this.store.batch(() => {
344
+ if (this.disposed)
345
+ return undefined;
346
+ const live = this.sessions.get(sessionId);
347
+ if (live !== undefined) {
348
+ this.refreshNativeTitles();
349
+ this.sessionInfo(live);
350
+ return live;
351
+ }
352
+ const restored = this.subagents.restore(sessionId) ?? this.resolveRestored(sessionId);
353
+ if (restored !== undefined)
354
+ this.sessionInfo(restored);
355
+ return restored;
356
+ });
349
357
  }
350
358
  async remove(sessionId) {
351
359
  return this.lifecycle.run(() => this.removeLive(sessionId));
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antechamber-omo-compat",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "OpenChamber extension: displays OmO tool calls the way OmO means them when OpenChamber runs on antechamber",
5
5
  "private": true,
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antechamber-omo-graph",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "OpenChamber extension: live, interactive graph of the OmO sub-agents behind the current session",
5
5
  "private": true,
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antechamber-omo-work",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "OpenChamber 2.x Work Status section: OmO todo phases, monitors, eval cells and background work of the current session",
5
5
  "private": true,
6
6
  "type": "module",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antechamber",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "OpenCode-compatible HTTP/SSE server that lets OpenChamber drive the pi coding agent (and OmO) over RPC",
5
5
  "type": "module",
6
6
  "license": "MIT",