Commit graph

210 commits

Author SHA1 Message Date
Jeremy McSpadden
4ab053d9ba Merge pull request #4156 from jeremymcs/fix/4144-claude-code-subturn-regression
fix(tui): reset segment state on claude-code sub-turn shrink
2026-04-13 20:45:36 -05:00
Jeremy
2bf2313395 test(tui): finalize sub-turn regression tests to stop pinned spinner
The two new sub-turn shrink regression tests created a pinned
DynamicBorder (via message_update with pinnable text + tool) but never
emitted message_end, so the spinner's setInterval kept the test process
alive until CI timed out after 15 minutes. Append a message_end to
each test so the module-level pinnedBorder is torn down.
2026-04-13 20:36:52 -05:00
Jeremy
03b7142400 fix(tui): reset segment state on claude-code sub-turn shrink
Commit c8c416802 (#4144) introduced module-level renderedSegments state
to track interleaved text/tool components per assistant turn, but never
reset it when an adapter shrinks streamingMessage.content[] back to 0/1
at a provider sub-turn boundary within one assistant lifecycle (the
claude-code adapter does this). Consequence chain: the segment walker
finds the stale text-run entry at startIndex=0, calls updateContent on
it with the new (shrunk) message, and the in-place edit destroys the
prior sub-turn's visible text. New tool blocks at contentIndex=1 then
collide with stale registrations, causing visual ordering corruption.
hasToolsInTurn stays sticky-true and lastPinnedText never clears, so
the pinned "Working - Latest Output" mirror freezes on the pre-shrink
snapshot.

Track lastContentLength explicitly. On shrink, clear renderedSegments,
reset lastPinnedText, and reset lastProcessedContentIndex so the
walker treats the new sub-turn as fresh segments that append after
prior sub-turn children. Prior history stays rendered as frozen
components; pendingTools and the spinner border are untouched.

Adds two regression tests in chat-controller-ordering.test.ts: one
verifies prior sub-turn components are not overwritten and new tools
append in content[] order after a shrink, the other verifies the
pinned markdown updates from the first sub-turn's text to the second
sub-turn's text across a shrink boundary.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 19:58:11 -05:00
Jeremy McSpadden
cdd257e59a Merge pull request #4059 from mastertyko/fix/4054-compaction-safe-role-markers
fix(pi-coding-agent): use safe compaction role markers
2026-04-13 17:58:04 -05:00
Jeremy
c8c416802f fix(tui): render assistant tool calls inline with text instead of grouped at end
Previously the chat-controller created one AssistantMessageComponent per
assistant message and removed/re-appended it to the chat container's tail
on every tool block, forcing all narration after every tool execution
regardless of stream order. Users had to scroll up to read text that was
written before each tool call.

Replace the reorder hack with a stream-order segment walker that walks
content[] left-to-right, collapses contiguous text/thinking blocks into
text-run segments, emits one segment per tool block, and append-only adds
new segments to chatContainer. AssistantMessageComponent gains a
ContentRange API so a single message can spawn multiple text-run
components, plus a separate showMetadata flag so timestamp/error footers
render only on the trailing segment without duplicating earlier text.

Adds a regression test that streams [text, tool, text, tool, text] and
asserts both interleaved order and per-segment rendered text content.

Closes #4144
2026-04-13 17:23:17 -05:00
Jeremy
cce3bc6828 fix(model-resolver): gate saved default restore on provider readiness
Restore the isProviderRequestReady() guard lost during the main merge.
Tests in model-resolver.test.ts and model-resolver-initial-model-auth.test.ts
require findInitialModel() to skip an unauth'd saved default and fall
through to the first available model.
2026-04-13 10:26:28 -05:00
Jeremy
bafa4e483d Merge remote-tracking branch 'upstream/main' into claude/model-agnostic-selection-rmDX3
# Conflicts:
#	packages/pi-coding-agent/src/core/model-resolver.ts
#	src/cli.ts
2026-04-13 10:22:16 -05:00
Jeremy McSpadden
4fcf5d6e6b Merge pull request #4117 from NilsR0711/fix/localhost-custom-provider-compaction-auth
fix(pi-coding-agent): skip localhost dummy key when fallback resolver provides a configured key
2026-04-13 09:12:17 -05:00
Claude
0ed576ac00 Make model selection model-agnostic
Remove hard-coded Anthropic/Claude defaults and silent provider swaps so
the app honors whatever model/provider the user has configured.

- src/cli.ts: drop the anthropic->claude-code auto-migration blocks that
  were rewriting the user's saved defaultProvider on every startup.
- packages/pi-coding-agent/src/core/model-resolver.ts: delete the
  defaultModelPerProvider table, drop the "recommended variant" swap
  that silently upgraded e.g. claude-opus-4-6 to -extended, and replace
  the provider-iteration first-available fallback with provider-sticky
  (user's saved provider first, then first registry entry).
- src/startup-model-validation.ts: replace the openai/anthropic-first
  fallback chain with Pi-default -> same-provider -> first-available.
- src/help-text.ts: use a generic provider/model-id example for --model
  instead of claude-opus-4-6.
- src/tests/startup-model-validation.test.ts: update the fallback test
  to assert provider stickiness rather than a specific Claude model id.

https://claude.ai/code/session_01CvuUuzuVjRcQN25263nG6V
2026-04-13 14:03:35 +00:00
Nils Reeh
1ef6ba16f9 fix(pi-coding-agent): skip localhost dummy key when fallback resolver provides a configured key
Custom OpenAI-compatible providers running on localhost (e.g. a local proxy)
with an explicit apiKey in models.json received 'local-no-key-needed' during
compaction instead of their configured key, causing 401 errors.

The localhost shortcut in AuthStorage.getApiKey() was unconditional. Normal
dispatch calls getApiKeyForProvider() which skips the baseUrl check entirely,
so the fallback resolver was reached and the real key was used. Compaction
calls getApiKey(model) which passes baseUrl, hitting the shortcut first.

Closes #4106
2026-04-13 14:32:16 +02:00
NilsR0711
ddff956a91 feat(pi-ai): add Alibaba DashScope as standalone provider (#3891)
* feat(pi-ai): add Alibaba DashScope as standalone provider

Adds `alibaba-dashscope` for users with a regular DashScope API key,
separate from the existing `alibaba-coding-plan` free-tier provider.

- types.ts: register `alibaba-dashscope` as KnownProvider
- env-api-keys.ts: map to DASHSCOPE_API_KEY
- models.custom.ts: add qwen3-max, qwen3.5-plus, qwen3.5-flash,
  qwen3-coder-plus with international endpoint and real pricing
- model-resolver.ts: default model qwen3.5-plus
- key-manager.ts: add alibaba-coding-plan and alibaba-dashscope
  to PROVIDER_REGISTRY so /gsd keys add works for both

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

* feat(pi-ai): add qwen3.6-plus to alibaba-dashscope provider

qwen3.6-plus is available on DashScope international endpoint.
Pricing: $0.5/M input, $3/M output (base tier, 0-256K tokens).
Supports thinking mode (reasoning: true).

Source: https://www.alibabacloud.com/help/en/model-studio/model-pricing

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

* test(pi-ai): add tests for alibaba-dashscope provider and key-manager regression

- packages/pi-ai/src/models.test.ts: add describe block covering all 5
  alibaba-dashscope models (presence, base URL, API, provider field,
  context window, paid pricing, per-model reasoning/cost assertions,
  independence from alibaba-coding-plan, failure path for unknown model)
- src/resources/extensions/gsd/tests/key-manager.test.ts: add regression
  tests for #3891 — alibaba-coding-plan was missing from PROVIDER_REGISTRY,
  causing /gsd keys add alibaba-coding-plan to fail silently; also covers
  alibaba-dashscope registration, env var separation, and getAllKeyStatuses

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

---------

Co-authored-by: Claude Code <noreply@anthropic.com>
2026-04-13 08:04:39 -04:00
Rebecca Chernoff
110c01b8c6 fix: flush extension provider registrations before model resolution (#1923)
Extension-based providers like pi-claude-cli register their models
during extension loading, but registrations were queued and not flushed
until after model resolution ran. This caused findInitialModel() and
the startup model validation to see extension models as nonexistent,
permanently overwriting the user's saved model selection on every launch.

- Flush pendingProviderRegistrations in createAgentSession() before
  findInitialModel() so extension models are visible in the registry
- Move model validation to after createAgentSession() in both print
  and interactive code paths
- Load extensions before --list-models so extension models appear
2026-04-13 07:06:16 -04:00
Jeremy McSpadden
c189b2152e Merge pull request #4092 from jeremymcs/fix/openrouter-credit-retry
fix(auto): recover from OpenRouter affordability 402 errors
2026-04-12 23:04:58 -05:00
Jeremy
724464c7ae fix(auto): recover from OpenRouter credit affordability errors 2026-04-12 22:48:55 -05:00
Claude
701ab18d81 fix(models): block unconfigured models from selection surfaces
Filter models whose provider has no working API key or OAuth out of
every user-facing selection path. Previously, stale defaults and scoped
sets could leak unconfigured models into /model, /gsd model, and auto
run — the user could "pick" a model that immediately threw on use.

- model-selector: filter scopedModels via isProviderRequestReady;
  default to "all" scope when no scoped model is ready.
- model-controller: same filter for getModelCandidates, so exact-match
  resolution from /model <term> can't return an unauth'd scoped model.
- model-resolver: gate findInitialModel step 3 on provider readiness so
  a stale saved default falls through to the available-models path.
- startup-model-validation: check configuredExists against getAvailable
  instead of getAll, so a configured-but-unauth default triggers the
  fallback picker and thinking-level reset.
- auto-start: validate resolveDefaultSessionModel against the live
  registry + auth before snapshotting, and warn when PREFERENCES.md
  names an unconfigured model.

https://claude.ai/code/session_015q6b23ap9Pyqdogzz2FXGh
2026-04-12 17:25:06 -05:00
Jeremy McSpadden
c3aa3a3bf0 Merge pull request #4067 from jeremymcs/fix/gsd-model-session-override 2026-04-12 12:50:12 -05:00
Jeremy
c96d01acb7 fix(model): require provider readiness for saved default selection 2026-04-12 12:24:49 -05:00
mastertyko
761177c8c4 fix(pi-coding-agent): use safe compaction role markers 2026-04-12 18:14:03 +02:00
Jeremy
4f2e90e1e8 test(auto): add tests for credential cooldown fix
- auth-storage.test.ts: 8 tests for getEarliestBackoffExpiry()
- sdk.test.ts: 12 tests for CredentialCooldownError class
- infra-errors-cooldown.test.ts: 35 tests for isTransientCooldownError(),
  getCooldownRetryAfterMs(), and exported constants

Required by CI lint (require-tests.sh) per CONTRIBUTING.md.

Closes #4052
2026-04-12 09:30:52 -05:00
Jeremy
d0afe018eb fix(auto): add structured cooldown error and bounded retry budget
Address Codex adversarial review findings:

- Replace string-matched cooldown detection with typed
  CredentialCooldownError (code: AUTH_COOLDOWN, retryAfterMs)
- Add MAX_COOLDOWN_RETRIES (5) cap so cooldown retries can't spin for
  hours on persistent quota exhaustion
- Auto-loop uses retryAfterMs from structured error when available,
  falls back to 35s default
- Export CredentialCooldownError from pi-coding-agent package
- Retain regex fallback for cross-process error propagation

Closes #4052
2026-04-12 09:16:05 -05:00
Jeremy
1ae93e9822 fix(auto): survive transient 429 credential cooldown in auto sessions
getApiKey() retry loop (3 attempts, ~12s) couldn't outlast the 30s
rate-limit backoff window, causing cooldown errors to cascade through
the auto-loop and trigger a hard stop after 3 consecutive failures.

- Add AuthStorage.getEarliestBackoffExpiry() to expose when the next
  credential becomes available
- Update getApiKey() to sleep until backoff expiry (up to 60s) instead
  of fixed 2s/4s/6s delays
- Add isTransientCooldownError() detector in infra-errors.ts
- Auto-loop now waits 35s on cooldown errors without incrementing the
  consecutive error counter

Closes #4052
2026-04-12 09:04:41 -05:00
mastertyko
d2ed5a91a6 fix(pi-coding-agent): match renderable tools case-insensitively 2026-04-12 14:05:30 +02:00
Jeremy
b488961609 fix(tui): clear pinned output on message_end to prevent duplicate display
The pinned "Latest Output" zone was only cleared at agent_end, but during
flows with form elicitation (e.g. discuss-phase), there is a gap between
message_end and agent_end where the agent waits for user input. During this
gap, the same content was visible in both the chat history and the pinned
zone. Clear the pinned zone at message_end when the assistant message is
finalized in the chat container.
2026-04-11 17:41:50 -05:00
Jeremy
5531538e0d fix(tui): clear pinned latest output on turn completion 2026-04-11 16:58:48 -05:00
Git-Scram
1d1e47e78b fix(tui): restore pinned output above editor during tool execution
Restores the pinned assistant output zone that shows the latest narration
during tool execution. Adds markdown rendering, animated spinner, height
capping to prevent render flashing, and session rebuild support.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 15:53:29 -04:00
Jeremy
74fee9ed48 fix(interactive): keep MCP tool output ordered and restore secure prompt fallback 2026-04-11 12:47:41 -05:00
Jeremy McSpadden
5d48038816 Merge pull request #3992 from jeremymcs/fix/mcp-output-stream-order
fix(interactive): preserve MCP tool output stream ordering
2026-04-11 11:46:23 -05:00
Jeremy
ba6c1f5d7e test(interactive): cover MCP tool output ordering in chat controller 2026-04-11 11:32:28 -05:00
Jeremy McSpadden
19cbb17683 Merge pull request #3961 from jeremymcs/fix/windows-portability-sweep
fix: harden Windows portability across runtime and tooling
2026-04-10 21:53:10 -05:00
Jeremy
61204ce771 fix(windows): harden portability across runtime and tooling 2026-04-10 20:33:18 -05:00
Jeremy
d64056f833 fix claude code mcp elicitation bridge 2026-04-10 19:24:51 -05:00
Jeremy
b1c0dafc70 feat(gsd): implement ADR-005 multi-model provider and tool strategy
Implements all 4 phases of ADR-005 (issue #2790):

Phase 1: Provider Capabilities Registry
- Declarative ProviderCapabilities interface and PROVIDER_CAPABILITIES
  registry covering all 12 API providers
- Consolidates scattered *-shared.ts knowledge into queryable registry
- Unknown providers get permissive defaults (backward compatible)

Phase 2: Tool Compatibility Metadata
- ToolCompatibility interface (producesImages, schemaFeatures, minCapabilityTier)
- compatibility field on ToolDefinition
- Tool compatibility registry with pre-populated built-in tools
- Auto-registration from registerTool() and MCP tool defaults

Phase 3: Tool-Compat Filter + ProviderSwitchReport
- ProviderSwitchReport tracks thinking blocks dropped/downgraded,
  tool call IDs remapped, synthetic results inserted, thought
  signatures dropped during cross-provider message transformation
- isToolCompatibleWithProvider(), filterToolsForProvider(), adjustToolSet()
  functions in model router
- filteredTools field on RoutingDecision
- Verbose output for filtered tools in auto-model-selection

Phase 4: adjustToolSet Extension Hook
- AdjustToolSetEvent and AdjustToolSetResult interfaces
- emitAdjustToolSet() on ExtensionAPI and ExtensionRuntime
- Default no-op handler in register-hooks.ts

Includes 47 new tests (20 provider caps + 10 switch report + 17 tool compat)

Closes #2790
2026-04-10 12:33:40 -05:00
Jeremy McSpadden
da352847e2 Merge pull request #2281 from jeremymcs/worktree-local-commands-stay-local
feat: contextual tips
2026-04-10 07:38:18 -05:00
Jeremy
c07ecc1028 fix(providers): match 'out of extra usage' error and respect claude-code provider in model resolution (#3772)
Two bugs prevented subscription users from routing through Claude Code CLI:
1. Retry handler regex only matched "third-party" errors but actual error is
   "You're out of extra usage" — fallback never triggered
2. auto-model-selection actively rerouted bare model IDs back to anthropic
   even after startup migration set claude-code as the session provider
2026-04-08 10:47:35 -05:00
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
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
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
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
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