Files
codex/codex-rs/skills/src/mentions_tests.rs
T
Matthew Zeng 460b63e5f4 Honor explicit plugin mentions during MCP startup (#42406)
## Why

Selected plugin MCP servers may be skipped after the optional startup grace
period. When a user explicitly mentions a plugin or MCP server, the turn should
continue waiting so the requested tools are available to the model.

## What changed

- Track explicitly mentioned plugin IDs and MCP servers across later samples,
  steered input, and same-turn restarts.
- Require matching selected-plugin MCP servers to finish startup before
  capturing the tool catalog, while leaving unmentioned servers optional.
- Match `plugin://` identities independently of display names and ignore
  targeting query parameters such as `app` and `browserFamily`.

## Testing

Added coverage for linked and structured plugin mentions, direct and batched
capability discovery, delayed environment attachment, steering, and same-turn
restarts.

GitOrigin-RevId: c509ef8df45df1a7edb0da6afdb2682dc53a4de8
2026-09-03 01:33:32 +00:00

106 lines
2.5 KiB
Rust

use std::collections::HashSet;
use pretty_assertions::assert_eq;
use super::*;
fn set<'a>(items: &'a [&'a str]) -> HashSet<&'a str> {
items.iter().copied().collect()
}
fn assert_mentions(text: &str, expected_names: &[&str], expected_paths: &[&str]) {
let mentions = extract_tool_mentions(text);
assert_eq!(mentions.names, set(expected_names));
assert_eq!(mentions.paths, set(expected_paths));
}
#[test]
fn handles_plain_and_linked_mentions() {
assert_mentions(
"use $alpha and [$beta](/tmp/beta)",
&["alpha", "beta"],
&["/tmp/beta"],
);
}
#[test]
fn skips_common_env_vars() {
assert_mentions("use $PATH and $alpha", &["alpha"], &[]);
assert_mentions("use [$HOME](/tmp/skill)", &[], &[]);
assert_mentions("use $XDG_CONFIG_HOME and $beta", &["beta"], &[]);
}
#[test]
fn requires_link_syntax() {
assert_mentions("[beta](/tmp/beta)", &[], &[]);
assert_mentions("[$beta] /tmp/beta", &["beta"], &[]);
assert_mentions("[$beta]()", &["beta"], &[]);
}
#[test]
fn trims_linked_paths_and_allows_spacing() {
assert_mentions("use [$beta] ( /tmp/beta )", &["beta"], &["/tmp/beta"]);
}
#[test]
fn stops_at_non_name_chars() {
assert_mentions(
"use $alpha.skill and $beta_extra",
&["alpha", "beta_extra"],
&[],
);
}
#[test]
fn keeps_plugin_skill_namespaces() {
assert_mentions(
"use $slack:search and $alpha",
&["alpha", "slack:search"],
&[],
);
}
#[test]
fn requires_exact_name_boundaries() {
assert_mentions(
"use $notion-research-doc but not $notion-research-docs or $notion-research-doc_extra",
&[
"notion-research-doc",
"notion-research-docs",
"notion-research-doc_extra",
],
&[],
);
}
#[test]
fn handles_many_sigils_without_looping() {
let prefix = "$".repeat(256);
assert_mentions(&format!("{prefix} not-a-mention"), &[], &[]);
}
#[test]
fn plugin_config_names_ignore_mention_query_parameters() {
let paths = [
"plugin://sample@test",
"plugin://sample@test?app=com.example.editor",
"plugin://sample@test?browserFamily=chrome",
];
assert_eq!(
paths.map(plugin_config_name_from_path),
[Some("sample@test"); 3],
);
}
#[test]
fn plugin_config_names_require_a_plugin_identity() {
let paths = [
"plugin://",
"plugin://?app=com.example.editor",
"app://sample@test?app=com.example.editor",
];
assert_eq!(paths.map(plugin_config_name_from_path), [None; 3]);
}