Commit graph

302 commits

Author SHA1 Message Date
Jeremy
cf6ea332b7 test(retry): add tests for third-party block provider guard (#3772)
Verify claude-code fallback only fires for anthropic provider and
does not reroute non-anthropic providers on similar error text.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 07:52:58 -05:00
Jeremy
3ea46099d0 fix(retry): guard claude-code fallback to anthropic provider only
Prevent _tryClaudeCodeFallback from firing for non-Anthropic providers
that may produce similar error text, avoiding unintended provider drift.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 07:31:32 -05:00
Jeremy
ea456d4cdb fix(providers): route Anthropic subscription users through Claude Code CLI (#3772)
Anthropic now blocks third-party apps from using Pro/Max subscription
quotas via direct API calls. This change makes the claude-code provider
(which delegates to the local claude CLI binary) the default path for
Anthropic subscription users — TOS-compliant because requests flow
through Anthropic's own infrastructure.

Changes:
- Enhanced readiness check to verify CLI auth status (not just binary)
- Startup migration: auto-switch anthropic → claude-code when CLI ready
- Error recovery: auto-switch on third-party 400 block error
- Onboarding: removed Anthropic from OAuth, added Claude CLI option
- Added claude-code to flat-rate providers (no dynamic routing benefit)

Closes #3772
2026-04-08 07:20:20 -05:00
github-actions[bot]
66a824a8b8 release: v2.66.1 2026-04-08 05:08:15 +00:00
github-actions[bot]
0d69b9c57d release: v2.66.0 2026-04-08 03:07:16 +00:00
Jeremy McSpadden
caca43814f Merge pull request #3554 from Tibsfox/fix/filepath-slash-command
fix(tui): treat absolute file paths as plain text, not commands
2026-04-07 07:19:25 -05:00
Jeremy McSpadden
ad67824b46 Merge pull request #3548 from Tibsfox/fix/429-quota-cascade
fix(retry): prevent 429 quota cascade and 30-min provider lockout
2026-04-07 07:17:40 -05:00
Jeremy McSpadden
53cd0bf892 Merge pull request #3553 from Tibsfox/fix/cmux-image-rerender-loop
fix(tui): break infinite re-render loop for images in cmux
2026-04-07 07:17:00 -05:00
Jeremy McSpadden
91ed0055ef Merge pull request #3688 from Tibsfox/fix/provider-manager-enter-key
fix(provider): handle Enter key to initiate auth setup in provider manager
2026-04-07 07:02:48 -05:00
github-actions[bot]
a1fb64a6ba release: v2.65.0 2026-04-07 04:25:48 +00:00
Tibsfox
f121d6f170 fix(provider): handle Enter key to initiate auth setup in provider manager
The provider manager let users navigate with arrow keys but pressing
Enter did nothing. Users had no way to set up authentication from within
the /provider command.

Adds selectConfirm (Enter) handler that routes to showLoginDialog for
the selected provider, with a hint in the status bar.

Closes #3579
Closes #3567

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 19:23:14 -07:00
Jeremy McSpadden
c53f8ab471 Merge pull request #3608 from deseltrus/perf/session-memory-cpu-leaks
perf: fix CPU/memory leaks in long-running sessions
2026-04-06 15:56:22 -05:00
Jeremy
90feebeccf fix(pi-coding-agent): restore extension tools after session switch (#3616)
newSession() only rebuilt the tool registry when cwd changed. When cwd
stayed the same (e.g., discuss → plan-slice in the same worktree), any
tool narrowing from setActiveTools() persisted — stripping gsd_plan_slice
and other DB tools from auto-mode subagent sessions.

Add an else-branch that calls _refreshToolRegistry with
includeAllExtensionTools:true on every session switch, regardless of cwd.

Also call resetExtensionLoaderCache() in DefaultResourceLoader.reload()
so hot-updated extension code on disk is re-compiled instead of served
from the stale jiti module cache.

Closes #3616
2026-04-06 09:51:58 -05:00
deseltrus
0b40d39b0e perf(interactive): cap rendered chat components + kill orphan descendants
Chat component cap: After 100 rendered components, oldest are removed
from the container (session transcript persists on disk via
SessionManager). Prevents unbounded memory growth in long sessions
where thousands of tool calls accumulate DOM-like component trees.

Orphan process prevention: On shutdown, listDescendants(process.pid)
finds ALL child processes (including those spawned by the Bash tool
that bg-shell doesn't track) and kills them with SIGTERM + 500ms
grace + SIGKILL. Prevents orphaned dev servers, build processes, etc.
from persisting after session exit.
2026-04-06 09:52:20 +02:00
deseltrus
c5227f7570 perf(tui): render-skip, frame isolation, Text cache guard, dispose
Container.render() now returns a stable array reference when output is
unchanged — TUI.doRender() skips ALL post-processing (isImageLine scans,
applyLineResets, differential diffs) when the reference matches.

Loader decouples spinner frame rotation from Text content updates.
Previously every 80ms tick called setText() which invalidated Text's
wrapTextWithAnsi/visibleWidth caches. Now the frame is prepended in
render() while Text caches the message separately.

Text.setText() returns early when text is unchanged, avoiding cache
invalidation on redundant updates.

ToolExecutionComponent.dispose() clears heavy references (image maps,
diff previews, result data) so GC can reclaim memory when components
are removed from the chat history.
2026-04-06 09:52:08 +02:00
github-actions[bot]
f6a1549edd release: v2.64.0 2026-04-06 02:11:42 +00:00
Jeremy McSpadden
2298b9acab Merge pull request #3576 from jeremymcs/feat/llm-safety-harness
feat(gsd): LLM safety harness for auto-mode damage control
2026-04-05 16:03:16 -05:00
Jeremy McSpadden
46b18818a6 Merge pull request #3561 from Tibsfox/fix/ollama-fallback-provider-ready
fix(pi-coding-agent): make Ollama visible to fallback resolver
2026-04-05 15:59:20 -05:00
Jeremy
8d11e5d507 test: add regression tests for adversarial review fixes (#3576)
- git-checkpoint: rollback on checked-out branch, detached HEAD, ref cleanup
- ollama streaming: terminal done:true chunk content preservation
- provider registration: preflush clears queue to prevent double registration
2026-04-05 15:52:26 -05:00
Jeremy
ac20eab501 fix: address adversarial review findings for #3576
- Use `git reset --hard <sha>` for rollback instead of `git branch -f`
  which fails on checked-out branches and worktrees
- Clear pendingProviderRegistrations after preflush to prevent duplicate
  registration when bindCore() runs
- Process Ollama stream content on terminal `done:true` chunks to avoid
  truncating trailing assistant text
2026-04-05 15:48:25 -05:00
Tibsfox
935cc9a464 fix(pi-coding-agent): register models.json providers and await Ollama probe in headless mode 2026-04-05 11:09:08 -07:00
Tibsfox
31e20e0fe3 fix(tui): treat absolute file paths as plain text, not commands 2026-04-05 10:34:34 -07:00
Tibsfox
11239140db fix(tui): break infinite re-render loop for images in cmux 2026-04-05 10:30:13 -07:00
Tibsfox
fde2aafa64 fix(retry): prevent 429 quota cascade and 30-min lockout 2026-04-05 09:37:28 -07:00
Jeremy
4ba2d5a219 feat(ollama): native /api/chat provider with full option exposure
Replace the OpenAI-compat shim with a native Ollama /api/chat streaming
provider that exposes all commonly-used Ollama options and surfaces
inference performance metrics.

Key changes:
- Native NDJSON streaming from /api/chat (no more OpenAI shim)
- Known models send num_ctx from capability table; unknown models defer
  to Ollama's default to avoid OOM on constrained hosts
- Exposes: temperature, top_p, top_k, repeat_penalty, seed, num_gpu,
  keep_alive, num_predict via per-model providerOptions
- Extracts <think>...</think> blocks for reasoning models (deepseek-r1, qwq)
- Surfaces InferenceMetrics (tokens/sec, durations) on AssistantMessage
- Adds remove and show actions to ollama_manage LLM tool
- Adds "ollama-chat" to KnownApi, providerOptions to Model<TApi>
- NDJSON parser uses strict mode for chat (fails on malformed frames)
- Mixed content+tool_call chunks handled independently

Closes #3544
2026-04-05 09:01:40 -05:00
Jeremy
9fe13da3f2 fix(pi-coding-agent): resolve model fallback race that ignores configured provider (#3534)
Extension-provided models (e.g. claude-code/*) were unavailable during
findInitialModel() because pendingProviderRegistrations had not been
flushed yet, causing the fallback chain to select Google Gemini even
when the user explicitly configured claude-code as their default.

Three compounding issues fixed:

(A) Flush pendingProviderRegistrations in createAgentSession() before
    findInitialModel() runs, so extension models are in the registry
    when initial model selection happens.

(B) Re-apply the validated model to the session after
    validateConfiguredModel() in both print and interactive CLI paths.
    Previously, validation updated settingsManager but never called
    session.setModel(), leaving the session on the wrong model.

(C) Update defaultModelPerProvider.anthropic from "claude-opus-4-6[1m]"
    to "claude-opus-4-6" — the [1m] variant was removed from the model
    registry when the base model was upgraded to 1M context, causing the
    Anthropic fallback to silently fail and skip to Google.

Closes #3534
2026-04-05 07:14:24 -05:00
Tom Boucher
4cb6252b9b fix(perf): share jiti module cache across extension loads (#3308)
* fix(perf): share jiti module cache across extension loads (#2108)

Each extension was creating a new jiti instance with moduleCache: false,
causing shared dependencies to be recompiled for every extension. Use a
shared singleton with moduleCache: true so shared modules are compiled once.

Export resetExtensionLoaderCache() for test teardown and explicit reload.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: correct loader path in extension-load-perf test (4 → 3 levels up)

The test file is at src/tests/ (2 levels deep from repo root), so
fileURLToPath(import.meta.url) + 3x'..' reaches the repo root.
Using 4 levels exits the repo into the GitHub Actions workspace parent,
causing ERR_MODULE_NOT_FOUND for loader.js in dist/.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: use process.cwd() for loader path in perf test (source/compiled portability)

import.meta.url resolves to different depths in source (src/tests/) vs compiled
(dist-test/src/tests/), so relative '../' navigation produces the wrong path in
the build phase. process.cwd() is always the repo root in CI regardless of
where the test file is compiled to.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: trek-e <trek-e@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 07:59:17 -04:00
Tom Boucher
d9cea627bf fix: detect and block Gemini CLI OAuth tokens used as API keys (#3296)
* fix: detect and block Gemini CLI OAuth tokens used as API keys

Users who install Google's standalone Gemini CLI may inadvertently set
GEMINI_API_KEY to an OAuth access token (ya29.*) instead of an AI Studio
API key (AIza*). These tokens fail at the Google API with a confusing
error. This adds early detection at three entry points:

- AuthStorage.set(): throws when storing ya29.* as api_key for "google"
- AuthStorage.getApiKey(): blocks ya29.* from runtime overrides (--api-key)
- AuthStorage.getApiKey(): blocks ya29.* from environment variables

Each path provides a clear error message explaining the issue and
directing users to either get an API key from aistudio.google.com or
use /login google-gemini-cli for OAuth-based access.

Fixes #2157

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: retrigger CI

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: trek-e <trek-e@users.noreply.github.com>
2026-04-05 01:05:08 -04:00
Tom Boucher
34e73b01d1 fix(extensions): use bundledExtensionKeys for conflict detection instead of broken path heuristic (#3305)
Fixes #2075

The isBuiltIn check in detectExtensionConflicts used a path-based
heuristic that excluded paths containing /.gsd/agent/extensions/.
Since initResources() syncs all bundled extensions into that same
directory, the heuristic could never return true for bundled extensions,
preventing the "supersedes" hint from being appended. This caused
downstream cli.ts to display "Extension load error" instead of the
intended "Extension conflict" for benign precedence collisions.

Replace the path heuristic with an explicit bundledExtensionKeys set
(already computed by buildResourceLoader) threaded through to
detectExtensionConflicts. The set is matched against the extension
directory name extracted from the owner path.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 01:04:50 -04:00
github-actions[bot]
647954098a release: v2.63.0 2026-04-05 03:48:56 +00:00
github-actions[bot]
b8cb665ba5 release: v2.62.1 2026-04-05 01:33:15 +00:00
Jeremy McSpadden
12ef95024c Merge pull request #3451 from deseltrus/fix/stale-retries-after-model-switch
fix: cancel stale retries after model switch
2026-04-04 18:27:33 -05:00
github-actions[bot]
40fc92a2a6 release: v2.62.0 2026-04-04 21:10:16 +00:00
github-actions[bot]
f6521cd92e release: v2.61.0 2026-04-04 20:53:42 +00:00
Jeremy McSpadden
af82c37041 Merge pull request #2755 from jeremymcs/feat/capability-aware-model-routing-pr
feat: capability-aware model routing (ADR-004)
2026-04-04 15:23:38 -05:00
Jeremy
4df55a51c8 fix(lsp): add legacy alias for renamed kotlin-language-server key
Users with existing lsp.json overrides referencing the old
"kotlin-language-server" key would silently lose their Kotlin
LSP config after the rename to "kotlin-lsp". LEGACY_ALIASES
map remaps old keys during mergeServers() so overrides still
merge correctly.
2026-04-04 11:45:58 -05:00
Jeremy
1cea7fb8bc feat(01-04): add BeforeModelSelectEvent to extension API and wire emission
- Add BeforeModelSelectEvent interface and BeforeModelSelectResult type to types.ts
- Add on('before_model_select') subscription overload to ExtensionAPI interface
- Add emitBeforeModelSelect() method to ExtensionAPI interface and ExtensionRuntimeState
- Implement emitBeforeModelSelect() on ExtensionRunner using invokeHandlers (first-override-wins)
- Bind runner's emitBeforeModelSelect into shared runtime at construction time
- Wire emitBeforeModelSelect delegation through createExtensionAPI in loader.ts
2026-04-04 10:56:06 -05:00
Jeremy
851bb0bebe fix(pi-coding-agent): upgrade Kotlin LSP to official Kotlin/kotlin-lsp
Closes #3493
2026-04-04 10:45:15 -05:00
github-actions[bot]
6aaa244742 release: v2.60.0 2026-04-04 14:46:08 +00:00
deseltrus
58208634ac fix(pi-coding-agent): cancel stale retries after model switch
Explicit model changes aborted the active backoff sleep but left already-queued retry callbacks alive. That let stale provider retries continue after a user or runtime model switch, which could keep surfacing the old provider's backoff errors in the new session state.

Cancel the full retry generation on explicit model switches by clearing queued continue callbacks in RetryHandler, then cover the behavior with regression tests for both the session switch ordering and the queued-retry cancellation path.
2026-04-03 16:21:21 +02:00
Jeremy McSpadden
df93cdc43c Merge pull request #3432 from deseltrus/fix/slash-command-session-routing
fix: route non-builtin slash commands after TUI dispatch
2026-04-03 05:08:01 -05:00
github-actions[bot]
7589509156 release: v2.59.0 2026-04-03 06:18:06 +00:00
deseltrus
b7e0173e50 fix: route non-builtin slash commands after TUI dispatch
The TUI slash dispatcher started treating any unrecognized /command as handled before session.prompt() could resolve extension commands, prompt templates, or /skill:* inputs. That blocked valid non-builtin slash commands and also let /export swallow unrelated /export* prefixes.

Move unknown-command detection to the interactive entry points, allow only known builtins or session-resolved slash commands through, gate /skill:* on the skill-command setting, and tighten /export matching to exact command tokens.
2026-04-03 06:44:09 +02:00
Justin Wyer
95875c41c5 refactor(test): consolidate regression and override tests into #666 test files
Move regression tests and override tests from standalone files into
the existing test files introduced by PR #666:

- resolve-config-value.test.ts: add REGRESSION #666 describe block
  and setAllowedCommandPrefixes override tests
- url-utils.test.ts: add REGRESSION #666 describe block and
  setFetchAllowedUrls override tests
- Delete: regression-666.test.ts, resolve-config-value-override.test.ts,
  url-utils-override.test.ts

Same 59 tests, fewer files, tests live next to the code they test.
2026-04-02 14:06:19 +02:00
Justin Wyer
71caa18552 fix(security): add configurable overrides for command allowlist and SSRF blocklist
PR #666 introduced hardcoded SAFE_COMMAND_PREFIXES and SSRF URL
blocklists with no override mechanism. Users with non-standard
credential tools (sops, doppler, age, infisical) or needing to fetch
from internal URLs (self-hosted docs, VPN services) were silently
blocked with no recourse.

Add two global-only settings (ignored in project-level settings.json
to preserve the security property against malicious repos):

- allowedCommandPrefixes: replaces the built-in command allowlist
- fetchAllowedUrls: exempts hostnames from SSRF blocking

Both also support env var overrides (GSD_ALLOWED_COMMAND_PREFIXES,
GSD_FETCH_ALLOWED_URLS) for CI/container environments. Env vars
take precedence over settings.json.

Security model: global-only keys are stripped from project settings
at load time via stripGlobalOnlyKeys(), applied at all three
assignment points for this.projectSettings. The merge function
stays untouched — no future caller can accidentally skip stripping.

15 new tests covering override behavior, cache invalidation,
allowlist exemptions, and global-only enforcement.
2026-04-02 13:45:05 +02:00
Jeremy McSpadden
46d5fa56af Merge pull request #2312 from jeremymcs/fix/tui-review
fix(tui): comprehensive TUI review — layout, flow, rendering, and state fixes
2026-04-01 16:38:31 -05:00
Jeremy McSpadden
04ebe3f0a0 feat(extensions): add Ollama extension for first-class local LLM support (#3371)
Self-contained extension at src/resources/extensions/ollama/ that
auto-detects a running Ollama instance, discovers locally pulled models,
and registers them as a first-class provider with zero configuration.

Features:
- Auto-discovery of local models via /api/tags on session_start
- Capability detection (vision, reasoning, context window) for 40+ model families
- /ollama slash command with status, list, pull, remove, ps subcommands
- ollama_manage LLM-callable tool for agent-driven model operations
- Onboarding flow with auto-detect (no API key required)
- Non-blocking async probe — doesn't delay TUI paint
- Respects OLLAMA_HOST env var for non-default endpoints

Core changes (minimal):
- Add "ollama" to KnownProvider in pi-ai types
- Add "ollama" key resolution in env-api-keys.ts
- Add "ollama" default model in model-resolver.ts
- Add "Ollama (Local)" to onboarding wizard with probe flow
2026-04-01 08:37:31 -06:00
Jeremy McSpadden
e0d130e682 feat(extensions): wire up topological sort and unified registry filtering (#3152)
- Add extension-manifest.ts and extension-sort.ts to pi-coding-agent
  with manifest reading and Kahn's BFS topological sort algorithm
- Add extensionPathsTransform hook to DefaultResourceLoader that runs
  between path merging and loadExtensions() — enables pre-load
  filtering and reordering without modifying pi internals
- Wire GSD's buildResourceLoader() to provide a transform that:
  1. Filters ALL extensions (including community) through the GSD registry
  2. Sorts in topological dependency order via sortExtensionPaths()
- Mark discoverAndLoadExtensions() as @deprecated (dead code path)
- Add 16 tests covering manifest reading, dependency sorting, cycles,
  missing deps, and non-array deps

Previously, dependencies.extensions in manifests was decorative (sort
existed but was never called), and gsd extensions disable only worked
for bundled extensions. Community extensions in ~/.gsd/agent/extensions/
bypassed the registry entirely.
2026-03-31 11:54:48 -06:00
Tom Boucher
893c525578 fix(read-tool): clamp offset to file bounds instead of throwing (#3007) (#3042)
When an agent requests read(file, offset: 30) on a 13-line file, the
read tool threw "Offset 30 is beyond end of file" which propagated as
invalid JSON downstream during milestone completion. Now clamps the
offset to the last line and prepends a notice, allowing the agent to
continue with valid content.

Fixes both read.ts and hashline-read.ts variants.

Closes #3007

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:48:01 -06:00
Tom Boucher
3a1cedd7de fix(compaction): add chunked fallback when messages exceed model context window (#3038)
When a session grows beyond the context window of available models,
generateSummary() now detects the overflow and falls back to chunked
summarization: split messages into context-fitting chunks, summarize
the first chunk, then iteratively merge subsequent chunks using the
existing UPDATE_SUMMARIZATION_PROMPT path.

Closes #2932

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:47:41 -06:00