antechamber 1.0.8 → 1.0.10
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 +14 -0
- package/dist/sessions.d.ts +1 -0
- package/dist/sessions.js +10 -8
- package/dist/subagents.d.ts +0 -2
- package/dist/subagents.js +145 -62
- 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,20 @@ and this project uses [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.0.10] - 2026-10-21
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Keep sessions with many child tasks responsive by ignoring unchanged task snapshots and saving
|
|
15
|
+
each snapshot's changes in one registry write.
|
|
16
|
+
|
|
17
|
+
## [1.0.9] - 2026-10-21
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Reduce repeated native-title scans and task-record lookups while restoring and listing sessions,
|
|
22
|
+
preserving first-list costs, child-session identities and later native updates.
|
|
23
|
+
|
|
10
24
|
## [1.0.8] - 2026-10-20
|
|
11
25
|
|
|
12
26
|
### Fixed
|
package/dist/sessions.d.ts
CHANGED
|
@@ -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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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.
|
|
350
|
+
this.refreshNativeTitle(live);
|
|
349
351
|
this.sessionInfo(live);
|
|
350
352
|
return live;
|
|
351
353
|
}
|
package/dist/subagents.d.ts
CHANGED
|
@@ -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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** OmO task cards and read-only views of their independently persisted sessions. */
|
|
2
2
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, renameSync, statSync, watch, writeFileSync, } from "node:fs";
|
|
3
3
|
import { basename, dirname, join } from "node:path";
|
|
4
|
+
import { isDeepStrictEqual } from "node:util";
|
|
4
5
|
import { piAgentDir } from "./agent-dir.js";
|
|
5
6
|
import { syncSessionCost } from "./pi-event-translation.js";
|
|
6
7
|
import { ocIdFor, SessionPersistenceError, SessionStore } from "./session-store.js";
|
|
@@ -211,9 +212,81 @@ function childRecord(id, path) {
|
|
|
211
212
|
const data = jsonObject(path);
|
|
212
213
|
return data?.task_id === id && typeof data.parent_session_id === "string" ? data : undefined;
|
|
213
214
|
}
|
|
215
|
+
// History parsing calls the exported helpers synchronously, including from nested child discovery.
|
|
216
|
+
let taskLookupPass;
|
|
217
|
+
function withTaskLookups(store, action) {
|
|
218
|
+
if (taskLookupPass?.store === store)
|
|
219
|
+
return action();
|
|
220
|
+
const previous = taskLookupPass;
|
|
221
|
+
const pass = {
|
|
222
|
+
store,
|
|
223
|
+
childIds: store.childIds(),
|
|
224
|
+
identities: new Map(),
|
|
225
|
+
childByPi: new Map(),
|
|
226
|
+
piByChild: new Map(),
|
|
227
|
+
};
|
|
228
|
+
try {
|
|
229
|
+
taskLookupPass = pass;
|
|
230
|
+
for (const id of pass.childIds) {
|
|
231
|
+
const entry = store.get(id);
|
|
232
|
+
if (entry?.task)
|
|
233
|
+
indexChildIdentity(store, id, entry.task, entry.deleted ? undefined : entry.piSessionId);
|
|
234
|
+
}
|
|
235
|
+
return action();
|
|
236
|
+
}
|
|
237
|
+
finally {
|
|
238
|
+
taskLookupPass = previous;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function indexChildIdentity(store, id, task, piSessionId) {
|
|
242
|
+
const pass = taskLookupPass;
|
|
243
|
+
if (pass?.store !== store)
|
|
244
|
+
return;
|
|
245
|
+
let parents = pass.identities.get(task.id);
|
|
246
|
+
if (parents === undefined)
|
|
247
|
+
pass.identities.set(task.id, (parents = new Map()));
|
|
248
|
+
if (!parents.has(task.parentPiId))
|
|
249
|
+
parents.set(task.parentPiId, id);
|
|
250
|
+
if (piSessionId !== undefined) {
|
|
251
|
+
const previous = pass.piByChild.get(id);
|
|
252
|
+
if (previous !== undefined && previous !== piSessionId && pass.childByPi.get(previous) === id)
|
|
253
|
+
pass.childByPi.delete(previous);
|
|
254
|
+
pass.piByChild.set(id, piSessionId);
|
|
255
|
+
pass.childByPi.set(piSessionId, id);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function indexedTaskRecords() {
|
|
259
|
+
const pass = taskLookupPass;
|
|
260
|
+
if (pass === undefined)
|
|
261
|
+
return undefined;
|
|
262
|
+
if (pass.records !== undefined)
|
|
263
|
+
return pass.records;
|
|
264
|
+
const all = [];
|
|
265
|
+
const byId = new Map();
|
|
266
|
+
const byParent = new Map();
|
|
267
|
+
for (const { id, path } of taskFiles()) {
|
|
268
|
+
const data = childRecord(id, path);
|
|
269
|
+
if (data === undefined)
|
|
270
|
+
continue;
|
|
271
|
+
const row = { id, path, data };
|
|
272
|
+
all.push(row);
|
|
273
|
+
const sameId = byId.get(id) ?? [];
|
|
274
|
+
sameId.push(row);
|
|
275
|
+
byId.set(id, sameId);
|
|
276
|
+
const parent = String(data.parent_session_id);
|
|
277
|
+
const sameParent = byParent.get(parent) ?? [];
|
|
278
|
+
sameParent.push(row);
|
|
279
|
+
byParent.set(parent, sameParent);
|
|
280
|
+
}
|
|
281
|
+
return (pass.records = { all, byId, byParent });
|
|
282
|
+
}
|
|
214
283
|
/** 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
|
-
|
|
284
|
+
export function taskSessionId(id, parentPiId, store = taskLookupPass?.store ?? new SessionStore()) {
|
|
285
|
+
const pass = taskLookupPass?.store === store ? taskLookupPass : undefined;
|
|
286
|
+
const known = pass?.identities.get(id)?.get(parentPiId);
|
|
287
|
+
if (known !== undefined)
|
|
288
|
+
return known;
|
|
289
|
+
for (const sessionID of pass === undefined ? store.childIds() : []) {
|
|
217
290
|
const task = store.get(sessionID)?.task;
|
|
218
291
|
if (task?.id === id && task.parentPiId === parentPiId)
|
|
219
292
|
return sessionID;
|
|
@@ -228,6 +301,10 @@ export function taskSessionId(id, parentPiId, store = new SessionStore()) {
|
|
|
228
301
|
const qualifiedID = ocIdFor(`${parentPiId}:${id}`);
|
|
229
302
|
if (store.get(qualifiedID))
|
|
230
303
|
return qualifiedID;
|
|
304
|
+
const indexed = indexedTaskRecords();
|
|
305
|
+
if (indexed !== undefined) {
|
|
306
|
+
return indexed.byId.get(id)?.some(({ data }) => data.parent_session_id !== parentPiId) ? qualifiedID : legacyID;
|
|
307
|
+
}
|
|
231
308
|
for (const root of taskStores()) {
|
|
232
309
|
const data = childRecord(id, join(root, "tasks", `${id}.json`));
|
|
233
310
|
if (data && data.parent_session_id !== parentPiId)
|
|
@@ -236,10 +313,11 @@ export function taskSessionId(id, parentPiId, store = new SessionStore()) {
|
|
|
236
313
|
return legacyID;
|
|
237
314
|
}
|
|
238
315
|
/** Reverse lookup never resolves an unbound, ambiguous bare id by store enumeration order. */
|
|
239
|
-
export function findTaskBySession(sessionId, store = new SessionStore()) {
|
|
316
|
+
export function findTaskBySession(sessionId, store = taskLookupPass?.store ?? new SessionStore()) {
|
|
240
317
|
let found;
|
|
241
|
-
|
|
242
|
-
|
|
318
|
+
const rows = indexedTaskRecords()?.all ?? taskFiles();
|
|
319
|
+
for (const { id, path, data: cached } of rows) {
|
|
320
|
+
const data = cached ?? childRecord(id, path);
|
|
243
321
|
if (!data || taskSessionId(id, String(data.parent_session_id), store) !== sessionId)
|
|
244
322
|
continue;
|
|
245
323
|
if (found && found.data.parent_session_id !== data.parent_session_id)
|
|
@@ -270,10 +348,11 @@ export function evalAgentSession(parentPiId, args, claimed, after) {
|
|
|
270
348
|
if (parentPiId === undefined || typeof args.prompt !== "string")
|
|
271
349
|
return undefined;
|
|
272
350
|
let best;
|
|
273
|
-
|
|
351
|
+
const rows = indexedTaskRecords()?.byParent.get(parentPiId) ?? (taskLookupPass ? [] : taskFiles());
|
|
352
|
+
for (const { id, path, data: cached } of rows) {
|
|
274
353
|
if (claimed.has(id))
|
|
275
354
|
continue;
|
|
276
|
-
const data = childRecord(id, path);
|
|
355
|
+
const data = cached ?? childRecord(id, path);
|
|
277
356
|
if (data?.parent_session_id !== parentPiId)
|
|
278
357
|
continue;
|
|
279
358
|
const spec = record(data.spawn_spec) ? data.spawn_spec : {};
|
|
@@ -306,6 +385,9 @@ export function evalAgentSession(parentPiId, args, claimed, after) {
|
|
|
306
385
|
export function findTask(id, parentPiId) {
|
|
307
386
|
if (!taskId(id))
|
|
308
387
|
return undefined;
|
|
388
|
+
const indexed = indexedTaskRecords();
|
|
389
|
+
if (indexed !== undefined)
|
|
390
|
+
return indexed.byId.get(id)?.find(({ data }) => data.parent_session_id === parentPiId);
|
|
309
391
|
for (const store of taskStores()) {
|
|
310
392
|
const path = join(store, "tasks", `${id}.json`);
|
|
311
393
|
const data = jsonObject(path);
|
|
@@ -391,8 +473,6 @@ export class SubagentBridge {
|
|
|
391
473
|
children = new Map();
|
|
392
474
|
calls = new Map();
|
|
393
475
|
removed = new Set();
|
|
394
|
-
/** Parent pi session of each task record already read; a record's parent never changes. */
|
|
395
|
-
taskParents = new Map();
|
|
396
476
|
constructor(store, sessions, emit, parse) {
|
|
397
477
|
this.store = store;
|
|
398
478
|
this.sessions = sessions;
|
|
@@ -462,28 +542,31 @@ export class SubagentBridge {
|
|
|
462
542
|
observe(parent, ev) {
|
|
463
543
|
if (ev.name !== "omo.task.updated" || !record(ev.data) || !Array.isArray(ev.data.tasks))
|
|
464
544
|
return;
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
this.
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
545
|
+
const rows = ev.data.tasks;
|
|
546
|
+
this.store.batch(() => withTaskLookups(this.store, () => {
|
|
547
|
+
for (const row of rows) {
|
|
548
|
+
if (!record(row) || !taskId(row.task_id))
|
|
549
|
+
continue;
|
|
550
|
+
if (typeof row.parent_session_id === "string" &&
|
|
551
|
+
row.parent_session_id !== (parent.piSessionId ?? parent.restored?.piSessionId))
|
|
552
|
+
continue;
|
|
553
|
+
const id = this.sessionId(parent, row.task_id);
|
|
554
|
+
if (!this.children.has(id))
|
|
555
|
+
this.restore(id);
|
|
556
|
+
const child = this.children.get(id);
|
|
557
|
+
if (!child || child.session.info.parentID !== parent.info.id)
|
|
558
|
+
continue;
|
|
559
|
+
const data = snapshotState(row);
|
|
560
|
+
// OmO re-sends every row ~150 ms after each progress event of any task; only changes matter.
|
|
561
|
+
if (Object.entries(data).every(([key, value]) => isDeepStrictEqual(child.task.details[key], value)))
|
|
562
|
+
continue;
|
|
563
|
+
this.wire(parent);
|
|
564
|
+
if (!this.applyNative(child, data))
|
|
565
|
+
continue;
|
|
566
|
+
child.live = true;
|
|
567
|
+
this.refresh(child, true, false);
|
|
568
|
+
}
|
|
569
|
+
}));
|
|
487
570
|
}
|
|
488
571
|
/**
|
|
489
572
|
* Merge task state reported by OmO (record, snapshot) unless it is older than what is known: an
|
|
@@ -501,7 +584,7 @@ export class SubagentBridge {
|
|
|
501
584
|
return true;
|
|
502
585
|
}
|
|
503
586
|
discover(parent, messages) {
|
|
504
|
-
this.discoverParts(parent, messages.flatMap((message) => message.parts), false);
|
|
587
|
+
withTaskLookups(this.store, () => this.discoverParts(parent, messages.flatMap((message) => message.parts), false));
|
|
505
588
|
}
|
|
506
589
|
discoverParts(parent, parts, live) {
|
|
507
590
|
this.wire(parent);
|
|
@@ -562,23 +645,26 @@ export class SubagentBridge {
|
|
|
562
645
|
};
|
|
563
646
|
this.children.set(session.info.id, child);
|
|
564
647
|
this.sessions.set(session.info.id, session);
|
|
648
|
+
indexChildIdentity(this.store, session.info.id, task);
|
|
565
649
|
this.wire(session);
|
|
566
650
|
this.emit(session.info.directory, { type: "session.created", properties: { info: session.info } });
|
|
567
651
|
return child;
|
|
568
652
|
}
|
|
569
653
|
restore(id) {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
654
|
+
return withTaskLookups(this.store, () => {
|
|
655
|
+
const known = this.children.get(id);
|
|
656
|
+
if (known)
|
|
657
|
+
return known.session;
|
|
658
|
+
const entry = this.store.get(id);
|
|
659
|
+
if (!entry)
|
|
660
|
+
return this.removed.has(id) ? undefined : this.adopt(id);
|
|
661
|
+
if (entry.deleted || !entry.task || !entry.parentID)
|
|
662
|
+
return undefined;
|
|
663
|
+
const child = this.create(this.detachedParent(entry.parentID, entry.directory, entry), entry.task, id);
|
|
664
|
+
child.session.info.title = entry.title ?? child.session.info.title;
|
|
665
|
+
this.refresh(child, false);
|
|
666
|
+
return child.session;
|
|
667
|
+
});
|
|
582
668
|
}
|
|
583
669
|
/**
|
|
584
670
|
* Tasks spawned outside a `task` tool call (e.g. `agent()` inside an `eval`
|
|
@@ -623,6 +709,8 @@ export class SubagentBridge {
|
|
|
623
709
|
return this.sessions.get(id) ?? (entry ? this.detachedParent(id, entry.directory, entry) : undefined);
|
|
624
710
|
}
|
|
625
711
|
childIdsByPi() {
|
|
712
|
+
if (taskLookupPass?.store === this.store)
|
|
713
|
+
return taskLookupPass.childByPi;
|
|
626
714
|
const byPi = new Map();
|
|
627
715
|
for (const id of this.store.childIds()) {
|
|
628
716
|
const entry = this.store.get(id);
|
|
@@ -656,26 +744,20 @@ export class SubagentBridge {
|
|
|
656
744
|
};
|
|
657
745
|
}
|
|
658
746
|
restoreAll() {
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
const
|
|
666
|
-
if (
|
|
747
|
+
withTaskLookups(this.store, () => {
|
|
748
|
+
for (const id of taskLookupPass?.childIds ?? [])
|
|
749
|
+
this.restore(id);
|
|
750
|
+
const childByPi = this.childIdsByPi();
|
|
751
|
+
for (const { id, path, data } of indexedTaskRecords()?.all ?? []) {
|
|
752
|
+
const parentPi = String(data.parent_session_id);
|
|
753
|
+
const sessionId = taskSessionId(id, parentPi, this.store);
|
|
754
|
+
if (this.children.has(sessionId) || this.removed.has(sessionId) || this.store.get(sessionId))
|
|
755
|
+
continue;
|
|
756
|
+
if (!this.parentFor(parentPi, childByPi))
|
|
667
757
|
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
758
|
this.adopt(sessionId, { path, data });
|
|
678
|
-
|
|
759
|
+
}
|
|
760
|
+
});
|
|
679
761
|
}
|
|
680
762
|
owns(id) {
|
|
681
763
|
return this.children.has(id);
|
|
@@ -936,6 +1018,7 @@ export class SubagentBridge {
|
|
|
936
1018
|
created: info.time.created,
|
|
937
1019
|
updated: info.time.updated,
|
|
938
1020
|
});
|
|
1021
|
+
indexChildIdentity(this.store, info.id, child.task, child.session.piSessionId ?? child.task.id);
|
|
939
1022
|
}
|
|
940
1023
|
catch (error) {
|
|
941
1024
|
if (!(error instanceof SessionPersistenceError))
|