Replace brittle string-matching in headless-events.ts with structured
source/kind/blocking/dedupe_key metadata on notify() events. String
matching is preserved as a fallback for the ~940 untagged call sites.
- Add NotificationMetadata type to headless-types.ts (canonical definition)
- Extend rpc-types.ts notify event with optional metadata field
- Extend ExtensionUIContext.notify() signature with optional 3rd arg
- Pass metadata through RPC notify implementation in rpc-mode.ts
- Update headless-events.ts: isTerminalNotification, isBlockedNotification,
isMilestoneReadyNotification, isPauseNotification all check metadata first
- Update notification-store.ts: store metadata on NotificationEntry; use
metadata.dedupe_key as dedup key when provided (falls back to message hash)
- Update notify-interceptor.ts to thread metadata through to store + original
- Tag critical emit sites with structured metadata:
stopAuto → { kind: "terminal" } (+ blocking: true when reason includes "block")
pauseAuto → { kind: "terminal", blocking: true }
guided-flow milestone ready → { kind: "approval_request", blocking: true }
- Update notification-overlay.ts to prefer metadata.source for [label] display
- Add 17-test regression suite (notification-event-model.test.ts)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
Pi-mono Tier 0 #4 — manual port (sf went off-task; ported directly).
undici's default 300s bodyTimeout aborts long local-LLM SSE streams
(e.g. vLLM buffering a large tool call) with UND_ERR_BODY_TIMEOUT.
retry.provider.timeoutMs cannot lift this cap — it controls the
provider SDK's AbortController, not undici's per-socket idle timer.
Pass {bodyTimeout: 0, headersTimeout: 0} to EnvHttpProxyAgent. Provider
SDKs continue to enforce their own deadlines.
Type-check passes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
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>
reassess-roadmap: flip default from true → false. Most reassess units
conclude "roadmap is fine" burning a session for no change; the
plan-slice prompt now carries a JIT preamble at zero cost. (#4778)
tool-execution: always prefer toolDefinition.label when non-empty,
even when label === name — allows tools to display their canonical
name explicitly. (#4758)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
showDeprecationWarnings ran setRawMode(true)/once('data')/setRawMode(false)/
pause() right before pi-tui's own stdin setup. That handoff is fragile —
buffered bytes and mode flips between the migration prompt and the TUI's
raw-mode setup can leave stdin cooked and line-buffered, producing the
'Enter does nothing + garbled typing' symptom.
Warnings now print non-blocking. They stay visible in scrollback above
the TUI, so users still see them without a blocking acknowledge step.
Antigravity (Google's IDE sandbox product, different from Gemini CLI) is
removed from:
src/onboarding.ts — drop from LLM_PROVIDER_IDS + OAuth-flow picker
src/pi-migration.ts — drop from LLM_PROVIDER_IDS migration list
src/web/onboarding-service.ts — drop from web-UI provider list
src/tests/integration/web-onboarding-contract.test.ts — update contract
src/resources/extensions/sf/doctor-providers.ts — drop from CLI_AUTH_PROVIDERS
src/resources/extensions/sf/key-manager.ts — drop UI listing
src/resources/extensions/sf-usage-bar/index.ts — delete entire quota fetcher block (~200 lines)
packages/pi-coding-agent/src/cli/args.ts — drop PI_AI_ANTIGRAVITY_VERSION doc
packages/pi-coding-agent/src/utils/proxy-server.ts — drop from claude provider chain
Reason: antigravity has no vendor-published core library we can embed
(unlike @google/gemini-cli-core for the Gemini CLI). Continuing to
hand-roll OAuth against daily-cloudcode-pa.sandbox.googleapis.com is
exactly the pattern Google has started banning for third-party tools.
Removing the code removes the ban risk.
pi-ai provider code, OAuth util, and models.generated entries for
google-antigravity are removed in follow-up commits (separated for
reviewability — each layer verified independently).
Build passes. Note: this is a breaking change for any user who had
google-antigravity configured — they'll need to migrate to
google-gemini-cli (OAuth), google (API key), or google-vertex.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
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>
- 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>
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)
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)
- 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>
- 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>
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>
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>
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.
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).
`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.