Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9398cb8
docs: define persistent Codex session implementation
ScriptedAlchemy Sep 16, 2026
f7d9c96
feat(codex): add persistent event-capable app-server sessions
ScriptedAlchemy Sep 16, 2026
ed30ed9
fix(codex): consume response ownership before serialization
ScriptedAlchemy Sep 16, 2026
7f03632
docs: define plugin and MCP Codex conversations
ScriptedAlchemy Sep 16, 2026
0f86d73
feat(codex): add bounded conversations and plugin messaging tools
ScriptedAlchemy Sep 16, 2026
9d75150
fix(codex): enforce cancellation and preserve reconciliation identity
ScriptedAlchemy Sep 16, 2026
c1573bf
docs: define managed plugin conversation relay and live proof
ScriptedAlchemy Sep 16, 2026
21e9efb
docs: anchor active-turn replies to observed message identity
ScriptedAlchemy Sep 16, 2026
9a41c77
feat: add durable managed conversation relay core
ScriptedAlchemy Sep 16, 2026
d82b373
fix: preserve relay reconciliation and stop guarantees
ScriptedAlchemy Sep 16, 2026
b28c5a3
docs: record packaged worker and native source identity proof
ScriptedAlchemy Sep 16, 2026
6f39e94
Merge remote-tracking branch 'origin/main' into codex/duplex-bridge
ScriptedAlchemy Sep 16, 2026
ae51078
feat: ship managed Grok Codex relay worker and tools
ScriptedAlchemy Sep 16, 2026
76cc593
fix: enforce explicit managed relay constraints
ScriptedAlchemy Sep 16, 2026
6ee55dd
refactor: remove legacy API paths
ScriptedAlchemy Sep 16, 2026
18047d1
docs: record native messaging and worker recovery proof
ScriptedAlchemy Sep 16, 2026
e6cae1e
Merge remote-tracking branch 'origin/main' into codex/duplex-bridge
ScriptedAlchemy Sep 16, 2026
0c19253
fix: preserve relay send policy cancellation and recovery
ScriptedAlchemy Sep 16, 2026
239f36d
docs: record reviewed relay behavior and release evidence
ScriptedAlchemy Sep 16, 2026
ff76386
fix: force test mode before loading unit tests
ScriptedAlchemy Sep 16, 2026
5ed78b9
merge: retain forced test bootstrap on updated main
ScriptedAlchemy Sep 16, 2026
af5308c
merge: preserve managed relay contracts with canonical APIs
ScriptedAlchemy Sep 16, 2026
58b24eb
Merge commit '7939962334344d3b579ec75e1f2a6025a06f7c0e' into codex/re…
ScriptedAlchemy Sep 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fluid-codex-conversations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"grok-bot-cli": minor
---

Add Codex conversation tools to generated plugins and MCP: bounded thread discovery, guarded sends, completion waits, and event watching. Add CLI wait/watch and send --wait while preserving immediate send receipts.
5 changes: 5 additions & 0 deletions .changeset/managed-conversation-relay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"grok-bot-cli": minor
---

Add managed Grok/Codex conversation delivery with native Codex reply routing, explicit durable links, scoped operator responses, background worker lifecycle controls, and packaged foreground service entry. Preserve manual sends when native identity is unavailable and distinguish accepted submissions from execution and return delivery.
91 changes: 84 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,79 @@ Options are command-local (for example, `gbot send --history-dir DIR ...`);
a JSON document on stdout with `exitCode` (see below), every other command
prints the failure message on stderr and exits 1.

## Automatic Grok ↔ Codex replies

In a native Codex invocation, `gbot_send` sends once and returns a durable exchange
receipt. Continue working: matching Grok replies arrive in the originating Codex
thread automatically. That thread's next answer is not sent back to Grok unless
you deliberately send again. The background worker survives the MCP caller exiting. Outgoing MCP tool calls
still follow the host's tool-approval policy; the bridge does not change it.

When native identity is unavailable (including Cursor), supply `codexThreadId` on
the send, or make a one-time `gbot_bridge_start` binding with `grokTarget`,
`codexThreadId` and optional `expectedCwd`. New visible Grok bot messages then arrive
in that Codex thread, and its corresponding terminal answer returns to Grok.
Existing transcript history is not replayed. `codex_send` can also request a return
with `replyToGrok` or `bindingId`. Managed delivery defaults to guarded steering of
active work; `busyPolicy: "reject"` on a binding refuses busy threads. A managed
`codex_send` can select `whenBusy` for that one delivery; omitting it uses the
binding's stored policy. An override does not change subsequent linked traffic.

Without an identifiable native source or explicit route, `gbot_send` preserves the
ordinary send and returns `replyRoute: {mode: "manual", reason: "source-unavailable"}`;
read with `gbot_thread` later. `replyMode: "manual"` deliberately selects that flow.
An automatic-route startup failure never falls back to an untracked send.

```sh
# CLI sends remain manual unless routing is explicitly requested.
gbot send --reply-mode auto --codex-thread-id THREAD_ID Researcher "Please investigate"
gbot codex bridge start --codex-thread-id THREAD_ID --expected-cwd /project Researcher --json
gbot codex bridge status --json
gbot codex send --reply-to-grok Researcher THREAD_ID "Investigate and return your final answer"
gbot codex bridge stop --binding-id BINDING_ID --json
# Explicitly stop the process; pending records remain for a later restart.
gbot codex bridge stop --worker --json
```

`gbot_bridge_status` separates worker health, binding coverage, submission, execution,
return delivery and pending operator interactions. `unknown` is not rejection: inspect
status before resending. Caller-supplied `requestId` (CLI `--request-id`) permits exact
replay of a tracked send without another submission; returned `controlRequestId` is
that control identity, separate from the gateway's transcript `requestId`.
Explicit chain `hop`/`correlationId` remain bounded. Automatic CLI routes own their
envelope; omit the legacy `--envelope` and `--reply-to` flags. Managed Codex return
routes also reject explicit `expectedTurnId`/`--expected-turn-id`; use plain Codex
send when a caller-selected turn guard is required. If a binding and an explicit
Grok target are supplied together, the target must resolve to that binding's recipient.
Paused gaps/capacity or unsupported interactions need attention; never guess a cursor
or automatically approve. `gbot_codex_respond` / `gbot codex bridge respond` requires
current `interactionId`, `generation`, `threadId`, `turnId` and binding/exchange scope.
Supply one-time `decision: accept|decline|cancel`, or `answersJson` containing exact
question IDs mapped to `{"answers":["answer"]}`. Session permissions and policy
amendments remain in the owning Codex UI.

State lives in the user-owned `~/.grok-bot-cli/relay/` directory, or
`GROK_BOT_RELAY_DIR`, outside plugin caches. Profile mismatches fail visibly; gateway
overrides and thread restrictions cannot reuse a differently authorized worker.
Authentication failures use bounded retries (1..30 seconds) from the saved cursor;
restored credentials do not reset coverage or resend uncertain submissions.
No login service is installed. Network reconnects are automatic; a dead process
needs a new tracked send or bridge start to resume saved routes. A dead worker is
reported as stopped, not running.

`gbot codex bridge run --lifetime-ms 60000` runs in the foreground for an explicit
bounded lifetime (default and maximum 23 hours), then closes cleanly before the CLI
renderer deadline. For an unlimited foreground service entry, run
`node /path/to/plugin/scripts/gbot-relay.mjs` (npm package: `dist/scripts/gbot-relay.mjs`)
with the same authorized environment. SIGINT/SIGTERM or explicit worker stop closes
observation without interrupting a Codex turn. Windows retains the Codex Unix-socket
limitation.

Codex, Cursor, Claude and portable artifacts contain these tools and the worker.
Grok Bot participates through its existing gateway conversation; a native Grok plugin
loader or remote stdio tunnel has not been established. Artifact availability alone
does not mean a host has loaded the plugin.

## Gateway URL policy

By default `gbot` only sends credentials to expected hosts:
Expand All @@ -72,7 +145,7 @@ gbot codex send <threadId> "Grok here: the build is green, please continue."

`send` resumes the thread, starts a turn with your text, prints the turn id, and returns; Codex keeps working after `gbot` disconnects. Every command accepts `--json`.

**Which Codex you reach.** `gbot` connects to `$CODEX_HOME/app-server-control/app-server-control.sock` (default `~/.codex/...`) with a built-in WebSocket client. The daemon must be started by `codex app-server daemon start`. `list-threads` shows the threads recorded under `CODEX_HOME` (CLI, TUI, VS Code); `send` works on any of them that no other client currently holds open. Method and parameter names are pinned to the Codex release recorded in `src/core/codex-bridge.js` (`codex app-server generate-json-schema`); `status` prints the daemon and CLI versions so a stale daemon is visible, and `codex app-server daemon restart` picks up the installed CLI. Native Windows is not supported yet (AF_UNIX control socket); use WSL, Linux, or macOS.
**Which Codex you reach.** `gbot` connects to `$CODEX_HOME/app-server-control/app-server-control.sock` (default `~/.codex/...`) with a built-in WebSocket client. The daemon must be started by `codex app-server daemon start`. `list-threads` shows the threads recorded under `CODEX_HOME` (CLI, TUI, VS Code); `send` uses the resumed thread state and selected busy policy: ordinary sends reject active work, while explicitly selected guarded steering can deliver into the active turn. Method and parameter names are pinned to the Codex release recorded in `src/core/codex-bridge.js` (`codex app-server generate-json-schema`); `status` prints the daemon and CLI versions so a stale daemon is visible, and `codex app-server daemon restart` picks up the installed CLI. Native Windows is not supported yet (AF_UNIX control socket); use WSL, Linux, or macOS.

**ChatGPT Desktop limitation.** Desktop runs its own private stdio app-server and does not publish the shared control socket, so external clients cannot reach live Desktop tasks. When the socket is absent, `gbot codex status` exits 1 and says so, naming the upstream issues: [openai/codex#41014](https://github.com/openai/codex/issues/41014) and [openai/codex#41112](https://github.com/openai/codex/issues/41112). `gbot` never reads Desktop's temporary `CODEX_APP_TOOLS_PIPE_PATH` sockets under `/tmp/codex-browser-use/`; that channel is private to Desktop.

Expand All @@ -90,7 +163,7 @@ Fail-open runs only before any stdin byte is consumed and no daemon payload was

Residual risks, stated honestly: requests without both matching thread and turn IDs remain unanswered; a Codex client must handle those requests. A daemon that speaks framing-valid but semantically unexpected JSON-RPC (unknown methods, id-less responses) is treated as transport; pins are to app-server schema 0.154.0. The bridge trusts the local control socket; a malicious local daemon could hold the session up to the stated budgets, not past them.

Permanent tradeoff, stated plainly: Desktop's app-tools MCP (`-c` overrides on its spawn line) is not applied to the already-running managed daemon, and no config/`mcpServer`/`reload` path imports Desktop's `-c` flags — Desktop app-tools stay degraded while pointed at the shared daemon. Fully quit and relaunch ChatGPT.app after install (or login) so it inherits `CODEX_CLI_PATH`. `status` reads the macOS GUI-domain value via `launchctl getenv` (what Desktop actually inherits) alongside the calling shell's value. LaunchAgent persistence is macOS-first; elsewhere install still writes the wrapper and bridge but leaves `CODEX_CLI_PATH` for you to export. `~/.codex/bin` holds scripts only — there is no extra revert note to clean up; revert is `gbot codex desktop-shim uninstall` plus this section.
Current shim limitation: Desktop's spawn-time app-tools MCP `-c` overrides are not forwarded to the already-running managed daemon. No restoration path or app-tools parity has been demonstrated here; this is not a claim of permanent protocol impossibility. Fully quit and relaunch ChatGPT.app after install (or login) so it inherits `CODEX_CLI_PATH`. `status` reads the macOS GUI-domain value via `launchctl getenv` (what Desktop actually inherits) alongside the calling shell's value. LaunchAgent persistence is macOS-first; elsewhere install still writes the wrapper and bridge but leaves `CODEX_CLI_PATH` for you to export. `~/.codex/bin` holds scripts only — there is no extra revert note to clean up; revert is `gbot codex desktop-shim uninstall` plus this section.

**Status contract (`gbot codex status --json`).** `reachable` is endpoint reachability only. `socketState` is `socket`, `absent`, `permission-denied`, or `not-a-socket`; `mode` is `daemon` for a usable daemon, otherwise the failure: `socket-absent`, `permission-denied` (the file or the connect refused this user), `not-a-socket`, `connect-failed` (socket present, nothing completed the WebSocket upgrade), `handshake-failed` (upgrade or `initialize` failed), `windows-unsupported`, or `bad-response` (reachable, but `initialize` returned something off-schema — `reachable` stays `true`). `schema.compatibility` is `exact` when the daemon reports the pinned version, `unverified` when it differs (methods usually survive upgrades, but the shapes are not re-checked), or `unknown`. `cliVersionProbe` reports whether `codex --version` answered (`ok`, `missing`, `timeout` after 3 s, `error`). The document is always written to stdout and includes `exitCode`; it is `0` only for a usable daemon.

Expand All @@ -107,7 +180,7 @@ gbot codex send --correlation-id M --reply-to M --hop 1 <threadId> "ack" # the a

Sends at `hop >= GROK_BOT_MAX_HOPS` (default 4) are refused with `reason: "hop-limit"` before anything reaches the daemon, so two agents cannot acknowledge each other forever; `gbot` never auto-acknowledges. `--envelope` (implied by any envelope flag) prepends a one-line `[gbot msg=… corr=… reply-to=… hop=… from=user@host]` header so the receiving agent can quote the ids back. That header is caller-authored provenance for the reader, not authentication: the daemon authenticates the local user through the socket, nothing else. Private ChatGPT Desktop pipes and arbitrary ChatGPT chats stay out of scope; only Codex threads on a reachable app-server daemon are routes.

**Busy threads.** `send` reads the thread status on resume. Only `idle` and `notLoaded` threads start a turn. An `active` thread (a turn in progress, or waiting on approval / user input) is refused with `reason: "busy"`: in app-server 0.154.0 a `turn/start` on an active thread steers that turn rather than queueing behind it, and `gbot` never steers or interrupts work a human may be doing. Either wait for `list-threads` to show `idle` and resend, or pass `--when-busy queue` to hand the message to the daemon's own queue through Codex's experimental `thread/queue/add` — that needs `GROK_BOT_CODEX_EXPERIMENTAL=1`, returns `delivery: "queued"` with `queuedSubmissionId`, and `gbot codex queue <threadId>` shows what is still waiting. `systemError` threads are refused with `reason: "thread-error"`, statuses this version does not know with `reason: "unknown-status"`. Receipts distinguish `delivery: "accepted"` (turn started; `turnId`, `turnStatus`), `"queued"`, `"rejected"` (nothing was sent; see `reason`), and `"unknown"` (the request left but no acknowledgment came back — look for `messageId` in the thread or queue before resending). The decision record, with the schema evidence and a live probe of the queue API, is in [`docs/codex-busy-threads.md`](docs/codex-busy-threads.md).
**Busy threads.** `send` reads the thread status on resume. Only `idle` and `notLoaded` threads start a turn. An `active` thread (a turn in progress, or waiting on approval / user input) is refused with `reason: "busy"`: in app-server 0.154.0 a `turn/start` on an active thread steers that turn rather than queueing behind it, by default. Explicit guarded steering and managed bridge routes can deliver into active work; they never interrupt a turn. Either wait for `list-threads` to show `idle` and resend, or pass `--when-busy queue` to hand the message to the daemon's own queue through Codex's experimental `thread/queue/add` — that needs `GROK_BOT_CODEX_EXPERIMENTAL=1`, returns `delivery: "queued"` with `queuedSubmissionId`, and `gbot codex queue <threadId>` shows what is still waiting. `systemError` threads are refused with `reason: "thread-error"`, statuses this version does not know with `reason: "unknown-status"`. Receipts distinguish `delivery: "accepted"` (turn started; `turnId`, `turnStatus`), `"queued"`, `"rejected"` (nothing was sent; see `reason`), and `"unknown"` (the request left but no acknowledgment came back — look for `messageId` in the thread or queue before resending). The decision record, with the schema evidence and a live probe of the queue API, is in [`docs/codex-busy-threads.md`](docs/codex-busy-threads.md).

**Failure modes.** Every `send` and `codex` outcome under `--json` is one document on stdout with `exitCode`; failures include `{ error, delivery, reason, messageId, correlationId, hop, exitCode: 1, … }` and the process exits 1. Framework argument/schema errors remain on stderr and exit 2. `--json` is reserved anywhere before `--`; put `--` before flag-like message text. `reason` values are stable:

Expand All @@ -123,10 +196,14 @@ Sends at `hop >= GROK_BOT_MAX_HOPS` (default 4) are refused with `reason: "hop-l
## Talking to Grok Bot from Codex

The npm package is also an [Agent Bundle](https://scriptedalchemy.github.io/agent-bundle/) plugin
that gives Codex, Claude Code, and Cursor two MCP tools on a `grok-bot` server,
`gbot_send` and `gbot_thread`, plus a `talk-to-grok-bot` skill that tells the agent
when to ping a bot and how to word the message. The tools bundle this repository's
gateway client, so the installed plugin does not need `gbot` on `PATH`.
that gives Codex, Claude Code, and Cursor a `grok-bot` MCP server with messaging,
Codex conversation, and managed bridge tools, plus a `talk-to-grok-bot` skill.
`gbot_send` and `gbot_thread` handle Grok conversations; `codex_threads`,
`codex_send`, `codex_wait`, and `codex_watch` handle Codex conversations.
`gbot_bridge_start`, `gbot_bridge_status`, `gbot_bridge_stop`, and
`gbot_codex_respond` manage automatic delivery and scoped operator responses.
The tools bundle this repository's gateway client and worker, so the installed
plugin does not need `gbot` on `PATH`.

Install the bundled host projections from the same npm package:

Expand Down
2 changes: 1 addition & 1 deletion agent-bundle.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default defineConfig({
output: { distPath: 'artifact' },
plugin: {
description:
'Message Grok Bot bots and groups and read their threads from Codex, Claude Code, and Cursor.',
'Message Grok Bot from Codex, Claude Code, and Cursor, with managed automatic replies and explicit Codex conversation links.',
// plugin.name is also the routed bin name: `dist/bin/gbot.mjs`.
name: 'gbot',
},
Expand Down
Loading