antechamber 1.0.35 → 1.0.37

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,25 @@ and this project uses [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.0.37] - 2026-11-03
11
+
12
+ - feat(session-hosts): tear down private OmO hosts on macOS (#85)
13
+
14
+ ## [1.0.36] - 2026-11-03
15
+
16
+ ### Added
17
+
18
+ - Clean up after the agent on macOS. Disposal now records the agent's process group and re-identifies each member
19
+ before signalling it, so child processes that outlive the agent are stopped instead of being left behind. Linux
20
+ keeps reading `/proc`; macOS reads `/bin/ps`, because Node has neither `/proc` nor pidfd there. A process that has
21
+ already exited (zombie) never counts as a survivor.
22
+
23
+ - Tear down OmO's private task hosts on macOS. The connector finds the supervisors it started by the `--socket` session
24
+ shard, entry point and working directory, records every process in their groups, and signals each one by PID only
25
+ after re-checking its group and start time, escalating to `SIGKILL` after a grace window. A process that outlives its
26
+ supervisor is still stopped. `antechamber doctor` reports the check as PASS when `/bin/ps` and `/usr/sbin/lsof`
27
+ exist. Linux keeps the pidfd path.
28
+
10
29
  ## [1.0.35] - 2026-11-03
11
30
 
12
31
  - test: resolve fixture paths with fileURLToPath (#86)
package/README.md CHANGED
@@ -354,10 +354,13 @@ Hardening controls (full reference in [docs/configuration.md](docs/configuration
354
354
  ## Platform notes
355
355
 
356
356
  Linux is the primary supported platform. OmO's private task-host teardown
357
- (killing detached supervisor processes antechamber spawned) needs Linux
358
- pidfds (`os.pidfd_open`, Linux 5.3+) and `python3` on PATH; Node has no pidfd
359
- API. On other platforms that teardown step is skipped with one warning instead
360
- of crashing; antechamber never falls back to signalling numeric PIDs. macOS is untested.
357
+ (killing detached supervisor processes antechamber spawned) uses Linux
358
+ pidfds (`os.pidfd_open`, Linux 5.3+) and `python3` on PATH, because Node has no pidfd
359
+ API; on Linux it never signals a numeric PID. macOS has no pidfd, so there it
360
+ reads `/bin/ps` and `/usr/sbin/lsof` and signals each owned process by PID after
361
+ re-checking its process group and start time (see
362
+ [the OmO backend notes](docs/backends/omo.md#private-task-host-teardown)). CI runs the suite on
363
+ macOS. On other platforms that teardown step is skipped with one warning instead of crashing.
361
364
 
362
365
  ## Limitations / non-goals
363
366
 
@@ -84,9 +84,15 @@ export function doctor(options = {}, ctx = createContext(), latestVersion) {
84
84
  }
85
85
  })
86
86
  : undefined;
87
- add("private-host-teardown", python === undefined ? "WARN" : "PASS", python === undefined
88
- ? "requires Linux and python3 with working pidfd support"
89
- : `Linux pidfd support via ${python}`);
87
+ if (ctx.platform === "darwin") {
88
+ const tools = ["/bin/ps", "/usr/sbin/lsof"];
89
+ const missing = tools.filter((tool) => !ctx.fs.existsSync(tool));
90
+ add("private-host-teardown", missing.length === 0 ? "PASS" : "WARN", missing.length === 0 ? `macOS teardown via ${tools.join(" and ")}` : `requires ${missing.join(" and ")}`);
91
+ }
92
+ else
93
+ add("private-host-teardown", python === undefined ? "WARN" : "PASS", python === undefined
94
+ ? "requires Linux and python3 with working pidfd support"
95
+ : `Linux pidfd support via ${python}`);
90
96
  const openchamber = resolveBinary("openchamber", ctx);
91
97
  if (openchamber === undefined)
92
98
  add("openchamber", "WARN", "openchamber is not on PATH");
package/dist/pi-rpc.d.ts CHANGED
@@ -9,6 +9,8 @@
9
9
  */
10
10
  import { EventEmitter } from "node:events";
11
11
  import { type LifecycleClock } from "./lifecycle.js";
12
+ import { type ProcessTable } from "./proc-table.js";
13
+ export type { ProcessIdentity, ProcessTable } from "./proc-table.js";
12
14
  export interface PiRpcCommand {
13
15
  id?: string;
14
16
  type: string;
@@ -30,17 +32,6 @@ export declare class UpstreamProtocolError extends Error {
30
32
  * earlyoom take a workload before the adapter, whose death stops every session at once.
31
33
  */
32
34
  export declare const AGENT_OOM_SCORE_ADJ_MARGIN = 300;
33
- /** A process's group and kernel start time: with the PID, the identity that survives PID reuse checks. */
34
- interface ProcessIdentity {
35
- readonly pid: number;
36
- readonly group: number;
37
- readonly start: string;
38
- }
39
- /** Reads process identities so disposal can tell owned survivors from a reused PID. */
40
- export interface ProcessTable {
41
- identity(pid: number): ProcessIdentity | undefined;
42
- groupMembers(group: number): ProcessIdentity[];
43
- }
44
35
  /** Why the agent stopped without antechamber disposing it on purpose (eviction, delete, shutdown). */
45
36
  export type PiStopReason = {
46
37
  readonly kind: "timeout";
@@ -143,4 +134,3 @@ export declare class PiRpcClient extends EventEmitter {
143
134
  notify(cmd: Omit<PiRpcCommand, "id">): void;
144
135
  dispose(): Promise<void>;
145
136
  }
146
- export {};
package/dist/pi-rpc.js CHANGED
@@ -11,10 +11,11 @@ import { spawn } from "node:child_process";
11
11
  import { StringDecoder } from "node:string_decoder";
12
12
  import { EventEmitter } from "node:events";
13
13
  import { randomUUID } from "node:crypto";
14
- import { readdirSync, readFileSync, writeFileSync } from "node:fs";
14
+ import { readFileSync, writeFileSync } from "node:fs";
15
15
  import { constants as osConstants } from "node:os";
16
16
  import { systemClock } from "./lifecycle.js";
17
17
  import { agentBinary, envSetting } from "./config.js";
18
+ import { createProcessTable } from "./proc-table.js";
18
19
  export const RPC_COMMAND_TIMEOUT_MS = 60_000;
19
20
  /**
20
21
  * `compact` answers only when the summary is written. OmO bounds one compaction by
@@ -50,34 +51,7 @@ function rankAboveAdapterForOom(pid) {
50
51
  console.error(`[pi-rpc] could not raise the agent's OOM score: ${error instanceof Error ? error.message : error}`);
51
52
  }
52
53
  }
53
- // Linux /proc. Any read failure (gone, no permission, no /proc) means the identity cannot be
54
- // verified, and an unverifiable process is never signalled: fail closed.
55
- const procTable = {
56
- identity(pid) {
57
- let stat;
58
- try {
59
- stat = readFileSync(`/proc/${pid}/stat`, "utf8");
60
- }
61
- catch {
62
- return undefined;
63
- }
64
- const fields = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
65
- return { pid, group: Number(fields[2]), start: fields[19] ?? "" };
66
- },
67
- groupMembers(group) {
68
- let names;
69
- try {
70
- names = readdirSync("/proc");
71
- }
72
- catch {
73
- return [];
74
- }
75
- return names
76
- .filter((name) => /^\d+$/.test(name))
77
- .flatMap((name) => procTable.identity(Number(name)) ?? [])
78
- .filter((member) => member.group === group);
79
- },
80
- };
54
+ const procTable = createProcessTable();
81
55
  function duration(ms) {
82
56
  if (ms % 60_000 === 0)
83
57
  return `${ms / 60_000} min`;
@@ -0,0 +1,23 @@
1
+ import type { ProcessIdentity, ProcessTable } from "./proc-table.js";
2
+ /**
3
+ * macOS process table, backed by `/bin/ps`. Used by disposal to identify owned survivors, so it must
4
+ * report the same three facts Linux reads from `/proc/<pid>/stat`: PID, process group, and a start
5
+ * time that survives PID reuse checks. `ps` exposes the latter as `lstart` (second granularity).
6
+ */
7
+ /** Runs `/bin/ps` with the given arguments and returns stdout. Injected so tests never spawn. */
8
+ export type PsRunner = (args: readonly string[]) => string;
9
+ /**
10
+ * `execFileSync` with an absolute binary: no shell, and no PATH lookup that a user-controlled
11
+ * directory could shadow. `-ww` disables width truncation; the column set is fixed so every line
12
+ * has the same shape. LC_ALL is pinned because `lstart` is rendered in the current locale: two
13
+ * calls must agree with each other even if the user changes the system language mid-session.
14
+ */
15
+ export declare function runPs(args: readonly string[]): string;
16
+ /** One `ps -A` row with its state and argv, for callers that match processes by command line. */
17
+ export interface PsProcess extends ProcessIdentity {
18
+ readonly state: string;
19
+ readonly args: string;
20
+ }
21
+ /** Every live (non-zombie) process with its argv. Throws when `ps` fails, so the caller decides. */
22
+ export declare function listDarwinProcesses(run?: PsRunner): PsProcess[];
23
+ export declare function createDarwinProcTable(run?: PsRunner): ProcessTable;
@@ -0,0 +1,93 @@
1
+ import { execFileSync } from "node:child_process";
2
+ const PS_TIMEOUT_MS = 2_000;
3
+ const PS_MAX_BUFFER = 8 * 1024 * 1024;
4
+ const PS_COLUMNS = "pid=,pgid=,stat=,lstart=";
5
+ /** `pid pgid stat lstart`, where lstart is "Sun Nov 1 15:55:48 2026" and may end in padding. */
6
+ const PS_LINE = /^\s*(\d+)\s+(\d+)\s+(\S+)\s+(.+?)\s*$/;
7
+ /**
8
+ * `execFileSync` with an absolute binary: no shell, and no PATH lookup that a user-controlled
9
+ * directory could shadow. `-ww` disables width truncation; the column set is fixed so every line
10
+ * has the same shape. LC_ALL is pinned because `lstart` is rendered in the current locale: two
11
+ * calls must agree with each other even if the user changes the system language mid-session.
12
+ */
13
+ export function runPs(args) {
14
+ return execFileSync("/bin/ps", args, {
15
+ encoding: "utf8",
16
+ timeout: PS_TIMEOUT_MS,
17
+ maxBuffer: PS_MAX_BUFFER,
18
+ env: { ...process.env, LC_ALL: "C", LANG: "C" },
19
+ });
20
+ }
21
+ function parseLine(line) {
22
+ const match = PS_LINE.exec(line);
23
+ if (match === null)
24
+ return undefined;
25
+ const pid = Number(match[1]);
26
+ const group = Number(match[2]);
27
+ const state = match[3];
28
+ const start = match[4];
29
+ if (!Number.isInteger(pid) || !Number.isInteger(group) || state === undefined || start === undefined)
30
+ return undefined;
31
+ if (start === "")
32
+ return undefined;
33
+ return { pid, group, start, state };
34
+ }
35
+ /**
36
+ * A zombie has already exited; only its parent is left to reap it. Counting it as a survivor would
37
+ * make disposal wait out the full grace window for a process that can no longer run, so `Z` entries
38
+ * are treated as gone in both methods, as `session-hosts` already does on Linux.
39
+ */
40
+ function alive(entry) {
41
+ return !entry.state.startsWith("Z");
42
+ }
43
+ // Under LC_ALL=C, lstart is a fixed 24-character field, so argv starts right after it even when it
44
+ // contains spaces.
45
+ const PS_ARGS_LINE = /^\s*(\d+)\s+(\d+)\s+(\S+)\s+(.{24})\s?(.*)$/;
46
+ /** Every live (non-zombie) process with its argv. Throws when `ps` fails, so the caller decides. */
47
+ export function listDarwinProcesses(run = runPs) {
48
+ return run(["-ww", "-o", `${PS_COLUMNS},args=`, "-A"])
49
+ .split("\n")
50
+ .flatMap((line) => {
51
+ const match = PS_ARGS_LINE.exec(line);
52
+ if (match === null)
53
+ return [];
54
+ const entry = { pid: Number(match[1]), group: Number(match[2]), state: match[3] ?? "", start: match[4] ?? "" };
55
+ return alive(entry) ? [{ ...entry, args: match[5] ?? "" }] : [];
56
+ });
57
+ }
58
+ function toIdentity(entry) {
59
+ return { pid: entry.pid, group: entry.group, start: entry.start };
60
+ }
61
+ export function createDarwinProcTable(run = runPs) {
62
+ return {
63
+ identity(pid) {
64
+ if (!Number.isInteger(pid) || pid <= 0)
65
+ return undefined;
66
+ let output;
67
+ try {
68
+ output = run(["-ww", "-o", PS_COLUMNS, "-p", String(pid)]);
69
+ }
70
+ catch {
71
+ // The process is gone, or ps could not read it. An unverifiable identity fails closed.
72
+ return undefined;
73
+ }
74
+ const entry = parseLine(output.trim());
75
+ if (entry === undefined || entry.pid !== pid || !alive(entry))
76
+ return undefined;
77
+ return toIdentity(entry);
78
+ },
79
+ groupMembers(group) {
80
+ let output;
81
+ try {
82
+ output = run(["-ww", "-o", PS_COLUMNS, "-A"]);
83
+ }
84
+ catch {
85
+ return [];
86
+ }
87
+ return output.split("\n").flatMap((line) => {
88
+ const entry = parseLine(line);
89
+ return entry !== undefined && entry.group === group && alive(entry) ? [toIdentity(entry)] : [];
90
+ });
91
+ },
92
+ };
93
+ }
@@ -0,0 +1,17 @@
1
+ /** A process's group and kernel start time: with the PID, the identity that survives PID reuse checks. */
2
+ export interface ProcessIdentity {
3
+ readonly pid: number;
4
+ readonly group: number;
5
+ readonly start: string;
6
+ }
7
+ /** Reads process identities so disposal can tell owned survivors from a reused PID. */
8
+ export interface ProcessTable {
9
+ identity(pid: number): ProcessIdentity | undefined;
10
+ groupMembers(group: number): ProcessIdentity[];
11
+ }
12
+ /**
13
+ * The process table for this platform. Linux reads `/proc`; macOS reads `/bin/ps`, because Node has no
14
+ * `/proc` and no pidfd there. Every other platform keeps the Linux table so a missing `/proc` fails
15
+ * closed (an identity is never reported that cannot be verified).
16
+ */
17
+ export declare function createProcessTable(platform?: NodeJS.Platform): ProcessTable;
@@ -0,0 +1,38 @@
1
+ import { readdirSync, readFileSync } from "node:fs";
2
+ import { createDarwinProcTable } from "./proc-table-darwin.js";
3
+ // Linux /proc. Any read failure (gone, no permission, no /proc) means the identity cannot be
4
+ // verified, and an unverifiable process is never signalled: fail closed.
5
+ const linuxProcTable = {
6
+ identity(pid) {
7
+ let stat;
8
+ try {
9
+ stat = readFileSync(`/proc/${pid}/stat`, "utf8");
10
+ }
11
+ catch {
12
+ return undefined;
13
+ }
14
+ const fields = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
15
+ return { pid, group: Number(fields[2]), start: fields[19] ?? "" };
16
+ },
17
+ groupMembers(group) {
18
+ let names;
19
+ try {
20
+ names = readdirSync("/proc");
21
+ }
22
+ catch {
23
+ return [];
24
+ }
25
+ return names
26
+ .filter((name) => /^\d+$/.test(name))
27
+ .flatMap((name) => linuxProcTable.identity(Number(name)) ?? [])
28
+ .filter((member) => member.group === group);
29
+ },
30
+ };
31
+ /**
32
+ * The process table for this platform. Linux reads `/proc`; macOS reads `/bin/ps`, because Node has no
33
+ * `/proc` and no pidfd there. Every other platform keeps the Linux table so a missing `/proc` fails
34
+ * closed (an identity is never reported that cannot be verified).
35
+ */
36
+ export function createProcessTable(platform = process.platform) {
37
+ return platform === "darwin" ? createDarwinProcTable() : linuxProcTable;
38
+ }
@@ -1,3 +1,24 @@
1
+ import { type PsRunner } from "./proc-table-darwin.js";
1
2
  /** Capability and warning state are captured once, before any shutdown work. */
2
3
  export declare function createSessionHostStopper(platform?: NodeJS.Platform, probe?: () => string | undefined): (created: ReadonlyMap<string, string>) => Promise<void>;
3
4
  export declare const stopSessionHosts: (created: ReadonlyMap<string, string>) => Promise<void>;
5
+ /** Injected so the darwin path can be tested without running ps, lsof or a signal. */
6
+ export interface DarwinHostDependencies {
7
+ readonly run: PsRunner;
8
+ readonly cwdOf: (pid: number) => string | undefined;
9
+ readonly realpath: (path: string) => string;
10
+ readonly signal: (pid: number, signal: NodeJS.Signals) => void;
11
+ readonly delay: (ms: number) => Promise<void>;
12
+ readonly graceMs: number;
13
+ readonly pollMs: number;
14
+ }
15
+ /**
16
+ * macOS teardown without pidfd. Ownership is the Linux rule: a live group leader whose argv names the
17
+ * supervisor entry point and, as its `--socket` value, the shard of a session this connector created,
18
+ * and whose working directory is that session's directory. Every member of an owned group is recorded
19
+ * with its start time in the same scan, and each later signal goes to one PID only after a fresh scan
20
+ * still shows the same group and start time. Members are tracked individually, so a child that outlives
21
+ * its supervisor is still stopped. A PID can be recycled between that scan and the signal; the window
22
+ * is the same one `PiRpcClient.dispose()` accepts.
23
+ */
24
+ export declare function createDarwinHostStopper(overrides?: Partial<DarwinHostDependencies>): (created: ReadonlyMap<string, string>) => Promise<void>;
@@ -1,11 +1,17 @@
1
- import { spawn } from "node:child_process";
1
+ import { execFileSync, spawn } from "node:child_process";
2
2
  import { createHash } from "node:crypto";
3
3
  import { once } from "node:events";
4
4
  import { closeSync, constants, openSync, readFileSync, readdirSync, readlinkSync, realpathSync } from "node:fs";
5
5
  import { basename } from "node:path";
6
6
  import { findPidfdPython } from "./pidfd.js";
7
+ import { listDarwinProcesses, runPs } from "./proc-table-darwin.js";
8
+ /** How long a darwin teardown waits after SIGTERM before escalating, and how often it re-reads the table. */
9
+ const HOST_TERM_GRACE_MS = 5_000;
10
+ const HOST_POLL_MS = 100;
7
11
  /** Capability and warning state are captured once, before any shutdown work. */
8
12
  export function createSessionHostStopper(platform = process.platform, probe = findPidfdPython) {
13
+ if (platform === "darwin")
14
+ return createDarwinHostStopper();
9
15
  const python = platform === "linux" ? probe() : undefined;
10
16
  let warned = false;
11
17
  return async (created) => {
@@ -22,6 +28,121 @@ export function createSessionHostStopper(platform = process.platform, probe = fi
22
28
  };
23
29
  }
24
30
  export const stopSessionHosts = createSessionHostStopper();
31
+ /** macOS has no `/proc/<pid>/cwd`; lsof prints `p<pid>`, `fcwd`, `n<path>`. */
32
+ function cwdOfHost(pid) {
33
+ try {
34
+ const output = execFileSync("/usr/sbin/lsof", ["-a", "-p", String(pid), "-d", "cwd", "-Fn"], {
35
+ encoding: "utf8",
36
+ timeout: 2_000,
37
+ maxBuffer: 1024 * 1024,
38
+ });
39
+ for (const line of output.split("\n"))
40
+ if (line.startsWith("n"))
41
+ return line.slice(1);
42
+ return undefined;
43
+ }
44
+ catch {
45
+ // No permission, no process, or no lsof: the directory is unconfirmed and nothing is signalled.
46
+ return undefined;
47
+ }
48
+ }
49
+ function signalHost(pid, signal) {
50
+ try {
51
+ process.kill(pid, signal);
52
+ }
53
+ catch (error) {
54
+ const code = error instanceof Error && "code" in error ? error.code : undefined;
55
+ // Gone since the last scan, or not ours to signal: either way nothing was sent.
56
+ if (code === "ESRCH" || code === "EPERM")
57
+ return;
58
+ throw error;
59
+ }
60
+ }
61
+ function socketName(sessionId) {
62
+ return `p-${createHash("sha256").update(`p:${sessionId}`).digest("hex").slice(0, 16)}.sock`;
63
+ }
64
+ /**
65
+ * The value of `--socket`, as Linux reads it from argv. `ps` joins argv with spaces, so the value runs
66
+ * to the next ` --` flag (a socket directory may contain spaces). Anything else after it makes the
67
+ * basename miss every shard, which fails closed.
68
+ */
69
+ function socketArgument(args) {
70
+ const match = /(?:^|\s)--socket\s+(.+?)(?=\s--|$)/.exec(args);
71
+ return match?.[1] === undefined ? undefined : basename(match[1].trimEnd());
72
+ }
73
+ function isSupervisor(args) {
74
+ const tokens = args.split(/\s+/);
75
+ return (tokens.includes("--internal-rpc-host-supervisor") || tokens.some((token) => token.endsWith("/host-lifecycle.js")));
76
+ }
77
+ /**
78
+ * macOS teardown without pidfd. Ownership is the Linux rule: a live group leader whose argv names the
79
+ * supervisor entry point and, as its `--socket` value, the shard of a session this connector created,
80
+ * and whose working directory is that session's directory. Every member of an owned group is recorded
81
+ * with its start time in the same scan, and each later signal goes to one PID only after a fresh scan
82
+ * still shows the same group and start time. Members are tracked individually, so a child that outlives
83
+ * its supervisor is still stopped. A PID can be recycled between that scan and the signal; the window
84
+ * is the same one `PiRpcClient.dispose()` accepts.
85
+ */
86
+ export function createDarwinHostStopper(overrides = {}) {
87
+ const deps = {
88
+ run: overrides.run ?? runPs,
89
+ cwdOf: overrides.cwdOf ?? cwdOfHost,
90
+ realpath: overrides.realpath ?? realpathSync,
91
+ signal: overrides.signal ?? signalHost,
92
+ delay: overrides.delay ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
93
+ graceMs: overrides.graceMs ?? HOST_TERM_GRACE_MS,
94
+ pollMs: overrides.pollMs ?? HOST_POLL_MS,
95
+ };
96
+ return async (created) => {
97
+ if (created.size === 0)
98
+ return;
99
+ const sockets = new Map([...created].map(([id, directory]) => [socketName(id), directory]));
100
+ const processes = listDarwinProcesses(deps.run);
101
+ const groups = new Set();
102
+ for (const row of processes) {
103
+ if (row.group !== row.pid || !isSupervisor(row.args))
104
+ continue;
105
+ const socket = socketArgument(row.args);
106
+ const directory = socket === undefined ? undefined : sockets.get(socket);
107
+ if (directory === undefined)
108
+ continue;
109
+ const cwd = deps.cwdOf(row.pid);
110
+ if (cwd === undefined)
111
+ continue;
112
+ let expected;
113
+ try {
114
+ expected = deps.realpath(directory);
115
+ }
116
+ catch {
117
+ continue;
118
+ }
119
+ if (cwd === expected)
120
+ groups.add(row.group);
121
+ }
122
+ const members = processes.filter((row) => groups.has(row.group));
123
+ if (members.length === 0)
124
+ return;
125
+ const survivors = () => {
126
+ const now = new Map(listDarwinProcesses(deps.run).map((row) => [row.pid, row]));
127
+ return members
128
+ .filter((member) => {
129
+ const row = now.get(member.pid);
130
+ return row !== undefined && row.group === member.group && row.start === member.start;
131
+ })
132
+ .map((member) => member.pid);
133
+ };
134
+ for (const signal of ["SIGTERM", "SIGKILL"]) {
135
+ for (const pid of survivors())
136
+ deps.signal(pid, signal);
137
+ for (let waited = 0; waited < deps.graceMs; waited += deps.pollMs) {
138
+ await deps.delay(deps.pollMs);
139
+ if (survivors().length === 0)
140
+ return;
141
+ }
142
+ }
143
+ throw new Error(`private OmO hosts survived shutdown: ${survivors().join(", ")}`);
144
+ };
145
+ }
25
146
  function processInfo(pid) {
26
147
  let fd;
27
148
  try {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antechamber-omo-compat",
3
- "version": "1.0.35",
3
+ "version": "1.0.37",
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.35",
3
+ "version": "1.0.37",
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.35",
3
+ "version": "1.0.37",
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.35",
3
+ "version": "1.0.37",
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",