Skip to content

feat(model): multi-account rotation + model picker - #421

Merged
jexShain merged 5 commits into
AI-Shell-Team:mainfrom
jexShain:feat/model-rotation
Jul 29, 2026
Merged

feat(model): multi-account rotation + model picker#421
jexShain merged 5 commits into
AI-Shell-Team:mainfrom
jexShain:feat/model-rotation

Conversation

@jexShain

@jexShain jexShain commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

Restores the /model multi-account + automatic rotation + model picker feature that was deferred when the closed 0.4.0 mega-PR (#415) was split into focused PRs. The original, tested implementation lives in cf3da26; this PR ports the rotation subset verbatim, excluding the orthogonal approval_memory tool-scoping (a separate concern) and the session commands (/export, /sessions, /fork — each its own PR).

Part of the 0.4.0 milestone (#3). No dedicated tracking issue — this refills the feature dropped from #415.

What's included

aish-llm — rotation engine + request-path wiring

  • New rotation.rs (707 lines): RotationState, RetryPolicy, FailureKind, ApiAccount, ResolvedCredential, RotationSnapshot. On a recoverable failure (429 / usage-limit / 5xx / network / model error) it advances to the next API account, then the fallback model chain, and restores the primary after its cooldown window.
  • session.rs: chat_completion now routes through the rotation loop (fast path when no rotation); set_rotation / clear_rotation / use_rotation_account / current_rotation_account / rotation_snapshot accessors.

aish-config — schema

  • api_accounts: Vec<ApiAccountConfig> (extra keys under the same provider), fallback_models, recent_models, fallback_revert_on_cooldown. Additive + serde-defaulted → existing configs unaffected.

aish-shell — /model UX

  • /model (no arg) opens a picker spanning every configured endpoint: fetches each endpoint's model list, shows the URL beside each model, search by name or URL, recently-used entries float to the top.
  • switch_endpoint_model: switching endpoint demotes the outgoing primary into api_accounts (never silently lost) and promotes the target out of the pool (no duplicate).
  • a adds an account interactively (name/key/base + connectivity & tool-support verification); m opens a manage panel (delete accounts, add/remove fallback models).
  • rebuild_rotation keeps the live session in sync with config changes.

i18nshell.model picker/manage keys + shell.accounts + shell.common across all 6 locales.

Stacking

Based on #420 (feat/bugfixes) — the picker uses the Action / with_action / with_shimmer panel features that #420 adds to aish-ui/select.rs. Rebase to main after #420 merges. Disjoint from #417#419 (different files/regions), low conflict risk.

Excluded (separate PRs / future)

Verification

  • cargo fmt --check
  • cargo clippy --all-targets -- -D warnings
  • cargo test --workspace ✅ (incl. rotation + account_helpers tests)

Summary by CodeRabbit

  • New Features
    • Added multi-account API credential rotation with ordered model fallback, cooldowns, and recent-model tracking.
    • Upgraded the interactive model/account picker, including a dedicated /accounts management flow.
    • Improved searchable selection with relevance-ranked results and configurable single-key actions.
  • Bug Fixes
    • Improved skill install/reinstall reliability with transactional validation and rollback.
    • Adjusted resume picker handling so action submissions behave as a no-op.
  • Localization
    • Added/updated translated UI strings for model/account switching and account-rotation management across all supported languages.
  • Tests
    • Added coverage for rotation and i18n key presence in embedded locales.

@github-actions

Copy link
Copy Markdown
Contributor

Thanks for the pull request. A maintainer will review it when available.

Please keep the PR focused, explain the why in the description, and make sure local checks pass before requesting review.

Contribution guide: https://github.com/AI-Shell-Team/aish/blob/main/CONTRIBUTING.md

@github-actions

Copy link
Copy Markdown
Contributor

This pull request description looks incomplete. Please update the missing sections below before review.

Missing items:

  • User-visible Changes
  • Compatibility
  • Testing
  • Change Type
  • Scope

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds configurable API account rotation and model fallback, integrates recovery into LLM sessions and shell model management, introduces picker actions and localized UI text, centralizes skill metadata parsing, makes skill installation transactional, and exposes shared auto-vet state.

Changes

Account and model rotation

Layer / File(s) Summary
Rotation configuration contract
crates/aish-config/src/lib.rs, crates/aish-config/src/model.rs
Adds account, fallback-model, recent-model, and cooldown configuration fields.
Rotation state machine
crates/aish-llm/src/lib.rs, crates/aish-llm/src/rotation.rs
Adds failure classification, account rotation, model fallback, cooldown handling, snapshots, and tests.
Session rotation execution
crates/aish-llm/src/session.rs
Retries completions with resolved credentials and exposes rotation controls.
Shell configuration and model management
crates/aish-shell/src/ai_handler.rs, crates/aish-shell/src/app.rs
Builds, persists, rebuilds, switches, and interactively manages accounts and fallback models.
Picker actions and localized account UI
crates/aish-ui/src/select.rs, crates/aish-shell/src/resume_selector.rs, crates/aish-i18n/locales/*, crates/aish-i18n/src/manager.rs
Adds ranked search, single-key actions, action handling, localized strings, and locale coverage tests.

Skill metadata and transactional installation

Layer / File(s) Summary
Shared skill metadata validation
crates/aish-skills/src/manager.rs, crates/aish-skills/src/registry/verifier.rs
Centralizes frontmatter parsing and validation for skill loading and verification.
Transactional registry installation
crates/aish-skills/src/registry/mod.rs
Adds locked installation with cancellation, quarantine, validation, rollback, and reinstall preservation.

Auto-vet state access

Layer / File(s) Summary
Shared auto-vet handle
crates/aish-tools/src/skill_registry/skill_registry.rs
Exposes the shared auto-vet flag and tests external reset behavior.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant AiHandler
  participant LlmSession
  participant RotationState
  participant Provider
  User->>AiHandler: Select model or account
  AiHandler->>RotationState: Rebuild and apply state
  AiHandler->>LlmSession: Set rotation
  LlmSession->>RotationState: Resolve current credential
  LlmSession->>Provider: Send chat completion
  Provider-->>LlmSession: Return success or classified failure
  LlmSession->>RotationState: Advance account or fallback model
Loading

Possibly related PRs

  • AI-Shell-Team/aish#395: Directly overlaps with account-rotation configuration, LLM rotation plumbing, and shell management.
  • AI-Shell-Team/aish#415: Directly overlaps with multi-account rotation configuration and session/shell wiring.

Suggested labels: docs

Suggested reviewers: f16shen

Poem

A rabbit hops through accounts in a row,
Switching models when quotas say no.
Skills roll back clean, pickers rank bright,
Auto-vet resets by moonlight.
Hop, hop—the configs now flow!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: multi-account rotation and the model picker.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 18

🧹 Nitpick comments (9)
crates/aish-skills/src/registry/mod.rs (1)

319-336: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Silently swallowed rollback/cleanup errors can lose the skill directory without any diagnostic.

On the failure path, remove_dir_all(&live) and rename(s, &live) both discard their Result via let _ =, with no logging — unlike the stash-creation rename above (lines 304-308) which surfaces a descriptive error. If the restore rename fails, the skill effectively vanishes from live while the original content is stranded under the hidden .{slug}.reinstall-* stash path with no trace in the logs to explain why. The same applies to the success-path stash cleanup at line 322-324 (a failed cleanup just leaks a hidden dir silently).

Consider at least logging a tracing::warn! on these failure paths so an inconsistent post-install state is diagnosable instead of silent.

🩹 Proposed fix: log rollback failures
             Err(e) => {
                 // Failure: drop the failed replacement and restore the original.
-                let _ = std::fs::remove_dir_all(&live);
-                if let Some(s) = stash {
-                    let _ = std::fs::rename(s, &live);
+                if let Err(re) = std::fs::remove_dir_all(&live) {
+                    tracing::warn!(slug = %skill.slug, error = %re, "failed to remove failed replacement");
+                }
+                if let Some(s) = stash {
+                    if let Err(re) = std::fs::rename(&s, &live) {
+                        tracing::warn!(slug = %skill.slug, stash = ?s, error = %re, "failed to restore stashed skill after failed install");
+                    }
                 }
                 Err(e)
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/aish-skills/src/registry/mod.rs` around lines 319 - 336, Update the
success and failure cleanup paths in the surrounding reinstall operation to log
cleanup errors with tracing::warn! instead of discarding the Results. Cover
stash removal, live-directory removal, and restoration rename failures,
including the relevant paths and error details while preserving the existing
return and rollback behavior.
crates/aish-skills/src/manager.rs (1)

302-335: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Cache the compiled frontmatter regex instead of recompiling per call.

parse_skill_metadata now compiles FRONTMATTER_REGEX via regex::Regex::new(...) on every invocation. This function is the shared chokepoint called from the skill loader (once per SKILL.md during every reload), the installer's validate_installed_skill (every install), and the verifier's verify_skill_dir (every verify) — centralizing the parse also centralized (and multiplied) this recompilation cost. Compiling a regex is non-trivial relative to a simple match, and it's now on several hot/user-facing paths.

♻️ Proposed fix: compile once with a lazily-initialized static
+static FRONTMATTER_RE: std::sync::LazyLock<regex::Regex> =
+    std::sync::LazyLock::new(|| regex::Regex::new(FRONTMATTER_REGEX).expect("valid frontmatter regex"));
+
 pub fn parse_skill_metadata(content: &str) -> aish_core::Result<(SkillMetadata, &str)> {
-    let re = regex::Regex::new(FRONTMATTER_REGEX)
-        .map_err(|e| aish_core::AishError::Skill(format!("Invalid frontmatter regex: {}", e)))?;
-    let caps = re.captures(content).ok_or_else(|| {
+    let caps = FRONTMATTER_RE.captures(content).ok_or_else(|| {
         aish_core::AishError::Skill(
             "Invalid skill file format: must start with YAML frontmatter".into(),
         )
     })?;

Note: INSTALL_LOCKS in registry/mod.rs already uses std::sync::LazyLock for a similar static-init pattern, so this stays consistent with the codebase's existing idiom.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/aish-skills/src/manager.rs` around lines 302 - 335, Update
parse_skill_metadata to use a lazily initialized static LazyLock<regex::Regex>
for FRONTMATTER_REGEX, compiling the pattern only once and reusing it for all
calls. Preserve the existing invalid-regex error behavior as appropriate for
static initialization, while leaving frontmatter extraction and validation
unchanged.
crates/aish-shell/src/app.rs (4)

5119-5164: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Validate the account name before prompting for the key.

The duplicate-name check at Line 5154 runs after the user has already typed a secret API key and an endpoint; on collision everything is thrown away and the flow restarts from scratch. Move the check directly after the name prompt.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/aish-shell/src/app.rs` around lines 5119 - 5164, Move the existing
duplicate-name check using self.config.api_accounts and name to immediately
after the name prompt succeeds, before prompt_edit_value requests the API key or
base endpoint. Preserve the current already_exists message and early return, and
remove the later redundant check.

5481-5505: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Endpoint collection + fetch is copy-pasted from the entry block.

Lines 5484-5504 repeat Lines 5373-5394 verbatim. Extract a fn collect_endpoints(&self) -> Vec<(String, String)> and a fn fetch_all_models(endpoints) -> Vec<(String, String)> so a fix to either (the dedupe key, timeout, concurrency) lands in one place.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/aish-shell/src/app.rs` around lines 5481 - 5505, Extract the
duplicated endpoint-building and model-fetching logic from the entry flow and
the `PanelOutcome::Submitted(SearchSelectOutcome::Action('a', _))` branch into
shared `collect_endpoints` and `fetch_all_models` methods. Replace both inline
blocks with calls to these methods, preserving endpoint deduplication,
disabled-account filtering, API key handling, fetch timeout, and fetched-model
output behavior.

5387-5394: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Serial blocking /models fetches gate the panel with no feedback.

Each endpoint costs up to 4s and the calls run sequentially before anything renders, so a handful of configured accounts (some unreachable) leaves /model frozen for tens of seconds. Consider fetching concurrently, and/or printing a "fetching models…" line before the loop so the delay is attributable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/aish-shell/src/app.rs` around lines 5387 - 5394, The endpoint model
fetches in the loop over endpoints run serially without user feedback, delaying
panel rendering. Update the fetch flow around fetch_models_from_api to run
endpoint requests concurrently where supported, and emit a “fetching models…”
status before waiting; preserve aggregation of each model with its originating
base.

342-366: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Rotation-state construction is duplicated and already diverging. Both sites build the same RetryPolicy + RotationState and apply the same is_active() gate, but only one restores the previously active account — exactly the kind of drift a shared factory prevents (and it would remove the Default + field-reassign pattern flagged by Clippy's field_reassign_with_default).

  • crates/aish-shell/src/app.rs#L342-L366: extract the policy/RotationState/is_active block into fn rotation_state_from_config(config: &ConfigModel) -> Option<RotationState> (building RetryPolicy with struct-update syntax) and call it here.
  • crates/aish-shell/src/app.rs#L5334-L5358: call the same helper, then apply only the rebuild-specific step — re-selecting prev via use_account — before apply_rotation_state.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/aish-shell/src/app.rs` around lines 342 - 366, Extract the duplicated
rotation construction into rotation_state_from_config(config: &ConfigModel) ->
Option<RotationState), using RetryPolicy struct-update syntax and retaining the
is_active gate. In crates/aish-shell/src/app.rs lines 342-366, replace the
inline construction with this helper and set the session rotation when it
returns Some. In crates/aish-shell/src/app.rs lines 5334-5358, call the same
helper, re-select prev with use_account as the rebuild-specific step, then pass
the result to apply_rotation_state.
crates/aish-llm/src/session.rs (1)

522-542: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Error path takes the rotation lock three times, so concurrent turns can over-advance.

advance_on_error, model_exhaustion_error, and current each re-lock (Lines 532, 535, 540). If two in-flight completions on the same session both fail (agent loops share one LlmSession), they can each advance rotation for the same outage, silently burning an extra account or fallback slot. Holding one guard for the whole failure handling would make the transition atomic.

♻️ Single-guard failure handling
-                    let advanced = rotation.lock().unwrap().advance_on_error(kind);
-                    if !advanced {
-                        if kind == FailureKind::ModelError {
-                            let msg = rotation.lock().unwrap().model_exhaustion_error();
-                            return Err(AishError::Llm(msg));
-                        }
-                        return Err(err);
-                    }
-                    let next = rotation.lock().unwrap().current(&self.stream_ctx.api_base);
-                    tracing::info!(credential = %next.label, "rotated to next credential/model");
+                    let next_label = {
+                        let mut guard = rotation.lock().unwrap();
+                        if !guard.advance_on_error(kind) {
+                            if kind == FailureKind::ModelError {
+                                return Err(AishError::Llm(guard.model_exhaustion_error()));
+                            }
+                            return Err(err);
+                        }
+                        guard.current(&self.stream_ctx.api_base).label
+                    };
+                    tracing::info!(credential = %next_label, "rotated to next credential/model");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/aish-llm/src/session.rs` around lines 522 - 542, Update the Err
handling in the session request flow to acquire the rotation lock once and
retain that guard across advance_on_error, model_exhaustion_error, and current.
Use the single guard for all failure-path operations so credential/model
advancement and selection are atomic, while preserving the existing return
behavior.
crates/aish-config/src/model.rs (2)

594-596: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

weight is accepted but never used. RotationState::pick_available_account is plain round-robin (crates/aish-llm/src/rotation.rs), so a user setting weight: 10 gets no behavior change. Either document it as reserved in the user-facing config docs or drop it until weighted selection lands, so the config surface doesn't promise something it doesn't do.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/aish-config/src/model.rs` around lines 594 - 596, Resolve the unused
weight field in the configuration model: either remove weight and its default
from the relevant model and config surface, or mark it explicitly as reserved in
the user-facing configuration documentation until weighted selection is
implemented. Ensure RotationState::pick_available_account does not imply weight
is currently honored.

513-516: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Avoid persisting NUL-delimited entries in recent_models.

crates/aish-shell/src/app.rs:5564 stores model switches as "{model}\u{0}{base}", then ConfigLoader::save writes ConfigModel through serde_yaml_string; serde_yaml does not support raw U+0000 in scalars, so this can become invalid or non-portable YAML and can break config round-tripping or editors. Store a small struct instead, e.g. RecentModel { model, api_base }, and remove the manual split_once('\u{0}') parsing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/aish-config/src/model.rs` around lines 513 - 516, Replace the
String-based recent_models representation in ConfigModel with a serializable
RecentModel struct containing model and api_base fields. Update the app.rs
model-switch storage path to construct RecentModel values instead of
concatenating with a NUL delimiter, and update consumers to access the fields
directly rather than using split_once('\u{0}').
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/aish-i18n/locales/de-DE.yaml`:
- Line 289: Update the fetch_failed_subtitle translation to use neutral wording
that does not claim the endpoint is unreachable when the model list is merely
empty. Preserve the existing key and indicate only that no models are currently
available or that only current/history options can be used.

In `@crates/aish-i18n/locales/es-ES.yaml`:
- Around line 292-315: Correct the missing ñ in the Spanish locale entries
action_add, fallback_added, and manage_footer: use “añadir” and “Añadido”
consistently with the correctly accented added and no_extra_hint strings,
without changing unrelated translations.
- Around line 287-372: In crates/aish-i18n/locales/es-ES.yaml (lines 287-372),
fr-FR.yaml (lines 287-372), ja-JP.yaml (lines 287-372), and zh-CN.yaml (lines
632-717), move the picker_title through manage_empty block under shell.model,
and restore conclusion_fixed through no_evidence under shell.failure_diagnose;
apply the same correction to de-DE.yaml. Preserve all translations and existing
key contents while fixing the YAML parent nesting.

In `@crates/aish-llm/src/rotation.rs`:
- Around line 433-480: Update current_account_name() and snapshot() so they
resolve the current account through the same enabled-account fallback behavior
as current(). When current_account points to a disabled account, return the
first enabled account’s name instead of the disabled name or None, while
preserving the existing selection for enabled accounts and the empty result when
no enabled account exists.

In `@crates/aish-llm/src/session.rs`:
- Around line 498-544: Update the rotation-aware request flow around
stream_simple and the model reporting methods model_name()/resolved_model_name()
so the successfully used fallback credential/model is reflected in observability
and the shell response footer. Reuse rotation_snapshot() or record the resolved
model from the successful attempt, ensuring reporting prefers the active rotated
model while preserving primary-model behavior when no rotation is active.

In `@crates/aish-shell/src/app.rs`:
- Around line 5373-5386: Update endpoint de-duplication in the endpoint
construction flow to distinguish accounts sharing an api_base but having
different api_key values, using the (base, key) pair or equivalent account
identity. Ensure the select branch lookup and switch_endpoint_model resolve the
selected model to its intended account credentials rather than the first
endpoint inserted for that base.
- Around line 5614-5632: Add a y/N confirmation using the existing
confirm_action helper before deleting the account in the SearchSelectOutcome
action 'd' branch. Only execute api_accounts.retain and
persist_config_and_rebuild_rotation when confirmation is affirmative; otherwise
leave the stored credential unchanged, including when deletion is triggered with
an empty query.
- Around line 371-391: Reserve the synthetic "primary" account name across the
rotation-account flow: update unique_account_name and accounts_add_interactive
to reject or rename user accounts named "primary", and ensure any
account-labeling paths apply the same rule. Preserve unambiguous use_account,
current_rotation_account, rebuild_rotation, and manage-panel delete-by-name
behavior.
- Around line 5526-5554: Update the endpoint_changed handling so the outgoing
account added with prev_key and prev_base is not removed when the incoming key
is the same but the base differs. In the api_accounts retain logic, only remove
existing entries matching both the incoming credential and incoming API base, or
move the retain before the preservation push; preserve removal of the target
account without discarding the saved outgoing endpoint.
- Around line 5321-5329: Update the translation key passed to t_with_args in the
config-save warning error path to the existing shell.config_save_warning key.
Preserve the current error argument construction and eprintln! behavior, and use
the matching key already referenced elsewhere in this file.
- Around line 5286-5301: Update the account persistence block that constructs
ApiAccountConfig to always assign the verified base value to api_base, rather
than converting it to None when it matches self.config.api_base. Keep the
persisted endpoint pinned to the account’s verified provider so later changes
through switch_endpoint_model cannot redirect the account.
- Around line 5434-5445: Update the api_accounts iteration that builds
SearchSelectItem entries to skip accounts where a.disabled is true, matching the
endpoint scan’s filtering behavior. Ensure disabled accounts cannot contribute
models to the selection list or trigger switch_endpoint_model with the primary
key fallback.
- Around line 5564-5570: Replace the manual ConfigLoader::save call and
rebuild_rotation sequence in the recent-model update flow with the existing
persist_config_and_rebuild_rotation helper. Ensure the helper’s failure warning
and rotation rebuild are used while preserving the recent_models update and
switch-success behavior.

In `@crates/aish-skills/src/registry/mod.rs`:
- Around line 284-318: Update the per-slug lock acquisition in
install_transactional to recover from a poisoned mutex instead of calling unwrap
and panicking. Preserve the existing install transaction and guard lifetime,
allowing subsequent installs for the same skill slug to proceed and return
normal errors.

In `@crates/aish-tools/src/skill_registry/skill_registry.rs`:
- Around line 205-215: The /forget-approvals reset path must clear both
approval_memory and the session-scoped auto-vet decision. Update the installed
shell command to use SkillRegistry::auto_vet_handle() and reset its AtomicBool
alongside approval memory, then add an integration test verifying both decisions
are cleared together.

In `@crates/aish-ui/src/select.rs`:
- Around line 468-486: Remove bare-letter action handling from the query input
path in the select component, including the action lookup around
filtered_entries and PanelEvent::Submit; action keys must not intercept normal
search characters. Require modified/non-letter shortcuts or an explicit action
mode instead, and update the picker footer text plus related localizations to
document the new interaction.
- Around line 169-173: Normalize the key in Select's with_action method before
storing it in self.actions, so configured uppercase keys match the lowercased
characters processed by handle_event. Preserve the existing label and
builder-return behavior.
- Around line 473-484: Update the action handling in the query-empty branch to
return PanelEvent::Continue when no item is highlighted or its value cannot be
resolved, instead of submitting Action with an empty string. Preserve
PanelEvent::Submit(SearchSelectOutcome::Action(...)) only when
filtered_entries().get(self.selected) resolves to a SelectEntry::Item with a
valid item value.

---

Nitpick comments:
In `@crates/aish-config/src/model.rs`:
- Around line 594-596: Resolve the unused weight field in the configuration
model: either remove weight and its default from the relevant model and config
surface, or mark it explicitly as reserved in the user-facing configuration
documentation until weighted selection is implemented. Ensure
RotationState::pick_available_account does not imply weight is currently
honored.
- Around line 513-516: Replace the String-based recent_models representation in
ConfigModel with a serializable RecentModel struct containing model and api_base
fields. Update the app.rs model-switch storage path to construct RecentModel
values instead of concatenating with a NUL delimiter, and update consumers to
access the fields directly rather than using split_once('\u{0}').

In `@crates/aish-llm/src/session.rs`:
- Around line 522-542: Update the Err handling in the session request flow to
acquire the rotation lock once and retain that guard across advance_on_error,
model_exhaustion_error, and current. Use the single guard for all failure-path
operations so credential/model advancement and selection are atomic, while
preserving the existing return behavior.

In `@crates/aish-shell/src/app.rs`:
- Around line 5119-5164: Move the existing duplicate-name check using
self.config.api_accounts and name to immediately after the name prompt succeeds,
before prompt_edit_value requests the API key or base endpoint. Preserve the
current already_exists message and early return, and remove the later redundant
check.
- Around line 5481-5505: Extract the duplicated endpoint-building and
model-fetching logic from the entry flow and the
`PanelOutcome::Submitted(SearchSelectOutcome::Action('a', _))` branch into
shared `collect_endpoints` and `fetch_all_models` methods. Replace both inline
blocks with calls to these methods, preserving endpoint deduplication,
disabled-account filtering, API key handling, fetch timeout, and fetched-model
output behavior.
- Around line 5387-5394: The endpoint model fetches in the loop over endpoints
run serially without user feedback, delaying panel rendering. Update the fetch
flow around fetch_models_from_api to run endpoint requests concurrently where
supported, and emit a “fetching models…” status before waiting; preserve
aggregation of each model with its originating base.
- Around line 342-366: Extract the duplicated rotation construction into
rotation_state_from_config(config: &ConfigModel) -> Option<RotationState), using
RetryPolicy struct-update syntax and retaining the is_active gate. In
crates/aish-shell/src/app.rs lines 342-366, replace the inline construction with
this helper and set the session rotation when it returns Some. In
crates/aish-shell/src/app.rs lines 5334-5358, call the same helper, re-select
prev with use_account as the rebuild-specific step, then pass the result to
apply_rotation_state.

In `@crates/aish-skills/src/manager.rs`:
- Around line 302-335: Update parse_skill_metadata to use a lazily initialized
static LazyLock<regex::Regex> for FRONTMATTER_REGEX, compiling the pattern only
once and reusing it for all calls. Preserve the existing invalid-regex error
behavior as appropriate for static initialization, while leaving frontmatter
extraction and validation unchanged.

In `@crates/aish-skills/src/registry/mod.rs`:
- Around line 319-336: Update the success and failure cleanup paths in the
surrounding reinstall operation to log cleanup errors with tracing::warn!
instead of discarding the Results. Cover stash removal, live-directory removal,
and restoration rename failures, including the relevant paths and error details
while preserving the existing return and rollback behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a97116ed-e63e-422a-8574-c043198eba07

📥 Commits

Reviewing files that changed from the base of the PR and between 78117a9 and 27a852e.

📒 Files selected for processing (19)
  • crates/aish-config/src/lib.rs
  • crates/aish-config/src/model.rs
  • crates/aish-i18n/locales/de-DE.yaml
  • crates/aish-i18n/locales/en-US.yaml
  • crates/aish-i18n/locales/es-ES.yaml
  • crates/aish-i18n/locales/fr-FR.yaml
  • crates/aish-i18n/locales/ja-JP.yaml
  • crates/aish-i18n/locales/zh-CN.yaml
  • crates/aish-llm/src/lib.rs
  • crates/aish-llm/src/rotation.rs
  • crates/aish-llm/src/session.rs
  • crates/aish-shell/src/ai_handler.rs
  • crates/aish-shell/src/app.rs
  • crates/aish-shell/src/resume_selector.rs
  • crates/aish-skills/src/manager.rs
  • crates/aish-skills/src/registry/mod.rs
  • crates/aish-skills/src/registry/verifier.rs
  • crates/aish-tools/src/skill_registry/skill_registry.rs
  • crates/aish-ui/src/select.rs

Comment thread crates/aish-i18n/locales/es-ES.yaml Outdated
Comment thread crates/aish-i18n/locales/es-ES.yaml Outdated
Comment thread crates/aish-llm/src/rotation.rs
Comment thread crates/aish-llm/src/session.rs
Comment thread crates/aish-shell/src/app.rs
Comment thread crates/aish-skills/src/registry/mod.rs
Comment thread crates/aish-tools/src/skill_registry/skill_registry.rs
Comment thread crates/aish-ui/src/select.rs
Comment thread crates/aish-ui/src/select.rs
Comment thread crates/aish-ui/src/select.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/aish-i18n/locales/de-DE.yaml (1)

335-335: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fix the malformed manage subtitle.

The current text starts with a stray d and is grammatically incomplete, so the manage panel renders broken German text.

Suggested fix
-    manage_subtitle: "d markierten Eintrag loeschen"
+    manage_subtitle: "d: Markierten Eintrag löschen"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/aish-i18n/locales/de-DE.yaml` at line 335, Update the manage_subtitle
translation in de-DE.yaml to remove the stray leading “d” and provide
grammatically complete German text describing deletion of the selected entry.
🧹 Nitpick comments (1)
crates/aish-i18n/src/manager.rs (1)

306-332: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover all newly introduced translation keys.

This test checks only nine keys, so missing entries such as shell.model.fetch_failed_subtitle, deletion/edit/fallback labels, most shell.accounts.*, and relocated shell.common.* keys would still pass and render raw key names because t() falls back to the key itself. Maintain one complete expected-key list for this cohort.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/aish-i18n/src/manager.rs` around lines 306 - 332, Expand the
expected-key list in picker_keys_present_in_all_embedded_locales to include
every newly introduced translation key in the model, accounts, and relocated
common-key cohorts, including fetch-failure, deletion/edit/fallback, and
remaining shell.accounts entries. Keep one centralized list and continue
asserting each key resolves to a translated value for every EMBEDDED_LOCALES
entry.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/aish-i18n/locales/de-DE.yaml`:
- Line 310: Update the fetch_failed_subtitle translations to use neutral wording
for an unavailable or empty model list without claiming the endpoint is
unreachable: change crates/aish-i18n/locales/de-DE.yaml lines 310-310 to the
requested neutral German wording, and update crates/aish-i18n/locales/fr-FR.yaml
lines 310-310 with the equivalent neutral French wording.

---

Outside diff comments:
In `@crates/aish-i18n/locales/de-DE.yaml`:
- Line 335: Update the manage_subtitle translation in de-DE.yaml to remove the
stray leading “d” and provide grammatically complete German text describing
deletion of the selected entry.

---

Nitpick comments:
In `@crates/aish-i18n/src/manager.rs`:
- Around line 306-332: Expand the expected-key list in
picker_keys_present_in_all_embedded_locales to include every newly introduced
translation key in the model, accounts, and relocated common-key cohorts,
including fetch-failure, deletion/edit/fallback, and remaining shell.accounts
entries. Keep one centralized list and continue asserting each key resolves to a
translated value for every EMBEDDED_LOCALES entry.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: acd79cc5-85ac-4449-a6c4-e7283c1cea52

📥 Commits

Reviewing files that changed from the base of the PR and between 267101a and 1ac9492.

📒 Files selected for processing (6)
  • crates/aish-i18n/locales/de-DE.yaml
  • crates/aish-i18n/locales/es-ES.yaml
  • crates/aish-i18n/locales/fr-FR.yaml
  • crates/aish-i18n/locales/ja-JP.yaml
  • crates/aish-i18n/locales/zh-CN.yaml
  • crates/aish-i18n/src/manager.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • crates/aish-i18n/locales/zh-CN.yaml
  • crates/aish-i18n/locales/ja-JP.yaml
  • crates/aish-i18n/locales/es-ES.yaml

Comment thread crates/aish-i18n/locales/de-DE.yaml

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/aish-i18n/locales/es-ES.yaml`:
- Line 317: Update the fetch_failed_subtitle translation in
crates/aish-i18n/locales/es-ES.yaml at lines 317-317 to use neutral
unavailable/empty-list wording instead of “endpoint inalcanzable”; make the
equivalent wording change in crates/aish-i18n/locales/zh-CN.yaml at lines
672-672, without implying a connectivity failure.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b209dc26-2250-49ad-a30a-36ea54747376

📥 Commits

Reviewing files that changed from the base of the PR and between 1ac9492 and 7cc894f.

📒 Files selected for processing (11)
  • crates/aish-i18n/locales/de-DE.yaml
  • crates/aish-i18n/locales/en-US.yaml
  • crates/aish-i18n/locales/es-ES.yaml
  • crates/aish-i18n/locales/fr-FR.yaml
  • crates/aish-i18n/locales/ja-JP.yaml
  • crates/aish-i18n/locales/zh-CN.yaml
  • crates/aish-i18n/src/manager.rs
  • crates/aish-llm/src/rotation.rs
  • crates/aish-shell/src/app.rs
  • crates/aish-skills/src/registry/mod.rs
  • crates/aish-ui/src/select.rs
💤 Files with no reviewable changes (1)
  • crates/aish-i18n/locales/en-US.yaml
🚧 Files skipped from review as they are similar to previous changes (6)
  • crates/aish-i18n/src/manager.rs
  • crates/aish-i18n/locales/ja-JP.yaml
  • crates/aish-ui/src/select.rs
  • crates/aish-llm/src/rotation.rs
  • crates/aish-skills/src/registry/mod.rs
  • crates/aish-shell/src/app.rs

Comment thread crates/aish-i18n/locales/es-ES.yaml
jexShain added 5 commits July 29, 2026 16:06
…ank search by relevance

Skill registry hardening (aish-skills / aish-tools):
- parse_skill_metadata is now the single source of truth for "would the
  loader accept this file"; the loader, installer, and verifier all route
  through it so install/verify-time validation cannot drift from load-time.
  Rejects context=fork/subagent skills that do not declare an agent.
- Reinstalls are transactional: the existing skill is stashed (same-FS atomic
  rename) before the replacement is downloaded + validated, and restored on
  failure — a bad replacement can no longer overwrite/quarantine a previously
  trusted skill. Path-escaping slugs are rejected before the stash path is
  built.

Search panel (aish-ui):
- SearchSelect ranks results by relevance (exact > prefix > substring) and
  adds an opt-in single-key Action outcome (intercepted only on an empty
  query so typing never triggers an action).

Refs AI-Shell-Team#414 (skill hardening + search-ranking slice; the remaining AI-Shell-Team#414 items
— approval memory, rotation, /export & /sessions fixes — ship with their
feature PRs or a follow-up).
Restore the /model multi-account + automatic rotation feature that was
deferred when the closed 0.4.0 mega-PR (AI-Shell-Team#415) was split. Ported from the
original combined implementation, excluding the orthogonal approval_memory
tool-scoping (separate concern).

- aish-llm: new rotation engine (RotationState/RetryPolicy/FailureKind) that
  advances to the next API account or fallback model on 429/usage-limit/5xx/
  network errors, wired into the chat-completion request path; restores the
  primary after its cooldown window.
- aish-config: api_accounts + ApiAccountConfig, fallback_models,
  recent_models, fallback_revert_on_cooldown.
- aish-shell: /model opens a picker spanning every configured endpoint
  (fetches model lists, search by name/URL, recents float up); switch_endpoint
  demotes the outgoing primary into api_accounts and promotes the target;
  interactive add-account + manage panel (add/remove accounts & fallbacks);
  rebuild_rotation keeps the live session in sync with config.
- i18n: shell.model picker/manage keys + shell.accounts + shell.common (x6).

Excludes approval_memory (independent tool-scoped approval feature) and the
session commands (/export, /sessions, /fork) which ship in their own PRs.
select.rs render_footer now composes the footer from the registered
(key, i18n-label) actions plus any explicit footer hint, so the action_*
labels (action_add/action_manage/action_del/action_fallback) are actually
displayed instead of being dead values computed but never rendered.

The model picker / manage footers drop their hardcoded action descriptions
(now auto-rendered from with_action) and keep only the Esc hint, so the
visible hints stay in sync with the registered actions.
The picker i18n keys (picker_title/subtitle/search/...) were inserted
after the first `verify_failed:` in the YAML. For non-English locales
that is `shell.failure_diagnose.verify_failed`, because the insertion
anchor matched the English value "model validation", which is absent
from the translated values — so the fallback picked the wrong section.
The keys landed under shell.failure_diagnose.* instead of shell.model.*,
and t("shell.model.picker_title") missed, making the /model panel render
raw key strings in zh-CN/de-DE/es-ES/fr-FR/ja-JP (en-US was correct).

Move the picker + accounts + common block to after the real
shell.model verify_failed in all 5 non-en-US locales, and add a
regression test asserting the picker/accounts/common keys resolve in
every embedded locale.
Security/data-integrity:
- switch_endpoint_model: scope the promote-retain on (key, base) so a
  same-key/different-base switch no longer drops the just-preserved
  outgoing endpoint; key the preserve check the same way.
- accounts_add: always persist the verified api_base (Some) so a later
  primary-endpoint switch can't redirect a credential to another host.
- model_picker: skip disabled accounts in the model list (their endpoints
  are excluded, so selecting one fell back to the primary key) and
  de-duplicate endpoints by (base, key) so same-host/different-key
  accounts don't collapse.

Correctness/quality:
- persist_config_and_rebuild_rotation: reuse the existing
  shell.config_save_warning key; drop the redundant shell.common one.
- switch_endpoint_model: route persistence through the shared persist
  helper (warns on save failure instead of swallowing it).
- reserve the "primary" account name so a user account can't collide with
  the synthetic index-0 entry.
- rotation: current_account_name()/snapshot() now fall back to the first
  enabled account when the selected one is disabled, matching current().
- skills registry: recover from a poisoned per-slug install lock instead
  of panicking (a single poison used to break that slug for the process).
- select panel: normalize action keys on register; don't emit an Action
  event when no item is selected.
- manage panel: confirm (y/N) before deleting a stored credential.
- es-ES: restore the tilde in añadir/Añadido.

i18n: the failure_diagnose tail (conclusion_fixed..no_evidence) was
stranded under shell.common in the non-English locales by the earlier
splice; move it back under shell.failure_diagnose and add a regression
assertion.
@jexShain
jexShain force-pushed the feat/model-rotation branch from 7cc894f to a47d418 Compare July 29, 2026 08:07

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/aish-skills/src/registry/mod.rs (1)

319-336: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Cleanup/restore failures are silently swallowed, weakening the "never overwrite the original" guarantee.

Both remove_dir_all(&live) (line 329) and the restore rename(s, &live) (line 331) discard their Result via let _ =. If remove_dir_all fails or only partially removes the failed replacement, live can remain a non-empty directory; POSIX rename(2) then fails when the destination directory exists and isn't empty, so the restore also silently fails — leaving the original skill orphaned under the hidden .{slug}.reinstall-* stash path and a broken directory at live. The comment at lines 279-283 states this "can never" happen, but nothing surfaces or prevents it when the two fs ops themselves fail.

🩹 Proposed fix: surface failures instead of swallowing them
             Err(e) => {
                 // Failure: drop the failed replacement and restore the original.
-                let _ = std::fs::remove_dir_all(&live);
-                if let Some(s) = stash {
-                    let _ = std::fs::rename(s, &live);
-                }
+                if let Err(remove_err) = std::fs::remove_dir_all(&live) {
+                    tracing::warn!(error = %remove_err, dir = ?live, "failed to remove failed install replacement");
+                }
+                if let Some(s) = stash {
+                    if let Err(restore_err) = std::fs::rename(&s, &live) {
+                        tracing::error!(
+                            error = %restore_err, stash = ?s, dir = ?live,
+                            "failed to restore original skill after failed reinstall; \
+                             original preserved at stash path, not at its live location"
+                        );
+                    }
+                }
                 Err(e)
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/aish-skills/src/registry/mod.rs` around lines 319 - 336, Update the
reinstall transaction’s failure branch around remove_dir_all and the stash
restore rename so neither filesystem Result is discarded. Surface cleanup and
restore failures through the existing error path, and ensure the original error
is augmented or replaced when restoration fails so callers can detect that the
original was not restored. Preserve successful cleanup and restoration behavior.
🧹 Nitpick comments (1)
crates/aish-i18n/src/manager.rs (1)

307-336: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover all newly introduced translation keys.

The test checks only a small representative subset, so missing keys such as fallback_*, account state/action/verification strings, or errors.litellm_not_installed can still reach users as raw key names. Build the assertion list from the complete new key contract, or compare each locale’s keys against the canonical locale.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/aish-i18n/src/manager.rs` around lines 307 - 336, The
picker_keys_present_in_all_embedded_locales test does not cover the complete set
of newly introduced translation keys. Expand its assertion list to include every
new fallback, account state/action/verification, and
errors.litellm_not_installed key, or compare each embedded locale against the
canonical locale’s complete key set while preserving the missing-key failure
diagnostics.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@crates/aish-skills/src/registry/mod.rs`:
- Around line 319-336: Update the reinstall transaction’s failure branch around
remove_dir_all and the stash restore rename so neither filesystem Result is
discarded. Surface cleanup and restore failures through the existing error path,
and ensure the original error is augmented or replaced when restoration fails so
callers can detect that the original was not restored. Preserve successful
cleanup and restoration behavior.

---

Nitpick comments:
In `@crates/aish-i18n/src/manager.rs`:
- Around line 307-336: The picker_keys_present_in_all_embedded_locales test does
not cover the complete set of newly introduced translation keys. Expand its
assertion list to include every new fallback, account state/action/verification,
and errors.litellm_not_installed key, or compare each embedded locale against
the canonical locale’s complete key set while preserving the missing-key failure
diagnostics.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6738a439-9809-473d-a2e9-44a8b7caf881

📥 Commits

Reviewing files that changed from the base of the PR and between 7cc894f and a47d418.

📒 Files selected for processing (20)
  • crates/aish-config/src/lib.rs
  • crates/aish-config/src/model.rs
  • crates/aish-i18n/locales/de-DE.yaml
  • crates/aish-i18n/locales/en-US.yaml
  • crates/aish-i18n/locales/es-ES.yaml
  • crates/aish-i18n/locales/fr-FR.yaml
  • crates/aish-i18n/locales/ja-JP.yaml
  • crates/aish-i18n/locales/zh-CN.yaml
  • crates/aish-i18n/src/manager.rs
  • crates/aish-llm/src/lib.rs
  • crates/aish-llm/src/rotation.rs
  • crates/aish-llm/src/session.rs
  • crates/aish-shell/src/ai_handler.rs
  • crates/aish-shell/src/app.rs
  • crates/aish-shell/src/resume_selector.rs
  • crates/aish-skills/src/manager.rs
  • crates/aish-skills/src/registry/mod.rs
  • crates/aish-skills/src/registry/verifier.rs
  • crates/aish-tools/src/skill_registry/skill_registry.rs
  • crates/aish-ui/src/select.rs
🚧 Files skipped from review as they are similar to previous changes (15)
  • crates/aish-config/src/lib.rs
  • crates/aish-shell/src/ai_handler.rs
  • crates/aish-llm/src/lib.rs
  • crates/aish-i18n/locales/de-DE.yaml
  • crates/aish-i18n/locales/es-ES.yaml
  • crates/aish-shell/src/resume_selector.rs
  • crates/aish-i18n/locales/en-US.yaml
  • crates/aish-skills/src/manager.rs
  • crates/aish-i18n/locales/ja-JP.yaml
  • crates/aish-skills/src/registry/verifier.rs
  • crates/aish-llm/src/rotation.rs
  • crates/aish-llm/src/session.rs
  • crates/aish-config/src/model.rs
  • crates/aish-ui/src/select.rs
  • crates/aish-shell/src/app.rs

@jexShain
jexShain merged commit 5be48d2 into AI-Shell-Team:main Jul 29, 2026
8 checks passed
@jexShain
jexShain deleted the feat/model-rotation branch July 29, 2026 09:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant