antechamber 1.0.31 → 1.0.32

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.32] - 2026-10-29
11
+
12
+ ### Fixed
13
+
14
+ - Stop killing the agent when a shell command run from the composer takes longer than a minute, such as an
15
+ interactive login. A user shell now runs until its command exits or you press Stop; only a shell that OmO still
16
+ has not answered a minute after Stop stops the agent.
17
+ - Show a shell command's output live in its card while it runs, so a link or code a command prints before it waits
18
+ (such as a login URL) can be copied. Before, the card stayed empty until the command ended. A command you stop
19
+ keeps the output it printed.
20
+
10
21
  ## [1.0.31] - 2026-10-29
11
22
 
12
23
  ### Fixed
package/dist/pi-rpc.d.ts CHANGED
@@ -137,6 +137,8 @@ export declare class PiRpcClient extends EventEmitter {
137
137
  probe<T = unknown>(cmd: Omit<PiRpcCommand, "id">, timeoutMs?: number, signal?: AbortSignal): Promise<PiRpcResponse<T>>;
138
138
  /** Send a command and wait for its correlated response. A timeout stops the agent unless `stopOnTimeout` is false. */
139
139
  command<T = unknown>(cmd: Omit<PiRpcCommand, "id">, timeoutMs?: number, signal?: AbortSignal, onDispatch?: () => void, stopOnTimeout?: boolean): Promise<PiRpcResponse<T>>;
140
+ /** Asks OmO to stop the user shell; a pending `bash` still unanswered after the ordinary deadline means a hung agent. */
141
+ abortBash(): void;
140
142
  /** Fire-and-forget command (no response correlation). */
141
143
  notify(cmd: Omit<PiRpcCommand, "id">): void;
142
144
  dispose(): Promise<void>;
package/dist/pi-rpc.js CHANGED
@@ -403,6 +403,10 @@ export class PiRpcClient extends EventEmitter {
403
403
  const expectedCommand = String(cmd.type);
404
404
  const deadlineMs = timeoutMs ?? RPC_COMMAND_TIMEOUT_MS;
405
405
  const waitsForWork = expectedCommand === "prompt" && timeoutMs === undefined;
406
+ // A user `bash` answers when its command exits: OmO awaits `executeBash` with no time limit (senpi
407
+ // 2026.10.25-2, dist/modes/rpc/connection-handler.js `case "bash"`), and an interactive command such as a
408
+ // login waits on the user. Without a caller deadline it gets one only once `abortBash` asks OmO to stop it.
409
+ const waitsForShell = expectedCommand === "bash" && timeoutMs === undefined;
406
410
  return new Promise((resolve, reject) => {
407
411
  let timer;
408
412
  let admissionTimer;
@@ -431,7 +435,8 @@ export class PiRpcClient extends EventEmitter {
431
435
  if (!waitsForWork || !(this.agentActive || this.compacting))
432
436
  timer = setTimeout(() => expire(deadlineMs), deadlineMs);
433
437
  };
434
- armDeadline();
438
+ if (!waitsForShell)
439
+ armDeadline();
435
440
  if (waitsForWork)
436
441
  admissionTimer = setTimeout(() => expire(PROMPT_ADMISSION_TIMEOUT_MS), PROMPT_ADMISSION_TIMEOUT_MS);
437
442
  this.pending.set(id, {
@@ -443,12 +448,19 @@ export class PiRpcClient extends EventEmitter {
443
448
  },
444
449
  reject: rejectCommand,
445
450
  ...(waitsForWork ? { onWorkState: armDeadline } : {}),
451
+ ...(waitsForShell ? { onAbortBash: () => timer === undefined && armDeadline() } : {}),
446
452
  });
447
453
  signal?.addEventListener("abort", onAbort, { once: true });
448
454
  this.write({ ...cmd, id });
449
455
  onDispatch?.();
450
456
  });
451
457
  }
458
+ /** Asks OmO to stop the user shell; a pending `bash` still unanswered after the ordinary deadline means a hung agent. */
459
+ abortBash() {
460
+ this.write({ type: "abort_bash" });
461
+ for (const request of this.pending.values())
462
+ request.onAbortBash?.();
463
+ }
452
464
  /** Fire-and-forget command (no response correlation). */
453
465
  notify(cmd) {
454
466
  this.write(cmd);
@@ -54,6 +54,8 @@ type CompactResult = {
54
54
  kind: "error";
55
55
  message: string;
56
56
  };
57
+ /** Appends a chunk and keeps the newest `limit` chars, starting at a line boundary when one leaves anything. */
58
+ export declare function appendLiveOutput(previous: string, delta: string, limit?: number): string;
57
59
  export declare const unsupportedShare: {
58
60
  readonly name: "UnsupportedError";
59
61
  readonly data: {
@@ -2,6 +2,22 @@ import { randomUUID } from "node:crypto";
2
2
  import { PiRpcClient, UpstreamProtocolError } from "./pi-rpc.js";
3
3
  import { getAgentName } from "./agent-name.js";
4
4
  import { nativePromptInput, parsePromptParts } from "./prompt-parts.js";
5
+ /**
6
+ * Live output kept on a running shell card: the newest part. Every snapshot carries all of it, so it is kept small;
7
+ * the finished card gets OmO's full output.
8
+ */
9
+ const LIVE_SHELL_OUTPUT_CHARS = 64 * 1024;
10
+ /** Output chunks arriving within this window reach clients as one card snapshot. */
11
+ const LIVE_SHELL_OUTPUT_FLUSH_MS = 100;
12
+ /** Appends a chunk and keeps the newest `limit` chars, starting at a line boundary when one leaves anything. */
13
+ export function appendLiveOutput(previous, delta, limit = LIVE_SHELL_OUTPUT_CHARS) {
14
+ const output = previous + delta;
15
+ if (output.length <= limit)
16
+ return output;
17
+ const tail = output.slice(output.length - limit);
18
+ const lineStart = tail.indexOf("\n") + 1;
19
+ return lineStart > 0 && lineStart < tail.length ? tail.slice(lineStart) : tail;
20
+ }
5
21
  // OpenChamber drops rejected Share actions, so emit a visible session notice too.
6
22
  export const unsupportedShare = {
7
23
  name: "UnsupportedError",
@@ -311,13 +327,35 @@ export async function runShellCommand(context) {
311
327
  const afterEntryId = session.tree.leafId ?? null;
312
328
  const controller = new AbortController();
313
329
  let interrupted = false;
330
+ // OmO streams user bash output as `bash_execution_update {id: executionId, delta}` frames before its reply
331
+ // (agent-session.js `executeBash`; real RPC probe on 2026-10-28). Showing them live lets the user act on
332
+ // output while the command waits, such as a login link.
333
+ let streaming;
334
+ let liveOutput = "";
335
+ let flush;
336
+ const onOutput = (event) => {
337
+ const [part] = message.parts;
338
+ if (event.type !== "bash_execution_update" || event.id !== part.callID || typeof event.delta !== "string")
339
+ return;
340
+ if (part.state.status !== "running")
341
+ return;
342
+ liveOutput = appendLiveOutput(liveOutput, event.delta);
343
+ part.state.metadata = { ...part.state.metadata, output: liveOutput };
344
+ flush ??= setTimeout(() => {
345
+ flush = undefined;
346
+ if (part.state.status === "running")
347
+ shellEvent(context, { type: "message.part.updated", properties: { part } });
348
+ }, LIVE_SHELL_OUTPUT_FLUSH_MS);
349
+ };
350
+ // A stopped card keeps what the command printed, such as a link the user still needs.
351
+ const streamed = () => (liveOutput === "" ? {} : { output: liveOutput });
314
352
  session.cancelShell = (byRestart = false) => {
315
353
  interrupted = byRestart;
316
354
  delete session.cancelShell;
317
355
  // Agent abort does not stop user bash. Dispatch the dedicated RPC first,
318
356
  // then release our waiter; a correlated late response is discarded.
319
357
  if (dispatch[message.info.id])
320
- session.pi?.notify({ type: "abort_bash" });
358
+ session.pi?.abortBash();
321
359
  controller.abort();
322
360
  };
323
361
  sessions.appendMessage(session.info.id, user);
@@ -332,8 +370,10 @@ export async function runShellCommand(context) {
332
370
  });
333
371
  try {
334
372
  await sessions.update(session.info.id, {});
335
- const response = await sessions.sessionRpc(session.info.id, { type: "bash", command }, controller.signal, () => {
373
+ const response = await sessions.sessionRpc(session.info.id, { type: "bash", command, executionId: message.parts[0].callID }, controller.signal, () => {
336
374
  dispatch[message.info.id] = true;
375
+ streaming = session.pi;
376
+ streaming?.onEvent(onOutput);
337
377
  // Only actual dispatch admissions are durable; queued attempts stay in the live cache.
338
378
  (session.tree.localShellTurns ??= []).push({ afterEntryId, messages: [user, message] });
339
379
  // A native result can land beyond the last synchronized leaf.
@@ -351,7 +391,11 @@ export async function runShellCommand(context) {
351
391
  }
352
392
  const result = parseShellResult(response.data);
353
393
  if (result.cancelled) {
354
- failShellMessage(message, command, "Command cancelled", { cancelled: true, exitCode: result.exitCode });
394
+ failShellMessage(message, command, "Command cancelled", {
395
+ ...streamed(),
396
+ cancelled: true,
397
+ exitCode: result.exitCode,
398
+ });
355
399
  }
356
400
  else {
357
401
  completeShellMessage(message, command, result);
@@ -361,7 +405,10 @@ export async function runShellCommand(context) {
361
405
  }
362
406
  catch (error) {
363
407
  if (controller.signal.aborted) {
364
- failShellMessage(message, command, interrupted ? "Interrupted by restart" : "Command cancelled", interrupted ? { interrupted: true } : { cancelled: true });
408
+ failShellMessage(message, command, interrupted ? "Interrupted by restart" : "Command cancelled", {
409
+ ...streamed(),
410
+ ...(interrupted ? { interrupted: true } : { cancelled: true }),
411
+ });
365
412
  emitShellMessage(context, message);
366
413
  return { kind: "completed", message };
367
414
  }
@@ -370,6 +417,8 @@ export async function runShellCommand(context) {
370
417
  throw error;
371
418
  }
372
419
  finally {
420
+ streaming?.off("event", onOutput);
421
+ clearTimeout(flush);
373
422
  delete session.cancelShell;
374
423
  const state = message.parts[0].state;
375
424
  const metadata = state.status === "pending" ? undefined : state.metadata;
package/dist/sessions.js CHANGED
@@ -839,7 +839,12 @@ export class SessionManager {
839
839
  if (pi === undefined)
840
840
  throw new Error(`session cannot be resumed: ${sessionId}`);
841
841
  this.lifecycle.touch(s);
842
- const timeout = command.type === "compact" ? COMPACT_RPC_TIMEOUT_MS : RPC_COMMAND_TIMEOUT_MS;
842
+ // A user shell runs until its command exits or the user stops it (`PiRpcClient.abortBash`).
843
+ const timeout = command.type === "compact"
844
+ ? COMPACT_RPC_TIMEOUT_MS
845
+ : command.type === "bash"
846
+ ? undefined
847
+ : RPC_COMMAND_TIMEOUT_MS;
843
848
  // OmO cancels a compaction whenever an MCP server's tools register, which a freshly revived agent
844
849
  // does for about a second; start it once they have. The retries below remain the backstop.
845
850
  if (command.type === "compact")
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antechamber-omo-compat",
3
- "version": "1.0.31",
3
+ "version": "1.0.32",
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.31",
3
+ "version": "1.0.32",
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.31",
3
+ "version": "1.0.32",
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.31",
3
+ "version": "1.0.32",
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",