- Extract emitMessagePair() to consolidate 6 message_start/message_end push pairs in agent-loop.ts
- Extract emitErrorSequence() to deduplicate identical catch blocks in agentLoop and agentLoopContinue
- Export ZERO_USAGE constant and reuse it in agent.ts instead of inline object literals
- Merge identical message_start/message_update switch cases in Agent._runLoop
- Extract Agent._updatePendingToolCalls() to consolidate tool_execution_start/end Set mutation
Replace 30+ repetitive setter method bodies with four generic private
helpers: setGlobalSetting, setScopedSetting, setNestedGlobalSetting,
and setProjectSetting. Each setter method retains its original public
signature and behavior — only the internal implementation is consolidated.
Methods with custom logic (setEditorPaddingX, setAutocompleteMaxVisible,
setSearchExcludeDirs, setFallbackChain, removeFallbackChain,
setDefaultModelAndProvider) are left unchanged or minimally adapted.
Net reduction: 79 lines (164 deleted, 85 added).
- Delete theme-schema.json (335 lines): redundant with the TypeBox
schema already defined in theme.ts, only referenced via $schema URLs
in the JSON files for editor autocomplete.
- Delete dark.json (85 lines) and light.json (84 lines): move built-in
theme definitions into themes.ts as TypeScript objects, eliminating
runtime filesystem reads and the getThemesDir() dependency.
- Export ThemeJson type from theme.ts so themes.ts can reference it.
- Net reduction: ~319 lines removed.
Move overlay positioning (resolveOverlayLayout, resolveAnchorRow/Col),
line compositing (compositeLineAt, compositeOverlays, applyLineResets),
cursor extraction, and size parsing into overlay-layout.ts. These are
pure functions with no TUI state dependencies, reducing tui.ts from
1,200 to 899 lines.
Move slash command dispatch logic and 12 individual command handlers
(/export, /share, /copy, /name, /session, /changelog, /hotkeys,
/compact, /thinking, /edit-mode, /arminsayshi, plus showThinkingSelector)
into a new slash-command-handlers.ts module.
InteractiveMode now delegates to dispatchSlashCommand() via a
SlashCommandContext interface, keeping the integration surface minimal.
Handlers that are also invoked from keybindings/events remain on
InteractiveMode and are accessed through the context.
Reduces interactive-mode.ts from 4,783 to 4,272 lines (-511).
* refactor: replace recursive auto-dispatch with linear autoLoop, delete ~3k lines of dead code
Replace the complex recursive dispatch system (dispatchNextUnit, reentrancy
guards, stall detection, idempotency tracking, skip-depth machinery) with a
simple linear while(s.active) loop in auto-loop.ts.
Key changes:
- New auto-loop.ts with autoLoop(), runUnit(), resolveAgentEnd()
- Deleted auto-idempotency.ts, auto-stuck-detection.ts, session-lock.ts,
mechanical-completion.ts, progress-score.ts, auto-constants.ts, unit-id.ts
- Extracted WorktreeResolver class for worktree path resolution
- Added auto-worktree-sync.ts for worktree synchronization
- Simplified auto.ts from ~1400 lines to ~400 lines
- Fixed 9 TypeScript errors (NotifyCtx type widening, capture typing)
- Comprehensive test coverage: 32 auto-loop tests + worktree resolver/DB tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address 6 audit findings in auto-loop refactor
1. CRITICAL: Move pendingResolve to AutoSession + queue orphaned agent_end
events instead of silently dropping them. Prevents permanent stalls when
error-recovery sendMessage retries fire between loop iterations.
2. HIGH: Scope pendingResolve per-session via _activeSession ref, preventing
concurrent /gsd auto sessions from corrupting each other's promises.
3. HIGH: Replace console.log in dispatchHookUnit with debugLog to prevent
hook prompt content (potentially containing secrets) from leaking to stdout.
4. HIGH: Restore parked milestone handling in state.ts — Phase 1 skips
parked milestones so they don't satisfy depends_on, Phase 2 registers
them as 'parked' status. Add 'parked' to MilestoneRegistryEntry type.
5. MEDIUM: Restore queuePhaseActive parameter in shouldBlockContextWrite
and re-export setQueuePhaseActive for guided-flow-queue.ts consumers.
6. MEDIUM: Add MAX_LOOP_ITERATIONS (500) lifetime cap to autoLoop to prevent
runaway loops when units alternate between IDs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: resolve build breakers, add correctness fixes, and graduated recovery
Build breakers (CRITICAL):
- Restore unit-id.ts (deleted but still imported by complexity-classifier.ts, metrics.ts)
- Restore progress-score.ts (deleted but still imported by commands.ts, dashboard-overlay.ts, doctor.ts)
- Rewrite worktree-sync-milestones.test.ts to use new syncProjectRootToWorktree API
Correctness fixes (MEDIUM):
- Cap pendingAgentEndQueue to 3 entries to prevent unbounded growth from stale events
- Add milestoneId path traversal validation in WorktreeResolver
- Clear depthVerificationDone on session_start to prevent cross-session leaks in RPC mode
- Add verification gate for non-hook sidecar units (triage, quick-tasks)
- Remove dead handleAgentEnd import from index.ts
Graduated recovery (Jeremy's feedback):
- Blanket try/catch around loop body — one bad iteration no longer kills the session
- Graduated stuck recovery: at count 3 try artifact verification + cache invalidation,
at count 5 hard stop (was: binary stop at 5 with no recovery attempt)
- Graduated error recovery: 1st error retries, 2nd invalidates caches, 3rd stops
Test results: 32/32 auto-loop, 28/28 worktree-resolver, 11/11 sidecar-queue, tsc clean.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: restore copyWorktreeDb/reconcileWorktreeDb exports and fix loadToolApiKeys import
Two missing exports caused ~90% of the 120 pre-existing test failures:
1. copyWorktreeDb + reconcileWorktreeDb — imported by auto-worktree.ts but
never added to gsd-db.ts. Restored with the original implementations.
2. loadToolApiKeys — moved to commands-config.ts but index.ts still imported
from commands.ts. Fixed the import path.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: move loadToolApiKeys import to commands-config.js
loadToolApiKeys was moved to commands-config.ts but index.ts still
imported it from commands.ts, causing runtime failures in all tests
that transitively load the extension entry point.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: fix provider error assertion on windows
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
On older Linux distributions (e.g., RHEL 8 with older glibc), the native
Rust addon fails to load. The proxy throws on every function call, but
wrapTextWithAnsi and visibleWidth in pi-tui had no JS fallback — causing
an uncaught crash during TUI rendering.
Fix: Both functions now catch native throws and fall back to JS
implementations (simple word-wrap and ANSI-strip length).
Fixes#1418
getSessionStats() calculated cost by summing usage from assistant messages
in state.messages. After auto-compaction, pre-compaction messages are
replaced by a compactionSummary with no usage field — dropping the cost.
Fix: Added cumulative accumulators (_cumulativeCost, _cumulativeInputTokens,
_cumulativeOutputTokens, _cumulativeToolCalls) that are incremented on
every assistant message event, independent of the message array.
getSessionStats() now returns max(array-sum, cumulative) to ensure
monotonically non-decreasing values.
Fixes#1423
Added scripts/generate-openrouter-models.mjs that fetches the full model
list from OpenRouter's API and generates TypeScript entries matching the
existing models.generated.ts format. Run with:
node scripts/generate-openrouter-models.mjs > /tmp/openrouter.ts
Updated the OpenRouter section in models.generated.ts from 241 → 350
models, including all nvidia/nemotron variants requested in the issue.
Fixes#1407
* fix: sync worktree completion artifacts back to external state before merge (#1412)
When a worktree's .gsd/ was a real directory (not symlinked to external
state), milestone completion artifacts (SUMMARY, VALIDATION, updated
ROADMAP) were written locally but never synced back. The project root's
deriveState() read from external state and found no SUMMARY — reporting
the milestone as incomplete.
Changes:
- auto-worktree.ts: Added syncWorktreeStateBack() that copies milestone
and slice .md files from worktree .gsd/ to the main external state dir
- auto.ts: Call syncWorktreeStateBack() in tryMergeMilestone before the
git merge, ensuring artifacts are visible from the project root
Fixes#1412
* fix: emit agent_end after abort during tool execution (#1414)
When a user aborts a turn while a tool call is running, the abort RPC
succeeds but agent_end was never emitted. RPC consumers tracking turn
lifecycle via events got stuck in a 'streaming' state permanently.
Fix: After abort() + waitForIdle(), emit a synthetic agent_end if the
agent is no longer streaming. This ensures consumers always see the
turn-complete signal regardless of how the turn ended.
Fixes#1414
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
* fix: graceful fallback when native addon is unavailable on unsupported platforms
On platforms without a pre-built native binary (e.g., win32-arm64),
the native loader threw at import time, crashing the entire application.
Now returns a Proxy object that:
- Allows the import to succeed (no startup crash)
- Throws per-function when a native function is actually called
- Individual consumers (GSD parser bridge, fuzzy find, autocomplete)
already wrap native calls in try/catch and fall back to JS
implementations
Also exports nativeAvailable boolean for consumers that want to check
upfront. Prints a one-line warning to stderr on startup.
GSD is now fully functional on unsupported platforms — just slower for
file parsing, grep, and fuzzy search.
Fixes#1223
* fix: remove __nativeUnavailable from exported type to fix TS2352 cast errors
The __nativeUnavailable boolean property on the native type caused
TS2352 errors in consumers that cast native as Record<string, Function>
(ast/index.ts, diff/index.ts, gsd-parser/index.ts).
Replaced with a module-level _loadedSuccessfully flag and exported
nativeAvailable boolean. The proxy no longer needs a sentinel property.
* 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>
* fix: strip model variant suffix for all auth methods, not just OAuth (#1097)
The model ID variant suffix (e.g., `[1m]` in `claude-opus-4-6[1m]`) was
only stripped for OAuth token auth. When using an API key, the suffix was
sent to the Anthropic API as-is, causing a 400 "upstream_error" because
`claude-opus-4-6[1m]` is not a valid API model ID.
The default Anthropic model is `claude-opus-4-6[1m]` (1M context variant),
so every API key user hits this on every request.
Fix: strip `[...]` suffix unconditionally for all auth methods.
* fix: update source-reading tests for post-refactor file locations
triage-dispatch.test.ts: read auto-post-unit.ts (dispatch logic moved
from auto.ts) and update comment string matches to reflect renamed
section headers.
token-profile.test.ts: read preferences-types.ts, preferences-validation.ts,
and preferences-models.ts (GSDPreferences interface and validation logic
split from preferences.ts).
* 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: disable reasoning for MiniMax-M2.5 in alibaba-coding-plan provider (#1003)
MiniMax-M2.5 via Dashscope's Anthropic-compatible API does not
properly support extended thinking, causing the model to get stuck
in a thinking loop. Set reasoning: false for this model entry in
the alibaba-coding-plan provider.
* 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>
- Align pi-tui chalk from ^5.5.0 to ^5.6.2 (matches root, pi-ai, pi-coding-agent)
- Convert @mistralai/mistralai and openai to caret ranges (^1.14.1, ^6.26.0)
in both root and pi-ai — no intentional pin rationale found in git history,
versions were just hoisted as-is from workspace deps
- Keep gaxios@7.1.4 override pinned — intentionally set in 5c64f99 to
eliminate glob@10.5.0 deprecation warnings from transitive deps
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