antechamber 0.2.21 → 0.2.22

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,10 @@ and this project uses [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.22] - 2026-10-17
11
+
12
+ - fix(tools): stream live bash and eval output into OpenChamber cards (#29)
13
+
10
14
  ## [0.2.21] - 2026-10-17
11
15
 
12
16
  ### Fixed
@@ -29,6 +33,13 @@ and this project uses [Semantic Versioning](https://semver.org/).
29
33
  - Report the real agent startup failure on macOS instead of `kill EPERM`. When OmO exited while a session was being
30
34
  created, cleanup signalled its not yet reaped process group, macOS answered EPERM, and OpenChamber showed "Failed
31
35
  to create session" with the original error hidden.
36
+ - Show live bash and eval output in OpenChamber 1.24, and display eval and terminal controls as shell cards with
37
+ their final results retained after history reload. A failed cell shows as a failed card.
38
+
39
+ ### Added
40
+
41
+ - OmO Compatibility, a second bundled OpenChamber extension (1.24 or newer) with no code or permissions. It heads
42
+ eval cards with `JS Eval` or `Python Eval` and the cell's summary.
32
43
 
33
44
  ## [0.2.18] - 2026-10-17
34
45
 
package/README.md CHANGED
@@ -234,6 +234,18 @@ OpenChamber clones the repository root, which is antechamber itself, not the
234
234
  extension, and refuses it as an invalid manifest. Details:
235
235
  [extensions/omo-dag/README.md](extensions/omo-dag/README.md).
236
236
 
237
+ ## OpenChamber extension: OmO Compatibility
238
+
239
+ `extensions/omo-compat` is a second, code-free extension for OpenChamber 1.24
240
+ or newer. It tells OpenChamber how to display OmO tool calls that antechamber
241
+ maps onto OpenChamber's cards; today it heads eval cells with `JS Eval` or
242
+ `Python Eval` and the cell's summary. Add it the same way as OmO Graph: the
243
+ folder `antechamber setup` prints (`.../extensions/omo-compat`, no build
244
+ needed), or the release archive
245
+ `https://github.com/eliottness/antechamber/releases/latest/download/antechamber-omo-compat.zip`.
246
+ It asks for no permissions. Details:
247
+ [extensions/omo-compat/README.md](extensions/omo-compat/README.md).
248
+
237
249
  ## Configuration
238
250
 
239
251
  Connector settings use the `ANTECHAMBER_` prefix; the deprecated `OCPI_`
@@ -18,6 +18,11 @@ function omoGraphExtensionDir(ctx) {
18
18
  const built = ["package.json", "panel/main.js", "service/main.js"].every((file) => ctx.fs.existsSync(join(dir, file)));
19
19
  return built ? dir : undefined;
20
20
  }
21
+ /** The bundled OmO Compatibility extension folder; it is a manifest only, so it needs no build. */
22
+ function omoCompatExtensionDir(ctx) {
23
+ const dir = resolve(dirname(ctx.cliPath), "..", "extensions", "omo-compat");
24
+ return ctx.fs.existsSync(join(dir, "package.json")) ? dir : undefined;
25
+ }
21
26
  /** Plan all inputs before applying changes; dependencies are injected for hermetic tests. */
22
27
  export function setup(options, ctx = createContext()) {
23
28
  const lines = [];
@@ -174,5 +179,9 @@ export function setup(options, ctx = createContext()) {
174
179
  if (extension !== undefined) {
175
180
  report(`Optional OmO Graph extension: in OpenChamber, Settings \u2192 Extensions \u2192 Add \u2192 Folder: ${extension}`);
176
181
  }
182
+ const compat = omoCompatExtensionDir(ctx);
183
+ if (compat !== undefined) {
184
+ report(`Optional OmO Compatibility extension (eval card headers): Settings \u2192 Extensions \u2192 Add \u2192 Folder: ${compat}`);
185
+ }
177
186
  return lines;
178
187
  }
@@ -1,6 +1,6 @@
1
1
  /** Pi message/part translation and the live event bridge, with explicit manager services. */
2
2
  import { randomUUID } from "node:crypto";
3
- import { applyProviderToolResults, finishProviderToolParts, mapTool, providerToolParts, resultMetadata, resultText, } from "./tool-map.js";
3
+ import { applyProviderToolResults, finishProviderToolParts, mapTool, providerToolParts, resultMetadata, resultText, toolFailed, } from "./tool-map.js";
4
4
  import { TodoBridge } from "./todo-bridge.js";
5
5
  import { readSessionTree, recordTurn } from "./session-tree.js";
6
6
  import { touchedFiles } from "./session-diff.js";
@@ -408,6 +408,22 @@ export class PiEventTranslator {
408
408
  const part = typeof ev.toolCallId === "string" ? s.toolByCallId.get(ev.toolCallId) : undefined;
409
409
  if (part === undefined || part.state.status !== "running")
410
410
  return;
411
+ const partial = ev.partialResult;
412
+ let output = resultText(partial !== null && typeof partial === "object" && "content" in partial ? partial.content : undefined);
413
+ if (ev.toolName === "eval") {
414
+ // raw RPC capture of installed omo-ai 5.1.33 / senpi runtime, 2026-10-17:
415
+ // eval prefixes cumulative stdout with status lines, unlike its final result.
416
+ const header = /^\d+\/(\d+) cells? (?:running|complete)\b[^\n]*(?:\n|$)/.exec(output);
417
+ if (header !== null) {
418
+ output = output.slice(header[0].length);
419
+ if (header[1] === "1")
420
+ output = output.replace(/^\[1\] \S+ [^\n]* (?:running|complete)(?:\r?\n|$)/, "");
421
+ }
422
+ if (/^waiting for the \w+ kernel to be ready$/.test(output.trim()))
423
+ output = "";
424
+ }
425
+ if (output.trim() !== "")
426
+ part.state.metadata = { ...part.state.metadata, output };
411
427
  this.emitPart(s, part);
412
428
  return;
413
429
  }
@@ -774,7 +790,7 @@ export class PiEventTranslator {
774
790
  if (!part || callID === undefined)
775
791
  continue;
776
792
  s.toolByCallId.delete(callID);
777
- s.todoBridge?.finish(part, tr.isError === true);
793
+ s.todoBridge?.finish(part, toolFailed(tr));
778
794
  const text = resultText(tr.content);
779
795
  const startedAt = part.state.status !== "pending" ? part.state.time.start : Date.now();
780
796
  const input = part.state.input ?? {};
@@ -785,7 +801,7 @@ export class PiEventTranslator {
785
801
  const title = part.state.status === "running" || part.state.status === "completed"
786
802
  ? (part.state.title ?? part.tool)
787
803
  : part.tool;
788
- if (tr.isError === true) {
804
+ if (toolFailed(tr)) {
789
805
  part.state = {
790
806
  status: "error",
791
807
  input,
@@ -1,6 +1,6 @@
1
1
  /** Reconstruct OpenCode history from pi JSONL session files. */
2
2
  import { readFileSync } from "node:fs";
3
- import { applyProviderToolResults, finishProviderToolParts, mapTool, providerToolParts, resultMetadata, resultText, } from "./tool-map.js";
3
+ import { applyProviderToolResults, finishProviderToolParts, mapTool, providerToolParts, resultMetadata, resultText, toolFailed, } from "./tool-map.js";
4
4
  import { TodoBridge } from "./todo-bridge.js";
5
5
  import { userMessageModel } from "./thinking-levels.js";
6
6
  import { contextMessageID } from "./prompt-parts.js";
@@ -396,23 +396,22 @@ export function parsePiSessionText(s, agentName, raw) {
396
396
  const title = part.state.status === "running" || part.state.status === "completed"
397
397
  ? (part.state.title ?? part.tool)
398
398
  : part.tool;
399
- part.state =
400
- m.isError === true
401
- ? {
402
- status: "error",
403
- input,
404
- error: text !== "" ? text : `tool ${part.tool} failed`,
405
- metadata,
406
- time: { start: startedAt, end: created },
407
- }
408
- : {
409
- status: "completed",
410
- input,
411
- output: text,
412
- title,
413
- metadata,
414
- time: { start: startedAt, end: created },
415
- };
399
+ part.state = toolFailed(m)
400
+ ? {
401
+ status: "error",
402
+ input,
403
+ error: text !== "" ? text : `tool ${part.tool} failed`,
404
+ metadata,
405
+ time: { start: startedAt, end: created },
406
+ }
407
+ : {
408
+ status: "completed",
409
+ input,
410
+ output: text,
411
+ title,
412
+ metadata,
413
+ time: { start: startedAt, end: created },
414
+ };
416
415
  }
417
416
  // system / custom entries are not rendered
418
417
  }
@@ -15,6 +15,11 @@ export interface ToolResult {
15
15
  type ToolMapper = (args: Record<string, unknown>, result?: ToolResult) => MappedTool;
16
16
  /** Mutable registry: todos 6/7 can assign handlers for `todo` and `task`. */
17
17
  export declare const toolMappers: Record<string, ToolMapper>;
18
+ /**
19
+ * A failed result. Codemode leaves a failed eval cell's top-level isError false and sets
20
+ * details.isError instead (senpi-codemode src/tool/render-result.ts:42).
21
+ */
22
+ export declare function toolFailed(result: ToolResult): boolean;
18
23
  /** Text blocks only; images and structured details are not tool output text. */
19
24
  export declare function resultText(content: unknown): string;
20
25
  /** Only edit cards need patch metadata; write cards already preview input.content. */
package/dist/tool-map.js CHANGED
@@ -5,6 +5,25 @@ function generic(tool, input) {
5
5
  const hint = [input.summary, input.description, input.query, input.url, input.path, input.command].find((value) => typeof value === "string" && value !== "");
6
6
  return { tool, input, title: (hint ?? tool).slice(0, 60) };
7
7
  }
8
+ function terminalTool(name, args) {
9
+ const target = typeof args.bash_id === "string" ? ` ${args.bash_id}` : args.all === true ? " all" : "";
10
+ let command = `${name}${target}`;
11
+ if (name === "bash_input") {
12
+ if (typeof args.input === "string")
13
+ command += ` ${JSON.stringify(args.input)}`;
14
+ if (Array.isArray(args.keys)) {
15
+ const keys = args.keys.filter((key) => typeof key === "string").join(" ");
16
+ if (keys !== "")
17
+ command += ` ${keys}`;
18
+ }
19
+ }
20
+ if (name === "bash_resize" && typeof args.cols === "number" && typeof args.rows === "number") {
21
+ command += ` ${args.cols}x${args.rows}`;
22
+ }
23
+ return { tool: "bash", input: { command, description: command }, title: command.slice(0, 60) };
24
+ }
25
+ /** Display names for codemode's kernel ids. */
26
+ const EVAL_LANGUAGES = { js: "JS", py: "Python" };
8
27
  /** Mutable registry: todos 6/7 can assign handlers for `todo` and `task`. */
9
28
  export const toolMappers = {
10
29
  read: (args) => typeof args.path === "string"
@@ -40,13 +59,21 @@ export const toolMappers = {
40
59
  title: args.command.slice(0, 60),
41
60
  }
42
61
  : generic("bash", args),
43
- eval: (args) => typeof args.code === "string"
44
- ? {
45
- tool: "eval",
46
- input: { language: args.language, code: args.code, description: args.summary },
47
- title: typeof args.summary === "string" ? args.summary : "eval",
48
- }
49
- : generic("eval", args),
62
+ eval: (args) => {
63
+ const language = typeof args.language === "string" && args.language !== "" ? args.language : "js";
64
+ const summary = typeof args.summary === "string" && args.summary !== "" ? args.summary : "eval";
65
+ const name = `${EVAL_LANGUAGES[language] ?? language.toUpperCase()} Eval`;
66
+ const command = typeof args.code === "string" && args.code !== ""
67
+ ? args.code
68
+ : ["eval", args.action, args.cell_id].filter((value) => typeof value === "string").join(" ");
69
+ // Read by the OmO Compatibility extension's `bash` header rule; plain shell cards lack them and keep the default header.
70
+ const metadata = { evalTitle: name, evalSubtitle: summary };
71
+ return { tool: "bash", input: { command, description: `${name}: ${summary}` }, title: summary, metadata };
72
+ },
73
+ bash_output: (args) => terminalTool("bash_output", args),
74
+ bash_input: (args) => terminalTool("bash_input", args),
75
+ kill_bash: (args) => terminalTool("kill_bash", args),
76
+ bash_resize: (args) => terminalTool("bash_resize", args),
50
77
  ask_user_question: (args) => {
51
78
  if (!Array.isArray(args.questions) || !args.questions.every(record))
52
79
  return generic("ask_user_question", args);
@@ -67,6 +94,13 @@ export const toolMappers = {
67
94
  return { tool: "task", input, title: typeof input.description === "string" ? input.description : "task" };
68
95
  },
69
96
  };
97
+ /**
98
+ * A failed result. Codemode leaves a failed eval cell's top-level isError false and sets
99
+ * details.isError instead (senpi-codemode src/tool/render-result.ts:42).
100
+ */
101
+ export function toolFailed(result) {
102
+ return result.isError === true || (record(result.details) && result.details.isError === true);
103
+ }
70
104
  /** Text blocks only; images and structured details are not tool output text. */
71
105
  export function resultText(content) {
72
106
  if (!Array.isArray(content))
@@ -0,0 +1,41 @@
1
+ # OmO Compatibility
2
+
3
+ An OpenChamber extension that ships with antechamber. It changes how OpenChamber
4
+ displays some OmO tool calls, using only the display rules OpenChamber lets an
5
+ extension declare. It has no panel, page, service or permissions.
6
+
7
+ What it does today:
8
+
9
+ - **Eval cell headers.** OpenChamber 1.24 streams live output only for shell
10
+ cards, so antechamber shows OmO eval cells as shell cards. With this extension
11
+ their header reads `JS Eval` (or `Python Eval`) and the cell's summary instead
12
+ of "Shell Command" and the first line of code. Other shell cards keep their
13
+ normal header.
14
+
15
+ ## Requirements
16
+
17
+ - OpenChamber **1.24 or newer** (the first release with `contributes.tools`).
18
+ - antechamber as OpenChamber's backend. Without it the rules match nothing and
19
+ change nothing.
20
+
21
+ ## Install
22
+
23
+ In OpenChamber, open **Settings → Extensions → Add**:
24
+
25
+ - **Folder**, when OpenChamber runs on the machine where antechamber is
26
+ installed. `antechamber setup` prints the path:
27
+ `$(npm root -g)/antechamber/extensions/omo-compat` for an npm install, or
28
+ `<checkout>/extensions/omo-compat` from a source checkout. It needs no build.
29
+ - **URL**, from anywhere:
30
+ `https://github.com/eliottness/antechamber/releases/latest/download/antechamber-omo-compat.zip`.
31
+ Every GitHub release attaches this archive.
32
+
33
+ There is nothing to approve and no rail icon. Reload open chats after
34
+ installing so their cards pick up the new header.
35
+
36
+ For a server you reach over HTTP:
37
+
38
+ ```bash
39
+ curl -sX POST "$OC/api/guests" -H 'content-type: application/json' \
40
+ -d '{"path":"/abs/path/to/extensions/omo-compat"}'
41
+ ```
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><title>OmO Compatibility</title><path d="M8 7 3 12l5 5"/><path d="m16 7 5 5-5 5"/><path d="M14 4l-4 16"/></svg>
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "antechamber-omo-compat",
3
+ "version": "0.2.22",
4
+ "description": "OpenChamber extension: displays OmO tool calls the way OmO means them when OpenChamber runs on antechamber",
5
+ "private": true,
6
+ "type": "module",
7
+ "license": "MIT",
8
+ "openchamber": {
9
+ "apiVersion": 1,
10
+ "engines": {
11
+ "openchamber": ">=1.24.0"
12
+ },
13
+ "contributes": {
14
+ "panel": {
15
+ "id": "omo-compat",
16
+ "name": "OmO Compatibility",
17
+ "icon": "icon.svg"
18
+ },
19
+ "tools": [
20
+ {
21
+ "match": "bash",
22
+ "title": "{metadata.evalTitle}",
23
+ "subtitle": "{metadata.evalSubtitle}"
24
+ }
25
+ ]
26
+ }
27
+ }
28
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antechamber-omo-graph",
3
- "version": "0.2.21",
3
+ "version": "0.2.22",
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.21",
3
+ "version": "0.2.22",
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",
@@ -35,6 +35,9 @@
35
35
  "extensions/omo-dag/service/main.js",
36
36
  "extensions/omo-dag/README.md",
37
37
  "extensions/omo-dag/docs/DESIGN.md",
38
+ "extensions/omo-compat/package.json",
39
+ "extensions/omo-compat/icon.svg",
40
+ "extensions/omo-compat/README.md",
38
41
  "README.md",
39
42
  "LICENSE",
40
43
  "CHANGELOG.md"