antechamber 0.2.11 → 0.2.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 CHANGED
@@ -7,6 +7,22 @@ and this project uses [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.13] - 2026-10-16
11
+
12
+ ### Fixed
13
+
14
+ - Show and resume turns that another OmO client, such as the terminal, appended to a session while the connector
15
+ was detached. Previously the connector stayed pinned to its last saved message. A committed revert's abandoned
16
+ branch stays hidden.
17
+
18
+ ## [0.2.12] - 2026-10-16
19
+
20
+ ### Fixed
21
+
22
+ - Preserve recorded conversation progress when the agent process exits before
23
+ `agent_end`, and retain successful background compaction across revival.
24
+ Checkpoints respect reverted selections and do not switch to sibling branches.
25
+
10
26
  ## [0.2.11] - 2026-10-16
11
27
 
12
28
  ### Fixed
@@ -544,6 +544,7 @@ export class PiEventTranslator {
544
544
  },
545
545
  });
546
546
  if (ev.aborted !== true && typeof result?.summary === "string" && typeof result.firstKeptEntryId === "string") {
547
+ recordTurn(s);
547
548
  const messages = this.services.ensureMessages(s);
548
549
  let key;
549
550
  // compaction_end follows persistence, but its result omits the entry ID.
@@ -563,6 +564,7 @@ export class PiEventTranslator {
563
564
  for (const part of message.parts)
564
565
  this.emitPart(s, part);
565
566
  }
567
+ this.services.persistMeta(s);
566
568
  }
567
569
  this.services.emit(s.info.directory, { type: "session.updated", properties: { info: s.info } });
568
570
  if (s.compactingFromIdle) {
@@ -305,6 +305,8 @@ function sessionTreeState(value) {
305
305
  return false;
306
306
  if (value.leafId !== undefined && value.leafId !== null && typeof value.leafId !== "string")
307
307
  return false;
308
+ if (value.fileSize !== undefined && (typeof value.fileSize !== "number" || !Number.isFinite(value.fileSize)))
309
+ return false;
308
310
  if (value.revert !== undefined) {
309
311
  if (!record(value.revert) ||
310
312
  typeof value.revert.messageID !== "string" ||
@@ -18,6 +18,8 @@ export interface SessionTreeState {
18
18
  messages: [OCMessageWithParts, OCMessageWithParts];
19
19
  }>;
20
20
  leafId?: string | null;
21
+ /** Session file size when this connector last saved a live runtime; growth past it came from another client. */
22
+ fileSize?: number;
21
23
  revert?: {
22
24
  messageID: string;
23
25
  previousLeafId: string | null;
@@ -52,8 +54,15 @@ export declare class SessionTreeError extends Error {
52
54
  export declare function durableShellTree(tree: SessionTreeState): SessionTreeState;
53
55
  export declare function getEntries(pi: PiRpcClient, signal?: AbortSignal): Promise<Entries>;
54
56
  export declare function readSessionTree(piPath: string): Entries;
55
- /** agent_end runs after message persistence; no async lease or timing race. */
57
+ /** Checkpoint persisted progress after agent_end, successful compaction, or process exit. */
56
58
  export declare function recordTurn(s: ManagedSession): void;
59
+ /**
60
+ * Another OmO client (e.g. the terminal) resumes at the file tail and appends there.
61
+ * Follow that tail when it descends from the saved selection and was written after
62
+ * this connector last saved, so a committed revert's abandoned branch stays hidden.
63
+ * Registries saved before the size marker existed follow any descendant tail.
64
+ */
65
+ export declare function followExternalAppends(s: ManagedSession, piPath: string): void;
57
66
  export declare function branchEntries(tree: Entries, leafId?: string | null): TreeEntry[];
58
67
  /** Live ids belong to the UI; restored ids belong to JSONL offsets. Save both. */
59
68
  export declare function bindMessageEntries(s: ManagedSession, tree: Entries): void;
@@ -1,5 +1,5 @@
1
1
  import { RPC_COMMAND_TIMEOUT_MS } from "./pi-rpc.js";
2
- import { readFileSync } from "node:fs";
2
+ import { readFileSync, statSync } from "node:fs";
3
3
  import { contextMessageID, nativePromptInput } from "./prompt-parts.js";
4
4
  export class SessionTreeError extends Error {
5
5
  }
@@ -44,10 +44,46 @@ export function readSessionTree(piPath) {
44
44
  }
45
45
  return { entries, leafId: entries.at(-1)?.id ?? null };
46
46
  }
47
- /** agent_end runs after message persistence; no async lease or timing race. */
47
+ /** Checkpoint persisted progress after agent_end, successful compaction, or process exit. */
48
48
  export function recordTurn(s) {
49
- if (s.piPath)
50
- bindMessageEntries(s, readSessionTree(s.piPath));
49
+ if (!s.piPath)
50
+ return;
51
+ const tree = readSessionTree(s.piPath);
52
+ // Disk order is not navigation: only a descendant can advance a saved selection.
53
+ // An ancestor, missing selection, or newer sibling must leave its bindings intact.
54
+ if (!s.tree.revert &&
55
+ s.tree.leafId !== undefined &&
56
+ s.tree.leafId !== null &&
57
+ !branchEntries(tree).some((entry) => entry.id === s.tree.leafId))
58
+ return;
59
+ bindMessageEntries(s, tree);
60
+ }
61
+ /**
62
+ * Another OmO client (e.g. the terminal) resumes at the file tail and appends there.
63
+ * Follow that tail when it descends from the saved selection and was written after
64
+ * this connector last saved, so a committed revert's abandoned branch stays hidden.
65
+ * Registries saved before the size marker existed follow any descendant tail.
66
+ */
67
+ export function followExternalAppends(s, piPath) {
68
+ const saved = s.tree.leafId;
69
+ if (s.tree.revert || saved === undefined || saved === null)
70
+ return;
71
+ const size = statSync(piPath, { throwIfNoEntry: false })?.size;
72
+ if (size === undefined || (s.tree.fileSize !== undefined && size <= s.tree.fileSize))
73
+ return;
74
+ let tree;
75
+ try {
76
+ tree = readSessionTree(piPath);
77
+ }
78
+ catch (error) {
79
+ // History parsing skips malformed lines; keep the saved selection rather than guess a tail.
80
+ if (error instanceof SessionTreeError)
81
+ return;
82
+ throw error;
83
+ }
84
+ if (tree.leafId === saved || !branchEntries(tree).some((entry) => entry.id === saved))
85
+ return;
86
+ s.tree.leafId = tree.leafId;
51
87
  }
52
88
  export function branchEntries(tree, leafId = tree.leafId) {
53
89
  const byId = new Map(tree.entries.map((entry) => [entry.id, entry]));
package/dist/sessions.js CHANGED
@@ -14,7 +14,7 @@ import { SessionLifecycle } from "./lifecycle.js";
14
14
  import { ApprovalBridge, extensionUiRequestFrom } from "./approvals.js";
15
15
  import { getAgentName, getExposedAgentNames } from "./agent-name.js";
16
16
  import { RequestBodyError } from "./http.js";
17
- import { bindMessageEntries, durableShellTree, forkConversation, getEntries, navigate, readSessionTree, revertConversation, SessionTreeError, unrevertConversation, } from "./session-tree.js";
17
+ import { bindMessageEntries, durableShellTree, followExternalAppends, forkConversation, getEntries, navigate, recordTurn, readSessionTree, revertConversation, SessionTreeError, unrevertConversation, } from "./session-tree.js";
18
18
  import { SubagentBridge } from "./subagents.js";
19
19
  import { MonitorSessions } from "./monitor-sessions.js";
20
20
  import { sessionDiff } from "./session-diff.js";
@@ -239,6 +239,13 @@ export class SessionManager {
239
239
  name: "UnknownError",
240
240
  data: { message: `pi process exited (code=${code})` },
241
241
  });
242
+ try {
243
+ recordTurn(s);
244
+ this.persistMeta(s);
245
+ }
246
+ catch (error) {
247
+ this.reportPersistenceError(s, error);
248
+ }
242
249
  delete s.interruptRequested;
243
250
  this.subagents.stopParent(s.info.id);
244
251
  this.monitors.stopParent(s.info.id);
@@ -1160,6 +1167,9 @@ export class SessionManager {
1160
1167
  if (this.monitors.owns(s.info.id))
1161
1168
  return this.monitors.messages(s.info.id);
1162
1169
  if (s.messages === undefined) {
1170
+ // A dormant session may have been continued by another OmO client since the last save.
1171
+ if (s.pi === undefined && s.restored !== undefined)
1172
+ followExternalAppends(s, s.restored.piPath);
1163
1173
  const admissions = s.tree.localShellTurns?.length;
1164
1174
  s.messages = parsePiSessionFile(s, getAgentName());
1165
1175
  this.subagents.discover(s, s.messages);
@@ -1327,6 +1337,12 @@ export class SessionManager {
1327
1337
  const piSessionId = s.piSessionId ?? s.restored?.piSessionId;
1328
1338
  if (piPath === undefined || piSessionId === undefined)
1329
1339
  return;
1340
+ // Only a live runtime vouches for the file contents; dormant saves keep the previous marker.
1341
+ if (s.pi !== undefined) {
1342
+ const size = statSync(piPath, { throwIfNoEntry: false })?.size;
1343
+ if (size !== undefined)
1344
+ s.tree.fileSize = size;
1345
+ }
1330
1346
  this.store.set(s.info.id, {
1331
1347
  ...(this.createdSessions.has(piSessionId) ? { connectorOwned: true } : {}),
1332
1348
  tree: durableShellTree(s.tree),
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antechamber-omo-graph",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antechamber",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
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",