antechamber 1.0.31 → 1.0.33
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 +21 -0
- package/dist/pi-rpc.d.ts +2 -0
- package/dist/pi-rpc.js +13 -1
- package/dist/session-commands.d.ts +2 -0
- package/dist/session-commands.js +53 -4
- package/dist/sessions.d.ts +1 -0
- package/dist/sessions.js +13 -6
- 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,27 @@ and this project uses [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.0.33] - 2026-11-02
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Stop showing replies twice, and keep new messages and `!` shell commands at the end of the conversation, in a
|
|
15
|
+
session that has a shell command OmO never reported back (for example one stopped by the old one-minute deadline).
|
|
16
|
+
Every read of the conversation re-parsed the session file without the live replies, so each reply read mid-turn
|
|
17
|
+
gained a second, restored copy, and those copies piled up after newer messages. Restart OpenChamber's adapter to
|
|
18
|
+
clear copies already in memory.
|
|
19
|
+
|
|
20
|
+
## [1.0.32] - 2026-10-29
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Stop killing the agent when a shell command run from the composer takes longer than a minute, such as an
|
|
25
|
+
interactive login. A user shell now runs until its command exits or you press Stop; only a shell that OmO still
|
|
26
|
+
has not answered a minute after Stop stops the agent.
|
|
27
|
+
- Show a shell command's output live in its card while it runs, so a link or code a command prints before it waits
|
|
28
|
+
(such as a login URL) can be copied. Before, the card stayed empty until the command ended. A command you stop
|
|
29
|
+
keeps the output it printed.
|
|
30
|
+
|
|
10
31
|
## [1.0.31] - 2026-10-29
|
|
11
32
|
|
|
12
33
|
### 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
|
-
|
|
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: {
|
package/dist/session-commands.js
CHANGED
|
@@ -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?.
|
|
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", {
|
|
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",
|
|
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.d.ts
CHANGED
|
@@ -225,6 +225,7 @@ export declare class SessionManager {
|
|
|
225
225
|
private reviveLive;
|
|
226
226
|
/** Record, persist and show a stop that no exit handler will report. */
|
|
227
227
|
private reportStop;
|
|
228
|
+
/** `reparse` rebuilds from the JSONL while `s.messages` stays visible, so the binder can reuse live ids. */
|
|
228
229
|
private ensureMessages;
|
|
229
230
|
/**
|
|
230
231
|
* The stop notice card. Its id derives from the stop time, so the live card and every replay after a
|
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
|
-
|
|
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")
|
|
@@ -1319,7 +1324,8 @@ export class SessionManager {
|
|
|
1319
1324
|
properties: { sessionID: s.info.id, error: { name: "AgentStoppedError", data: { message } } },
|
|
1320
1325
|
});
|
|
1321
1326
|
}
|
|
1322
|
-
|
|
1327
|
+
/** `reparse` rebuilds from the JSONL while `s.messages` stays visible, so the binder can reuse live ids. */
|
|
1328
|
+
ensureMessages(s, reparse = false) {
|
|
1323
1329
|
if (this.subagents.owns(s.info.id))
|
|
1324
1330
|
return this.subagents.messages(s.info.id);
|
|
1325
1331
|
if (this.monitors.owns(s.info.id))
|
|
@@ -1329,9 +1335,9 @@ export class SessionManager {
|
|
|
1329
1335
|
const dormantPath = s.pi === undefined ? s.restored?.piPath : undefined;
|
|
1330
1336
|
const fileSize = dormantPath === undefined ? undefined : statSync(dormantPath, { throwIfNoEntry: false })?.size;
|
|
1331
1337
|
const cachedFileSize = s.historyFileSize ?? s.tree.fileSize;
|
|
1332
|
-
if (s.messages !== undefined && fileSize !== undefined && fileSize !== cachedFileSize)
|
|
1338
|
+
if (!reparse && s.messages !== undefined && fileSize !== undefined && fileSize !== cachedFileSize)
|
|
1333
1339
|
return this.refreshMessageCache(s, true);
|
|
1334
|
-
if (s.messages === undefined) {
|
|
1340
|
+
if (s.messages === undefined || reparse) {
|
|
1335
1341
|
// A dormant session may have been continued by another OmO client since the last save.
|
|
1336
1342
|
if (s.pi === undefined && s.restored !== undefined)
|
|
1337
1343
|
followExternalAppends(s, s.restored.piPath);
|
|
@@ -1388,8 +1394,9 @@ export class SessionManager {
|
|
|
1388
1394
|
}
|
|
1389
1395
|
refreshMessageCache(s, replaceNative = false) {
|
|
1390
1396
|
const previous = s.messages ?? [];
|
|
1391
|
-
|
|
1392
|
-
|
|
1397
|
+
// Keep the live list during the re-parse: with a pending shell turn the parse binds entries to visible
|
|
1398
|
+
// messages, and an empty list would give every unbound live reply a second, restored `_r` copy.
|
|
1399
|
+
const native = this.ensureMessages(s, true);
|
|
1393
1400
|
if (replaceNative) {
|
|
1394
1401
|
// Legacy compaction IDs can collide; retain every parsed native row.
|
|
1395
1402
|
let index = 0;
|