mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 10:39:32 +01:00
fix(input): keep the line breaks of a multi-line mux prompt, pasted as one block
TmuxManager.sendInput() removed every `\n` before the text reached tmux, so a multi-line prompt sent with `useMux` (or a multi-line respawn prompt) arrived as one line with its lines run together: `"echo A\necho B\r"` ran `echo Aecho B`. The text half of a submit has gone in as one bracketed paste (buildPasteTextCommands) since the Codex paste-burst fix, and inside the paste markers Claude Code and Codex insert a pasted line break into the composer instead of submitting. So the `\n` is now kept (`\r\n` loses only its `\r`) and the existing paste carries the lines; the Enter stays a separate tmux call. Multi-line text without an Enter is pasted too rather than typed with `send-keys -l`, where every line break would reach the pane as a key. Single-line input is unchanged. The test-mode gate moves from the top of sendInput() into inputRunner(), as readPaneRows() does, so a test subclass can record the commands sendInput() would run instead of pinning the branch shapes on the source. SubmitVerifier still compares the composer's last glyph line with the prompt minus its newlines, so it does not reliably recognise a multi-line paste and may not re-press an Enter that Claude Code ignored. `agent send` and the TUI therefore keep refusing or joining multi-line input; the docs that described the stripping now describe the paste and that limit. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
39077281d1
commit
e3de354461
19 files changed
+164
-56
No files matched your search
@@ -119,7 +119,7 @@ Spawn commands are pinned as literal strings in `test/cli-registry-spawn-golden.
|
||||
|
||||
### Input delivery and WS resilience
|
||||
|
||||
**Input**: `session.writeViaMux()` for programmatic/curl input — the text as one tmux bracketed paste (`paste-buffer -p`, so Codex cannot fold the Enter into a paste burst) + `send-keys Enter`. A single character is typed with `send-keys -l` instead (`submitsAsKeys()`): Claude Code's and Codex's selection dialogs ignore a pasted `3`, and the Enter then confirms the highlighted option. Single-line only (fire-and-once). Interactive **browser** input goes through a durable **exactly-once** layer: each frame carries a stable `clientId` + monotonic per-session `seq`, persisted to localStorage until the server ACKs (`{t:'ia',seq}` over WS, or HTTP 2xx), so a dropped link/reconnect can't lose or double-deliver a prompt. A duplicate is ACKed as `{t:'ia',seq,dup:true,last}` so a client whose persisted counter rolled back below the server's watermark can lift itself out instead of typing into a silently dead terminal (`docs/reliable-input-delivery.md`). **WS resilience** (#149): the upgrade URL carries `cid = clientId + ':' + perTabNonce`, and `ws-connection-registry.ts` supersedes only same-TAB reconnects (two tabs on one session coexist; input frames keep the bare `clientId` for seq dedup); reconnects back off exponentially (attempts preserved across `_connectWs`), and the header connection chip renders from a real `_wsState` lifecycle (`connecting`/`connected`/`fallback`/`reconnecting`/`disconnected`).
|
||||
**Input**: `session.writeViaMux()` for programmatic/curl input — the text as one tmux bracketed paste (`paste-buffer -p`, so Codex cannot fold the Enter into a paste burst) + `send-keys Enter`. A single character is typed with `send-keys -l` instead (`submitsAsKeys()`): Claude Code's and Codex's selection dialogs ignore a pasted `3`, and the Enter then confirms the highlighted option. A multi-line prompt keeps its line breaks inside that one paste, with or without the Enter (fire-and-once). Interactive **browser** input goes through a durable **exactly-once** layer: each frame carries a stable `clientId` + monotonic per-session `seq`, persisted to localStorage until the server ACKs (`{t:'ia',seq}` over WS, or HTTP 2xx), so a dropped link/reconnect can't lose or double-deliver a prompt. A duplicate is ACKed as `{t:'ia',seq,dup:true,last}` so a client whose persisted counter rolled back below the server's watermark can lift itself out instead of typing into a silently dead terminal (`docs/reliable-input-delivery.md`). **WS resilience** (#149): the upgrade URL carries `cid = clientId + ':' + perTabNonce`, and `ws-connection-registry.ts` supersedes only same-TAB reconnects (two tabs on one session coexist; input frames keep the bare `clientId` for seq dedup); reconnects back off exponentially (attempts preserved across `_connectWs`), and the header connection chip renders from a real `_wsState` lifecycle (`connecting`/`connected`/`fallback`/`reconnecting`/`disconnected`).
|
||||
|
||||
### Per-session env overrides: exact-key allowlist and CLAUDE_CONFIG_DIR
|
||||
|
||||
|
||||
@@ -183,7 +183,7 @@ curl -u admin:$PASS -X POST http://127.0.0.1:3000/api/v1/sessions \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"workingDir":"/home/me/project","mode":"claude"}'
|
||||
|
||||
# Send a prompt (single-line only, and it must end with \r: Enter is sent only
|
||||
# Send a prompt (it must end with \r: Enter is sent only
|
||||
# when the input contains a carriage return; without it the text sits on the
|
||||
# session's prompt unsubmitted)
|
||||
curl -u admin:$PASS -X POST http://127.0.0.1:3000/api/v1/sessions/$ID/input \
|
||||
|
||||
@@ -78,7 +78,7 @@ input for the session, and came back from localStorage on each reload.
|
||||
- `_sendInputAsync()` splits a paste over the frame limit into in-limit frames
|
||||
(`CodemanInputLimit.split`, constants.js, never cutting a surrogate pair). They
|
||||
go out in seq order, so the PTY sees one contiguous stream. A paste over
|
||||
`PASTE_MAX_CHARS` (1 MiB), or an oversized `useMux` write (line-oriented, never
|
||||
`PASTE_MAX_CHARS` (1 MiB), or an oversized `useMux` write (one paste plus its own Enter, never
|
||||
split), is refused with a toast and never queued.
|
||||
- The WebSocket answers an oversized sequenced frame with
|
||||
`{t:'ia', seq, err:'too_large', max}`; the client drops it with a toast. A
|
||||
|
||||
@@ -125,11 +125,13 @@ These are set in every managed session. Read them rather than hardcoding anythin
|
||||
|
||||
Read these before writing any code. Each one has cost somebody an afternoon.
|
||||
|
||||
1. **Input is single line and must end with `\r`.** Enter fires only when the payload
|
||||
contains a carriage return. Without it the text sits unsubmitted on the prompt, the
|
||||
request still succeeds, and a combined wait burns its full timeout on a turn that never
|
||||
started. Embedded newlines are stripped rather than rejected, so `"echo A\necho B\r"` runs
|
||||
the joined `echo Aecho B`. One line per call.
|
||||
1. **Input must end with `\r`.** Enter fires only when the payload contains a carriage
|
||||
return. Without it the text sits unsubmitted on the prompt, the request still succeeds,
|
||||
and a combined wait burns its full timeout on a turn that never started. Embedded
|
||||
newlines are kept: the text goes in as one paste and the Enter after it submits all of
|
||||
it, so `"echo A\necho B\r"` runs both commands in a shell. The check that re-presses a
|
||||
stranded Enter cannot reliably recognise a multi-line paste, so a long multi-line
|
||||
prompt to a just-started agent is still safer in a file it is told to read.
|
||||
2. **Make input idempotent.** Send a stable `clientId` and a monotonic per-session `seq`. The
|
||||
server deduplicates, so a retry after a dropped connection cannot double-deliver.
|
||||
3. **Auth.** With `CODEMAN_PASSWORD` set, use HTTP Basic or the session cookie. A missing
|
||||
|
||||
@@ -125,9 +125,10 @@ Sending prompts over the API has one rule that catches everyone: **the payload m
|
||||
`\r`** or Enter is never sent. The request still succeeds, the text sits unsubmitted in the
|
||||
composer, and any wait burns its whole timeout on a turn that never started.
|
||||
|
||||
Input is also **single line**. Embedded newlines are stripped rather than rejected, so
|
||||
`"echo A\necho B\r"` runs the joined `echo Aecho B`. Put multi-line content in a file and
|
||||
tell the agent to read it.
|
||||
Embedded newlines are kept: multi-line input goes in as one paste and the Enter after it
|
||||
submits all of it, so `"echo A\necho B\r"` runs both commands in a shell. The check that
|
||||
re-presses a stranded Enter cannot reliably recognise a multi-line paste, so long
|
||||
multi-line content is still safer in a file the agent is told to read.
|
||||
|
||||
See [Driving Codeman From An Agent](Driving-Codeman-From-An-Agent).
|
||||
|
||||
|
||||
Reference in new issue
Block a user