Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 19 additions & 2 deletions .syncwheel/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,26 @@
"branch": "main-integration",
"base": "origin/main",
"strategy": "cherry-pick",
"stacks": []
"stacks": [
"codex-live-socket"
]
},
"stacks": [],
"stacks": [
{
"id": "codex-live-socket",
"branch": "codex/tmux-bridge-live-socket",
"base": "origin/main",
"target_remote": "origin",
"target_branch": "main",
"integration_branch": "main-integration",
"commits": [
"f22c1d82da4ea4520c9957b00b9ecad1b4dbfe98"
],
"meta": {
"purpose": "Fix Codex remote socket live detection"
}
}
],
"syncwheel_worktree_root": "var/syncwheel",
"syncwheel_tracking": "git-tracked"
}
15 changes: 12 additions & 3 deletions packages/tmux-bridge/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ MESH_AGENT_CAPABILITIES="code,review,plan"

## Usage

Prerequisites: `bash`, `tmux`, `python3`, `ss`, and the target agent CLI
(`codex`, `claude`, or another configured agent).

```bash
BIN="packages/tmux-bridge/bin" # relative to repo root

Expand Down Expand Up @@ -88,12 +91,18 @@ has not changed for the stall window, they return `stalled` / `STALLED` with exi
`124`. The caller LLM should inspect status and decide whether to keep waiting,
update the user, or ask before stopping the other agent.

For large prompts, `agent-send.sh` waits for bracketed-paste rendering to settle
before submitting, then confirms the submit with a real working/done marker
instead of treating any pane repaint as success.

## Codex Desktop visibility (remote mode)

Bridge Codex sessions show up **inside Codex Desktop automatically** whenever the
desktop app-server socket is live — no env var to remember. `codex.conf`
auto-detects `~/.codex/app-server-control/desktop-ssh-websocket-v0.sock` and adds
`--remote unix://…` when it exists. Overrides:
desktop app-server has a listening socket — no env var to remember. `codex.conf`
checks the known app-server socket candidates in order and adds `--remote
unix://…` only for the first candidate that is actually listening. If no
candidate is live, the bridge falls back to a standalone tmux-only Codex session
instead of failing on a stale socket file. Overrides:

| Want | Do |
|---|---|
Expand Down
24 changes: 17 additions & 7 deletions packages/tmux-bridge/agents/codex.conf
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,27 @@ AGENT_IDLE_PATTERN="gpt-|o[0-9] |claude-"
# Regex: explicit "turn finished" marker (used by agent-wait.sh for robust completion detection)
AGENT_DONE_PATTERN="Worked for"

# Codex Desktop visibility: when the desktop app-server socket is live, connect the
# TUI to it via --remote so bridge sessions also appear inside Codex Desktop. This is
# now AUTOMATIC — no need to remember to export CODEX_REMOTE_SOCK. Precedence:
# Codex Desktop visibility: when an app-server socket is live, connect the TUI to it
# via --remote so bridge sessions also appear inside Codex Desktop. This is now
# AUTOMATIC — no need to remember to export CODEX_REMOTE_SOCK. Precedence:
# * CODEX_REMOTE_SOCK already set (even to "") → respected verbatim (your override)
# * CODEX_NO_REMOTE=1 → force standalone, tmux-only session
# * otherwise → auto-detect the desktop socket
# * otherwise → auto-detect the first LISTENING socket
# We must test that the socket is actually listening, not merely present: a stale
# socket file (e.g. a leftover SSH-tunnel websocket) makes `codex --remote` fail with
# "failed to connect to remote app server". Candidates are tried in order; if none is
# live we fall back to a standalone tmux-only session instead of failing the launch.
# Verified: --remote coexists with -c overrides (e.g. model_reasoning_effort=xhigh).
if [[ -z "${CODEX_REMOTE_SOCK+x}" && -z "${CODEX_NO_REMOTE:-}" ]]; then
_codex_desktop_sock="${HOME}/.codex/app-server-control/desktop-ssh-websocket-v0.sock"
[[ -S "$_codex_desktop_sock" ]] && CODEX_REMOTE_SOCK="$_codex_desktop_sock"
unset _codex_desktop_sock
for _codex_sock in \
"${HOME}/.codex/app-server-control/desktop-ssh-websocket-v0.sock" \
"${HOME}/.codex/app-server-control/app-server-control.sock"; do
if [[ -S "$_codex_sock" ]] && ss -lx 2>/dev/null | grep -qF "$_codex_sock"; then
CODEX_REMOTE_SOCK="$_codex_sock"
break
fi
done
unset _codex_sock
fi

# Resume an existing on-disk session ({SESSION_ID} is substituted by agent-session.sh).
Expand Down
28 changes: 20 additions & 8 deletions packages/tmux-bridge/bin/agent-send.sh
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,30 @@ mtmux delete-buffer -b "$BUFFER_NAME" 2>/dev/null || true
trap - EXIT
sleep 0.2

# Submit and CONFIRM submission. A large paste is ingested asynchronously by the
# TUI (shown as a collapsed "[Pasted Content N chars]" placeholder); a submit key
# sent during ingestion is dropped, leaving the prompt unsent. So we send the
# submit key, then verify the turn actually started (working spinner appears or
# the pane changes); if not, re-send the submit key a few times.
_pre_submit="$(mtmux capture-pane -t "$TARGET" -p 2>/dev/null)"
# A large paste lands in several bracketed-paste chunks, each rendered
# asynchronously as a collapsed "[Pasted Content N chars]" placeholder. Wait for
# the pane to stop mutating before submitting, otherwise the still-rendering
# paste looks like "the turn started".
_settle_prev=""
for _ in 1 2 3 4 5 6 7 8; do
sleep 0.5
_settle_now="$(mtmux capture-pane -t "$TARGET" -p 2>/dev/null)"
[[ "$_settle_now" == "$_settle_prev" ]] && break
_settle_prev="$_settle_now"
done

# Submit and CONFIRM the turn actually started. Confirmation keys on the working
# spinner ONLY — NOT "the pane changed" (the paste keeps repainting the pane as it
# renders) and NOT the done marker (the PREVIOUS turn's "Worked for …" lingers on
# screen and would falsely confirm after a single dropped submit key). A new turn
# always shows the working spinner, which a finished turn does not. Keep re-sending
# until it appears; submit keys hitting an already-empty composer are harmless.
submitted=0
for _attempt in 1 2 3 4; do
for _attempt in 1 2 3 4 5 6 7 8; do
mtmux send-keys -t "$TARGET" "$AGENT_SUBMIT_KEY"
sleep 1
_now="$(mtmux capture-pane -t "$TARGET" -p 2>/dev/null)"
if echo "$_now" | grep -qE "$AGENT_WORKING_PATTERN" || [[ "$_now" != "$_pre_submit" ]]; then
if echo "$_now" | grep -qE "$AGENT_WORKING_PATTERN"; then
submitted=1; break
fi
done
Expand Down
13 changes: 9 additions & 4 deletions skills/agent-tmux/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export AGENT_MESH_ROOT="<path-to-agent-mesh-repo>"
BIN="${AGENT_MESH_ROOT}/packages/tmux-bridge/bin"
```

Prerequisites: `bash`, `tmux`, `python3`, and the target agent CLI (`codex`
Prerequisites: `bash`, `tmux`, `python3`, `ss`, and the target agent CLI (`codex`
and/or `claude`). All sessions share the dedicated tmux socket `mesh`
(`tmux -L mesh`), kept alive with `exit-empty off` — never the default server.

Expand Down Expand Up @@ -106,9 +106,11 @@ Codex bridge sessions attach to the desktop **app-server automatically** wheneve
its socket is live — they show up inside Codex Desktop and are co-pilotable from
mobile over the remote-control tunnel. No env var to remember.

- `codex.conf` auto-detects `~/.codex/app-server-control/desktop-ssh-websocket-v0.sock`
and adds `--remote unix://…`; it also passes `--cd "<dir>"` (in remote mode the
app-server ignores `tmux -c`, so this keeps the session in the right project).
- `codex.conf` checks the known app-server socket candidates in order and adds
`--remote unix://…` only for the first candidate that is actually listening; if
none is live, it falls back to a standalone tmux-only session. It also passes
`--cd "<dir>"` in remote mode (the app-server ignores `tmux -c`, so this keeps
the session in the right project).
- Override: `CODEX_REMOTE_SOCK=/path.sock` forces a socket; `CODEX_NO_REMOTE=1`
forces a standalone, tmux-only session (use for long unattended runs you want
isolated from daemon restarts).
Expand Down Expand Up @@ -169,6 +171,9 @@ See `references/agent-mesh-repo-sync.md` for the full sequence and pitfalls.
`tmux send-keys … Enter` directly for prompt text.
- **Multiline / special chars.** `agent-send.sh` injects prompts via
`tmux paste-buffer`; always use it, never raw `send-keys`.
- **Large prompts.** `agent-send.sh` waits for bracketed-paste rendering to
settle before submitting, then confirms submit via working/done markers so a
paste repaint is not mistaken for a started turn.
- **Codex trust dialog.** New directories prompt a workspace-trust dialog;
`agent-session.sh` auto-confirms with Enter. If stuck, check `--status` and
capture the pane with `--full`.
Expand Down