antechamber 1.0.28 → 1.0.29

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,17 @@ and this project uses [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.0.29] - 2026-10-28
11
+
12
+ ### Fixed
13
+
14
+ - Stop OmO from cancelling a `/compact` that revives a session. OmO cancels a running compaction whenever an MCP
15
+ server's tools register, which a freshly started agent does for about a second, so the chat showed
16
+ "Compaction failed" twice (OpenChamber 2.5 draws these events as transcript rows) above a compaction that then
17
+ succeeded. A compaction now waits, at most 10 s, until OmO reports no MCP server still connecting. Cancellations
18
+ nobody asked for are retried up to four times instead of two, and a retried attempt is no longer reported as a
19
+ failure; a compaction the user stops still is.
20
+
10
21
  ## [1.0.28] - 2026-10-28
11
22
 
12
23
  - fix(mcp): sign in to MCP servers without a running OmO session (#79)
package/dist/mcp.d.ts CHANGED
@@ -23,6 +23,7 @@
23
23
  * An extension that registers its own `mcp` command makes OmO rename the builtin (`mcp:1`),
24
24
  * so the builtin is found by its source, not by name.
25
25
  */
26
+ import { PiRpcClient } from "./pi-rpc.js";
26
27
  import type { RouteServices } from "./route-services.js";
27
28
  type OCMcpStatus = {
28
29
  status: "connected";
@@ -64,6 +65,13 @@ export declare const MCP_AUTH_COMMAND_TIMEOUT_MS: number;
64
65
  /** OmO's runtime states fold onto OpenCode's; lazy servers are usable, so they read as connected. */
65
66
  export declare function openCodeMcpStatus(server: OmoMcpServer): OCMcpStatus;
66
67
  export declare function v2McpStatus(server: OmoMcpServer): V2McpStatus;
68
+ /**
69
+ * Resolves once no MCP server is still connecting, after `budgetMs`, or on abort. OmO cancels a running
70
+ * compaction whenever a server's tools register (setActiveTools -> abortCompaction, senpi
71
+ * chunks/chunk-NNQ7DPUD.js). A probe on 2026-10-28 found three servers connecting when get_state answered,
72
+ * all connected within about a second, each change announced by `loaded_surfaces_changed`.
73
+ */
74
+ export declare function waitForMcpServers(pi: PiRpcClient, budgetMs: number, signal?: AbortSignal): Promise<void>;
67
75
  /** The URL OmO prints on its own line after naming the server (`auth` and `auth-start` both do). */
68
76
  export declare function authorizationUrl(text: string, name: string): string | undefined;
69
77
  /**
package/dist/mcp.js CHANGED
@@ -95,6 +95,51 @@ async function loadedServers(pi) {
95
95
  ]
96
96
  : []);
97
97
  }
98
+ /**
99
+ * Resolves once no MCP server is still connecting, after `budgetMs`, or on abort. OmO cancels a running
100
+ * compaction whenever a server's tools register (setActiveTools -> abortCompaction, senpi
101
+ * chunks/chunk-NNQ7DPUD.js). A probe on 2026-10-28 found three servers connecting when get_state answered,
102
+ * all connected within about a second, each change announced by `loaded_surfaces_changed`.
103
+ */
104
+ export async function waitForMcpServers(pi, budgetMs, signal) {
105
+ const deadline = Date.now() + budgetMs;
106
+ let changed = false;
107
+ let wake = () => { };
108
+ const listener = (ev) => {
109
+ if (ev.type !== "loaded_surfaces_changed")
110
+ return;
111
+ changed = true;
112
+ wake();
113
+ };
114
+ pi.onEvent(listener);
115
+ try {
116
+ while (!signal?.aborted) {
117
+ changed = false;
118
+ const servers = await loadedServers(pi).catch(() => []);
119
+ if (!servers.some((server) => server.status === "connecting"))
120
+ return;
121
+ if (changed)
122
+ continue;
123
+ const left = deadline - Date.now();
124
+ if (left <= 0)
125
+ return;
126
+ await new Promise((resolve) => {
127
+ const done = () => {
128
+ clearTimeout(timer);
129
+ signal?.removeEventListener("abort", done);
130
+ wake = () => { };
131
+ resolve();
132
+ };
133
+ const timer = setTimeout(done, left);
134
+ signal?.addEventListener("abort", done, { once: true });
135
+ wake = done;
136
+ });
137
+ }
138
+ }
139
+ finally {
140
+ pi.off("event", listener);
141
+ }
142
+ }
98
143
  async function directoryServers(sessions, directory) {
99
144
  // A directory whose sessions are all dormant would list nothing. Its servers are mostly
100
145
  // the user's global and built-in ones, so any running session gives a better answer.
@@ -76,6 +76,8 @@ export declare class PiEventTranslator {
76
76
  private onToolEvent;
77
77
  private onMessageEvent;
78
78
  private onCompactionEvent;
79
+ /** Publishes a held compaction cancellation once sessionRpc will not retry it. */
80
+ releaseCompactionEnd(s: ManagedSession): void;
79
81
  private finishStreaming;
80
82
  private onMessageUpdate;
81
83
  private onProviderTools;
@@ -634,6 +634,11 @@ export class PiEventTranslator {
634
634
  onCompactionEvent(s, ev) {
635
635
  switch (ev.type) {
636
636
  case "compaction_start":
637
+ // sessionRpc retried a cancelled compaction: the UI still shows the first attempt running.
638
+ if (s.heldCompactionEnd !== undefined) {
639
+ delete s.heldCompactionEnd;
640
+ return;
641
+ }
637
642
  // An auto-compaction can fall between steps; its summary is a new step.
638
643
  this.completeAwaitingTools(s);
639
644
  s.compactionID = id("compaction");
@@ -654,6 +659,11 @@ export class PiEventTranslator {
654
659
  }
655
660
  return;
656
661
  case "compaction_end": {
662
+ const retry = s.compactRetry;
663
+ if (ev.aborted === true && retry !== undefined && retry.left > 0 && (s.abortRequests ?? 0) === retry.aborts) {
664
+ s.heldCompactionEnd = ev;
665
+ return;
666
+ }
657
667
  delete s.info.time.compacting;
658
668
  const result = ev.result;
659
669
  const inputID = s.compactionID ?? id("compaction");
@@ -715,6 +725,14 @@ export class PiEventTranslator {
715
725
  return;
716
726
  }
717
727
  }
728
+ /** Publishes a held compaction cancellation once sessionRpc will not retry it. */
729
+ releaseCompactionEnd(s) {
730
+ const ev = s.heldCompactionEnd;
731
+ if (ev === undefined)
732
+ return;
733
+ delete s.heldCompactionEnd;
734
+ this.onCompactionEvent(s, ev);
735
+ }
718
736
  finishStreaming(s) {
719
737
  if (s.streaming)
720
738
  s.streaming = undefined;
@@ -51,6 +51,13 @@ export interface ManagedSession extends ThinkingState {
51
51
  /** A manual compaction started while idle; its end returns the session to idle. */
52
52
  compactingFromIdle?: boolean;
53
53
  compactionID?: string;
54
+ /** Retries sessionRpc still owes a `compact` command that OmO cancels on its own. */
55
+ compactRetry?: {
56
+ left: number;
57
+ aborts: number;
58
+ };
59
+ /** A cancelled compaction_end held back while its retry may continue the same compaction. */
60
+ heldCompactionEnd?: Record<string, unknown>;
54
61
  /** Cancels the adapter-owned user bash waiter and its OmO execution. */
55
62
  cancelShell?: (interrupted?: boolean) => void;
56
63
  /**
package/dist/sessions.js CHANGED
@@ -20,6 +20,7 @@ import { MonitorSessions } from "./monitor-sessions.js";
20
20
  import { WorkStatusBridge } from "./work-status.js";
21
21
  import { OmoShells } from "./omo-shells.js";
22
22
  import { sessionDiff } from "./session-diff.js";
23
+ import { waitForMcpServers } from "./mcp.js";
23
24
  import { announceSessionNotice, noticeMessage, skillCommandText, slashTitlePrompt } from "./session-commands.js";
24
25
  import { ocIdFor, piSessionDir, piSessionsRootMtime, scanAllPiSessions, scanPiSessions, SessionStore, readSessionFileMeta, } from "./session-store.js";
25
26
  import { ADAPTER_VERSION } from "./version.js";
@@ -30,7 +31,11 @@ const PI_FILE_RESOLVE_RETRY_MS = 200;
30
31
  const SESSION_TITLE_MAX_CHARS = 60;
31
32
  /** OmO's reply when a compaction is aborted (`CompactionCancelledError`). */
32
33
  const OMO_COMPACTION_CANCELLED = "Compaction cancelled";
33
- const COMPACT_CANCEL_RETRIES = 2;
34
+ // Real revivals took two cancels before the third attempt committed (OmO compaction.log, 2026-10-27),
35
+ // so two retries left no margin for one more MCP server connecting late.
36
+ const COMPACT_CANCEL_RETRIES = 4;
37
+ /** Longest a compaction waits for connecting MCP servers; a stuck server falls back to the retries. */
38
+ const MCP_SETTLE_WAIT_MS = 10_000;
34
39
  /**
35
40
  * A dormant session's record as OmO will resume it. OpenChamber 2.x restores a reopened session's effort
36
41
  * from this record and resends it, so the model and level come together from the file; the registry only
@@ -829,20 +834,39 @@ export class SessionManager {
829
834
  throw new Error(`session cannot be resumed: ${sessionId}`);
830
835
  this.lifecycle.touch(s);
831
836
  const timeout = command.type === "compact" ? COMPACT_RPC_TIMEOUT_MS : RPC_COMMAND_TIMEOUT_MS;
837
+ // OmO cancels a compaction whenever an MCP server's tools register, which a freshly revived agent
838
+ // does for about a second; start it once they have. The retries below remain the backstop.
839
+ if (command.type === "compact")
840
+ await waitForMcpServers(pi, MCP_SETTLE_WAIT_MS, signal);
832
841
  signal?.throwIfAborted();
833
842
  const abortsBefore = s.abortRequests ?? 0;
834
- let response = await pi.command(command, timeout, undefined, onDispatch);
835
- // OmO cancels a running compaction whenever its active tool set changes. On a session
836
- // just revived for this request, MCP servers finish connecting a moment after startup
837
- // and cancel it although nobody asked. Retry unless the user sent an abort meanwhile.
838
- for (let retry = 0; retry < COMPACT_CANCEL_RETRIES &&
839
- command.type === "compact" &&
840
- !response.success &&
841
- response.error === OMO_COMPACTION_CANCELLED &&
842
- (s.abortRequests ?? 0) === abortsBefore &&
843
- !signal?.aborted &&
844
- s.pi === pi; retry++)
845
- response = await pi.command(command, timeout);
843
+ // The translator holds back a cancellation this loop may retry, so the UI shows one compaction.
844
+ if (command.type === "compact")
845
+ s.compactRetry = { left: COMPACT_CANCEL_RETRIES, aborts: abortsBefore };
846
+ let response;
847
+ try {
848
+ response = await pi.command(command, timeout, undefined, onDispatch);
849
+ // OmO cancels a running compaction whenever its active tool set changes. On a session
850
+ // just revived for this request, MCP servers finish connecting a moment after startup
851
+ // and cancel it although nobody asked. Retry unless the user sent an abort meanwhile.
852
+ for (let retry = 0; retry < COMPACT_CANCEL_RETRIES &&
853
+ command.type === "compact" &&
854
+ !response.success &&
855
+ response.error === OMO_COMPACTION_CANCELLED &&
856
+ (s.abortRequests ?? 0) === abortsBefore &&
857
+ !signal?.aborted &&
858
+ s.pi === pi; retry++) {
859
+ if (s.compactRetry)
860
+ s.compactRetry.left--;
861
+ response = await pi.command(command, timeout);
862
+ }
863
+ }
864
+ finally {
865
+ if (command.type === "compact") {
866
+ delete s.compactRetry;
867
+ this.events.releaseCompactionEnd(s);
868
+ }
869
+ }
846
870
  if (!signal?.aborted && response.success && (command.type === "compact" || command.type === "bash"))
847
871
  await this.reloadMessagesLive(s, signal);
848
872
  return response;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antechamber-omo-compat",
3
- "version": "1.0.28",
3
+ "version": "1.0.29",
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.28",
3
+ "version": "1.0.29",
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.28",
3
+ "version": "1.0.29",
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.28",
3
+ "version": "1.0.29",
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",