* 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>
- config.ts: Replace execSync(`which ${command}`) with spawnSync("which", [command])
to prevent shell injection from malicious lsp.json config files
- client.ts: Wrap JSON.parse in parseMessage with try/catch and handle null messages
in the stream reader to prevent process crashes from malformed LSP output
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tests initialize, hover, go-to-definition, references, document symbols,
diagnostics (type error detection), and clean shutdown against a real
typescript-language-server instance with a temp TypeScript project.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All 10 LSP files ported and adapted. Wired into tools/index.ts.
Remaining work: fix TypeScript compilation errors (see below).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
In RPC mode, `ctx.ui.custom()` returns `undefined as never`, causing
`showInterviewRound` to return undefined and `Object.keys(result.answers)`
to throw TypeError.
When `showInterviewRound` returns undefined (RPC mode), fall back to
sequential `ctx.ui.select()` calls for each question, forwarding the
abort signal (#171) and supporting `allowMultiple` (#165).
- Add `allowMultiple` to `ExtensionUIDialogOptions`
- Widen `select()` return type to `string | string[] | undefined`
- Add `allowMultiple` to RPC select request and `values` array to response
- Update RPC `select()` to forward `allowMultiple` and parse array responses
- Guard existing `ctx.ui.select()` callers against the widened return type
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
After /login, if the current model has no valid API key, auto-switch to
a model from the newly authenticated provider. After /logout, if the
current model belongs to the logged-out provider, auto-switch to a
fallback model from a different provider.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Three locations used lastIndexOf("/") or includes("/") for path
manipulation, which fails on Windows where paths use backslashes.
- auto.ts: writeBlockerPlaceholder directory extraction → dirname()
- interactive-mode.ts: parent directory traversal → path.dirname() loop
- path-utils.ts: non-null assertion on MSYS drive letter access
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On Windows, LLMs convert absolute paths like F:\Projects\.gsd\... to
Unix-style /f/Projects/.gsd/... which Node's path.resolve interprets
as drive-root-relative, creating F:\f\Projects\.gsd\... instead.
Replace all *AbsPath template variables in prompt templates with
relative .gsd/... paths that resolve correctly on all platforms.
Add MSYS path normalization in resolveToCwd as defense-in-depth.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
LLM-generated commands with `> NUL` create undeletable files on Windows
because Git Bash treats NUL as a literal filename. Rewrite NUL redirects
to /dev/null at all three bash spawn sites.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
External packages (pi-rtk, pi-context, pi-agent-browser, etc.) import from
the original @mariozechner/* scope which GSD forked to @gsd/*. Add aliases
in both jiti resolution paths (virtualModules for Bun, getAliases for Node)
so these packages resolve correctly without manual workarounds.
Closes#161
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add .gitignore negation for vendor path
- Restore marked.min.js from pi-mono upstream
- Restore highlight.min.js from pi-mono upstream
Fixes build failure in pi-coding-agent caused by
global vendor/ ignore rule excluding vendored libs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Vendor all 4 Pi packages (tui, ai, agent-core, coding-agent) from
pi-mono v0.57.1 as @gsd/* workspace packages under packages/. This
replaces the compiled npm dependency (@mariozechner/pi-coding-agent)
and patch-package workflow, giving direct source access for
modifications.
- Copy Pi source from pi-mono v0.57.1 into packages/
- Create workspace package.json + tsconfig.json for each package
- Rename ~240 imports from @mariozechner/pi-* to @gsd/pi-*
- Apply existing patches as source edits (setModel persist, VT input)
- Remove @mariozechner/pi-coding-agent dep and patch-package
- Update build pipeline to build packages in dependency order
- Add pi-upstream git remote for future selective syncing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>