Synced from monorepo
Synced from monorepo Changes: - Temporarily disable session share link creation in the TUI - Do not approve plan on empty Enter from the revise prompt - Expose chat product Skills via ACP available_commands_update - Return immediately from a blocking wait on an already-completed ACP task - Split headless pager module for clearer structure - Stop git worktree prune from removing user registrations on resume - Use compaction sampler tokenizer for item token counts - Opt-in extra root CAs via GROK_EXTRA_CA_BUNDLE - Cancel all session subagents when the user stops - Let the session persistence actor exit when its session ends - Make fullscreen terminal resize much cheaper on long sessions - Report honestly from kill_task when an ACP task does not exist - Hide /usage for external-auth deployments - Forward the history-load trailer’s computer_reason to the client - Remove ineffective no-op tool reminder - Declare slash-command screen-mode support in one place - Keep settings enum picker on the committed value until Enter - Reap a PTY’s full process tree - Stream tool calls from headless mode over ACP - Bridge gateway task lifecycle to ACP for chat session background tasks - Don’t warn about truncated history on a suppressed replay - Fit full-replace summarizer input and recover on context-length errors - Stop dropping agents over an unrecognized frontmatter color - Add /undo as a slash alias for /rewind - Harden sleep/wake token-refresh paths against forced re-login - Add session/list ACP method - Give each sampling backend its own conversion module - Treat an unenrolled child process as a lint error - Suppress the cancelled marker on send-now wake turns - Stop tearing down Roslyn on every edit, and read C# diagnostics Source-Revision: 2a28b4a86cfc4a4c133c35b7fc2a6a9964387c39
This commit is contained in:
parent
500129c714
commit
dd04f397b1
367 changed files with 29489 additions and 10051 deletions
|
|
@ -399,14 +399,9 @@ fn all_subagents_with_plugins_and_home(
|
|||
if path.extension().and_then(|e| e.to_str()) != Some("md") {
|
||||
continue;
|
||||
}
|
||||
// Use frontmatter-only parsing for untrusted plugins
|
||||
let def = if plugin.trusted {
|
||||
AgentDefinition::from_file(&path).ok()
|
||||
} else {
|
||||
AgentDefinition::from_file_frontmatter_only(&path).ok()
|
||||
let Some(def) = load_plugin_agent_definition(plugin, &path) else {
|
||||
continue;
|
||||
};
|
||||
let Some(mut def) = def else { continue };
|
||||
def.plugin_name = Some(plugin.name.clone());
|
||||
|
||||
let qualified_name = format!("{}:{}", plugin.name, def.name);
|
||||
|
||||
|
|
@ -481,17 +476,11 @@ fn by_name_in_cwd_with_plugins_and_home(
|
|||
{
|
||||
for agent_dir in &plugin.agent_dirs {
|
||||
let agent_file = agent_dir.join(format!("{agent_name}.md"));
|
||||
if agent_file.is_file() {
|
||||
let load_fn = if plugin.trusted {
|
||||
AgentDefinition::from_file
|
||||
} else {
|
||||
AgentDefinition::from_file_frontmatter_only
|
||||
};
|
||||
if let Ok(mut def) = load_fn(&agent_file) {
|
||||
def.plugin_name = Some(plugin_name.to_string());
|
||||
substitute_plugin_vars(&mut def, plugin);
|
||||
return Some(def);
|
||||
}
|
||||
if agent_file.is_file()
|
||||
&& let Some(mut def) = load_plugin_agent_definition(plugin, &agent_file)
|
||||
{
|
||||
substitute_plugin_vars(&mut def, plugin);
|
||||
return Some(def);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -510,13 +499,7 @@ fn by_name_in_cwd_with_plugins_and_home(
|
|||
}
|
||||
if matches.len() == 1 {
|
||||
let (plugin, agent_file) = &matches[0];
|
||||
let load_fn = if plugin.trusted {
|
||||
AgentDefinition::from_file
|
||||
} else {
|
||||
AgentDefinition::from_file_frontmatter_only
|
||||
};
|
||||
if let Ok(mut def) = load_fn(agent_file) {
|
||||
def.plugin_name = Some(plugin.name.clone());
|
||||
if let Some(mut def) = load_plugin_agent_definition(plugin, agent_file) {
|
||||
substitute_plugin_vars(&mut def, plugin);
|
||||
return Some(def);
|
||||
}
|
||||
|
|
@ -533,6 +516,37 @@ fn by_name_in_cwd_with_plugins_and_home(
|
|||
None
|
||||
}
|
||||
|
||||
/// Load one plugin-provided agent file, tagged with its owning plugin.
|
||||
///
|
||||
/// Untrusted plugins are parsed frontmatter-only so their prompt body never
|
||||
/// reaches the model before the plugin is trusted. A parse failure drops the
|
||||
/// agent from discovery entirely, so it is logged rather than swallowed.
|
||||
fn load_plugin_agent_definition(
|
||||
plugin: &crate::plugins::LoadedPlugin,
|
||||
path: &Path,
|
||||
) -> Option<AgentDefinition> {
|
||||
let loaded = if plugin.trusted {
|
||||
AgentDefinition::from_file(path)
|
||||
} else {
|
||||
AgentDefinition::from_file_frontmatter_only(path)
|
||||
};
|
||||
match loaded {
|
||||
Ok(mut def) => {
|
||||
def.plugin_name = Some(plugin.name.clone());
|
||||
Some(def)
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
plugin = %plugin.name,
|
||||
path = %path.display(),
|
||||
error = %e,
|
||||
"Failed to parse plugin agent definition, skipping"
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Expand `${CLAUDE_PLUGIN_ROOT}` / `${CLAUDE_PLUGIN_DATA}` (and the Grok
|
||||
/// aliases) in a plugin agent's body so the model receives absolute paths,
|
||||
/// matching the expected load-time resolution for these variables.
|
||||
|
|
@ -1368,6 +1382,44 @@ mod tests {
|
|||
assert!(entries.iter().any(|e| e.name == "plugin-one:reviewer"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plugin_agent_with_unrecognized_color_is_still_discovered() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let cwd = tmp.path().join("workspace");
|
||||
let home = tmp.path().join("home");
|
||||
fs::create_dir_all(&cwd).unwrap();
|
||||
fs::create_dir_all(&home).unwrap();
|
||||
|
||||
let plugin_root = tempfile::tempdir().unwrap();
|
||||
let plugin_agents = plugin_root.path().join("agents");
|
||||
fs::create_dir_all(&plugin_agents).unwrap();
|
||||
fs::write(
|
||||
plugin_agents.join("painter.md"),
|
||||
"---\nname: painter\ndescription: Plugin painter\ncolor: chartreuse\n---\nBody.\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let registry = make_plugin_registry("plugin-one", PluginScope::User, vec![plugin_agents]);
|
||||
let entries = all_subagents_with_plugins_and_home(
|
||||
&cwd,
|
||||
&HashMap::new(),
|
||||
Some(®istry),
|
||||
Some(&home),
|
||||
Some(&home.join(".grok")),
|
||||
);
|
||||
assert!(entries.iter().any(|e| e.name == "plugin-one:painter"));
|
||||
|
||||
let def = by_name_in_cwd_with_plugins_and_home(
|
||||
"plugin-one:painter",
|
||||
&cwd,
|
||||
Some(®istry),
|
||||
Some(&home),
|
||||
Some(&home.join(".grok")),
|
||||
)
|
||||
.expect("agent must resolve despite the unrecognized color");
|
||||
assert_eq!(def.color, None, "unrecognized color must be dropped");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_by_name_in_cwd_with_plugins_prefers_native_over_plugin_bare_name() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
|
|
|
|||
Loading…
Reference in a new issue