antechamber 1.0.8 → 1.0.9

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.9] - 2026-10-21
11
+
12
+ ### Fixed
13
+
14
+ - Reduce repeated native-title scans and task-record lookups while restoring and listing sessions,
15
+ preserving first-list costs, child-session identities and later native updates.
16
+
10
17
  ## [1.0.8] - 2026-10-20
11
18
 
12
19
  ### Fixed
@@ -107,6 +107,7 @@ export declare class SessionManager {
107
107
  private rollbackCreate;
108
108
  private applyNativeTitle;
109
109
  private refreshNativeTitles;
110
+ private refreshNativeTitle;
110
111
  private wirePi;
111
112
  /** pi writes `<ts>_<sessionId>.jsonl` at startup — poll briefly for it. */
112
113
  private resolvePiFile;
package/dist/sessions.js CHANGED
@@ -209,13 +209,15 @@ export class SessionManager {
209
209
  this.emit(s.info.directory, { type: "session.updated", properties: { info: s.info } });
210
210
  }
211
211
  refreshNativeTitles() {
212
- for (const s of this.sessions.values()) {
213
- if (s.pi !== undefined || s.restored === undefined || !existsSync(s.restored.piPath))
214
- continue;
215
- const metadata = readSessionTitle(s.restored.piPath, s.restored.piSessionId);
216
- if (metadata.titleSource === "native")
217
- this.applyNativeTitle(s, metadata.title);
218
- }
212
+ for (const s of this.sessions.values())
213
+ this.refreshNativeTitle(s);
214
+ }
215
+ refreshNativeTitle(s) {
216
+ if (s.pi !== undefined || s.restored === undefined || !existsSync(s.restored.piPath))
217
+ return;
218
+ const metadata = readSessionTitle(s.restored.piPath, s.restored.piSessionId);
219
+ if (metadata.titleSource === "native")
220
+ this.applyNativeTitle(s, metadata.title);
219
221
  }
220
222
  wirePi(s, pi) {
221
223
  s.unwirePi?.();
@@ -345,7 +347,7 @@ export class SessionManager {
345
347
  return undefined;
346
348
  const live = this.sessions.get(sessionId);
347
349
  if (live !== undefined) {
348
- this.refreshNativeTitles();
350
+ this.refreshNativeTitle(live);
349
351
  this.sessionInfo(live);
350
352
  return live;
351
353
  }
@@ -51,8 +51,6 @@ export declare class SubagentBridge {
51
51
  private readonly children;
52
52
  private readonly calls;
53
53
  private readonly removed;
54
- /** Parent pi session of each task record already read; a record's parent never changes. */
55
- private readonly taskParents;
56
54
  constructor(store: SessionStore, sessions: Map<string, ManagedSession>, emit: (directory: string, event: OCEvent) => void, parse: ParseHistory);
57
55
  private archive;
58
56
  private sessionId;
package/dist/subagents.js CHANGED
@@ -211,9 +211,81 @@ function childRecord(id, path) {
211
211
  const data = jsonObject(path);
212
212
  return data?.task_id === id && typeof data.parent_session_id === "string" ? data : undefined;
213
213
  }
214
+ // History parsing calls the exported helpers synchronously, including from nested child discovery.
215
+ let taskLookupPass;
216
+ function withTaskLookups(store, action) {
217
+ if (taskLookupPass?.store === store)
218
+ return action();
219
+ const previous = taskLookupPass;
220
+ const pass = {
221
+ store,
222
+ childIds: store.childIds(),
223
+ identities: new Map(),
224
+ childByPi: new Map(),
225
+ piByChild: new Map(),
226
+ };
227
+ try {
228
+ taskLookupPass = pass;
229
+ for (const id of pass.childIds) {
230
+ const entry = store.get(id);
231
+ if (entry?.task)
232
+ indexChildIdentity(store, id, entry.task, entry.deleted ? undefined : entry.piSessionId);
233
+ }
234
+ return action();
235
+ }
236
+ finally {
237
+ taskLookupPass = previous;
238
+ }
239
+ }
240
+ function indexChildIdentity(store, id, task, piSessionId) {
241
+ const pass = taskLookupPass;
242
+ if (pass?.store !== store)
243
+ return;
244
+ let parents = pass.identities.get(task.id);
245
+ if (parents === undefined)
246
+ pass.identities.set(task.id, (parents = new Map()));
247
+ if (!parents.has(task.parentPiId))
248
+ parents.set(task.parentPiId, id);
249
+ if (piSessionId !== undefined) {
250
+ const previous = pass.piByChild.get(id);
251
+ if (previous !== undefined && previous !== piSessionId && pass.childByPi.get(previous) === id)
252
+ pass.childByPi.delete(previous);
253
+ pass.piByChild.set(id, piSessionId);
254
+ pass.childByPi.set(piSessionId, id);
255
+ }
256
+ }
257
+ function indexedTaskRecords() {
258
+ const pass = taskLookupPass;
259
+ if (pass === undefined)
260
+ return undefined;
261
+ if (pass.records !== undefined)
262
+ return pass.records;
263
+ const all = [];
264
+ const byId = new Map();
265
+ const byParent = new Map();
266
+ for (const { id, path } of taskFiles()) {
267
+ const data = childRecord(id, path);
268
+ if (data === undefined)
269
+ continue;
270
+ const row = { id, path, data };
271
+ all.push(row);
272
+ const sameId = byId.get(id) ?? [];
273
+ sameId.push(row);
274
+ byId.set(id, sameId);
275
+ const parent = String(data.parent_session_id);
276
+ const sameParent = byParent.get(parent) ?? [];
277
+ sameParent.push(row);
278
+ byParent.set(parent, sameParent);
279
+ }
280
+ return (pass.records = { all, byId, byParent });
281
+ }
214
282
  /** Keep bookmarks for known tasks; a bare native id is only safe while it has one parent. */
215
- export function taskSessionId(id, parentPiId, store = new SessionStore()) {
216
- for (const sessionID of store.childIds()) {
283
+ export function taskSessionId(id, parentPiId, store = taskLookupPass?.store ?? new SessionStore()) {
284
+ const pass = taskLookupPass?.store === store ? taskLookupPass : undefined;
285
+ const known = pass?.identities.get(id)?.get(parentPiId);
286
+ if (known !== undefined)
287
+ return known;
288
+ for (const sessionID of pass === undefined ? store.childIds() : []) {
217
289
  const task = store.get(sessionID)?.task;
218
290
  if (task?.id === id && task.parentPiId === parentPiId)
219
291
  return sessionID;
@@ -228,6 +300,10 @@ export function taskSessionId(id, parentPiId, store = new SessionStore()) {
228
300
  const qualifiedID = ocIdFor(`${parentPiId}:${id}`);
229
301
  if (store.get(qualifiedID))
230
302
  return qualifiedID;
303
+ const indexed = indexedTaskRecords();
304
+ if (indexed !== undefined) {
305
+ return indexed.byId.get(id)?.some(({ data }) => data.parent_session_id !== parentPiId) ? qualifiedID : legacyID;
306
+ }
231
307
  for (const root of taskStores()) {
232
308
  const data = childRecord(id, join(root, "tasks", `${id}.json`));
233
309
  if (data && data.parent_session_id !== parentPiId)
@@ -236,10 +312,11 @@ export function taskSessionId(id, parentPiId, store = new SessionStore()) {
236
312
  return legacyID;
237
313
  }
238
314
  /** Reverse lookup never resolves an unbound, ambiguous bare id by store enumeration order. */
239
- export function findTaskBySession(sessionId, store = new SessionStore()) {
315
+ export function findTaskBySession(sessionId, store = taskLookupPass?.store ?? new SessionStore()) {
240
316
  let found;
241
- for (const { id, path } of taskFiles()) {
242
- const data = childRecord(id, path);
317
+ const rows = indexedTaskRecords()?.all ?? taskFiles();
318
+ for (const { id, path, data: cached } of rows) {
319
+ const data = cached ?? childRecord(id, path);
243
320
  if (!data || taskSessionId(id, String(data.parent_session_id), store) !== sessionId)
244
321
  continue;
245
322
  if (found && found.data.parent_session_id !== data.parent_session_id)
@@ -270,10 +347,11 @@ export function evalAgentSession(parentPiId, args, claimed, after) {
270
347
  if (parentPiId === undefined || typeof args.prompt !== "string")
271
348
  return undefined;
272
349
  let best;
273
- for (const { id, path } of taskFiles()) {
350
+ const rows = indexedTaskRecords()?.byParent.get(parentPiId) ?? (taskLookupPass ? [] : taskFiles());
351
+ for (const { id, path, data: cached } of rows) {
274
352
  if (claimed.has(id))
275
353
  continue;
276
- const data = childRecord(id, path);
354
+ const data = cached ?? childRecord(id, path);
277
355
  if (data?.parent_session_id !== parentPiId)
278
356
  continue;
279
357
  const spec = record(data.spawn_spec) ? data.spawn_spec : {};
@@ -306,6 +384,9 @@ export function evalAgentSession(parentPiId, args, claimed, after) {
306
384
  export function findTask(id, parentPiId) {
307
385
  if (!taskId(id))
308
386
  return undefined;
387
+ const indexed = indexedTaskRecords();
388
+ if (indexed !== undefined)
389
+ return indexed.byId.get(id)?.find(({ data }) => data.parent_session_id === parentPiId);
309
390
  for (const store of taskStores()) {
310
391
  const path = join(store, "tasks", `${id}.json`);
311
392
  const data = jsonObject(path);
@@ -391,8 +472,6 @@ export class SubagentBridge {
391
472
  children = new Map();
392
473
  calls = new Map();
393
474
  removed = new Set();
394
- /** Parent pi session of each task record already read; a record's parent never changes. */
395
- taskParents = new Map();
396
475
  constructor(store, sessions, emit, parse) {
397
476
  this.store = store;
398
477
  this.sessions = sessions;
@@ -501,7 +580,7 @@ export class SubagentBridge {
501
580
  return true;
502
581
  }
503
582
  discover(parent, messages) {
504
- this.discoverParts(parent, messages.flatMap((message) => message.parts), false);
583
+ withTaskLookups(this.store, () => this.discoverParts(parent, messages.flatMap((message) => message.parts), false));
505
584
  }
506
585
  discoverParts(parent, parts, live) {
507
586
  this.wire(parent);
@@ -562,23 +641,26 @@ export class SubagentBridge {
562
641
  };
563
642
  this.children.set(session.info.id, child);
564
643
  this.sessions.set(session.info.id, session);
644
+ indexChildIdentity(this.store, session.info.id, task);
565
645
  this.wire(session);
566
646
  this.emit(session.info.directory, { type: "session.created", properties: { info: session.info } });
567
647
  return child;
568
648
  }
569
649
  restore(id) {
570
- const known = this.children.get(id);
571
- if (known)
572
- return known.session;
573
- const entry = this.store.get(id);
574
- if (!entry)
575
- return this.removed.has(id) ? undefined : this.adopt(id);
576
- if (entry.deleted || !entry.task || !entry.parentID)
577
- return undefined;
578
- const child = this.create(this.detachedParent(entry.parentID, entry.directory, entry), entry.task, id);
579
- child.session.info.title = entry.title ?? child.session.info.title;
580
- this.refresh(child, false);
581
- return child.session;
650
+ return withTaskLookups(this.store, () => {
651
+ const known = this.children.get(id);
652
+ if (known)
653
+ return known.session;
654
+ const entry = this.store.get(id);
655
+ if (!entry)
656
+ return this.removed.has(id) ? undefined : this.adopt(id);
657
+ if (entry.deleted || !entry.task || !entry.parentID)
658
+ return undefined;
659
+ const child = this.create(this.detachedParent(entry.parentID, entry.directory, entry), entry.task, id);
660
+ child.session.info.title = entry.title ?? child.session.info.title;
661
+ this.refresh(child, false);
662
+ return child.session;
663
+ });
582
664
  }
583
665
  /**
584
666
  * Tasks spawned outside a `task` tool call (e.g. `agent()` inside an `eval`
@@ -623,6 +705,8 @@ export class SubagentBridge {
623
705
  return this.sessions.get(id) ?? (entry ? this.detachedParent(id, entry.directory, entry) : undefined);
624
706
  }
625
707
  childIdsByPi() {
708
+ if (taskLookupPass?.store === this.store)
709
+ return taskLookupPass.childByPi;
626
710
  const byPi = new Map();
627
711
  for (const id of this.store.childIds()) {
628
712
  const entry = this.store.get(id);
@@ -656,26 +740,20 @@ export class SubagentBridge {
656
740
  };
657
741
  }
658
742
  restoreAll() {
659
- for (const id of this.store.childIds())
660
- this.restore(id);
661
- const childByPi = this.childIdsByPi();
662
- for (const { id, path } of taskFiles()) {
663
- let parentPi = this.taskParents.get(path);
664
- if (parentPi === undefined) {
665
- const parent = childRecord(id, path)?.parent_session_id;
666
- if (typeof parent !== "string")
743
+ withTaskLookups(this.store, () => {
744
+ for (const id of taskLookupPass?.childIds ?? [])
745
+ this.restore(id);
746
+ const childByPi = this.childIdsByPi();
747
+ for (const { id, path, data } of indexedTaskRecords()?.all ?? []) {
748
+ const parentPi = String(data.parent_session_id);
749
+ const sessionId = taskSessionId(id, parentPi, this.store);
750
+ if (this.children.has(sessionId) || this.removed.has(sessionId) || this.store.get(sessionId))
751
+ continue;
752
+ if (!this.parentFor(parentPi, childByPi))
667
753
  continue;
668
- this.taskParents.set(path, (parentPi = parent));
669
- }
670
- const sessionId = taskSessionId(id, parentPi, this.store);
671
- if (this.children.has(sessionId) || this.removed.has(sessionId) || this.store.get(sessionId))
672
- continue;
673
- if (!this.parentFor(parentPi, childByPi))
674
- continue;
675
- const data = childRecord(id, path);
676
- if (data)
677
754
  this.adopt(sessionId, { path, data });
678
- }
755
+ }
756
+ });
679
757
  }
680
758
  owns(id) {
681
759
  return this.children.has(id);
@@ -936,6 +1014,7 @@ export class SubagentBridge {
936
1014
  created: info.time.created,
937
1015
  updated: info.time.updated,
938
1016
  });
1017
+ indexChildIdentity(this.store, info.id, child.task, child.session.piSessionId ?? child.task.id);
939
1018
  }
940
1019
  catch (error) {
941
1020
  if (!(error instanceof SessionPersistenceError))
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antechamber-omo-compat",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
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.8",
3
+ "version": "1.0.9",
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.8",
3
+ "version": "1.0.9",
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.8",
3
+ "version": "1.0.9",
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",