Replace existsSync collision loop with atomic O_CREAT|O_EXCL file
creation, hoist regex to module-level constant, and memoize
getPackageDir() to avoid repeated directory walks.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When auto-mode creates a worktree and chdir's into it, the Node process
cwd changes but AgentSession._cwd stays frozen at the original path.
Every newSession() builds a system prompt telling the LLM "Current
working directory: /original/path", so the LLM cd's back there and
writes files to the wrong location.
Update _cwd = process.cwd() at the start of newSession() so the system
prompt reflects the actual working directory after chdir.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: add startup performance analysis and optimization plan
Profiled GSD CLI startup finding 2.2s for --version and ~3.8s for
interactive mode. Identified 5 root causes with measured timings and
created a phased optimization plan targeting <0.2s for --version
and ~0.8s for interactive startup.
* perf: speed up GSD startup with lazy loading and fast paths
- Fast-path --version/-v and --help/-h in loader.ts before importing
any heavy dependencies (2.2s → 0.15s, 14x faster)
- Lazy-load undici (~200ms) only when HTTP_PROXY env vars are set
- Skip initResources cpSync when managed-resources.json version
matches current GSD version (~128ms saved per launch)
- Lazy-load Mistral SDK (~369ms) on first API call instead of startup
- Lazy-load Google GenAI SDK (~186ms) on first API call instead of
startup
- Parallelize extension loading with Promise.all() instead of
sequential for-loop
---------
Co-authored-by: TÂCHES <afromanguy@me.com>
The Input component had no placeholder text support — when empty, it
showed only "> " with a blinking cursor and no hint of expected input.
The ExtensionInputComponent received a placeholder parameter but
discarded it (_placeholder with underscore = intentionally unused).
Fix: Input now has a public placeholder property. When value is empty,
renders the placeholder in dim text. ExtensionInputComponent passes
the placeholder through to Input.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Split skill diagnostics into [Skill conflicts] (actual collisions) and
[Skill issues] (validation warnings like missing description) so users
aren't misled by the label. Add wizard hint to /gsd prefs output.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GitHub Copilot users with Claude models got 400 errors because the native
Anthropic web_search_20250305 tool was injected into requests to Copilot's
API proxy, which doesn't support it. The root cause was that model_select
never fires before the first API request on new sessions, so the fallback
heuristic (model name starts with "claude-") couldn't distinguish direct
Anthropic from proxied providers.
Fix: pass the resolved Model object through to the before_provider_request
event so extensions can check model.provider directly instead of relying on
model name heuristics.
Moves extension tool_call/tool_result interception from wrapToolsWithExtensions
(which fires inside the agent loop, bypassing event settlement) to
beforeToolCall/afterToolCall hooks that await _agentEventQueue. This ensures
extensions always see settled state — including the appended assistant message —
even when tools execute in parallel.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add Ollama Cloud (ollama.com) as a built-in provider with both model
hosting and web search/fetch capabilities.
Model provider:
- 13 curated models via OpenAI-compatible API (Llama 3.1, Qwen 3,
DeepSeek R1, Gemma 3, Mistral, Phi-4, GPT-OSS)
- Auth via OLLAMA_API_KEY environment variable
- Registered in onboarding, env hydration, and model resolver
Web tool provider:
- Search via POST ollama.com/api/web_search
- Page fetch via POST ollama.com/api/web_fetch (fallback after Jina)
- Added as third search provider option alongside Tavily and Brave
- /search-provider command updated with ollama option
Closes#430
When all credentials for a provider are exhausted, the system now
automatically falls back to the next available provider in a
user-configured fallback chain. Higher-priority providers are
restored automatically when their backoff expires.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: normalize .ts import extensions to .js for Node 22.22+ compatibility
Node 22.22.0's --experimental-strip-types handles .ts import specifiers
differently, causing ERR_INVALID_TYPESCRIPT_SYNTAX in CI. The project
convention uses .js specifiers with a custom resolve hook that rewrites
them to .ts at test time.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: replace better-sqlite3 with sql.js to eliminate native compilation failures
better-sqlite3 requires prebuilt binaries or node-gyp compilation, which
fails on newer Node versions (e.g. 25.x) that lack prebuilds. sql.js uses
WASM-compiled SQLite with zero native dependencies, making installation
reliable across all platforms and Node versions.
Closes#355
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
On macOS the Alt key is the Option key (⌥), but all UI hints showed
"Alt+". Added formatKeyForDisplay() utility that converts alt+ to ⌥
on darwin, applied it in formatKeys() for dynamic keybinding hints,
and updated hardcoded strings in tree-selector, models-selector,
settings-selector, auto-mode dashboard, and extension shortcut display.
Hashline prefixes (e.g. "1#BQ:") were leaking into the TUI display
for file reads, showing as weird characters to users. Strip them
before rendering since they're only meant for model consumption.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Anthropic's 429 responses include retry-after and x-ratelimit-reset-*
headers that tell us exactly when to retry. Previously we ignored these
and used exponential backoff (2s, 4s, 8s), which is both wrong and
misleading in the UI countdown.
- Add retryAfterMs to AssistantMessage as the structured carrier
- Extract retry-after / x-ratelimit-reset-requests / x-ratelimit-reset-tokens
from Anthropic SDK APIError.headers in the provider catch block
- Session uses retryAfterMs when present (capped by maxDelayMs=60s),
falls back to exponential backoff for errors with no timing hint
The UI countdown now shows the actual Anthropic reset time. No UI changes needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: replace pure-JS xxHash32 with native Rust implementation via napi
The hashline edit tool calls xxHash32 on every line of every file read/edit.
Moving this to a native Rust implementation (xxhash-rust crate) eliminates
JS overhead for this hot path. Hash output is identical -- verified by tests
comparing native vs JS reference across 11 input vectors including empty
strings, short/long inputs, unicode, and seeded variants.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: use typed native interface and remove version-drag comment in xxhash wrapper
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the multi-pass JS pipeline (TextDecoder → stripAnsi → sanitizeBinaryOutput)
in bash-executor.ts with a single native Rust call that handles UTF-8 decoding,
ANSI stripping, binary sanitization, and CR removal in one pass.
Key features:
- StreamState tracks incomplete UTF-8 and ANSI sequences across chunk boundaries
- Standalone stripAnsiNative() and sanitizeBinaryOutputNative() for use elsewhere
- Comprehensive test coverage for split multibyte, split ANSI, binary data
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Add the 1M context variant of Claude Opus 4.6 to the model registry
and fix model resolver to try exact match before glob detection, so
model IDs containing bracket characters (like [1m]) are not
misinterpreted as glob patterns.
* feat: add task isolation for subagent filesystem safety
Subagents can run in isolated git worktrees (or FUSE overlays on Linux)
so concurrent tasks don't stomp on each other's files. Changes are
captured as unified diffs and merged back via git apply.
- New isolation.ts module with worktree and FUSE overlay backends
- TaskIsolationSettings in settings-manager (mode + merge strategy)
- isolated parameter on the subagent tool schema
- Baseline capture/apply mirrors the parent repo's dirty state
- Process exit handler for best-effort cleanup of stale worktrees
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: correct delta capture to exclude parent baseline state
The worktree backend now commits a baseline snapshot after applying the
parent's dirty state, so captureDeltaPatch diffs only the subagent's
actual changes against the post-baseline HEAD (not the original HEAD).
The FUSE overlay backend tracks the parent's dirty file set at mount
time and filters the upper dir during delta capture to exclude inherited
dirty files.
Also removes dead code: findGitRoot (unused), readIsolationMergeStrategy
(exported but never called).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The async-jobs PR (#260) accidentally dropped `bashInterceptor` from the
Settings interface and the getBashInterceptorEnabled/getBashInterceptorRules
methods from SettingsManager, breaking the TypeScript build on main.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix cat rule to exclude heredoc syntax (cat <<EOF) via negative lookahead
- Fix write rule: exclude >> append and digit-prefixed fd redirects (2>)
using lookbehind (?<![|>\d])>(?!>)
- Add compileInterceptor() — pre-compiles rules once at construction time
instead of on every bash call; export CompiledInterceptor type
- Update createBashTool to use pre-compiled interceptor instance
- Add 33 unit tests covering all rules, edge cases, and pass-throughs
Regex-based pre-execution check in the bash tool blocks shell commands
(grep, cat, sed -i, etc.) when the dedicated replacement tool is available
in the session. Configurable via bashInterceptor settings.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Support multiple API keys per provider with automatic rotation:
- AuthStorageData accepts single credential or array per provider
- Round-robin selection across credentials (no sessionId)
- Session-sticky hashing when sessionId is provided
- Credential backoff on rate limits (30s), quota exhaustion (30min),
server errors (20s)
- markUsageLimitReached() backs off failing credential and returns
whether an alternate is available
- Login accumulation: duplicate provider logins append API keys
instead of replacing
- Agent retry handler tries credential fallback before counting
against retry budget (immediate retry, no delay)
- All getApiKey call sites thread sessionId for sticky selection
Backward compatible: single credentials work unchanged.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move the edit tool's hot-path diffing operations from JS to native Rust:
- `normalizeForFuzzyMatch`: single-pass Unicode normalization (smart quotes,
dashes, special spaces, trailing whitespace)
- `fuzzyFindText`: exact-then-fuzzy substring search with UTF-16 index
conversion for JS compatibility
- `generateDiff`: unified diff generation using the `similar` crate
(Myers' algorithm with optimizations)
The Rust module at `native/crates/engine/src/diff.rs` exposes three napi
functions. The TypeScript wrapper at `packages/native/src/diff/` follows
the existing module pattern. `edit-diff.ts` now delegates to native
implementations while keeping line-ending handling and file I/O in JS.
18 tests covering normalization, fuzzy matching (including UTF-16 index
correctness with emoji/surrogate pairs), and diff generation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace manual binary header parsing (PNG/JPEG/GIF/WebP) in terminal-image.ts
with the native @gsd/native/image module, and replace photon-node (WASM) with
native N-API calls for image resize and format conversion.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The autocomplete file search no longer spawns the external `fd` binary via
spawnSync. It calls the in-process Rust fuzzyFind() function from @gsd/native,
which handles directory walking, gitignore, hidden files, and fuzzy scoring
in a single native call. The fdPath constructor parameter and ensureTool("fd")
download are removed since the binary is no longer needed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use the Rust-backed arboard clipboard (via @gsd/native/clipboard) for
text copy and image read, replacing the platform-tool shelling
(pbcopy/xclip/xsel) and @mariozechner/clipboard optional dependency.
OSC 52 is preserved as a fallback for SSH/mosh sessions. Linux Wayland
still falls back to wl-paste/xclip for image reads since arboard may
lack compositor access from a terminal.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Switch syntax highlighting from the cli-highlight npm package to the
@gsd/native Rust-based highlight module (syntect). The native module
accepts raw ANSI escape sequences via the HighlightColors interface,
eliminating the wrapper-function indirection of the old CliHighlightTheme.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The find tool's default path spawned `fd` and used the `glob` npm package
to discover nested .gitignore files. The native @gsd/native glob module
handles gitignore traversal natively via Rust's `ignore` crate, making
both dependencies unnecessary for this code path.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Check if the destination file exists before performing a move in
hashline-edit. If it does, return an error instead of silently
overwriting the file.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The delete operation in hashline-edit.ts wrapped both access() and
unlink() in a single try/catch. If access succeeded but unlink failed
(e.g., permissions), the error was silently swallowed and "Deleted" was
falsely reported. Now access and unlink have separate error handling:
access failures indicate the file doesn't exist, while unlink failures
propagate to the caller.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implement hashline edit mode inspired by Oh My Pi's approach. Each line
in a file is identified by a content hash (xxHash32, 2-char nibble
alphabet), enabling the model to reference lines by stable LINE#ID tags
instead of reproducing full line text. This eliminates the most common
edit failure mode (slightly misquoted original text) and reduces output
tokens.
New files:
- hashline.ts: core hash computation, formatting, parsing, validation,
and edit application engine (pure JS xxHash32, no native deps)
- hashline-edit.ts: AgentTool wrapper for hash-anchored file edits
- hashline-read.ts: read tool variant that outputs LINE#ID:CONTENT format
- hashline.test.ts: 54 tests covering all core operations
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>