Commit graph

437 commits

Author SHA1 Message Date
Jeremy McSpadden
b4c6229360 Merge pull request #3646 from jeremymcs/fix/notification-overlay-backdrop
fix(gsd): notification overlay backdrop and sizing
2026-04-06 19:09:22 -05:00
Jeremy
2c91b8c6d8 test(tui): add test for 256-color backdrop codes 2026-04-06 18:57:02 -05:00
Jeremy
c35385fe53 fix(gsd): improve notification overlay backdrop and content-fit sizing
Use dark gray background + dim foreground for visible backdrop effect
instead of barely-perceptible SGR dim. Size overlay box to content
instead of padding to fill the entire viewport.
2026-04-06 18:53:26 -05:00
Jeremy McSpadden
6d2345e939 Merge pull request #3638 from jeremymcs/fix/notification-overlay-backdrop
fix(gsd): notification overlay backdrop dimming and viewport padding
2026-04-06 18:27:52 -05:00
Jeremy
9d1e343e41 test(gsd): add overlay backdrop and notification lock safety tests
- Overlay layout: verify backdrop dims base lines, no dim without flag,
  overlay composites on top of dimmed background
- Notification store: verify markAllRead and clearNotifications do not
  delete a foreign process's lock file
2026-04-06 17:44:34 -05:00
Jeremy
2c4ac844f1 fix(gsd): add backdrop dimming and viewport padding to notification overlay
The notification overlay was rendering too small with few entries, allowing
underlying content to bleed through. Added viewport padding to fill the
overlay box and a new `backdrop` option to OverlayOptions that dims the
background behind modal overlays.
2026-04-06 17:34:45 -05: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 McSpadden
0d87df94be Merge pull request #3619 from frizynn/fix/3618-schema-overload-bash-exit-code
fix(agent-loop): schema overload cap ignores bash execution errors (#3618)
2026-04-06 10:13:03 -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
frizynn
cd14a4c765 fix(agent-loop): schema overload cap ignores bash execution errors (#3618)
The schema overload detector counted ALL isError tool results toward the
consecutive-failure cap, including bash commands that returned non-zero exit
codes (e.g. rg/grep exit 1 = 'no matches'). Three consecutive exploratory
searches with no matches would trigger the cap and abort the session.

Root cause: the allToolsFailed check used toolResults.every(r => r.isError)
which conflates preparation-phase errors (schema validation, tool-not-found,
tool-blocked) with execution-phase errors (the tool ran successfully but
returned a non-zero exit code).

Fix: track preparationErrorCount alongside tool results. Only preparation
errors (schema/validation failures) increment the consecutive failure
counter. Tool execution errors — like bash exit code 1 — are valid usage
and do not count toward the cap.

Also fixes pre-existing StopReason type mismatches in agent-loop tests
(end_turn → stop, tool_use → toolUse).
2026-04-06 11:35:41 -03: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
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
db7a6372a6 fix: cap consecutive tool validation failures to prevent stuck-loop (#3301)
* fix: cap consecutive tool validation failures to prevent stuck-loop (#2783)

When the LLM repeatedly emits tool calls with arguments that fail schema
validation, the agent loop retries indefinitely — each failed validation
returns an error tool result, the LLM retries with the same broken args,
and the cycle burns budget with no progress.

Add a consecutive-failure counter in runLoop that tracks turns where ALL
tool calls fail. After MAX_CONSECUTIVE_VALIDATION_FAILURES (3) consecutive
all-error turns, the loop emits a diagnostic stop message and terminates
cleanly. The counter resets whenever any tool call in a turn succeeds, so
intermittent failures do not trigger early termination.

Closes #2783

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

* chore: retrigger CI

* fix(test): repair agent-loop.test.ts — close unclosed blocks, merge imports

Two test suites were concatenated without closing the first suite's
it+describe blocks, placing the second suite's imports inside a function
body and triggering 'Unexpected "{" ' from esbuild. Merged into a single
well-structured file with consolidated imports and proper closings.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: trek-e <trek-e@users.noreply.github.com>
2026-04-05 01:04:58 -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
74d5200cfd Merge pull request #3516 from jeremymcs/feat/mcp-server-readonly-tools
feat(mcp-server): add 6 read-only tools for project state queries
2026-04-04 19:56:49 -05:00
Jeremy McSpadden
246e706991 Merge pull request #3495 from Tibsfox/fix/tool-argument-json-robustness
fix(pi-ai): extend repairToolJson to handle XML tags and truncated numbers
2026-04-04 19:02:28 -05: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
Jeremy
45b606744f feat(mcp-server): add 6 read-only tools for project state queries (#3515)
Add gsd_progress, gsd_roadmap, gsd_history, gsd_doctor, gsd_captures,
and gsd_knowledge tools that parse .gsd/ on disk — no session needed.

Inline lightweight readers in src/readers/ keep the package standalone
(zero new dependencies). 33 new tests, 64 total passing.
2026-04-04 16:41:24 -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
Tibsfox
920fed7122 fix(pi-ai): extend repairToolJson to handle XML tags and truncated numbers
The existing repairToolJson only handles YAML bullet lists (#2660).
Two additional malformation patterns from smaller models now cause
tool call failures and stuck retry loops:

1. XML parameter tags mixed into JSON values (#3403):
   LLMs (especially Haiku-class) sometimes emit hybrid XML/JSON
   syntax like <parameter name="X">value</parameter> inside
   JSON string values. Add stripXmlParameterTags() to remove
   the tags while preserving content.

2. Truncated numeric values (#3464):
   Smaller models emit incomplete numbers like "exitCode": -,
   or "durationMs": , when values are cut off mid-generation.
   Add repairTruncatedNumbers() to replace these with 0.

Both repairs run before the existing YAML bullet repair phase.
The AJV validation layer (coerceTypes: true) then handles any
remaining string-to-number coercion.

Adds 13 new tests covering detection and repair for both patterns.

Closes #3464, closes #3403, addresses #3369
2026-04-04 03:59:55 -07: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
Tom Boucher
fad23944e7 fix: add Windows shell guard to remaining spawn sites (#3058)
Three spawn call sites were missing `shell: process.platform === "win32"`,
causing ENOENT/EINVAL errors on Windows where npm-installed tools are .cmd
batch scripts that require shell resolution:

- exec.ts: hardcoded `shell: false` -> platform-guarded
- lsp/index.ts: missing shell option on project-type command spawn
- lsp/lspmux.ts: missing shell option on lspmux binary spawn

Adds a structural regression test that scans all spawn sites invoking
user-facing binaries and asserts the Windows shell guard is present.

Closes #2854

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