Commit graph

241 commits

Author SHA1 Message Date
Mikael Hugo
cd69e85608 Harden SF model routing and harness contracts 2026-04-30 07:41:24 +02:00
Mikael Hugo
a45f873124 chore: snapshot WIP before resuming M004/S03 auto
84 files spanning provider capabilities, model routing, headless
runtime, sf auto subsystems, gitbook docs, and test coverage. Snapshotted
so headless auto can resume M004 (Production Readiness) S03
(Verification Gate Validation) on a clean tree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 06:31:19 +02:00
Mikael Hugo
9c4bf9b3e6 fix(sf): use live ollama k2.6 routes 2026-04-29 21:38:51 +02:00
Mikael Hugo
701ec8fb88 port(pi-mono): escape session metadata + image data in HTML export (refs 7617c1ad9, 57787b655)
Pi-mono Tier 0 #1 (security) — sf-driven port.

Two upstream security fixes (pi-mono PR #3819, #3883) that escape
user-controlled session content before embedding in HTML exports.
Crafted session content (image mime types, image data, model IDs,
tool names, entry IDs) could otherwise inject markup at the export
boundary.

What sf changed in
packages/pi-coding-agent/src/core/export-html/template.js:

- Image tags: escape `mimeType` and `data` attributes for both
  tool-result and user-message image renders (PR #3819).
- Session metadata: escape `msg.toolName`, `msg.role`, `entry.modelId`,
  `entry.thinkingLevel`, `entry.type`, `entry.id`, and
  `globalStats.models` (PR #3883).
- DOM id construction: renamed `entryId` → `entryDomId` and escape
  `entry.id` to prevent attribute-breakout from a crafted id.

The existing `escapeHtml()` helper was used at every site; no new
helper introduced. Type-check passes.

Co-Authored-By: sf v2.75.1 (session 150fe2c1)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 14:20:23 +02:00
Mikael Hugo
d38e5ea092 fix(schema): auto-coerce string → [string] for sf_* list fields + provider_model_allow tests
Two codex-rescue tasks landed together:

1. Auto-coerce JSON-schema validator: when a tool field declares
   {type:"array", items:{type:"string"}} and the model sends a single
   string, wrap it in [string] before validation instead of hard-rejecting.
   Fixes the recurring "keyDecisions: must be array" rejection on
   sf_complete_task that wasted retries.

2. Provider_model_allow filter (proper implementation with helpers):
   - resolveProviderModelAllowList / isProviderModelAllowed /
     filterModelsByProviderModelAllow helpers in preferences-models
   - Wired into model-registry and auto-model-selection
   - New tests/provider-model-allow.test.ts

Tools coerced: sf_complete_task, sf_complete_milestone, sf_plan_milestone,
sf_plan_slice, sf_replan_slice, sf_reassess_roadmap (key list fields).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: OpenAI Codex <noreply@openai.com>
2026-04-28 12:30:55 +02:00
Mikael Hugo
e2147c0694 sf snapshot: pre-dispatch, uncommitted changes after 43m inactivity 2026-04-25 06:34:49 +02:00
Mikael Hugo
233432d486 model-registry: drop google-antigravity from claude family_failover (preparing rip-out) 2026-04-19 10:35:56 +02:00
Mikael Hugo
ffe86284d2 model-registry: split direct vs family_failover providers per model family
Prior PROXY_FAMILY_PRIORITY table conflated "direct provider" with
"failover provider that happens to serve this family". Observed case:
claude-* family listed anthropic, google-antigravity, and
github-copilot all as "providers" — but only anthropic is the direct
vendor. google-antigravity re-serves Claude via Google's sandbox
IDE product (same endpoint as gemini-cli, different auth contract);
github-copilot re-serves via GitHub's paid platform.

This matters for the 429 fallback chain: a broken anthropic key
should try genuinely-vendored endpoints first (none, for Claude),
then fall into family_failover (antigravity, copilot), and only then
reach the generic GLOBAL_PROVIDER_FALLBACK (opencode, opencode-go,
openrouter, ollama-cloud). The old all-flat list hid this distinction.

New shape:
  { providers: [...], family_failover?: [...] }

Corrections applied:
  claude-*: providers=[anthropic], failover=[google-antigravity, github-copilot]
  gemini-*: providers=[google-gemini-cli, google, google-vertex],
            failover=[github-copilot]
  gpt-* / o* / codex-*: providers=[openai],
            failover=[azure-openai-responses, openai-codex, github-copilot]
  mimo-*: providers=[xiaomi]  (new: was [] — Xiaomi MiMo Open Platform
          is direct API at api.xiaomimimo.com / token-plan-sgp.xiaomimimo.com)

buildCandidateOrder stitches [direct, family_failover, global_fallback]
with deduplication. User overrides via settings.proxy.providerPriority
continue to replace only the direct-provider list, keeping family
failover and global fallback intact.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 10:20:32 +02:00
Mikael Hugo
6450b37025 core + search + benchmarks: auth-error recovery, multi-provider search, M2.7-highspeed entry
Four related improvements that landed in the working tree after the
auto-hardening merge but hadn't been committed:

1. auth_error as a distinct error type (auth-storage + retry-handler).
   Previously invalid/expired API keys would retry the same failing
   credential until the retry budget exhausted. Now:
     - classifyErrorType() recognizes 401s, "invalid api key",
       "authentication error", "unauthorized" etc as "auth_error"
     - RetryHandler triggers cross-provider fallback on auth_error just
       like it does for rate_limit and quota_exhausted — switch
       providers rather than burning retries on a broken key
   Outcome: a stale OPENCODE_API_KEY in sops now fails over to kimi or
   minimax immediately instead of stalling the unit.

2. Multi-provider search-key detection (native-search.ts).
   The "Web search: Set BRAVE_API_KEY" warning fired whenever a
   non-Anthropic model lacked BRAVE_API_KEY, even when the user had
   TAVILY_API_KEY or OLLAMA_API_KEY available. Now: the warning
   suppresses if any of BRAVE/TAVILY/OLLAMA keys is present, and the
   warning text lists all three options. Matches the preferences-
   validation allow-list for search_provider.

3. MiniMax-M2.7-highspeed benchmark entry (model-benchmarks.json).
   Routes the fast-tier variant of M2.7 through the Bayesian blender
   with inherited RULER scores. Lets dynamic routing consider the
   highspeed model when speed matters more than peak quality.

No regressions: the 41 pre-existing test failures in pi-coding-agent
(FallbackResolver chain-membership + LSP integration) are unchanged
relative to the prior commit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 09:24:54 +02:00
Mikael Hugo
3bb93b1612 Cherry-pick process lifecycle fixes for multi-day autonomous operation
- shell: add trackDetachedChildPid / untrackDetachedChildPid /
  killTrackedDetachedChildren (#9b7948c)
- bash: track/untrack detached child PIDs so they are killed on shutdown
- interactive-mode: register SIGTERM/SIGHUP handlers for clean shutdown
  (#5d440b0); kill tracked bash children on shutdown
- rpc-mode: register SIGTERM/SIGHUP handlers, refactor to forceShutdown()
  that deduplicates shutdown path (#5d440b0); kill tracked bash children
- print-mode: register SIGTERM/SIGHUP handlers for graceful exit

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 14:38:55 +02:00
Mikael Hugo
aff49e52aa Cherry-pick 4 critical recovery fixes from pi-mono upstream
- agent-loop: wrap afterToolCall in try/catch so hook throws don't crash
  parallel tool batches (#3084)
- retry-handler: add "connection lost" to retryable error patterns (#3317)
- rpc-mode: redirect console.log to stderr to protect JSON stdout (#2388)
- openai-completions: ignore null/non-object chunks in stream (#2466)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 14:28:15 +02:00
Mikael Hugo
f153521c24 Cherry-pick tool bug fixes from pi-mono upstream
- compaction: fix repeated compaction dropping kept messages (#2608)
  Re-summarize from previous compaction's firstKeptEntryId instead of
  prevCompactionIndex+1; use buildSessionContext for accurate tokensBefore

- edit: add multi-edit support via edits[] array
  Single call can update multiple disjoint regions in one file;
  applyEditsToNormalizedContent matches all edits against original content
  and applies in reverse order for stable offsets

- bash: persist full output when line-count truncation occurs (#2852)
  ensureTempFile now called on any truncation, not only byte overflow;
  prevents data loss when output exceeds line limit before byte threshold

- bash-executor: same fix for remote/operations-based execution
  ensureTempFile includes SF cleanup registration (registerTempCleanup,
  bashTempFiles tracking)

- grep: include lineText from rg JSON events to avoid per-match file reads
  Eliminates stall when context=0 on broad searches (#3148)

- agent-session: forward isError override from afterToolCall extension hook
  Allows extensions to change error status of tool results (#3051)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 14:18:52 +02:00
Jeremy
b5e1beff8e fix(auth): self-heal stale Anthropic OAuth credential (#4399)
Anthropic OAuth was removed in v2.74.0 for TOS compliance (#3952). Users
who upgraded through that version still have type:"oauth" entries under
`anthropic` in auth.json which cannot resolve to a valid API key.

stale entry, so hasAuth("anthropic") kept reporting true and masked the
claude-code fallback path. Users had to hand-edit auth.json to recover.

Self-heal instead:

- AuthStorage.removeLegacyOAuthCredential(provider) strips only
  type:"oauth" entries and preserves any api_key credentials.
- sdk.ts getApiKey() calls it when the legacy-OAuth branch triggers,
  logs a one-line warning, and throws a message pointing the user at
  the "claude-code" provider when the `claude` binary is in PATH, or
  at ANTHROPIC_API_KEY otherwise.

Closes #4399

(cherry picked from commit b8ef6604617fda239a037cf5d5e6020b168d2e62)
2026-04-18 13:40:02 +02:00
Yeon Gil Kang
b73763d944 fix(pi-ai): hide unsupported ChatGPT codex oauth models
ChatGPT-backed Codex sign-in no longer exposes the removed 5.1/5.2 Codex variants. Filter those models from openai-codex OAuth so GSD stops surfacing selections that immediately fail while leaving API-key-backed OpenAI models available.

(cherry picked from commit 1aedba583916826fc5c6129037f61e9802010e46)
2026-04-18 13:35:45 +02:00
Mikael Hugo
78c5c3a78b Add proxy routing tests; fix two build errors
- 15 tests for ModelRegistry.getModelsForProxy covering family priority
  ordering, auth-ready promotion, overrides, and edge cases
- Fix StreamOptions cast in proxy-server.ts (lost during rebase conflict)
- Fix .ts import extension in args.test.ts (pre-existing)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 12:40:03 +02:00
mikkihugo
dc0db3868a
Add per-family proxy provider priority system with TUI and 429 fallback
- model-registry: export PROXY_FAMILY_PRIORITY and GLOBAL_PROVIDER_FALLBACK
  constants; add getModelsForProxy() returning candidates ordered by family
  priority then global fallback (opencode → opencode-go → openrouter →
  ollama-cloud); add getModel() convenience wrapper
- proxy-server: add priorityOverrides option; handleChat iterates all
  candidates in priority order and falls through to the next on 429
- settings-manager: add ProxySettings type with providerPriority override
  map; add getProxyProviderPriority() / setProxyFamilyProvider() accessors
- settings-selector: add ProxyPrioritySubmenu — a two-level TUI submenu
  (family → provider) that dynamically generates entries from
  PROXY_FAMILY_PRIORITY; wired in interactive-mode with full callback

Family defaults: MiniMax→minimax, GLM→zai, Kimi→kimi-coding,
MiMo→global-fallback, Gemini/Gemma→google-gemini-cli, Claude→anthropic,
GPT/o-series→openai

https://claude.ai/code/session_013BwmqG3NuwwZY3vsUb4Y9Y

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-17 19:17:50 +02:00
ace-pm
f92ee8d64c
Rename @sf-run/* → @singularity-forge/* package scope
- All 373 source files updated
- Package.json scopes in all workspace packages
- Loader workspace symlink dir updated
- RpcClient import unified from pi-coding-agent (fixes type mismatch)
- Scripts, configs, flake.nix updated
- Workspace symlinks rebuilt
2026-04-15 22:56:33 +02:00
ace-pm
9d739dfa5d Rename GSD→SF: complete rebrand from fork origin
- All gsdDir/gsdRoot/gsdHome → sfDir/sfRootDir/sfHome
- GSDWorkspace* → SFWorkspace* interfaces
- bootstrapGsdProject → bootstrapProject
- runGSDDoctor → runSFDoctor
- GsdClient → SfClient, gsd-client.ts → sf-client.ts
- .gsd/ → .sf/ in all tests, docs, docker, native, vscode
- Auto-migration: headless detects .gsd/ → renames to .sf/
- Deleted gsd-phase-state.ts backward-compat re-export
- Renamed bin/gsd-from-source → bin/sf-from-source
- Updated mintlify docs, github workflows, docker configs
2026-04-15 18:33:47 +02:00
ace-pm
421fccd898 refactor: rebrand gsd_ tool names and references to sf_ namespace
Updates workflow tool names, documentation references, and internal naming
conventions across MCP server, CLI, tests, and web components to complete
the singularity-forge rebrand from gsd to sf.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 15:51:38 +02:00
ace-pm
6b0ac484ba refactor: update log prefixes and string values from gsd- to sf- namespace
Updates channel prefixes, log messages, comments, and configuration values
across daemon, mcp-server, and related packages to complete the rebrand from
gsd to sf-run naming.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 15:37:12 +02:00
ace-pm
35dc87ef53 chore: sync workspace state after rebrand
- Rebrand commits already in history (gsd → forge)
- Sync pre-existing doc, docker, and CI config updates
- All rebrand artifacts verified in place:
  * Native crates: forge-engine, forge-ast, forge-grep
  * Log prefixes: [forge] across 22+ files
  * Binary: ~/bin/sf-run
  * Workspace scopes: @sf-run/*, @singularity-forge/*
  * Nix flake: Rust toolchain ready

System ready for: nix develop && bun run build:native

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 14:54:20 +02:00
ace-pm
e5d655bdb3 chore: checkpoint workspace changes 2026-04-15 13:38:15 +02:00
ace-pm
c0de3538ec fix(retry-handler): classify 529/overloaded as rate_limit for fallback walk
Minimax and other Anthropic-protocol providers return HTTP 529 with
`overloaded_error` bodies under heavy load. The retryable regex (line 119)
matched `overloaded` so the error was retried, but the rate-limit
classifier (line 423) only matched `429`, so the error never triggered
credential rotation or cross-provider fallback — the handler looped on
the same provider forever.

Adds `529|overloaded` to the rate-limit classifier so 529 responses
route through the same backoff + fallback path as real rate limits.
2026-04-15 11:04:41 +02:00
ace-pm
1f1c029c74 fix(cli): invert persistModelChanges default to false (#4251)
Followup to 828c5edf6. Swarm review flagged default=true as a latent
footgun: any SDK consumer of createAgentSession() that forgets to pass
persistModelChanges would silently mutate ~/.gsd/agent/settings.json.

Flip the default to false so persistence is opt-in. Interactive CLI
entry points now explicitly pass persistModelChanges: true:
- src/cli.ts interactive createAgentSession call
- packages/pi-coding-agent/src/main.ts: persistModelChanges = isInteractive

Print/rpc/mcp stay at the safe default. Tests updated (9/9 green).
2026-04-15 10:45:26 +02:00
ace-pm
828c5edf62 fix(cli): don't persist --model override in print mode (#4251)
`gsd -p --model X "msg"` was silently overwriting defaultProvider/
defaultModel in settings.json. One-shot verification runs must use the
model for that invocation only.

Adds an AgentSessionConfig.persistModelChanges flag (default true so
interactive behavior is unchanged), forwards it through createAgentSession,
and sets it false in main.ts when !isInteractive and in src/cli.ts print
mode. The gsd wrapper also skips validateConfiguredModel when --model is
explicitly passed, so a CLI-provided model can't trigger a fallback repair
that writes the wrong default back.

Three settings.json write sinks audited: agent-session._applyModelChange
(gated on flag), model-selector.ts (interactive only, unreachable in
print), startup-model-validation (gated by !cliFlags.model in print).

Regression: 8 source-assertion tests in
agent-session-print-mode-persist.test.ts.
2026-04-15 10:12:32 +02:00
Jeremy
bc98495cdd fix(chat): preserve claude MCP thinking visibility during tool windows 2026-04-14 23:09:20 -05:00
Jeremy
9a344ad6ca fix(chat): prune orphaned claude MCP provisional sub-turn text 2026-04-14 22:22:10 -05:00
Jeremy McSpadden
b803d6e023 Merge pull request #3878 from mastertyko/fix/3782-minimax-env-key-fallback
fix(pi-coding-agent): fall back to env keys for built-ins
2026-04-14 22:03:31 -05:00
Jeremy
7208a6af36 fix(chat): prune claude MCP provisional text above tool output 2026-04-14 21:41:29 -05:00
Nils Reeh
736e542304 test(pi-coding-agent): add regression tests for agent_end DOM destruction (issue #4197) 2026-04-15 03:01:11 +02:00
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