Repository navigation
Release v0.20.0 (candidate): workspace, background tasks, native modules - #392
Draft
raiseCatError wants to merge 87 commits into
Draft
raiseCatError wants to merge 87 commits into
raiseCatError wants to merge 87 commits into
Conversation
…son transport Records the initialize metadata (model catalog, commands, agents), get_context_usage, mcp_status, set_model, apply_flag_settings (effort), set_permission_mode and the runtime facts that confirm each change, in a disposable workspace with customizations disabled and a budget cap. Account identity values are never printed.
… and bounded rows Agent replies, provider command output and GitHub bodies are Markdown from outside NMSh. This renders them as terminal rows that always fit their width: headings, nested and task lists, quotes, GFM tables that wrap cells (records when far too narrow), fenced code with a gutter and language-aware highlighting from the person's syntax roles, and rules. Links become OSC 8 hyperlinks only for http(s) targets and always show where they go; refused targets stay visible as text. Safe glyphs and NO_COLOR keep the structure with ASCII markers and backticks. The authored-only /help renderer is unchanged.
…keep parsing linear A security review of the untrusted-Markdown renderer found three problems, fixed here: - Link text could hide a different destination when the target's host was only a substring of the text (example.com -> le.com). Links are now judged on their whole text: no destination is revealed only when the text names exactly the target's host as a whole hostname and no other; internationalized (punycode) hosts are always shown. - Numeric entities decoded after the caller's scrubber, so ‮ and similar could reintroduce bidi overrides or invisible characters. Entities now refuse controls, format characters, separators and private use, and the renderer scrubs its own input instead of trusting callers. - Several recognizers backtracked quadratically on long lines (heading, fence, rule, hard and soft breaks, YAML keys, code spans, emphasis, link brackets, long-word breaking). They are linear scans or bounded searches now, with timing tests on pathological inputs.
The adapter now speaks the documented Agent SDK control requests over its existing stream-json channel, verified against Claude Code 2.1.295 by the control probe: - set_model, apply_flag_settings (session-only effortLevel), set_permission_mode, get_context_usage, mcp_status/toggle/reconnect and stop_task, each correlated by request id with a timeout, waiting for the initialize handshake. Bypassing every permission check is never sent; refusals are reported as the provider worded them. - The initialize response is kept: the model catalog with per-model effort levels, the provider's commands (built-in or not), agents, output styles and account plan. - Telemetry with provenance (provider, requested, observed, estimate): runtime facts from each turn's init, usage and cost, rate limits (shared only with targets of the same launch profile, labelled), compaction, retries, tasks, the TodoWrite plan and the files tools read and edited, with the provider's own patch hunks. - Structured tool results (patches, reads, command output, searches, plans, subagent reports), bounded, never keeping whole file contents. - Permission requests carry the provider's reason and its own rule offers; an accepted offer is returned verbatim, never composed by NMSh. - Streaming text (--include-partial-messages) is presentation only and is replaced by the completed message; a profile's effort is a launch flag.
…s are exact and session-only An MCP tool returning a result shaped like an edit could have appeared as a diff and a file activity. Structured results are now bound to the built-in tool that produced them. Permission rule offers are rebuilt from exactly the fields shown, limited to this session, allow-only, and never switch to a mode that bypasses checks.
In a managed Claude composer, /model and /effort open NMSh's own picker in the composer's place instead of going to Claude's prompt. Rows are what Claude published: its model catalog, and only the effort levels the running model supports (none for a model without levels, which the picker says). Up/Down choose, typing filters, Enter applies through set_model or apply_flag_settings, Esc backs out. /model sonnet and /effort high apply directly on an exact match; a leading space sends text verbatim. Nothing is claimed without evidence: a model change reads "requested" until Claude reports it; effort is shown as acknowledged, set at launch, read from the account's settings, or the model default (unreported) - never invented. The header shows model and effort with that provenance. A refusal keeps the picker open with the reason.
A FIFO or very large file at a project's .claude/settings.json could block or flood the effort lookup. The path is checked with lstat first.
…and, live reload /claude mods and /mods now show each account's Claude plugins as they are: - Type from the plugin's own manifest files, read as data (nothing is imported or run): Mod (hook modules), Plugin, Settings hook or Skill, with its components. - The effective state for this directory. The old inventory read projectEnabled, which is false for nearly every installed plugin, so everything showed disabled; project and local enabledPlugins overrides are now read and named instead. - Conflicts (also installed at another scope, other version), dependencies and options, in words; project installations of other directories are not shown. - What it does in an NMSh-managed session, per Claude's documented behavior for claude -p: a mod's hooks run, nothing it draws appears; skills, agents, commands, hooks, MCP and LSP load. "Loaded in" names targets whose own report lists it. Space proposes a change and Enter confirms it: claude plugin enable|disable at the plugin's installed scope and account (CLAUDE_CONFIG_DIR), --json. Only ids from the current listing are sent, since Claude's command writes an unknown id into settings. The listing is read again as evidence, then running managed sessions on that account reload plugins (reload_plugins) and the result is reported, errors included. Standalone skills and settings hooks are listed without a switch, with the reason. i inspects through claude plugin details and validate (static). No sandbox is implied.
…and the project From telemetry already collected (nothing read on the render path): context pressure with its source and age (Claude's own count with headroom to auto-compact and the largest categories, or an estimate from the last turn, never 0% when unreported), model, effort and permission mode with provenance, the plan when Claude emits one, running subagents and tasks, files read and edited with their line changes, compactions, usage limits only once close, and the session's estimated cost. It sits beside the conversation from 120 columns; /panel shows or hides it (72 columns at least), and /context asks Claude for its breakdown. Safe glyphs draw ASCII marks and bars; NO_COLOR keeps every distinction in words.
Mechanisms chosen for each change, what is operational versus a representation of provider facts versus unsupported, the research conclusions that shaped the panel, and the next steps (left sidebar, per-turn diffs, MCP status, prompt navigation).
… real Claude Runs the picker logic and the documented control requests against a logged-in Claude Code in safe mode (no plugins, hooks or mods run, no session persistence), and proves the account's settings.json is unchanged. Verified with 2.1.295: the catalog opens the picker, set_model reads as requested until the next turn reports the model, effort is acknowledged (never claimed as confirmed), and an unknown model is refused in Claude's own words.
…able configuration Installs a code-free probe plugin from a local marketplace into a disposable, separately logged-in Claude configuration, starts a managed session that loads it, then disables and re-enables it through NMSh's own path (listing, setPluginEnabled, reload_plugins) and checks Claude's own reports: plugin list, offered commands, load errors. Refuses any non-temp or account-looking directory and hashes the real accounts' settings before and after.
…on through any path The plugin probe now accepts only /tmp/nmsh-cc-probe, resolved through symlinks, and refuses a directory that equals, contains or lies inside ~/.claude, ~/.claude-account1, ~/.claude-account2 or an inherited CLAUDE_CONFIG_DIR. Checked before any claude command runs, since even auth status writes into a fresh directory.
…ounts The plugin probe's guard and settings hashes listed two personal account directories by name; it now discovers the default ~/.claude, every ~/.claude-* sibling and an inherited CLAUDE_CONFIG_DIR, so it refuses any real identity on any machine. The picker probe's usage line no longer points at a real account.
/copy confirmations, notices and completion rows split mid-word at narrow
widths ('5 l' / 'ines'). Lines NMSh writes itself now break after a space,
with continuation rows hanging under the text after a leading glyph; a word
wider than the row still breaks by character. Each continuation row's
indent covers only added padding, so a selection copies the line exactly.
Shell output keeps the terminal's column wrapping.
Live tests that read joined screen rows match these messages across a
word break.
…t differ without color
Controls are semantic ({id, label, state}); the renderer draws only what its caller says is available, explains a disabled one, folds non-essential controls into a count on narrow windows, and returns regions so the pointer reaches the same action as the key. States, styles (outline, filled, soft, plain) and geometry (square, rounded) are independent; marks carry every state, so NO_COLOR and Safe/ASCII lose nothing. The worktree confirmation uses it (removal is the destructive control).
…e project's own files and the install location Same pattern as the existing runtimes: the requested version from the project's files (.ruby-version/Gemfile, .php-version/composer.json, global.json, .swift-version/Package.swift, build.zig.zon/.zigversion, .dvmrc, plus .tool-versions and mise), the active version from the install path, never by running the tool. Minimum versions (global.json, swift-tools-version, minimum_zig_version) are floors and are not judged as mismatches. A binary inside the workspace is not inspected.
- Package.swift: swift-tools-version comes from the first line only, as SwiftPM reads it, and manifests larger than 4 KiB are no longer skipped. - build.zig.zon: read up to the standard 64 KiB metadata bound. - go.mod/go.work: directives stay on one line (CRLF and a trailing comment allowed); a match can no longer span line breaks. - .tool-versions: asdf's .NET plugin name (dotnet-core) is recognized. - Install layouts: RVM and chruby/ruby-install rubies report their version. The linear-time regression now asserts the parsed values and a 250 ms bound that the previous Gemfile pattern (about 0.8 s on the same input) fails. The hostile-workspace test plants php, dotnet, swift and zig bait, hostile Ruby, .NET, Swift, Zig, Deno and PHP metadata, and checks every runtime's active version stays unknown for workspace binaries.
… with its control
- The reason line is measured: the label yields first (down to half the
width), then the whole line is cut, so narrow windows never overflow.
- A reason is listed only for a disabled control that is drawn; a control
folded into "+N" is explained by describeActions, not an orphan line.
- The tightest layout keeps the label's first letter (">C<") instead of an
ellipsis that names nothing.
…re recommended from their project files The six modules named icon ids the core icon set did not have, so they showed their text label even with Nerd icons on. The code points match Starship's Nerd Font preset and Nerd Fonts' glyph names (provenance in icons.ts). Each module is now recommended when its own project files are present (Gemfile, composer.json, global.json, Package.swift, build.zig, deno.json and their version files); recommendations still enable nothing. A test keeps every first-party pack module on a core icon.
…tate, without running hg or jj - vcs.hg: the nearest .hg directory, found upward from the working directory (Mercurial-only repositories have no Git root, so the search is its own: it stops at home when it starts below it, and is bounded to 32 levels). .hg/branch (absent means `default`) and .hg/bookmarks.current; oversized or symlinked files are not names. hgrc and hooks are never read. - vcs.jj: marks a Jujutsu repository, whether Git is colocated, and whether this is the main workspace (.jj/repo is the store) or a secondary one (.jj/repo is a pointer file). jj's protobuf working-copy state and operation log are not parsed, so no change id or bookmark is guessed. - nmsh.vcs (now "Version control extras") gains hg and jj modules; they start hidden like other non-cloud pack modules, so nothing is demanded until a person chooses them, and are recommended when .hg or .jj exists.
…ent the six modules The planted-binary checks passed home as the workspace, and a workspace that contains home is deliberately not a workspace (it holds the user's own tools), so the refusal path was never reached; the planted binaries also had no install layout to report. They now use a home outside the workspace and a Homebrew/rbenv-shaped layout that would report 6.6.6 if the boundary did not hold, and assert the refusal note. The Context Modules guide lists the new capabilities, what each reads, and which declarations are floors.
…and resource model Shared groundwork for the sidebar (#331), detail panel (#332) and keymap (#335) under #330, as standalone modules; the running app is unchanged. - layout.ts: the column counterpart of ScreenPlan. Docked, compact, drawer and hidden modes by measured width; a docked region never squeezes the center below 60 cells (it becomes a drawer), drawers overlay the center without resizing the PTY, and passthrough/idle suppress both regions. - focus.ts: LEFT_SIDEBAR and RIGHT_PANEL extend #329's InputOwner rather than adding a dispatcher. Protected owners (passthrough, approval, choice, search, agent message, panels) are never intercepted; in the composer only opt-in bindings apply, only with an empty draft, never to typing. Closing a focused region returns focus to the composer; focus resumes after passthrough. - keymap.ts: key -> semantic action per context, with slash-command paths and help derived from the bindings. The defaults bind nothing in the composer. - resources.ts: stable ids; activatable/informational/unsupported with an activation intent required for the first; selected, focused, active, attention and stale kept apart; hostile text sanitized; trees bounded. docs/design/workspace.md records the integration contract for TerminalApp.ts, which waits for the workflow track's stabilization checkpoint (#359).
…nd manager sections #331, with the tmux navigation part of #333; stacked on the workspace foundation. Standalone controller and renderer: the running app is unchanged until the integration step. - SidebarController: selection by id across refreshes (a vanished one moves to the row in its place), tree expansion kept across refreshes, parent on Left, a `/` filter that shows matches with their ancestors. Moving only proposes a preview; activation returns an intent and only for an activatable resource; anything else says why. - renderSidebar: exactly rows x width; focus, selection, active, attention, exited and stale each have their own mark in Unicode and Safe glyphs, so NO_COLOR loses nothing; hostile names are inert. - tmux.ts: one bounded `list-panes -a` on the server NMSh runs in ($TMUX), fields separated by U+001F and every row validated; session -> window -> pane with NMSh's own pane marked active; navigation with tmux's own select-pane/select-window/switch-client on ids, never names, as argv. - sources.ts: worktrees (open = the /worktrees cd intent; missing or bare paths listed with a reason) and links to /github, /agents, /sessions. Tests include a real tmux server on a private socket: listing, selecting a window with the navigation argv, and a missing server reported as an error.
…own files and the SDK's metadata - Elixir: mix.exs's `elixir:` requirement (read as text, never evaluated), .tool-versions/mise; active from the install path (Homebrew, asdf, mise) or the release's lib/elixir/ebin/elixir.app. bin/elixir is never run. - Dart: pubspec.yaml environment.sdk (failsafe YAML: aliases bounded, tags and non-string values ignored); active from the SDK's own `version` file. Flutter's bin/dart wrapper reads the bundled bin/cache/dart-sdk/version, and that metadata wins over an FVM or cask install path, which names Flutter's version, not Dart's. - Flutter: .fvmrc (FVM 3) or .fvm/fvm_config.json, pubspec environment.flutter, .tool-versions; only for a package that depends on the Flutter SDK or pins one with FVM. Active from the install's bin/cache/flutter.version.json or `version`. An SDK linked inside the project (.fvm/flutter_sdk) is workspace-controlled and refused. - Install layouts: FVM (~/fvm/versions/<v>) and Homebrew casks. - Pack modules elixir, dart and flutter (show-on-command, Context Rail), with icons (Seti elixir/dart, Devicons flutter) and recommendations from mix.exs, pubspec.yaml and FVM files. Tests use sentinel executables that fail if run, a planted workspace SDK with version metadata that would be reported if the boundary did not hold, and hostile pubspec/mix.exs inputs (alias bomb, tags, whitespace runs).
…on and Make project.build reports the build system of the nearest directory with a build file (up to the repository root) and the version the project pins for it: - Gradle and Maven: the wrapper properties' distributionUrl (never fetched). - Bazel: .bazelversion, as Bazelisk reads it. - CMake and Meson: cmake_minimum_required / meson_version, matched as text and shown as a minimum (≥); a subproject without a declaration uses the nearest CMakeLists.txt that has one. - Make: presence only. No wrapper (gradlew, mvnw), Bazelisk or build file is run or evaluated. Versions are read only when demanded. The `build` pack module is show-on-command (gradle, mvn, cmake, meson, ninja, bazel, make...) and is recommended from the build files; one module instead of one per tool. Tests: wrapper sentinels that fail if run, subproject lookups, the case-insensitive Makefile spelling, control payloads in properties, symlinked metadata and a whitespace-run CMakeLists.txt.
…f closing what cannot be seen
…to integration/v020-modules
…ion/v020-modules # Conflicts: # docs/architecture/context-modules.md # tests/contextCapabilities.test.ts # tests/contextPacks.test.ts
…tegration/v020-modules
…to integration/v020-modules
…integration/v020-modules
…' into integration/v020-modules
…integration/v020-modules
…tegration/v020-modules
…tegration/v020-modules
… integration/v020-modules
Brings #370 -> #372 -> #373, #374, #369, #381, #379, #380, #375 -> #376/#377/#378 and #383 -> #384 onto the v0.19.0 release candidate (9e85a91). Ruby/PHP overlap with #368, resolved as recorded in #385: #370's runtimes.ts, nmsh.project.json and Ruby/PHP test (canonical, linear Gemfile parsing); module count 16 from #373. Kept the v0.19 regression that a Ruby declaration never spans lines, which #370's tests did not assert, and marked #370/#372/#373/#374 done in docs/design/native-module-coverage.md.
TranscriptPresenter: keeps v0.19's NO_COLOR stripping of restored NMSh rows and adds #349's word wrapping for NMSh metadata lines; program output still wraps by column with its original SGR. CHANGELOG: #349 was based on a pre-release v0.18 changelog; kept the release branch's file and added #349's one entry under Unreleased.
dimensions() is the workspace center whenever a region is open, so transcript wrapping, ScreenPlan, panels, hit testing and the PTY size follow the same plan; drawers never change it. Sidebar, panel and drawer cells are composed at the final paint (after effects and caret), with the Status Strip row at full width and erase-in-line removed from center rows. Keyboard ownership is derived from the frontend's state: startup, a program reading hidden or single-key input, passthrough, the agent view, panels and searches are protected and never intercepted. Workspace keys are routed after all of them; an opt-in keymap binding from an idle composer comes before the agent shelf and history. Pointer events are translated once: a click focuses a region, center events continue with center-relative columns. Activation goes through existing paths: a worktree stages its cd in an empty composer, managers open as their commands do, tmux navigates by id through tmux from fixed system locations. Adds /sidebar [focus], /panel [focus] and /keys [reload], with help and Ask coverage.
Found by dispatch CI 38608819740 on the merge-only head 207de26: #379's gate allowed one process use under src/agents/mods (claudeDiscovery.ts listing plugins), and #347 moved that listing into claudePlugins.ts and added details, validate and enable/disable at the installed scope. Neither PR's own CI could see the other. The gate stays strict: every loader check is unchanged, the only child_process import allowed is execFile, a shell is refused, and each argv given to Claude's CLI must be one of the four reviewed forms verbatim. None of them loads or runs mod code in NMSh. view.ts used inline type imports (import('./controller.js').X), which the loader check rightly cannot tell from a runtime import(); they are ordinary type imports now. No runtime change.
Each session owns its background tasks beside its shell, in the session service or in the in-process client: SessionTasks wraps the #383 supervisor, tasks keep running while a window is detached, a reattached window receives the list and each task's recent output (never a restart), and ending the session ends its tasks. A shell switch keeps them. Protocol: the background-tasks feature on both sides; task (start, stop, remove) from the frontend; tasks and task-output from the owner, only to a frontend that listed the feature and only after created/attached and the replay. Every request, record and update is validated on arrival. Task output travels separately from the shell's stream and never reaches its PTY; an update carries at most 1 MiB, newest lines first. A start carries the live shell's PATH; the owner supplies the session shell and its launch environment, which is used for the spawn only. TaskOutput gains receive() so a frontend mirror keeps the owner's line numbers under the same limits.
…ission hook The task bar and a waiting suggestion sit above the composer with the other notices. Up from an idle composer goes to an opt-in keymap binding, then a shown agent shelf, then the task bar, then history; Shift+Up enters the bar from an empty composer. The bar, the manager and the output view are protected owners for the workspace router. Only routeComposer's direct submission from an idle composer consults the detector; queued entries, program input, startup, history reruns, install retries and presets never do. Suggestions are now off by default, so a recognized command runs in the foreground exactly as before unless the person turns them on or sets its family to Always; /background run is the explicit path. Any key other than a choice returns the draft and keeps typing. A task's output is mirrored from its owner and shown only in the bar, the output view and /background; the transcript gets one NMSh line when a task starts and when it ends (failures by default), so copies, reports, comparisons and failure navigation never see task output. /background t3 opens a task's output; /bg is the short form.
/worktrees gets pullRequests from the PR list /github last loaded, only when the shell's github.com origin is the same repository; the manager still never fetches. openPullRequest closes the manager and opens that pull request's detail in /github.
0.20.0 section marked as an unreleased candidate built on the unpublished v0.19.0 candidate, with compatibility, upgrade notes and limitations. README, ROADMAP and AGENTS describe it as a release candidate; the latest published release pointers stay at v0.18.0.
Found by CI 38649080918/38649082204 and the local canonical run on 62c6fad: /keys derived the host profile with TERM_PROGRAM and the Ghostty profile inside TerminalApp, which the general frontend must never branch on (hostIntegration). keymapHostProfile(env) now lives beside hostNotes; TerminalApp only asks for it.
…ay arguments Security review of 1c41862. The gate accepted, as fixtures show, a shell given by path, a second namespace import of child_process, any executable, an argv not starting with 'plugin', args changed after its reviewed literal, a direct execFile call, createRequire, and a process helper imported from outside src/agents/mods. It now refuses each of them, and a self-test runs every one through it. Runtime: a plugin id from Claude's listing could start with '-' and reach the CLI in option position, and validate was given the listing's install path unchecked. Ids now start with a word character, and only an absolute install path is validated. No loader check is removed and executable Portable Mods stay disabled.
… it does Found by the local canonical run on 44f4c23 ("cancellation is acknowledged only when the turn completes as interrupted", 10.4 s) and reproduced deterministically: send() queues the text while the thread starts, and cancel() returned silently without a thread and turn id, so the turn then ran with no interrupt and no word to the person. The test waited 300 ms and usually won the race; under CPU contention it failed 2 of 2. The request now waits and is sent once, as soon as the turn/start answer or turn/started names the turn; it is forgotten when the turn settles, is refused or the session fails, and a cancel with nothing running is not kept for a later turn. Only Codex's own interrupted result still counts as a cancellation. The adapter remains unwired.
…s the hangup Found by the local canonical run on 44f4c23 (both leftover tests, 1.4 s and 10.3 s) and an earlier dev run. The tests assumed the background Node sleeper had installed its SIGHUP handler before the leader's `sleep 1` / `sleep 0.2` ended. Node alone took 0.3-0.5 s to start on this host under moderate load, and a member that has not ignored the hangup by then is ended by it (plain sleep 2 of 2, a late trap 3 of 4); the supervisor then correctly reports no leftovers. With an 800 ms slow start the old shape lost the member 1 time in 10, the new one 0 in 10. The sleeper now creates a file once the hangup is ignored and the leader waits for it (bounded at about ten seconds) before its unchanged exit. No assertion, deadline or supervisor code changes.
Owner
Author
|
Exact-head verification, Automated gates on this head: all pass.
What changed since
Failures, all preserved. On Still open:
|
This was referenced Oct 15, 2026
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release candidate — do not merge. Based on
release/v0.19.0(#390), which is itself an unpublished candidate: this PR shows only the v0.20 delta. Once v0.19.0 is published it retargets tomaster. No tag or release without maintainer physical QA and explicit approval.Integration (merge commits, history preserved)
9f8f1085integration/v020-modules8f079208(#370 → #372 → #373, #374, #369, #381, #379, #380, #375 → #376/#377/#378, #383 → #384). Ruby/PHP overlap with #368 resolved per #385: #370'sruntimes.ts, pack and test, module count 16; v0.19's "a Ruby declaration never spans lines" regression kept; coverage doc updated7377af4febdb1ad9(clean)207de26b66b60f3c:TranscriptPresenterkeeps v0.19's NO_COLOR restore and adds word wrap; CHANGELOG entry carried overfe60bdc4/sidebar,/panel,/keys1c418629960e925f5d547246/background, submission hook84730131/worktreesfrom the last/githublisting62c6fad0docs/testing/v020-physical-qa.md44f4c232/keyshost profile moved out of TerminalApp (hostIntegration defect found by CI)00163da71c418629: the gate now refuses eight demonstrated ways around it (shell by path, extra namespace import, any executable, non-pluginargv,argschanged after review, directexecFile,createRequire, a process helper imported from outside the folder), with a self-test; plugin ids and the validated path can no longer reach Claude's argv as options1861666ac71a4284Decisions for maintainer review
207de26b, run 38608819740) failedno code-loading path exists under src/agents/mods: Agent workspace: native /model and /effort, Claude mods, context panel #347'sclaudePlugins.tsruns Claude's ownplugin list|details|validate|enable|disable, andview.tsused inlineimport('…')type annotations. Resolution keeps every loader check, allows only anexecFileimport without a shell, and pins each argv to four reviewed forms verbatim;view.tsnow uses ordinary type imports. This changes a security test: please confirm. Security review (2026-10-15): acceptable with conditions. The static gate could not establish what it claimed (eight bypasses accepted, now refused in00163da7). It still cannot establish thatclaude plugin validate/detailsnever run plugin code, howclaude plugin listtreats an untrusted repository's settings, or live enable/disable behavior; conditions:00163da7, physical item 4.2 with a disposable account, and your sign-off.ask), so Enter ontail -f,npm run dev,pingkeeps running in the foreground as before. Background starts only via/background run, a family set to Always, or after/background suggestions on./background./modules(the canonical Module observability: explain visibility, provenance, collection timings and cache state #365 view)./keysnaming tmux prefix/bind -nkeys is not implemented (the design never promised it): defer and reword (recommended) or implement. 1.6: clicks focus a region, rows are not selected and the wheel scrolls the transcript (both deferred, both CHANGELOG known limitations); proposed wording added, nothing removed.Preserved boundaries
Workspace keys are routed only after every panel, program reader, startup and search handler (derived owner; protected owners never intercepted).
dimensions()is the center width, so wrapping, ScreenPlan, hit testing and PTY size share one plan; drawers never resize the shell; passthrough gets every column. The background hook runs only onrouteComposer's direct submission from an idle composer, never for queued entries, program input, startup or othersubmit()callers. Up precedence: keymap → agent shelf → task bar → history.Gates (head
c71a4284)workflow_dispatchCI 38723501877: every job success, attempt 1, including Fedora, both timing smokes and Node 22npm run verify:release(run 3, macOS arm64 8 GB, Node 26.10.0): 2612 tests, 2611 pass, 0 fail, 1 skipped (opt-in real Claude); bench typecheck and timing smoke pass; clean before/afterfe60bdc4..44f4c232): no issue in task ownership/protocol, suggestion hook, focus/routing, tmux, worktreecd, related-PR action or transcript isolation; the mods gate finding is fixed in00163da7docs/testing/v020-physical-qa.md): not performed. Runbook:docs/development/release-qa-runbook.mdondocs/release-train-v019-v020History (all preserved, none reclassified as flaky):
44f4c232: PR CI 38650280119 and dispatch 38650273295 passed. Local canonical run 1 failed 2/2609 (absolutePathLivefish ownership-probe timeout,codexAdaptercancellation); run 2 failed 5/2609 (backgroundSupervisor×2,codexAdapter,configuredCompletion×2). The earlier note "under heavy unrelated load" was not established: the suite alone drives load 10–70 on this host.codexAdapterwas a real defect (deterministic reproduction; fixed in1861666a);backgroundSupervisorwas a test race (the hangup ends a member that has not yet ignored it; fixed inc71a4284);configuredCompletion(also on62c6fad0),copyFolding(62c6fad0) andabsolutePathLive(run 1) are not reproduced and not explained; they stay open records.207de26b), hostIntegration defect (62c6fad0).Ledger:
docs/development/release-train-v019-v020.mdondocs/release-train-v019-v020.