Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ jobs:
- name: Check
run: cargo check --manifest-path "$MANIFEST" --all-targets
- name: Check with the language features (rutis-bridge 0.9)
run: cargo check --manifest-path "$MANIFEST" --all-targets --features node,python,bun,go,websocket
run: cargo check --manifest-path "$MANIFEST" --all-targets --features node,python,bun,go,websocket,probe

# TypeScript (Node and Bun 1.4), Python and Go (1.24) plugins through rutis-bridge 0.9, end to end: real
# Node 22 (the floor: pi needs 22.19, rutis 0.9 22)
Expand Down Expand Up @@ -381,13 +381,15 @@ jobs:
"$PLUGINS/.venv/bin/pip" install -r "$PLUGINS/requirements.txt"
- name: Clippy (each language feature, and all)
run: |
for features in node python bun go websocket node,python,bun,go,websocket; do
for features in node python bun go websocket probe node,python,bun,go,websocket,probe; do
cargo clippy --manifest-path "$MANIFEST" --all-targets --features "$features"
done
- name: Documentation
run: RUSTDOCFLAGS="-Dwarnings" cargo doc --manifest-path "$MANIFEST" --no-deps --features node,python,bun,go,websocket
run: RUSTDOCFLAGS="-Dwarnings" cargo doc --manifest-path "$MANIFEST" --no-deps --features node,python,bun,go,websocket,probe
# `probe` adds tests/probe_test.rs: the pi and DSH adapters' fixtures
# through `yoagent_rutis::probe`.
- name: Test
run: cargo test --manifest-path "$MANIFEST" --features node,python,bun,go,websocket
run: cargo test --manifest-path "$MANIFEST" --features node,python,bun,go,websocket,probe
- name: Example
run: cargo run --manifest-path "$MANIFEST" --features node,python --example language_plugins
- name: pi extensions example (scripted, the fixture extension)
Expand Down
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@ 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

### yoagent-rutis (0.4.0)

- **`yoagent_rutis::adapters`: where the pi and DSH adapters are.** `adapters()` returns the folders of the adapter files the crate ships (`plugins/pi`, `plugins/dsh`, `plugins/yoagent.d.ts`), so a host can copy them and `npm ci` the copy instead of keeping its own copy of them in step with the crate's.
- **`yoagent_rutis::probe` (feature `probe`): vet a plugin package without running it as an agent.** `probe::pi` and `probe::dsh` load a pi extension or DSH plugin rows through the adapter, as an agent would, and report what an agent gets — tools, prompt additions, which DSH services it needs and whether they exist, skills, and what the plugin logged — without calling a tool or a model. Both take `plugins`, a copy of `adapters().root` with `npm ci` run in its `pi/` and `dsh/`. Each report's `problem()` is the verdict (`None` = a clean load). Probes in one process run one at a time, so each report carries its own plugin logs. It is what `examples/ecosystem_probe.rs` (the compatibility matrix's probe, now with `--plugins`) ran; the example now calls it, with the same verdicts. The feature adds `rutis-loader`, `tempfile` and `tracing-subscriber`; the default build is unchanged.
- **The probe reports what used to read as clean.**
- A pi extension the adapter refuses after load (a handler for an event yoagent never fires, or a same-name tool added later) is `loaded: false` with the refusal, where it read as loaded with no tools. A late same-name tool is retried with `withoutBuiltins`, like one at load.
- A DSH plugin's injected services are read from its modules inside the runtime, so a missing one is found without the caller listing it (`inject` adds to them). Every key counts: Cordis requires them all, so one declared `required: false` and missing used to read clean (also in `inspect-package.mjs` and the matrix). A module whose `inject` could not be read is a problem, not a clean verdict.
- The probe copies its two DSH files (`dsh-tools-adapter.ts`, `probe-services.ts`) into `base` when they are missing: they must resolve the base's Cordis, and a base with only one of them failed every service check.
- DSH rows that are not an array of rows with string ids are an error.
- A bare `SKILL.md` path is a skill in the current folder (it was dropped).
- pi's early failures keep their logs.
- Logs include the bridge's reports of failed hooks (`yoagent_rutis::extension`).
- A DSH report's rows are only the plugin's (the base's host-service rows were listed too).
- On error paths the reports now carry empty fields (`rows: []`, `services: null`) where the old example printed nothing, and an unreadable `host-services.json` is an error, not a panic.

## 0.25.5 (2026-10-16)

### yoagent
Expand Down
2 changes: 1 addition & 1 deletion integrations/yoagent-rutis/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ Its own `Cargo.toml` (not a workspace member; cargo leaves the nested package ou

### Plugin compatibility matrix

`scripts/ecosystem-matrix.mjs` (repo root) → `docs/evals/plugin-compatibility.{md,json}`: top N (default 20) npm packages by `downloads.monthly` among the keyword search results (`pi-package`, `dsh-plugin`; the API returns ≤1000), each probed by `examples/ecosystem_probe.rs` (feature `node`; one JSON line; nothing called, no model). **Harness errors are classified `failed` with the reason, never as a verdict** (a failed resolve, a service check that did not report, rows whose `inject` could not be read). pi: installed alone `--legacy-peer-deps --ignore-scripts` (pi supplies its own packages), resolved by `plugins/pi/resolve-package.mjs` (pi's `DefaultPackageManager.resolveExtensionSources`, in-memory settings, a temp agent dir removed after — as `pi -e <dir>`), loaded through the adapter; the probe waits for the adapter's handler, then re-reads the row (a TS row reads `Active` before its async `apply` ends); a same-name refusal is retried with that built-in in `withoutBuiltins`; an "adapter stopped" warning → `refused`; skills loaded with yoagent's `SkillSet` (paths compared canonicalized — the loader canonicalizes, and macOS `/var` is `/private/var`; rejected ones reported); the adapter's debug line `[pi] loaded <file>: events …; tools …` gives the hooks. Logs via a **global** tracing subscriber (the bridge logs from worker threads). DSH: base = `plugins/dsh` lock `npm ci`'d once, copied per package; the plugin is installed in its own dir **with** its peers (its `@deepseek-ai/dsh-*` peers the base lacks at the base's release, other `@deepseek-ai/` peers at their range; retried without peers only if that fails, noted on the row), then **merged without npm** (`mergeInto`: the plugin first, replacing any base copy; the base's `@deepseek-ai`/`@arcships` packages win; any other name the base has is nested under the plugin — `npm install` in the copy re-plans and prunes/swaps DSH packages); the adapter and `probe-services.ts` are copied into it (one Cordis); `plugins/dsh/inspect-package.mjs` reads the manifest directly, the bundle patch's `insert` rows, and every row's `inject` (`importErrors` per row); the probe loads the base, then the rows, and diffs tools / guidance; `probe-services.ts` (plain Cordis plugin, row id `probe-services`) reports which injected services exist — a Node row waiting on a missing service reads `Active` to the host. A DSH plugin with a web client is "works, partly" (its UI ignored). `--from-json` re-labels earlier results without probing; `--only` keeps the other ecosystem's results and dates (`runs.{pi,dsh}`). Manual workflow `plugin-matrix.yml` (artifact only, read-only token, `persist-credentials: false`: loading runs third-party code).
`scripts/ecosystem-matrix.mjs` (repo root) → `docs/evals/plugin-compatibility.{md,json}`: top N (default 20) npm packages by `downloads.monthly` among the keyword search results (`pi-package`, `dsh-plugin`; the API returns ≤1000), each probed by `examples/ecosystem_probe.rs` (feature `probe`; one JSON line; nothing called, no model), the command-line face of `src/probe.rs` (`yoagent_rutis::probe`, feature `probe` = `node` + optional `rutis-loader`/`tempfile`/`tracing-subscriber`: `pi(plugins, cwd, extensions, skills, logs) -> PiReport`, `dsh(plugins, base, rows, inject, logs) -> DshReport` (`plugins` = a copy of `adapters().root` with `npm ci` in `pi/` and `dsh/`; the plugin's rows only, by id), typed, `#[non_exhaustive]`, `Serialize` to the JSON the matrix reads, `problem()` = the verdict; probes in one process serialized by a static tokio mutex (`Logs` is one buffer; it collects targets `yoagent_rutis::plugin` and `::extension`); a refusal after load (`before_model` → `Stop`/`Fail`, read in `offered`) is `loaded: false` + the refusal, and a late same-name refusal retries `withoutBuiltins`; `probe-services.ts` reads each row module's `inject` itself (`modules` + `base`, as `inspect-package.mjs` does; **every** key — Cordis requires them all, `required: false` included) plus the caller's `inject`; `dsh_file` copies `dsh-tools-adapter.ts` / `probe-services.ts` into `base` when missing (they must resolve base's Cordis); `importErrors` count in `problem()`; rows must be an array with string ids (`row_ids`); tests `tests/probe_test.rs` on the adapters' fixtures incl. `fixture-late-refusal.ts`, `fixture-shadow.ts`, `fixture-missing-service.ts`, unit tests pin the report JSON keys). `src/adapters.rs`: `adapters() -> Adapters { root, pi, dsh, types }`, the crate's packaged `plugins/` files (`env!("CARGO_MANIFEST_DIR")`; read or copy, never install into). **Harness errors are classified `failed` with the reason, never as a verdict** (a failed resolve, a service check that did not report, rows whose `inject` could not be read). pi: installed alone `--legacy-peer-deps --ignore-scripts` (pi supplies its own packages), resolved by `plugins/pi/resolve-package.mjs` (pi's `DefaultPackageManager.resolveExtensionSources`, in-memory settings, a temp agent dir removed after — as `pi -e <dir>`), loaded through the adapter; the probe waits for the adapter's handler, then re-reads the row (a TS row reads `Active` before its async `apply` ends); a same-name refusal is retried with that built-in in `withoutBuiltins`; an "adapter stopped" warning, or an `error` with it (the probe's refusal after load), → `refused`; skills loaded with yoagent's `SkillSet` (paths compared canonicalized — the loader canonicalizes, and macOS `/var` is `/private/var`; rejected ones reported); the adapter's debug line `[pi] loaded <file>: events …; tools …` gives the hooks. Logs via a **global** tracing subscriber (the bridge logs from worker threads). DSH: base = `plugins/dsh` lock `npm ci`'d once, copied per package; the plugin is installed in its own dir **with** its peers (its `@deepseek-ai/dsh-*` peers the base lacks at the base's release, other `@deepseek-ai/` peers at their range; retried without peers only if that fails, noted on the row), then **merged without npm** (`mergeInto`: the plugin first, replacing any base copy; the base's `@deepseek-ai`/`@arcships` packages win; any other name the base has is nested under the plugin — `npm install` in the copy re-plans and prunes/swaps DSH packages); the adapter and `probe-services.ts` are copied into it (one Cordis); `plugins/dsh/inspect-package.mjs` reads the manifest directly, the bundle patch's `insert` rows, and every row's `inject` (`importErrors` per row); the probe loads the base, then the rows, and diffs tools / guidance; `probe-services.ts` (plain Cordis plugin, row id `probe-services`) reports which injected services exist — a Node row waiting on a missing service reads `Active` to the host. A DSH plugin with a web client is "works, partly" (its UI ignored). `--from-json` re-labels earlier results without probing; `--only` keeps the other ecosystem's results and dates (`runs.{pi,dsh}`). Manual workflow `plugin-matrix.yml` (artifact only, read-only token, `persist-credentials: false`: loading runs third-party code).
16 changes: 14 additions & 2 deletions integrations/yoagent-rutis/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,14 @@ tokio = { version = "1", features = ["rt", "sync", "time", "macros"] }
# Same crate rutis hands its cancellation tokens out as.
tokio-util = "0.7"
tracing = "0.1"
# The `probe` feature: loading plugin rows (the bridge itself never loads
# plugins), a DSH home per probe, and collecting what plugins log.
rutis-loader = { version = "0.9", optional = true, features = ["node"] }
tempfile = { version = "3", optional = true }
tracing-subscriber = { version = "0.3", optional = true, default-features = false, features = ["registry"] }

[package.metadata.docs.rs]
features = ["node", "python", "bun", "go", "websocket"]
features = ["node", "python", "bun", "go", "websocket", "probe"]
rustdoc-args = ["--cfg", "docsrs"]

[features]
Expand All @@ -66,6 +71,9 @@ python = ["rutis-bridge/python"]
bun = ["rutis-bridge/bun"]
# Remote nodes and runtimes over WebSocket (rustls).
websocket = ["rutis-bridge/websocket"]
# `yoagent_rutis::probe`: load a pi or DSH package through its adapter and
# report what an agent gets, without calling a tool or a model (Unix).
probe = ["node", "dep:rutis-loader", "dep:tempfile", "dep:tracing-subscriber"]

[dev-dependencies]
tokio = { version = "1", features = ["rt-multi-thread", "macros", "time", "sync"] }
Expand Down Expand Up @@ -100,6 +108,10 @@ required-features = ["node"]
name = "pi_log_test"
required-features = ["node"]

[[test]]
name = "probe_test"
required-features = ["probe"]

[[example]]
name = "language_plugins"
required-features = ["node", "python"]
Expand All @@ -114,4 +126,4 @@ required-features = ["node"]

[[example]]
name = "ecosystem_probe"
required-features = ["node"]
required-features = ["probe"]
32 changes: 31 additions & 1 deletion integrations/yoagent-rutis/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,36 @@ The end-to-end tests cover local Node and Python runtimes; a remote node over

## Tools from other agent ecosystems

The adapters below ship in the crate (`plugins/pi`, `plugins/dsh`,
`plugins/yoagent.d.ts`, without `node_modules`), and
`yoagent_rutis::adapters::adapters()` says where they are: the crate's own
files, where it was built (cargo's registry or a path dependency). Copy
the whole `root` (the adapters import `../yoagent.d.ts`) and run `npm ci`
in the copy's `pi/` and `dsh/`; never install into the registry's.

With the `probe` feature, `yoagent_rutis::probe` loads a pi or DSH package
through its adapter and reports what an agent gets — tools, prompt
additions, the DSH services it needs and whether they exist, skills, and
what the plugin logged — without calling a tool or a model (loading runs
the package's code, as its own host would). Each report's `problem()` is
the verdict: `None` for a clean load, else what kept the package from
giving an agent what it offers (a refusal after load included). Probes in
one process run one at a time.

```rust,no_run
# async fn check() {
use yoagent_rutis::probe;
// A copy of `adapters().root`, with `npm ci` run in its `pi/` and `dsh/`.
let plugins = std::path::Path::new("/path/to/plugins");
let cwd = std::path::Path::new("/path/to/project");
let report = probe::pi(plugins, cwd, &["/path/to/extension.ts".into()], &[], None).await;
match report.problem() {
None => println!("usable: {:?}", report.tools),
Some(why) => println!("not usable: {why}"),
}
# }
```

### dsh (DeepSeek Harness) tool plugins

dsh plugins are Cordis plugins, and rutis's Node runtime runs them
Expand Down Expand Up @@ -552,7 +582,7 @@ downloads, among npm's first 1000 search results) load through these
adapters is measured in
[plugin compatibility](../../docs/evals/plugin-compatibility.md)
(`scripts/ecosystem-matrix.mjs`, which runs `examples/ecosystem_probe.rs`
per package).
— `yoagent_rutis::probe` on the command line — per package).

The point is that **a plugin's safety policy never silently stops
applying**. Not every rule arises for every adapter: the DSH adapter maps
Expand Down
Loading
Loading