From 7f5f6a09ddd7b7cc85d189b68ac7499b76e780a3 Mon Sep 17 00:00:00 2001 From: "Andrei G." Date: Tue, 25 Aug 2026 04:25:13 +0200 Subject: [PATCH 1/2] build: raise MSRV to Rust 1.98 and resolve new clippy lints Rust 1.98's clippy introduced clippy::unused_async_trait_impl and clippy::chunks_exact_to_as_chunks, which fired on ~120 pre-existing call sites across the workspace and blocked the MSRV bump documented in ci.yml's toolchain-pin rationale comment. Side-effect-free async fn bodies are converted to fn -> impl Future + Send { std::future::ready(body) }. Side-effecting bodies (mutation, panic!, process-global calls) stay genuine async fn with #[allow(clippy::unused_async_trait_impl)], since std::future::ready evaluates eagerly at construction time rather than at first poll and would otherwise fire even when a tokio::select! arm loses its race and is dropped unpolled. The Channel trait now documents this contract explicitly. Bumps rust-version to "1.98" in Cargo.toml and updates all CI toolchain pins accordingly. Closes #6746 --- .github/workflows/ci.yml | 36 ++- AGENTS.md | 4 +- CHANGELOG.md | 2 + Cargo.toml | 2 +- README.md | 4 +- crates/zeph-a2a/README.md | 2 +- crates/zeph-acp/README.md | 4 +- crates/zeph-acp/src/fs.rs | 7 +- crates/zeph-acp/src/terminal.rs | 7 +- crates/zeph-agent-context/README.md | 4 +- crates/zeph-agent-context/src/compaction.rs | 23 +- crates/zeph-agent-feedback/README.md | 4 +- crates/zeph-agent-persistence/README.md | 2 +- crates/zeph-agent-tools/README.md | 2 +- crates/zeph-bench/README.md | 4 +- crates/zeph-bench/src/channel.rs | 9 + .../src/loaders/tau2_bench/envs/airline.rs | 7 +- .../src/loaders/tau2_bench/envs/retail.rs | 7 +- crates/zeph-bench/src/runner.rs | 7 +- crates/zeph-channels/README.md | 2 +- crates/zeph-channels/src/confirm.rs | 1 + crates/zeph-channels/src/discord/mod.rs | 6 +- crates/zeph-channels/src/json_cli.rs | 58 +++-- crates/zeph-channels/src/slack/mod.rs | 6 +- crates/zeph-commands/README.md | 2 +- crates/zeph-common/README.md | 2 +- crates/zeph-config/README.md | 2 +- crates/zeph-context/README.md | 2 +- crates/zeph-context/src/fidelity.rs | 116 +++++---- crates/zeph-core/README.md | 2 +- crates/zeph-core/src/agent/speculative/mod.rs | 17 +- .../src/agent/speculative/stream_drainer.rs | 90 ++++--- crates/zeph-core/src/agent/state/security.rs | 5 +- .../zeph-core/src/agent/subagent_commands.rs | 17 +- .../src/agent/tests/agent_tests/common.rs | 35 ++- .../tests/agent_tests/owner_key_tests.rs | 9 +- .../tests/confirmation_propagation_tests.rs | 17 +- .../src/agent/tests/inline_tool_loop_tests.rs | 33 ++- .../agent/tests/pre_execution_audit_tests.rs | 14 +- .../src/agent/tests/shutdown_summary_tests.rs | 26 +- .../tests/consent_gate_toctou_tests.rs | 47 ++-- .../tool_execution/tests/native_tests.rs | 32 ++- crates/zeph-core/src/channel.rs | 108 ++++++-- .../zeph-core/src/lsp_hooks/test_helpers.rs | 5 +- crates/zeph-core/src/memory_tools.rs | 7 +- crates/zeph-core/src/overflow_tools.rs | 7 +- crates/zeph-core/src/pipeline/builtin.rs | 16 +- crates/zeph-core/src/pipeline/mod.rs | 46 +++- crates/zeph-core/src/skill_invoker.rs | 7 +- crates/zeph-core/src/skill_loader.rs | 7 +- crates/zeph-core/src/skill_trust_gate.rs | 18 +- crates/zeph-core/src/testing.rs | 11 +- crates/zeph-core/tests/turn_lifecycle.rs | 30 ++- crates/zeph-db/README.md | 4 +- crates/zeph-durable/README.md | 4 +- crates/zeph-experiments/README.md | 2 +- crates/zeph-gateway/README.md | 2 +- crates/zeph-index/README.md | 2 +- crates/zeph-llm/README.md | 2 +- crates/zeph-llm/src/candle_whisper.rs | 4 +- crates/zeph-llm/src/extractor.rs | 38 ++- crates/zeph-llm/src/provider.rs | 53 ++-- crates/zeph-llm/src/provider_dyn.rs | 14 +- crates/zeph-mcp/README.md | 2 +- crates/zeph-mcp/src/client.rs | 69 ++--- crates/zeph-mcp/src/testing.rs | 8 +- crates/zeph-memory/README.md | 2 +- crates/zeph-memory/src/db_vector_store.rs | 4 +- crates/zeph-memory/src/embedding_store.rs | 4 +- crates/zeph-memory/src/response_cache.rs | 4 +- crates/zeph-orchestration/README.md | 2 +- crates/zeph-orchestration/src/aggregator.rs | 8 +- crates/zeph-orchestration/src/plan_cache.rs | 6 +- crates/zeph-orchestration/src/planner.rs | 8 +- crates/zeph-orchestration/src/verifier.rs | 24 +- crates/zeph-plugins/README.md | 2 +- crates/zeph-sanitizer/README.md | 2 +- crates/zeph-sanitizer/src/nli.rs | 52 ++-- crates/zeph-scheduler/README.md | 4 +- crates/zeph-session/README.md | 2 +- crates/zeph-session/src/llm_condenser.rs | 10 +- crates/zeph-skills/README.md | 2 +- crates/zeph-skills/src/embedding.rs | 6 +- crates/zeph-skills/src/matcher.rs | 11 +- crates/zeph-subagent/README.md | 2 +- crates/zeph-tools/README.md | 2 +- crates/zeph-tools/src/adversarial_gate.rs | 58 +++-- crates/zeph-tools/src/audit.rs | 2 +- crates/zeph-tools/src/composite.rs | 98 ++++--- .../zeph-tools/src/compression/decorator.rs | 27 +- crates/zeph-tools/src/cwd.rs | 12 +- crates/zeph-tools/src/diagnostics.rs | 7 +- crates/zeph-tools/src/executor.rs | 98 ++++--- crates/zeph-tools/src/file.rs | 9 +- crates/zeph-tools/src/moderation.rs | 7 +- crates/zeph-tools/src/policy_gate.rs | 67 +++-- crates/zeph-tools/src/scope.rs | 25 +- crates/zeph-tools/src/search/mod.rs | 7 +- crates/zeph-tools/src/search_code.rs | 7 +- crates/zeph-tools/src/shadow_probe.rs | 42 ++- crates/zeph-tools/src/time.rs | 75 +++--- crates/zeph-tools/src/tool_filter.rs | 43 ++-- crates/zeph-tools/src/trust_gate.rs | 63 +++-- crates/zeph-tui/README.md | 2 +- crates/zeph-tui/src/channel.rs | 13 + crates/zeph-vault/README.md | 2 +- crates/zeph-worktree/README.md | 2 +- crates/zeph-worktree/src/git_runner.rs | 1 + .../brd.md | 2 +- specs/BRD.md | 2 +- specs/SRS.md | 2 +- specs/constitution.md | 2 +- src/acp.rs | 77 +++--- src/agent_setup.rs | 27 +- src/circuit_breaker_exporter.rs | 1 + src/daemon.rs | 39 ++- src/gateway_spawn.rs | 22 +- src/runner.rs | 19 +- src/scheduler.rs | 15 +- tests/integration.rs | 241 ++++++++++++------ tests/performance_agent_integration.rs | 45 +++- 121 files changed, 1647 insertions(+), 815 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8559073c1..4e40d6f01 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,13 +1,11 @@ name: CI -# `toolchain:` is pinned to "1.97" (matches root Cargo.toml's rust-version) across every +# `toolchain:` is pinned to "1.98" (matches root Cargo.toml's rust-version) across every # job below, rather than a rolling `stable`. dtolnay/rust-toolchain@stable with an -# unpinned toolchain floats to whatever the latest stable release is at run time; Rust -# 1.98.0 introduced new clippy lints (clippy::unused_async_trait_impl, -# clippy::chunks_exact_to_as_chunks) that broke this workflow on ~105 pre-existing call -# sites the moment CI first ran on it, unrelated to whatever PR happened to trigger that -# run. Pin stays until the pre-existing lint violations are cleaned up (#6746); bump -# deliberately alongside that cleanup, not as a side effect of a rolling toolchain. +# unpinned toolchain floats to whatever the latest stable release is at run time, which +# has previously broken CI the moment a new clippy lint landed unrelated to whatever PR +# triggered the run (#6746). Bump the pin deliberately alongside a cleanup pass for any +# new lints the target version introduces, not as a side effect of a rolling toolchain. on: push: @@ -164,7 +162,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable with: - toolchain: "1.97" + toolchain: "1.98" components: clippy - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 with: @@ -173,7 +171,7 @@ jobs: run: cargo clippy --profile ci --workspace --all-targets --features ${{ matrix.features }} -- -D warnings msrv: - name: MSRV check (1.97, ${{ matrix.label }}) + name: MSRV check (1.98, ${{ matrix.label }}) needs: detect-changes if: needs.detect-changes.outputs.run-full-ci == 'true' runs-on: ubuntu-latest @@ -190,7 +188,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable with: - toolchain: "1.97" + toolchain: "1.98" - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 with: shared-key: "msrv-${{ matrix.label }}" @@ -260,7 +258,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable with: - toolchain: "1.97" + toolchain: "1.98" - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 with: cache-targets: "false" @@ -495,7 +493,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable with: - toolchain: "1.97" + toolchain: "1.98" - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 with: cache-targets: "false" @@ -575,7 +573,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable with: - toolchain: "1.97" + toolchain: "1.98" - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 with: shared-key: "ci" @@ -607,7 +605,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable with: - toolchain: "1.97" + toolchain: "1.98" - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 with: shared-key: "ci" @@ -714,7 +712,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable with: - toolchain: "1.97" + toolchain: "1.98" - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 with: shared-key: "ci" @@ -747,7 +745,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable with: - toolchain: "1.97" + toolchain: "1.98" - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 with: cache-targets: "false" @@ -774,7 +772,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable with: - toolchain: "1.97" + toolchain: "1.98" - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 with: cache-targets: "false" @@ -798,7 +796,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable with: - toolchain: "1.97" + toolchain: "1.98" - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 with: shared-key: "ci" @@ -854,7 +852,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable with: - toolchain: "1.97" + toolchain: "1.98" - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 with: shared-key: "ci" diff --git a/AGENTS.md b/AGENTS.md index 33453af78..aa5115aa8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,7 +24,7 @@ All feature and system specifications live in `specs/`. **Compliance is non-nego Path-specific instructions for GitHub Copilot live in `.github/instructions/*.instructions.md` with `applyTo` frontmatter. - Use `cargo nextest run` as the default test runner. -- Keep Rust changes compatible with Edition 2024 and MSRV `1.97`. +- Keep Rust changes compatible with Edition 2024 and MSRV `1.98`. - Prefer zero-warning `clippy`; avoid `unwrap`/`expect` in production code when proper error propagation is possible. - Any user-facing change must update relevant docs, config defaults, and `CHANGELOG.md` (`[Unreleased]` section). - For new functionality, provide all integration points: config section, CLI subcommand/argument, TUI command palette entry, `--init` wizard, `--migrate-config` migration step, live testing playbook in `.local/testing/playbooks/`, and coverage row in `.local/testing/coverage-status.md`. @@ -69,7 +69,7 @@ All secrets and API keys are stored exclusively in the Zeph age vault. Never use - `cargo llvm-cov --all-features --workspace`: Generate coverage locally. ## Coding Style & Naming Conventions -Use Rust 2024 edition and MSRV `1.97`. Follow `rustfmt` defaults (4-space indentation) and keep Clippy warnings at zero where practical. +Use Rust 2024 edition and MSRV `1.98`. Follow `rustfmt` defaults (4-space indentation) and keep Clippy warnings at zero where practical. Use `snake_case` for functions/modules/files, `PascalCase` for types/traits, and `SCREAMING_SNAKE_CASE` for constants. Prefer small modules with explicit responsibilities; keep public APIs in `lib.rs` minimal and re-export intentionally. ## Testing Guidelines diff --git a/CHANGELOG.md b/CHANGELOG.md index e3a830680..1930abf14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Changed +- Raised the workspace MSRV to Rust 1.98 and resolved the new `clippy::unused_async_trait_impl` / + `clippy::chunks_exact_to_as_chunks` lints introduced by it (#6746, PR link pending). - `release.yml`: removed `Swatinem/rust-cache` and `sccache` from the `build-binaries` job. Release builds only run on `v*` tag push, infrequently enough that any cache entry written by the previous release has almost certainly been evicted from the shared 10 GiB GHA cache diff --git a/Cargo.toml b/Cargo.toml index 1ff362b4c..551377f66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ resolver = "3" [workspace.package] edition = "2024" -rust-version = "1.97" +rust-version = "1.98" version = "0.22.4" authors = ["bug-ops"] license = "MIT OR Apache-2.0" diff --git a/README.md b/README.md index f5aa087c7..0ae230f7c 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![docs](https://img.shields.io/badge/docs-book-blue)](https://bug-ops.github.io/zeph/) [![CI](https://img.shields.io/github/actions/workflow/status/bug-ops/zeph/ci.yml?branch=main&label=CI)](https://github.com/bug-ops/zeph/actions) [![codecov](https://codecov.io/gh/bug-ops/zeph/graph/badge.svg?token=S5O0GR9U6G)](https://codecov.io/gh/bug-ops/zeph) - [![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) + [![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) [![Tests](https://img.shields.io/badge/tests-15609-brightgreen)](https://github.com/bug-ops/zeph/actions) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-yellow.svg)](LICENSE) @@ -246,7 +246,7 @@ cd zeph && cargo build --release --features full Builds run only what you need via [feature bundles](https://bug-ops.github.io/zeph/reference/feature-flags.html): `desktop` (TUI), `ide` (ACP), `server` (gateway + A2A + telemetry), `chat` (Discord + Slack), `ml` (Candle + PDF), or `full`. Cross-platform: Linux, macOS, Windows on x86_64 and ARM64. > [!IMPORTANT] -> Building from source requires Rust 1.97 or later. Pre-built binaries do not need a toolchain. +> Building from source requires Rust 1.98 or later. Pre-built binaries do not need a toolchain. ## Common commands diff --git a/crates/zeph-a2a/README.md b/crates/zeph-a2a/README.md index 9e2a756c3..e9e7e9d4e 100644 --- a/crates/zeph-a2a/README.md +++ b/crates/zeph-a2a/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-a2a)](https://crates.io/crates/zeph-a2a) [![docs.rs](https://img.shields.io/docsrs/zeph-a2a)](https://docs.rs/zeph-a2a) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-yellow.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) A2A protocol client and server with agent discovery for Zeph. diff --git a/crates/zeph-acp/README.md b/crates/zeph-acp/README.md index 724746b19..2e018b8b7 100644 --- a/crates/zeph-acp/README.md +++ b/crates/zeph-acp/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-acp)](https://crates.io/crates/zeph-acp) [![docs.rs](https://img.shields.io/docsrs/zeph-acp)](https://docs.rs/zeph-acp) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-yellow.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) ACP (Agent Client Protocol) server adapter for embedding Zeph in IDE environments. @@ -22,7 +22,7 @@ zeph-acp = { version = "0.22", features = ["acp-http"] } ``` **Important:** -> Requires Rust 1.97 or later. +> Requires Rust 1.98 or later. ## Features diff --git a/crates/zeph-acp/src/fs.rs b/crates/zeph-acp/src/fs.rs index 59905ead8..332a8033c 100644 --- a/crates/zeph-acp/src/fs.rs +++ b/crates/zeph-acp/src/fs.rs @@ -280,8 +280,11 @@ fn validate_path(raw: &str) -> Result { } impl zeph_tools::ToolExecutor for AcpFileExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(None)) } fn tool_definitions(&self) -> Vec { diff --git a/crates/zeph-acp/src/terminal.rs b/crates/zeph-acp/src/terminal.rs index 51ef405c8..bf8e38c01 100644 --- a/crates/zeph-acp/src/terminal.rs +++ b/crates/zeph-acp/src/terminal.rs @@ -389,8 +389,11 @@ struct BashStdinParams { } impl zeph_tools::ToolExecutor for AcpShellExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(None)) } fn tool_definitions(&self) -> Vec { diff --git a/crates/zeph-agent-context/README.md b/crates/zeph-agent-context/README.md index 8ced7e191..360693bb1 100644 --- a/crates/zeph-agent-context/README.md +++ b/crates/zeph-agent-context/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-agent-context)](https://crates.io/crates/zeph-agent-context) [![docs.rs](https://img.shields.io/docsrs/zeph-agent-context)](https://docs.rs/zeph-agent-context) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) Agent context-assembly service for the [Zeph](https://github.com/bug-ops/zeph) AI agent. @@ -20,7 +20,7 @@ zeph-agent-context = { version = "0.22", workspace = true } ``` > [!IMPORTANT] -> Requires Rust 1.97 or later (Edition 2024). This crate does **not** depend on `zeph-core` — only on lower-level crates (`zeph-memory`, `zeph-llm`, `zeph-skills`, `zeph-context`, `zeph-sanitizer`, `zeph-config`, `zeph-common`). +> Requires Rust 1.98 or later (Edition 2024). This crate does **not** depend on `zeph-core` — only on lower-level crates (`zeph-memory`, `zeph-llm`, `zeph-skills`, `zeph-context`, `zeph-sanitizer`, `zeph-config`, `zeph-common`). ## Usage diff --git a/crates/zeph-agent-context/src/compaction.rs b/crates/zeph-agent-context/src/compaction.rs index bff90880a..43ea61c45 100644 --- a/crates/zeph-agent-context/src/compaction.rs +++ b/crates/zeph-agent-context/src/compaction.rs @@ -830,8 +830,11 @@ mod tests { } impl zeph_llm::provider::LlmProvider for StubProvider { - async fn chat(&self, _messages: &[Message]) -> Result { - Ok(self.response.to_owned()) + fn chat( + &self, + _messages: &[Message], + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(self.response.to_owned())) } async fn chat_stream( @@ -848,8 +851,12 @@ mod tests { false } - async fn embed(&self, _text: &str) -> Result, zeph_llm::LlmError> { - Ok(vec![]) + fn embed( + &self, + _text: &str, + ) -> impl std::future::Future, zeph_llm::LlmError>> + Send + { + std::future::ready(Ok(vec![])) } fn supports_embeddings(&self) -> bool { @@ -881,8 +888,12 @@ mod tests { false } - async fn embed(&self, _text: &str) -> Result, zeph_llm::LlmError> { - Ok(vec![]) + fn embed( + &self, + _text: &str, + ) -> impl std::future::Future, zeph_llm::LlmError>> + Send + { + std::future::ready(Ok(vec![])) } fn supports_embeddings(&self) -> bool { diff --git a/crates/zeph-agent-feedback/README.md b/crates/zeph-agent-feedback/README.md index 599beabbb..2cf7ed6d6 100644 --- a/crates/zeph-agent-feedback/README.md +++ b/crates/zeph-agent-feedback/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-agent-feedback)](https://crates.io/crates/zeph-agent-feedback) [![docs.rs](https://img.shields.io/docsrs/zeph-agent-feedback)](https://docs.rs/zeph-agent-feedback) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) Implicit correction detection for the [Zeph](https://github.com/bug-ops/zeph) AI agent. @@ -27,7 +27,7 @@ Or with cargo-add: cargo add zeph-agent-feedback ``` -**Note:** Requires Rust 1.97 or later (Edition 2024). +**Note:** Requires Rust 1.98 or later (Edition 2024). ## Usage diff --git a/crates/zeph-agent-persistence/README.md b/crates/zeph-agent-persistence/README.md index 022c298f4..e1f9a8acb 100644 --- a/crates/zeph-agent-persistence/README.md +++ b/crates/zeph-agent-persistence/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-agent-persistence)](https://crates.io/crates/zeph-agent-persistence) [![docs.rs](https://img.shields.io/docsrs/zeph-agent-persistence)](https://docs.rs/zeph-agent-persistence) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) Agent persistence service for Zeph: loads conversation history from and writes messages to the `SemanticMemory` backend (SQLite + Qdrant), with tool-pair sanitization and embedding decisions. diff --git a/crates/zeph-agent-tools/README.md b/crates/zeph-agent-tools/README.md index 95599adad..495d9ea1c 100644 --- a/crates/zeph-agent-tools/README.md +++ b/crates/zeph-agent-tools/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-agent-tools)](https://crates.io/crates/zeph-agent-tools) [![docs.rs](https://img.shields.io/docsrs/zeph-agent-tools)](https://docs.rs/zeph-agent-tools) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) Doom-loop detection utilities for the Zeph tool dispatch loop. diff --git a/crates/zeph-bench/README.md b/crates/zeph-bench/README.md index 0485ea655..dd1258cfd 100644 --- a/crates/zeph-bench/README.md +++ b/crates/zeph-bench/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-bench)](https://crates.io/crates/zeph-bench) [![docs.rs](https://img.shields.io/docsrs/zeph-bench)](https://docs.rs/zeph-bench) [![CI](https://img.shields.io/github/actions/workflow/status/bug-ops/zeph/ci.yml?branch=main)](https://github.com/bug-ops/zeph/actions) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) [![License](https://img.shields.io/crates/l/zeph-bench)](../../LICENSE) Benchmark harness for evaluating Zeph agent performance on standardized datasets. @@ -167,7 +167,7 @@ impl Evaluator for MyEvaluator { > the response text. Every other dataset runs under `ResponseMode::TerseAnswer`. > [!IMPORTANT] -> Requires Rust 1.97 or later. +> Requires Rust 1.98 or later. ## Architecture diff --git a/crates/zeph-bench/src/channel.rs b/crates/zeph-bench/src/channel.rs index d27da5d8c..86b061943 100644 --- a/crates/zeph-bench/src/channel.rs +++ b/crates/zeph-bench/src/channel.rs @@ -288,6 +288,10 @@ impl BenchmarkChannel { } impl zeph_core::channel::Channel for BenchmarkChannel { + // Genuine `async fn` on purpose (see `Channel`'s doc comment): this body pops the prompt + // queue, an observable side effect, so it must stay poll-time-lazy — never rewrite to a + // synchronous `fn` returning `std::future::ready(..)` (#6746). + #[allow(clippy::unused_async_trait_impl)] async fn recv(&mut self) -> Result, ChannelError> { match self.prompts.pop_front() { Some(text) => { @@ -308,6 +312,7 @@ impl zeph_core::channel::Channel for BenchmarkChannel { false } + #[allow(clippy::unused_async_trait_impl)] async fn send(&mut self, text: &str) -> Result<(), ChannelError> { self.responses.push(CapturedResponse { prompt_index: self.current_index.saturating_sub(1), @@ -323,6 +328,7 @@ impl zeph_core::channel::Channel for BenchmarkChannel { Ok(()) } + #[allow(clippy::unused_async_trait_impl)] async fn send_chunk(&mut self, chunk: &str) -> Result<(), ChannelError> { if self.chunk_start.is_none() { self.chunk_start = Some(Instant::now()); @@ -331,11 +337,13 @@ impl zeph_core::channel::Channel for BenchmarkChannel { Ok(()) } + #[allow(clippy::unused_async_trait_impl)] async fn flush_chunks(&mut self) -> Result<(), ChannelError> { self.flush_chunk_buffer(); Ok(()) } + #[allow(clippy::unused_async_trait_impl)] async fn send_usage( &mut self, input_tokens: u64, @@ -351,6 +359,7 @@ impl zeph_core::channel::Channel for BenchmarkChannel { Ok(()) } + #[allow(clippy::unused_async_trait_impl)] async fn send_tool_output(&mut self, event: ToolOutputEvent) -> Result<(), ChannelError> { self.tool_outputs.push(event); Ok(()) diff --git a/crates/zeph-bench/src/loaders/tau2_bench/envs/airline.rs b/crates/zeph-bench/src/loaders/tau2_bench/envs/airline.rs index df7da5da4..d223b9983 100644 --- a/crates/zeph-bench/src/loaders/tau2_bench/envs/airline.rs +++ b/crates/zeph-bench/src/loaders/tau2_bench/envs/airline.rs @@ -193,8 +193,11 @@ impl AirlineEnv { } impl ToolExecutor for AirlineEnv { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(None)) } fn tool_definitions(&self) -> Vec { diff --git a/crates/zeph-bench/src/loaders/tau2_bench/envs/retail.rs b/crates/zeph-bench/src/loaders/tau2_bench/envs/retail.rs index 671ac54e3..87d5ff30d 100644 --- a/crates/zeph-bench/src/loaders/tau2_bench/envs/retail.rs +++ b/crates/zeph-bench/src/loaders/tau2_bench/envs/retail.rs @@ -212,9 +212,12 @@ impl RetailEnv { } impl ToolExecutor for RetailEnv { - async fn execute(&self, _response: &str) -> Result, ToolError> { + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { // tau2-bench uses structured tool calls only, not fenced code blocks. - Ok(None) + std::future::ready(Ok(None)) } fn tool_definitions(&self) -> Vec { diff --git a/crates/zeph-bench/src/runner.rs b/crates/zeph-bench/src/runner.rs index 9bfbed48f..82c8f4e80 100644 --- a/crates/zeph-bench/src/runner.rs +++ b/crates/zeph-bench/src/runner.rs @@ -148,8 +148,11 @@ pub struct RunOptions { struct NoopExecutor; impl ToolExecutor for NoopExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(None)) } zeph_tools::tool_executor_no_inner_defaults!(); diff --git a/crates/zeph-channels/README.md b/crates/zeph-channels/README.md index 3cd3aab9b..415df4684 100644 --- a/crates/zeph-channels/README.md +++ b/crates/zeph-channels/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-channels)](https://crates.io/crates/zeph-channels) [![docs.rs](https://img.shields.io/docsrs/zeph-channels)](https://docs.rs/zeph-channels) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-yellow.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) Multi-channel I/O adapters (CLI, Telegram, Discord, Slack) for Zeph. diff --git a/crates/zeph-channels/src/confirm.rs b/crates/zeph-channels/src/confirm.rs index 6e37a97f5..509bb1a58 100644 --- a/crates/zeph-channels/src/confirm.rs +++ b/crates/zeph-channels/src/confirm.rs @@ -153,6 +153,7 @@ mod tests { &incoming.text } + #[allow(clippy::unused_async_trait_impl)] async fn confirm_send_prompt(&mut self, text: &str) -> Result<(), ChannelError> { self.sent.push(text.to_string()); Ok(()) diff --git a/crates/zeph-channels/src/discord/mod.rs b/crates/zeph-channels/src/discord/mod.rs index 5ba7f2ba1..99c8d48c5 100644 --- a/crates/zeph-channels/src/discord/mod.rs +++ b/crates/zeph-channels/src/discord/mod.rs @@ -376,15 +376,15 @@ impl Channel for DiscordChannel { crate::confirm::ConfirmLoop::run_confirm(self, prompt).await } - async fn elicit( + fn elicit( &mut self, request: ElicitationRequest, - ) -> Result { + ) -> impl std::future::Future> + Send { tracing::warn!( server = %request.server_name, "elicit() not supported on Discord channel — declining" ); - Ok(ElicitationResponse::Declined) + std::future::ready(Ok(ElicitationResponse::Declined)) } } diff --git a/crates/zeph-channels/src/json_cli.rs b/crates/zeph-channels/src/json_cli.rs index 51d4954d6..b258382bf 100644 --- a/crates/zeph-channels/src/json_cli.rs +++ b/crates/zeph-channels/src/json_cli.rs @@ -152,6 +152,7 @@ impl Channel for JsonCliChannel { true } + #[allow(clippy::unused_async_trait_impl)] async fn send(&mut self, text: &str) -> Result<(), ChannelError> { self.sink.emit(&JsonEvent::ResponseChunk { text }); self.pending_chunks = true; @@ -162,16 +163,21 @@ impl Channel for JsonCliChannel { /// programmatically. The resume banner (spec-068 §13.5) is a human-facing presentation /// nicety and must not leak into it as an unsolicited `ResponseChunk`, same as it is /// excluded from the process-startup path via `is_cli` in `src/runner.rs`. - async fn send_resume_banner(&mut self, _text: &str) -> Result<(), ChannelError> { - Ok(()) + fn send_resume_banner( + &mut self, + _text: &str, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(())) } + #[allow(clippy::unused_async_trait_impl)] async fn send_chunk(&mut self, chunk: &str) -> Result<(), ChannelError> { self.sink.emit(&JsonEvent::ResponseChunk { text: chunk }); self.pending_chunks = true; Ok(()) } + #[allow(clippy::unused_async_trait_impl)] async fn flush_chunks(&mut self) -> Result<(), ChannelError> { if self.pending_chunks { self.sink.emit(&JsonEvent::ResponseEnd); @@ -180,9 +186,11 @@ impl Channel for JsonCliChannel { Ok(()) } - async fn send_typing(&mut self) -> Result<(), ChannelError> { + fn send_typing( + &mut self, + ) -> impl std::future::Future> + Send { // No typing indicator in JSON mode. - Ok(()) + std::future::ready(Ok(())) } async fn confirm(&mut self, prompt: &str) -> Result { @@ -199,19 +207,21 @@ impl Channel for JsonCliChannel { } } - async fn elicit( + fn elicit( &mut self, _request: ElicitationRequest, - ) -> Result { + ) -> impl std::future::Future> + Send { // Elicitation is not supported in JSON mode; decline quietly to avoid log spam. - Ok(ElicitationResponse::Declined) + std::future::ready(Ok(ElicitationResponse::Declined)) } + #[allow(clippy::unused_async_trait_impl)] async fn send_status(&mut self, text: &str) -> Result<(), ChannelError> { self.sink.emit(&JsonEvent::Status { message: text }); Ok(()) } + #[allow(clippy::unused_async_trait_impl)] async fn send_queue_count(&mut self, count: usize) -> Result<(), ChannelError> { self.sink.emit(&JsonEvent::Status { message: &format!("queue: {count}"), @@ -219,26 +229,33 @@ impl Channel for JsonCliChannel { Ok(()) } - async fn send_diff( + fn send_diff( &mut self, _diff: DiffData, _tool_call_id: &str, - ) -> Result<(), ChannelError> { + ) -> impl std::future::Future> + Send { // v1: diffs are not emitted as JSON events. - Ok(()) + std::future::ready(Ok(())) } /// No-op: `JsonEventLayer` emits `tool_result` from its `after_tool` hook. /// Double-emission would corrupt the JSONL stream. - async fn send_tool_output(&mut self, _event: ToolOutputEvent) -> Result<(), ChannelError> { - Ok(()) + fn send_tool_output( + &mut self, + _event: ToolOutputEvent, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(())) } - async fn send_thinking_chunk(&mut self, _chunk: &str) -> Result<(), ChannelError> { + fn send_thinking_chunk( + &mut self, + _chunk: &str, + ) -> impl std::future::Future> + Send { // v1: thinking chunks are not emitted in JSON mode. - Ok(()) + std::future::ready(Ok(())) } + #[allow(clippy::unused_async_trait_impl)] async fn send_stop_hint(&mut self, hint: StopHint) -> Result<(), ChannelError> { self.sink.emit(&JsonEvent::Status { message: &format!("stop_hint: {hint:?}"), @@ -247,7 +264,7 @@ impl Channel for JsonCliChannel { } /// No-op: `JsonEventLayer` emits `cost` from its `after_chat` hook. - async fn send_usage( + fn send_usage( &mut self, _input_tokens: u64, _output_tokens: u64, @@ -255,13 +272,16 @@ impl Channel for JsonCliChannel { _cache_read_tokens: u64, _cache_write_tokens: u64, _cost_cents: f64, - ) -> Result<(), ChannelError> { - Ok(()) + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(())) } /// No-op: `JsonEventLayer` emits `tool_call` from its `before_tool` hook. - async fn send_tool_start(&mut self, _event: ToolStartEvent) -> Result<(), ChannelError> { - Ok(()) + fn send_tool_start( + &mut self, + _event: ToolStartEvent, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(())) } } diff --git a/crates/zeph-channels/src/slack/mod.rs b/crates/zeph-channels/src/slack/mod.rs index 77f8c8865..1bebcce67 100644 --- a/crates/zeph-channels/src/slack/mod.rs +++ b/crates/zeph-channels/src/slack/mod.rs @@ -357,15 +357,15 @@ impl Channel for SlackChannel { crate::confirm::ConfirmLoop::run_confirm(self, prompt).await } - async fn elicit( + fn elicit( &mut self, request: ElicitationRequest, - ) -> Result { + ) -> impl std::future::Future> + Send { tracing::warn!( server = %request.server_name, "elicit() not supported on Slack channel — declining" ); - Ok(ElicitationResponse::Declined) + std::future::ready(Ok(ElicitationResponse::Declined)) } } diff --git a/crates/zeph-commands/README.md b/crates/zeph-commands/README.md index 05dc2667c..11ac14ba3 100644 --- a/crates/zeph-commands/README.md +++ b/crates/zeph-commands/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-commands)](https://crates.io/crates/zeph-commands) [![docs.rs](https://img.shields.io/docsrs/zeph-commands)](https://docs.rs/zeph-commands) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-yellow.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) Slash command registry, handler trait, and channel sink abstraction for [Zeph](https://github.com/bug-ops/zeph). diff --git a/crates/zeph-common/README.md b/crates/zeph-common/README.md index 82037e708..9ca99f662 100644 --- a/crates/zeph-common/README.md +++ b/crates/zeph-common/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-common)](https://crates.io/crates/zeph-common) [![docs.rs](https://img.shields.io/docsrs/zeph-common)](https://docs.rs/zeph-common) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-yellow.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) Shared utility functions and security primitives for the Zeph workspace. Zero `zeph-*` dependencies — safe to depend on from any crate. diff --git a/crates/zeph-config/README.md b/crates/zeph-config/README.md index 218fb04a7..ef4b41026 100644 --- a/crates/zeph-config/README.md +++ b/crates/zeph-config/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-config)](https://crates.io/crates/zeph-config) [![docs.rs](https://img.shields.io/docsrs/zeph-config)](https://docs.rs/zeph-config) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-yellow.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) Pure-data configuration types for Zeph — all TOML config structs with serde derive, validation, and migration support. diff --git a/crates/zeph-context/README.md b/crates/zeph-context/README.md index 99d95481e..658fe1fe7 100644 --- a/crates/zeph-context/README.md +++ b/crates/zeph-context/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-context)](https://crates.io/crates/zeph-context) [![docs.rs](https://img.shields.io/docsrs/zeph-context)](https://docs.rs/zeph-context) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-yellow.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) Context budget, lifecycle management, compaction strategy, and stateless context assembler for the [Zeph](https://github.com/bug-ops/zeph) AI agent. diff --git a/crates/zeph-context/src/fidelity.rs b/crates/zeph-context/src/fidelity.rs index 3f3e2ab1f..f63bda619 100644 --- a/crates/zeph-context/src/fidelity.rs +++ b/crates/zeph-context/src/fidelity.rs @@ -898,7 +898,54 @@ fn parse_placeholder_importance(content: &str) -> f32 { mod tests { use super::*; use zeph_common::ProviderName; - use zeph_llm::provider::{Message, MessageMetadata, MessagePart, Role}; + use zeph_llm::provider::{ChatStream, Message, MessageMetadata, MessagePart, Role}; + + /// Deterministic embedding mock shared by fidelity semantic-scoring tests. + #[derive(Debug)] + struct EmbedMockProvider; + + impl zeph_llm::provider::LlmProvider for EmbedMockProvider { + fn chat( + &self, + _: &[Message], + ) -> impl std::future::Future> + Send { + std::future::ready(Err(LlmError::Unavailable)) + } + fn chat_stream( + &self, + _: &[Message], + ) -> impl std::future::Future> + Send { + std::future::ready(Err(LlmError::Unavailable)) + } + fn supports_streaming(&self) -> bool { + false + } + fn embed( + &self, + text: &str, + ) -> impl std::future::Future, LlmError>> + Send { + let v = if text.contains("cat") + || text.contains("mat") + || text.contains("feline") + || text.contains("rug") + { + if text.contains("feline") || text.contains("rug") { + vec![0.9f32, 0.1, 0.0] + } else { + vec![1.0f32, 0.0, 0.0] + } + } else { + vec![0.0f32, 0.0, 1.0] + }; + std::future::ready(Ok(v)) + } + fn supports_embeddings(&self) -> bool { + true + } + fn name(&self) -> &'static str { + "embed-mock" + } + } struct FixedTc(usize); impl TokenCounting for FixedTc { @@ -1795,29 +1842,36 @@ mod tests { // 15. LLM path stores deferred_summary and updates content. #[tokio::test] async fn compress_llm_path_stores_deferred_summary() { - use zeph_llm::LlmError; - use zeph_llm::provider::ChatStream; - #[derive(Debug)] struct MockProvider; impl zeph_llm::provider::LlmProvider for MockProvider { - async fn chat(&self, _messages: &[Message]) -> Result { - Ok("summary text".to_string()) + fn chat( + &self, + _messages: &[Message], + ) -> impl std::future::Future> + Send { + std::future::ready(Ok("summary text".to_string())) } - async fn chat_stream(&self, _messages: &[Message]) -> Result { - Err(LlmError::Unavailable) + fn chat_stream( + &self, + _messages: &[Message], + ) -> impl std::future::Future> + Send + { + std::future::ready(Err(LlmError::Unavailable)) } fn supports_streaming(&self) -> bool { false } - async fn embed(&self, _text: &str) -> Result, LlmError> { - Err(LlmError::EmbedUnsupported { + fn embed( + &self, + _text: &str, + ) -> impl std::future::Future, LlmError>> + Send { + std::future::ready(Err(LlmError::EmbedUnsupported { provider: "mock".into(), - }) + })) } fn supports_embeddings(&self) -> bool { @@ -1969,46 +2023,6 @@ mod tests { // query "cat mat" → [1.0, 0.0, 0.0] #[tokio::test] async fn semantic_scoring_higher_for_similar_messages() { - use zeph_llm::LlmError; - use zeph_llm::provider::ChatStream; - - #[derive(Debug)] - struct EmbedMockProvider; - - impl zeph_llm::provider::LlmProvider for EmbedMockProvider { - async fn chat(&self, _: &[Message]) -> Result { - Err(LlmError::Unavailable) - } - async fn chat_stream(&self, _: &[Message]) -> Result { - Err(LlmError::Unavailable) - } - fn supports_streaming(&self) -> bool { - false - } - async fn embed(&self, text: &str) -> Result, LlmError> { - let v = if text.contains("cat") - || text.contains("mat") - || text.contains("feline") - || text.contains("rug") - { - if text.contains("feline") || text.contains("rug") { - vec![0.9f32, 0.1, 0.0] - } else { - vec![1.0f32, 0.0, 0.0] - } - } else { - vec![0.0f32, 0.0, 1.0] - }; - Ok(v) - } - fn supports_embeddings(&self) -> bool { - true - } - fn name(&self) -> &'static str { - "embed-mock" - } - } - let provider = EmbedMockProvider; let scorer = FidelityScorer; let cfg = FidelityConfig { diff --git a/crates/zeph-core/README.md b/crates/zeph-core/README.md index e5011b2f0..956059900 100644 --- a/crates/zeph-core/README.md +++ b/crates/zeph-core/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-core)](https://crates.io/crates/zeph-core) [![docs.rs](https://img.shields.io/docsrs/zeph-core)](https://docs.rs/zeph-core) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-yellow.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) Core agent loop, configuration, context builder, metrics, vault, and sub-agent orchestration for Zeph. diff --git a/crates/zeph-core/src/agent/speculative/mod.rs b/crates/zeph-core/src/agent/speculative/mod.rs index 0389733e0..530571f16 100644 --- a/crates/zeph-core/src/agent/speculative/mod.rs +++ b/crates/zeph-core/src/agent/speculative/mod.rs @@ -310,15 +310,20 @@ mod tests { struct AlwaysOkExecutor; impl ToolExecutor for AlwaysOkExecutor { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } - async fn execute_tool_call( + fn execute_tool_call( &self, _call: &ToolCall, - ) -> Result, ToolError> { - Ok(Some(ToolOutput { + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(Some(ToolOutput { tool_name: zeph_common::ToolName::new("test"), summary: "ok".into(), blocks_executed: 1, @@ -330,7 +335,7 @@ mod tests { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } fn is_tool_speculatable(&self, _: &str) -> bool { diff --git a/crates/zeph-core/src/agent/speculative/stream_drainer.rs b/crates/zeph-core/src/agent/speculative/stream_drainer.rs index 68cf2ca4a..7a887c40a 100644 --- a/crates/zeph-core/src/agent/speculative/stream_drainer.rs +++ b/crates/zeph-core/src/agent/speculative/stream_drainer.rs @@ -204,14 +204,19 @@ mod tests { struct NullExec; impl ToolExecutor for NullExec { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } - async fn execute_tool_call( + fn execute_tool_call( &self, _: &ToolCall, - ) -> Result, ToolError> { - Ok(None) + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } fn is_tool_speculatable(&self, _: &str) -> bool { false @@ -258,14 +263,19 @@ mod tests { count: Arc, } impl ToolExecutor for SpyExec { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } - async fn execute_tool_call( + fn execute_tool_call( &self, _: &ToolCall, - ) -> Result, ToolError> { - Ok(None) + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } fn is_tool_speculatable(&self, _: &str) -> bool { self.count.fetch_add(1, Ordering::Relaxed); @@ -347,14 +357,19 @@ mod tests { struct NullExec; impl ToolExecutor for NullExec { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } - async fn execute_tool_call( + fn execute_tool_call( &self, _: &ToolCall, - ) -> Result, ToolError> { - Ok(None) + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } fn is_tool_speculatable(&self, _: &str) -> bool { false @@ -400,14 +415,19 @@ mod tests { struct NullExec; impl ToolExecutor for NullExec { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } - async fn execute_tool_call( + fn execute_tool_call( &self, _: &ToolCall, - ) -> Result, ToolError> { - Ok(None) + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } fn is_tool_speculatable(&self, _: &str) -> bool { false @@ -455,14 +475,19 @@ mod tests { struct NullExec; impl ToolExecutor for NullExec { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } - async fn execute_tool_call( + fn execute_tool_call( &self, _: &ToolCall, - ) -> Result, ToolError> { - Ok(None) + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } fn is_tool_speculatable(&self, _: &str) -> bool { false @@ -518,14 +543,19 @@ mod tests { struct NullExec; impl ToolExecutor for NullExec { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } - async fn execute_tool_call( + fn execute_tool_call( &self, _: &ToolCall, - ) -> Result, ToolError> { - Ok(None) + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } fn is_tool_speculatable(&self, _: &str) -> bool { false diff --git a/crates/zeph-core/src/agent/state/security.rs b/crates/zeph-core/src/agent/state/security.rs index 17aaadeda..b0f59c96f 100644 --- a/crates/zeph-core/src/agent/state/security.rs +++ b/crates/zeph-core/src/agent/state/security.rs @@ -84,7 +84,10 @@ impl SecurityState { /// /// Returns a [`PiiScrubResult`] with the scrubbed text and metric side-effects. /// The caller is responsible for applying metrics updates from the result. - #[cfg_attr(not(feature = "classifiers"), allow(clippy::unused_async))] + #[cfg_attr( + not(feature = "classifiers"), + allow(clippy::unused_async, clippy::unused_async_trait_impl) + )] pub(crate) async fn scrub_pii(&mut self, text: &str, tool_name: &str) -> PiiScrubResult { use zeph_sanitizer::pii::{merge_spans, redact_spans}; diff --git a/crates/zeph-core/src/agent/subagent_commands.rs b/crates/zeph-core/src/agent/subagent_commands.rs index 357a07ea9..1cc861003 100644 --- a/crates/zeph-core/src/agent/subagent_commands.rs +++ b/crates/zeph-core/src/agent/subagent_commands.rs @@ -2204,8 +2204,12 @@ mod tests { } impl ToolExecutor for TrustRecordingExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } fn set_skill_env(&self, _env: Option>) {} @@ -2280,10 +2284,15 @@ mod tests { } impl ToolExecutor for RecordingExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } + #[allow(clippy::unused_async_trait_impl)] async fn execute_tool_call( &self, call: &ToolCall, diff --git a/crates/zeph-core/src/agent/tests/agent_tests/common.rs b/crates/zeph-core/src/agent/tests/agent_tests/common.rs index 2c5ea1ca5..ce6aeeec1 100644 --- a/crates/zeph-core/src/agent/tests/agent_tests/common.rs +++ b/crates/zeph-core/src/agent/tests/agent_tests/common.rs @@ -182,6 +182,7 @@ impl MockChannel { } impl Channel for MockChannel { + #[allow(clippy::unused_async_trait_impl)] async fn recv(&mut self) -> Result, crate::channel::ChannelError> { let mut msgs = self.messages.lock().unwrap(); if msgs.is_empty() { @@ -212,6 +213,7 @@ impl Channel for MockChannel { } } + #[allow(clippy::unused_async_trait_impl)] async fn send(&mut self, text: &str) -> Result<(), crate::channel::ChannelError> { if self.fail_send { return Err(crate::channel::ChannelError::ChannelClosed); @@ -220,20 +222,25 @@ impl Channel for MockChannel { Ok(()) } + #[allow(clippy::unused_async_trait_impl)] async fn send_chunk(&mut self, chunk: &str) -> Result<(), crate::channel::ChannelError> { self.chunks.lock().unwrap().push(chunk.to_string()); Ok(()) } - async fn flush_chunks(&mut self) -> Result<(), crate::channel::ChannelError> { - Ok(()) + fn flush_chunks( + &mut self, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(())) } + #[allow(clippy::unused_async_trait_impl)] async fn send_status(&mut self, text: &str) -> Result<(), crate::channel::ChannelError> { self.statuses.lock().unwrap().push(text.to_string()); Ok(()) } + #[allow(clippy::unused_async_trait_impl)] async fn send_tool_start( &mut self, event: ToolStartEvent, @@ -242,6 +249,7 @@ impl Channel for MockChannel { Ok(()) } + #[allow(clippy::unused_async_trait_impl)] async fn confirm(&mut self, prompt: &str) -> Result { self.confirmed_prompts .lock() @@ -263,6 +271,7 @@ impl Channel for MockChannel { self.input_sanitization_required } + #[allow(clippy::unused_async_trait_impl)] async fn notify_foreground_subagent_completed( &mut self, id: &str, @@ -276,6 +285,7 @@ impl Channel for MockChannel { Ok(()) } + #[allow(clippy::unused_async_trait_impl)] async fn notify_background_subagent_completed( &mut self, id: &str, @@ -304,16 +314,24 @@ impl Channel for PendingChannel { std::future::pending().await } - async fn send(&mut self, _text: &str) -> Result<(), crate::channel::ChannelError> { - Ok(()) + fn send( + &mut self, + _text: &str, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(())) } - async fn send_chunk(&mut self, _chunk: &str) -> Result<(), crate::channel::ChannelError> { - Ok(()) + fn send_chunk( + &mut self, + _chunk: &str, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(())) } - async fn flush_chunks(&mut self) -> Result<(), crate::channel::ChannelError> { - Ok(()) + fn flush_chunks( + &mut self, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(())) } } @@ -379,6 +397,7 @@ impl MockToolExecutor { } impl ToolExecutor for MockToolExecutor { + #[allow(clippy::unused_async_trait_impl)] async fn execute(&self, _response: &str) -> Result, ToolError> { let mut outputs = self.outputs.lock().unwrap(); if outputs.is_empty() { diff --git a/crates/zeph-core/src/agent/tests/agent_tests/owner_key_tests.rs b/crates/zeph-core/src/agent/tests/agent_tests/owner_key_tests.rs index 1c557a4fd..3c41861d4 100644 --- a/crates/zeph-core/src/agent/tests/agent_tests/owner_key_tests.rs +++ b/crates/zeph-core/src/agent/tests/agent_tests/owner_key_tests.rs @@ -38,22 +38,27 @@ impl OwnerKeyChannel { } impl Channel for OwnerKeyChannel { + #[allow(clippy::unused_async_trait_impl)] async fn recv(&mut self) -> Result, crate::channel::ChannelError> { Ok(self.inbox.pop_front()) } + #[allow(clippy::unused_async_trait_impl)] async fn send(&mut self, text: &str) -> Result<(), crate::channel::ChannelError> { self.sent.lock().unwrap().push(text.to_owned()); Ok(()) } + #[allow(clippy::unused_async_trait_impl)] async fn send_chunk(&mut self, chunk: &str) -> Result<(), crate::channel::ChannelError> { self.sent.lock().unwrap().push(chunk.to_owned()); Ok(()) } - async fn flush_chunks(&mut self) -> Result<(), crate::channel::ChannelError> { - Ok(()) + fn flush_chunks( + &mut self, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(())) } } diff --git a/crates/zeph-core/src/agent/tests/confirmation_propagation_tests.rs b/crates/zeph-core/src/agent/tests/confirmation_propagation_tests.rs index 4ab235515..4dc7c4d17 100644 --- a/crates/zeph-core/src/agent/tests/confirmation_propagation_tests.rs +++ b/crates/zeph-core/src/agent/tests/confirmation_propagation_tests.rs @@ -50,8 +50,11 @@ impl DagAwareToolExecutor { } impl ToolExecutor for DagAwareToolExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(None)) } fn tool_definitions(&self) -> Vec { @@ -83,16 +86,20 @@ impl ToolExecutor for DagAwareToolExecutor { ] } + #[allow(clippy::unused_async_trait_impl)] async fn execute_tool_call(&self, call: &ToolCall) -> Result, ToolError> { let mut results = self.results.lock().unwrap(); results.remove(call.tool_id.as_str()).unwrap_or(Ok(None)) } - async fn execute_tool_call_confirmed( + fn execute_tool_call_confirmed( &self, call: &ToolCall, - ) -> Result, ToolError> { - Ok(Some(Self::make_output(call.tool_id.as_str(), "confirmed"))) + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(Some(Self::make_output( + call.tool_id.as_str(), + "confirmed", + )))) } fn checkpoint_undo(&self, _n: usize) -> zeph_tools::CheckpointActionResult { diff --git a/crates/zeph-core/src/agent/tests/inline_tool_loop_tests.rs b/crates/zeph-core/src/agent/tests/inline_tool_loop_tests.rs index 1d0015558..12a560a3d 100644 --- a/crates/zeph-core/src/agent/tests/inline_tool_loop_tests.rs +++ b/crates/zeph-core/src/agent/tests/inline_tool_loop_tests.rs @@ -49,10 +49,14 @@ impl CallableToolExecutor { } impl ToolExecutor for CallableToolExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(None)) } + #[allow(clippy::unused_async_trait_impl)] async fn execute_tool_call(&self, _call: &ToolCall) -> Result, ToolError> { let mut outputs = self.outputs.lock().unwrap(); if outputs.is_empty() { @@ -245,10 +249,15 @@ async fn network_deny_wrapped_executor_blocks_fetch_before_reaching_inner() { } impl ToolExecutor for FlaggingExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } + #[allow(clippy::unused_async_trait_impl)] async fn execute_tool_call( &self, _call: &ToolCall, @@ -331,8 +340,12 @@ async fn elicitation_event_during_tool_execution_is_handled() { } impl ToolExecutor for BlockingElicitingExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } async fn execute_tool_call( @@ -431,8 +444,12 @@ mod run_inline_timeout { } impl ToolExecutor for SlowToolExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } async fn execute_tool_call( diff --git a/crates/zeph-core/src/agent/tests/pre_execution_audit_tests.rs b/crates/zeph-core/src/agent/tests/pre_execution_audit_tests.rs index 88074f783..91b60c919 100644 --- a/crates/zeph-core/src/agent/tests/pre_execution_audit_tests.rs +++ b/crates/zeph-core/src/agent/tests/pre_execution_audit_tests.rs @@ -17,8 +17,11 @@ use crate::agent::agent_tests::{MockChannel, create_test_registry}; struct NoOpExecutor; impl ToolExecutor for NoOpExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(None)) } fn tool_definitions(&self) -> Vec { @@ -32,8 +35,11 @@ impl ToolExecutor for NoOpExecutor { }] } - async fn execute_tool_call(&self, _call: &ToolCall) -> Result, ToolError> { - Ok(None) + fn execute_tool_call( + &self, + _call: &ToolCall, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(None)) } zeph_tools::tool_executor_no_inner_defaults!(); diff --git a/crates/zeph-core/src/agent/tests/shutdown_summary_tests.rs b/crates/zeph-core/src/agent/tests/shutdown_summary_tests.rs index 3c4858c8f..8160b21ea 100644 --- a/crates/zeph-core/src/agent/tests/shutdown_summary_tests.rs +++ b/crates/zeph-core/src/agent/tests/shutdown_summary_tests.rs @@ -329,15 +329,20 @@ async fn filter_stats_metrics_increment_on_normal_native_tool_path() { struct FilteredToolExecutor; impl ToolExecutor for FilteredToolExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } - async fn execute_tool_call( + fn execute_tool_call( &self, _call: &ToolCall, - ) -> Result, ToolError> { - Ok(Some(ToolOutput { + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(Some(ToolOutput { tool_name: "shell".into(), summary: "filtered output".to_owned(), blocks_executed: 1, @@ -357,7 +362,7 @@ async fn filter_stats_metrics_increment_on_normal_native_tool_path() { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } zeph_tools::tool_executor_no_inner_defaults!(); @@ -405,13 +410,16 @@ struct TwoToolExecutor { } impl zeph_tools::executor::ToolExecutor for TwoToolExecutor { - async fn execute( + fn execute( &self, _response: &str, - ) -> Result, zeph_tools::executor::ToolError> { - Ok(None) + ) -> impl std::future::Future< + Output = Result, zeph_tools::executor::ToolError>, + > + Send { + std::future::ready(Ok(None)) } + #[allow(clippy::unused_async_trait_impl)] async fn execute_tool_call( &self, call: &zeph_tools::executor::ToolCall, diff --git a/crates/zeph-core/src/agent/tool_execution/tests/consent_gate_toctou_tests.rs b/crates/zeph-core/src/agent/tool_execution/tests/consent_gate_toctou_tests.rs index 07bbb29d2..5f36021b3 100644 --- a/crates/zeph-core/src/agent/tool_execution/tests/consent_gate_toctou_tests.rs +++ b/crates/zeph-core/src/agent/tool_execution/tests/consent_gate_toctou_tests.rs @@ -41,8 +41,11 @@ struct SingleToolExecutor { } impl ToolExecutor for SingleToolExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(None)) } fn tool_definitions(&self) -> Vec { @@ -56,23 +59,29 @@ impl ToolExecutor for SingleToolExecutor { }] } - async fn execute_tool_call(&self, call: &ToolCall) -> Result, ToolError> { - if call.tool_id.as_str() != self.tool_name { - return Ok(None); - } - Ok(Some(ToolOutput { - tool_name: self.tool_name.into(), - summary: "scraped body".to_owned(), - blocks_executed: 1, - filter_stats: None, - diff: None, - streamed: false, - terminal_id: None, - locations: None, - raw_response: None, - claim_source: None, - ..Default::default() - })) + fn execute_tool_call( + &self, + call: &ToolCall, + ) -> impl std::future::Future, ToolError>> + Send { + let result = (|| { + if call.tool_id.as_str() != self.tool_name { + return Ok(None); + } + Ok(Some(ToolOutput { + tool_name: self.tool_name.into(), + summary: "scraped body".to_owned(), + blocks_executed: 1, + filter_stats: None, + diff: None, + streamed: false, + terminal_id: None, + locations: None, + raw_response: None, + claim_source: None, + ..Default::default() + })) + })(); + std::future::ready(result) } zeph_tools::tool_executor_no_inner_defaults!(); diff --git a/crates/zeph-core/src/agent/tool_execution/tests/native_tests.rs b/crates/zeph-core/src/agent/tool_execution/tests/native_tests.rs index 076ea594e..b7d18b1bb 100644 --- a/crates/zeph-core/src/agent/tool_execution/tests/native_tests.rs +++ b/crates/zeph-core/src/agent/tool_execution/tests/native_tests.rs @@ -777,12 +777,18 @@ use crate::agent::speculative::prediction::{Prediction, PredictionSource}; struct AlwaysOkSpecExec; impl ToolExecutor for AlwaysOkSpecExec { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(None)) } - async fn execute_tool_call(&self, call: &ToolCall) -> Result, ToolError> { - Ok(Some(ToolOutput { + fn execute_tool_call( + &self, + call: &ToolCall, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(Some(ToolOutput { tool_name: call.tool_id.clone(), summary: "speculative-ok".into(), blocks_executed: 1, @@ -794,7 +800,7 @@ impl ToolExecutor for AlwaysOkSpecExec { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } fn is_tool_speculatable(&self, _: &str) -> bool { @@ -823,14 +829,20 @@ impl ToolExecutor for AlwaysOkSpecExec { struct AlwaysErrSpecExec; impl ToolExecutor for AlwaysErrSpecExec { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(None)) } - async fn execute_tool_call(&self, _: &ToolCall) -> Result, ToolError> { - Err(ToolError::Execution(std::io::Error::other( + fn execute_tool_call( + &self, + _: &ToolCall, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Err(ToolError::Execution(std::io::Error::other( "simulated error", - ))) + )))) } fn is_tool_speculatable(&self, _: &str) -> bool { diff --git a/crates/zeph-core/src/channel.rs b/crates/zeph-core/src/channel.rs index fa4d8c620..b75f10531 100644 --- a/crates/zeph-core/src/channel.rs +++ b/crates/zeph-core/src/channel.rs @@ -270,6 +270,19 @@ const STATUS_SEND_TIMEOUT: std::time::Duration = std::time::Duration::from_secs( /// **Blocked by:** workspace-wide breaking change affecting CLI, Telegram, TUI, gateway, JSON, /// Discord, Slack, loopback channels, and all integration tests. Must be migrated channel by /// channel across ≥5 PRs. Requires its own SDD spec. See critic review §S4. +/// +/// # Implementor contract: side effects must stay poll-time-lazy (#6746) +/// +/// Every method here returns `impl Future + Send`. A body with no internal +/// `.await` must remain a genuine `async fn` (with `#[allow(clippy::unused_async_trait_impl)]`) +/// whenever it has an observable side effect — mutating `self`, sending on a channel, or +/// panicking. **Never** rewrite such a body to a synchronous `fn` wrapping +/// `std::future::ready(body)`: `std::future::ready` evaluates its argument eagerly, at the +/// moment the function is *called*, not when the returned future is first polled. A caller +/// that constructs the future as a losing `tokio::select!` arm (or otherwise never polls it) +/// would still trigger the side effect — see `BenchmarkChannel::recv`'s history for a +/// confirmed instance of exactly this bug. `std::future::ready` is safe only for bodies that +/// are pure computation over their arguments. pub trait Channel: Send { /// Receive the next message. Returns `None` on EOF or shutdown. /// @@ -859,8 +872,11 @@ impl Channel for LoopbackChannel { .map_err(|_| ChannelError::ChannelClosed) } - async fn confirm(&mut self, _prompt: &str) -> Result { - Ok(true) + fn confirm( + &mut self, + _prompt: &str, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(true)) } async fn send_stop_hint(&mut self, hint: StopHint) -> Result<(), ChannelError> { @@ -981,20 +997,31 @@ mod tests { struct StubChannel; impl Channel for StubChannel { - async fn recv(&mut self) -> Result, ChannelError> { - Ok(None) + fn recv( + &mut self, + ) -> impl std::future::Future, ChannelError>> + Send + { + std::future::ready(Ok(None)) } - async fn send(&mut self, _text: &str) -> Result<(), ChannelError> { - Ok(()) + fn send( + &mut self, + _text: &str, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(())) } - async fn send_chunk(&mut self, _chunk: &str) -> Result<(), ChannelError> { - Ok(()) + fn send_chunk( + &mut self, + _chunk: &str, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(())) } - async fn flush_chunks(&mut self) -> Result<(), ChannelError> { - Ok(()) + fn flush_chunks( + &mut self, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(())) } } @@ -1046,24 +1073,38 @@ mod tests { struct ErroringStatusChannel; impl Channel for ErroringStatusChannel { - async fn recv(&mut self) -> Result, ChannelError> { - Ok(None) + fn recv( + &mut self, + ) -> impl std::future::Future, ChannelError>> + Send + { + std::future::ready(Ok(None)) } - async fn send(&mut self, _text: &str) -> Result<(), ChannelError> { - Ok(()) + fn send( + &mut self, + _text: &str, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(())) } - async fn send_chunk(&mut self, _chunk: &str) -> Result<(), ChannelError> { - Ok(()) + fn send_chunk( + &mut self, + _chunk: &str, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(())) } - async fn flush_chunks(&mut self) -> Result<(), ChannelError> { - Ok(()) + fn flush_chunks( + &mut self, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(())) } - async fn send_status(&mut self, _text: &str) -> Result<(), ChannelError> { - Err(ChannelError::ChannelClosed) + fn send_status( + &mut self, + _text: &str, + ) -> impl std::future::Future> + Send { + std::future::ready(Err(ChannelError::ChannelClosed)) } } @@ -1103,20 +1144,31 @@ mod tests { struct HangingStatusChannel; impl Channel for HangingStatusChannel { - async fn recv(&mut self) -> Result, ChannelError> { - Ok(None) + fn recv( + &mut self, + ) -> impl std::future::Future, ChannelError>> + Send + { + std::future::ready(Ok(None)) } - async fn send(&mut self, _text: &str) -> Result<(), ChannelError> { - Ok(()) + fn send( + &mut self, + _text: &str, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(())) } - async fn send_chunk(&mut self, _chunk: &str) -> Result<(), ChannelError> { - Ok(()) + fn send_chunk( + &mut self, + _chunk: &str, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(())) } - async fn flush_chunks(&mut self) -> Result<(), ChannelError> { - Ok(()) + fn flush_chunks( + &mut self, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(())) } async fn send_status(&mut self, _text: &str) -> Result<(), ChannelError> { diff --git a/crates/zeph-core/src/lsp_hooks/test_helpers.rs b/crates/zeph-core/src/lsp_hooks/test_helpers.rs index 6875ba121..e7f419c45 100644 --- a/crates/zeph-core/src/lsp_hooks/test_helpers.rs +++ b/crates/zeph-core/src/lsp_hooks/test_helpers.rs @@ -38,6 +38,7 @@ impl RecordingCaller { } impl McpCaller for RecordingCaller { + #[allow(clippy::unused_async_trait_impl)] async fn call_tool( &self, server_id: &str, @@ -57,7 +58,7 @@ impl McpCaller for RecordingCaller { queue.remove(0) } - async fn list_servers(&self) -> Vec { - vec!["mcpls".to_owned()] + fn list_servers(&self) -> impl std::future::Future> + Send { + std::future::ready(vec!["mcpls".to_owned()]) } } diff --git a/crates/zeph-core/src/memory_tools.rs b/crates/zeph-core/src/memory_tools.rs index b14eae208..de94996af 100644 --- a/crates/zeph-core/src/memory_tools.rs +++ b/crates/zeph-core/src/memory_tools.rs @@ -350,8 +350,11 @@ impl ToolExecutor for MemoryToolExecutor { ] } - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(None)) } #[allow(clippy::too_many_lines)] // two tools with validation, search, and multi-source aggregation diff --git a/crates/zeph-core/src/overflow_tools.rs b/crates/zeph-core/src/overflow_tools.rs index a769e08bc..bdf5cbf2c 100644 --- a/crates/zeph-core/src/overflow_tools.rs +++ b/crates/zeph-core/src/overflow_tools.rs @@ -54,8 +54,11 @@ impl ToolExecutor for OverflowToolExecutor { }] } - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(None)) } async fn execute_tool_call(&self, call: &ToolCall) -> Result, ToolError> { diff --git a/crates/zeph-core/src/pipeline/builtin.rs b/crates/zeph-core/src/pipeline/builtin.rs index 464c29be7..14601c7b0 100644 --- a/crates/zeph-core/src/pipeline/builtin.rs +++ b/crates/zeph-core/src/pipeline/builtin.rs @@ -107,8 +107,13 @@ impl Step for ExtractStep { type Input = String; type Output = T; - async fn run(&self, input: Self::Input) -> Result { - serde_json::from_str(&input).map_err(|e| PipelineError::Extract(e.to_string())) + fn run( + &self, + input: Self::Input, + ) -> impl std::future::Future> + Send { + std::future::ready( + serde_json::from_str(&input).map_err(|e| PipelineError::Extract(e.to_string())), + ) } } @@ -136,7 +141,10 @@ where type Input = In; type Output = Out; - async fn run(&self, input: Self::Input) -> Result { - Ok((self.f)(input)) + fn run( + &self, + input: Self::Input, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok((self.f)(input))) } } diff --git a/crates/zeph-core/src/pipeline/mod.rs b/crates/zeph-core/src/pipeline/mod.rs index dad80f130..e4e0f1b78 100644 --- a/crates/zeph-core/src/pipeline/mod.rs +++ b/crates/zeph-core/src/pipeline/mod.rs @@ -48,8 +48,11 @@ mod tests { type Input = String; type Output = String; - async fn run(&self, input: Self::Input) -> Result { - Ok(format!("{input}{}", self.suffix)) + fn run( + &self, + input: Self::Input, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(format!("{input}{}", self.suffix))) } } @@ -59,8 +62,11 @@ mod tests { type Input = String; type Output = usize; - async fn run(&self, input: Self::Input) -> Result { - Ok(input.len()) + fn run( + &self, + input: Self::Input, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(input.len())) } } @@ -128,8 +134,12 @@ mod tests { type Input = String; type Output = String; - async fn run(&self, _input: Self::Input) -> Result { - Err(PipelineError::Custom("boom".into())) + fn run( + &self, + _input: Self::Input, + ) -> impl std::future::Future> + Send + { + std::future::ready(Err(PipelineError::Custom("boom".into()))) } } @@ -266,8 +276,12 @@ mod tests { impl Step for FailStep { type Input = String; type Output = String; - async fn run(&self, _input: Self::Input) -> Result { - Err(PipelineError::Custom("fail_a".into())) + fn run( + &self, + _input: Self::Input, + ) -> impl std::future::Future> + Send + { + std::future::ready(Err(PipelineError::Custom("fail_a".into()))) } } @@ -287,16 +301,24 @@ mod tests { impl Step for FailA { type Input = String; type Output = String; - async fn run(&self, _input: Self::Input) -> Result { - Err(PipelineError::Custom("fail_a".into())) + fn run( + &self, + _input: Self::Input, + ) -> impl std::future::Future> + Send + { + std::future::ready(Err(PipelineError::Custom("fail_a".into()))) } } struct FailB; impl Step for FailB { type Input = String; type Output = String; - async fn run(&self, _input: Self::Input) -> Result { - Err(PipelineError::Custom("fail_b".into())) + fn run( + &self, + _input: Self::Input, + ) -> impl std::future::Future> + Send + { + std::future::ready(Err(PipelineError::Custom("fail_b".into()))) } } diff --git a/crates/zeph-core/src/skill_invoker.rs b/crates/zeph-core/src/skill_invoker.rs index f85933570..c1c643e18 100644 --- a/crates/zeph-core/src/skill_invoker.rs +++ b/crates/zeph-core/src/skill_invoker.rs @@ -109,8 +109,11 @@ impl SkillInvokeExecutor { } impl ToolExecutor for SkillInvokeExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(None)) } fn tool_definitions(&self) -> Vec { diff --git a/crates/zeph-core/src/skill_loader.rs b/crates/zeph-core/src/skill_loader.rs index 611637ba6..1db229a53 100644 --- a/crates/zeph-core/src/skill_loader.rs +++ b/crates/zeph-core/src/skill_loader.rs @@ -91,8 +91,11 @@ impl SkillLoaderExecutor { } impl ToolExecutor for SkillLoaderExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(None)) } fn tool_definitions(&self) -> Vec { diff --git a/crates/zeph-core/src/skill_trust_gate.rs b/crates/zeph-core/src/skill_trust_gate.rs index 40c43d41c..d286897a8 100644 --- a/crates/zeph-core/src/skill_trust_gate.rs +++ b/crates/zeph-core/src/skill_trust_gate.rs @@ -559,18 +559,22 @@ mod tests { struct AlwaysOkExecutor; impl zeph_tools::executor::ToolExecutor for AlwaysOkExecutor { - async fn execute( + fn execute( &self, _response: &str, - ) -> Result, ToolError> { - Ok(None) + ) -> impl std::future::Future< + Output = Result, ToolError>, + > + Send { + std::future::ready(Ok(None)) } - async fn execute_tool_call( + fn execute_tool_call( &self, call: &zeph_tools::executor::ToolCall, - ) -> Result, ToolError> { - Ok(Some(zeph_tools::executor::ToolOutput { + ) -> impl std::future::Future< + Output = Result, ToolError>, + > + Send { + std::future::ready(Ok(Some(zeph_tools::executor::ToolOutput { tool_name: call.tool_id.clone(), summary: "ok".into(), blocks_executed: 1, @@ -582,7 +586,7 @@ mod tests { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } zeph_tools::tool_executor_no_inner_defaults!(); diff --git a/crates/zeph-core/src/testing.rs b/crates/zeph-core/src/testing.rs index 911018d36..3e78f2c08 100644 --- a/crates/zeph-core/src/testing.rs +++ b/crates/zeph-core/src/testing.rs @@ -73,6 +73,7 @@ impl MockChannel { } impl Channel for MockChannel { + #[allow(clippy::unused_async_trait_impl)] async fn recv(&mut self) -> Result, ChannelError> { let mut msgs = self.messages.lock().unwrap(); if msgs.is_empty() { @@ -103,20 +104,25 @@ impl Channel for MockChannel { } } + #[allow(clippy::unused_async_trait_impl)] async fn send(&mut self, text: &str) -> Result<(), ChannelError> { self.sent.lock().unwrap().push(text.to_string()); Ok(()) } + #[allow(clippy::unused_async_trait_impl)] async fn send_chunk(&mut self, chunk: &str) -> Result<(), ChannelError> { self.chunks.lock().unwrap().push(chunk.to_string()); Ok(()) } - async fn flush_chunks(&mut self) -> Result<(), ChannelError> { - Ok(()) + fn flush_chunks( + &mut self, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(())) } + #[allow(clippy::unused_async_trait_impl)] async fn confirm(&mut self, _prompt: &str) -> Result { let mut confs = self.confirmations.lock().unwrap(); Ok(if confs.is_empty() { @@ -184,6 +190,7 @@ impl MockToolExecutor { } impl zeph_tools::executor::ToolExecutor for MockToolExecutor { + #[allow(clippy::unused_async_trait_impl)] async fn execute(&self, _response: &str) -> Result, ToolError> { let mut outputs = self.outputs.lock().unwrap(); if outputs.is_empty() { diff --git a/crates/zeph-core/tests/turn_lifecycle.rs b/crates/zeph-core/tests/turn_lifecycle.rs index d9ca15896..2981d3954 100644 --- a/crates/zeph-core/tests/turn_lifecycle.rs +++ b/crates/zeph-core/tests/turn_lifecycle.rs @@ -46,6 +46,7 @@ impl TestChannel { } impl Channel for TestChannel { + #[allow(clippy::unused_async_trait_impl)] async fn recv(&mut self) -> Result, ChannelError> { if self.inbox.is_empty() { Ok(None) @@ -74,22 +75,29 @@ impl Channel for TestChannel { } } + #[allow(clippy::unused_async_trait_impl)] async fn send(&mut self, text: &str) -> Result<(), ChannelError> { self.outbox.lock().unwrap().push(text.to_owned()); Ok(()) } + #[allow(clippy::unused_async_trait_impl)] async fn send_chunk(&mut self, chunk: &str) -> Result<(), ChannelError> { self.outbox.lock().unwrap().push(chunk.to_owned()); Ok(()) } - async fn flush_chunks(&mut self) -> Result<(), ChannelError> { - Ok(()) + fn flush_chunks( + &mut self, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(())) } - async fn confirm(&mut self, _prompt: &str) -> Result { - Ok(true) + fn confirm( + &mut self, + _prompt: &str, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(true)) } } @@ -97,8 +105,11 @@ impl Channel for TestChannel { struct NoopExecutor; impl ToolExecutor for NoopExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(None)) } fn set_skill_env(&self, _env: Option>) {} @@ -132,8 +143,11 @@ impl SingleToolExecutor { } impl ToolExecutor for SingleToolExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(self.output.lock().unwrap().take()) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(self.output.lock().unwrap().take())) } fn set_skill_env(&self, _env: Option>) {} diff --git a/crates/zeph-db/README.md b/crates/zeph-db/README.md index 2cae5767c..027e8b799 100644 --- a/crates/zeph-db/README.md +++ b/crates/zeph-db/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-db)](https://crates.io/crates/zeph-db) [![docs.rs](https://img.shields.io/docsrs/zeph-db)](https://docs.rs/zeph-db) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-yellow.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) Database abstraction layer for [Zeph](https://github.com/bug-ops/zeph) — unified SQLite and PostgreSQL backends with compile-time backend selection, automatic migrations, dialect-aware SQL helpers, and FTS support. @@ -181,7 +181,7 @@ Migrations run automatically on first `DbConfig::connect` call. The active backe ## MSRV -Rust **1.97** (Edition 2024, resolver 3). +Rust **1.98** (Edition 2024, resolver 3). ## License diff --git a/crates/zeph-durable/README.md b/crates/zeph-durable/README.md index f4d443f32..8df544d84 100644 --- a/crates/zeph-durable/README.md +++ b/crates/zeph-durable/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-durable)](https://crates.io/crates/zeph-durable) [![docs.rs](https://img.shields.io/docsrs/zeph-durable)](https://docs.rs/zeph-durable) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-yellow.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) Native durable execution layer for [Zeph](https://github.com/bug-ops/zeph) — journals the *control flow* of an execution (steps, promises, timers) so a crashed or interrupted run can resume at the @@ -203,7 +203,7 @@ let reply: String = ctx ## MSRV -Rust **1.97** (Edition 2024, resolver 3). +Rust **1.98** (Edition 2024, resolver 3). ## License diff --git a/crates/zeph-experiments/README.md b/crates/zeph-experiments/README.md index 336be6986..7d16b0e09 100644 --- a/crates/zeph-experiments/README.md +++ b/crates/zeph-experiments/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-experiments)](https://crates.io/crates/zeph-experiments) [![docs.rs](https://img.shields.io/docsrs/zeph-experiments)](https://docs.rs/zeph-experiments) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-yellow.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) Experiment engine for adaptive agent behavior — autonomous hyperparameter search and A/B testing for Zeph. diff --git a/crates/zeph-gateway/README.md b/crates/zeph-gateway/README.md index d3a3f77bc..993111830 100644 --- a/crates/zeph-gateway/README.md +++ b/crates/zeph-gateway/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-gateway)](https://crates.io/crates/zeph-gateway) [![docs.rs](https://img.shields.io/docsrs/zeph-gateway)](https://docs.rs/zeph-gateway) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-yellow.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) HTTP gateway for webhook ingestion with bearer auth for Zeph. diff --git a/crates/zeph-index/README.md b/crates/zeph-index/README.md index 955b331f7..0d5d8ab9f 100644 --- a/crates/zeph-index/README.md +++ b/crates/zeph-index/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-index)](https://crates.io/crates/zeph-index) [![docs.rs](https://img.shields.io/docsrs/zeph-index)](https://docs.rs/zeph-index) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-yellow.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) AST-based code indexing, semantic retrieval, and repo map generation for Zeph. diff --git a/crates/zeph-llm/README.md b/crates/zeph-llm/README.md index 93a359783..0138b06d7 100644 --- a/crates/zeph-llm/README.md +++ b/crates/zeph-llm/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-llm)](https://crates.io/crates/zeph-llm) [![docs.rs](https://img.shields.io/docsrs/zeph-llm)](https://docs.rs/zeph-llm) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-yellow.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) LLM provider abstraction with Ollama, Claude, OpenAI, Gemini, Gonka, and Candle backends. diff --git a/crates/zeph-llm/src/candle_whisper.rs b/crates/zeph-llm/src/candle_whisper.rs index 06d65138a..1ca2726b3 100644 --- a/crates/zeph-llm/src/candle_whisper.rs +++ b/crates/zeph-llm/src/candle_whisper.rs @@ -114,8 +114,8 @@ impl CandleWhisperProvider { } }; let mut mel_filters = vec![0f32; mel_bytes.len() / 4]; - for (i, chunk) in mel_bytes.chunks_exact(4).enumerate() { - mel_filters[i] = f32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]); + for (i, chunk) in mel_bytes.as_chunks::<4>().0.iter().enumerate() { + mel_filters[i] = f32::from_le_bytes(*chunk); } tracing::info!("candle whisper model loaded"); diff --git a/crates/zeph-llm/src/extractor.rs b/crates/zeph-llm/src/extractor.rs index 1962f2876..6460f8940 100644 --- a/crates/zeph-llm/src/extractor.rs +++ b/crates/zeph-llm/src/extractor.rs @@ -94,8 +94,11 @@ mod tests { } impl LlmProvider for StubProvider { - async fn chat(&self, _messages: &[Message]) -> Result { - Ok(self.response.clone()) + fn chat( + &self, + _messages: &[Message], + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(self.response.clone())) } async fn chat_stream(&self, messages: &[Message]) -> Result { @@ -109,10 +112,13 @@ mod tests { false } - async fn embed(&self, _text: &str) -> Result, LlmError> { - Err(LlmError::EmbedUnsupported { + fn embed( + &self, + _text: &str, + ) -> impl std::future::Future, LlmError>> + Send { + std::future::ready(Err(LlmError::EmbedUnsupported { provider: "stub".into(), - }) + })) } fn supports_embeddings(&self) -> bool { @@ -164,20 +170,30 @@ mod tests { struct FailProvider; impl LlmProvider for FailProvider { - async fn chat(&self, _messages: &[Message]) -> Result { - Err(LlmError::Unavailable) + fn chat( + &self, + _messages: &[Message], + ) -> impl std::future::Future> + Send { + std::future::ready(Err(LlmError::Unavailable)) } - async fn chat_stream(&self, _messages: &[Message]) -> Result { - Err(LlmError::Unavailable) + fn chat_stream( + &self, + _messages: &[Message], + ) -> impl std::future::Future> + Send + { + std::future::ready(Err(LlmError::Unavailable)) } fn supports_streaming(&self) -> bool { false } - async fn embed(&self, _text: &str) -> Result, LlmError> { - Err(LlmError::Unavailable) + fn embed( + &self, + _text: &str, + ) -> impl std::future::Future, LlmError>> + Send { + std::future::ready(Err(LlmError::Unavailable)) } fn supports_embeddings(&self) -> bool { diff --git a/crates/zeph-llm/src/provider.rs b/crates/zeph-llm/src/provider.rs index 8d43b0fdc..32d91c425 100644 --- a/crates/zeph-llm/src/provider.rs +++ b/crates/zeph-llm/src/provider.rs @@ -1095,8 +1095,11 @@ mod tests { } impl LlmProvider for StubProvider { - async fn chat(&self, _messages: &[Message]) -> Result { - Ok(self.response.clone()) + fn chat( + &self, + _messages: &[Message], + ) -> impl Future> + Send { + std::future::ready(Ok(self.response.clone())) } async fn chat_stream(&self, messages: &[Message]) -> Result { @@ -1110,8 +1113,8 @@ mod tests { false } - async fn embed(&self, _text: &str) -> Result, LlmError> { - Ok(vec![0.1, 0.2, 0.3]) + fn embed(&self, _text: &str) -> impl Future, LlmError>> + Send { + std::future::ready(Ok(vec![0.1, 0.2, 0.3])) } fn supports_embeddings(&self) -> bool { @@ -1186,8 +1189,11 @@ mod tests { struct FailProvider; impl LlmProvider for FailProvider { - async fn chat(&self, _messages: &[Message]) -> Result { - Err(LlmError::Unavailable) + fn chat( + &self, + _messages: &[Message], + ) -> impl Future> + Send { + std::future::ready(Err(LlmError::Unavailable)) } async fn chat_stream(&self, messages: &[Message]) -> Result { @@ -1201,8 +1207,11 @@ mod tests { false } - async fn embed(&self, _text: &str) -> Result, LlmError> { - Err(LlmError::Unavailable) + fn embed( + &self, + _text: &str, + ) -> impl Future, LlmError>> + Send { + std::future::ready(Err(LlmError::Unavailable)) } fn supports_embeddings(&self) -> bool { @@ -1243,8 +1252,11 @@ mod tests { struct FailProvider; impl LlmProvider for FailProvider { - async fn chat(&self, _messages: &[Message]) -> Result { - Err(LlmError::Unavailable) + fn chat( + &self, + _messages: &[Message], + ) -> impl Future> + Send { + std::future::ready(Err(LlmError::Unavailable)) } async fn chat_stream(&self, messages: &[Message]) -> Result { @@ -1258,10 +1270,13 @@ mod tests { false } - async fn embed(&self, _text: &str) -> Result, LlmError> { - Err(LlmError::EmbedUnsupported { + fn embed( + &self, + _text: &str, + ) -> impl Future, LlmError>> + Send { + std::future::ready(Err(LlmError::EmbedUnsupported { provider: "fail".into(), - }) + })) } fn supports_embeddings(&self) -> bool { @@ -1673,12 +1688,14 @@ mod tests { } impl LlmProvider for SequentialStub { + #[allow(clippy::unused_async_trait_impl)] async fn chat(&self, _messages: &[Message]) -> Result { let mut responses = self.responses.lock().unwrap(); if responses.is_empty() { - return Err(LlmError::Other("no more responses".into())); + Err(LlmError::Other("no more responses".into())) + } else { + responses.remove(0) } - responses.remove(0) } async fn chat_stream(&self, messages: &[Message]) -> Result { @@ -1692,10 +1709,10 @@ mod tests { false } - async fn embed(&self, _text: &str) -> Result, LlmError> { - Err(LlmError::EmbedUnsupported { + fn embed(&self, _text: &str) -> impl Future, LlmError>> + Send { + std::future::ready(Err(LlmError::EmbedUnsupported { provider: "sequential-stub".into(), - }) + })) } fn supports_embeddings(&self) -> bool { diff --git a/crates/zeph-llm/src/provider_dyn.rs b/crates/zeph-llm/src/provider_dyn.rs index 94d4554b7..2205dca06 100644 --- a/crates/zeph-llm/src/provider_dyn.rs +++ b/crates/zeph-llm/src/provider_dyn.rs @@ -390,8 +390,11 @@ mod tests { } impl LlmProvider for StubProvider { - async fn chat(&self, _messages: &[Message]) -> Result { - Ok(self.response.clone()) + fn chat( + &self, + _messages: &[Message], + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(self.response.clone())) } async fn chat_stream(&self, messages: &[Message]) -> Result { @@ -405,8 +408,11 @@ mod tests { false } - async fn embed(&self, _text: &str) -> Result, LlmError> { - Ok(vec![0.1, 0.2, 0.3]) + fn embed( + &self, + _text: &str, + ) -> impl std::future::Future, LlmError>> + Send { + std::future::ready(Ok(vec![0.1, 0.2, 0.3])) } fn supports_embeddings(&self) -> bool { diff --git a/crates/zeph-mcp/README.md b/crates/zeph-mcp/README.md index aeeda7660..e6962beac 100644 --- a/crates/zeph-mcp/README.md +++ b/crates/zeph-mcp/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-mcp)](https://crates.io/crates/zeph-mcp) [![docs.rs](https://img.shields.io/docsrs/zeph-mcp)](https://docs.rs/zeph-mcp) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-yellow.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) MCP client with multi-server lifecycle and Qdrant tool registry for Zeph. diff --git a/crates/zeph-mcp/src/client.rs b/crates/zeph-mcp/src/client.rs index 89f5228ef..cceb88302 100644 --- a/crates/zeph-mcp/src/client.rs +++ b/crates/zeph-mcp/src/client.rs @@ -2206,6 +2206,7 @@ mod tests { ) } + #[allow(clippy::unused_async_trait_impl)] async fn list_tools( &self, _request: Option, @@ -2221,40 +2222,46 @@ mod tests { ])) } - async fn call_tool( + fn call_tool( &self, request: CallToolRequestParams, _context: rmcp::service::RequestContext, - ) -> Result { - if request.name.as_ref() != DUPLEX_TEST_TOOL_NAME { - return Err(rmcp::model::ErrorData::invalid_params( - format!("unknown tool: {}", request.name), - None, - )); - } - let echo = request - .arguments - .as_ref() - .and_then(|args| args.get("echo")) - .and_then(serde_json::Value::as_str) - .unwrap_or_default(); - Ok(CallToolResult::success(vec![ - rmcp::model::ContentBlock::text(format!("hello from duplex test, echo={echo}")), - rmcp::model::ContentBlock::image("c2VjcmV0Ynl0ZXM=", "image/png"), - rmcp::model::ContentBlock::embedded_text( - "file:///notes.txt", - "embedded text resource content", - ), - rmcp::model::ContentBlock::resource(rmcp::model::ResourceContents::blob( - "Ymxvb2I=", - "file:///x.bin", - )), - rmcp::model::ContentBlock::resource_link(rmcp::model::Resource::new( - "file:///report.pdf", - "report.pdf", - )), - ]) - .into()) + ) -> impl std::future::Future< + Output = Result, + > + rmcp::service::MaybeSendFuture + + '_ { + let result = (|| { + if request.name.as_ref() != DUPLEX_TEST_TOOL_NAME { + return Err(rmcp::model::ErrorData::invalid_params( + format!("unknown tool: {}", request.name), + None, + )); + } + let echo = request + .arguments + .as_ref() + .and_then(|args| args.get("echo")) + .and_then(serde_json::Value::as_str) + .unwrap_or_default(); + Ok(CallToolResult::success(vec![ + rmcp::model::ContentBlock::text(format!("hello from duplex test, echo={echo}")), + rmcp::model::ContentBlock::image("c2VjcmV0Ynl0ZXM=", "image/png"), + rmcp::model::ContentBlock::embedded_text( + "file:///notes.txt", + "embedded text resource content", + ), + rmcp::model::ContentBlock::resource(rmcp::model::ResourceContents::blob( + "Ymxvb2I=", + "file:///x.bin", + )), + rmcp::model::ContentBlock::resource_link(rmcp::model::Resource::new( + "file:///report.pdf", + "report.pdf", + )), + ]) + .into()) + })(); + std::future::ready(result) } } diff --git a/crates/zeph-mcp/src/testing.rs b/crates/zeph-mcp/src/testing.rs index 772539394..7796a476c 100644 --- a/crates/zeph-mcp/src/testing.rs +++ b/crates/zeph-mcp/src/testing.rs @@ -131,13 +131,17 @@ impl ToolExecutor for MockMcpServer { .collect() } - async fn execute(&self, _response: &str) -> Result, ToolError> { + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { // Intentionally no-op: this mock operates at the ToolExecutor level // and bypasses the rmcp transport layer entirely. Tool dispatch always // goes through execute_tool_call; execute is never called in tests. - Ok(None) + std::future::ready(Ok(None)) } + #[allow(clippy::unused_async_trait_impl)] async fn execute_tool_call(&self, call: &ToolCall) -> Result, ToolError> { // Record the call for test assertions. self.recorded_calls.lock().unwrap().push(( diff --git a/crates/zeph-memory/README.md b/crates/zeph-memory/README.md index c7ceed223..c78253876 100644 --- a/crates/zeph-memory/README.md +++ b/crates/zeph-memory/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-memory)](https://crates.io/crates/zeph-memory) [![docs.rs](https://img.shields.io/docsrs/zeph-memory)](https://docs.rs/zeph-memory) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-yellow.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) Semantic memory with SQLite and Qdrant for Zeph agent. diff --git a/crates/zeph-memory/src/db_vector_store.rs b/crates/zeph-memory/src/db_vector_store.rs index 184dbcda8..a4fd95624 100644 --- a/crates/zeph-memory/src/db_vector_store.rs +++ b/crates/zeph-memory/src/db_vector_store.rs @@ -198,7 +198,9 @@ impl VectorStore for DbVectorStore { return None; } let stored: Vec = blob - .chunks_exact(4) + .as_chunks::<4>() + .0 + .iter() .map(|b| f32::from_le_bytes([b[0], b[1], b[2], b[3]])) .collect(); let payload: HashMap = diff --git a/crates/zeph-memory/src/embedding_store.rs b/crates/zeph-memory/src/embedding_store.rs index 403cfb159..8ac129d69 100644 --- a/crates/zeph-memory/src/embedding_store.rs +++ b/crates/zeph-memory/src/embedding_store.rs @@ -799,7 +799,9 @@ impl EmbeddingStore { return None; } let vec: Vec = blob - .chunks_exact(4) + .as_chunks::<4>() + .0 + .iter() .map(|b| f32::from_le_bytes([b[0], b[1], b[2], b[3]])) .collect(); Some((msg_id, vec)) diff --git a/crates/zeph-memory/src/response_cache.rs b/crates/zeph-memory/src/response_cache.rs index 3f38fb8ed..8752d1473 100644 --- a/crates/zeph-memory/src/response_cache.rs +++ b/crates/zeph-memory/src/response_cache.rs @@ -126,7 +126,9 @@ impl ResponseCache { continue; } let stored: Vec = blob - .chunks_exact(4) + .as_chunks::<4>() + .0 + .iter() .map(|b| f32::from_le_bytes([b[0], b[1], b[2], b[3]])) .collect(); let score = zeph_common::math::cosine_similarity(embedding, &stored); diff --git a/crates/zeph-orchestration/README.md b/crates/zeph-orchestration/README.md index 7cd6c8d7f..e46bd7c57 100644 --- a/crates/zeph-orchestration/README.md +++ b/crates/zeph-orchestration/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-orchestration)](https://crates.io/crates/zeph-orchestration) [![docs.rs](https://img.shields.io/docsrs/zeph-orchestration)](https://docs.rs/zeph-orchestration) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-yellow.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) DAG-based task orchestration with failure propagation, LLM planning, and SQLite persistence for Zeph. diff --git a/crates/zeph-orchestration/src/aggregator.rs b/crates/zeph-orchestration/src/aggregator.rs index cb2b4fc15..0596168ee 100644 --- a/crates/zeph-orchestration/src/aggregator.rs +++ b/crates/zeph-orchestration/src/aggregator.rs @@ -409,8 +409,12 @@ mod tests { fn supports_streaming(&self) -> bool { false } - async fn embed(&self, _: &str) -> Result, LlmError> { - Err(LlmError::Unavailable) + fn embed( + &self, + _: &str, + ) -> impl std::future::Future, LlmError>> + Send + { + std::future::ready(Err(LlmError::Unavailable)) } fn supports_embeddings(&self) -> bool { false diff --git a/crates/zeph-orchestration/src/plan_cache.rs b/crates/zeph-orchestration/src/plan_cache.rs index 99ea2cab7..08fe5f25f 100644 --- a/crates/zeph-orchestration/src/plan_cache.rs +++ b/crates/zeph-orchestration/src/plan_cache.rs @@ -193,8 +193,10 @@ fn blob_to_embedding(blob: &[u8]) -> Option> { return None; } Some( - blob.chunks_exact(4) - .map(|chunk| f32::from_le_bytes(chunk.try_into().expect("chunk is exactly 4 bytes"))) + blob.as_chunks::<4>() + .0 + .iter() + .map(|chunk| f32::from_le_bytes(*chunk)) .collect(), ) } diff --git a/crates/zeph-orchestration/src/planner.rs b/crates/zeph-orchestration/src/planner.rs index ef08c79e8..107a50a66 100644 --- a/crates/zeph-orchestration/src/planner.rs +++ b/crates/zeph-orchestration/src/planner.rs @@ -1099,8 +1099,12 @@ mod tests { fn supports_streaming(&self) -> bool { false } - async fn embed(&self, _: &str) -> Result, LlmError> { - Err(LlmError::Unavailable) + fn embed( + &self, + _: &str, + ) -> impl std::future::Future, LlmError>> + Send + { + std::future::ready(Err(LlmError::Unavailable)) } fn supports_embeddings(&self) -> bool { false diff --git a/crates/zeph-orchestration/src/verifier.rs b/crates/zeph-orchestration/src/verifier.rs index 6c0cf9dd8..146e26c87 100644 --- a/crates/zeph-orchestration/src/verifier.rs +++ b/crates/zeph-orchestration/src/verifier.rs @@ -1127,11 +1127,14 @@ mod tests { } impl LlmProvider for MockProvider { - async fn chat(&self, _messages: &[Message]) -> Result { - match &self.response { + fn chat( + &self, + _messages: &[Message], + ) -> impl std::future::Future> + Send { + std::future::ready(match &self.response { Ok(s) => Ok(s.clone() as String), Err(_) => Err(LlmError::Unavailable), - } + }) } async fn chat_stream(&self, messages: &[Message]) -> Result { @@ -1145,8 +1148,11 @@ mod tests { false } - async fn embed(&self, _text: &str) -> Result, LlmError> { - Err(LlmError::Unavailable) + fn embed( + &self, + _text: &str, + ) -> impl std::future::Future, LlmError>> + Send { + std::future::ready(Err(LlmError::Unavailable)) } fn supports_embeddings(&self) -> bool { @@ -1549,8 +1555,12 @@ mod tests { fn supports_streaming(&self) -> bool { false } - async fn embed(&self, _: &str) -> Result, zeph_llm::LlmError> { - Err(zeph_llm::LlmError::Unavailable) + fn embed( + &self, + _: &str, + ) -> impl std::future::Future, zeph_llm::LlmError>> + Send + { + std::future::ready(Err(zeph_llm::LlmError::Unavailable)) } fn supports_embeddings(&self) -> bool { false diff --git a/crates/zeph-plugins/README.md b/crates/zeph-plugins/README.md index 1655826aa..bac32ebb3 100644 --- a/crates/zeph-plugins/README.md +++ b/crates/zeph-plugins/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-plugins)](https://crates.io/crates/zeph-plugins) [![docs.rs](https://img.shields.io/docsrs/zeph-plugins)](https://docs.rs/zeph-plugins) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-yellow.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) Plugin packaging, installation, and runtime config overlay for Zeph. diff --git a/crates/zeph-sanitizer/README.md b/crates/zeph-sanitizer/README.md index ae0d173ad..f5404a298 100644 --- a/crates/zeph-sanitizer/README.md +++ b/crates/zeph-sanitizer/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-sanitizer)](https://crates.io/crates/zeph-sanitizer) [![docs.rs](https://img.shields.io/docsrs/zeph-sanitizer)](https://docs.rs/zeph-sanitizer) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-yellow.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) Content sanitization, exfiltration guard, PII filtering, and quarantine for Zeph — untrusted input isolation before LLM context injection. diff --git a/crates/zeph-sanitizer/src/nli.rs b/crates/zeph-sanitizer/src/nli.rs index 4c958e839..6f86c6e3d 100644 --- a/crates/zeph-sanitizer/src/nli.rs +++ b/crates/zeph-sanitizer/src/nli.rs @@ -415,26 +415,34 @@ mod tests { } impl zeph_llm::provider::LlmProvider for OkProvider { - async fn chat(&self, _messages: &[Message]) -> Result { - Ok(self.response.clone()) + fn chat( + &self, + _messages: &[Message], + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(self.response.clone())) } - async fn chat_stream( + fn chat_stream( &self, _messages: &[Message], - ) -> Result { + ) -> impl std::future::Future> + Send + { let r = self.response.clone(); - Ok(Box::pin(tokio_stream::once(Ok( + let stream: zeph_llm::provider::ChatStream = Box::pin(tokio_stream::once(Ok( zeph_llm::provider::StreamChunk::Content(r), - )))) + ))); + std::future::ready(Ok(stream)) } fn supports_streaming(&self) -> bool { false } - async fn embed(&self, _text: &str) -> Result, LlmError> { - Ok(vec![]) + fn embed( + &self, + _text: &str, + ) -> impl std::future::Future, LlmError>> + Send { + std::future::ready(Ok(vec![])) } fn supports_embeddings(&self) -> bool { @@ -451,23 +459,30 @@ mod tests { struct ErrProvider; impl zeph_llm::provider::LlmProvider for ErrProvider { - async fn chat(&self, _messages: &[Message]) -> Result { - Err(LlmError::Inference("mock error".into())) + fn chat( + &self, + _messages: &[Message], + ) -> impl std::future::Future> + Send { + std::future::ready(Err(LlmError::Inference("mock error".into()))) } - async fn chat_stream( + fn chat_stream( &self, _messages: &[Message], - ) -> Result { - Err(LlmError::Inference("mock error".into())) + ) -> impl std::future::Future> + Send + { + std::future::ready(Err(LlmError::Inference("mock error".into()))) } fn supports_streaming(&self) -> bool { false } - async fn embed(&self, _text: &str) -> Result, LlmError> { - Ok(vec![]) + fn embed( + &self, + _text: &str, + ) -> impl std::future::Future, LlmError>> + Send { + std::future::ready(Ok(vec![])) } fn supports_embeddings(&self) -> bool { @@ -503,8 +518,11 @@ mod tests { false } - async fn embed(&self, _text: &str) -> Result, LlmError> { - Ok(vec![]) + fn embed( + &self, + _text: &str, + ) -> impl std::future::Future, LlmError>> + Send { + std::future::ready(Ok(vec![])) } fn supports_embeddings(&self) -> bool { diff --git a/crates/zeph-scheduler/README.md b/crates/zeph-scheduler/README.md index 2ebe15d34..499318016 100644 --- a/crates/zeph-scheduler/README.md +++ b/crates/zeph-scheduler/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-scheduler)](https://crates.io/crates/zeph-scheduler) [![docs.rs](https://img.shields.io/docsrs/zeph-scheduler)](https://docs.rs/zeph-scheduler) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-yellow.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) Cron-based periodic and one-shot task scheduler with SQLite persistence for Zeph. @@ -220,7 +220,7 @@ cargo add zeph-scheduler Enabled via the `scheduler` feature flag on the root `zeph` crate. > [!IMPORTANT] -> Requires Rust 1.97 or later. +> Requires Rust 1.98 or later. ## Features diff --git a/crates/zeph-session/README.md b/crates/zeph-session/README.md index 6dd080136..451840bf3 100644 --- a/crates/zeph-session/README.md +++ b/crates/zeph-session/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-session)](https://crates.io/crates/zeph-session) [![docs.rs](https://img.shields.io/docsrs/zeph-session)](https://docs.rs/zeph-session) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-yellow.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) Conversation-session persistence for [Zeph](https://github.com/bug-ops/zeph): an append-only JSONL event log, deterministic replay, and fork engine, shared by every channel (CLI, TUI, Telegram, ACP, diff --git a/crates/zeph-session/src/llm_condenser.rs b/crates/zeph-session/src/llm_condenser.rs index c6dc85fb2..7adfb3052 100644 --- a/crates/zeph-session/src/llm_condenser.rs +++ b/crates/zeph-session/src/llm_condenser.rs @@ -47,8 +47,14 @@ impl LlmCondenser { } impl Condenser for LlmCondenser { - async fn should_condense(&self, state: &ReconstructedState, budget_used_fraction: f64) -> bool { - budget_used_fraction >= self.threshold && state.messages.len() > self.keep_recent + fn should_condense( + &self, + state: &ReconstructedState, + budget_used_fraction: f64, + ) -> impl std::future::Future + Send { + std::future::ready( + budget_used_fraction >= self.threshold && state.messages.len() > self.keep_recent, + ) } #[tracing::instrument( diff --git a/crates/zeph-skills/README.md b/crates/zeph-skills/README.md index 313efb0a7..5897c4b7f 100644 --- a/crates/zeph-skills/README.md +++ b/crates/zeph-skills/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-skills)](https://crates.io/crates/zeph-skills) [![docs.rs](https://img.shields.io/docsrs/zeph-skills)](https://docs.rs/zeph-skills) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-yellow.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) SKILL.md parser, registry, embedding matcher, and hot-reload for Zeph. diff --git a/crates/zeph-skills/src/embedding.rs b/crates/zeph-skills/src/embedding.rs index ae5e68616..c5ef754d9 100644 --- a/crates/zeph-skills/src/embedding.rs +++ b/crates/zeph-skills/src/embedding.rs @@ -389,12 +389,14 @@ mod tests { false } + #[allow(clippy::unused_async_trait_impl)] async fn embed(&self, _text: &str) -> Result, zeph_llm::LlmError> { let call_no = self.embed_calls.fetch_add(1, Ordering::SeqCst) + 1; if self.fail_embed_at_call == Some(call_no) { - return Err(zeph_llm::LlmError::Unavailable); + Err(zeph_llm::LlmError::Unavailable) + } else { + Ok(vec![1.0, 0.0]) } - Ok(vec![1.0, 0.0]) } fn embed_batch( diff --git a/crates/zeph-skills/src/matcher.rs b/crates/zeph-skills/src/matcher.rs index 749099ac5..e513a328a 100644 --- a/crates/zeph-skills/src/matcher.rs +++ b/crates/zeph-skills/src/matcher.rs @@ -610,7 +610,14 @@ impl SkillMatcherBackend { /// call it when a consumer of [`Self::skill_embedding`] is actually enabled (RL rerank /// and/or `GoSkills` grouping), so turns using neither feature don't pay for the extra /// Qdrant round-trip (see issue #5786). - #[cfg_attr(not(feature = "qdrant"), allow(unused_variables, clippy::unused_async))] + #[cfg_attr( + not(feature = "qdrant"), + allow( + unused_variables, + clippy::unused_async, + clippy::unused_async_trait_impl + ) + )] pub async fn refresh_skill_embeddings(&self, meta: &[&SkillMeta], scored: &[ScoredMatch]) { match self { Self::InMemory(_) => {} @@ -694,7 +701,7 @@ impl SkillMatcherBackend { feature = "profiling", tracing::instrument(name = "skill.matcher_sync", skip_all) )] - #[allow(clippy::unused_async)] + #[allow(clippy::unused_async, clippy::unused_async_trait_impl)] pub async fn sync( &mut self, meta: &[&SkillMeta], diff --git a/crates/zeph-subagent/README.md b/crates/zeph-subagent/README.md index 30e88249e..8f12dae84 100644 --- a/crates/zeph-subagent/README.md +++ b/crates/zeph-subagent/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-subagent)](https://crates.io/crates/zeph-subagent) [![docs.rs](https://img.shields.io/docsrs/zeph-subagent)](https://docs.rs/zeph-subagent) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-yellow.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) Subagent management for Zeph — spawning, zero-trust grants, JSONL transcripts, scoped tool access, and lifecycle hooks. diff --git a/crates/zeph-tools/README.md b/crates/zeph-tools/README.md index d5911ec20..db15b2676 100644 --- a/crates/zeph-tools/README.md +++ b/crates/zeph-tools/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-tools)](https://crates.io/crates/zeph-tools) [![docs.rs](https://img.shields.io/docsrs/zeph-tools)](https://docs.rs/zeph-tools) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-yellow.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) Tool executor trait with shell, web scrape, and composite executors for Zeph. diff --git a/crates/zeph-tools/src/adversarial_gate.rs b/crates/zeph-tools/src/adversarial_gate.rs index 8199a0eb8..3e66d9e19 100644 --- a/crates/zeph-tools/src/adversarial_gate.rs +++ b/crates/zeph-tools/src/adversarial_gate.rs @@ -337,10 +337,15 @@ mod tests { } impl ToolExecutor for MockInner { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } + #[allow(clippy::unused_async_trait_impl)] async fn execute_tool_call( &self, call: &ToolCall, @@ -556,8 +561,12 @@ mod tests { #[derive(Debug)] struct SpeculatableInner; impl ToolExecutor for SpeculatableInner { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } fn is_tool_speculatable(&self, _tool_id: &str) -> bool { true @@ -603,8 +612,12 @@ mod tests { #[derive(Debug)] struct ConfirmationRequiredInner; impl ToolExecutor for ConfirmationRequiredInner { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } fn requires_confirmation(&self, _call: &ToolCall) -> bool { true @@ -648,11 +661,19 @@ mod tests { struct CheckpointingInner; impl ToolExecutor for CheckpointingInner { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } - async fn execute_tool_call(&self, _: &ToolCall) -> Result, ToolError> { - Ok(None) + fn execute_tool_call( + &self, + _: &ToolCall, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } fn checkpoint_undo(&self, n: usize) -> crate::executor::CheckpointActionResult { crate::executor::CheckpointActionResult { @@ -856,15 +877,20 @@ mod tests { struct InnerWithClaimSource; impl ToolExecutor for InnerWithClaimSource { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } - async fn execute_tool_call( + fn execute_tool_call( &self, call: &ToolCall, - ) -> Result, ToolError> { - Ok(Some(ToolOutput { + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(Some(ToolOutput { tool_name: call.tool_id.clone(), summary: "ok".into(), blocks_executed: 1, @@ -876,7 +902,7 @@ mod tests { raw_response: None, claim_source: Some(crate::executor::ClaimSource::Shell), ..Default::default() - })) + }))) } crate::tool_executor_no_inner_defaults!(); diff --git a/crates/zeph-tools/src/audit.rs b/crates/zeph-tools/src/audit.rs index 084b9d910..b32030ddb 100644 --- a/crates/zeph-tools/src/audit.rs +++ b/crates/zeph-tools/src/audit.rs @@ -366,7 +366,7 @@ impl AuditLogger { /// # Errors /// /// Returns an error if a file destination cannot be opened. - #[allow(clippy::unused_async)] + #[allow(clippy::unused_async, clippy::unused_async_trait_impl)] pub async fn from_config(config: &AuditConfig, tui_mode: bool) -> Result { use zeph_config::AuditDestination as CfgDest; diff --git a/crates/zeph-tools/src/composite.rs b/crates/zeph-tools/src/composite.rs index f68e6a2de..9094ac1ff 100644 --- a/crates/zeph-tools/src/composite.rs +++ b/crates/zeph-tools/src/composite.rs @@ -267,8 +267,12 @@ mod tests { #[derive(Debug)] struct MatchingExecutor; impl ToolExecutor for MatchingExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(Some(ToolOutput { + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(Some(ToolOutput { tool_name: ToolName::new("test"), summary: "matched".to_owned(), blocks_executed: 1, @@ -280,7 +284,7 @@ mod tests { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } crate::tool_executor_no_inner_defaults!(); @@ -289,8 +293,12 @@ mod tests { #[derive(Debug)] struct NoMatchExecutor; impl ToolExecutor for NoMatchExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } crate::tool_executor_no_inner_defaults!(); @@ -299,10 +307,14 @@ mod tests { #[derive(Debug)] struct ErrorExecutor; impl ToolExecutor for ErrorExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Err(ToolError::Blocked { + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Err(ToolError::Blocked { command: "test".to_owned(), - }) + })) } crate::tool_executor_no_inner_defaults!(); @@ -311,8 +323,12 @@ mod tests { #[derive(Debug)] struct SecondExecutor; impl ToolExecutor for SecondExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(Some(ToolOutput { + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(Some(ToolOutput { tool_name: ToolName::new("test"), summary: "second".to_owned(), blocks_executed: 1, @@ -324,7 +340,7 @@ mod tests { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } crate::tool_executor_no_inner_defaults!(); @@ -396,9 +412,14 @@ mod tests { unconfirmed_called: std::sync::Mutex, } impl ToolExecutor for ConfirmedSpy { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } + #[allow(clippy::unused_async_trait_impl)] async fn execute_tool_call( &self, call: &ToolCall, @@ -418,6 +439,7 @@ mod tests { ..Default::default() })) } + #[allow(clippy::unused_async_trait_impl)] async fn execute_tool_call_confirmed( &self, call: &ToolCall, @@ -506,14 +528,19 @@ mod tests { #[derive(Debug)] struct FileToolExecutor; impl ToolExecutor for FileToolExecutor { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } - async fn execute_tool_call( + fn execute_tool_call( &self, call: &ToolCall, - ) -> Result, ToolError> { - if call.tool_id == "read" || call.tool_id == "write" { + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(if call.tool_id == "read" || call.tool_id == "write" { Ok(Some(ToolOutput { tool_name: call.tool_id.clone(), summary: "file_handler".to_owned(), @@ -529,7 +556,7 @@ mod tests { })) } else { Ok(None) - } + }) } crate::tool_executor_no_inner_defaults!(); @@ -538,14 +565,19 @@ mod tests { #[derive(Debug)] struct ShellToolExecutor; impl ToolExecutor for ShellToolExecutor { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } - async fn execute_tool_call( + fn execute_tool_call( &self, call: &ToolCall, - ) -> Result, ToolError> { - if call.tool_id == "bash" { + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(if call.tool_id == "bash" { Ok(Some(ToolOutput { tool_name: ToolName::new("bash"), summary: "shell_handler".to_owned(), @@ -561,7 +593,7 @@ mod tests { })) } else { Ok(None) - } + }) } crate::tool_executor_no_inner_defaults!(); @@ -631,8 +663,12 @@ mod tests { last_trust: Mutex>, } impl ToolExecutor for SpyExecutor { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } fn set_skill_env(&self, env: Option>) { *self.last_env.lock().unwrap() = env; @@ -652,8 +688,12 @@ mod tests { #[derive(Debug)] struct FixedConfirmation(bool); impl ToolExecutor for FixedConfirmation { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } fn requires_confirmation(&self, _call: &ToolCall) -> bool { self.0 diff --git a/crates/zeph-tools/src/compression/decorator.rs b/crates/zeph-tools/src/compression/decorator.rs index 822527240..c880d650e 100644 --- a/crates/zeph-tools/src/compression/decorator.rs +++ b/crates/zeph-tools/src/compression/decorator.rs @@ -216,11 +216,15 @@ mod tests { } impl ToolExecutor for SpyExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(Some(make_output( + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(Some(make_output( ToolName::new("spy"), self.raw_output.clone(), - ))) + )))) } async fn execute_confirmed(&self, response: &str) -> Result, ToolError> { @@ -231,6 +235,7 @@ mod tests { vec![] } + #[allow(clippy::unused_async_trait_impl)] async fn execute_tool_call( &self, call: &ToolCall, @@ -381,11 +386,19 @@ mod tests { struct CheckpointStubExecutor; impl ToolExecutor for CheckpointStubExecutor { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } - async fn execute_tool_call(&self, _: &ToolCall) -> Result, ToolError> { - Ok(None) + fn execute_tool_call( + &self, + _: &ToolCall, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } fn requires_confirmation(&self, _call: &ToolCall) -> bool { true diff --git a/crates/zeph-tools/src/cwd.rs b/crates/zeph-tools/src/cwd.rs index caccaac83..58847919d 100644 --- a/crates/zeph-tools/src/cwd.rs +++ b/crates/zeph-tools/src/cwd.rs @@ -101,6 +101,11 @@ impl SetCwdExecutor { } impl ToolExecutor for SetCwdExecutor { + // `resolve_and_set_cwd` calls `std::env::set_current_dir` — a process-global mutation. + // Kept as a genuine `async fn` (never `std::future::ready`) so the mutation stays + // lazy (poll-time), matching the pre-1.98 behavior and avoiding a select!-cancellation + // hazard (see #6746). + #[allow(clippy::unused_async_trait_impl)] async fn execute_tool_call(&self, call: &ToolCall) -> Result, ToolError> { if call.tool_id != TOOL_NAME { return Ok(None); @@ -140,8 +145,11 @@ impl ToolExecutor for SetCwdExecutor { false } - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(None)) } crate::tool_executor_no_inner_defaults!(); diff --git a/crates/zeph-tools/src/diagnostics.rs b/crates/zeph-tools/src/diagnostics.rs index 2ceb0fa3d..a97826749 100644 --- a/crates/zeph-tools/src/diagnostics.rs +++ b/crates/zeph-tools/src/diagnostics.rs @@ -118,8 +118,11 @@ impl DiagnosticsExecutor { } impl ToolExecutor for DiagnosticsExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(None)) } #[cfg_attr( diff --git a/crates/zeph-tools/src/executor.rs b/crates/zeph-tools/src/executor.rs index adc238f06..48fd4fc44 100644 --- a/crates/zeph-tools/src/executor.rs +++ b/crates/zeph-tools/src/executor.rs @@ -632,24 +632,32 @@ pub fn deserialize_params( /// struct EchoExecutor; /// /// impl ToolExecutor for EchoExecutor { -/// async fn execute(&self, _response: &str) -> Result, ToolError> { -/// Ok(None) // not a fenced-block executor +/// fn execute( +/// &self, +/// _response: &str, +/// ) -> impl std::future::Future, ToolError>> + Send { +/// std::future::ready(Ok(None)) // not a fenced-block executor /// } /// -/// async fn execute_tool_call(&self, call: &ToolCall) -> Result, ToolError> { -/// if call.tool_id != "echo" { -/// return Ok(None); -/// } -/// let text = call.params.get("text") -/// .and_then(|v| v.as_str()) -/// .unwrap_or("") -/// .to_owned(); -/// Ok(Some(ToolOutput { -/// tool_name: "echo".into(), -/// summary: text, -/// blocks_executed: 1, -/// ..Default::default() -/// })) +/// fn execute_tool_call( +/// &self, +/// call: &ToolCall, +/// ) -> impl std::future::Future, ToolError>> + Send { +/// let result = if call.tool_id != "echo" { +/// Ok(None) +/// } else { +/// let text = call.params.get("text") +/// .and_then(|v| v.as_str()) +/// .unwrap_or("") +/// .to_owned(); +/// Ok(Some(ToolOutput { +/// tool_name: "echo".into(), +/// summary: text, +/// blocks_executed: 1, +/// ..Default::default() +/// })) +/// }; +/// std::future::ready(result) /// } /// /// zeph_tools::tool_executor_no_inner_defaults!(); @@ -801,8 +809,11 @@ pub trait ToolExecutor: Send + Sync { /// /// struct ReadOnlyExecutor; /// impl ToolExecutor for ReadOnlyExecutor { - /// async fn execute(&self, _: &str) -> Result, ToolError> { - /// Ok(None) + /// fn execute( + /// &self, + /// _: &str, + /// ) -> impl std::future::Future, ToolError>> + Send { + /// std::future::ready(Ok(None)) /// } /// fn is_tool_speculatable(&self, _tool_id: &str) -> bool { /// true // read-only, idempotent @@ -1408,8 +1419,12 @@ mod tests { #[derive(Debug)] struct DefaultExecutor; impl ToolExecutor for DefaultExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } crate::tool_executor_no_inner_defaults!(); @@ -1485,8 +1500,12 @@ mod tests { } impl ToolExecutor for FixedExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(Some(ToolOutput { + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(Some(ToolOutput { tool_name: ToolName::new(self.tool_id), summary: self.output.to_owned(), blocks_executed: 1, @@ -1498,18 +1517,19 @@ mod tests { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } fn tool_definitions(&self) -> Vec { vec![] } - async fn execute_tool_call( + fn execute_tool_call( &self, _call: &ToolCall, - ) -> Result, ToolError> { - Ok(Some(ToolOutput { + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(Some(ToolOutput { tool_name: ToolName::new(self.tool_id), summary: self.output.to_owned(), blocks_executed: 1, @@ -1521,7 +1541,7 @@ mod tests { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } crate::tool_executor_no_inner_defaults!(); @@ -1590,8 +1610,12 @@ mod tests { struct TrustCapture(AtomicU8); impl ToolExecutor for TrustCapture { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } fn set_effective_trust(&self, level: crate::SkillTrustLevel) { // encode: Trusted=0, Verified=1, Quarantined=2, Blocked=3 @@ -1893,8 +1917,12 @@ mod tests { /// Stub implementing only `ToolExecutor` without overriding `requires_confirmation`. struct StubExecutor; impl ToolExecutor for StubExecutor { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } crate::tool_executor_no_inner_defaults!(); @@ -1903,8 +1931,12 @@ mod tests { /// Stub that always signals confirmation is required via `ToolExecutor::requires_confirmation`. struct ConfirmingExecutor; impl ToolExecutor for ConfirmingExecutor { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } fn requires_confirmation(&self, _call: &ToolCall) -> bool { true diff --git a/crates/zeph-tools/src/file.rs b/crates/zeph-tools/src/file.rs index 21b79b2df..dc544c6ba 100644 --- a/crates/zeph-tools/src/file.rs +++ b/crates/zeph-tools/src/file.rs @@ -656,8 +656,11 @@ impl FileExecutor { } impl ToolExecutor for FileExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(None)) } #[cfg_attr( @@ -1795,7 +1798,7 @@ mod tests { let config = crate::config::FileConfig { deny_read: vec!["**/*.env".to_owned()], - allow_read: vec![format!("**/public.env")], + allow_read: vec!["**/public.env".to_string()], }; let exec = FileExecutor::new(vec![dir.path().to_path_buf()]).with_read_sandbox(&config); let params = make_params(&[("path", serde_json::json!(public.to_str().unwrap()))]); diff --git a/crates/zeph-tools/src/moderation.rs b/crates/zeph-tools/src/moderation.rs index 29cb88d72..c85e6ff82 100644 --- a/crates/zeph-tools/src/moderation.rs +++ b/crates/zeph-tools/src/moderation.rs @@ -179,8 +179,11 @@ fn moderation_error_to_tool_error(e: ModerationError) -> ToolError { } impl ToolExecutor for ModerationExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(None)) } #[tracing::instrument(skip(self), fields(tool_id = %call.tool_id))] diff --git a/crates/zeph-tools/src/policy_gate.rs b/crates/zeph-tools/src/policy_gate.rs index 10c0baf99..21d9e8436 100644 --- a/crates/zeph-tools/src/policy_gate.rs +++ b/crates/zeph-tools/src/policy_gate.rs @@ -291,20 +291,26 @@ impl PolicyGateExecutor { impl ToolExecutor for PolicyGateExecutor { // CRIT-03: legacy unstructured dispatch has no tool_id; policy cannot be enforced. // PolicyGateExecutor is only constructed when policy is enabled, so reject unconditionally. - async fn execute(&self, _response: &str) -> Result, ToolError> { - Err(ToolError::Blocked { + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Err(ToolError::Blocked { command: "legacy unstructured dispatch is not supported when policy enforcement is enabled" .into(), - }) + })) } - async fn execute_confirmed(&self, _response: &str) -> Result, ToolError> { - Err(ToolError::Blocked { + fn execute_confirmed( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Err(ToolError::Blocked { command: "legacy unstructured dispatch is not supported when policy enforcement is enabled" .into(), - }) + })) } fn tool_definitions(&self) -> Vec { @@ -434,14 +440,19 @@ mod tests { struct MockExecutor; impl ToolExecutor for MockExecutor { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } - async fn execute_tool_call( + fn execute_tool_call( &self, call: &ToolCall, - ) -> Result, ToolError> { - Ok(Some(ToolOutput { + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(Some(ToolOutput { tool_name: call.tool_id.clone(), summary: "ok".into(), blocks_executed: 1, @@ -453,7 +464,7 @@ mod tests { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } crate::tool_executor_no_inner_defaults!(); @@ -498,11 +509,19 @@ mod tests { struct CheckpointingExecutor; impl ToolExecutor for CheckpointingExecutor { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } - async fn execute_tool_call(&self, _: &ToolCall) -> Result, ToolError> { - Ok(None) + fn execute_tool_call( + &self, + _: &ToolCall, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } fn checkpoint_undo(&self, n: usize) -> crate::executor::CheckpointActionResult { crate::executor::CheckpointActionResult { @@ -546,11 +565,19 @@ mod tests { struct ConfirmationRequiredExecutor; impl ToolExecutor for ConfirmationRequiredExecutor { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } - async fn execute_tool_call(&self, _: &ToolCall) -> Result, ToolError> { - Ok(None) + fn execute_tool_call( + &self, + _: &ToolCall, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } fn requires_confirmation(&self, _call: &ToolCall) -> bool { true diff --git a/crates/zeph-tools/src/scope.rs b/crates/zeph-tools/src/scope.rs index 1497aa40b..80fe2f36a 100644 --- a/crates/zeph-tools/src/scope.rs +++ b/crates/zeph-tools/src/scope.rs @@ -748,19 +748,24 @@ mod tests { } impl ToolExecutor for NullExecutor { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } fn tool_definitions(&self) -> Vec { self.defs.clone() } - async fn execute_tool_call( + fn execute_tool_call( &self, call: &ToolCall, - ) -> Result, ToolError> { - Ok(Some(ToolOutput { + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(Some(ToolOutput { tool_name: call.tool_id.clone(), summary: "ok".to_owned(), blocks_executed: 1, @@ -772,7 +777,7 @@ mod tests { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } crate::tool_executor_no_inner_defaults!(); @@ -781,8 +786,12 @@ mod tests { struct CheckpointingExecutor; impl ToolExecutor for CheckpointingExecutor { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } fn checkpoint_undo(&self, n: usize) -> crate::executor::CheckpointActionResult { crate::executor::CheckpointActionResult { diff --git a/crates/zeph-tools/src/search/mod.rs b/crates/zeph-tools/src/search/mod.rs index f707500b4..2f22713d8 100644 --- a/crates/zeph-tools/src/search/mod.rs +++ b/crates/zeph-tools/src/search/mod.rs @@ -650,9 +650,12 @@ impl ToolExecutor for WebSearchExecutor { }] } - async fn execute(&self, _response: &str) -> Result, ToolError> { + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { // Structured tool-call only — no fenced-block invocation path. - Ok(None) + std::future::ready(Ok(None)) } #[cfg_attr( diff --git a/crates/zeph-tools/src/search_code.rs b/crates/zeph-tools/src/search_code.rs index c3cf52db7..785b3c473 100644 --- a/crates/zeph-tools/src/search_code.rs +++ b/crates/zeph-tools/src/search_code.rs @@ -435,8 +435,11 @@ impl SearchCodeExecutor { } impl ToolExecutor for SearchCodeExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(None)) } #[cfg_attr( diff --git a/crates/zeph-tools/src/shadow_probe.rs b/crates/zeph-tools/src/shadow_probe.rs index 59a2ce153..d733996e5 100644 --- a/crates/zeph-tools/src/shadow_probe.rs +++ b/crates/zeph-tools/src/shadow_probe.rs @@ -518,15 +518,20 @@ mod tests { struct OkInner; impl ToolExecutor for OkInner { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } - async fn execute_tool_call( + fn execute_tool_call( &self, call: &ToolCall, - ) -> Result, ToolError> { - Ok(Some(ToolOutput { + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(Some(ToolOutput { tool_name: call.tool_id.clone(), summary: "ok".to_owned(), blocks_executed: 1, @@ -538,7 +543,7 @@ mod tests { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } crate::tool_executor_no_inner_defaults!(); @@ -548,17 +553,22 @@ mod tests { /// `TrustGateExecutor::execute_tool_call` for a `PermissionAction::Ask`-gated tool. struct ConfirmationRequiredInner; impl ToolExecutor for ConfirmationRequiredInner { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } - async fn execute_tool_call( + fn execute_tool_call( &self, call: &ToolCall, - ) -> Result, ToolError> { - Err(ToolError::ConfirmationRequired { + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Err(ToolError::ConfirmationRequired { command: call.tool_id.to_string(), - }) + })) } crate::tool_executor_no_inner_defaults!(); @@ -799,8 +809,12 @@ mod tests { struct CheckpointingInner; impl ToolExecutor for CheckpointingInner { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } fn checkpoint_undo(&self, n: usize) -> crate::executor::CheckpointActionResult { crate::executor::CheckpointActionResult { diff --git a/crates/zeph-tools/src/time.rs b/crates/zeph-tools/src/time.rs index ac17e04ed..a3cb48a0f 100644 --- a/crates/zeph-tools/src/time.rs +++ b/crates/zeph-tools/src/time.rs @@ -72,37 +72,43 @@ impl std::fmt::Debug for GetCurrentTimeExecutor { } impl ToolExecutor for GetCurrentTimeExecutor { - async fn execute_tool_call(&self, call: &ToolCall) -> Result, ToolError> { - if call.tool_id != TOOL_NAME { - return Ok(None); - } - let params: TimeParams = deserialize_params(&call.params)?; - let now = self.clock.now(); - let format = match params.format.as_deref() { - Some("unix") => TimeFormat::Unix, - _ => TimeFormat::Rfc3339, - }; - let summary = match format { - TimeFormat::Rfc3339 => zeph_common::timestamp::rfc3339_from(now), - TimeFormat::Unix => now - .duration_since(std::time::UNIX_EPOCH) - .map_or(0, |d| d.as_secs()) - .to_string(), - }; - - Ok(Some(ToolOutput { - tool_name: ToolName::new(TOOL_NAME), - summary, - blocks_executed: 1, - filter_stats: None, - diff: None, - streamed: false, - terminal_id: None, - locations: None, - raw_response: None, - claim_source: None, - ..Default::default() - })) + fn execute_tool_call( + &self, + call: &ToolCall, + ) -> impl std::future::Future, ToolError>> + Send { + let result = (|| { + if call.tool_id != TOOL_NAME { + return Ok(None); + } + let params: TimeParams = deserialize_params(&call.params)?; + let now = self.clock.now(); + let format = match params.format.as_deref() { + Some("unix") => TimeFormat::Unix, + _ => TimeFormat::Rfc3339, + }; + let summary = match format { + TimeFormat::Rfc3339 => zeph_common::timestamp::rfc3339_from(now), + TimeFormat::Unix => now + .duration_since(std::time::UNIX_EPOCH) + .map_or(0, |d| d.as_secs()) + .to_string(), + }; + + Ok(Some(ToolOutput { + tool_name: ToolName::new(TOOL_NAME), + summary, + blocks_executed: 1, + filter_stats: None, + diff: None, + streamed: false, + terminal_id: None, + locations: None, + raw_response: None, + claim_source: None, + ..Default::default() + })) + })(); + std::future::ready(result) } fn tool_definitions(&self) -> Vec { @@ -120,8 +126,11 @@ impl ToolExecutor for GetCurrentTimeExecutor { true } - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(None)) } crate::tool_executor_no_inner_defaults!(); diff --git a/crates/zeph-tools/src/tool_filter.rs b/crates/zeph-tools/src/tool_filter.rs index b1e8f9338..a7177c6a8 100644 --- a/crates/zeph-tools/src/tool_filter.rs +++ b/crates/zeph-tools/src/tool_filter.rs @@ -97,8 +97,12 @@ mod tests { #[derive(Debug)] struct StubExecutor; impl ToolExecutor for StubExecutor { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } fn tool_definitions(&self) -> Vec { vec![ @@ -128,11 +132,12 @@ mod tests { }, ] } - async fn execute_tool_call( + fn execute_tool_call( &self, call: &ToolCall, - ) -> Result, ToolError> { - Ok(Some(ToolOutput { + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(Some(ToolOutput { tool_name: call.tool_id.clone(), summary: "stub".to_owned(), blocks_executed: 1, @@ -144,7 +149,7 @@ mod tests { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } crate::tool_executor_no_inner_defaults!(); @@ -199,14 +204,19 @@ mod tests { struct CrossCuttingStubExecutor; impl ToolExecutor for CrossCuttingStubExecutor { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } - async fn execute_tool_call( + fn execute_tool_call( &self, call: &ToolCall, - ) -> Result, ToolError> { - Ok(Some(ToolOutput { + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(Some(ToolOutput { tool_name: call.tool_id.clone(), summary: "stub".to_owned(), blocks_executed: 1, @@ -218,13 +228,14 @@ mod tests { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } - async fn execute_tool_call_confirmed( + fn execute_tool_call_confirmed( &self, call: &ToolCall, - ) -> Result, ToolError> { - Ok(Some(ToolOutput { + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(Some(ToolOutput { tool_name: call.tool_id.clone(), summary: "stub-confirmed".to_owned(), blocks_executed: 1, @@ -236,7 +247,7 @@ mod tests { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } fn is_tool_retryable(&self, _tool_id: &str) -> bool { true diff --git a/crates/zeph-tools/src/trust_gate.rs b/crates/zeph-tools/src/trust_gate.rs index 422f9cf7a..3cd367ebf 100644 --- a/crates/zeph-tools/src/trust_gate.rs +++ b/crates/zeph-tools/src/trust_gate.rs @@ -340,14 +340,19 @@ mod tests { #[derive(Debug)] struct MockExecutor; impl ToolExecutor for MockExecutor { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } - async fn execute_tool_call( + fn execute_tool_call( &self, call: &ToolCall, - ) -> Result, ToolError> { - Ok(Some(ToolOutput { + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(Some(ToolOutput { tool_name: call.tool_id.clone(), summary: "ok".into(), blocks_executed: 1, @@ -359,7 +364,7 @@ mod tests { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } crate::tool_executor_no_inner_defaults!(); @@ -723,11 +728,19 @@ mod tests { } } impl ToolExecutor for EnvCapture { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } - async fn execute_tool_call(&self, _: &ToolCall) -> Result, ToolError> { - Ok(None) + fn execute_tool_call( + &self, + _: &ToolCall, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } fn set_skill_env(&self, env: Option>) { *self.captured.lock().unwrap() = env; @@ -741,14 +754,19 @@ mod tests { #[derive(Debug)] struct RetryableExecutor; impl ToolExecutor for RetryableExecutor { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } - async fn execute_tool_call( + fn execute_tool_call( &self, _: &ToolCall, - ) -> Result, ToolError> { - Ok(None) + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } fn is_tool_retryable(&self, tool_id: &str) -> bool { tool_id == "fetch" @@ -766,14 +784,19 @@ mod tests { #[derive(Debug)] struct CheckpointingExecutor; impl ToolExecutor for CheckpointingExecutor { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } - async fn execute_tool_call( + fn execute_tool_call( &self, _: &ToolCall, - ) -> Result, ToolError> { - Ok(None) + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } fn checkpoint_undo(&self, n: usize) -> crate::executor::CheckpointActionResult { crate::executor::CheckpointActionResult { diff --git a/crates/zeph-tui/README.md b/crates/zeph-tui/README.md index c3e1d5e8f..f00e99593 100644 --- a/crates/zeph-tui/README.md +++ b/crates/zeph-tui/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-tui)](https://crates.io/crates/zeph-tui) [![docs.rs](https://img.shields.io/docsrs/zeph-tui)](https://docs.rs/zeph-tui) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-yellow.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) Ratatui-based TUI dashboard with real-time metrics and multi-session support for Zeph. diff --git a/crates/zeph-tui/src/channel.rs b/crates/zeph-tui/src/channel.rs index 047609653..69d9d7a4a 100644 --- a/crates/zeph-tui/src/channel.rs +++ b/crates/zeph-tui/src/channel.rs @@ -183,6 +183,7 @@ impl Channel for TuiChannel { feature = "profiling", tracing::instrument(name = "tui.channel.send_chunk", skip_all) )] + #[allow(clippy::unused_async_trait_impl)] async fn send_chunk(&mut self, chunk: &str) -> Result<(), ChannelError> { self.accumulated.push_str(chunk); // Non-critical: dropping a chunk loses partial streaming output but agent continues. @@ -196,6 +197,7 @@ impl Channel for TuiChannel { feature = "profiling", tracing::instrument(name = "tui.channel.flush_chunks", skip_all) )] + #[allow(clippy::unused_async_trait_impl)] async fn flush_chunks(&mut self) -> Result<(), ChannelError> { // Non-critical: visual signal that streaming ended. let _ = self.agent_event_tx.try_send(AgentEvent::Flush); @@ -206,6 +208,7 @@ impl Channel for TuiChannel { feature = "profiling", tracing::instrument(name = "tui.channel.send_typing", skip_all) )] + #[allow(clippy::unused_async_trait_impl)] async fn send_typing(&mut self) -> Result<(), ChannelError> { // Non-critical: throbber hint only. let _ = self.agent_event_tx.try_send(AgentEvent::Typing); @@ -216,6 +219,7 @@ impl Channel for TuiChannel { feature = "profiling", tracing::instrument(name = "tui.channel.send_status", skip_all) )] + #[allow(clippy::unused_async_trait_impl)] async fn send_status(&mut self, text: &str) -> Result<(), ChannelError> { // Non-critical: informational status text. let _ = self @@ -228,6 +232,7 @@ impl Channel for TuiChannel { feature = "profiling", tracing::instrument(name = "tui.channel.send_queue_count", skip_all) )] + #[allow(clippy::unused_async_trait_impl)] async fn send_queue_count(&mut self, count: usize) -> Result<(), ChannelError> { // Non-critical: display-only counter. let _ = self.agent_event_tx.try_send(AgentEvent::QueueCount(count)); @@ -238,6 +243,7 @@ impl Channel for TuiChannel { feature = "profiling", tracing::instrument(name = "tui.channel.send_context_estimate", skip_all) )] + #[allow(clippy::unused_async_trait_impl)] async fn send_context_estimate(&mut self, tokens: usize) -> Result<(), ChannelError> { // Non-critical: informational estimate shown in the input block title. let _ = self @@ -250,6 +256,7 @@ impl Channel for TuiChannel { feature = "profiling", tracing::instrument(name = "tui.channel.send_skill_catalog", skip_all) )] + #[allow(clippy::unused_async_trait_impl)] async fn send_skill_catalog(&mut self, items: &[SkillCatalogItem]) -> Result<(), ChannelError> { // Non-critical: refreshes the mention picker's Skills tab if one is open; // startup/hot-reload emits arrive well before any user interaction. @@ -263,6 +270,7 @@ impl Channel for TuiChannel { feature = "profiling", tracing::instrument(name = "tui.channel.send_transcript_backfill", skip_all) )] + #[allow(clippy::unused_async_trait_impl)] async fn send_transcript_backfill( &mut self, entries: &[zeph_commands::TranscriptEntry], @@ -281,6 +289,7 @@ impl Channel for TuiChannel { feature = "profiling", tracing::instrument(name = "tui.channel.send_resume_banner", skip_all) )] + #[allow(clippy::unused_async_trait_impl)] async fn send_resume_banner(&mut self, text: &str) -> Result<(), ChannelError> { // Persistent header banner (spec-068 §13.5), same as the startup path — covers // live mid-session swaps too (`/conv resume`, `/conv fork`, AC-23). @@ -324,6 +333,7 @@ impl Channel for TuiChannel { feature = "profiling", tracing::instrument(name = "tui.channel.send_tool_start", skip_all) )] + #[allow(clippy::unused_async_trait_impl)] async fn send_tool_start(&mut self, event: ToolStartEvent) -> Result<(), ChannelError> { let command = event .params @@ -425,6 +435,7 @@ impl Channel for TuiChannel { feature = "profiling", tracing::instrument(name = "tui.channel.notify_foreground_subagent_started", skip_all) )] + #[allow(clippy::unused_async_trait_impl)] async fn notify_foreground_subagent_started( &mut self, id: &str, @@ -444,6 +455,7 @@ impl Channel for TuiChannel { feature = "profiling", tracing::instrument(name = "tui.channel.notify_foreground_subagent_completed", skip_all) )] + #[allow(clippy::unused_async_trait_impl)] async fn notify_foreground_subagent_completed( &mut self, id: &str, @@ -465,6 +477,7 @@ impl Channel for TuiChannel { feature = "profiling", tracing::instrument(name = "tui.channel.notify_background_subagent_completed", skip_all) )] + #[allow(clippy::unused_async_trait_impl)] async fn notify_background_subagent_completed( &mut self, id: &str, diff --git a/crates/zeph-vault/README.md b/crates/zeph-vault/README.md index 610884fef..dca1cb0c9 100644 --- a/crates/zeph-vault/README.md +++ b/crates/zeph-vault/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-vault)](https://crates.io/crates/zeph-vault) [![docs.rs](https://img.shields.io/docsrs/zeph-vault)](https://docs.rs/zeph-vault) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-yellow.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) `VaultProvider` trait and backends (env, age) for Zeph secret management. diff --git a/crates/zeph-worktree/README.md b/crates/zeph-worktree/README.md index 6bb627c73..4f5148828 100644 --- a/crates/zeph-worktree/README.md +++ b/crates/zeph-worktree/README.md @@ -3,7 +3,7 @@ [![Crates.io](https://img.shields.io/crates/v/zeph-worktree)](https://crates.io/crates/zeph-worktree) [![docs.rs](https://img.shields.io/docsrs/zeph-worktree)](https://docs.rs/zeph-worktree) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-yellow.svg)](../../LICENSE) -[![MSRV](https://img.shields.io/badge/MSRV-1.97-blue)](https://www.rust-lang.org) +[![MSRV](https://img.shields.io/badge/MSRV-1.98-blue)](https://www.rust-lang.org) Git worktree lifecycle management for Zeph subagents. diff --git a/crates/zeph-worktree/src/git_runner.rs b/crates/zeph-worktree/src/git_runner.rs index ad085a4ef..3c3137bde 100644 --- a/crates/zeph-worktree/src/git_runner.rs +++ b/crates/zeph-worktree/src/git_runner.rs @@ -185,6 +185,7 @@ impl GitRunner for std::sync::Arc { #[cfg(test)] impl GitRunner for FakeGitRunner { + #[allow(clippy::unused_async_trait_impl)] async fn run(&self, args: &[&str], cwd: &Path) -> Result { // Record the call for post-test assertions. self.calls.lock().unwrap().push(( diff --git a/specs/065-ephemeral-plugins-provider-overrides/brd.md b/specs/065-ephemeral-plugins-provider-overrides/brd.md index e63a6c14d..4e276d260 100644 --- a/specs/065-ephemeral-plugins-provider-overrides/brd.md +++ b/specs/065-ephemeral-plugins-provider-overrides/brd.md @@ -75,7 +75,7 @@ These deferrals are **explicit** (not silent omissions). They must be tracked as - No new SQLite schema migration needed (use existing `channel_preferences` key-value table with a new `pref_key`) - Implementation stays within `zeph-plugins`, `zeph-core`, `zeph-config`, `zeph-commands`, and the root binary crate -- MSRV remains Rust 1.97 +- MSRV remains Rust 1.98 - No new mandatory dependencies ## 8. Dependencies diff --git a/specs/BRD.md b/specs/BRD.md index eddde8a54..09525653a 100644 --- a/specs/BRD.md +++ b/specs/BRD.md @@ -398,7 +398,7 @@ Detailed targets are in [[NFR]]. High-level constraints for business context: ### Technical Constraints -- Language: Rust 1.97 (MSRV), Edition 2024, no `unsafe` blocks. +- Language: Rust 1.98 (MSRV), Edition 2024, no `unsafe` blocks. - Async: tokio; no `async-trait` crate in library crates. - TLS: rustls only; `openssl-sys` banned. - YAML: `serde_norway` only; `serde_yaml` / `serde_yml` banned. diff --git a/specs/SRS.md b/specs/SRS.md index f241fb1db..47fc7f72d 100644 --- a/specs/SRS.md +++ b/specs/SRS.md @@ -216,7 +216,7 @@ Major functional areas: ### 2.5 Design and Implementation Constraints -- Rust 1.97 (MSRV), Edition 2024; `unsafe_code = "deny"` workspace-wide. +- Rust 1.98 (MSRV), Edition 2024; `unsafe_code = "deny"` workspace-wide. - Async: tokio; no `async-trait` crate in library crates. - TLS: rustls; `openssl-sys` banned. - Crate layering: `zeph-core` orchestrates all leaf crates; same-layer imports prohibited. diff --git a/specs/constitution.md b/specs/constitution.md index b534dbc13..589a7abae 100644 --- a/specs/constitution.md +++ b/specs/constitution.md @@ -42,7 +42,7 @@ related: ## II. Technology Stack -- Language: Rust 1.97 (MSRV), Edition 2024 +- Language: Rust 1.98 (MSRV), Edition 2024 - Async: tokio + native async traits; no `async-trait` crate for new code in library crates - HTTP: reqwest 0.13 (rustls, no openssl-sys) - Database: SQLite (sqlx 0.8) for persistence + Qdrant for semantic search diff --git a/src/acp.rs b/src/acp.rs index 8962688aa..d8579f22c 100644 --- a/src/acp.rs +++ b/src/acp.rs @@ -3490,33 +3490,40 @@ mod tests { struct AcpTaggedMock(&'static str); impl zeph_tools::executor::ToolExecutor for AcpTaggedMock { - async fn execute( + fn execute( &self, _response: &str, - ) -> Result, zeph_tools::ToolError> { - Ok(None) + ) -> impl std::future::Future< + Output = Result, zeph_tools::ToolError>, + > + Send { + std::future::ready(Ok(None)) } - async fn execute_tool_call( + fn execute_tool_call( &self, call: &zeph_tools::ToolCall, - ) -> Result, zeph_tools::ToolError> { - if call.tool_id != self.0 { - return Ok(None); - } - Ok(Some(zeph_tools::ToolOutput { - tool_name: call.tool_id.clone(), - summary: "ok".into(), - blocks_executed: 1, - filter_stats: None, - diff: None, - streamed: false, - terminal_id: None, - locations: None, - raw_response: None, - claim_source: None, - ..Default::default() - })) + ) -> impl std::future::Future< + Output = Result, zeph_tools::ToolError>, + > + Send { + let result = (|| { + if call.tool_id != self.0 { + return Ok(None); + } + Ok(Some(zeph_tools::ToolOutput { + tool_name: call.tool_id.clone(), + summary: "ok".into(), + blocks_executed: 1, + filter_stats: None, + diff: None, + streamed: false, + terminal_id: None, + locations: None, + raw_response: None, + claim_source: None, + ..Default::default() + })) + })(); + std::future::ready(result) } zeph_tools::tool_executor_no_inner_defaults!(); } @@ -3950,14 +3957,21 @@ mod tests { struct OkExec; impl ToolExecutor for OkExec { - async fn execute(&self, _: &str) -> Result, zeph_tools::ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future< + Output = Result, zeph_tools::ToolError>, + > + Send { + std::future::ready(Ok(None)) } - async fn execute_tool_call( + fn execute_tool_call( &self, call: &ToolCall, - ) -> Result, zeph_tools::ToolError> { - Ok(Some(ToolOutput { + ) -> impl std::future::Future< + Output = Result, zeph_tools::ToolError>, + > + Send { + std::future::ready(Ok(Some(ToolOutput { tool_name: call.tool_id.clone(), summary: "command completed".to_owned(), blocks_executed: 1, @@ -3969,7 +3983,7 @@ mod tests { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } zeph_tools::tool_executor_no_inner_defaults!(); } @@ -4441,12 +4455,15 @@ mod tests { tool_id: &'static str, } impl ToolExecutor for AcpNativeStandIn { - async fn execute( + fn execute( &self, _response: &str, - ) -> Result, zeph_tools::ToolError> { - Ok(None) + ) -> impl std::future::Future< + Output = Result, zeph_tools::ToolError>, + > + Send { + std::future::ready(Ok(None)) } + #[allow(clippy::unused_async_trait_impl)] async fn execute_tool_call( &self, call: &zeph_tools::ToolCall, diff --git a/src/agent_setup.rs b/src/agent_setup.rs index c39f7e11f..943cbf800 100644 --- a/src/agent_setup.rs +++ b/src/agent_setup.rs @@ -3275,8 +3275,12 @@ mod tests { struct NoopExec; impl zeph_tools::executor::ToolExecutor for NoopExec { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } zeph_tools::tool_executor_no_inner_defaults!(); } @@ -4251,18 +4255,23 @@ mod tests { struct TaggedMock(String); impl ToolExecutor for TaggedMock { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } - async fn execute_tool_call( + fn execute_tool_call( &self, call: &zeph_tools::ToolCall, - ) -> Result, ToolError> { + ) -> impl std::future::Future, ToolError>> + Send + { if call.tool_id != self.0 { - return Ok(None); + return std::future::ready(Ok(None)); } - Ok(Some(ToolOutput { + std::future::ready(Ok(Some(ToolOutput { tool_name: call.tool_id.clone(), summary: "ok".into(), blocks_executed: 1, @@ -4274,7 +4283,7 @@ mod tests { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } zeph_tools::tool_executor_no_inner_defaults!(); } diff --git a/src/circuit_breaker_exporter.rs b/src/circuit_breaker_exporter.rs index 277c96853..3c05b8f95 100644 --- a/src/circuit_breaker_exporter.rs +++ b/src/circuit_breaker_exporter.rs @@ -150,6 +150,7 @@ mod tests { } impl SpanExporter for MockExporter { + #[allow(clippy::unused_async_trait_impl)] async fn export(&self, _batch: Vec) -> OTelSdkResult { let n = self.call_count.fetch_add(1, Ordering::Relaxed); if n < self.fail_until { diff --git a/src/daemon.rs b/src/daemon.rs index 6bde4317a..2bff9ad06 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -2014,21 +2014,25 @@ mod tests { struct DaemonTaggedMock(&'static str); impl zeph_tools::executor::ToolExecutor for DaemonTaggedMock { - async fn execute( + fn execute( &self, _response: &str, - ) -> Result, zeph_tools::ToolError> { - Ok(None) + ) -> impl std::future::Future< + Output = Result, zeph_tools::ToolError>, + > + Send { + std::future::ready(Ok(None)) } - async fn execute_tool_call( + fn execute_tool_call( &self, call: &zeph_tools::ToolCall, - ) -> Result, zeph_tools::ToolError> { + ) -> impl std::future::Future< + Output = Result, zeph_tools::ToolError>, + > + Send { if call.tool_id != self.0 { - return Ok(None); + return std::future::ready(Ok(None)); } - Ok(Some(zeph_tools::ToolOutput { + std::future::ready(Ok(Some(zeph_tools::ToolOutput { tool_name: call.tool_id.clone(), summary: "ok".into(), blocks_executed: 1, @@ -2040,7 +2044,7 @@ mod tests { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } zeph_tools::tool_executor_no_inner_defaults!(); } @@ -2710,14 +2714,21 @@ mod tests { struct OkExec; impl ToolExecutor for OkExec { - async fn execute(&self, _: &str) -> Result, zeph_tools::ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future< + Output = Result, zeph_tools::ToolError>, + > + Send { + std::future::ready(Ok(None)) } - async fn execute_tool_call( + fn execute_tool_call( &self, call: &ToolCall, - ) -> Result, zeph_tools::ToolError> { - Ok(Some(ToolOutput { + ) -> impl std::future::Future< + Output = Result, zeph_tools::ToolError>, + > + Send { + std::future::ready(Ok(Some(ToolOutput { tool_name: call.tool_id.clone(), summary: "command completed".to_owned(), blocks_executed: 1, @@ -2729,7 +2740,7 @@ mod tests { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } zeph_tools::tool_executor_no_inner_defaults!(); } diff --git a/src/gateway_spawn.rs b/src/gateway_spawn.rs index f4b4f0f6e..96ca804a5 100644 --- a/src/gateway_spawn.rs +++ b/src/gateway_spawn.rs @@ -590,19 +590,27 @@ mod tests { })) } - async fn send(&mut self, _text: &str) -> Result<(), zeph_core::channel::ChannelError> { - Ok(()) + fn send( + &mut self, + _text: &str, + ) -> impl std::future::Future> + Send + { + std::future::ready(Ok(())) } - async fn send_chunk( + fn send_chunk( &mut self, _chunk: &str, - ) -> Result<(), zeph_core::channel::ChannelError> { - Ok(()) + ) -> impl std::future::Future> + Send + { + std::future::ready(Ok(())) } - async fn flush_chunks(&mut self) -> Result<(), zeph_core::channel::ChannelError> { - Ok(()) + fn flush_chunks( + &mut self, + ) -> impl std::future::Future> + Send + { + std::future::ready(Ok(())) } } diff --git a/src/runner.rs b/src/runner.rs index db4051d95..bdf877e74 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -5821,15 +5821,22 @@ mod tests { struct OkExec; impl ToolExecutor for OkExec { - async fn execute(&self, _: &str) -> Result, zeph_tools::ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future< + Output = Result, zeph_tools::ToolError>, + > + Send { + std::future::ready(Ok(None)) } - async fn execute_tool_call( + fn execute_tool_call( &self, call: &ToolCall, - ) -> Result, zeph_tools::ToolError> { - Ok(Some(ToolOutput { + ) -> impl std::future::Future< + Output = Result, zeph_tools::ToolError>, + > + Send { + std::future::ready(Ok(Some(ToolOutput { tool_name: call.tool_id.clone(), summary: "command completed".to_owned(), blocks_executed: 1, @@ -5841,7 +5848,7 @@ mod tests { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } zeph_tools::tool_executor_no_inner_defaults!(); } diff --git a/src/scheduler.rs b/src/scheduler.rs index b3081ba16..6871d916c 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -563,17 +563,22 @@ mod tests { struct StubExec; impl ToolExecutor for StubExec { - async fn execute(&self, _: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } fn tool_definitions(&self) -> Vec { vec![] } - async fn execute_tool_call( + fn execute_tool_call( &self, _: &ToolCall, - ) -> Result, ToolError> { - Ok(None) + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } zeph_tools::tool_executor_no_inner_defaults!(); } diff --git a/tests/integration.rs b/tests/integration.rs index 60cced410..98ab053a7 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -111,6 +111,7 @@ impl MockChannel { } impl Channel for MockChannel { + #[allow(clippy::unused_async_trait_impl)] async fn recv(&mut self) -> Result, ChannelError> { Ok(self.inputs.pop_front().map(|text| ChannelMessage { text, @@ -121,17 +122,23 @@ impl Channel for MockChannel { })) } + #[allow(clippy::unused_async_trait_impl)] async fn send(&mut self, text: &str) -> Result<(), ChannelError> { self.outputs.lock().unwrap().push(text.to_string()); Ok(()) } - async fn send_chunk(&mut self, _chunk: &str) -> Result<(), ChannelError> { - Ok(()) + fn send_chunk( + &mut self, + _chunk: &str, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(())) } - async fn flush_chunks(&mut self) -> Result<(), ChannelError> { - Ok(()) + fn flush_chunks( + &mut self, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(())) } } @@ -143,6 +150,7 @@ struct ConfirmMockChannel { } impl Channel for ConfirmMockChannel { + #[allow(clippy::unused_async_trait_impl)] async fn recv(&mut self) -> Result, ChannelError> { Ok(self.inputs.pop_front().map(|text| ChannelMessage { text, @@ -153,19 +161,26 @@ impl Channel for ConfirmMockChannel { })) } + #[allow(clippy::unused_async_trait_impl)] async fn send(&mut self, text: &str) -> Result<(), ChannelError> { self.outputs.lock().unwrap().push(text.to_string()); Ok(()) } - async fn send_chunk(&mut self, _chunk: &str) -> Result<(), ChannelError> { - Ok(()) + fn send_chunk( + &mut self, + _chunk: &str, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(())) } - async fn flush_chunks(&mut self) -> Result<(), ChannelError> { - Ok(()) + fn flush_chunks( + &mut self, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(())) } + #[allow(clippy::unused_async_trait_impl)] async fn confirm(&mut self, _prompt: &str) -> Result { *self.confirm_called.lock().unwrap() = true; Ok(self.confirm_result) @@ -177,8 +192,11 @@ impl Channel for ConfirmMockChannel { struct MockToolExecutor; impl ToolExecutor for MockToolExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(None)) } zeph_tools::tool_executor_no_inner_defaults!(); } @@ -188,8 +206,11 @@ struct OutputToolExecutor { } impl ToolExecutor for OutputToolExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(Some(ToolOutput { + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(Some(ToolOutput { tool_name: zeph_tools::ToolName::new("bash"), summary: self.output.clone(), blocks_executed: 1, @@ -201,11 +222,14 @@ impl ToolExecutor for OutputToolExecutor { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } - async fn execute_tool_call(&self, call: &ToolCall) -> Result, ToolError> { - Ok(Some(ToolOutput { + fn execute_tool_call( + &self, + call: &ToolCall, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(Some(ToolOutput { tool_name: call.tool_id.clone(), summary: self.output.clone(), blocks_executed: 1, @@ -217,7 +241,7 @@ impl ToolExecutor for OutputToolExecutor { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } zeph_tools::tool_executor_no_inner_defaults!(); } @@ -225,8 +249,11 @@ impl ToolExecutor for OutputToolExecutor { struct EmptyOutputToolExecutor; impl ToolExecutor for EmptyOutputToolExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(Some(ToolOutput { + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(Some(ToolOutput { tool_name: zeph_tools::ToolName::new("bash"), summary: String::new(), blocks_executed: 1, @@ -238,11 +265,14 @@ impl ToolExecutor for EmptyOutputToolExecutor { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } - async fn execute_tool_call(&self, call: &ToolCall) -> Result, ToolError> { - Ok(Some(ToolOutput { + fn execute_tool_call( + &self, + call: &ToolCall, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(Some(ToolOutput { tool_name: call.tool_id.clone(), summary: String::new(), blocks_executed: 1, @@ -254,7 +284,7 @@ impl ToolExecutor for EmptyOutputToolExecutor { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } zeph_tools::tool_executor_no_inner_defaults!(); } @@ -262,8 +292,11 @@ impl ToolExecutor for EmptyOutputToolExecutor { struct ErrorOutputToolExecutor; impl ToolExecutor for ErrorOutputToolExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(Some(ToolOutput { + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(Some(ToolOutput { tool_name: zeph_tools::ToolName::new("bash"), summary: "[error] command failed".into(), blocks_executed: 1, @@ -275,11 +308,14 @@ impl ToolExecutor for ErrorOutputToolExecutor { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } - async fn execute_tool_call(&self, call: &ToolCall) -> Result, ToolError> { - Ok(Some(ToolOutput { + fn execute_tool_call( + &self, + call: &ToolCall, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(Some(ToolOutput { tool_name: call.tool_id.clone(), summary: "[error] command failed".into(), blocks_executed: 1, @@ -291,7 +327,7 @@ impl ToolExecutor for ErrorOutputToolExecutor { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } zeph_tools::tool_executor_no_inner_defaults!(); } @@ -299,16 +335,22 @@ impl ToolExecutor for ErrorOutputToolExecutor { struct BlockedToolExecutor; impl ToolExecutor for BlockedToolExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Err(ToolError::Blocked { + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Err(ToolError::Blocked { command: "rm -rf /".into(), - }) + })) } - async fn execute_tool_call(&self, _call: &ToolCall) -> Result, ToolError> { - Err(ToolError::Blocked { + fn execute_tool_call( + &self, + _call: &ToolCall, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Err(ToolError::Blocked { command: "rm -rf /".into(), - }) + })) } zeph_tools::tool_executor_no_inner_defaults!(); } @@ -316,14 +358,20 @@ impl ToolExecutor for BlockedToolExecutor { struct ConfirmToolExecutor; impl ToolExecutor for ConfirmToolExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Err(ToolError::ConfirmationRequired { + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Err(ToolError::ConfirmationRequired { command: "rm -rf /tmp".into(), - }) + })) } - async fn execute_confirmed(&self, _response: &str) -> Result, ToolError> { - Ok(Some(ToolOutput { + fn execute_confirmed( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(Some(ToolOutput { tool_name: zeph_tools::ToolName::new("bash"), summary: "confirmed output".into(), blocks_executed: 1, @@ -335,20 +383,23 @@ impl ToolExecutor for ConfirmToolExecutor { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } - async fn execute_tool_call(&self, _call: &ToolCall) -> Result, ToolError> { - Err(ToolError::ConfirmationRequired { + fn execute_tool_call( + &self, + _call: &ToolCall, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Err(ToolError::ConfirmationRequired { command: "rm -rf /tmp".into(), - }) + })) } - async fn execute_tool_call_confirmed( + fn execute_tool_call_confirmed( &self, call: &ToolCall, - ) -> Result, ToolError> { - Ok(Some(ToolOutput { + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(Some(ToolOutput { tool_name: call.tool_id.clone(), summary: "confirmed output".into(), blocks_executed: 1, @@ -360,7 +411,7 @@ impl ToolExecutor for ConfirmToolExecutor { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } fn checkpoint_undo(&self, _n: usize) -> zeph_tools::CheckpointActionResult { @@ -383,16 +434,22 @@ impl ToolExecutor for ConfirmToolExecutor { struct SandboxToolExecutor; impl ToolExecutor for SandboxToolExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Err(ToolError::SandboxViolation { + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Err(ToolError::SandboxViolation { path: "/etc/passwd".into(), - }) + })) } - async fn execute_tool_call(&self, _call: &ToolCall) -> Result, ToolError> { - Err(ToolError::SandboxViolation { + fn execute_tool_call( + &self, + _call: &ToolCall, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Err(ToolError::SandboxViolation { path: "/etc/passwd".into(), - }) + })) } zeph_tools::tool_executor_no_inner_defaults!(); } @@ -400,18 +457,24 @@ impl ToolExecutor for SandboxToolExecutor { struct IoErrorToolExecutor; impl ToolExecutor for IoErrorToolExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Err(ToolError::Execution(std::io::Error::new( + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Err(ToolError::Execution(std::io::Error::new( std::io::ErrorKind::NotFound, "command not found", - ))) + )))) } - async fn execute_tool_call(&self, _call: &ToolCall) -> Result, ToolError> { - Err(ToolError::Execution(std::io::Error::new( + fn execute_tool_call( + &self, + _call: &ToolCall, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Err(ToolError::Execution(std::io::Error::new( std::io::ErrorKind::NotFound, "command not found", - ))) + )))) } zeph_tools::tool_executor_no_inner_defaults!(); } @@ -419,8 +482,11 @@ impl ToolExecutor for IoErrorToolExecutor { struct ExitCodeToolExecutor; impl ToolExecutor for ExitCodeToolExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(Some(ToolOutput { + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(Some(ToolOutput { tool_name: zeph_tools::ToolName::new("bash"), summary: "[exit code 1] process failed".into(), blocks_executed: 1, @@ -432,11 +498,14 @@ impl ToolExecutor for ExitCodeToolExecutor { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } - async fn execute_tool_call(&self, call: &ToolCall) -> Result, ToolError> { - Ok(Some(ToolOutput { + fn execute_tool_call( + &self, + call: &ToolCall, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(Some(ToolOutput { tool_name: call.tool_id.clone(), summary: "[exit code 1] process failed".into(), blocks_executed: 1, @@ -448,7 +517,7 @@ impl ToolExecutor for ExitCodeToolExecutor { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } zeph_tools::tool_executor_no_inner_defaults!(); } @@ -2564,6 +2633,7 @@ mod self_learning { } impl Channel for MockChannel { + #[allow(clippy::unused_async_trait_impl)] async fn recv(&mut self) -> Result, ChannelError> { Ok(self.inputs.pop_front().map(|text| ChannelMessage { text, @@ -2574,25 +2644,35 @@ mod self_learning { })) } + #[allow(clippy::unused_async_trait_impl)] async fn send(&mut self, text: &str) -> Result<(), ChannelError> { self.outputs.lock().unwrap().push(text.to_string()); Ok(()) } - async fn send_chunk(&mut self, _chunk: &str) -> Result<(), ChannelError> { - Ok(()) + fn send_chunk( + &mut self, + _chunk: &str, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(())) } - async fn flush_chunks(&mut self) -> Result<(), ChannelError> { - Ok(()) + fn flush_chunks( + &mut self, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(())) } } struct MockToolExecutor; impl ToolExecutor for MockToolExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(None)) } zeph_tools::tool_executor_no_inner_defaults!(); } @@ -2600,8 +2680,12 @@ mod self_learning { struct ErrorToolExecutor; impl ToolExecutor for ErrorToolExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(Some(ToolOutput { + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(Some(ToolOutput { tool_name: zeph_tools::ToolName::new("bash"), summary: "[error] command failed".into(), blocks_executed: 1, @@ -2613,14 +2697,15 @@ mod self_learning { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } - async fn execute_tool_call( + fn execute_tool_call( &self, call: &zeph_tools::executor::ToolCall, - ) -> Result, ToolError> { - Ok(Some(ToolOutput { + ) -> impl std::future::Future, ToolError>> + Send + { + std::future::ready(Ok(Some(ToolOutput { tool_name: call.tool_id.clone(), summary: "[error] command failed".into(), blocks_executed: 1, @@ -2632,7 +2717,7 @@ mod self_learning { raw_response: None, claim_source: None, ..Default::default() - })) + }))) } zeph_tools::tool_executor_no_inner_defaults!(); } diff --git a/tests/performance_agent_integration.rs b/tests/performance_agent_integration.rs index cbb359e66..716396b3d 100644 --- a/tests/performance_agent_integration.rs +++ b/tests/performance_agent_integration.rs @@ -63,6 +63,7 @@ impl MockChannel { } impl Channel for MockChannel { + #[allow(clippy::unused_async_trait_impl)] async fn recv(&mut self) -> Result, ChannelError> { Ok(self.inputs.pop_front().map(|text| ChannelMessage { text, @@ -73,21 +74,29 @@ impl Channel for MockChannel { })) } + #[allow(clippy::unused_async_trait_impl)] async fn send(&mut self, text: &str) -> Result<(), ChannelError> { self.output_sent.lock().unwrap().push(text.to_string()); Ok(()) } - async fn send_chunk(&mut self, _chunk: &str) -> Result<(), ChannelError> { - Ok(()) + fn send_chunk( + &mut self, + _chunk: &str, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(())) } - async fn flush_chunks(&mut self) -> Result<(), ChannelError> { - Ok(()) + fn flush_chunks( + &mut self, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(())) } - async fn send_typing(&mut self) -> Result<(), ChannelError> { - Ok(()) + fn send_typing( + &mut self, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(())) } } @@ -112,10 +121,14 @@ impl InstrumentedMockExecutor { } impl ToolExecutor for InstrumentedMockExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(None)) } + #[allow(clippy::unused_async_trait_impl)] async fn execute_tool_call(&self, call: &ToolCall) -> Result, ToolError> { *self.call_count.lock().unwrap() += 1; self.execution_log @@ -144,14 +157,20 @@ impl ToolExecutor for InstrumentedMockExecutor { struct BlockingMockExecutor; impl ToolExecutor for BlockingMockExecutor { - async fn execute(&self, _response: &str) -> Result, ToolError> { - Ok(None) + fn execute( + &self, + _response: &str, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Ok(None)) } - async fn execute_tool_call(&self, call: &ToolCall) -> Result, ToolError> { - Err(ToolError::Blocked { + fn execute_tool_call( + &self, + call: &ToolCall, + ) -> impl std::future::Future, ToolError>> + Send { + std::future::ready(Err(ToolError::Blocked { command: call.tool_id.to_string(), - }) + })) } zeph_tools::tool_executor_no_inner_defaults!(); } From 8ff7668e239f3b2fe1f87a79defa2506b25d7d9e Mon Sep 17 00:00:00 2001 From: "Andrei G." Date: Tue, 25 Aug 2026 04:32:24 +0200 Subject: [PATCH 2/2] docs: add PR link to MSRV 1.98 changelog entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1930abf14..1c2d574c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Changed - Raised the workspace MSRV to Rust 1.98 and resolved the new `clippy::unused_async_trait_impl` / - `clippy::chunks_exact_to_as_chunks` lints introduced by it (#6746, PR link pending). + `clippy::chunks_exact_to_as_chunks` lints introduced by it (#6746, #6748). - `release.yml`: removed `Swatinem/rust-cache` and `sccache` from the `build-binaries` job. Release builds only run on `v*` tag push, infrequently enough that any cache entry written by the previous release has almost certainly been evicted from the shared 10 GiB GHA cache