Commit graph

440 commits

Author SHA1 Message Date
frizynn
3090f968f4 refactor: extract retry handler and compaction orchestrator from agent-session
Extract two self-contained subsystems from agent-session.ts (3,367 -> 2,737 lines):

- RetryHandler: auto-retry with exponential backoff, credential rotation,
  and cross-provider fallback logic
- CompactionOrchestrator: manual/auto compaction, overflow recovery, and
  extension integration for custom compaction providers

Also add shared getErrorMessage() utility to replace repeated
`err instanceof Error ? err.message : String(err)` patterns.

The extracted modules receive AgentSession state via dependency injection
interfaces, avoiding state duplication. AgentSession remains the coordinator
that delegates to these modules.
2026-03-19 16:46:14 -03:00
frizynn
502067c213 refactor: deduplicate RPC mode shared patterns
Extract the duplicated commandContextActions implementations from
rpc-mode.ts and print-mode.ts into a shared
createDefaultCommandContextActions() factory in
modes/shared/command-context-actions.ts.

Both headless modes (RPC and print) had identical implementations of
waitForIdle, newSession, fork, navigateTree, switchSession, and reload
that simply delegate to AgentSession. The shared factory provides these
defaults; interactive mode continues to layer TUI-specific behavior on
top via its own overrides.

Also fixes a subtle redundancy in print mode where newSession manually
called options.setup after session.newSession(), even though
session.newSession() already handles the setup callback internally.
2026-03-19 16:38:51 -03:00
frizynn
6cb8e3c524 refactor: extract shared tree rendering utilities
Extract duplicated tree rendering logic from tree-selector.ts and
session-selector.ts into tree-render-utils.ts:

- computeScrollWindow: centered scroll window calculation
- renderCursor: accent-colored selection cursor indicator
- applyRowHighlight: selected-row background + truncation
- renderScrollPosition: muted (current/total) position indicator
- buildTreePrefix: tree connector prefix from ancestor-continuation flags
- TREE_BRANCH/TREE_LAST/TREE_PIPE/TREE_SPACE connector character constants
2026-03-19 16:37:40 -03:00
frizynn
23d0ea656d refactor: extract shared file lock utilities
Extract the duplicated file lock mechanism from auth-storage.ts and
session-manager.ts into a shared lock-utils.ts module.

- acquireLockSyncWithRetry(): throwing variant (used by auth-storage)
- tryAcquireLockSync(): non-throwing variant (used by session-manager)
- acquireLockAsync(): async lock with retries and staleness detection

Removes ~55 lines of duplicated retry-loop logic. The shared module
also provides a foundation for deduplicating identical patterns in
settings-manager.ts and models-json-writer.ts.
2026-03-19 15:16:56 -03:00
frizynn
a78121b35c refactor: consolidate resource loader with generic update/dedupe methods
- Replace dedupePrompts() and dedupeThemes() with generic dedupeResources<T>()
  that accepts getName/getPath/resourceType callbacks
- Replace discoverSystemPromptFile() and discoverAppendSystemPromptFile() with
  generic discoverFileInSearchPaths(filename)
- Import ResourceCollision type for use in dedupeResources signature
- Net reduction of 24 lines (868 → 844) with elimination of duplicated logic
2026-03-19 15:16:21 -03:00
frizynn
c6c45cb1c0 refactor: consolidate model switching logic in agent-session
Extract the duplicated model-switching sequence (set model, append
session change, persist settings, re-clamp thinking level, emit event)
from setModel(), _cycleScopedModel(), and _cycleAvailableModel() into a
shared _applyModelChange() helper. Removes ~30 lines of repeated code.
2026-03-19 15:16:18 -03:00
frizynn
166243bfe5 refactor: extract shared helpers in compaction module
Move duplicated patterns from compaction.ts and branch-summarization.ts
into shared utilities in utils.ts:

- getMessageFromEntry(): unified entry-to-message conversion with
  optional toolResult skipping for branch summarization
- collectMessages(): replaces three identical for-loops that collect
  AgentMessages from entry ranges
- extractTextContent(): replaces five instances of the
  .filter(text).map(text).join() pattern
- createSummarizationMessage(): replaces three identical user-message
  construction blocks for LLM summarization calls

Net reduction of ~90 lines of duplication.
2026-03-19 14:56:00 -03:00
frizynn
385d936689 refactor: deduplicate toPosixPath, ZERO_USAGE, and shortenPath utilities
- toPosixPath: remove private copies in skills.ts and package-manager.ts,
  import from canonical utils/path-display.ts
- ZERO_USAGE: export from agent-loop.ts, replace inline zero-usage
  objects in agent.ts and proxy.ts
- shortenPath: extract to shared modes/interactive/utils/shorten-path.ts,
  import in tool-execution.ts and session-selector.ts
2026-03-19 14:55:30 -03:00
frizynn
02b42bfffe refactor: consolidate 9 emit methods in extension runner into shared invokeHandlers
Extract the duplicated loop pattern (create context, iterate extensions,
iterate handlers, try/catch, emitError) into a private invokeHandlers()
helper. Each emit method is now a thin wrapper that delegates the
iteration to invokeHandlers and provides only its result-processing
callback. Net reduction of ~124 lines with identical runtime behavior.
2026-03-19 14:55:18 -03:00
frizynn
bbc180a3b6 refactor: consolidate extension type guards and inline handler type aliases
Replace 7 individual ToolResultEvent type guards (isBashToolResult,
isReadToolResult, etc.) with a unified isToolResultEventType() function,
mirroring the existing isToolCallEventType() pattern.

Inline 14 handler type aliases (SendMessageHandler, SetModelHandler, etc.)
directly into the ExtensionActions interface since they were only used there
and added no semantic value.

Update documentation examples to use the new unified guard.
2026-03-19 14:55:00 -03:00
frizynn
e01e994d82 refactor: consolidate duplicate patterns in LSP module
- Remove duplicate SYMBOL_KIND_NAMES from types.ts (keep SYMBOL_KIND_LABELS in utils.ts)
- Export which() from config.ts, import in lspmux.ts instead of duplicating
- Inline getLspServersForFile/getLspServerForFile thin wrappers in index.ts
- Extract formatLocationResults() helper for definition/type_definition/implementation
- Extract formatCallHierarchyResults() helper for incoming_calls/outgoing_calls
- Remove unused formatPosition() from utils.ts
2026-03-19 14:54:47 -03:00
Tom Boucher
8b0727c0e5 fix: accumulate session cost independently of message array (#1423)
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
2026-03-19 12:44:11 -04:00
Tom Boucher
f0fe4b2443 fix: emit agent_end after abort during tool execution (#1414) (#1417)
* 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
2026-03-19 10:24:39 -06:00
Jeremy McSpadden
d7bf3d4e72 Improve startup performance with lazy extension loading (#1336) 2026-03-19 07:38:50 -06:00
github-actions[bot]
d25c174f8b release: v2.33.1 2026-03-19 04:02:21 +00:00
github-actions[bot]
106f5d8d32 release: v2.33.0 2026-03-19 02:40:54 +00:00
github-actions[bot]
113c5e6518 release: v2.32.0 2026-03-19 00:15:12 +00:00
github-actions[bot]
a488de99bb release: v2.31.2 2026-03-18 22:40:20 +00:00
github-actions[bot]
f2b637a596 release: v2.31.1 2026-03-18 22:13:47 +00:00
github-actions[bot]
b095e352a7 release: v2.31.0 2026-03-18 21:40:31 +00:00
Jean-Dominique Stepek
acec5b5fda feat: add aws-auth extension for automatic Bedrock credential refresh (#1253)
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.
2026-03-18 15:07:10 -06:00
github-actions[bot]
558b2e1c10 release: v2.30.0 2026-03-18 20:26:29 +00:00
Tom Boucher
ebe59a987f fix: use shell: true for LSP spawn on Windows to resolve .cmd executables (#1233)
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
2026-03-18 13:55:23 -06:00
TÂCHES
28c741c196 refactor: replace MCPorter with native MCP client (#1210)
* 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>
2026-03-18 12:26:16 -06:00
github-actions[bot]
d57b117aea release: v2.29.0 2026-03-18 17:44:12 +00:00
Tom Boucher
0f97f938f7 feat: add searchExcludeDirs setting for @ file autocomplete blacklist (#1202)
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
2026-03-18 11:23:40 -06:00
Tom Boucher
1236919c39 fix: include promptGuidelines in customPrompt path of buildSystemPrompt (#1187)
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
2026-03-18 10:55:16 -06:00
Tom Boucher
4c98d3e708 fix: handle Windows non-ASCII paths in cpSync with copyFileSync fallback (#1181)
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
2026-03-18 10:13:37 -06:00
Copilot
05beb9cba7 fix: text-based fallbacks for RPC mode where TUI widgets produce empty turns (#1112)
* 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>
2026-03-18 08:34:49 -06:00
Tom Boucher
fa39a87465 fix(model-resolver): prefer provider's recommended variant over saved base model (#1131) 2026-03-18 08:23:56 -06:00
Tom Boucher
85642bbb6d fix(lsp): use where.exe on Windows to resolve command paths (#1134) 2026-03-18 08:21:33 -06:00
Copilot
1020c140af Fix Codex server_error handling: extraction, retry matching, escalating backoff (#1106)
* Initial plan

* Fix OpenAI Codex error handling: proper error extraction, retry matching, and escalating backoff

- Fix mapCodexEvents to extract error details from nested event.error object
  (Codex API returns {type:"error", error:{type:"server_error", message:"..."}})
- Fix _isRetryableError regex: server.?error matches both server_error and server error
- Add escalating backoff for repeated transient auto-resumes (30s→60s→120s→240s→480s)
- Cap consecutive transient auto-resumes at 5 before pausing indefinitely
- Reset counter on successful unit completion

Co-authored-by: glittercowboy <186001655+glittercowboy@users.noreply.github.com>

* Improve regex test to be behavioral instead of structural

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>
2026-03-18 01:10:55 -06:00
Tom Boucher
792b166ce6 fix: improve LSP diagnostics when no servers detected (#1082) (#1086)
* 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.
2026-03-17 21:45:11 -06:00
TÂCHES
27e79f76b3 refactor: centralize magic numbers into constants.ts (#1044)
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>
2026-03-17 18:45:43 -06:00
TÂCHES
5657f302a6 refactor: fix unicode regex discrepancy and standardize function naming (#1031)
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>
2026-03-17 18:29:44 -06:00
TÂCHES
61f4693f16 perf: add concurrency limits to unbounded Promise.all operations (#1029)
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>
2026-03-17 18:29:28 -06:00
TÂCHES
311f3c2c36 fix(security): use execFileSync in resolve-config-value to prevent shell operator bypass (#1025)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 18:29:12 -06:00
TÂCHES
61858b914f fix(security): use execFile for browser URL opening to prevent shell injection (#1022)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 18:28:24 -06:00
Jeremy McSpadden
fd9565299c feat(autocomplete): add /thinking completions, GSD subcommand descriptions, and test coverage (#1019)
- 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
2026-03-17 18:27:17 -06:00
Tom Boucher
cc4e3aa7b5 feat: add respectGitignoreInPicker setting for @ file picker (#979) (#1016)
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
2026-03-17 18:03:02 -06:00
Jeremy McSpadden
f85c41c693 feat(ux): group model list by provider in /gsd prefs wizard (#993)
* 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.
2026-03-17 17:20:13 -06:00
TÂCHES
4da793f576 fix: remove broken ./hooks export path from pi-coding-agent (#984)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 17:02:12 -06:00
Lex Christopherson
a8b9030560 fix: export RPC utilities from pi-coding-agent public API
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 16:20:13 -06:00
Jeremy McSpadden
0e0f47ef9f fix: failure recovery & resume safeguards (all 4 waves) (#956)
* 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
2026-03-17 16:03:49 -06:00
Tom Boucher
2306e6bb34 fix: LSP command resolution and ENOENT crash on Windows/MSYS (#901) (#925)
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.
2026-03-17 14:01:16 -06:00
Tom Boucher
3542b17c97 fix: normalize Windows paths in LLM-visible text to prevent bash failures (#874) (#884)
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.
2026-03-17 09:02:23 -06:00
Tom Boucher
62bbaa8e8e feat: integrate hashline edit mode into active workflow (#870) (#872) 2026-03-17 08:23:53 -06:00
Jeremy McSpadden
a8eb66b8b3 feat: group /model selector by provider (#871) 2026-03-17 08:23:29 -06:00
Tom Boucher
0abc61987d fix: add Alt+V as clipboard image paste shortcut on macOS and document it (#852) (#854) 2026-03-17 08:19:13 -06:00
Tom Boucher
d593b2e367 fix: handle BMP clipboard images on WSL2 via wl-paste PNG conversion or ImageMagick (#813) (#841) 2026-03-17 07:59:56 -06:00