Skip to content

Release v0.18.0: Context Engine, Managed Agents & Workspaces - #345

Open
raiseCatError wants to merge 56 commits into
masterfrom
release/v0.18.0
Open

raiseCatError wants to merge 56 commits into
masterfrom
release/v0.18.0

Conversation

@raiseCatError

@raiseCatError raiseCatError commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Release candidate for v0.18.0. No tag or GitHub Release exists yet; both follow only after this PR's full CI and the master merge commit's CI pass.

What this brings together

Merging this PR marks #329, #342, #343, #344 and #346 merged.

Integration done here

The worktree and GitHub cores shipped without live routing. This PR wires them into the existing panel slot (no new dispatcher):

  • /worktrees: Enter stages cd -- '<path>' in an empty composer and never replaces a draft. n takes a branch name and plans a sibling worktree, created only on Enter at the reviewed plan. x previews non-forced removal, which is also refused while a live managed agent works inside that worktree.
  • /github, /prs, /issues: read-only over the user's gh, for a github.com origin. o opens only github.com URLs; w opens /worktrees on the PR branch (same-repository PRs only).
  • Ask knows both surfaces.

Fixes found during release checks

  • Ask config edits refused files under a symlinked home or project (macOS /tmp, /var); roots are now compared in resolved form. This was the cause of a cursor native-setup test failing on any Mac.
  • GitHub error text could show the client's IP address (rate-limit message); addresses are now redacted.
  • Worktree plans abbreviate home, and a new branch starts from the main worktree's full refs/heads/... ref, so a same-named tag can't win.

Docs and media

CHANGELOG [0.18.0] (features vs fixes, upgrade notes, known limitations), README, ROADMAP, architecture/design docs, version 0.18.0 in package and lockfile. New VHS captures from this build: Status Strip, worktrees, GitHub (docs/demos.md).

Verification

Fixes added after this PR was opened

Found during release checks and physical use; each has regression tests.

  • Absolute executable paths (/bin/zsh -c …, /usr/bin/env …) run in the shell instead of being refused as unknown NMSh commands. One text rule: a first word with a / after the leading one is a path. /zsh still switches shells.
  • /copy copies only the command's own output, never NMSh's completion row (✔ Completed · 21.6s · 15:42).
  • Folding after a screen clear. clear, tmux, or anything writing ESC[2J/ESC[3J emptied the stored lines but kept the earlier commands' records, whose line numbers the next commands reused. A fold row such as 15 lines shown · Ctrl+O then appeared inside another command's output, and Ctrl+O folded unrelated lines together with /copy rows. Records now go with their lines. Saved transcripts are repaired when opened: a record is kept only if its range fits, its header lines are command lines and its lines read as the output it recorded. The first record that fails is dropped together with everything older, never attributed to the wrong command.
  • Fish keystroke loss. Fish 4's line editor (its prompt and read) queries the terminal and drops keys that arrive while it waits for the answer. During read, those queries made a round trip through the session service, the frontend and the host terminal, and keys typed meanwhile were lost (8 of 8 under load). NMSh now answers fish's editor itself, as it already did at the prompt, and holds input until fish has redrawn its prompt. For any program, keys never overtake the answer to a device-attributes query. Ctrl+C discards held input, and bracketed pastes are never reordered (both found by a security review of the first version). Remaining limit: type-ahead typed while a fish read is still running can be dropped by fish itself, in any terminal (docs/development/fish-typeahead.md).
  • Physical QA checklist for this release: docs/testing/v018-physical-qa.md. It includes the Fish residual and a known issue: a lone ESC[3J (scrollback clear) empties NMSh's transcript. That behavior is pre-existing since the first release and is not a release blocker; the planned fix is to ignore a lone ESC[3J, after v0.18.

Dependent PRs (merge after this one)

Local verification of this head: npm run verify:release passed (2108/2108 tests, 8/8 timing budgets).

…rubber

The mods panel cycles tabs with the shared PanelShell helpers, so Tab has one
rule across panels. Agent and mod display text uses the shared terminal
control scrubber line by line, so unterminated, C1, DCS and APC sequences
leave no debris (the local scrubber kept ]0;... from an unterminated OSC).
The model comes only from the provider's structured events (system/init
model, assistant message.model; <synthetic> placeholders ignored). A failed
turn's reason comes from the assistant error code, an error_* subtype or
terminal_reason: Claude reported an authentication failure as is_error with
subtype "success", which read "Run failed: success". Interrupted turns
read Interrupted; a denied tool fails only that tool.
…e import

/claude, /claude new and /ai -> Claude open one launcher: targets grouped
under the launch profile that started them, a + New target per profile,
factual states and resumability. Selection never activates; Enter, N, /
and Esc are explicit; configured profiles are never bypassed for the
default identity. Up from an idle composer focuses an already visible
shelf (Left/Right select, Enter opens, Down/Esc return) with a non-color
focus marker. The agent view opens on an NMSh-native provider welcome and
keeps a compact header with the reported model; implementation names are
gone. Simple CLAUDE_CONFIG_DIR launch aliases are found by a text-only
match and imported only on explicit confirmation.
One porcelain -z list call plus bounded concurrent status probes. Hooks,
fsmonitor and transport are disabled per invocation; repositories with
content filters or includes fail closed to unknown status. Proven on an
armed fixture.
…ced removal

Removal is offered only for clean, unlocked, present linked worktrees and
aborts when branch, HEAD, directory identity or cleanliness change after
the preview.
Selection by identity, explicit refresh with a visibly stale snapshot, local
search, typed navigation/diff intents and fact-driven action availability.
Safe/ASCII, NO_COLOR and narrow layouts fit display cells.
…g status there

git -C follows the worktree's own .git pointer; a pointer leading to another
repository with content filters could otherwise run its filter.
Read-only foundation for #338: GitHub-compatible PR/issue search through the
authenticated gh CLI, lazy PR/issue detail, a bounded diff review model, merge
planning with exact head SHA (never execution), a keyboard-first controller, a
pure renderer for 30-200 columns with Safe glyph and NO_COLOR paths, and a
development-only interactive preview with offline fixtures.

Not wired into TerminalApp; no remote writes exist.
The launch command stays on screen after detach. A base-36 socket suffix
ending in e.g. "5d" matched the stray-'d' assertion (~2% of runs; CI run
37743508082, reproduced on master with the same suffix). Use a digit-only
suffix; the assertion is unchanged.
inspectFile resolved the target through symlinks but compared it with the
roots as given, so a home folder or project reached through a symlink
(/tmp and /var on macOS) refused its own files. The roots are now resolved
too; a symlink leading out of them is still refused. The cursor native-setup
test failed this way on any Mac whose temp directory is under /private.
…ersation

The managed agent view fills the screen below its frame instead of sitting
under the shell's welcome with an empty band. The header leads with the
target's state and gives way in a fixed order at narrow widths. User turns
carry a marker, agent prose is indented and word-wrapped, tools and outcomes
are quiet rows, and the composer shows the draft as edited with a caret.
Copy, routing, identity and drafts are unchanged.
Under NO_COLOR the active tab rested on bold alone. Use the same reverse-video
fallback as the selected row band; colored output is unchanged.
The shared tab strip, a search and provider row, grouped rows (kind, and
provider with launch profile) on the shared selected band, and a contextual
footer. Browsing shows name, factual state and scope; the selected entry's
execution facts stay visible below the list and wrap rather than truncate.
Enter opens the full record: execution first, then provenance. Nothing
implies an NMSh sandbox. Discovery, broker and controller are unchanged.
The worktree manager and GitHub workspace cores now run inside NMSh:
- /worktrees lists this repository's worktrees. Enter stages cd -- <path> in
  an empty composer (never runs it, never replaces a draft); n asks for a
  branch name and plans a sibling worktree, created only after Enter on the
  reviewed plan; x previews removal, which is also refused while a managed
  agent works inside that worktree.
- /github and /prs open pull requests, /issues opens issues, for the
  github.com origin remote, read-only through the person's gh. o opens only
  github.com URLs; w hands the PR branch to /worktrees (forks excluded).
- Keys map onto each controller's own keyboard model; the panels use the
  existing panel slot, frame and glyph/color settings.
A tag sharing the branch's name would otherwise win when Git resolves the
start point. The plan still shows the resolved commit before confirmation.
…ase build

Three VHS tapes in the existing pipeline (disposable demo home, 1440x960).
github.tape is the one networked recording: it reads this public repository
through the recorder's gh login, passed as GH_TOKEN in the environment and
never written to a file. The gallery gains a Context and repositories chapter
and their stills.
…) and reverse-video active tabs without color
…sation that meets the composer

Physical QA found no visible caret in the /claude draft and a fresh session's
welcome far above the composer.

- The draft uses the shell composer's layoutInput geometry and reports its
  caret; TerminalApp shows the terminal cursor there through the frame cursor
  and CursorPresenter, so /cursor shape, color and effects apply. No painted
  caret glyph. Up and Down move between draft rows at the drawn width. No caret
  while approvals, questions or transcript focus own the keys.
- The conversation is a stream that meets the composer: the compact welcome
  rests on the composer rule and gives way to turns; the state rides the rule.
  Scrollback reaches the first turn and a scrolled-back view does not move when
  rows arrive. Normal and Chat follow the transcript presentation setting;
  Composer Top is honoured.
- Tests for the caret geometry and ownership, both composer positions, Normal and
  Chat order, scroll anchoring, resume, Safe glyphs and NO_COLOR; #344's layout
  tests updated to the new contract. Before/after VHS captures.
…an NMSh command

/Volumes/tools/adb devices was parsed as an unknown slash command and refused, and
path input lost shell completion and suggestions. One text-only rule now decides:
when the first word has a / after the leading one it is a path. No NMSh command name
contains one, so /zsh is still the handoff while /bin/zsh runs. Unknown single-word
input (/stauts) remains an NMSh typo; a leading space still sends anything to the shell.
… the real composer

Types each line into a real NMSh frontend and waits for the shell's output and the
journal record: arguments, quoted arguments with spaces, a backslash-escaped path,
/usr/bin/env and /bin/echo. A directory used as a command gets the shell's own
behavior, never "Unknown NMSh command"; /help still opens NMSh's help.
Cherry-picked from 95bd7ff (feature/agent-workspace) for v0.18.0; the changelog line
is filed under 0.18.0 Fixed instead of Unreleased.

The completed-command record keeps the command's PTY output (output) apart from
NMSh's lifecycle row (lifecycleText), but serializeCopyPayload appended the row, so
/copy after an extraction included "✔ Completed · 21.6s · 15:42". The payload is
now the output field alone, the same source the block actions and the pager use;
the row stays recorded and displayed. Output that merely looks like a status line is
the command's and is kept. A command that printed nothing no longer replaces the
clipboard with the status row: /copy says so and leaves the clipboard alone.
A clear (clear, tmux, ESC[2J/3J) emptied the stored lines but left every
completed record with its old line ids. Commands after the clear reused
those ids, so a stale record drew its fold row inside another command's
output and Ctrl+O folded unrelated lines, /copy rows included.

Records now leave with their lines, fold toggles and the output filter
reset, and a running command that clears owns what it writes afterwards
(its /copy payload included). Restored transcripts drop records that
cannot be placed on their lines.
A bounds check alone kept stale records that happened to fit: a pre-clear
record could sit on NMSh notice lines or on another command's output. A
restored record now also needs command header lines (NMSh's own line for
an Ask block) and lines that read as its recorded output, compared without
trailing blanks. The first record that fails marks an unrecorded clear, so
it and every older record are dropped, never attributed to other lines.

A reattached running command whose journaled start predates the clear rule
is treated as having cleared: it owns the stored lines and older records go.
Fish 4's line editor (its prompt and its read builtin) queries the
terminal each time it starts and drops keys that arrive while it waits
for the device-attributes answer. At the prompt NMSh answered fish
itself; during read the queries went through the session service, the
frontend and the host terminal and back, tens of milliseconds per round,
and keys typed meanwhile reached fish first: type-ahead during a fish
read was lost 8 of 8 times under load.

ShellSession now answers fish's editor queries wherever the editor runs
(the foreground process is fish itself), and QueryOrder keeps any
program's input behind the answer to its device-attributes query, with
a bounded fallback for hosts that never answer. Native fish still drops
type-ahead already queued when its reader asks; that remaining limit is
documented with measurements in docs/development/fish-typeahead.md.
…head, ESC[3J

Lists what still needs a person at a real terminal, keeps the residual
Fish type-ahead limit on the list, and records the lone ESC[3J
behavior with its cause, impact and planned follow-up.
A security review of the Fish fix found two ways the hold could change
what reaches the program:
- interrupt() wrote Ctrl+C directly, so keys held behind a terminal
  answer were delivered after it and a cancelled line could still run.
  Held input is now discarded first, as a terminal's Ctrl+C discards
  pending input; Ctrl+D keeps its place in order.
- answer-like bytes inside a bracketed paste were moved before it and
  could end the hold. Paste spans are now opaque: never searched for the
  answer, never split or reordered.
…edrawn

Fish asks again as soon as it is answered and redraws its prompt. Keys
from the frontend could reach fish after that next question but before
the session service had read it from the PTY, and fish dropped them: in
the full suite under load, an answer typed after fish showed its read
prompt was lost. After NMSh answers fish's editor, input now waits for
the editor's prompt-drawn mark (OSC 133;B), answering further rounds on
the way, with the same bounded fallback.
The fish read test removed fish's temporary home while fish was still
writing its history on exit (ENOTEMPTY under full-suite load).
…ip is used

NMSh picks a Linux clipboard tool only when DISPLAY or WAYLAND_DISPLAY is
set; CI has neither, so the test timed out on Ubuntu before reaching its
stand-in. No real display or clipboard is involved.
Fake agent processes can still be writing into it when the suite ends;
on a busy Linux runner the removal raced them (ENOTEMPTY).
…are not its output

A /copy that finished after the next command started added its
confirmation inside that command's block, and the line became part of
the command's recorded output: the next /copy copied NMSh's own text
(found by CI on Linux). A finished command's output now leaves out the
lines NMSh added while it ran; they stay where they were shown. The
transcript repair accepts records saved either way.

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