| Name | Modified | Size | Downloads / Week |
|---|---|---|---|
| Parent folder | |||
| nanocoder v1.31.0 source code.tar.gz | 2026-09-26 | 3.7 MB | |
| nanocoder v1.31.0 source code.zip | 2026-09-26 | 4.3 MB | |
| README.md | 2026-09-26 | 111.4 kB | |
| Totals: 3 Items | 8.1 MB | 1 | |
What's Changed
- Added a first-class provider template for Cheaper Inference, an OpenAI-compatible gateway, to the
/settings providerswizard. Selecting it fills in the base URL (https://api.cheaperinference.com/v1) so only an API key and a model name are needed, and the wizard can fetch the account's model list over the standard/modelsendpoint. - Added a
showAgentBashOutputpreference (/settings→ Behavior → Tool Results and Thinking). By default a completed card for a command the agent runs shows the command and its status, and the command output is not kept; with compact tool display on, the card collapses into a tally line. This preference shows the output on that card, whether compact tool display is on or off, and for failed commands too. Commands you type yourself (!command) always show their output and are unaffected. - Publish a JSON Schema for
agents.config.jsonasschemas/agents.config.schema.json. It is generated deterministically from the on-diskDiskConfigtype (pnpm run generate:schema), ships with an Ajv validation suite plus a CI drift check, and enables editor autocompletion — either by dropping the$schemakey into your config or by wiring up the schema viajsonValidation/ a JSON Schema mapping in your editor. - Added automatic session titles. A session keeps its opening prompt as the title, and when that prompt is too thin to be useful the agent generates a descriptive name once, after the first turn that ran a tool or the first follow-up message. Manual renames are never overwritten.
Titling runs in the ACP agent, so it applies to the VS Code extension and other ACP clients; the CLI keeps its heuristic title.
It uses the session's own model by default - set sessions.titleModel / sessions.titleProvider to point it at a cheaper or local one, or sessions.smartTitles: false to turn it off.
Pointing titleProvider at a different provider sends it the opening user turns and a summary of the tools that ran, which includes file paths and bash command strings.
Two things worth knowing:
the tokens a title costs are billed by the provider but are not counted in /usage, since the call is made outside the conversation loop that builds usage records;
and existing sessions are retitled from their first message on their next autosave, which is a one-time visible reshuffle of the history list.
Also fixed the CLI's autosave deriving the session title from the latest user message and rewriting it on every save, which overwrote titles in the store the VS Code extension reads from. Closes [#808].
- Added Anthropic prompt caching. The system prompt, tool schemas, and conversation history are now marked with cache breakpoints, so multi-turn sessions read the stable prefix back out of cache instead of paying full price for it every turn. Cost reporting is cache-aware throughout:
/usageand the per-response indicator price cache reads and writes at their own rates instead of billing every cache hit at the full input rate, and the per-response indicator surfaces the cached token count alongside the total. Opt out with"promptCaching": falseon the provider config. Closes [#888]. - Fix multi-line paste submitting the prompt partway through.
Nanocoder never enabled bracketed paste, so the terminal delivered a paste as bare bytes and the carriage return at each line break reached Ink's keypress parser as Enter. Pasting two lines sent the first line to the model and left the second in the input box. Paste handling relied entirely on heuristics (input rate, size, line count) that only ever saw text which had already made it into the buffer.
DECSET 2004 is now enabled in both screen modes. Paste payloads are lifted off stdin before Ink sees them and delivered to the input as a single event, so a pasted newline can no longer submit. The old heuristics remain as a fallback for terminals without bracketed paste support.
Fullscreen mode enables mouse reporting for wheel scrolling, which takes click-drag text selection away from the terminal. Selection there is Shift+drag (Option+drag in iTerm2), or turn mouse reporting off for the session with --no-mouse.
- Add JSON exports for
/export --jsonand.jsonfilenames, preserving full message content and session metadata for replay and evaluation workflows. Closes [#1309]. - Grouped each VS Code chat turn's thoughts, tool calls, edit cards, and task plan into one ordered, collapsible work summary while leaving the final answer visible. The summary reports completed, stopped, or failed duration and reopens for pending approvals. Thanks to @Gambit-Checkmate. Closes [#858].
- Added support for a .nanocoderignore file. Patterns in it keep tracked-but-noisy files (lockfiles, generated fixtures) out of directory listings, file search and the file explorer, so they stop eating context even though .gitignore doesn't cover them. It is a context-hygiene tool rather than a secrets boundary: read_file and execute_bash don't consult it, and checkpoints deliberately skip it so hidden files are still snapshotted and restored. Thanks to @A-S-Manoj. Closes [#755].
- Add configurable agent-loop retry limits to prevent token drain (#897). A new
nanocoder.retriessection inagents.config.jsonexposes the previously hardcoded caps:maxRepeatedToolCalls(default 3),maxEmptyTurns(default 2), andmaxMalformedRetries(default 2). When the repeated-tool-call limit is hit in an interactive session, Nanocoder now pauses and asks whether to continue (granting another window of attempts) or stop, instead of always hard-stopping; non-interactive runs keep the hard stop. The same limits now also protect the--plainruntime used bynanocoder runin CI and non-TTY environments, which previously had no repeated-call cap at all: each cap hard-stops with a clear error there. Note this also loosens--plainin two places: it used to return an error on the first empty response and on the first malformed tool call, and it now nudges or asks the model to self-correct up tomaxEmptyTurns/maxMalformedRetriesbefore stopping, so a silent or malformed-output model costs up to 3 model calls instead of 1. Set either limit to0to restore the old fail-fast behaviour. Calls to unknown tools count toward the repeated-call streak in both runtimes, so a model stuck on a nonexistent tool trips the same cap instead of looping until the turn ceiling. Delegated subagent runs, whose loop previously had no cap at all, now applymaxRepeatedToolCallstoo and stop with an error naming the setting.
One change reaches further than the retry limits themselves: a tool call naming a tool that does not exist is now kept in the assistant message's tool_calls rather than dropped from it. Without this the paired Unknown tool: X result was orphaned and pruned before the request went out, so the self-correction hint never reached the model and it re-emitted the same nonexistent call. This applies to all three runtimes that partition tool calls, including the ACP loop (--acp, used by editor clients), which is otherwise unaffected by the retry limits. The practical consequence is that providers now receive a tool call naming a tool that was not in the request's tool list.
- Add
nanocoder configto see your settings and which file each one came from.
Settings come from four places: built-in defaults, your global config folder, the project folder you're in, and NANOCODER_* environment variables. When a setting didn't do what you expected, finding out which file set it meant opening all four by hand.
Three commands:
nanocoder config list— every setting, its value, and the file it came fromnanocoder config show <key>— one setting in detail: its default, and any values it beatnanocoder config diff— only what your files change, plus values that are set but unused
All three take --json.
The part worth knowing. Settings are grouped into blocks, like autoCompact. Nanocoder takes the whole block from the first file that mentions it — it does not mix fields from two files. So if your global config sets threshold and notifyUser, and your project config sets only threshold, your notifyUser is thrown away and the built-in default is used instead. Nothing told you that before. config diff now lists it under "Ignored values".
Two smaller things. Values are shown the way the app really uses them — write threshold: 200 and you'll see 95, because it's capped at 95. And API keys show as <redacted>, so you can paste the output into a bug report.
- Auto-generate descriptive filenames for /export instead of generic timestamps. Closes [#934]
Exports are now contained to the project directory, matching read_file / write_file / string_replace: ~ is not expanded and absolute paths outside the project root are refused rather than written. Rejections name the specific cause (null byte, ~, .. segment, outside the root) instead of failing generically.
- Added bundled React, Next.js, and Rust project presets for
nanocoder init --preset <type>and/init --preset <type>. Presets seed analyzedAGENTS.mdguidance, stack-specific context ignores, and a/checkcommand skill while preserving existing files. Closes [#1008]. - Make the fullscreen TUI the default for interactive sessions.
Interactive sessions now run on the terminal's alternate screen buffer, the way vim, less and htop do. The chat transcript is a bottom-anchored viewport that clips at the top instead of spilling into scrollback, so the prompt can never be pushed off-screen by a long turn, and the frame is repainted cleanly on resize. Scroll the transcript with PgUp / PgDn or the mouse wheel.
Inline mode is still available for anyone who wants finished messages to land in the terminal's own scrollback, where the terminal's scrollbar and search work: pass --no-alt-screen, or set alternateScreen: false in preferences. There is also an "Alternate Screen" toggle under /settings. Run mode (nanocoder run ...), non-TTY and CI environments are unaffected - they print a transcript you need to keep after exit, so they never enter the alternate screen.
- toggle todo-list visibility using ctrl-t
- Add Architect mode with an approval workflow for reviewing changes before execution.
- Auto-compact now runs on
--plain, ACP, and subagent loops, not just the TUI. Subagents also honoursessions.maxMessages. Thanks to @Dhirenderchoudhary. Closes [#1048]. - Add
matchPathsto lifecycle hooks, so a hook can be scoped to the file a tool acted on and not just the tool name. This is what lets one formatter per language be declared directly —{"matchTools": ["write_file", "string_replace"], "matchPaths": ["**/*.{ts,tsx}"], "command": "npx prettier --write \"$NANOCODER_FILE\""}— instead of dispatching on the extension inside the command with acasestatement, which was the only option before and is not portable to Windows. It applies to any file-scoped hook, not just formatting: "only lintsrc/**", "audit-log writes underinfra/". Patterns use the same glob dialect as skill subscriptions (**,*,?,{a,b}), and the file is whichever ofpath,file_pathorfilePaththe tool was called with.
Two deliberate behaviours: a hook scoped by path does not fire for a tool that touched no file (execute_bash), since matchPaths asks a question about a file and excluding is the right answer rather than waving it through — the opposite of an omitted matchTools, which widens to every tool; and an absolute path is also matched relative to the project root, so a root-anchored pattern like src/** fires whether the model wrote src/a.ts or the absolute form for the same edit.
- Added discoverable usage tips to the welcome screen and a
/tipcommand for showing another tip on demand./tip <text>narrows the pick to tips mentioning that text, and consecutive runs will not repeat the tip they just showed. Thanks to @OllieinCanada. Closes [#929]. - Added an
lsp_format_documenttool that formats a file through the connected language server (honouring.editorconfigindent settings) and writes the result to disk. Thanks to @Dhirenderchoudhary. Closes [#1171]. - Added MCP resources and prompts support, closing the gap between the MCP client and
docs/battlemap.md's claim of parity with Claude Code.MCPClientnow discovers a connected server's resources and prompts alongside its tools (best-effort — a server that doesn't declare either capability just contributes none, same as before). Resources are usable the same way local files already are: type@to fuzzy-search filenames and connected servers' resources together, and select one to inline its content. Prompts are usable the same way custom commands already are: type/mcp:<server>:<prompt>to fetch the prompt fresh from its server and send it as the next turn, with positional arguments filled in against the prompt's declared parameter order. Refs [#1162]. /model-databasenow does something useful with Enter: it copies the highlighted model's ID to the clipboard, and, when the active provider is OpenRouter, switches the session to that model immediately (same behavior as/model, including its own confirmation toast). The footer hint reflects which of the two Enter will do. Closes [#1310].- Scroll the chat transcript with the mouse wheel, and stop the wheel cycling prompt history.
The alternate screen has no native scrollback, so the terminal's own wheel and scrollbar cannot work there - the app has to receive wheel events itself. Nanocoder now enables SGR mouse reporting in fullscreen mode and scrolls the transcript three rows per tick. Text selection in that mode is Shift+drag (Option+drag in iTerm2).
Prefer native double-click and drag selection? Pass --no-mouse, set mouseReporting: false in preferences, or use the "Mouse Wheel Reporting" toggle under /settings. The wheel then no longer scrolls the transcript.
This also fixes a bug in that opted-out path. Terminals enable alternate scroll mode (DECSET 1007) by default: while the alt screen is active and the app is not reporting mouse events, they translate wheel ticks into cursor up/down key sequences. Those are indistinguishable from real arrow keys, so scrolling the wheel cycled through prompt history instead of the transcript. Nanocoder now turns that mode off for the lifetime of the session and restores it on exit.
- Added a terminal bell option to notifications. Every notifier Nanocoder had was desktop-only —
terminal-notifier,osascript,notify-send, the PowerShell balloon — so anyone driving Nanocoder over SSH, inside tmux, or from a remote container got nothing at all when a long run finished.notifications.bellnow also writes a BEL character to stdout for whichever events you have enabled, which the terminal in front of you renders as a beep or a visual flash. Toggle it under/settings→ Input → Notifications, next to Sound. BEL is non-printing, so it does not disturb the rendered frame, and it is skipped when stdout is not a TTY so piped output and daemon logs stay clean. Also corrected the notification docs, which described the preference as living under ananocoder.notificationsnamespace — it is read from the top-levelnotificationskey, so anything written at the documented path was silently ignored. Closes [#931]. - Optional OS sandbox for
execute_bash/!(nanocoder.sandbox, default off). macOS uses sandbox-exec, Linux uses bubblewrap or errors instead of falling through, Windows is unsupported. Thanks to @Dhirenderchoudhary. Closes [#1049]. - Added an action timeline in the VS Code sidebar so you can click a prior mutating tool step and revert the workspace files and conversation back to that point.
- Added a
professionalTonepreference (/settings→ Behavior → Professional Tone). When on, the end-of-turn note drops its random adjective ("Completed in 12s." instead of "Worked for a plucky 12s.") and the system prompt gains a TONE section instructing the model to stay terse and strictly functional — no filler, no preamble, no celebratory wrap-ups. nanocoder run --prompt-file <path>reads the prompt from a file instead of the command line. Linux caps a single argv entry atMAX_ARG_STRLEN— 32 pages, 131072 bytes — independently of the much largerARG_MAXtotal, andexecvefails withE2BIGbefore the process starts. macOS has no equivalent per-argument cap.
That combination is a trap for any caller that assembles a prompt rather than
typing one: it works in local testing on a Mac and then cannot spawn at all
on a Linux CI runner. Sentinel embeds the files it audits into the prompt and
hit exactly this — every pack returned spawnSync nanocoder E2BIG on
ubuntu-latest at 314 KiB and 218 KiB, having passed on macOS throughout.
A file has no such ceiling. The flag takes precedence over a positional prompt, and a missing or unreadable path exits with a message rather than running against an empty prompt.
- The CLI parsing test mirror is back in step with the parser.
cli.spec.tsduplicates the argv filter by hand, and had fallen six flags behind it:--mode,--json,--output-format,--trust-directoryand the alt-screen pair were all stripped by the real parser and left in the prompt by the mirror, so those tests were passing against a parser that is not shipped. A test now asserts the two agree. - Add automatic collapsing for long user messages in the conversation view. Messages exceeding 40 words or 300 characters are collapsed for display, while the original prompt remains intact and can be recalled from input history.
- Added lifecycle hooks: shell commands configured under
nanocoder.hooksinagents.config.jsonthat run at fixed points in the agent loop —session-start,session-end,user-prompt-submit,pre-tool-use,post-tool-use, andpre-compact. Hooks cost no tokens and fire every time, so rules the model used to be merely asked to remember ("format after every edit", "never touch .env") are now enforced. Apre-tool-useoruser-prompt-submithook that exits non-zero denies the action and its stdout goes back to the model as the reason; the tool gate runs ahead of the approval decision, so a vetoed tool never renders a confirmation prompt. A hook that hangs past its timeout is killed along with its whole process tree and skipped, rather than wedging the session.post-tool-useoutput is folded into the tool result — including when the tool failed, so an audit hook sees the errors too — andsession-startoutput is injected as context on the next chat prompt, leaving slash commands and!bash passthroughs untouched. Hooks run from the project root so a relative command survives acd, andsession-enddefaults to a shorter timeout that fits inside the shutdown budget. The tool hooks apply on every surface that runs tools — the interactive TUI (including streamedexecute_bash),run,--plain, ACP, and subagents — the session and prompt hooks cover the TUI andrun/--plain, and/doctorlists what a project has wired up. Closes [#1032]. - Added
/repomap, a local codebase map. Nanocoder indexes the symbols defined in each source file, links files that reference each other's symbols into a directed graph, and ranks that graph with PageRank - so the map leads with the files the rest of the codebase leans on most. Everything runs on your machine with no LLM round-trip, covering TypeScript/JavaScript, Python, Go, Rust, Java/Kotlin/C#/Swift, Ruby, PHP, and C/C++. The map is budgeted to 1024 tokens by default;/repomap --tokens <n>widens it. Thanks to @akramcodez. Refs [#890]. - Retired the legacy
.nanocoder/tasks.jsonfile in the working directory. Task state is now session-scoped and stored with the session's other artifacts under the app data directory, so nanocoder no longer writes agent bookkeeping into your repository and two concurrent sessions no longer share one task list. Resuming a session restores its tasks. Any leftover.nanocoder/tasks.jsonis ignored and can be deleted. - Add /review slash command and
nanocoder reviewCLI subcommand for AI-powered code review of branch diffs and PRs - File search (path matching and content search) is now backed by
ripgrepinstead of a hand-rolled JS walker.
Search also respects .nanocoderignore and binary files again, matching list_directory and file autocomplete.
A failed search now reports the failure instead of returning an empty result set.
.nanocoderignore directories are skipped during the walk rather than filtered afterwards, so a large ignored directory can no longer crowd real files out of the results.
- Give every interactive surface the same rounded, titled chrome.
The prompt box now has rounded borders, and the surfaces that sit above it were inconsistent: some drew a bare title over unboxed content, others had no frame at all. The session selector (/resume, /history), the IDE selector (/ide), the file explorer (/explorer), the plan review bar, the question prompt and the tool confirmation prompt are now all enclosed in the same rounded titled box, at the same width, honouring the title shape you picked in /settings.
The transcript and the development mode indicator also line up with the prompt's left border instead of sitting a column off it, so the whole frame shares one vertical edge.
- Extended prompt scrubbing to tool results. Privacy scrubbing ran on the system prompt and on user/assistant message content, but tool-role messages were passed through verbatim — the largest and least-reviewed body of text leaving the machine. A
cat .env, agit diffcarrying a key, or a stack trace with a token went to the provider in the clear even with scrubbing on. Three channels are now covered: tool resultcontent, thestructuredContentproduced by tools likelsp_get_diagnosticsandwrite_tasks(MCP results travel as plain strings oncontent, already covered), and the arguments of assistanttool_calls, which are rehydrated to their real values before entering history and were therefore replayed unscrubbed on every later turn. Structured payloads and tool-call arguments are scrubbed leaf by leaf rather than as serialised JSON, so a replacement can never span a quote boundary and corrupt the document. Tool output is truncated toMAX_TOOL_RESULT_CHARSbefore the detectors run, and the truncation boundary is placeholder-aware, so a cut — including the converter's own re-truncation of text that grew past the cap once placeholders replaced what they redact — can never land mid-placeholder. Paths and URLs stay in the clear, as on the existing scrub paths — the agent has to be able to act on the files and endpoints a result names. Closes [#1161]. - Added a dedicated Semantic Memory toggle to
/settingsunder Advanced, backed by thesemanticMemoryEnabledpreference. The setting defaults on to preserve existing behavior, and can be turned off to keep agents from persisting reusable context across sessions. - The MCP setup wizard (
/settings mcp) now offers a Serply template alongside Brave Search, DuckDuckGo and You.com. It builds a remote HTTP MCP config pointing athttps://api.serply.io/mcp, giving the agent Google, Bing, News, Scholar, Maps, Jobs and Amazon search plus URL scraping. The API key is required and is sent as anX-API-Keyheader.
Editing a wizard-built server whose key is stored in an X-API-Key header now re-opens the form with that key prefilled. Previously the edit flow only recovered keys from env vars or a bearer Authorization header, so the required key field came back empty.
- Added a session-scoped artifact lifecycle to the CLI and VS Code: implementation plans with explicit review and prose-plan fallback persistence, persistent task tracking, completion walkthroughs after an approved plan, clickable artifact shortcuts that survive session resume, and reliable cancellation recovery. Task lists now live with the session instead of
.nanocoder/tasks.jsonin your project, so/clearstarts a fresh list while the previous session keeps its record. Plan approval always works, falling back to the plan in the transcript when no artifact was written. Headless--plainruns keep their artifacts ephemeral and are not forced to produce a walkthrough. Subagents can no longer reach the plan, task, or walkthrough tools, and their declaredtools:allow-list is now enforced when a tool runs rather than only when the tool set is offered. Thanks to @2409324124. Closes [#805]. - Added
nanocoder completion <bash|zsh|fish>, which prints a self-contained tab-completion script for the requested shell so subcommands, flags, and known flag values (like--mode normal|auto-accept|yolo|plan) complete at the shell prompt. The shell argument is required, so a missing or unknown shell fails with usage instead of silently installing the wrong script. Scripts are rendered from a single spec insource/cli-completions/spec.tsshared by all three shells, and the command runs on the startup fast path so it costs nothing. Install viaeval "$(nanocoder completion zsh)"or by piping to your shell's completion directory — see docs/features/shell-completions.md. Closes [#1003]. - Added
nanocoder skills add, an install path for skill bundles. Point it at an index name,owner/repo, any git URL, or a local checkout and it shallow-clones the bundle into a temp dir, strips.git, refuses any bundle containing a symlink, and validates it with the same linter/skills checkruns — all before anything is written into the project. Because installing a skill means running its code, the trust prompt names every tool with its declared approval policy (neverreally does mean "runs without asking") and every event subscription the daemon would fire unattended. Accepting lands the bundle through the same copy/skills promoteuses, so an existing skill is never overwritten without--force. Bare names resolve through a plainskills.jsonindex hosted in a git repo — no registry service — overridable withNANOCODER_SKILLS_INDEXor--index. Refs [#1163]. - Add
/statslifetime usage command with 7d / 3m / all-time ranges (←/→ to switch), cumulative token chart, peak day, streak, and top provider·model pairs. Thanks to @puri-adityakumar. Closes [#933]. - Added
/tasks list,/tasks done,/tasks complete, and/tasks startsubcommands for viewing and updating task status, with validation for unknown subcommands and task numbers. - Syntax highlighting now follows your theme, and takes a
syntaxThemepreference when you want code to keep a palette of its own. All five highlighting call sites - markdown code blocks,string_replacediff context, thewrite_filepreview, and the file explorer preview - passedtheme: 'default', a string wherecli-highlightexpects a token-to-formatter map, so the option was silently dropped and every theme rendered code identically in the library's own colours. Each one now derives its token map from a theme's palette: keywords takeprimary, built-ins and declarationstool, stringssuccess, numberswarning, commentssecondary, attributes and variablesinfo, and everything else the theme's bodytext. Code followsselectedThemeby default; settingsyntaxThemeinnanocoder-preferences.json(e.g."syntaxTheme": "dracula") points code at any other theme's palette while the rest of the UI stays put, and an unknown name falls back toselectedThemerather than dropping the styling. Closes [#935]. - Render optional description/intent field in tool approval preview cards. Tools that modify files or execute shell commands (
execute_bash,string_replace,write_file,diff_edit, andfile_op) now accept an optionaldescriptionparameter in their schemas, which renders above the command or path in the approval card to make the model's intent clear before execution. - Added created and modified files to the VS Code extension's context panel: every file the agent writes during a turn now appears as a chip above the composer as soon as the edit lands, and clicking it opens the current version in the editor for review. The chips are dashed to set them apart from files you attached yourself, survive sending a message, and can be dismissed individually or, for a turn that touched many files, all at once with a "Clear N changed files" control; the row scrolls at a fixed height rather than growing the composer. They are deliberately not inlined into the next prompt, since the agent just wrote them. The row follows the rest of the file lifecycle too - a deleted file loses its chip, and a rename moves the chip to the new path. Two tools also report themselves more accurately over ACP as a result:
diff_editis now an edit, so edits from the nano tool profile get the same file card and chip thatwrite_fileandstring_replacealready did, andfile_opreports the kind its operation actually performs (delete, move, or an edit for a copy) instead of a generic tool call. Closes [#857]. - Added a "Retry" button to AI responses in the VS Code chat panel. Reorganised the assistant turn footer so the action buttons (Retry, Copy) sit together with the timestamp, and resubmitting a retried prompt no longer creates a duplicate user message bubble.
Behind the scenes, the webview now calls a new retryTurn extMethod on the ACP agent. The method truncates the session message history at the matching user turn and drops any action-timeline checkpoints captured inside that turn, so reverting a checkpoint can no longer rewind past the retried turn. The pendingUserMessageText lifecycle was tightened so user bubbles reconstruct correctly when resuming or switching between history sessions.
- Added a
nanocoder.showTokenUsageVS Code setting that defaults off and hides token usage plus estimated cost footers unless users opt in. Closes [#1096]. - Redesigned the welcome screen: a gradient block wordmark (full NANOCODER from 90 columns, a compact NC monogram below that), version line, centered quick-action menu, and a branch/directory location line — all centered on one axis at every terminal size, with vertical centering on tall terminals. The prompt input now spans the full terminal width and resizes live with the window, and the boot summary no longer duplicates information the welcome screen already shows. The subtitle under "Welcome to Nanocoder" is the project's own description rather than the old "local-first coding agent" line, wrapped and centered to fit the terminal. Closes [#949].
- The MCP setup wizard (
/settings mcp) now offers a You.com template alongside Brave Search and DuckDuckGo. It builds a remote HTTP MCP config pointing athttps://api.you.com/mcp, giving the agent web search, URL reading, and research tools. The API key prompt is optional: pasting aYDC_API_KEYbuilds an authenticated config with a bearer header, while leaving it empty falls back to the keyless free profile (https://api.you.com/mcp?profile=free) so search works with zero signup.
Wizard-built configs now record which template created them (templateId), so editing a custom-named instance (e.g. you-paid) re-opens the original template's form instead of the generic custom one — previously the saved bearer token was silently dropped on re-save.
- Fixed
ask_userrejecting 5-6 options over ACP. The tool schema allows 2-6 options, but the ACP path used by the VS Code extension capped them at 4, so an identical prompt succeeded in the CLI and failed in the editor. The bound now matches the schema, and the error string returned to the model says "2-6" rather than telling it the limit is 4 and pushing it to retry with a needlessly narrowed list. Closes [#1036]. - Fixed sub-agent tool calls being denied silently under ACP. A tool call made inside a dispatched sub-agent went through the global approval slot, which no ACP code path installed a handler for, so its safe fallback denied every one without the client seeing a
session/request_permission. Delegated work could only write by bypassing approval entirely, which was invisible to a client that gates writes. Sub-agent calls now use the same permission channel as top-level ones, announced first so the request names a known tool call, prefixed so their cards cannot collide with a top-level id, and titled with the sub-agent so the client can tell them apart. The handler is scoped to the turn that installs it, so it no longer outlives that turn holding a finished session. Two known limits remain. The approval slot is still a process-wide singleton, so while two sessions have turns in flight at once the later one's handler answers the earlier one's approvals, against the wrong session id and abort controller; closing that needs the slot keyed by session or an approval channel threaded through the sub-agent executor. And an approved sub-agent call is markedcompletedas soon as it is approved rather than when it runs, because the sub-agent layer does not report results back, so a client seescompletedfor a tool that may still fail. Note also that sub-agent approvals ignore the ACP session's mode and the configuredalwaysAllowlist, so ayoloorauto-acceptsession is prompted inside a sub-agent for a tool it would not be prompted for at top level. Closes [#1019]. - Allow projects located at a filesystem root, including
/and Windows drive roots, to pass path-containment checks. File tools andsearch_file_contentsno longer reject every path withescapes project directorywhen the workspace is mounted at the root, as it is in many containers. Closes [#1240]. - Made the three "ask the user" queues abort-aware, so a cancelled turn no longer strands its callers. The queue only advances when a human answers, so anything parked on an approval, confirmation or question when its turn died waited there forever, and the prompt for that dead turn stayed on screen for the user to answer on its behalf.
tool-executorstarts a batch of subagents and joins them withPromise.allSettled, so a single stranded subagent kept the whole turn open - [#1156] fixed the overwriting resolver but noted this await was still not abort-aware, and it was the remaining half.
signal() now takes the turn's AbortSignal, passed by the subagent executor, the conversation loop's tool confirmation, and the repeated-tool-call question. An aborted request leaves the queue and settles with the same safe default each slot already used when no handler is installed at all: both approval slots deny, so cancelling a turn can never be a route to approving a tool nobody was shown. Removing the head advances the display to the next request; removing one from the middle leaves the screen alone and closes the gap. A request that was already answered is untouched by a later abort, and a signal that is already aborted never puts a prompt on screen in the first place.
- Fix six defects in architect mode's review gate. Revert now deletes files the turn created instead of leaving them on disk; Escape keeps rather than silently reverting, and the footer says so; Revert & Revise sends the instructions you typed instead of a fixed string; the composer is hidden while the gate is open, so keystrokes no longer reach two components and the gate cannot be bypassed; the checkpoint now covers every file-mutating tool architect auto-executes, including
lsp_format_documentand custom file tools; and each turn's checkpoint is released once the gate resolves. Reverting also tells the model its changes are gone, so the next turn does not edit against a stale view of disk. Adds architect to--helpand shell completions, and documents the mode. - Fixed Backspace deleting the character after the cursor on virtually every terminal. Ink parses both the physical Backspace (
\x7f, sent by macOS Terminal, iTerm2 and essentially all Linux terminals) and the forward Delete key (\x1b[3~) askey.delete, so Backspace was routed to a forward delete (a no-op at end of line). TextInput now disambiguates at the raw-sequence level so Backspace always deletes backward.
Fixed undo/redo reading stale stacks when an edit and an undo/redo arrive in the same stdin batch. pushToUndoStack now keeps the undo/redo stack refs in lockstep synchronously (not just after a render), and redo() now caps the undo stack it pushes back onto, matching undo().
- Fix fetch_url truncation warning to display the content limit in characters.
- Show the workspace, not a config path, in the startup summary.
The startup line used to print the directory of the closest agents.config.json, which is rarely where you are working and reads as noise. It now shows the directory Nanocoder is operating in, followed by the active branch when the workspace is a git repository. Narrow terminals keep the provider, model and mode on the first line and drop the workspace and branch underneath.
- The streaming reasoning trace no longer re-wraps its entire history on every token flush. It now wraps only a bounded tail, the same way the streaming message does, and skips that work and the token count altogether while the panel is collapsed — which is the default, so the cost was being paid for a panel nobody could see. Measured over 30 flushes of a ~150KB trace: 91.9ms to 11.6ms per flush collapsed, 99.2ms to 25.2ms expanded. Closes [#1329].
- Preserve every copy of repeated pasted text through placeholder display, chunked updates, and prompt assembly.
- Fix fullscreen chat rendering getting progressively slower in long conversations by scaling the mounted transcript tail to terminal height instead of a flat 60-component cap.
- Resume queued prompts after slash commands and manual context compaction complete. Refs [#1060].
/checkpoint loadnow shows a windowed, filterable list instead of rendering every checkpoint at once. A project with a few dozen checkpoints pushed the picker well past the visible terminal. It now uses the same list component as session history, so typing filters, the window scrolls with the highlight, and it shrinks further on short terminals. Closes [#1372].- Fix
--model=value,--provider=value,--vscode-port=value, and--context-max=valuebeing ignored and included in prompts afterrun. Closes [#1238]. - The live compact tool-activity summary above the input now shows at most 5 tool rows, followed by a
+N moreline. A turn that uses many distinct tools (for example with several MCP servers enabled) could previously add one footer row per tool and push the input box out of view on a short terminal. Closes [#1291]. - Grouped the VS Code chat composer controls: model stays on the input row, the current approval mode stays visible on the gear, and provider plus mode pickers live in a Configuration popover. Closes [#859].
- Tool approval prompts now answer exactly once. The approval queue resolves its head on every answer, so a second answer from an already-settled prompt would have consumed the next queued request and resolved it unseen — approving a tool the user was never shown. The prompt now ignores any answer after the first and resets when a new request arrives.
- Add terminal support for
/copy code.
Running /copy code in the terminal previously returned "This functionality isn't supported in the terminal." It now parses fenced code blocks from the last assistant message and copies the most recent block to the system clipboard, matching the VS Code webview behaviour. Returns "No code blocks found in the last response." when the last response contains no fenced blocks.
- Fixed subagent tool results that return structured data without
llmContentso the complete output is preserved for the model instead of being passed asundefined. Closes [#1033]. - Fixed custom command arguments containing
$being rewritten before they reached the model.substituteTemplateVariablespassed the value straight toString.prototype.replace, which treats a string replacement as a substitution template rather than a literal:$&expanded to the{{arg}}placeholder it was replacing,$`and$'spliced a whole half of the command template into the middle of the prompt, and$$collapsed to a single$. Those are ordinary characters in a shell snippet, a regex, a Makefile or a price, so/mycommand "use $' to quote"silently sent something other than what was typed, with no way to see it. Values are now inserted through a replacer function, the same waycustom-tools/template.tsalready did. This is the same defect #1057 fixed forstring_replaceanddiff_edit; the custom-command path was not covered by it.
Also fixed a parameter name containing a regex metacharacter crashing the command. The key was interpolated into the pattern unescaped, so a parameter called a(b built /\{\{\s*a(b\s*\}\}/ and threw Invalid regular expression: Unterminated group. Keys are escaped now, which also means a key like a.b matches literally instead of matching axb.
- Custom tool
cwdnow stays inside the project after symlink and${VAR}resolution. Acwdthat really resolves outside the project - an absolute path,${HOME}, a../traversal, or an in-repo symlink pointing out - now fails the tool call withCustom tool cwd escapes the project directoryrather than running the script somewhere unexpected. A missingcwdstill falls back to the project root. Closes [#1027]. - Fixed custom tools hanging past their own timeout. Three things went wrong on the timeout path. The
SIGKILLescalation was guarded on!child.killed— a flag Node sets the moment a signal is delivered, so theSIGTERMon the line above had already made it true and the force-kill never ran. Nothing held a reference to the escalation timer either, so on a clean exit it was never cleared and fired later against a dead process. And the rejection lived in theclosehandler, which waits for the stdio pipes to drain as well as for the process to exit; sinceSIGKILLonly reaches the shell, any grandchild that inherited stdout — a background job, a dev server, a wrapper script — held those pipes open and kept the call pending long after the shell was gone. Both timers are now tracked and cleared together, the dead guard is gone, and the timeout settles onexitwith the stdio streams destroyed. Thanks to @yashksaini-coder. Closes [#1141]. - Custom tools on Windows now spawn
cmd.exe /d /s /cinstead of-c, which cmd does not accept./dskips AutoRun;/smakes quote stripping deterministic.{{ }}substitution stays POSIX-quoted and is not shell-safe under cmd. Closes [#1028]. - Fixed
nanocoder daemon logsreturning only a sliver of the tail when a single oversized log line runs past the start of the 64KB window. Realigning the window to the first line break discarded everything before it, so a log holding one long serialized payload came back as just the few bytes that followed. The tail now only realigns when a line break is near the start of the window, and otherwise keeps the partial first line. - Fixed
nanocoder daemon logsreading the whole log file into memory to return its last 64KB, so a long-running daemon with a large log made the command allocate the entire file and stall. The tail is now streamed from a byte offset, which also corrects a byte offset applied to a decoded string: any multi-byte content in the log shifted the window and returned far less than the intended 64KB. Closes [#1042]. nanocoder daemon startnow refuses to boot in a directory that hasn't been trusted. The daemon fast path (cli.tsx) went straight torunDaemonCli→startDaemon→bootSkillPipelinewithout ever touchingApp.tsx/useDirectoryTrust, so it loaded and registered every.nanocoder/agents|commands|tools/*.mdandskills/<name>/skill.yamlin the project unconditionally. Once booted, headless mode (used for every daemon-triggered run) executesexecute_bash,write_file,string_replace,diff_edit, and MCP tools with no confirmation prompt.startnow checkspreferences.trustedDirectories(reusing the sameensureDirectoryTrusthelper--plainalready used, now shared via@/config/preferences) before spawning the daemon process, and refuses with a clear message otherwise. A new--trust-directoryflag bypasses the check for a singledaemon startrun without persisting it;NANOCODER_TRUST_DIRECTORY=1still persists trust as it does for--plain. Closes [#1245].- Covered the previously untested
daemon statusanddaemon stopsubcommands with regression tests.
daemon/cli.spec.ts had 12 tests for start and logs but none for status or stop, so a regression in either handler (wrong exit code, lockfile race, missing error handling) would not have been caught by the suite. Five new tests cover the empty-state branches (Not running. / No daemon is running.), the stale-lockfile cleanup path (which removes the lockfile and reports the previous pid), the live-status format string (pid, socket, uptime), and the live-stop shutdown path. The live-stop test forks a real child so SIGTERM lands on something other than the test runner, which would otherwise trip the shutdown manager and abort the run.
No runtime behavior change — tests only.
- The VS Code discovery file's
versionfield is now actually checked. It was parsed and echoed back but never compared, so the "bump on breaking changes" its constant promises would have done nothing: av2file written by a newer CLI was consumed by an older extension as if it werev1, surfacing as a failed handshake or a misread field rather than a clean "not ready". A file declaring a newer schema is now treated as missing, which is the direction that matters given the extension and the CLI update independently. Older files, including ones written before the field existed, still load.
PID reuse in the same file's stale detection is documented as an accepted residual rather than papered over with an age cutoff. A recycled PID reads as live, but the recycled process is not listening on the recorded port and does not hold the bearer token, so the cost is one failed connection that the next start() repairs. An age bound would trade that self-healing transient for a worse failure mode: disconnecting a genuinely long-lived session.
- Document the tool output convention for file content and add a regression test.
-
Overhaul VS Code Add Provider UI:
-
Implement fully dynamic provider and SDK preset dropdowns.
- Synchronize 23 provider templates exactly with CLI defaults.
- Add support for defining multiple custom model names dynamically.
- Fix missing theme CSS tokens in settings panel webview.
- Fixed the test suite failing on
main. [#1417] changedexecute_bash's execute function to return{llmContent, isError}instead of a bare string and updated its own spec, butexecute-function.spec.tscalls the same function and asserted on the result directly. It passedtscbecause it cast the resultas string- the cast asserted away the very change that broke it, so the only thing left to catch it was a runtimet.regex()failure. The threeexecute_bashcases now readllmContentthrough a helper, and theas stringcasts are gone from theread_filecases too, so the next return-shape change fails at compile time rather than at assert time. - Fix markdown export formatting for tool outputs containing inner code fences
fetch_urlno longer transfers the target page twice on every call. The redirect walk that validates each hop issued a GET, so when it reached a non-redirect it had already downloaded the whole final response - and then discarded it, becauseconvertToMarkdownfetches the resolved URL itself. Every call therefore moved the target twice: doubled bandwidth, doubled rate-limit consumption, and two hits on any endpoint that meters or logs requests. The walk only ever needed a status and aLocation, so it now asks for headers. A server that refuses HEAD (400, 403, 405, 501) or fails it outright still falls back to the GET-and-discard it used before, so nothing regresses on the servers that need it. Hop-by-hop validation is unchanged - a redirect into a private or loopback address is still rejected at the hop rather than followed.- fetch_url now blocks the whole
.localhostzone, not just the barelocalhostlabel. RFC 6761 reserves it for loopback and systemd-resolved resolves every label under it to 127.0.0.1, sohttp://foo.localhostreached a local service on Linux. Real hosts that merely contain the string (localhost.example.com) are unaffected. - fetch_url now rejects the request URL inside execute, not only the validator. readOnly tools skip confirmation, so yolo/headless/subagents never ran the old hostname list. Blocks loopback CIDR, RFC1918, and GCP metadata names (
metadata,metadata.goog,metadata.google.internal). HTTP redirects and DNS rebinding are not covered (#1089). - Fixed the
@file mention list losing its highlight after the fifth suggestion, which let Tab insert a file that was never shown. The list now scrolls with the selection and shows "Showing X-Y of Z" when there are more than five matches, like the slash command list. Closes [#1405]. - Fixed
find_filesreturning no results for patterns written with a leading./. Project entries are relative paths with no./prefix, so./src/**/*.tsor./package.jsonwas compared againstsrc/index.tsand never matched, leaving the model to conclude the files did not exist. The prefix is now stripped before matching, and the basename fallback is computed from the stripped pattern so./*.tsxbehaves exactly like*.tsx. Closes [#1343]. - Use
atomicWriteFileSyncforwriteUsageDatato prevent partial or corrupted file visibility. - The Architect review gate now lists only files the turn actually changed or created, rather than every file a tool set out to touch, so a rejected or no-op edit no longer shows up as changed. A turn whose edits all failed skips the gate. Creating a new file no longer flashes a "Could not capture file" warning in the chat.
- The Architect review gate no longer shows "Esc to keep" while you type Revert & Revise instructions. Escape goes back to the options there, which the input's own hint already says.
- Consolidated the atomic-deletion overlap checks onto a single half-open
[start, end)range helper, so the boundary tests that had drifted between<and<=now agree by construction rather than by coincidence. Deletion behaviour is unchanged - the longhand forms were already equivalent for every reachable input. Placeholder lookup at a cursor position does change: a position now counts as on a placeholder when it falls in(start, end], so the position immediately before a placeholder reads as outside it and the position immediately after it reads as inside, matching where the cursor actually sits when you press Backspace. Thanks to @hiarun02. Closes [#977]. - Fixed control keys that arrive in one read (a held Backspace, a double Ctrl+Z, keys typed while the UI is busy) doing nothing and being inserted into the prompt as raw bytes that were then sent to the model. Each control key is now handed to the input handlers separately.
- The
runmode boot line no longer marks the default branch as(default), as intended when the marker was restored to/status. The boot line had since moved to a different label helper that kept it. - Refuse to snapshot a file from outside the workspace. Snapshot keys are the file's path relative to the workspace, so a file above it was keyed
../nameand would have been written outside the checkpoint's own files directory.captureFilesnow drops those paths and reports them throughskipped, so an incomplete checkpoint still says so at restore time. The same containment rule already guarded restore and delete and is now shared by all three. - Fixed checkpoints irrecoverably corrupting binary files. Every layer of the pipeline read and wrote contents as UTF-8, so each byte of an image,
.vsixbundle or database came back as U+FFFD — and because the damage was done at save time, the checkpoint itself held nothing left to recover. Snapshots are now carried asBufferand written with no encoding argument; text still round-trips byte-identically.
A checkpoint that came out incomplete also restored in silence. Files that could not be read at capture, and files dropped by the MAX_CHECKPOINT_FILES cap, are now recorded on the checkpoint's metadata and named when it is restored. Old checkpoints still load, though binaries captured before this fix stay corrupt. Closes [#962].
- Checkpoints no longer snapshot nanocoder's own state. In a repo that doesn't gitignore
.nanocoder/, every checkpoint captured the earlier checkpoints and the action timeline, so each one grew (0, 2, 6, 14, 30 files) until the 50-file cap crowded out your real changes, and restoring one wrote stale checkpoint data back over the live store. User content under.nanocoder/(commands, agents, tools, skills) is still captured. - Fixed the
cli-value-flagsintegration tests failing onmain. The suite's module loader short-circuits@/config/preferenceswith a stub, andcli.tsxstarted importinggetAlternateScreenandgetMouseReportingfrom it while that stub still exported onlyloadPreferences. Both changes passed on their own branches and only collided once merged, so the missing exports surfaced asgetMouseReporting is not a functionacross all 30 cases. - Fix Expand Tool Results label inverted and compactToolDisplay preference never read at startup
- Fixed parallel subagents hanging the turn on the first approval prompt. The three "ask the user" slots each held a single resolver, so a second caller arriving before the first was answered overwrote it and that first promise could never settle.
tool-executorstarts up to five subagents in one turn and awaits them withPromise.allSettled, so one stranded caller meant the batch never resolved, the turn never ended, and Escape could not free it - the subagent was parked in an await that is not abort-aware, so recovery meant killing the process. Each slot now queues requests in arrival order and presents them one at a time, so every caller settles with its own answer. The same design backsask_userand the main agent's tool confirmation, and both are fixed by the same change. Closes [#1156]. - Fixed
--context-maxand/context-maxsilently accepting malformed values.10kgused to parse as10and128kbas128, so a typo quietly set a context limit nothing like the one you asked for. The value is now validated as a whole, and anything that is not a positive number with an optionalk/Ksuffix is rejected with the existing error message. Values large enough to overflow toInfinityare rejected too, rather than being stored as a session limit. Thanks to @hiarun02. Closes [#973]. {{args}}in a custom command now receives the arguments exactly as typed. It was rebuilt from shell-style parsed tokens, so quotes were stripped and an apostrophe opened a quote:/cmd don't break itreached the model asdont break it. Declared positional parameters still use the parsed tokens.- Fixed an issue in
CustomCommandLoaderwhere encountering an unreadable subdirectory or inaccessible file in custom command directories aborted the entire scan.scanDirectoryandloadResourcesnow catch filesystem permission and inspection errors, log a warning, and continue scanning remaining commands. - Runs triggered by the skill daemon can now use their own skill's tools, custom tools and MCP tools. The daemon registered skill tools into one tool registry but ran triggered agents against a second, empty one, and it never connected MCP servers, so a subscribed agent could only use the built-in tools. MCP servers are now also disconnected when the daemon stops, so
nanocoder daemon stopexits cleanly. - Closed the remaining routes by which nanocoder's own UI text reached the model. The ACP timeline-revert notice ("Reverted to before step N…") was still pushed into history as a plain assistant message, and
/compact(plus auto-compact) fed display-only notices into the LLM summariser, whose summary re-enters context as a realusermessage — so a compacted session could still be told it had errored. Notices are now excluded from the summarised segment, and context-usage estimates and the auto-compact threshold count only what the provider actually receives. Also documents thedisplayOnlycontract onMessage, warns when a display-only message carriestool_calls(which would silently drop its tool results from the payload), and shares the "Tool approval required for: " prefix as a constant so the non-interactive exit-code path can't break on a reword. - Stopped nanocoder's own UI text from being sent to the model as its past output. Cancellation notices (
_Cancelled by user._), inline error banners (**Error:** ...), the non-interactive "Tool approval required" notice, and the VS Code replies to built-in slash commands (/help,/copy,/model, unrecognized commands) were all pushed into conversation history asassistantmessages, so on the next turn the provider received harness-authored markdown as if the model had written it — teaching it to imitate the chrome and, on a resumed session, to believe it had errored. These are now marked display-only: they still render in the chat and replay with session history, but they are filtered out before messages are converted to the provider payload. Closes [#893]. - Fixed
string_replace,diff_editandwrite_filedestroying PDF and DOCX files. Reading one of those formats returns a markdown transcript rather than the bytes on disk, and the write side had no matching branch: the edit was applied to the transcript and written back over the document as UTF-8, so a request to fix one word replaced a real document with a few hundred bytes of plain text and the tool reported success. Neither undo system could recover it - checkpoints skip binaries and file snapshots store text - so the original bytes were gone the moment the write landed. The three write tools now refuse any path whose content the read path can only transcribe, naming the reason so the model stops instead of retrying. Closes [#1058]. - Fix user prompts appearing twice in the terminal scrollback when the model starts streaming a response in default inline mode. Pressing Escape to recall an in-flight prompt in inline mode now leaves the bubble visible in scrollback rather than removing it from the live view, since it has already been committed to Ink's transcript and cannot be un-printed.
- The empty
/resumepicker now reads "Press Esc to close" instead of the contradictory "Press Escape to continue • Esc to cancel". - Fixed message compression so resolution text only marks an error resolved when it appears after that error.
- Fix
/explorercrashing the CLI.
Running /explorer killed the process with exit code 1 and useUIStateContext must be used within a UIStateProvider. The provider wrapped only the chat input, while the file explorer renders as a sibling, so the hook threw during render and took Ink down with it.
The same placement broke the feature even without the crash: explorer mode unmounts the chat input, so the provider holding the explorer's selection went with it and the files could never have reached the prompt. The provider now wraps the whole interactive tree, and picking files in the explorer inserts them as @ mentions when you exit.
- Fixed fast typing occasionally scrambling the prompt, for example
/tunecoming out as/etun, most often on the first command typed after startup. The input compared each echoed value only with its latest keystroke, so a render that ran late looked like an outside change and put the cursor back at the end of the older, shorter text. - Fixed the status row under the prompt being cut mid-word in fullscreen (for example
normainstead ofnormal) when the task badge and a session name share a narrow terminal. The row's width budget now accounts for the fullscreen frame's padding. - Daemon-triggered (headless) subagent runs are no longer offered the
ask_usertool. Nobody can answer in a headless run, so calling it only returned a "Question handler not initialized" error after the model had spent a turn on it. - This release introduces a suite of UI improvements to the VS Code chat panel and robust session state recovery:
- Timeline Removed: Deprecated and completely removed the action timeline feature and its internal event tracking.
- Artifacts Redesign: The artifacts list has been redesigned into a clean, collapsible container that can be toggled by the user.
- Session History Durations: Fixed a bug where history playback would render a duration of
0sfor cancelled or failed sessions. The chat data schema now officially tracks anoutcome('completed' | 'cancelled' | 'failed') and adurationMson every assistant message. Limitation: If a turn is cancelled before the agent emits any thought or tool call, no work summary container exists in the webview to display the duration. Those turns show no duration indicator at all (rather than the previous incorrect0s). - Stream-Time Footer Hiding: The message footer (Retry, Copy, timestamp) dynamically hides while the agent is streaming its response, rendering a much cleaner interface that only shows actions once the agent completes.
- Smooth Auto-Scroll: Restored intelligent auto-scrolling where reading past messages prevents forced scroll-to-bottoms during a stream. A forced scroll is now selectively applied only when a run finishes or a session is loaded.
- Wider User Bubbles: The user prompt bubble max-width was expanded from 85% to 90%.
list_directoryand the file explorer now hide a directory matched by a directory-only ignore pattern such asdist/ornode_modules/in.gitignoreor.nanocoderignore. Its contents were already hidden, but the folder itself still listed, and listing it then reported it as empty.- Fixed a paste restored from an older session's prompt history being sent to the model as its own label. Placeholder lookup now matches on each entry's display text, but entries persisted before placeholders carried a
displayTexthave none, so they were skipped and their[Paste #N: X chars]label survived into the prompt instead of expanding to the pasted content. Those entries now have their label rebuilt from the ordinal in their key and the content they hold, matching the legacy fallback that the display-text lookup replaced. - Fix the exported inline-diff similarity helper name from
areLinesSimlartoareLinesSimilar. Thanks to @hafzism. Closes [#968]. - Fixed
string_replaceanddiff_editcorrupting edits whose replacement text contains$. Both tools passed the model's replacement straight toString.prototype.replace, which treats that argument as a substitution template rather than a literal:$$collapsed to a single$,$&expanded to the matched text, and$`/$'spliced a whole half of the file into the middle of the edit. Those are ordinary characters in shell scripts, Makefiles, CI YAML and anything that builds a regex, so the bytes on disk silently diverged from the diff the user approved. Replacements are now spliced by index, so the approved preview - in the terminal and over ACP - is what lands. Closes [#1057]. - Fix macOS native notifications when title or message contains newlines, quotes, backslashes, or Unicode characters by passing arguments out-of-band to
osascript. Closes [#1139]. - Fixed the last row of a markdown table being left outside the rendered table as raw
| a | b |text whenever the table ended the reply, which is common. The table pattern required a newline after every row, and replies are trimmed. - The MCP setup wizard now shows hand-written
.mcp.jsonservers by name. A server defined only by its JSON key was listed as a blank "• (stdio)", the "Added:" line began with a stray comma, and its edit form opened with an empty name. - Ensure mechanical message compression preserves display-only messages in scrollback while excluding them from token counts and the recent message window calculation.
- Include the provider in the message token cache key. The tokenizer is chosen from provider and model together, so two providers serving the same model name were sharing cache entries and could report counts produced by the other provider's tokenizer.
- Stop mode-switch model toasts stacking up.
Cycling development modes with Shift+Tab pushed a [mode → model] line into the transcript on every step, and the handler races ahead of prop updates, so a fast cycle left several identical lines in scrollback. Repeats of the same toast are now suppressed, and returning to normal mode posts nothing at all - that only restores your own default model, and the status bar already shows the change.
- Fix a startup crash when
package.jsonis missing, unreadable, or malformed. Both module-load reads of the version (cli.tsxand the welcome banner) threw before any error handling existed, taking down the CLI on a misbuilt install. Version lookup now lives in a single helper that falls back tounknown, shared by the banner,/help, and/doctor(which previously reported a misleading0.0.0). - Fixed paste detection extracting the wrong text from the input buffer. It assumed every insertion was appended at the end, so pasting with the cursor anywhere but the end stored a truncated placeholder, and deletions or unchanged input reported a slice from the middle of the string. Detection now diffs the buffer against its previous revision and ignores non-positive deltas. Closes [#979].
- The filterable pickers (
/checkpoint load,/resume, model and provider lists) now fit a short terminal. The window only budgeted for the list's own rows, not the titled box and app frame around it, so on an 18-row terminal the hint and the box's bottom border were pushed off screen. The window also now shrinks when the terminal is resized while a picker is open. - Fixed paste placeholders silently overwriting each other. Placeholder ids were derived from the number of live entries, so deleting one freed its id for reuse and the next paste clobbered a placeholder that was still in the input - destroying its content and leaving a duplicate label that got sent to the model as literal text. Ids are now namespaced by type (
paste_1,file_1) and allocated from the highest id ever used, so a deletion can never free an id. Placeholder lookup now matches on each entry's own display text instead of a paste-shaped regex, which also makes@filementions delete atomically rather than leaving an orphan entry behind, and lets two placeholders that render identically expand to their own content. nanocoder --plainno longer fails when the last-used provider has since been renamed or removed fromagents.config.json. It now falls back to the first configured provider with a notice, as the interactive TUI already did. An explicit--providerthat does not exist is still an error.- Fixed a
{}entry appearing as the oldest prompt in Up-arrow history for new users. The history file is seeded as an empty JSON object, which was being read as a one-line legacy history. - The provider settings
promptCachingandmaxRetriesinagents.config.jsonnow take effect. Both were documented but dropped when provider entries were loaded, so"promptCaching": falsestill sent Anthropic cache breakpoints and a custommaxRetriesalways fell back to 2. - Fixed the
runmode boot line never showing the provider, model or mode. It pinned those values on the first render, before initialization had set them, so the line showed only the workspace, and on a narrow terminal it rendered nothing at all. nanocoder runnow exits with code 1 when the run fails: a provider or connection error, or a stop bynanocoder.retries(repeated tool calls, empty responses, malformed tool calls). It exited 0 in every one of those cases, because it looked for an error-role message that nothing ever produces, so CI could not tell a failed run from a finished one.--plainalready reported these correctly.- Prompt scrubbing now applies everywhere once it is switched on, not only to the main TUI conversation. The setting was only passed to the model client by the interactive chat, so
nanocoder --plain, ACP sessions (including the VS Code extension), subagents and helper calls such as compaction sent secrets from files, command output and tool results to the provider unscrubbed. - Reject unsupported
skill:<name>subscription targets with a clear error instead of registering subscriptions that cannot dispatch. Thanks to @1cbyc. Closes [#1011]. - Fixed a regression where the VS Code extension webview rendered without theme colors after the Tailwind v4 upgrade by migrating custom color variables to an
@themeblock in the CSS. - Timeline index saves are now atomic:
timeline.jsonis written to a temporary file and renamed into place instead of being overwritten in place. If the process dies mid-save, readers only ever see the complete old or complete new index, so a torn write can no longer silently discard every checkpoint of the session. Thanks to @puri-adityakumar. Closes [#1130]. - Truncated tool output no longer leaks part of a secret. Bash output is cut to fit before scrubbing runs, and a cut through the middle of a key left a fragment such as
sk-live-abcdef1234that no detector recognises, so it went to the provider even with scrubbing on. Cuts now avoid splitting whitespace-separated tokens, so a secret is either kept whole, where it gets scrubbed, or dropped entirely. - Fix auto-compact silently no-opping in the TUI after the shared-helper refactor.
maybeAutoCompactderived the provider and model from the client and swallowed any failure, so the chat loop's owncurrentProvider/currentModelwere ignored; it now accepts them as explicit overrides. Auto-compact in--plain, ACP, and subagent loops also stops writing the message cap back into stored history when no compaction ran. - A model stuck calling a tool that does not exist now trips
nanocoder.retries.maxRepeatedToolCallsas intended: the TUI asks whether to continue after three identical calls and--plainstops with an error. The AI SDK was answering each unknown call itself and looping up to ten steps inside a single request, so nanocoder never saw those calls and the cap never applied. - An install with a missing
package.jsonno longer posts "Failed to read current version" to the chat on startup, which also pushed the welcome screen away. The update check now uses the shared version helper and skips itself when the version is unknown, and the banner reads "(version unknown)" instead of "vunknown". - Fixed update command error detection when later output reports zero errors. Thanks to @anisayakmitra-in. Closes [#974].
- Fixed
/updatereporting a failure when the package manager exited 0.hasCommandFailedfell through from the exit-code check into the generic pattern match, so a benign success summary like "0 failed" or "0 cannot be updated" was reported as an error. When the command exits 0, only unambiguous error indicators (command not found, no such file or directory, permission denied,error:lines,fatal) still count as a failure. Closes [#1304]. - Fix zombie CLI on
Nanocoder: Restart Nanocoder Agent Process. - Fix unauthenticated cross-origin access to the VS Code companion WebSocket.
The companion server bound to a fixed loopback port (51820) and accepted every upgrade, so any local process or browser tab could deliver {"type":"send_prompt", ...} straight into a running agent and read every broadcast the server pushed out. This made prompt-injection and file-content exfiltration possible on any developer machine that had a session open.
Three changes close the hole:
- The server now mints a 256-bit bearer token at startup and only accepts WebSocket upgrades that present it in an
Authorization: Bearer <token>header (constant-time comparison). The token is never placed in the URL, where it would show up in browser bars, proxy logs and any other intermediary that happens to inspect the request line. - Any upgrade carrying an
Originheader is rejected before the handshake completes. The legitimate extension is a Nodewsclient and never sends one, so closing that door costs nothing and shuts out browsers specifically. - The server binds to an ephemeral port by default and publishes
{port, token, pid, cliVersion, startedAt}to<configDir>/vscode-server.json(mode 0600). The--vscode-portflag remains available for users who need a fixed port (SSH forwarding, etc.); in that mode the discovery file is still written so the extension can pick up the token automatically.
The VS Code extension (plugins/vscode) now reads the discovery file to learn the port and token instead of trusting nanocoder.serverPort. For SSH-style set-ups where the discovery file lives on the remote host, a new nanocoder.serverToken setting lets the user paste the token manually. Stale discovery files (where the recorded PID is no longer alive) are ignored, so a crashed CLI can no longer hold the port hostage; and a live foreign CLI that has taken over the file is left alone on shutdown, so two concurrent sessions do not clobber each other.
- Fix orphaned ACP session on concurrent init.
- Reject concurrent prompt submissions while a turn is in flight.
- Fix web_search TimeoutError not correctly captured as a timeout error
- Fixed
file.changedskill subscriptions silently never firing on Windows. Chokidar reports changed files using the platform separator, so a documented pattern likedocs/**was asked to matchdocs\guide.mdand never could — the daemon started, reported healthy, logged nothing, and did nothing. The same mismatch let a root-scoped pattern such as*.mdmatch the nestedsub\a.md, dispatching an unattended agent run against a file that was deliberately scoped out. Paths are now normalized to/at the watcher boundary, so the router, activity reports, and the payload handed to a triggered agent all read one path shape. Closes [#964]. - Fixed models cache path on Windows by using
os.homedir()instead ofprocess.env.HOME, which is undefined on Windows. The cache now writes to the correct location instead of creating a literal~folder. - Hardened Windows native notifications. Notification title and message are no longer interpolated into the PowerShell script; they are passed out-of-band as environment variables and read by a static script. Fixes rendering and parsing edge cases with backticks, quotes and other special characters. Closes [#1138].
- Fixed the git tools hanging when git or gh needed input.
execProcessnow closes stdin, setsGIT_TERMINAL_PROMPT=0, and gives up after 60s, so a credential prompt fails with an error instead of freezing nanocoder. Thanks to @Piyushrathoree. Closes [#1344]. - Fixed skill subscriptions never firing for a brace pattern such as
*.{ts,tsx}. The event router's glob matcher escaped{,}and,as literals, so the pattern only matched a path that literally contained the braces. Braces now expand to an alternation, and an unbalanced brace stays literal rather than building a regex that would throw. Negation (!) is still unsupported. Closes [#1012]. - Hide dotfiles when
list_directoryuses its default project-root path. Closes [#1237]. - Fixed lifecycle hook output being garbled when a multi-byte UTF-8 character landed on a chunk boundary. Each chunk was decoded on its own with
chunk.toString(), so both halves of a split sequence became U+FFFD. That is the same defect [#1305] fixed for bash and custom tools by routing them throughutils/stream-collector.ts; hooks were missed. It is not cosmetic output:pre-tool-useanduser-prompt-submitstdout is handed to the model as the reason an action was denied, andpost-tool-usestdout is folded into the tool result, so a formatter or linter emitting box-drawing characters, CJK paths or emoji status markers fed the model corrupted text. Each stream now decodes through aStringDecoderand is flushed when the process closes, so a stream ending on a partial sequence no longer drops its last character either. The character cap also stops mid-slice of a surrogate pair. - Fixed a lifecycle hook surviving its own timeout.
killHookTreesent oneSIGTERMto the hook's process group and returned; theSIGKILLbeside it ran only in thecatch, which is the branch taken when the group is already gone rather than when it refuses to die. A hook that trapsSIGTERM, or that is blocked in an uninterruptible call, therefore kept running after the agent moved on - the exact runaway the detached spawn exists to prevent. The timeout now destroys its end of the pipes, signals the group, and arms a deliberately uncancelledSIGKILLescalation a second later, mirroring whatcustom-tools/handler.tsdoes since [#1141]: a shell exiting does not mean its group is empty, so the escalation has to outlive it. Windows is unchanged, where the reap istaskkill /T /Fand already unconditional. - Fix a crash when running
/helpby bumping the bundledtsc-aliasfrom 1.9.4 to 1.9.5, which includes the upstream fix for justkey007/tsc-alias#276. - Fixed a leaked pending slot in the daemon IPC client. If serializing or writing a request threw synchronously, the request's entry stayed in the pending map for the lifetime of the client, one per failed request. The slot is now released and the request rejected explicitly. Closes [#1040].
- Added a keyboard shortcuts overlay. Pressing
?in an empty prompt now opens a legend of the chat input's shortcuts (submit, new line, history, readline editing, image attachments, mode cycling, compact output, reasoning traces, task list, subagent attach, cancel, exit) instead of typing a literal question mark;?or Esc closes it.?typed anywhere after the first character still inserts normally, and/helpnow points to the overlay. Closes [#1315]. - Hardened the VS Code action timeline: it no longer snapshots its own before-images, skips a checkpoint rather than recording a wrong one when the workspace scan is truncated, leaves binary files alone, reverts a whole assistant turn at once, validates paths read back from the timeline index, and keeps the chat thread on screen when a revert is refused.
- Fixed the daemon socket path on Unix when a deeply nested project pushes it past the
sockaddr_un.sun_pathlimit (104 bytes on macOS, 108 on Linux). libuv silently truncates overlong paths rather than failing, so the daemon reported a socket it never bound, its stale-socket cleanup missed the real file, and two projects sharing a truncation prefix could collide on a single socket. Nanocoder now falls back to a stable hashed socket name under the system temp directory (or/tmpifTMPDIRis itself too long), anddaemon startreports the path the daemon actually bound instead of recomputing it. - Fixed MCP tools bypassing the development-mode policy that governs every other tool. MCP built its own approval predicate rather than routing through the central mode system, which failed in two directions: a tool on a server's
alwaysAllowlist collapsed to "never needs approval" in every mode, so plan mode executed mutating MCP tools with no prompt despite being the mode you switch into specifically to inspect an untrusted model's intentions without side effects; and every other MCP tool required approval in headless, where no approval handler exists, so daemon-triggered skill runs failed with "Tool execution was denied by the user" when no user was ever asked. MCP tools now take the same posture as built-in tools — headless runs them unattended likeexecute_bashand the file tools, and plan mode gates them on the server'sreadOnlyHintannotation, treating an unannotated tool as a possible mutation and hiding it. A server'salwaysAllowlist now applies in normal mode only, as its documentation already stated, and can no longer override plan mode.readOnlyHintdecides plan-mode availability only: because it is supplied by the same server being gated, it never skips a confirmation prompt in normal mode — the user's ownalwaysAllowlist remains the only way to do that. - Fixed
ToolManager.disconnectMCP()rebuilding the whole tool registry from the built-in exports and clearing the custom tool map. TheunregisterMany()call above it already removes exactly the MCP tools, so the rebuild was redundant, and it discarded three other things with them: workspace custom tools along with theapprovalandread_onlymetadata that plan and headless filtering read, skill and bundle tools registered throughregisterSkillTool(), and the constructor's removal ofweb_searchwhen no Brave Search key is configured, which came back as an unusable tool. The only caller today is the shutdown handler registered ininitializeMCP, where discarding state is harmless, so nothing user-facing changes. The method is public and any other caller would have hit all four. Closes [#1034]. - Re-enable project-level MCP credential scanning.
loadAppConfigdropped the loader wrapper'ssourcebeforevalidateProjectConfigSecurityfiltered on it, and env substitution ran before the scanner so$API_KEYlooked hardcoded. Capturesourceand pre-substitutionrawEnv/rawHeadersso only real literals warn. Closes [#1248]. - Confined the MCP
readOnlyHintannotation to plan-mode availability, and made"enabled": falseactually disable an MCP server. ThereadOnlyHinta server reports was being copied onto its registered tool entries, whereToolManager.isReadOnly()also decides whether ACP captures a checkpoint before the call and whether the tool joins a parallel execution batch — so a server annotating itself read-only could talk its way out of a restore point. The hint now lives on the MCP tool mapping, which only plan-mode filtering reads, exactly as the documentation describes. Separately,enabledwas loaded from config and then ignored: every configured server connected regardless, including in headless runs where MCP tools execute unattended and"enabled": falseis the only documented opt-out. Disabled servers are now skipped before any connection is attempted. - Moved the VS Code mode selector onto the chat composer row with accessible mode labels and dropdown state.
- Fixed Settings → Nanocoder Shape doing nothing. The welcome screen redesign hardcoded the wordmark to the block font, so the panel kept previewing and saving a shape that never reached the banner. The banner reads the preference again, and now subscribes to preference writes so the new shape appears as soon as the panel is closed rather than on the next restart. The width and height thresholds that decide between "NANOCODER", the "NC" monogram and no wordmark at all are per-font too, so a short font like chrome gets the full wordmark on a 50-column terminal and a tall one like huge no longer pushes the menu and tip off screen. The default the settings panel reports is also the one now on screen: block, not tiny.
- Fixed the
/settings→ Notifications panel dropping thetriggeredRunCompleteevent. The panel seeded its fallback config with only three events and rendered a row for each, then wrote the whole object back on every toggle - so flipping any switch persisted a preference with notriggeredRunCompletekey, and the daemon's "triggered run completed" notification silently stopped firing. The event now has a default and a row of its own. Also documented thetriggeredRunCompleteevent in both preference tables, and noted that the terminal bell needs the master Notifications toggle on and that tmux swallows the bell unlessmonitor-bellis enabled. - Added improved spacing and removed the copy button from user messages in the VS Code chat panel.
- follow-ups to the Ctrl-T task-list toggle: the status-bar badge now yields space under width pressure instead of squeezing the session name and editor pill, the unread marker only fires on real task changes, and the "todo" naming is aligned with the "Tasks" list it summarises
- Fixed a slash command with leading whitespace being sent to the model as a chat message instead of running.
parseInputtrims before testing for the/prefix but the routing inhandleMessageSubmissiondid not, so/helpreached the LLM. With lifecycle hooks configured it also fireduser-prompt-submitand consumed bufferedsession-startcontext for an input that never reaches the model. Both the routing and the hook's local-action check now trim, matching the!bash passthrough. Also addednanocoder.hooksto theagents.config.jsonJSON Schema — hooks landed alongside the schema and were missing from it, so every documented hooks example was flagged as an unknown key in editors. -
--mouseand--no-mouseare stripped from arunprompt. They were documented but not filtered, sonanocoder run "fix the tests" --mousesent the model a prompt beginning with the word--mouse. Display flags on a non-interactive run are meaningless, exactly like the--alt-screenpair that was already stripped. -
The
runprompt parser exists once. It lived insidecli.tsxwith a hand-written copy incli.spec.ts, and the copy had fallen six flags behind the original —--mode,--json,--output-format,--trust-directoryand the alt-screen pair were removed by the parser and left in the prompt by the copy, so those tests passed against a parser that is not shipped. No test written against a duplicate can notice it has drifted, because it is testing the duplicate.
It moves to run-prompt-args.ts, imported by both. The tests now derive
their cases from the parser's own flag lists rather than a list typed beside
them, and a second test reads the flags out of --help and fails if one is
documented but not filtered. That second test is what found the --mouse
pair above, which a hand-written enumeration had missed twice.
- Paste bracketed text at the caret, not at the end of the input.
A terminal paste (DECSET 2004) used to append to the end of the prompt no matter where the caret was, and even after splicing the new content in at the caret, the caret itself snapped back to end-of-value. The TextInput caret is now read before the splice, the splice lands exactly at that offset, and the caret stays where it was set — so pasting with the cursor in the middle of existing text drops the placeholder exactly where you were editing and parks the caret right after it. Pasting at end-of-value still works the same way it always did, and the heuristic paste detector paths are unchanged.
Also exposes a TextInputHandle (getCursorOffset / setCursorOffset) on TextInput so the parent can drive the caret without lifting its state up the tree.
- Multi-line paste placeholders now show a line count instead of a character count, so pasting a code block, log, or stack trace reads as
[Paste #1: 7 lines]rather than[Paste #1: 157 chars]. A single trailing line break is not counted as an extra line. Long single-line pastes keep the[Paste #N: X chars]label, and a paste that arrives in chunks updates its label as it grows. Closes [#1292]. - Update the message token cache in place instead of copying every entry into a new map on each cache miss.
nanocoder --plain --json runreports now includesteps, the number of model round-trips in the run (retried turns included). It differs from the length oftoolCalls, since one step can make zero or several tool calls, and it is0when the run stops before the model is called. This gives the agent evaluation harness in [#1197] a step count to measure.- Fixed
--plainruns reporting success after the model's reply was cut off at the output-token limit. A truncated turn comes back as content with no tool calls, which is exactly what a finished turn looks like, so the headless loop returnedkind: 'success'carrying half a sentence — and a run whose entire deliverable was a tool call could exit0having written nothing.finishReasonis now carried out of the AI SDK client instead of only being logged, and a truncated content-only turn is asked to continue from where it stopped, up tonanocoder.retries.maxTruncatedTurnstimes (default 2,0restores the old accept-the-fragment behaviour). The nudge steers the model to make the outstanding tool call rather than re-explain itself, since spending the whole output budget narrating before acting is what triggers this. - A shell command that exits non-zero is now reported as failed in
--plain --jsonoutput and over ACP.execute_bashreturned its output as plain text, so the exit status never reached the tool result: the JSON report filed a failing build's output underresultrather thanerror, and ACP clients such as Zed showed it as completed. The handler now reports the failure alongside its text, which the model still receives unchanged. Closes [#1391]. - Professional Tone now applies as soon as you toggle it in
/settings. Previously the completion note changed immediately but the system prompt's TONE section waited for the next mode or model switch, so the model kept its old register and the toggle looked broken.buildSystemPromptalso takesprofessionalToneas an explicit argument instead of reading the preference file itself. - Professional Tone now uses a shortened TONE section under the
nanotool profile, matching how every other prompt section is slimmed for tiny models. The full section cost ~695 characters on a prompt that is deliberately minimal; the nano variant is ~228. - Fixed the output-token ceiling being unsettable, which silently truncated long replies. The AI SDK renamed this setting to
maxOutputTokensin v5; nanocoder was still sending the v4 namemaxTokens, and because object spreads bypass TypeScript's excess-property checking it was dropped silently rather than failing to compile. Every request therefore fell back to whatever ceiling the provider inferred from the model id.
That matters most on sdkProvider: "anthropic", where @ai-sdk/anthropic falls back to 4096 output tokens for any model id it does not recognise as a Claude model. An Anthropic-compatible endpoint serving something else - MiniMax, for instance - had every reply cut off at 4096 with no error.
Two changes:
- The setting is now sent under the name the installed SDK reads, so
/tune's max-tokens control works again. It has been inert since the v5 upgrade, which also means theNano (low-end hardware)preset'smaxTokens: 2048now actually applies. - Provider entries in
agents.config.jsonacceptmaxOutputTokens. Headless runs never carry/tuneparameters, so this is the only way to raise the ceiling in CI. A/tunevalue still wins where one is set.
Tests assert what reaches the wire rather than what typechecks, since typechecking demonstrably could not catch this.
- Fixed the bash tool silently dropping stdout when stderr was noisy; each stream now gets its own output budget and truncation marker. Worst case memory goes from 5MB to 10MB since both streams can now hit the cap at once. Closes [#1140].
- Fixed ACP prompts racing with each other by claiming session turns before asynchronous setup begins, preventing overlapping prompts from corrupting turn state. Built-in slash-command replies are kept in persisted session history for replay, while command-only sessions are not saved. Closes [#1038].
- Fixed
{{args}}in custom commands without declared parameters so it receives the raw command arguments instead of an empty string. Closes [#1035]. - Fixed
read_filenever rendering its metadata-only view. The formatter decided a response was metadata-only by testingresult.startsWith('File:'), but the metadata branch emitsFile Information for "...", so the test could never match. The same condition also required no line range and a file over the 1500-line preview threshold, neither of which applies — ametadata_onlyread returns before either is considered. As a result ametadata_only: trueread was displayed as an ordinary content read: no "(metadata only)" marker, the content-read layout instead of the metadata layout, and a token count measured from the metadata block rather than the full file. Detection now keys off the request flag that actually selects the response shape — matched withBoolean()so a truthy non-boolean value (as the XML tool-call fallback can produce) is still recognised, and set before any file read so a directory or symlink still renders the metadata layout even though reading its content throws. - Fixed the prompt caret jumping to the start of the text after a redo (Ctrl+Y), which sent the next keystroke to the front of the line - redoing "abcde" and typing "X" gave "Xabcde". TextInput only re-clamped the caret when the value shrank, so a redo that restored a longer value stranded the caret at the offset the preceding undo had clamped it to. Value replacements that come from outside the component - undo, redo, draft restore, a programmatic clear - carry no caret of their own, so the caret is now parked at the end of the restored text the way a fresh mount does. Mid-text typing is unaffected; the component still tracks its own edits and leaves the caret where it is.
- Fixed two
/repomapindexing bugs and added a progress spinner. Python docstring bodies are no longer indexed as definitions (the"""and'''branches were unreachable in the comment-stripping pattern, so names inside a docstring were reported as real symbols and sorted ahead of them), and a repo holding exactlymaxFilesindexable files is no longer reported as truncated./repomapnow shows a "Building repo map" spinner while it scans, and reuses the sharedcalculateTokenshelper for its budget maths. - Fixed the VS Code extension dropping token and estimated-cost footers when a saved chat is reopened. Completed ACP turns now persist their response usage on the matching assistant message and restore it during history replay, while existing sessions without usage metadata continue to load unchanged. Closes [#1097].
- Restored the VS Code panel features a bad merge in [#898] reverted: the action timeline and its undo, slash command autocomplete, editor-tab drag and drop, the MCP settings button opening
.mcp.json, and the denied-edit status icon. The work summary from [#898] is kept, and no longer opens a second box that never settles when a tool reports back after Stop, nor a box for whitespace-only reasoning. - Prevent
fetch_urlfrom following HTTP redirects before the destination has been validated, protecting the no-approval tool from redirect-based access to private and loopback addresses. Closes [#1089]. - Keep shared cron jobs alive until every subscriber unregisters.
ScheduleEventSource.unregister()used to stop the underlying job on the first call even when other subscriptions still used the same expression. Closes [#1239]. search_file_contentsnow shows what it found. The expanded tool display lists thefile:linehits under the match count, capped at the same 20 lines asexecute_bashoutput with a… (+N more lines)note, instead of only the query, flags, and a count. Closes [#1290].- Fixed the semantic memory lock being stolen from a process that was still using it. The lock file's mtime was stamped once at acquisition and never refreshed, so "held for more than ten seconds" and "abandoned" were the same test. Any write that legitimately took longer had its lock deleted by a waiter in another process; both then ran the critical section at once and finished with
atomicWriteFile, so the loser's memories were silently dropped rather than merged. The in-process queue meant this only bit across processes - two sessions in one repo, or a session plus the daemon, which is exactly what repo-scoped memory is for.
Ownership is now decided by whether the recorded owner is still running, with the heartbeat as a tiebreak. A holder refreshes the lock's mtime every two seconds while it works, so a waiter can tell "still going" from "gone". A dead owner is reclaimed immediately however fresh the file looks, which also removes the ten-second stall a crashed session used to impose on the next write. A live owner whose heartbeat has stopped for longer than the stale window is still reclaimed, covering both a wedged holder and a recorded pid that has been recycled by an unrelated process.
- Wire semantic memory recall into subagent and daemon runs, serialize writes across manager instances and processes, and cap each repo memory file at 500 entries.
- Rank recalled memories by how much of the query they cover, skip tool-call narration in reversal detection, and drop noisy
/memory proposecandidates. - Added a visual
savingindicator in the CLI status line that briefly displays whenever session state is autosaved to disk. Thanks to @rishu685. Closes [#932]. - Added keyword search to the
/resumesession selector. Typing now filters the session list by title, using the same type-to-filter list as the model picker, so an old conversation no longer has to be found by scrolling and guessing by date. The filter matches the title only, not the message count or age shown next to it; Backspace edits the query, arrow keys and Enter pick from the filtered results, and Esc cancels. Closes [#1316]. - Re-enabled the
/settingsscroll-indicator test. It was skipped because no tab exceeded the four visible rows, so the indicator could not be triggered organically, with a note to restore it once one did. That happened this cycle: Appearance gained Alternate Screen and Mouse Wheel Reporting alongside the fullscreen TUI and now has five rows, and Behavior and Advanced have six each. The settings window is the busiest it has been and had no coverage of its own scrolling; the test now also asserts the hidden-row count rather than only the label, so an off-by-one in the window arithmetic fails it. - Display compound bash commands on separate lines in execute_bash output
- Fixed
nanocoder skills addletting a repository or ref reachgitas an option.cloneAtReffalls back togit init+git fetch origin <ref>whengit clone --branch <ref>fails, and the ref was passed as a bare positional - but git keeps parsing options after positionals, so a ref of--upload-pack=<command>made git run that command through a shell. That is reachable from--ref, and, more seriously, from an index entry'sref: the index is remote JSON fetched before anything is shown, so a compromised index, a hostileNANOCODER_SKILLS_INDEX, or a typosquatted entry executed code before the trust prompt that the whole staged-install flow is built around. Bothfetchcalls now pass--, and a ref beginning with-is refused by name rather than reaching git at all.
Repository locations are now validated too. git clone -- <repo> stops a leading dash being read as a flag but does nothing about git's remote helpers: ext::<command> is a URL, not an option, and it runs that command. Stock git blocks it via protocol.ext.allow=never, so this was not exploitable by default - but that is git's default doing the work for a property this code claims to own, and it is one git config away from being untrue. A repo must now be an https/ssh/git/file URL, a git@host:owner/repo address, or a local path; anything else, including any name::address transport, is refused before the clone. The check runs at the single boundary every path goes through, so the user's own argument, --ref, and index entries are all covered by it.
- Fixed checkpoints capturing no files in a repository with no commits.
getModifiedFilesResultdiffed againstHEAD, which does not exist on an unborn branch, so the whole scan fell to its catch and reported git as unavailable - every file in agit inited workspace looked unmodified, and a checkpoint taken there restored nothing. The scan now attempts theHEADdiff directly and falls back togit diff --name-only --cachedwhen it fails, so a staged tree (git init && git add ., orgit checkout --orphan, which starts with a fully populated index) is captured rather than skipped;--othersalone would have missed all of it, becausegit ls-files --othersexcludes anything already in the index. Attempting the diff rather than probing withgit rev-parse --verify HEADbeforehand keeps the common case, a repository that has commits, at the twogitprocesses the scan always spawned, which matters because it runs on every checkpoint and twice per ACP tool call. This affects checkpoint creation and both timeline scans, which branch on theavailableflag. A genuinegit difffailure still falls through to the existing error handling and reports git as unavailable, unchanged. - The status bar no longer truncates mid-word on an 80-column terminal. It budgeted its segments against the full terminal width while rendering inside a wrapper indented by three columns, so the row overflowed by exactly that much and the terminal cut it ("auto-accept mode o"). The indent is now part of the budget, so a segment is dropped or a name is ellipsised cleanly instead. Closes [#1380].
- Restored the
(default)marker on the/statuspanel's Git line. Adding the git branch to the boot summary removed theisDefaultcase from the sharedformatGitStatusSummaryhelper so the boot summary could render a bare⎇ main, but/statusreads the same helper and silently lost its marker too. The shared helper reports every marker again and the boot summary drops thedefaultone itself, which is the only place the shorter label was wanted. - On a narrow terminal the status row now drops its optional hints (such as "(Shift+Tab to cycle)") before truncating the session name or the active editor file name.
- Fixed the subagent activity card in the ACP (VS Code extension) integration truncating token counts to whole thousands (
Math.floor(tokenCount / 1000)), so 1–999 tokens showed as0kand 1500–1999 showed as1k. Now uses the existingformatCompactTokenCountformatter fromsource/usage/format.ts, matching the precision already used by the per-response usage indicator (e.g.1.9k tokensinstead of1k tokens). Closes [#1133]. - Fixed the conversation summariser's truncation overshooting its character budget. The
... [truncated N chars]notice was appended after slicing to the limit, so every truncated system prompt, tool-call argument, and tool result sent to the summariser ran over budget by the width of that notice. The kept length is now solved for so the notice fits inside the limit, and a budget too small for the notice at all falls back to a plain slice. Thanks to @aashu2006. Closes [#1135]. - Fixed markdown tables with many columns rendering wider than the terminal and breaking their own borders. Columns now shrink to fit, and a table too wide to fit at all is left as markdown. Closes [#1404].
- Fix session selector dismissing on any keypress instead of only Escape when no sessions exist
-
Tool-execution safety fixes:
-
Tool approval: never auto-approve a call whose arguments fail schema validation. The approval prompt now renders together with the validation error, so a malformed call is never executed without explicit consent. (Deliberate trade-off: a malformed approval-required call now costs the user a consent prompt where it previously self-corrected without one — the price of "nothing executes without consent".)
- MCP tools: the executed handler now runs the same lenient schema type-check
the approval prompt shows, so a "wrong type" call is rejected locally before
it reaches the server instead of relying on the remote validator alone. MCP
was the last registry path that bypassed
withValidation. - Custom tools: cap captured stdout and stderr while streaming, so a
large-output tool can't exhaust memory before the final truncation runs.
Each stream gets its own budget via the same collector the built-in bash
executor uses (now shared in
source/utils/stream-collector.ts), so a stdout flood can't silently swallow the stderr explaining why the tool failed. - Custom tools: on timeout, the shell is spawned detached (Unix) and the whole process group is signalled, so a backgrounded descendant can no longer outlive the tool's timeout. The SIGKILL escalation now targets the group and deliberately outlives the shell's own exit, since the shell exiting does not mean its group is empty.
- Long tool output no longer floods the transcript.
write_file,string_replace,diff_edit, and tools without a formatter (such as MCP tools) now show at most 20 lines followed by… (+N more lines · /expand n), the same capexecute_bashoutput already had. When the cap cuts into a diff, the note says how many of the hidden lines are edits (+N more lines, K changed), and awrite_fileoverwrite diff skips long unchanged stretches so an edit deep in a large file is still shown. The new/expand <n>command prints a single tool result in full, including one folded into a compact tally, and/expandon its own lists recent results, so reading one result no longer means switching every result to expanded view with Ctrl+O. Closes [#1289]. - Startup initialization now waits for the directory-trust disclaimer.
useAppInitialization's mount effect ran on the very first render, before the user could answer the trust prompt — React runs every hook regardless of which JSX branch a component ultimately returns, so the<SecurityDisclaimer />early return inApp.tsxnever gated it. An untrusted directory could therefore have itsagents.config.json/.mcp.jsonread, its stdio MCP servers spawned viaTransportFactory.createStdioTransport(), and its provider entries resolved — including a project provider shadowing the global one by name, which forgithub-copilot/chatgpt-codexattaches a live OAuth bearer token to a config-suppliedbaseURL. The gate now lives inside the effect itself and is ref-guarded, so nothing is read or spawned until trust is confirmed and initialization still runs exactly once afterwards.--trust-directoryis unaffected. Closes [#1244]. - Fixed bash and custom-tool output getting garbled when a multi-byte UTF-8 character landed on a chunk boundary. Closes [#1305].
- Vertically center the welcome banner on tall terminals.
The banner sat flush at the top of the viewport on terminals taller than the banner itself, so a maximized window left the content stranded at the top. The banner's outer Box is now sized to the available rows and centers its children, so the block sits in the middle of the viewport at every terminal height while still fitting a standard 80×24 screen.
Also adds regression tests for the boot summary's working-directory label and for the new tagline text.
- Use the same button-based dropdown style for the provider and mode pickers as the model selector in the VS Code composer Configuration popover.
- Added slash command quick actions to the VS Code extension chat panel. Typing
/in the input opens an autocomplete menu listing/test,/explain, and/doc, which insert a human-readable prompt template into the textarea so the user sees and can edit exactly what gets sent, alongside the existing/clearand/copycommands, which complete to their name and run as they always have. The menu only opens on a slash that starts a line, so URLs and paths are left alone. - Added a TUI-style welcome screen to the VS Code extension's chat panel that displays the Nanocoder logo and open-source mission statement. It automatically hides when a session is loaded or messages are sent.
- The welcome screen now fits a standard 80x24 terminal. It sized itself against the whole terminal height, but fullscreen mode clips at the chat viewport — the terminal minus the input footer — so the last menu item and the tip were cut off with no hint anything was missing. The banner is now given the rows it actually has, and drops the block wordmark before the menu and tip when they are tight. Closes [#1330].
write_filenow shows a real diff (added/removed lines) against the file's previous content when overwriting an existing file, matchingstring_replace's colored-diff treatment, in both the confirmation preview and the post-execution result — including auto-accept/yolo mode, where the file is overwritten before the result is ever displayed. Writing a brand-new file still shows the full syntax-highlighted dump. Closes [#1288].
If there are any problems, feedback or thoughts please drop an issue or message us through Discord! Thank you for using Nanocoder.
Contributors
This release shipped thanks to @1cbyc, @2409324124, @24thAbhinav, @89799969, @A-S-Manoj, @aakash-env, @aashu2006, @addyCooks, @aiapienthusiast, @akramcodez, @alexsmolya, @AniketR10, @anisayakmitra-in, @arpan7sarkar, @AryanNandanwar, @awhite0030, @bigattichouse, @ColumbusLabs, @coralsundy, @DeepamJha, @Dhirenderchoudhary, @egmar, @ethanhawkes-gif, @Gambit-Checkmate, @googio, @hafzism, @hiarun02, @itsnevu, @jan-krieg, @kishore280, @laurinMlt, @macjayz, @mikemikimike, @mouse-value-add, @mrinalg297, @mrprohack, @niukanen1, @OllieinCanada, @Piyushrathoree, @puri-adityakumar, @raheebgill29, @RaunaK-cap, @rishu685, @rohanshrma222, @RRXXZZYY, @Rudra2637, @saksham-rathore, @shoryabansalgithub, @Shreya657, @Shreyasnalle, @soumojit-D48, @stag7824, @Swayamcodes, @tisankanj, @tusharui, @vansh2408, @will-lamerton, @yashksaini-coder, @yulinlina, @yunuschoudhurywork-lang and @Zer0codestuff.
First-time contributors: @1cbyc, @24thAbhinav, @89799969, @aakash-env, @aashu2006, @aiapienthusiast, @alexsmolya, @AniketR10, @anisayakmitra-in, @awhite0030, @ColumbusLabs, @coralsundy, @egmar, @ethanhawkes-gif, @Gambit-Checkmate, @googio, @hafzism, @hiarun02, @itsnevu, @jan-krieg, @laurinMlt, @macjayz, @mouse-value-add, @mrinalg297, @mrprohack, @niukanen1, @OllieinCanada, @Piyushrathoree, @puri-adityakumar, @raheebgill29, @RaunaK-cap, @rishu685, @RRXXZZYY, @Rudra2637, @saksham-rathore, @shoryabansalgithub, @Shreya657, @Shreyasnalle, @soumojit-D48, @stag7824, @Swayamcodes, @tisankanj, @tusharui, @vansh2408, @yulinlina, @yunuschoudhurywork-lang and @Zer0codestuff. Welcome, and thank you!
Installation
:::bash
npm install -g @nanocollective/nanocoder
Usage
:::bash
nanocoder
Full Changelog: https://github.com/Nano-Collective/nanocoder/compare/v1.30.0...v1.31.0