Use published terraphim crates for router integration#1
Open
AlexMikhalev wants to merge 34 commits into
Open
Conversation
Continues the cleanup begun in commit 8c54482 ("chore: update beads issue tracking, remove stale conformance test fixtures") by removing 70 additional fixture files under tests/ext_conformance/artifacts/ that are no longer referenced by the live conformance corpus. Removed buckets: - tests/ext_conformance/artifacts/claude-rules/claude-rules.ts - tests/ext_conformance/artifacts/npm/vaayne-agent-kit/claude-plugins/ specs-dev/ (README, agents, commands) - tests/ext_conformance/artifacts/plugins-community/plugins/community/ claude-never-forgets/ (plugin manifest, LICENSE, README, commands, hooks, skills/memory subtree) - tests/ext_conformance/artifacts/plugins-community/plugins/community/ claude-reflect/ (plugin manifest, LICENSE, README, SKILL, commands, skip-reflect) These were external snapshots of upstream plugin repos that have since been refreshed or dropped from the curated corpus; the live corpus still ships ~18,250 artifacts after this trim (~0.4% reduction). No production code paths reference any of the removed fixtures; the conformance runner discovers fixtures by glob over the artifacts/ tree so removing files simply shrinks the test set rather than breaking any specific test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t#90) Add an end-to-end configurable HTTP request timeout for provider API calls, with provider-aware defaults, fixing pi_agent_rust#90 where `pi --provider ollama ...` failed with "Request timed out" while Ollama was still loading a multi-GB model into memory on a cold start. The timeout bounds connect + request-write + first-response-header latency for each provider request. `0` disables it entirely (unbounded). Defaults are now provider-aware (src/http/client.rs): - Remote/cloud providers: 60s (DEFAULT_REMOTE_REQUEST_TIMEOUT_SECS) — generous for any healthy cloud API. - Local providers (Ollama, LM Studio, ...): 600s / 10 min — long enough to absorb realistic cold-start model loads from disk into RAM/VRAM, which the old flat 60s could not. Three configuration surfaces, resolved through one path: - CLI: `--request-timeout <SECONDS>` (src/cli.rs), bound by clap to the `PI_HTTP_REQUEST_TIMEOUT_SECS` env var so flag and env share storage. - Env: `PI_HTTP_REQUEST_TIMEOUT_SECS` (REQUEST_TIMEOUT_ENV constant). - Settings file: `request_timeout_secs` with serde aliases `requestTimeoutSecs` / `requestTimeoutSeconds` (src/config.rs), merged in Config::merge at the usual `.or(base)` precedence. Precedence wiring (src/main.rs): - Before any provider HTTP client is built, if `cli.request_timeout` is Some (flag or env), call `pi::http::client::set_request_timeout_override(secs)`. - After config load, only if `cli.request_timeout` is None, apply `config.request_timeout_secs` — so flag/env beat the settings file, settings file beats the built-in provider-aware default. Error UX (src/error_hints.rs): - `api_hints` now detects "timed out"/"timeout" messages and returns a dedicated hint: how to raise the timeout via flag/env/settings (and that 0 disables it), plus the local-provider note to pull the model (`ollama pull <model>`) and verify the server is reachable (`ollama list`). Surfaces `url` and `timeout_seconds` context fields. src/http/client.rs also drops the now-unused `#[cfg(not(test))] use std::sync::OnceLock;` import in favor of the new override storage / resolution helpers (`set_request_timeout_override` has a real impl for non-test builds and a no-op `#[cfg(test)]` variant so tests don't mutate global timeout state). Behavior is unchanged for anyone who never sets a timeout on a cloud provider (still 60s); local-provider users get the 600s default instead of spurious timeouts, and everyone can now override explicitly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reflow the doc comment on the REQUEST_TIMEOUT_ENV constant into a summary line plus a body paragraph, and fix the slightly garbled "This is also the env clap binds ..." sentence to "Clap binds the --request-timeout CLI flag and the requestTimeoutSecs setting to this same env var, so the three configuration surfaces share a single resolution path." Documentation only — the constant value (PI_HTTP_REQUEST_TIMEOUT_SECS) and all behavior are unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The TUI started the textarea cursor-blink loop in init(), which fires a BlinkMsg every ~530ms forever. Each tick repaints the whole alternate-screen TUI — wasted CPU and, over SSH, wasted bandwidth, even when the user is idle and the agent is doing nothing. TextArea::update also re-arms the blink via blink_cmd() on every cursor movement, so simply not starting it in init() is not enough on its own. - init(): never start the blink loop (input_cmd = None). The cursor still renders solid-on — a focused cursor's blink state starts "shown" and we never toggle it — so input remains fully usable; we just don't blink. - update_inner(): defensively drop InitialBlinkMsg / BlinkMsg / BlinkCanceledMsg before they reach the textarea, so the movement-triggered re-arm can never sustain a tick chain. Mirrors the existing SpinnerTickMsg drop just above. This removes the periodic idle repaint and is the prerequisite for the runtime to actually stay parked between events. NOTE: the larger idle-CPU win — removing main.rs's `.enable_parking(false)` so worker threads sleep instead of busy-spinning — is deliberately NOT done here: it guards against a real lost-wakeup stall in the pinned asupersync 0.3.2 (Dekker-style Relaxed atomics in WorkerCoordinator::wake_*, fixed only in unreleased commit 8b6e44824). Tracked in bd-rek8z; safe to flip once asupersync >0.3.2 ships. Independently reimplemented after reviewing PR Dicklesworthstone#95. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…the box (Dicklesworthstone#97) `CopilotOAuthConfig::default()` left `client_id` empty, and all four call sites read it from `GITHUB_COPILOT_CLIENT_ID` via `unwrap_or_default()` (empty when unset). Both the browser and device flows then hard-fail on the empty client_id, so Copilot login was impossible without the user first registering their own GitHub App — exactly the "same error with the new version" Dicklesworthstone#97 reports (and the real blocker behind the Dicklesworthstone#91 routing fix). - Add `DEFAULT_COPILOT_CLIENT_ID` = the well-known public Copilot client id that GitHub's own editor integrations (copilot.vim, Copilot CLI) ship and that the wider ecosystem reuses. It is a non-secret public app id, identical for all users, so embedding it is safe and standard. - Add `resolved_copilot_client_id()` (env override → public default) and route all four login sites through it; make it the `Default` client_id too. - `GITHUB_COPILOT_CLIENT_ID` still overrides for Enterprise/custom-app setups. - Update the stale device-flow routing doc + the default-config test; add a resolver fallback test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Includes the Dicklesworthstone#97 fix: GitHub Copilot login now works out of the box. The default config ships the well-known public Copilot client id (overridable via GITHUB_COPILOT_CLIENT_ID), so both browser and device flows succeed without the user first registering their own GitHub App. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Formatting-only change from `cargo fmt`. Reindents the closure body in `resolve_timeout`'s `unwrap_or_else` and wraps the long `let (status, response_headers, stream, timeout_info) = if let Some(duration) = resolved_timeout` binding across two lines. No logic change (verified via `git diff -w`: a single line-wrap, no token changes). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Re-export of the beads issue database to JSONL. The diff is whole-file re-serialization churn (record reordering/normalization) rather than substantive issue content changes. Keeps the git-tracked JSONL ledger in step with the local beads store. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…sworthstone#99) The publish job runs only on a real (non-prerelease) release tag, but when the CARGO_REGISTRY_TOKEN secret was unset it *silently skipped* `cargo publish` and still reported success — which is how crates.io fell behind the repo (stuck at 0.1.13 while tags advanced to 0.1.17). Add a guard step that fails the release with a clear error when the token is missing, so a misconfiguration is visible instead of silently dropping the publish. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… feature (Dicklesworthstone#98) SDK/library consumers (e.g. Jack-NimbleTron's pi-code-gui VSCode extension, 8.5k+ OpenVSX users) embed the `pi` library but do not want the interactive terminal stack. Previously the crate unconditionally compiled crossterm and the full charmed_rust stack (bubbletea/lipgloss/bubbles/glamour) even for pure library use, bloating SDK build times and the dependency surface. This adds a `tui` Cargo feature, included in `default` (and `full`), so the CLI build is byte-for-byte unchanged for existing users — `cargo build` still produces an identical `pi` binary with no action required. A `--no-default-features` build now compiles the library WITHOUT the terminal stack, which is the SDK contract the reporter asked for. WHY each dependency was gated vs left shared (audited via `cargo tree`): - Gated under `tui` (used exclusively by the interactive front-end): the four charmed_rust crates (charmed-bubbletea/-lipgloss/-bubbles/-glamour), plus the TUI-layout helpers unicode-width and textwrap. With `--no-default-features` these crates are entirely absent from the dependency graph. - crossterm is marked `optional`/gated as our DIRECT dependency, but note it is still pulled in TRANSITIVELY by rich_rust; that is expected and correct. - rich_rust stays unconditional: it backs plain styled stdout / markdown rendering in non-TUI code paths (src/tui.rs and elsewhere). The investigation flagged pulldown-cmark / fancy-regex / unicode-width as possibly shared — they are: each arrives transitively via rich_rust (and swc_common), so the library keeps them regardless of `tui`. Only our own direct TUI edges are gated. Source gating (mirrors the existing `wasm-host` / `sqlite-sessions` pattern): - `src/lib.rs`: `interactive` and `session_picker` modules are `#[cfg(feature = "tui")]` (both reach the charmed stack and are only used from the gated interactive surface / `main.rs`, which itself now carries `required-features = ["tui"]` so the bin builds by default but is correctly skipped under `--no-default-features` while the lib still compiles). - `src/theme.rs`: the `Theme` data struct stays unconditional (resource/config loading depend on it); only the lipgloss/glamour-backed `TuiStyles` struct and the `tui_styles()` / `glamour_style_config()` render helpers (and their two smoke tests) are gated. - `src/keybindings.rs`: the serde keybinding catalog stays available to library consumers; only `KeyBinding::from_bubbletea_key` (and the bubbletea-conversion test block, now a gated submodule) are gated. - `src/session.rs`: `resume_with_picker` gates only the interactive `session_picker::pick_session` branch; without `tui` it falls through to the existing non-interactive resolution path. CI: a new `library_no_default_features` job in ci.yml runs `cargo check --no-default-features --lib`, locking in the SDK-can-compile contract so it can never silently regress (matches the issue's proposed design). Verification: `cargo check --all-targets` (default) and `cargo check --no-default-features --lib` both pass clean; `cargo fmt --check` clean; no new clippy findings in any changed file. Independent implementation of the design proposed by Jack-NimbleTron in Dicklesworthstone#98 (we never merge outside PRs). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First crates.io-published release since 0.1.13 (0.1.14–0.1.17 were tagged but silently skipped by the publish workflow when CARGO_REGISTRY_TOKEN was unset; that silent-skip is now a hard failure per 6352b8c). Ships the TUI feature-gate (Dicklesworthstone#98) so SDK/library consumers can build without the terminal stack. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ry branch Follow-up to the Dicklesworthstone#99 guard: GitHub parses ::error:: workflow commands from stdout, not stderr, so the annotation now goes to stdout (it would not have rendered in the run UI otherwise). Also removed the Summary step's now-unreachable "Skipped - CARGO_REGISTRY_TOKEN not configured" branch — a missing token fails the job at the guard before Summary runs, so that branch was dead and misleading. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…y providers (Dicklesworthstone#100) append_upstream_nonlegacy_models unconditionally skipped any provider already in the legacy catalog, so snapshot and models-override.json entries for the native-adapter providers (openai-codex, github-copilot, google-gemini-cli, google-antigravity) were silently dropped — even though they still showed up in autocomplete and then failed to resolve. gitlab worked only because it's non-legacy. The legacy skip now admits a native-adapter legacy provider when it has a usable seed default — either a non-empty seed base_url (openai-codex, gemini-cli, antigravity) or a self-routing adapter (github-copilot resolves its own endpoint via GitHub token exchange despite an empty seed base_url). azure-openai and sap-ai-core stay excluded: their empty seed base_url genuinely needs a per-user resource URL and would fail at request time. The post-legacy dedupe means only genuinely-new ids are appended; curated legacy entries are untouched. Fixes github-copilot/claude-opus-4.6 etc. from the bundled snapshot immediately and enables models-override.json for all four providers. Regression test added; snapshot additions for the three providers absent from the snapshot are a follow-up (needs real upstream model ids). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…thstone#101) On macOS arm64, `pi` startup spent 10-15s at 275%+ CPU inside `SecTrustSettingsCopyTrustSettings` parsing the system cert trust plist. Root cause was twofold: 1. The HTTP client built its TLS connector with `with_native_roots()`, which on macOS loads the OS trust store via Security.framework. 2. `Client::new()` is called from many hot paths (every provider constructor, the version check ~4x, etc.) and rebuilt the trust store from scratch every time — no caching. Fix: 1. Switch the asupersync TLS feature from `tls-native-roots` to `tls-webpki-roots` (Cargo.toml) and the connector build from `.with_native_roots()` to `.with_webpki_roots()` (src/http/client.rs). In asupersync 0.3.2 `with_webpki_roots(self) -> Self` is infallible (unlike `with_native_roots(self) -> Result<Self, TlsError>`), so the former `.and_then(...)?`-style error handling is replaced by a direct builder chain; only `.build()` remains fallible. 2. Cache the built `TlsConnector` in a process-global `OnceLock` and clone it (cheap) on every `Client::new()`. The connector is now built exactly once per process. Per-call config (user_agent, vcr) is unchanged and still computed per `Client`, so public API/behavior is preserved. Cargo.lock updated as a direct consequence of the feature swap: drops security-framework, schannel, core-foundation, openssl-probe, rustls-native-certs; adds webpki-roots. Trade-off: bundled webpki (Mozilla) roots drop OS/enterprise custom root certificates. This is acceptable for a CLI that talks to public LLM API endpoints (Anthropic/OpenAI/Google/Cohere/Azure/etc.), all of which chain to well-known public CAs. Verified by compiling on Linux (cargo check --all-targets: exit 0; cargo fmt --check: clean; all 93 http::client tests + 133 http-related tests pass). The macOS perf win cannot be reproduced on Linux. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The hard =0.3.2 pin (and its dev-dependency twin) was a workaround holding back from the broken 0.3.3 release. 0.3.3 shipped a compile regression (E0277 in src/atp/policy/scope.rs under tls) and was yanked; 0.3.4 is the fixed release, so the pin is no longer needed. Move both asupersync entries to 0.3.4 and refresh Cargo.lock. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ache (Dicklesworthstone#103) The system prompt embedded a per-second timestamp ("...June 9, 2026, 03:04:20 PM +03:00" in app.rs; "%Y-%m-%d %H:%M:%S UTC" in acp.rs). That string is part of the cached system-prompt prefix, so a value that changes every second invalidates the provider's prompt/KV cache on every request — higher latency and cost for no benefit. Both paths now emit date granularity only, keeping the prefix stable within a day while still giving the model the current date. test_mode still substitutes <TIMESTAMP>, so the golden corpus is unaffected. Closes Dicklesworthstone#103 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… clippy drift (Dicklesworthstone#102) ACP mode hardcoded `Session::in_memory()` and never received `--session-dir`, so ACP sessions could not be persisted/resumed. AcpOptions now carries `session_dir` (populated from `cli.session_dir`); `handle_session_new` builds the session via a new `new_acp_session` helper that, when `--session-dir` is set, persists to that directory using the configured store kind and enables autosave (`save_enabled`) so the session is resumable via `pi --session`/`--resume`. Without the flag, ACP keeps its existing in-memory, non-persisted behavior (the `loadSession` capability stays false — this adds save, not ACP-side load). Helper is unit-tested for both the persisted and in-memory paths. Also resolves pre-existing nightly-clippy-drift that had been failing CI on the last several pushes (a rolling-nightly toolchain started flagging these): - interactive/commands.rs: `map(..).unwrap_or(false/true)` on Result → `is_ok_and`/`map_or` - providers/model_fetch.rs: `unwrap_or(fn())` → `unwrap_or_else`; tighten the cache_lookup lock-guard lifetime (clone owned data, drop guard); split two over-long first doc paragraphs - auth.rs (test): `map(..).unwrap_or(true)` → `map_or` - main.rs: box the large `run_rpc_mode` future (clippy::large_futures) - session.rs: make `SessionStoreKind::from_config` pub(crate) for acp.rs cargo fmt + clippy --all-targets -D warnings clean; new ACP tests pass. Closes Dicklesworthstone#102 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Dicklesworthstone#104) Running a local OpenAI-compatible server such as llama.cpp's `llama-server` or mistral.rs failed with: Error: Provider error: llamacpp: Missing API key for provider. even though, like ollama, these are LOCAL providers that need no API key. Root cause (two parts): 1. `llamacpp` and `mistral.rs` were not registered providers at all. Only the cloud `mistral` (api.mistral.ai) and Meta `llama` (api.llama.com) entries existed, so `--provider llamacpp` could only work via a hand-written models.json entry and never out-of-the-box. 2. More importantly, the OpenAI-completions and OpenAI-responses request paths (`OpenAIProvider::stream` / `OpenAIResponsesProvider::stream`) *unconditionally* required an API key whenever no `Authorization` override was present — regardless of whether the provider is a keyless local one. ollama only "works" because users follow the docs and set a throwaway `"apiKey": "ollama"`; a genuinely keyless local provider still hit the missing-key error at stream time. The provider's `auth_header == false` / empty `auth_env_keys` was honored by the startup readiness gate (`model_requires_configured_credential`) but NOT by the actual request path. Fix: - Register `llamacpp` (aliases `llama-cpp`/`llama.cpp`/`llama-server`, default `http://127.0.0.1:8080/v1`) and `mistralrs` (aliases `mistral.rs`/`mistral-rs`, default `http://127.0.0.1:1234/v1`) in `PROVIDER_METADATA` as OpenAI-compatible local providers with empty `auth_env_keys` and `auth_header: false`, exactly mirroring `ollama`. - Add `provider_is_keyless_local()`, derived from canonical metadata (empty `auth_env_keys` AND routing `auth_header == false`) rather than a hardcoded list, so it stays correct as new keyless local providers are added. - In both `OpenAIProvider::stream` and `OpenAIResponsesProvider::stream`, when no key and no auth override are present, proceed WITHOUT an Authorization header for keyless local providers instead of erroring. Cloud / keyed providers still raise the missing-key error as before. With this, `pi --provider llamacpp --model <id>` (and `--provider mistralrs`) attempts a real connection to the local server instead of failing on auth. Tests: - `provider_metadata::tests::local_keyless_providers_registered_without_auth`, `local_keyless_provider_aliases_resolve`, `keyless_local_predicate_matches_local_providers_only` — pin the metadata + predicate (ollama + llamacpp + mistralrs are keyless-local; openai/anthropic/ ollama-cloud/lmstudio are not). - `models::tests::local_providers_synthesize_ready_keyless_entries` — the synthesized ad-hoc entry for these providers is ready without credentials. - `openai::tests::test_stream_keyless_local_provider_does_not_require_key` and `test_stream_unknown_provider_without_key_still_errors` — the request path no longer raises the missing-key error for local providers, but still does for others. Both point at an unroutable address so they resolve fast and deterministically (observe the auth decision, not a full round-trip). Docs: document the built-in keyless local providers in docs/models.md. Co-Authored-By: Claude <noreply@anthropic.com>
The Azure-completions and Ollama config examples set `"api": "openai"`, but
`Api::from_str` doesn't recognize "openai" — it falls through to
`Api::Custom("openai")` (provider.rs:278), which routes incorrectly. The
intended OpenAI-compatible completions API is `"openai-completions"`.
Co-Authored-By: Claude <noreply@anthropic.com>
…icklesworthstone#105) ACP mode previously had no handlers for `session/set_model` or `session/set_config_option`, so clients (e.g. the Zed runtime in the issue) that tried to set the model or reasoning effort dynamically right after `session/new` got `Method not found: session/set_model` / `session/set_config_option` and were stuck with whatever static config the server started with. The underlying capability already existed (`AgentSession::set_provider_model` / the SDK's `set_thinking_level`); only the ACP JSON-RPC transport binding was missing — and the ACP sessions were not even wired with a model registry, so a switch could not have resolved a target model anyway. What this does: - `session/set_model`: accepts either an explicit `{provider, model}` pair or a bare model id via `model`/`modelId`/`value` (the issue's client sends `config=model value=gpt-5.5` with no provider). When only an id is given the provider is resolved from the model registry. Unknown models are rejected up front with an actionable error naming the requested provider/model. On success the live session's provider/model is switched in place and the new pair is returned. - `session/set_config_option`: accepts `{name|key, value}`. The only runtime-settable option today is the reasoning/thinking effort, under the aliases thought_level / thinking_level / thinking / reasoning / effort / reasoning_effort (matching the issue's `thought_level=off` and `effort=off`). Values may be a string (off|minimal|low|medium|high| xhigh) or an integer 0..=4; the level is clamped to what the active model supports. Any other (or restart-only) option returns a structured INVALID_PARAMS error that names the option and the settable set — never a silent success and never a panic. Runtime-vs-restart contract (documented in src/acp.rs): only the active model and the thinking/effort level are mutable on a live ACP session. Everything else (tool set, cwd, system prompt, compaction limits, image handling) is fixed at `session/new` and requires a new session; the config-option handler says so explicitly in its error. Wiring: - `AcpOptions` now carries the full `ModelRegistry` (not just the ready `available_models`); `handle_session_new` attaches it plus the auth storage to the `AgentSession` so `set_provider_model` can locate the target model and resolve its credentials/headers. Without this the switch path could only no-op on the already-active model. - Added `AgentSession::set_thinking_level` (mirrors the SDK handle's logic: clamp, dedupe history, persist, refresh extension host state) so the ACP transport — which holds an `AgentSession`, not an SDK handle — can apply it directly. The SDK handle now delegates to it so the logic lives in one place and cannot drift between the two paths. Both handlers reject missing `sessionId`, unknown sessions, bad params, and refuse to mutate while a prompt turn is in flight (the `agent_session` is taken out of the state during a turn) with a clear retryable error. All other ACP methods are unchanged. Tests (src/acp.rs): resolve_set_model_target (explicit pair, bare value → provider resolved, missing model, unknown model), parse_config_option (all thinking aliases, numeric value, unknown option → structured error, bad value), and end-to-end apply_set_model (switches provider/model) / apply_set_config_option (applies thinking level) / apply_set_model unknown-model (rejected, active model unchanged). `cargo check --all-targets` and `cargo fmt --check` pass clean. Targeted `cargo test --lib acp::` was running on a heavily overloaded remote build host (load ~88 local; many concurrent agent builds) and had not finished at commit time, so test confirmation rests on the clean all-targets check plus the unit tests compiling in the test target. The only remaining `cargo clippy -D warnings` findings are 19 pre-existing `unused_async` errors in unrelated files (extensions.rs, extension_dispatcher.rs, sdk.rs:781/1048) caused by floating-nightly clippy drift — the crate already carries `#![allow(clippy::unused_async)]` at lib.rs:25 and explicit per-fn allows that the current nightly no longer honors; none are in the code changed here. Co-Authored-By: Claude <noreply@anthropic.com>
An unpinned `nightly` channel let clippy drift across toolchains: each newer nightly strengthens lints and stops honoring the crate's existing `#![allow(clippy::unused_async)]`, turning `clippy --all-targets -D warnings` red in CI with no code change. Pin to nightly-2026-02-19 — the same dated nightly meta_skill and beads_rust pin, and the toolchain already active on the build host — so the crate's allows are honored again and CI is reproducible. Co-Authored-By: Claude <noreply@anthropic.com>
- feat(acp): session/set_model + session/set_config_option (Dicklesworthstone#105) - fix(providers): keyless local providers llamacpp + mistral.rs (Dicklesworthstone#104) - feat(acp): --session-dir ACP session persistence (Dicklesworthstone#102) - docs(models): correct api "openai-completions" example - ci: pin nightly-2026-02-19 to stop clippy drift Co-Authored-By: Claude <noreply@anthropic.com>
Cargo.toml already declares version 0.1.19; regenerate the lockfile entry for the pi_agent_rust package so the lock matches the published version (was still pinned at 0.1.18). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… hint (Dicklesworthstone#106) Layered Winsock providers on Windows (VPN clients, antivirus, firewall LSPs) can report an outbound TCP connect as complete — `getpeername` succeeds — while the base provider socket has not actually finished connecting, so the first send fails with `WSAENOTCONN` (os error 10057). The upstream asupersync peer_addr readiness probe (0.3.2+) is not sufficient in those environments (pi_agent_rust#106, previously Dicklesworthstone#66 / asupersync#35). src/http/client.rs: - Treat a "socket not connected" failure during connect/TLS handshake as transient and retry the whole connect with a fresh socket. New `is_retryable_not_connected` matches `ErrorKind::NotConnected` or raw os error 10057, and walks the error `source()` chain so a layered transport that re-wraps the original `io::Error` is still recognized. During an outbound connect this is never a legitimate terminal state (we just created the socket), so it is retried on every platform; refused/reset/DNS/certificate errors are deliberately not retried here. `NOT_CONNECTED_RETRY_BACKOFFS` gives three attempts total (initial + 250ms + 750ms). src/error_hints.rs: - Add `winsock_not_connected_hints()` — an `ErrorHint` surfaced for the 10057 / NotConnected signature (matched via both the error kind and the raw os error, including uncategorized kinds) explaining that pi already retried automatically and pointing at OS-level remediation (`netsh winsock reset`, disabling interfering VPN/AV/LSP). Tests cover the NotConnected-kind and raw-os-error-10057 mappings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…TCONN (Dicklesworthstone#106) `is_retryable_not_connected` only walked the generic `Error::source` chain, but `std::io::Error`'s `Error::source` intentionally returns the *inner's* source — not the wrapped inner `io::Error` itself — so a "socket not connected" error wrapped as `io::Error(io::Error(ENOTCONN/WSAENOTCONN))` was never recognized and the connect was not retried. Inspect both wrapping shapes: walk the `io::Error` -> `io::Error` chain via `std::io::Error::get_ref` + `downcast_ref`, and separately walk the generic `Error::source` chain for non-`io` wrappers (downcasting each link back to `io::Error`). Either path matching `ErrorKind::NotConnected` / raw os error 10057 now triggers the fresh-socket retry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ns ~200% CPU (Dicklesworthstone#107) `pi --acp` busy-spun two cores at idle because the multi-thread asupersync runtime was built with `.enable_parking(false)`. That flag was a deliberate workaround for asupersync 0.3.2's Dekker-style lost-wakeup bug in WorkerCoordinator::wake_one/ wake_many (Relaxed atomics in scheduler/three_lane.rs) — without it, parked workers could miss a wakeup, so they were kept spinning instead. The upstream fix landed as asupersync 8b6e44824 (AcqRel ordering, in three_lane.rs wake_one/wake_many) and is shipped in 0.3.4, which this workspace already depends on (verified: the v0.3.4 tag contains 8b6e44824 and Cargo.lock pins the registry 0.3.4). The bd-rek8z precondition ("re-enable parking once asupersync ships the AcqRel wake fix > 0.3.2") is therefore satisfied, so the workaround is removed and parking defaults back to true — idle workers sleep instead of spin. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…stone#108) The CHANGELOG had drifted: its most recent versioned entry was v0.1.15 (plus an "[Unreleased]" block that actually described shipped v0.1.16 work), while git tags and releases had advanced to v0.1.20. Issue Dicklesworthstone#108 reports "Changelog is 0.1.15, commit history has 0.1.20." Reconstructed accurate entries for each missing version directly from git history between the tags (no invented changes), following the file's existing format (## [vX.Y.Z] — DATE — Release/Tag-only, with Features / Bug Fixes / Internal subsections and issue/PR cross-links): - v0.1.16 (Release): converted the stale "[Unreleased]" block into the v0.1.16 entry (coding-plan provider selection + ad-hoc model/credential resolution + modernized default model ids — all committed between the v0.1.15 and v0.1.16 tags), and enriched it with the rest of that range: credential-loss auto-switch (Dicklesworthstone#81), network-unreachable hints (Dicklesworthstone#88), asupersync 0.3.2 bump fixing the post-session 500% CPU spin (Dicklesworthstone#83), the large QuickJS Node/Buffer-shim conformance push, and the swarm operations / autopilot tooling. - v0.1.17 (Release): provider-aware HTTP timeout (Dicklesworthstone#90), dynamic model fetch w/ TTL cache + static fallback, Copilot login out-of-the-box (Dicklesworthstone#97), idle cursor-blink churn fix, kimi fallbacks, CI size budget. - v0.1.18 (Release): TUI feature-gate for SDK/library consumers (Dicklesworthstone#98), fail-loud on missing CARGO_REGISTRY_TOKEN (Dicklesworthstone#99). - v0.1.19 (Tag-only, no GitHub Release): ACP set_model/set_config_option (Dicklesworthstone#105) + ACP --session-dir (Dicklesworthstone#102), date-only system prompt for KV-cache stability (Dicklesworthstone#103), keyless llamacpp/mistral.rs (Dicklesworthstone#104), bundled TLS roots + cached connector (Dicklesworthstone#101), native-adapter snapshot/override entries (Dicklesworthstone#100), asupersync 0.3.4. - v0.1.20 (Tag-only, no GitHub Release): Windows WSAENOTCONN connect retry + get_ref source-chain detection (Dicklesworthstone#106). Dates use the GitHub Release publish date for published releases and the release-commit date for the tag-only versions (v0.1.19/v0.1.20 have tags but no GitHub Release, matching the file's "Tag-only" convention). Also added compare-link references for v0.1.16–v0.1.20 and repointed the stale [Unreleased] compare link from v0.1.9...HEAD to v0.1.20...HEAD. Co-Authored-By: Claude <noreply@anthropic.com>
…orthstone#110) Prompt turns built StreamOptions with max_tokens = None, so every provider fell back to its hardcoded per-request default (4096 for openai-compat/azure/ cohere, 8192 for anthropic/gemini). The model registry's maxTokens was parsed into Model.max_tokens but only used for display, never applied to the request. In --mode rpc there was no way to raise it (no equivalent of the SDK's set_max_tokens), so turns emitting large tool-call arguments — most visibly the `write` tool — were truncated at finish_reason "length", deserializing to null arguments ("invalid type: null, expected struct WriteInput"). Seed StreamOptions.max_tokens from the selected model's registry maxTokens at every session-build site (app::build_stream_options for CLI/RPC, the SDK builder, and the ACP path) and refresh it on every runtime model switch (RPC set_model and the interactive /model command), mirroring how api_key/headers are already propagated. This makes the maxTokens users configure in models.json actually take effect across all providers and modes. SDK embedders can still override per-call via set_max_tokens (including back to None for the provider default), so that contract is preserved. Updated the SDK test to assert the new seeded-from-registry default while keeping the set/override/reset coverage. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Closes Dicklesworthstone#112: `/mcp` was reported as "Unknown command". Pi has no config-file MCP client — MCP servers are only registered by JS extensions via registerMcpServer, and standalone config files (.agents/mcp.json, .pi/mcp.json, ~/.pi/agent/mcp.json) are intentionally not read. Added a real `/mcp` slash command (enum + parse + help + autocomplete + handler) that lists extension-registered MCP servers and explains this, instead of the misleading "unknown command". - Dicklesworthstone#111: the WSAENOTCONN (os error 10057) fresh-socket retry classifier only inspected the direct `TlsError::Io` variant. Hardened is_retryable_not_connected_tls to also walk the std::error::Error source chain, so a "socket not connected" io::Error surfaced through a Rustls/ Handshake variant (or wrapped) still triggers the retry. (Does not by itself resolve Dicklesworthstone#111 — that failure is environment-level Winsock/VPN/AV interference; left open pending reporter logs.) Verified: cargo check --all-targets clean; new /mcp parse tests (15) and the tls_io source-chain tests (4) pass. Co-Authored-By: Claude <noreply@anthropic.com>
- Sync upstream changes through origin/main (v0.1.20) - Resolve merge conflicts in Cargo.lock and src/providers/model_fetch.rs - Update terraphim_router / terraphim_types path deps to local workspace layout - Fix clippy lints in src/autocomplete.rs and src/interactive/commands.rs
GLM-5.1 and minimax-m2.7-highspeed are now the latest defaults after the upstream model snapshot sync, so align the test expectations.
Drop local path dependencies for terraphim_router and terraphim_types that pointed at divergent forks under ../terraphim/ on this machine. Pin terraphim_automata and terraphim_types to =1.20.4, the coordinated release that exports the MarkdownDirectives / Thesaurus / RouteDirective types and the find_matches / parse_markdown_directives_dir functions used by the pi_terraphim_router module. The terraphim_router crate is not imported anywhere in src/pi_terraphim_router/** or src/main.rs, so it is removed from the dependency list. The terraphim-routing feature is narrowed to the two crates the code actually uses.
…rates Bring in the remote branch's pi_terraphim_router rewrite (KgRouter pattern using terraphim_automata::find_matches and parse_markdown_directives_dir), the readiness-aware fallback selection, the embedded planning / implementation / review tier taxonomy, and the corresponding unit tests. The remote's Cargo.toml still pointed at ../terraphim-ai/ path deps for terraphim_router / terraphim_types / terraphim_automata; that sibling checkout does not exist on this machine and the local terraphim-ai_main fork is a divergent 1.2.x. The previous commit in this branch already switched to crates.io =1.20.4 for terraphim_automata and terraphim_types, which is the coordinated release that exports the MarkdownDirectives / Thesaurus / RouteDirective types and the find_matches / parse_markdown_directives_dir functions the rewrite depends on. Drop the unused terraphim_router dep entirely and narrow the terraphim-routing feature to the two crates the code actually uses. Drop the remote branch's stray vendored crates/terraphim_settings data file and .beads runtime artefacts (.local_version, daemon-error, daemon.log); they were committed by mistake on the remote and are not part of the rewrite.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Switch the
terraphim-routingfeature dependencies from local path deps under../terraphim/...to crates.io=1.20.4releases ofterraphim_automataandterraphim_types.What
terraphim_routerandterraphim_typeswithterraphim_automata = "=1.20.4"andterraphim_types = "=1.20.4"from crates.io. The remote branch still pointed at../terraphim-ai/...which is not present on this machine, and theterraphim-ai_maincheckout available here is a divergent 1.2.x fork.terraphim_routerentirely. The rewrittenpi_terraphim_routerdoes not import anything from that crate; it builds on top ofterraphim_automata(Aho-Corasickfind_matches,parse_markdown_directives_dir) andterraphim_types(MarkdownDirectives,Thesaurus,NormalizedTerm,RouteDirective).terraphim-routingfeature to["dep:terraphim_automata", "dep:terraphim_types"]so it only pulls in the crates the code actually uses.=1.20.4so the lockfile is deterministic and matches the API we verified.Why
The path-dep approach is environment-fragile. It depends on a sibling
terraphim-ai/checkout whose layout differs between contributors and forks. The previous attempt to point atterraphim-serviceandterraphim-ai_mainworked only because the local code used a tiny surface (Capability); the rewritten module depends onMarkdownDirectives,Thesaurus,find_matches,parse_markdown_directives_dir, etc., which are only present in the published 1.20.x line.Using crates.io also removes the dependency on a single co-located workspace and makes the branch buildable on any machine with network access.
Verification
cargo check --bin pi: clean (default features).cargo check --bin pi --features terraphim-routing: clean. Resolves and compilesterraphim_types 1.20.4andterraphim_automata 1.20.4.cargo clippy --bin pi --features terraphim-routing -- -D warnings: clean.cargo fmt --check: clean.cargo test --lib --features terraphim-routing pi_terraphim_router: 51 passed, 0 failed, including the embedded planning / implementation / review tier tests.cargo test --lib provider_default_model_id_resolves_coding_plan_and_corrected_defaults: passed.cargo run --features terraphim-routing --example terraphim_router: smoke-tested end to end. Loaded 3 routing rules from the embedded fallback taxonomy, routed the example prompts to the expected providers/tiers.pi 0.1.20 a9a084bd, reinstalled viainstall.sh --from-source --force.Out of scope
cargo test --librun was attempted locally but a small number of unrelated long-running tests (inacp::,tools::,providers::) exhaust the local shell timeout. They do not relate to this change and should be exercised in CI.crates/terraphim_settings/default/settings.tomland.beads/.local_version,.beads/daemon-error,.beads/daemon.logfiles from commit88bc4254. They are not touched by this PR; a separate small cleanup will follow.Branches
task/use-published-terraphimtask/new-model-support-v2cc any reviewers interested in the router rewrite and the dep graph.