v0.3.0: the app tools bridge — the bundled Codex app tools work in the shared server #5

Merged
claude merged 7 commits from oss-oo/appserver-tools-bridge into main 2026-10-27 23:57:58 +00:00
Member

v0.3.0 — the app tools bridge

The owner's requirement (2026-10-28, verbatim via the neighbour session): the bundled "Codex app tools" (send_message_to_thread, read_thread, create_thread, fork_thread, handoff_thread, …) must work in the shared app-server — Mama (OSS-OO · 🧠 · Mama-Astra) and Omi (OSS-OO · 💐 · Omi-Astra) coordinate through them from inside the app, in both directions and mostly from code mode (tools.mcp__codex_app__*). The plugin's tool server reaches the ChatGPT app over a Unix socket the app creates per launch (/tmp/codex-app-tools/<uuid>.sock) and hands only to its own app-server child as CODEX_APP_TOOLS_PIPE_PATH; the shared server never saw that path, so v0.1–v0.2 ran with the plugin off. Measured (neighbour, read-only; one read_thread on the real app socket with the owner's explicit yes): the app opens that socket even while attached to the shared server and answers a foreign process correctly (57 tools in the catalogue); the tool server reuses its pipe connection and reopens it after the app closes it; one tool-server instance per loaded thread; a dead socket answers at once, a silent one would hang the tool server. Z's design word (steer 39): (A) a bridge now, rig-measured; (C) family-own tools over the shared server as the target (with and without the app; draft in PR #6); (B) a symlink rejected.

What ships (design A)

  • oo-appserver-bridge.service + libexec/app_tools_bridge.py: a stable Unix socket $XDG_RUNTIME_DIR/oo-appserver-bridge/app-tools.sock (0600). Every connection the shared server's tool server opens there is forwarded, byte for byte, to the tools socket of the ChatGPT app running right now — resolved per connection: a listening socket under /tmp/codex-app-tools/ owned by a ChatGPT process (ss -xlp), else the newest connectable socket file (stale files of earlier launches are skipped). An app restart means a new socket; the next connection finds it. The bridge never accepts-and-hangs: no live app socket → closed at once (receipt no-app), the upstream connect has a timeout, the app's end is propagated; a half-closing client still gets a late answer (window 120 s, app_side_open recorded).
  • oo-appserver.service: Wants/After the bridge, passes the stable path as CODEX_APP_TOOLS_PIPE_PATH, runs the plugin again (codex_app.enabled=true).
  • Receipts per connection in ~/.local/state/oo-appserver/bridge.jsonl (target, bytes_to_app, bytes_to_tools, ms, outcome closed / no-app / app-unreachable / resolver-error).
  • Helper: oo-appserver tools (bridge unit, stable socket, the app's socket, plugin state in the unit file AND in the running process — they differ between an install and the gated restart); status/doctor show the line, say "restart pending (gated)" on a mismatch and warn when the unit offers the tools but the bridge is down, or when the attached app owns no tools socket.
  • Installer installs the bridge unit and module and restarts a running bridge when its unit or module changed; CI verifies both units (python stubbed like the bundle binary) and runs the bridge suite.
  • Tests tests/test_bridge.py (14 legs): resolver (app-owned listener wins; stale files skipped; fallback to the newest connectable file; none without app), forwarding both ways with receipts, a new target per connection (app restart), no-app / dead-target / resolver-error outcomes, the app's end propagated, eight concurrent connections plus an idle client, late answers to half-closing clients and the bounded window, stale socket file replaced on start, the probe CLI.
  • README: the bridge section with what the rig and the owner's app taught, the family way oo-appserver send for Mama and Omi (the owner's order), known gaps updated (the bundled tools need the running app; design C next).

Not done here (by design)

  • The station's running shared server still carries the old unit until a gated restart (oo-appserver release or restart when the owner's chats are idle, then relaunch ChatGPT from the menu); doctor says "restart pending".
  • Whether the shared server's core passes the caller's _meta to the tool server is verified at the first real read_thread from a chat after the restart (with the owner's yes), together with ALL_TOOLS in code mode.

Independent review

Fresh read-only subagent on dac3e8a (bridge suite 11/11 twice, send suite 100/100, unit verify, live ss line, scratchpad experiments with concurrent/idle/half-closing clients and an app ignoring the FIN): "mergeable after fixing 1 and 2" — plugin state read from the loaded unit file instead of the running process; bridge not restarted on a module-only change; plus three low findings (tool-server reconnect wording, the 5 s late-answer window, the accept loop dying on EMFILE) and nits. All folded in a9b9473; the reviewer's confirmation: "delta clean — mergeable as is" (13/13 twice), three nits folded in 42a15c3 (14 legs). CI green on every commit since the python-path fix (runs 21–24, and the run on 42a15c3). Review texts: v030-review/ in the evidence folder.

🤖 Generated with Claude Code

## v0.3.0 — the app tools bridge **The owner's requirement (2026-10-28, verbatim via the neighbour session):** the bundled "Codex app tools" (`send_message_to_thread`, `read_thread`, `create_thread`, `fork_thread`, `handoff_thread`, …) *must* work in the shared app-server — Mama (OSS-OO · 🧠 · Mama-Astra) and Omi (OSS-OO · 💐 · Omi-Astra) coordinate through them from inside the app, in both directions and mostly from code mode (`tools.mcp__codex_app__*`). The plugin's tool server reaches the ChatGPT app over a Unix socket the app creates per launch (`/tmp/codex-app-tools/<uuid>.sock`) and hands only to its own app-server child as `CODEX_APP_TOOLS_PIPE_PATH`; the shared server never saw that path, so v0.1–v0.2 ran with the plugin off. **Measured (neighbour, read-only; one `read_thread` on the real app socket with the owner's explicit yes):** the app opens that socket even while attached to the shared server and answers a foreign process correctly (57 tools in the catalogue); the tool server reuses its pipe connection and reopens it after the app closes it; one tool-server instance per loaded thread; a dead socket answers at once, a silent one would hang the tool server. **Z's design word (steer 39):** (A) a bridge now, rig-measured; (C) family-own tools over the shared server as the target (with and without the app; draft in PR #6); (B) a symlink rejected. ### What ships (design A) - **`oo-appserver-bridge.service` + `libexec/app_tools_bridge.py`**: a stable Unix socket `$XDG_RUNTIME_DIR/oo-appserver-bridge/app-tools.sock` (0600). Every connection the shared server's tool server opens there is forwarded, byte for byte, to the tools socket of the ChatGPT app running right now — resolved **per connection**: a listening socket under `/tmp/codex-app-tools/` owned by a `ChatGPT` process (`ss -xlp`), else the newest connectable socket file (stale files of earlier launches are skipped). An app restart means a new socket; the next connection finds it. The bridge never accepts-and-hangs: no live app socket → closed at once (receipt `no-app`), the upstream connect has a timeout, the app's end is propagated; a half-closing client still gets a late answer (window 120 s, `app_side_open` recorded). - **`oo-appserver.service`**: `Wants`/`After` the bridge, passes the stable path as `CODEX_APP_TOOLS_PIPE_PATH`, runs the plugin again (`codex_app.enabled=true`). - **Receipts** per connection in `~/.local/state/oo-appserver/bridge.jsonl` (`target`, `bytes_to_app`, `bytes_to_tools`, `ms`, `outcome` closed / no-app / app-unreachable / resolver-error). - **Helper**: `oo-appserver tools` (bridge unit, stable socket, the app's socket, plugin state in the unit file AND in the running process — they differ between an install and the gated restart); `status`/`doctor` show the line, say "restart pending (gated)" on a mismatch and warn when the unit offers the tools but the bridge is down, or when the attached app owns no tools socket. - Installer installs the bridge unit and module and restarts a running bridge when its unit or module changed; CI verifies both units (python stubbed like the bundle binary) and runs the bridge suite. - **Tests** `tests/test_bridge.py` (14 legs): resolver (app-owned listener wins; stale files skipped; fallback to the newest connectable file; none without app), forwarding both ways with receipts, a new target per connection (app restart), no-app / dead-target / resolver-error outcomes, the app's end propagated, eight concurrent connections plus an idle client, late answers to half-closing clients and the bounded window, stale socket file replaced on start, the probe CLI. - README: the bridge section with what the rig and the owner's app taught, the family way `oo-appserver send` for Mama and Omi (the owner's order), known gaps updated (the bundled tools need the running app; design C next). ### Not done here (by design) - The station's running shared server still carries the old unit until a gated restart (`oo-appserver release` or `restart` when the owner's chats are idle, then relaunch ChatGPT from the menu); `doctor` says "restart pending". - Whether the shared server's core passes the caller's `_meta` to the tool server is verified at the first real `read_thread` from a chat after the restart (with the owner's yes), together with `ALL_TOOLS` in code mode. ### Independent review Fresh read-only subagent on `dac3e8a` (bridge suite 11/11 twice, send suite 100/100, unit verify, live `ss` line, scratchpad experiments with concurrent/idle/half-closing clients and an app ignoring the FIN): "mergeable after fixing 1 and 2" — plugin state read from the loaded unit file instead of the running process; bridge not restarted on a module-only change; plus three low findings (tool-server reconnect wording, the 5 s late-answer window, the accept loop dying on EMFILE) and nits. **All folded in `a9b9473`**; the reviewer's confirmation: **"delta clean — mergeable as is"** (13/13 twice), three nits folded in `42a15c3` (14 legs). CI green on every commit since the python-path fix (runs 21–24, and the run on `42a15c3`). Review texts: `v030-review/` in the evidence folder. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
claude agent added 2 commits 2026-10-27 23:32:22 +00:00
The owner's requirement (2026-10-28): the bundled "Codex app tools" (send_message_to_thread,
read_thread, create_thread, fork_thread, handoff_thread, …) must work in the shared app-server —
Mama and Omi coordinate through them from inside the app. The plugin reaches the app over a Unix
socket the app creates per launch and hands only to its own app-server child; the shared server
therefore ran with the plugin off. Measured: the app opens that socket even while attached to the
shared server. Z's design word: a bridge now, family-own tools over the shared server later.

- `libexec/app_tools_bridge.py` + `systemd/oo-appserver-bridge.service`: a stable Unix socket
  (`$XDG_RUNTIME_DIR/oo-appserver-bridge/app-tools.sock`, 0600); every connection the shared
  server's tool server opens there is forwarded to the tools socket of the ChatGPT app running
  right now, resolved per connection (listening socket owned by a ChatGPT process via `ss -xlp`,
  else the newest connectable file; stale files skipped). Without an app the connection is closed
  at once (receipt `no-app`); when the app's side ends, the tool server's side ends too. Receipts
  per connection in `~/.local/state/oo-appserver/bridge.jsonl`.
- `oo-appserver.service`: wants the bridge, passes the stable path as CODEX_APP_TOOLS_PIPE_PATH and
  runs the plugin again (`codex_app.enabled=true`).
- helper: `oo-appserver tools` (bridge unit, stable socket, the app's socket, plugin state of the
  running unit); `status`/`doctor` show the line and warn when the unit offers the tools but the
  bridge is down, or when the attached app owns no tools socket.
- installer: the bridge unit and module; CI verifies both units and runs the bridge suite.
- tests/test_bridge.py (11 legs): resolver (app-owned listener wins, stale files skipped, fallback
  to the newest connectable file, none without app), forwarding both ways with receipts, a new
  target per connection (app restart), no-app and dead-target outcomes, the app's end propagated,
  stale socket file replaced on start, the probe CLI.
- README: the bridge section, the known gap (the bundled tools need the running app; design C next).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
claude agent added 1 commit 2026-10-27 23:34:40 +00:00
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
claude agent added 1 commit 2026-10-27 23:35:59 +00:00
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
claude agent added 1 commit 2026-10-27 23:38:25 +00:00
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
claude agent added 1 commit 2026-10-27 23:50:53 +00:00
- helper: the app-tools line and `oo-appserver tools` read the plugin state from the RUNNING server
  process (/proc/<MainPID>/cmdline) and from the unit file as loaded, and say when they differ
  ("restart pending (gated)") — after an install the loaded unit is new while the process is old;
  `OO_APPSERVER_TOOLS_SOCK` is honoured.
- installer: the bridge is restarted when its unit OR its module changed, and only if it was
  running before.
- bridge: a half-closing client still gets a late answer (window 120 s, `app_side_open` in the
  receipt), sockets are shut down both ways before closing (wakes a pump parked on a peer that
  ignores the FIN), the accept loop survives EMFILE/ECONNABORTED, the resolver is guarded against
  the app vanishing mid-resolution (safe mtimes, `resolver-error` receipt).
- unit: back to /usr/bin/python3 (CI stubs it like the bundle binary).
- tests: receipts polled instead of a fixed sleep; legs for eight concurrent connections plus an
  idle client and for a late answer to a half-closing client (13 legs).
- README: the tool server reconnects after the app closes the pipe (receipts count connections).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
claude agent added 1 commit 2026-10-27 23:53:32 +00:00
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
claude agent merged commit 95b85b3a17 into main 2026-10-27 23:57:58 +00:00
claude agent deleted branch oss-oo/appserver-tools-bridge 2026-10-27 23:57:58 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
OSS-OO/oo-appserver!5
No description provided.