Skip to content

Queue composer, unfinished command lines and owner-side reply hold (stacked on #514) - #541

Draft
raiseCatError wants to merge 6 commits into
devfrom
fix/queue-mode-20261111
Draft

raiseCatError wants to merge 6 commits into
devfrom
fix/queue-mode-20261111

Conversation

@raiseCatError

@raiseCatError raiseCatError commented Nov 11, 2026 •

Copy link
Copy Markdown
Owner

Queue several commands while one runs, unfinished command lines, accurate program names, and an owner-side hold for late-detected readers.

Stacked on #514: this branch merges #514's head 416bc51a, so #514 must merge first. The diff against dev includes #514 until then.

What changed and why

Queue composer (replaces the earlier standing "Queue Mode")

A standing "Enter queues" mode can't be made safe. On macOS nothing reports that a program reads with no prompt, so no setting can tell a reply from a command, and such a mode would weaken #514 for every command. It was removed, together with its setting and /queue mode.

  • Ctrl+Q on an empty line while a command runs opens the queue composer for that command only. Each line entered in it is queued as typed, once and in order, with no approval step (what Ctrl+Q on a line already did).
  • It closes on Esc, on a second Ctrl+Q on the empty line, or when that command ends. It never carries over to the next command.
  • Plain Enter keeps Hold ambiguous program input for explicit recovery #514's rules. An echoing terminal is no evidence, and a line NMSh can't place waits for Ctrl+S, Tab, Esc or Ctrl+Q.
  • Hidden input closes the composer before anything is drawn. Its first key is looked at like the normal composer's: a hidden look closes it and holds the typing unseen. A request reported while it is empty closes it, so keys go where input ownership sends them. A line already begun in it stays a queued command, then the composer closes.

Owner-side hold for late-detected readers

When the running command is first reported asking for input, the session service holds every entry queued since that command was submitted, before any dispatch, and hides their text if the input is hidden. Entries come from Ctrl+Q, the composer, or anything routed before the reader was visible. This closes the timing gap where a frontend's hold request could arrive after the next entry had already been dispatched (QueueDispatcher.onInputRequest, wired in SessionService and InProcessSessionClient).

Unfinished command lines

zsh and Bash draw PS2 as a private marker. When the shell asks for more continuation lines than the submission's own line breaks explain, the session cancels the line (nothing ran), explains why (including zsh's !" history expansion), and gives the line back unless you're already typing. A queue pauses before the next entry.

Names

A line of several commands is called "the command", not its first word.

Limits that remain (documented in docs/design/input-awareness.md)

  • A reader NMSh never sees before it ends: a prompt-less external reader on macOS, or a timed read that gives up within about a second. A line typed into the queue composer for it runs afterwards as a command, exactly as a Ctrl+Q line always has.
  • A secret typed into the queue composer while the terminal echoes is shown and queued like any queued text.
  • Fish, which also happens on dev (not changed here): an unclosed quote leaves Fish waiting, and following submissions are absorbed until Ctrl+C. Fish has no continuation marker.

Automated verification (head a053031e)

  • Fixture level: 27 queue, input, settings and slash files, 274 of 274 passing (queueComposer, queueReplyHold, inputQuarantine, inputOwnership*, incompleteSubmission, …). Adversarial cases:
    • a reflex Enter during the next command
    • a password prompt not yet reported
    • a hidden question reported while the composer is open
    • a question appearing mid-line
    • a look that cannot tell
    • a reader that ends right after it is seen
  • Live PTY:
    • queueComposerLive: zsh, Bash and Fish run composed lines once, in order, without approval. The Fish run first timed out with 0 frontend output bytes at load 20 and passed on rerun; both logs are kept.
    • queueComposerRaceLive: Hold ambiguous program input for explicit recovery #514's controlled race, 0 ms and 300 ms. The reply is never run.
    • incompleteSubmission on zsh and Bash 5.3: heredocs, a quote inside a heredoc, multi-line strings, escaped newlines, functions, multi-line $( ), and blank lines in if all run. Five unfinished forms are cancelled with nothing run.
  • Full gate: npm run verify will run once, sequentially, on the pinned integration candidate. Hosted CI: earlier Ubuntu failures were the runner's apt mirror DNS outage (Temporary failure resolving 'azure.archive.ubuntu.com'); no NMSh test ran on Ubuntu.

Needs human testing (Rex, Ghostty, Terminal.app)

  1. sleep 15, then Ctrl+Q on an empty line. The composer should say "Queueing". Type three lines with Enter; they should run after sleep, in order, with no approval.
  2. Run sleep 5 again and type a line with plain Enter: the "Can't tell…" choice appears and nothing is queued.
  3. sudo -k true with the composer opened first: typing the password closes the composer, and the password is never drawn.
  4. echo "Finished!" in zsh: it is cancelled and explained, the line is returned, and nothing hangs.
  5. In Rex, check that Ctrl+Q and Ctrl+S reach NMSh.

Refs #426
Refs #473

Queue Mode (#473, #426): an explicit per-window choice that Enter queues a
line while a command runs, shown as typed and run once, in order, with no
approval step. Ctrl+Q on an empty line or /queue mode [on|off] toggles it;
Settings > Queue > Start in Queue Mode sets how windows start (off by
default). Input ownership is unchanged: the look before typing is drawn,
Enter to a program NMSh sees waiting, hidden input and the held state when
a look cannot tell all behave as before. At the prompt a Queue Mode line is
added after entries already waiting instead of running ahead of them; only
a paused queue gives the prompt back.

Unfinished command lines: zsh and Bash print PS2 as a private marker. A
continuation prompt the submission's own line breaks do not explain means
the shell waits for the rest of a line NMSh sent whole (an unclosed quote,
or zsh history expansion turning `!"` into an open quote). The session
cancels it with the adapter's sequence (zsh: Ctrl+C then Enter; Bash:
Ctrl+C), the prompt carries `incomplete`, the frontend explains it and gives
the line back, and the queue pauses before what follows. Previously the
line waited at an invisible PS2 forever and typing for it was held as
"Can't tell whether echo is reading your typing".

Program names: a line of several commands is called "the command", never
its first word, which may have finished long ago.
…input

An Enter pressed before NMSh can see that the running command reads (an echoing
terminal is no evidence, see #514) reaches the queue as a command, and in Queue
Mode without approval. The session service now holds every entry queued since
the command was submitted as soon as the command is first reported asking for
input, before any frontend hears of it, so the next dispatch can never overtake
the hold. Hidden input also hides the held text.

Adds a deterministic dispatcher test for a reader that ends right after it was
seen, and a live zsh test of #514's controlled race with Queue Mode on (the
fixture files are byte-identical copies of #514's).

Refs #426
Refs #473
… functions and substitutions

Complete multi-line constructs (quoted and unquoted heredocs, a quote inside a
heredoc, multi-line strings, escaped newlines, a function, a multi-line command
substitution, blank lines in an if block, ! in single quotes) run untouched on
zsh and Bash; five unfinished lines are cancelled with nothing run, and the
next command after each runs normally.

Refs #473
Same resolution as verified on integration/20261111: #514's queue-review hints
and #541's program naming; tests updated where each assumed only its own
behaviour. #514 must merge first.

Refs #426
Refs #473
A standing "Enter queues" mode cannot be made safe: on macOS nothing reports
that a program reads with no prompt, so no setting can tell a reply from a
command, and it would weaken #514's boundary for every command.

Ctrl+Q on an empty line while a command runs now opens the queue composer for
that command only: each line entered in it is queued as typed, without approval,
until Esc, a second Ctrl+Q on the empty line, or the end of that command. Plain
Enter keeps #514's rules. Hidden input closes the composer before anything is
drawn: its first key is looked at like the composer's, and a request reported
while it is empty closes it. A line already begun in it stays a queued command.

Removes the Start in Queue Mode setting and /queue mode. The owner-side hold on
late-detected readers stays. Documents the remaining limit: a reader NMSh never
sees before it ends.

Refs #426
Refs #473
@raiseCatError raiseCatError changed the title Queue Mode, unfinished command lines and accurate program names Queue composer, unfinished command lines and owner-side reply hold (stacked on #514) Nov 11, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant