Adds a new bundled extension that proactively checks and refreshes AWS
credentials for Bedrock model users.
Startup (session_start):
- Runs 'aws sts get-caller-identity' with the profile extracted from
the configured awsAuthRefresh command
- If credentials are expired, runs the refresh command (e.g. aws sso login)
before the user sends their first prompt
- Shows 'AWS Bedrock login confirmed ✓' when credentials are valid
Mid-session (before_provider_request):
- Re-verifies credentials every 15 minutes before Bedrock API calls
- Catches credential expiry during long sessions without needing retry logic
Zero changes to base files — the entire feature is a single extension file.
Only activates when awsAuthRefresh is set in settings.json and the current
model uses bedrock-converse-stream.
On Windows, executables like npx, tsc, and typescript-language-server
are .cmd batch scripts. Node.js's spawn() can't find them without
shell: true because it looks for exact binary names, not .cmd wrappers.
This caused ENOENT crashes during auto-mode when the LSP tried to
spawn npx tsc --noEmit for TypeScript diagnostics.
Added shell: true conditional on process.platform === 'win32' in the
LSP client's spawn call. Unix platforms are unaffected.
Fixes#1222
* refactor: replace MCPorter CLI with native MCP client using @modelcontextprotocol/sdk
MCPorter is a third-party global CLI that fails to install on many systems,
producing error noise on every startup. Replace it with a native extension
that uses the already-bundled @modelcontextprotocol/sdk Client class directly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: update README extension table from MCPorter to MCP Client
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add .js suffix to MCP SDK subpath imports for NodeNext resolution
The SDK wildcard export (./*) requires .js suffix for TypeScript NodeNext
module resolution. Also add .js-suffixed virtual module keys so jiti
resolves them correctly in compiled Bun binaries.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements the directory blacklist feature from #660 (incomplete items 3-4).
Users can now configure directories to exclude from the @ file picker
and fuzzy search via settings.json:
{ "searchExcludeDirs": ["node_modules", ".git", "dist", "build"] }
Changes:
- settings-manager.ts: added searchExcludeDirs setting with get/set
- autocomplete.ts (pi-tui): CombinedAutocompleteProvider accepts
excludeDirs option, filters excluded directory names in both
readdir-based and native fuzzy search paths
- interactive-mode.ts: passes searchExcludeDirs to the provider
The native fd fuzzy search already respects .gitignore. This setting
covers directories that aren't gitignored but shouldn't appear in
autocomplete (e.g., large vendor dirs, build outputs in projects
without comprehensive .gitignore).
Fixes#1190
When buildSystemPrompt() receives a customPrompt (as GSD's contract
provides), it returned early without appending promptGuidelines from
extension-registered tools. The tool definitions still reached the
API's tools parameter, but without prompt guidance the model didn't
know when to prefer them — causing subagent tools to be silently
ignored in favor of async_bash/bg_shell.
Added promptGuidelines append after date/time in the customPrompt
path, matching the behavior of the non-custom path.
Fixes#1184
Node.js's cpSync fails on Windows when the path contains non-ASCII
characters (e.g. C:\Users\Görloff) due to the \\?\ extended-length path
prefix not handling Unicode correctly. This affects both the build
script (copy-assets.cjs) and the runtime resource sync (resource-loader.ts).
Added a try/catch fallback: when cpSync throws, fall back to a manual
recursive copy using copyFileSync which handles non-ASCII paths correctly.
Changed files:
- src/resource-loader.ts: syncResourceDir() catches cpSync failure and
falls back to copyDirRecursive()
- packages/pi-coding-agent/scripts/copy-assets.cjs: all cpSync calls
wrapped in safeCpSync() with the same fallback
Fixes#1178
* Initial plan
* fix: add text-based fallbacks for RPC mode where TUI widgets produce empty turns
- rpc-mode.ts: Emit placeholder widget event instead of silently dropping factory-based setWidget calls
- commands.ts: handleStatus() falls back to text-based status summary when custom() returns undefined
- commands.ts: handleVisualize() notifies that TUI is required when custom() returns undefined
- auto-dashboard.ts: updateProgressWidget() emits string-array fallback before factory widget
- queue-reorder-ui.ts: showQueueReorder() notifies with current order when custom() returns undefined
- index.ts: Dashboard shortcut handler falls back to text status in RPC mode
Co-authored-by: glittercowboy <186001655+glittercowboy@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: glittercowboy <186001655+glittercowboy@users.noreply.github.com>
* docs: add Node LTS pinning guide for macOS Homebrew users
New doc (docs/node-lts-macos.md) explains how to pin Node 24 LTS
via Homebrew to avoid running on odd-numbered development releases.
Covers brew install/link/pin, version managers as alternatives,
and verification steps.
Added notice banner in README linking to the guide.
* fix: improve LSP diagnostics when no servers detected (#1082)
When lsp status returns 'No language servers configured', the output
now includes diagnostics:
- Which project markers were detected (e.g. package.json found)
- Which server commands are missing (e.g. typescript-language-server)
- Install instructions
Also added LSP troubleshooting section to docs/troubleshooting.md
with common install commands per language.
Extracts 11 hardcoded timeout, retry, compaction, and tool-default
values from 9 source files into a single constants.ts module. Each
source file now imports from the central definition, eliminating
duplicated literals and making tuning a single-file change.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add missing \u1680 (Ogham space mark) to UNICODE_SPACES in path-utils.ts
and loader.ts. Make edit-diff.ts import the shared constant from
path-utils.ts instead of maintaining an inline copy.
Rename hashlineParseText to parseHashlineText to follow the parseX()
convention used across the codebase.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cap parallel async operations to prevent memory spikes when processing
large numbers of items:
- session-manager.ts: limit file loading to 10 concurrent reads
- pipeline.ts: limit job execution to 5 concurrent LLM calls
- discovery.ts: limit tool scanning to 5 concurrent scanners
Uses an inline pLimit utility in each file to avoid adding a dependency.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add argument completions for /thinking command with all 6 levels
(off, minimal, low, medium, high, xhigh) and descriptions
- Add descriptions to all GSD 2nd-level subcommand completions across
14 subcommand groups (auto, mode, parallel, setup, prefs, remote,
next, history, undo, export, cleanup, knowledge, doctor, dispatch)
- Add 35 new tests for autocomplete and fuzzy matching systems
Adds a new setting 'respectGitignoreInPicker' (default: true) that
controls whether the @ file picker respects .gitignore when listing
files. When set to false, gitignored files appear in fuzzy search
results.
Wired through:
- CombinedAutocompleteProvider: new constructor option + setter
- SettingsManager: getter/setter with persistence
- Settings selector UI: toggle in settings panel
- InteractiveMode: reads setting at init, updates provider on change
* feat(ux): group model list by provider in /gsd prefs wizard
The model selection in configureModels() was a single flat alphabetical
list that became unwieldy with many providers. This groups models under
provider headers (e.g. "─── anthropic (24) ───") so users can quickly
scan to the right provider section.
Changes:
- ExtensionSelectorComponent: add SEPARATOR_PREFIX convention for
non-selectable group headers. Navigation skips separators, Enter
ignores them, and they render with borderAccent styling.
- configureModels: build grouped option list with provider headers
instead of flat map.
- New test: extension-selector-separator.test.ts (6 tests).
* fix(ci): use node:test instead of vitest in extension-selector test
tsconfig.extensions.json does not include vitest type declarations,
causing TS2307 in CI. Rewrite test to use node:test + node:assert
to match the convention used by other extension tests.
* fix(ci): rewrite separator test to avoid TS parameter property issue
The extension-selector component transitively imports countdown-timer.ts
which uses TypeScript parameter properties (private tui: TUI). Node's
--experimental-strip-types cannot handle these, causing ERR_UNSUPPORTED_
TYPESCRIPT_SYNTAX in CI.
Rewrite the test to verify the separator detection logic and model
grouping contract without importing the component. Duplicates the
isSeparator/nextSelectable helpers and tests them directly.
* fix: prevent data loss on crash with atomic writes, file locking, and error handling
Wave 1 of failure recovery safeguards:
1. Atomic session file rewrites (tmp+rename) — _rewriteFile() and forkFrom()
now use atomicWriteFileSync to prevent session file corruption on crash
2. Atomic auto.lock writes — crash-recovery.ts writeLock() uses tmp+rename
so the crash detection system itself can't be corrupted
3. unhandledRejection handler — catches silent process death from unhandled
promise rejections in OAuth, extensions, LSP, or MCP connections
4. try/catch in emitToolCall — matches pattern used by emitUserBash,
emitContext, and emitToolResult to prevent extension handler crashes
from killing the entire agent turn
5. File locking on session appends — prevents concurrent pi instances from
interleaving partial JSON lines in session JSONL files using the same
proper-lockfile pattern established in auth-storage.ts and settings-manager.ts
* fix: add OAuth timeouts, RPC exit detection, and command context guards
Wave 2 of failure recovery safeguards:
1. OAuth fetch timeouts — all fetch() calls across all OAuth providers
(Anthropic, OpenAI Codex, Google Antigravity, Google Gemini CLI,
GitHub Copilot) now have 30-second AbortSignal.timeout() to prevent
indefinite hangs when OAuth servers are unresponsive
2. RPC subprocess exit detection — pending requests are now rejected
when the agent subprocess exits unexpectedly, preventing indefinite
hangs in the RPC client
3. Extension command context guards — default handlers for newSession,
fork, navigateTree, switchSession, and reload now throw explicit
errors instead of silently returning success when called before
bindCommandContext()
4. OAuth error detail preservation — token refresh errors now preserve
the original error as `cause` for better diagnostics
* fix: resource cleanup, LSP retry, and crash detection on session resume
Wave 3 of failure recovery safeguards:
1. Atomic completed-units.json cleanup — milestone completion writes
now use tmp+rename pattern for consistency with auto-recovery.ts
2. Bash temp file cleanup — track temp files created for large output
and register a process exit handler to clean them up
3. Settings write queue flush on shutdown — call settingsManager.flush()
during interactive mode shutdown so queued writes aren't lost
4. LSP initialization retry — wrap getOrCreateClient with up to 2 retries
with exponential backoff (1s, 2s) for transient spawn failures
5. Crash detection on session resume — wasInterrupted() checks if last
assistant turn had tool calls without results, shows warning on resume
* fix: blob garbage collection and LSP debug logging
Wave 4 of failure recovery safeguards:
1. Blob garbage collection — BlobStore.gc(referencedHashes) removes
orphaned blobs not referenced by any session file, plus totalSize()
for monitoring blob directory growth
2. LSP JSON parse error logging — malformed LSP messages are now logged
at debug level (when DEBUG env is set) instead of being silently dropped
Two fixes:
1. lsp/config.ts: Use `where.exe` instead of `which` on Windows.
MSYS's `which` returns POSIX paths (/c/Users/...) that Node's
spawn() can't execute. `where.exe` returns native Windows paths.
2. lsp/client.ts: Handle spawn ENOENT error gracefully. When the LSP
server binary doesn't exist, the error event now triggers a clean
exit instead of bubbling up and crashing auto-mode.
On Windows, process.cwd() returns backslash paths (C:\Users\name\...).
When these paths are injected into system prompts, worktree context
blocks, or tool results, the model copies them into bash commands.
Bash interprets backslashes as escape characters, silently stripping
them — producing invalid paths like 'C:Usersnamedevelopmentapp-name'.
This is not a regex hack — it's a proper cross-platform boundary:
- Filesystem operations (fs, path.join, spawn cwd) use native paths
unchanged. Node handles both separators correctly for I/O.
- LLM-visible text (prompts, tool results, extension messages) uses
toPosixPath() to normalize to forward slashes. C:/Users/name/...
is valid in Git Bash, WSL bash, PowerShell, and Node.js.
Changes:
- utils/path-display.ts: New toPosixPath() utility in pi-coding-agent
package (for system prompt) and shared extension module (for
extensions that can't import from the compiled package at dev time)
- system-prompt.ts: Normalize resolvedCwd before injecting into the
'Current working directory' line
- gsd/index.ts: Normalize all process.cwd() and originalBase paths in
worktree context blocks injected into the system prompt
- bg-shell/index.ts: Normalize cwd in tool result text (start, env
actions) that the model reads and may reference in commands
- path-display.test.ts: 9 regression tests covering toPosixPath
behavior and system prompt output verification. Includes a scanner
that fails if any Windows absolute paths with backslashes appear in
buildSystemPrompt() output.
Audit scope: Checked all process.cwd() usage across pi-coding-agent
and all bundled extensions. Filesystem-only paths (join, readFile,
spawn cwd, existsSync) are correct and left unchanged. Only paths
entering LLM text are normalized.
OAuthSelectorComponent calls its onSelect callback synchronously (no
await), but the callback was async — calling showLoginDialog which
throws 'Login cancelled' on Escape. The unhandled rejection bubbled
up to the uncaughtException handler and crashed GSD.
Wrap the async work in a named function with .catch() so cancellation
errors are swallowed gracefully. showLoginDialog already handles its
own error display internally.
* feat: render native web search tool calls in TUI
The Anthropic streaming parser silently dropped server_tool_use and
web_search_tool_result content blocks, making native web search
invisible. Add ServerToolUseContent and WebSearchResultContent types,
handle both block types in the streaming parser and conversation replay,
and render them as ToolExecutionComponent in the interactive TUI.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add PREFER_BRAVE_SEARCH env var to bypass native web search
Set PREFER_BRAVE_SEARCH=1 to keep Brave/custom search tools active
on Anthropic models instead of injecting native server-side web search.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: skip non-toolCall blocks in Mistral provider conversation replay
The ServerToolUseContent and WebSearchResultContent types added for
native web search don't have id/name/arguments properties, causing
TypeScript errors when the Mistral provider tried to push them as
tool calls.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Anthropic rate limit reset windows are typically 60-120s. The previous 60s
default, combined with the +1s buffer in extractRetryAfterMs(), meant that
virtually all rate limit retries were immediately abandoned.
300s (5 min) covers the vast majority of rate limit windows and lets the
built-in retry logic work as intended.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: when the LLM runs `cmd &`, bash forks the process and
exits immediately. The forked process inherits Node's piped stdout/
stderr FDs. Node.js waits for all holders of those FDs to close before
firing the 'close' event — so the tool hangs until the background
process exits (which for a server is never).
Fix: add rewriteBackgroundCommand() in bash.ts. Before exec, detect
commands with a trailing & background operator and inject
>/dev/null 2>&1 before the & when stdout is not already redirected.
This severs the pipe inheritance so Node gets 'close' immediately
when the shell exits.
Guards:
- Commands already redirecting stdout (>, >>, &>, |) are not rewritten
- && (logical AND) is not affected
- & inside single-quoted strings is not affected
- A brief onUpdate advisory is surfaced when rewrite happens so the
LLM knows to prefer nohup/setsid for robust detachment
Export rewriteBackgroundCommand from pi-coding-agent for testability.
Tests: bash-background.test.ts — 12 cases covering no-op paths,
rewrite paths, compound commands, and already-safe nohup patterns.
Closes#733
1. Windows: `start` command opens CMD instead of browser during GitHub
Copilot login — pass empty title arg so URL is treated as target
2. Launch banner missing Tavily provider in web search status display
3. MCPorter auto-installs via npm when not found (like ripgrep auto-download)
4. Notification prefs showing [object Object] — guard against non-boolean values
Closes#663
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix loadStoredEnvKeys divergent provider lists: add telegram_bot and
custom-openai to wizard.ts (the canonical copy used by CLI), remove
dead duplicate from onboarding.ts
- Security: add SAFE_COMMAND_PREFIXES allowlist to resolveConfigValue
to prevent arbitrary RCE via settings.json shell commands
- Security: add TOFU (Trust On First Use) model for project-local
extensions — skip untrusted .pi/extensions/ with stderr warning
- Performance: debounce sql.js MemoryStorage persistence (500ms window)
so rapid mutations coalesce into a single db.export()+writeFileSync
- Fix double lstatSync call in tool-bootstrap.ts isRegularFile
- Add 26 new tests covering all changes
Two fixes for the model configuration bleeding between simultaneous
GSD instances that share the same global settings.json.
## Root Cause
1. `setDefaultModelAndProvider()` always persisted to `~/.gsd/agent/settings.json`
(global), so when either instance's interactive mode changed models (via
Ctrl+P or /model), it overwrote the other instance's saved default.
2. When auto-mode dispatched a new unit (after context wipe), if no
per-unit-type model preference was configured, the session picked up
the default from the now-contaminated global settings file.
## Fix 1: Project-scoped model persistence (settings-manager.ts)
`setDefaultModelAndProvider()`, `setDefaultModel()`, and `setDefaultProvider()`
now persist to project-level settings (`.pi/settings.json`) when a project
settings file exists, falling back to global only when no project context
is available. This prevents concurrent instances from overwriting each
other's model choice.
Added `hasProjectSettingsFile()` helper to detect project context.
## Fix 2: Auto-mode model capture (auto.ts)
Captures the session's model at auto-mode start (`autoModeStartModel`).
At each unit dispatch, if no model preference is configured for the unit
type, the captured model is re-applied with `persist: false`. This
ensures each auto-mode session maintains its own model regardless of
what other instances write to the shared settings file.
## Tests
3 new tests covering:
- Project settings file isolates model from global
- Two projects have independent model configs
- autoModeStartModel concept prevents model drift
All 448 existing tests pass.
Fixes#650
Tools (write, read, edit, bash) capture cwd at creation time via
createWriteTool(cwd), createReadTool(cwd), etc. When auto-mode
enters a worktree, process.cwd() changes but tools were not
recreated — they continued resolving relative paths against the
original project root.
This caused artifacts to be written to the main project's .gsd/
directory instead of the worktree's .gsd/ directory. The dispatcher
then couldn't find the artifact at the expected worktree path and
retried the unit indefinitely.
Fix: detect cwd change in newSession() and call _buildRuntime()
to recreate tools with the updated cwd. This is a targeted rebuild
that only fires when cwd actually changed (typically once per
auto-mode session when entering/exiting a worktree).
Fixes#633
LSP was never activated in interactive sessions because the default
active tools list hardcoded only read/bash/edit/write. This adds lsp
to that list and ships four new capabilities alongside edit sync and
stronger prompt guidance.
- Add "lsp" to default active tools in agent-session.ts
- New actions: incoming_calls, outgoing_calls, format, signature
- Wire edit/write tools to notify LSP clients on file changes
- Strengthen system prompt and GSD prompt with full LSP operation catalog
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: allow extensions to use 'yaml' and rework frontmatter parsing
* feat: add run-hook command for manual hook execution
* fix: sanitize slashes in unitType for runtime file paths