antechamber 1.0.10 → 1.0.13
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 +23 -0
- package/dist/session-store.d.ts +5 -1
- package/dist/session-store.js +55 -14
- package/dist/sessions.d.ts +5 -0
- package/dist/sessions.js +10 -0
- package/dist/v2.d.ts +2 -0
- package/dist/v2.js +2 -1
- package/extensions/omo-compat/package.json +1 -1
- package/extensions/omo-dag/package.json +1 -1
- package/extensions/omo-work/package.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,29 @@ and this project uses [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.0.13] - 2026-10-22
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Show the effort (thinking level) picker again in OpenChamber 2.x: `/api/model` and `/api/model/default`
|
|
15
|
+
now list each model's thinking levels in `variants` instead of an empty array.
|
|
16
|
+
- Keep a session's effort when OpenChamber 2.x reopens it on another device: the v2 session record now reports
|
|
17
|
+
the thinking level actually applied, instead of a stale one that the next prompt sent back.
|
|
18
|
+
|
|
19
|
+
## [1.0.12] - 2026-10-21
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Stop saving child-task spawn specs in the session registry; OmO's task records already hold them.
|
|
24
|
+
This shrinks the registry by about a third in sessions with hundreds of child tasks.
|
|
25
|
+
|
|
26
|
+
## [1.0.11] - 2026-10-21
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- Cut the event-loop stall of each session registry write by reusing the in-memory registry while
|
|
31
|
+
the file is unchanged, cloning only the entries a batch reads, and writing compact JSON.
|
|
32
|
+
|
|
10
33
|
## [1.0.10] - 2026-10-21
|
|
11
34
|
|
|
12
35
|
### Fixed
|
package/dist/session-store.d.ts
CHANGED
|
@@ -91,10 +91,14 @@ export declare class SessionStore {
|
|
|
91
91
|
readonly stateDir: string;
|
|
92
92
|
private readonly file;
|
|
93
93
|
private entries;
|
|
94
|
+
/** The registry bytes `entries` was parsed from or last written as; equal bytes on disk mean the cache is current. */
|
|
95
|
+
private raw;
|
|
94
96
|
private batchState;
|
|
95
97
|
constructor(stateDir?: string);
|
|
96
98
|
private load;
|
|
97
|
-
private
|
|
99
|
+
private committed;
|
|
100
|
+
private readRaw;
|
|
101
|
+
private parseEntries;
|
|
98
102
|
private withRegistryLock;
|
|
99
103
|
private persist;
|
|
100
104
|
private mutate;
|
package/dist/session-store.js
CHANGED
|
@@ -371,6 +371,17 @@ function registryEntry(value) {
|
|
|
371
371
|
return false;
|
|
372
372
|
return true;
|
|
373
373
|
}
|
|
374
|
+
/**
|
|
375
|
+
* OmO keeps a task's spawn spec in its own task record, which is where the adapter reads it
|
|
376
|
+
* (adoption, eval-agent matching). The registry copy was never read back and made up about a third
|
|
377
|
+
* of the file in sessions with hundreds of children.
|
|
378
|
+
*/
|
|
379
|
+
function persistedEntry(entry) {
|
|
380
|
+
if (entry.task === undefined || !("spawn_spec" in entry.task.details))
|
|
381
|
+
return entry;
|
|
382
|
+
const { spawn_spec: _spec, ...details } = entry.task.details;
|
|
383
|
+
return { ...entry, task: { ...entry.task, details } };
|
|
384
|
+
}
|
|
374
385
|
function errorCode(error) {
|
|
375
386
|
return error instanceof Error && "code" in error ? String(error.code) : undefined;
|
|
376
387
|
}
|
|
@@ -378,6 +389,8 @@ export class SessionStore {
|
|
|
378
389
|
stateDir;
|
|
379
390
|
file;
|
|
380
391
|
entries;
|
|
392
|
+
/** The registry bytes `entries` was parsed from or last written as; equal bytes on disk mean the cache is current. */
|
|
393
|
+
raw;
|
|
381
394
|
batchState;
|
|
382
395
|
constructor(stateDir) {
|
|
383
396
|
const dir = stateDir ?? envSetting("STATE_DIR") ?? defaultStateDir();
|
|
@@ -386,26 +399,41 @@ export class SessionStore {
|
|
|
386
399
|
}
|
|
387
400
|
load() {
|
|
388
401
|
if (this.batchState !== undefined) {
|
|
389
|
-
|
|
402
|
+
// Shallow: changes replace whole entries, and get() clones an entry before handing it out.
|
|
403
|
+
this.batchState.entries ??= new Map(this.committed());
|
|
390
404
|
return this.batchState.entries;
|
|
391
405
|
}
|
|
406
|
+
return this.committed();
|
|
407
|
+
}
|
|
408
|
+
committed() {
|
|
392
409
|
if (this.entries !== undefined)
|
|
393
410
|
return this.entries;
|
|
394
|
-
this.
|
|
411
|
+
this.raw = this.readRaw();
|
|
412
|
+
this.entries = this.parseEntries(this.raw);
|
|
395
413
|
return this.entries;
|
|
396
414
|
}
|
|
397
|
-
|
|
415
|
+
readRaw() {
|
|
416
|
+
try {
|
|
417
|
+
return readFileSync(this.file);
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
return undefined;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
parseEntries(raw) {
|
|
398
424
|
const entries = new Map();
|
|
425
|
+
if (raw === undefined)
|
|
426
|
+
return entries;
|
|
399
427
|
try {
|
|
400
|
-
const
|
|
401
|
-
if (!record(
|
|
428
|
+
const parsed = JSON.parse(raw.toString("utf8"));
|
|
429
|
+
if (!record(parsed))
|
|
402
430
|
return entries;
|
|
403
|
-
for (const [id, value] of Object.entries(
|
|
431
|
+
for (const [id, value] of Object.entries(parsed))
|
|
404
432
|
if (registryEntry(value))
|
|
405
433
|
entries.set(id, value);
|
|
406
434
|
}
|
|
407
435
|
catch {
|
|
408
|
-
/*
|
|
436
|
+
/* corrupt — start empty */
|
|
409
437
|
}
|
|
410
438
|
return entries;
|
|
411
439
|
}
|
|
@@ -450,11 +478,13 @@ export class SessionStore {
|
|
|
450
478
|
}
|
|
451
479
|
}
|
|
452
480
|
persist(entries) {
|
|
453
|
-
const obj = Object.fromEntries(entries);
|
|
481
|
+
const obj = Object.fromEntries([...entries].map(([id, entry]) => [id, persistedEntry(entry)]));
|
|
454
482
|
const tmp = `${this.file}.${process.pid}.tmp`;
|
|
455
|
-
|
|
483
|
+
const data = Buffer.from(JSON.stringify(obj));
|
|
484
|
+
writeFileSync(tmp, data, { mode: REGISTRY_FILE_MODE });
|
|
456
485
|
chmodSync(tmp, REGISTRY_FILE_MODE);
|
|
457
486
|
renameSync(tmp, this.file);
|
|
487
|
+
return data;
|
|
458
488
|
}
|
|
459
489
|
mutate(change) {
|
|
460
490
|
if (this.batchState !== undefined) {
|
|
@@ -463,10 +493,14 @@ export class SessionStore {
|
|
|
463
493
|
return;
|
|
464
494
|
}
|
|
465
495
|
this.withRegistryLock(() => {
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
496
|
+
// Another process may have written since; only parse again when the bytes differ.
|
|
497
|
+
const raw = this.readRaw();
|
|
498
|
+
const entries = this.entries !== undefined && raw !== undefined && this.raw !== undefined && raw.equals(this.raw)
|
|
499
|
+
? new Map(this.entries)
|
|
500
|
+
: this.parseEntries(raw);
|
|
501
|
+
const written = change(entries) ? this.persist(entries) : raw;
|
|
469
502
|
this.entries = entries;
|
|
503
|
+
this.raw = written;
|
|
470
504
|
});
|
|
471
505
|
}
|
|
472
506
|
/** Synchronous restoration reads its own changes, then merges them under one short-lived write lock. */
|
|
@@ -474,7 +508,7 @@ export class SessionStore {
|
|
|
474
508
|
if (this.batchState !== undefined)
|
|
475
509
|
return action();
|
|
476
510
|
const changes = [];
|
|
477
|
-
const batch = { entries: undefined, changes };
|
|
511
|
+
const batch = { entries: undefined, isolated: new WeakSet(), changes };
|
|
478
512
|
this.batchState = batch;
|
|
479
513
|
try {
|
|
480
514
|
const result = action();
|
|
@@ -495,7 +529,14 @@ export class SessionStore {
|
|
|
495
529
|
}
|
|
496
530
|
}
|
|
497
531
|
get(ocSessionId) {
|
|
498
|
-
|
|
532
|
+
const entries = this.load();
|
|
533
|
+
const entry = entries.get(ocSessionId);
|
|
534
|
+
if (this.batchState === undefined || entry === undefined || this.batchState.isolated.has(entry))
|
|
535
|
+
return entry;
|
|
536
|
+
const staged = structuredClone(entry);
|
|
537
|
+
entries.set(ocSessionId, staged);
|
|
538
|
+
this.batchState.isolated.add(staged);
|
|
539
|
+
return staged;
|
|
499
540
|
}
|
|
500
541
|
idFor(piSessionId) {
|
|
501
542
|
return [...this.load()].find(([, entry]) => entry.piSessionId === piSessionId)?.[0];
|
package/dist/sessions.d.ts
CHANGED
|
@@ -176,6 +176,11 @@ export declare class SessionManager {
|
|
|
176
176
|
*/
|
|
177
177
|
setModel(sessionId: string, ref: ModelSelection): Promise<OCModelRef>;
|
|
178
178
|
private setModelLive;
|
|
179
|
+
/**
|
|
180
|
+
* On a device with no saved pick, OpenChamber 2.x restores a reopened session's effort from this
|
|
181
|
+
* record and resends it with the next prompt, so a stale level silently resets the user's choice.
|
|
182
|
+
*/
|
|
183
|
+
private syncRecordVariant;
|
|
179
184
|
/**
|
|
180
185
|
* Single funnel for "pi is now running this model": adopt it, persist it for
|
|
181
186
|
* the next cold start, and tell the UI. Returns whether anything changed.
|
package/dist/sessions.js
CHANGED
|
@@ -837,6 +837,7 @@ export class SessionManager {
|
|
|
837
837
|
// not cost a round-trip nor emit a phantom "model switched" marker.
|
|
838
838
|
if (s.model !== undefined && s.model.providerID === ref.providerID && s.model.modelID === ref.modelID) {
|
|
839
839
|
await setThinkingLevel(s, pi, ref.variant);
|
|
840
|
+
this.syncRecordVariant(s);
|
|
840
841
|
return s.model;
|
|
841
842
|
}
|
|
842
843
|
const res = await pi.command({
|
|
@@ -868,8 +869,17 @@ export class SessionManager {
|
|
|
868
869
|
};
|
|
869
870
|
this.applyPiModel(s, resolved.providerID, resolved.modelID);
|
|
870
871
|
await setThinkingLevel(s, pi, ref.variant);
|
|
872
|
+
this.syncRecordVariant(s);
|
|
871
873
|
return resolved;
|
|
872
874
|
}
|
|
875
|
+
/**
|
|
876
|
+
* On a device with no saved pick, OpenChamber 2.x restores a reopened session's effort from this
|
|
877
|
+
* record and resends it with the next prompt, so a stale level silently resets the user's choice.
|
|
878
|
+
*/
|
|
879
|
+
syncRecordVariant(s) {
|
|
880
|
+
if (s.info.model !== undefined)
|
|
881
|
+
s.info.model = { ...s.info.model, variant: s.thinkingLevel ?? "default" };
|
|
882
|
+
}
|
|
873
883
|
/**
|
|
874
884
|
* Single funnel for "pi is now running this model": adopt it, persist it for
|
|
875
885
|
* the next cold start, and tell the UI. Returns whether anything changed.
|
package/dist/v2.d.ts
CHANGED
package/dist/v2.js
CHANGED
|
@@ -44,7 +44,8 @@ function toV2Model(m) {
|
|
|
44
44
|
providerID: m.providerID,
|
|
45
45
|
name: m.name,
|
|
46
46
|
capabilities: { tools: true, input: inputs.length > 0 ? inputs : ["text"], output: ["text"] },
|
|
47
|
-
|
|
47
|
+
// OpenChamber 2.x reads a model's efforts only from this array; empty hides the picker.
|
|
48
|
+
variants: Object.entries(m.variants ?? {}).map(([id, settings]) => ({ id, settings })),
|
|
48
49
|
time: { released: 0 },
|
|
49
50
|
cost: [
|
|
50
51
|
{ input: m.cost.input, output: m.cost.output, cache: { read: m.cost.cache.read, write: m.cost.cache.write } },
|