Skip to content

fix(ai): Ask AI uses only the session when a session bridge is present - #1685

Merged
backnotprop merged 4 commits into
mainfrom
fix/ask-ai-bridge-only
Oct 4, 2026
Merged

backnotprop merged 4 commits into
mainfrom
fix/ask-ai-bridge-only

Conversation

@backnotprop

@backnotprop backnotprop commented Oct 4, 2026 •

Copy link
Copy Markdown
Owner

Why

Owner's rule: when the modern plugin/mod/extension for Claude Code, Pi or OpenCode is in place, there is no provider, only direct pass-through to the original session.

Before this change, a session bridge was registered last, beside the SDK providers (claude-agent-sdk, codex-sdk, pi-sdk, opencode-sdk). The client preferred an explicit saved pick over the bridge. So a user who had once saved Codex got Codex answers, even with the Claude session attached.

What changed

Server (both runtimes)

  • createAIRuntime (packages/server/ai-runtime.ts) and createPiAIRuntime (apps/pi-extension/server/ai-runtime.ts) resolve the bridge first. With a bridge, session-bridge is the only Ask AI provider; the SDK providers go into a separate catalog-only registry (new catalogRegistry dep on createAIEndpoints). Without a bridge, the SDK providers register exactly as before.
  • Effect: /api/ai/capabilities lists only the bridge under providers, and defaultProvider is session-bridge. /api/ai/session with any other provider id answers 503.
  • The agent-job launchers (Review Agents, Code Tour, Guided Review) keep their discovered Claude/Codex catalogs. ?activate=claude-agent-sdk|codex-sdk runs that provider's discovery and reports it under a new catalogProviders field. Only useModelCatalogs reads that field; Ask AI reads providers only.
  • PLANNOTATOR_AI=disabled is gated before runtime creation, so it still disables everything.

Client (@plannotator/ui, editor, review-editor)

  • resolveAIProviderSelection selects a bridge in the list in every status (ready, busy, blocked, gone), ahead of saved picks. Without a bridge, the order is unchanged.
  • The saved provider cookies are never rewritten, so a saved pick still applies to sessions without a bridge.
  • With a bridge, AIProviderBar and review's AIConfigBar show only the bridge label, with no provider, model or effort picker. Settings → AI shows the bridge read-only and writes nothing.
  • A gone or blocked session no longer offers "Ask a separate AI instead". It shows a plain note that the session that opened Plannotator is gone, or is waiting on this decision, and that Ask AI can't reach it. "Ask when it finishes" and "Interrupt and ask now" are unchanged.
  • Removed the now-dead fallback: resolveSessionBridgeFallback, findUsableSessionBridge, the 'fallback' action, sessionAskFallbackLabel and fallbackLabel. These shipped in @plannotator/ui@0.50.0, so this is a breaking change for 0.50.0 consumers. packages/ui/HANDOFF.md records the removals and adds a Publishing note so the next ui release mentions them. No package versions are bumped. CLAUDE.md is updated.
  • useAIProviderActivation takes the listed providers and skips any other id. Both apps pass their capabilities list. At page load the selection can still hold the saved cookie id (e.g. codex-sdk) for one render, and activating it would start Codex discovery in a bridged session.
  • useModelCatalogs also reads catalogProviders. This is additive: a backend that never sends the field behaves as before.
  • Agent jobs (Review Agents, Code Tour, Guided Review, /api/agents/*) are untouched.

Per-host effect

Host Effect
Claude Code with the mod (pull bridge) Ask AI = "Ask this session · Claude Code" only
Pi (in-process bridge; off in remote mode) Ask AI = "Ask this session · Pi" only
OpenCode 2 (in-process / pull bridge) Only the bridge. Plan review's transient-only bridge counts too: "Quick answer from this session · OpenCode" only
OpenCode 1, Claude Code without the mod, remote mode, --tailscale, older hosts No bridge, so unchanged

Tests

  • packages/server/ai-runtime.sessionBridge.test.ts: 6 pass. The tests run in child processes with fake codex and claude CLIs on PATH (tests/test-fixtures/fake-agent-clis.ts). The fake codex speaks the app-server initialize and model/list messages.
    • With a bridge: a plain capabilities probe lists only session-bridge, which is the default, and returns no catalogProviders. Sessions for codex-sdk and claude-agent-sdk answer 503, and codex is never executed.
    • New: with a bridge, ?activate=codex-sdk returns the DISCOVERED codex list (modelsSource: "discovered", version 0.999.0) under catalogProviders, and ?activate=claude-agent-sdk reaches the installed claude (its version is read). providers stays bridge-only and sessions are still refused. This test fails on the previous commit.
    • Without a bridge: claude-agent-sdk and codex-sdk are registered as before.
    • The pull bridge from env is the only Ask AI provider. Remote mode keeps the SDK providers.
  • apps/pi-extension/server/ai-runtime.test.ts: the same assertions for the Pi runtime, including the launcher catalogs. This test also fails on the previous commit.
  • packages/ui/hooks/useModelCatalogs.test.ts: the launcher loader reads the discovered list from catalogProviders when providers is bridge-only.
  • packages/ui/hooks/useAIProviderActivation.dom.test.tsx: an id that is not listed is never activated, and without a list any id is activated as before. This test fails without the fix.
  • packages/ui/aiProvider.sessionBridge.test.ts: covers the resolver.
    • A bridge-only list with a saved codex pick selects the bridge in every status.
    • A bridge listed beside SDK providers wins over saved picks.
    • Without a bridge, the order is unchanged.
  • packages/ui/aiProviderConfigPersistence.test.tsx: through useAIProviderConfig, a bridge-only answer selects the bridge and leaves the saved cookie untouched.
  • packages/ui/components/ai/SessionAskNotice.dom.test.tsx: gone and blocked show the note, with no fallback button.
  • packages/ui/components/ai/AIProviderBar.dom.test.tsx: with a bridge, the bar renders the label and no <select>.

Suites run:

  • packages/server: 1046 pass, 0 fail.
  • apps/pi-extension: 356 pass.
  • packages/ai + apps/opencode-plugin + apps/hook/hooks/mod: 441 pass.
  • DOM_TESTS=1 packages/ui packages/editor packages/review-editor: 2987 pass, 4 fail. The 4 failures are in DocBadges.test.tsx and fail on main too.
  • bun run typecheck is clean. apps/review and build:hook build.

When a host attaches its session to the server (Claude Code mod over the
pull bridge, Pi in-process, OpenCode 2 in-process or pull), the AI runtime
now registers the session bridge as the ONLY Ask AI provider, in both the
Bun and Pi runtimes. No SDK provider is registered, so capabilities list
only the bridge (and it is the default) and /api/ai/session refuses any
other provider id, whatever the client saved.

Client: a bridge in the list is the selection in every status, ahead of
saved picks, without rewriting the provider cookies. The Ask AI bars and
Settings > AI show only the bridge label. A gone or blocked session no
longer offers "Ask a separate AI instead"; it shows a plain note that the
session can't be reached. The busy flow is unchanged.

Without a bridge (mod off, OpenCode 1, remote mode, --tailscale) the
provider set and selection are unchanged.
With a session bridge, the SDK providers now register in a catalog-only
registry instead of not at all. /api/ai/capabilities still lists only the
bridge under providers (Ask AI stays bridge-only, /api/ai/session still
refuses SDK ids), but ?activate=<id> naming a catalog-only provider runs its
discovery and reports it under catalogProviders, which only the agent-job
launchers' useModelCatalogs reads. Review Agents, Code Tour and Guided
Review get the same discovered Claude/Codex catalogs as before #1685.

HANDOFF: the Ask this session exports shipped in ui 0.50.0; record the
removals as breaking for 0.50.0 consumers in the next ui release.
At page load the Ask AI selection can hold the saved cookie's provider id
for one render before the resolver moves it to the session bridge, and the
activation effect then called ?activate=codex-sdk, starting Codex discovery
in a bridged session. useAIProviderActivation takes the listed providers and
skips any other id; both apps pass their capabilities list.
@backnotprop
backnotprop merged commit bb8a302 into main Oct 4, 2026
28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant