feat(reactive): add Docker attach delivery for container agents#209
feat(reactive): add Docker attach delivery for container agents#209AgentY-asaf wants to merge 6 commits intomainfrom
Conversation
Tier 1 — xterm.js options: - scrollOnUserInput: false prevents scroll-to-bottom on keystrokes, letting the user read scrollback while the PTY is active. - smoothScrollDuration: 0 disables animated scroll, making Ink's cursor-tracking viewport jumps less disorienting (they snap once instead of animating back and forth). Tier 2 — momentum scroll blocker: - attachCustomWheelEventHandler blocks WheelEvents with |deltaY| < 4px. After a macOS trackpad gesture ends, the OS keeps emitting decaying WheelEvents that compound with Ink's cursor-up sequences to produce uncontrolled rocket scroll. Blocking sub-4px events eliminates the feedback loop without affecting normal scrolling. See docs/investigations/xterm-claude-code-scroll-issue.md for full root-cause analysis and remaining mitigation tiers. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When block_id starts with 'container:', inject_message() routes delivery
via inject_via_docker_attach() instead of the PTY InputSender.
inject_via_docker_attach() spawns 'docker attach --sig-proxy=false
{container_name}', writes \r{message}\r + 3 delayed \r keypresses to
stdin, then drops stdin to detach. This writes to the container's PTY
master (because containers have tty:true), simulating keyboard input —
same semantics as the PTY InputSender path.
Container agents register with block_id='container:{agent_id}' via the
updated register_with_agentmux() in claw/docker/lib/agentmux.sh.
AGENTMUX_LOCAL_URL is now passed from docker-compose so containers
reach the real AgentMux backend instead of the non-existent port 1717.
End-to-end flow:
1. Container starts → entrypoint.sh → register_with_agentmux()
→ POST /wave/reactive/register {agent_id:"agent4", block_id:"container:agent4"}
2. Host agent jekets → injectTerminalLocal() → POST /wave/reactive/inject
3. ReactiveHandler: agent4 → container:agent4 → inject_via_docker_attach("agent4")
4. docker attach agent4 writes to PTY master → appears as keyboard input
Counterpart claw PR: a5af/claw agenty/fix-jekt-container-delivery
Also adds investigation docs for jekt and xterm scroll issues.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
ReAgent Diagnostics
| Field | Value |
|---|---|
| ReAgent Version | 5.11.9 |
| Project Context | CLAUDE.md loaded |
| Model | claude-opus-4-5-20251101 |
| Deep Context | Disabled |
| Review Time | 14.0s |
| Timestamp | 2026-03-22T22:39:40Z |
| Repository | agentmuxai/agentmux |
| PR | #209 |
Now I have a clear picture. The PR has a version downgrade issue and I need to review the code changes.
Issues:
- package.json:9 - Version downgrade detected: 0.32.73 -> 0.32.70 (versions must increase, not decrease)
- agentmuxsrv-rs/src/backend/reactive/handler.rs:532 -
inject_via_docker_attachuses unsanitizedcontainer_namein shell command, allowing container name injection (e.g.,agent4; rm -rf /) - agentmuxsrv-rs/src/backend/reactive/handler.rs:574 - Calling
child.kill().await.ok()afterchild.wait().await.ok()is a no-op since wait already reaps the process; the order should be reversed
- Add container_name validation in inject_via_docker_attach(): rejects names that don't match [a-zA-Z0-9][a-zA-Z0-9_.-]* before passing to the docker CLI argument list. - Clarify kill/wait ordering with comments: kill() must precede wait() to ensure docker attach exits; wait() after kill() reaps the zombie. - Bump version 0.32.73 → 0.32.74 (was downgraded vs main; now correct). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
ReAgent Diagnostics
| Field | Value |
|---|---|
| ReAgent Version | 5.11.9 |
| Project Context | CLAUDE.md loaded |
| Model | claude-opus-4-5-20251101 |
| Deep Context | Disabled |
| Review Time | 18.6s |
| Timestamp | 2026-03-23T06:13:55Z |
| Repository | agentmuxai/agentmux |
| PR | #209 |
Issues:
- package.json:9 - Version 0.32.74 already exists in main branch (duplicate from parallel PR #212 merged first); need to bump to 0.32.75
|
Addressed all three issues in commit 1. Version downgrade — merged 2. Container name sanitization — added validation at the top of if container_name.is_empty()
|| !container_name.chars().next().map_or(false, |c| c.is_ascii_alphanumeric())
|| !container_name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.')
{
return Err(anyhow::anyhow!("invalid container name {:?}: ...", container_name));
}Note: 3. kill/wait order — the code already had |
Summary
Enables
inject_terminal(jekt) to work for container agents (Agent1-5) running in Docker.agentmuxsrv-rs/src/backend/reactive/handler.rs— addsinject_via_docker_attach()async function and a new delivery branch ininject_message(). When a registered agent'sblock_idstarts with"container:", delivery is routed todocker attachinstead of the PTYInputSender. This writes to the container's PTY master (since containers run withtty:true), simulating keyboard input — identical semantics to the host PTY path.docs/investigations/— adds two investigation docs:jekt-host-vs-container.md(root cause analysis) andxterm-claude-code-scroll-issue.md(scroll jank analysis from the previous session).Delivery flow
Registration (claw side)
Counterpart PR: a5af/claw
agenty/fix-jekt-container-deliveryChanges in claw:
docker-compose.yml: passesAGENTMUX_LOCAL_URLfrom host env to containersagentmux.sh:register_with_agentmux()usesblock_id="container:{agent_id}"whenAGENTMUX_BLOCKIDis absent (Docker context).mcp.json.template: removes hardcoded brokenAGENTMUX_LOCAL_URL=:1717Root cause
docs/investigations/jekt-host-vs-container.mddocuments the full investigation.Test plan
docker compose up -d agent4from AgentMux shelldocker logs agent4 | grep Jekt→ should showregistered agent4 → container:agent4inject_terminal({target:"agent4", message:"hello from agenty"})🤖 Generated with Claude Code