diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d714ca..69fe951 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,9 @@ All notable changes to `yoagent` are documented here. The format loosely follows [Keep a Changelog](https://keepachangelog.com/), and the project adheres to [Semantic Versioning](https://semver.org/). -## Unreleased +## 0.25.1 (2026-10-09) + +Patch release: additive only. Ships with **yoagent-rutis 0.1.1** (requires yoagent 0.25.1); yoagent-workers is unchanged (0.2.0). ### GASP @@ -16,7 +18,7 @@ adheres to [Semantic Versioning](https://semver.org/). - **Images in tool results, across ecosystems.** TypeScript and Python handlers' `call_tool` results and `after_tool` edits take `content` blocks in yoagent's JSON shape (`{"type": "image", "data": , "mimeType"}` next to text blocks) instead of `text`, so pictures cross the bridge both ways (`after_tool` already saw `output.content`). The dsh adapter turns dsh image blocks (references into dsh's attachment store) into yoagent images through the `attachments` service when one is loaded; the rutis-agent example reads a runner's `{"content": [...]}` value as blocks (rutis-agent results are otherwise text). Rust handlers already returned full yoagent tool results. Tests: `content_blocks` unit test, TypeScript/Python round trip in `languages_test`, dsh with and without a store in `dsh_test`. - **Plugin logs reach the host.** The `yoagent` service gains `log(level, message, { run_id }?)`: a TypeScript/Python plugin's diagnostics go to the host's `tracing` output (target `yoagent_rutis::plugin`, with the run as a `run_id` field) instead of the runtime process's stderr. It never rejects (a rejected, uncaught call would end the plugin's runtime): any message, extra context fields ignored. In Python it is a coroutine to `await`. The dsh adapter uses it. Test: `tests/plugin_log_test.rs`. - **Docs: one contract, other ecosystems.** The extensions guide shows DSH and rutis-agent plugging in through `Extension` and the bridge with no core change (pi too, since #265); the bridge README states the adapter contract (map what the host honours, refuse what would decide unenforced, deny on failing policies, withhold on failing redactions, report what is ignored). CI now also runs the rutis-agent example. -- Test fix: `dsh_test` no longer reads the abort file mid-write (empty) as the result. +- Test fixes: `dsh_test` no longer reads the abort file mid-write (empty) as the result; `pi_test`'s refusal checks wait for the adapter to become active or fail (the loader counts a row still waiting for its Node runtime as settled, so on a slow runner a refusal could be read before it happened). ### Docs diff --git a/Cargo.toml b/Cargo.toml index bcecfb8..27880f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yoagent" -version = "0.25.0" +version = "0.25.1" edition = "2021" # MSRV-aware dependency resolution (cargo 1.84+): prefer dependency versions # whose own rust-version fits ours. Without it CI re-resolves to whatever is diff --git a/integrations/yoagent-rutis/Cargo.toml b/integrations/yoagent-rutis/Cargo.toml index acc69ef..e1a0c93 100644 --- a/integrations/yoagent-rutis/Cargo.toml +++ b/integrations/yoagent-rutis/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yoagent-rutis" -version = "0.1.0" +version = "0.1.1" edition = "2021" resolver = "3" description = "Extend yoagent agents at runtime with rutis plugins, as one yoagent Extension: tools, tool policy, output filters, turn notes, input checks, verifiers and events" @@ -26,7 +26,7 @@ rust-version = "1.86" [dependencies] # The path's version (0.25 is the first release with `Extension`). yoagent # types are in this crate's API: a yoagent minor bump is a minor bump here. -yoagent = { path = "../..", version = "0.25.0" } +yoagent = { path = "../..", version = "0.25.1" } # 0.x caret: 0.6.x only (0.7 would be breaking). Any rutis minor bump is a # yoagent-rutis minor bump — its types are part of this crate's API. rutis = "0.6" diff --git a/integrations/yoagent-rutis/tests/pi_test.rs b/integrations/yoagent-rutis/tests/pi_test.rs index 8218faa..98646b6 100644 --- a/integrations/yoagent-rutis/tests/pi_test.rs +++ b/integrations/yoagent-rutis/tests/pi_test.rs @@ -17,11 +17,11 @@ use std::sync::{Arc, Mutex}; use std::time::Duration; use common::*; -use rutis::Ctx; +use rutis::{Ctx, FiberState}; use rutis_bridge::runtime::LocalRuntime; use rutis_loader::{ - Chain, Layer, Loader, LoaderOptions, LoaderPlugin, Patch, RuntimeResolver, RuntimeRowsPlugin, - ServiceCatalog, + Chain, EntryStatus, Layer, Loader, LoaderOptions, LoaderPlugin, Patch, RuntimeResolver, + RuntimeRowsPlugin, ServiceCatalog, }; use serde_json::{json, Value}; use yoagent::provider::mock::{MockResponse, MockToolCall}; @@ -114,11 +114,43 @@ impl Host { .reconcile(vec![Layer::new("rows", patches)], None) .await .unwrap(); - if report.failures.is_empty() { - String::new() - } else { - format!("{:?}", report.failures) + if !report.failures.is_empty() { + return format!("{:?}", report.failures); } + // `reconcile` counts a row still waiting (`Pending`) as settled, so on a + // loaded machine the adapter may not have run its `apply` yet: wait + // until every row is active or has failed before calling it a success. + let ids: Vec = rows + .as_array() + .into_iter() + .flatten() + .filter_map(|row| row["id"].as_str().map(str::to_owned)) + .collect(); + tokio::time::timeout(Duration::from_secs(60), async { + loop { + let mut failures = Vec::new(); + let mut settled = true; + for id in &ids { + match self.loader.get(id).map(|info| info.status) { + Some(EntryStatus::Unresolved(e)) => failures.push(e.to_string()), + Some(EntryStatus::Running(s)) if s.state == FiberState::Failed => { + failures.push(format!("{:?}", s.error)) + } + Some(EntryStatus::Running(s)) if s.state == FiberState::Active => {} + _ => settled = false, + } + } + if !failures.is_empty() { + return failures.join("; "); + } + if settled { + return String::new(); + } + tokio::time::sleep(Duration::from_millis(20)).await; + } + }) + .await + .expect("the rows become active or fail") } async fn load(&self, rows: Value) {