Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/eight-seas-matter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@nanocollective/nanocoder': patch
---

Defer ink and @/app loading in the CLI entry point until the interactive TUI branch, so --acp, --plain and auth paths no longer pay the Ink/App module-graph cost at startup.
5 changes: 5 additions & 0 deletions .changeset/feat-vscode-settings-dashboard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nanocollective/nanocoder": minor
---

Added Settings Tab to the VS Code extension webview for configuring providers and behavior directly from the UI.
5 changes: 5 additions & 0 deletions .changeset/fix-cancel-stops-execution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nanocollective/nanocoder": patch
---

Fixed the VS Code extension's stop button leaving a request running. Two holes: `AcpSession.cancel()` aborted the current controller and immediately replaced it with a fresh one, so a cancel that landed before the turn read the signal — the window while the agent is still resolving the prompt's file references — handed the turn an unaborted controller and the stop was lost. The controller is now rotated when a turn begins instead. Separately, the extension never answered the agent's pending permission request when you hit stop: the tool card kept its spinner and Allow/Deny buttons, and because the request stayed on the pending list every later message was refused with "Please approve or deny the pending tool before sending a new message" until the window was reloaded. Stopping (or reconnecting after the agent process restarts) now resolves those requests as cancelled. Thanks to @akramcodez. Closes #864.
11 changes: 11 additions & 0 deletions .changeset/fix-vscode-tool-aggregator-reset.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@nanocollective/nanocoder": patch
---

Fixed new tool calls landing back in an earlier card instead of a fresh one when a thought, reply, edit card, or plan update came in between. Closes #856.

Fixed a manually collapsed tool card re-expanding on its next update.

Reused one footer per agent turn instead of creating one per text segment.

Fixed a turn's copy button sometimes copying a newer turn's text instead of its own.
5 changes: 5 additions & 0 deletions .changeset/loud-plums-invite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nanocollective/nanocoder": patch
---

Setup wizard's config location picker now shows the resolved path next to each option instead of a bare label.
5 changes: 5 additions & 0 deletions .changeset/pretty-clowns-double.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nanocollective/nanocoder": minor
---

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.
5 changes: 5 additions & 0 deletions .changeset/sunset-setup-commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nanocollective/nanocoder": minor
---

Sunset `/setup-providers` and `/setup-mcp` in favour of `/settings`. Both retired names still work for now — they open the matching `/settings` tab with a notice instead of erroring. `/settings` now takes a tab argument (`/settings providers`, `/settings mcp`), MCP has its own settings tab, and provider edits made from settings apply to the running session instead of waiting for the next launch. Selecting a provider or MCP server in settings now opens that entry's edit/delete choice directly, with a separate row for adding a new one.
5 changes: 5 additions & 0 deletions .changeset/vscode-code-lens-actions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nanocollective/nanocoder": minor
---

Added editor code lenses to the VS Code extension: every function, method, constructor and class now carries `Explain Code` and `Generate Tests` links, and clicking one reveals the chat view and sends that symbol - instruction, `file:startLine-endLine` and the source, fenced with the document language - as a prompt. Symbols come from the language server, so no per-language parsing is involved, and the lenses can be turned off with `nanocoder.codeLens`. Long symbols are capped before being inlined, so a lens click on a large class cannot spend a whole context window on one turn. Also fixes a pre-existing hang where sending a message while a tool approval was still pending left the composer spinning forever. Closes #750.
5 changes: 5 additions & 0 deletions .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ jobs:
- name: Run TypeScript type checking
run: pnpm test:types

# The root tsconfig only includes source/**, so the extension's own
# sources and specs are checked by their own project or not at all.
- name: Run TypeScript type checking (VS Code extension)
run: pnpm test:types:vscode

test-format:
name: Format Checks
runs-on: ubuntu-latest
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ agents
# These contain conversation snapshots and should not be committed
**/checkpoints/

# Action timeline before-images (per-session user data)
.nanocoder/timeline/

# Heap snapshots
.heap-snapshots

Expand Down
Binary file modified assets/nanocoder-vscode.vsix
Binary file not shown.
2 changes: 1 addition & 1 deletion badges/coverage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion badges/forks.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion badges/npm-downloads-monthly.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion badges/repo-size.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
50 changes: 34 additions & 16 deletions benchmarks/measure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,34 +122,30 @@ function measureModuleCount(args: string[]): number {
}

/**
* Boots the CLI in interactive mode under the counting loader, waits until
* the module-resolution count has been stable for `stableMs`, then kills the
* process and returns the final count.
* Boots the CLI under the counting loader, waits until the module-resolution
* count has been stable for `stableMs`, then kills the process and returns
* the final count plus approximate wall-clock boot time.
*
* This measures the full app graph (Ink, hooks, tool manager, command
* registry, MCP loader, etc.) rather than just the `--help` fast path.
* This measures the full resolved graph for the given args (interactive TUI,
* ACP server, etc.) rather than just a oneshot `--help` fast path.
* Polling-until-stable keeps the result deterministic regardless of machine
* speed: we wait for steady state, not a fixed wall-clock window.
*/
/**
* Same polling-until-stable pattern as `measureInteractiveModuleCount`, but
* returns both the module count AND the wall-clock time (in ms) from process
* spawn to the moment the module count reached its final value.
*
* The wall-clock number is labelled **approximate** because it's
* machine-dependent and subject to I/O / scheduler noise. It's not a
* deterministic metric like module count — it's a sanity check that
* optimizations translate into real boot-time savings on the local machine.
*/
async function measureInteractiveStartup(
async function measureStartupUntilStable(
args: string[] = [],
label = 'interactive',
): Promise<{moduleCount: number; bootMs: number}> {
const loaderPath = path.join(__dirname, 'count-loader.mjs');
const loaderUrl = new URL(`file://${loaderPath}`).href;
const countFile = path.join(
repoRoot,
'benchmarks',
`.module-count-interactive-${process.pid}-${Date.now()}`,
`.module-count-${label}-${process.pid}-${Date.now()}`,
);

const child = spawn(
Expand Down Expand Up @@ -356,7 +352,7 @@ async function runWithUrlCapture(
CI: '1',
// Keep the user's global MCP config out of the graph
// so the explain breakdown only reflects nanocoder's own
// boot cost. Matches `measureInteractiveStartup`.
// boot cost. Matches `measureStartupUntilStable`.
NODE_ENV: 'test',
},
},
Expand Down Expand Up @@ -709,13 +705,23 @@ export async function measure(): Promise<Measurements> {
// clock boot number; the module count is deterministic so we only keep
// the first one.
const interactiveRuns = [
await measureInteractiveStartup(),
await measureInteractiveStartup(),
await measureInteractiveStartup(),
await measureStartupUntilStable([], 'interactive'),
await measureStartupUntilStable([], 'interactive'),
await measureStartupUntilStable([], 'interactive'),
];
const interactiveModuleCount = interactiveRuns[0].moduleCount;
const bootSamples = interactiveRuns.map(r => r.bootMs).sort((a, b) => a - b);
const interactiveBootMsMedian = bootSamples[1];
// ACP server is long-lived (JSON-RPC over stdin/stdout). Poll until the
// module graph is stable, then terminate — same pattern as interactive.
const acpRuns = [
await measureStartupUntilStable(['--acp'], 'acp'),
await measureStartupUntilStable(['--acp'], 'acp'),
await measureStartupUntilStable(['--acp'], 'acp'),
];
const acpModuleCount = acpRuns[0].moduleCount;
const acpBootSamples = acpRuns.map(r => r.bootMs).sort((a, b) => a - b);
const acpBootMsMedian = acpBootSamples[1];
// Time from spawn to first stdout byte — what the user actually perceives.
const firstRenderMs = await measureTimeToFirstRender();
const distStats = measureDir(path.join(repoRoot, 'dist'));
Expand Down Expand Up @@ -810,6 +816,18 @@ export async function measure(): Promise<Measurements> {
value: interactiveBootMsMedian,
warnOnDecrease: false,
},
// ACP server steady-state module graph (no Ink / App). Deterministic
// proxy for editor-integration startup cost.
acp_module_count: {
kind: 'numeric',
value: acpModuleCount,
warnOnDecrease: false,
},
acp_boot_ms_approx: {
kind: 'numeric',
value: acpBootMsMedian,
warnOnDecrease: false,
},
// Time from spawn to first stdout byte — what the user sees.
// Measured without the ESM loader so there's no instrumentation
// overhead. Median of 3 runs.
Expand Down
Loading
Loading