mirror of
https://github.com/storytold/craft-anything.git
synced 2026-11-05 20:47:45 +01:00
Register an installed agent named with --backend (R5.5) (#821)
`rebuild --backend <name>` uses a configured backend of that name as it is, and registers an installed agent whose name matches when it is not added yet; an unknown name adds nothing. Follow-up to #819 (R5.5).
This commit is contained in:
committed by
GitHub
parent
f8d6e3585a
commit
e59b1ef4d8
@@ -38,7 +38,7 @@ pub struct RebuildArgs {
|
||||
/// the computer stopped); the request is ignored.
|
||||
#[arg(long, requires = "continue_in")]
|
||||
pub resume: bool,
|
||||
/// The coding agent to use (a backend name from `ai list`).
|
||||
/// The coding agent to use (a backend name from `ai list`, or an installed agent such as `Claude Code`).
|
||||
#[arg(long)]
|
||||
pub backend: Option<String>,
|
||||
/// Refinement rounds after the first build.
|
||||
@@ -71,14 +71,21 @@ pub fn rebuild(cli: &Cli, a: &RebuildArgs) -> u8 {
|
||||
}
|
||||
}
|
||||
|
||||
/// Add the first agent app found on this computer when no enabled AI can build at all.
|
||||
fn ensure_agent(hub: &AiHub) -> Result<(), ToolError> {
|
||||
// An enabled API backend builds with our own agent loop, so this run needs no agent app. Only a
|
||||
// computer with no AI set up needs one found for it.
|
||||
if hub.backends().iter().any(|b| b.enabled) {
|
||||
/// Add the agent app this run asks for, or the first one found when no enabled AI can build.
|
||||
fn ensure_agent(hub: &AiHub, requested: Option<&str>) -> Result<(), ToolError> {
|
||||
let configured = hub.backends();
|
||||
// An enabled backend can build, so a run that names none needs no agent app.
|
||||
if requested.is_none() && configured.iter().any(|b| b.enabled) {
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(d) = hub.detect().into_iter().find(|d| d.suggestion.kind == BackendKind::AgentCli) {
|
||||
// A name that is configured is used as it is (including a backend that is off): don't add a
|
||||
// second one under the same name.
|
||||
if requested.is_some_and(|name| configured.iter().any(|b| b.name == name)) {
|
||||
return Ok(());
|
||||
}
|
||||
// An explicit name may be an installed agent that isn't added yet: add only that one. With no
|
||||
// name, add the first agent found.
|
||||
if let Some(d) = hub.detect().into_iter().find(|d| d.suggestion.kind == BackendKind::AgentCli && requested.is_none_or(|name| d.suggestion.name == name)) {
|
||||
eprintln!("Using {} ({}).", d.suggestion.name, d.reason);
|
||||
hub.add_backend(d.suggestion, None)?;
|
||||
}
|
||||
@@ -95,7 +102,7 @@ fn run(cli: &Cli, a: &RebuildArgs) -> Result<bool, ToolError> {
|
||||
if a.generate_assets && a.continue_in.is_some() {
|
||||
return Err(ToolError::invalid("--generate-assets is for new projects.").with_remediation("Use project-assets-allow for an existing project."));
|
||||
}
|
||||
ensure_agent(&hub)?;
|
||||
ensure_agent(&hub, a.backend.as_deref())?;
|
||||
let (ws, message) = match &a.continue_in {
|
||||
Some(dir) => (Workspace::open(&std::path::absolute(dir).map_err(|e| ToolError::io(dir.display(), e))?)?, (!a.resume).then(|| a.request.clone())),
|
||||
None => {
|
||||
|
||||
@@ -102,6 +102,47 @@ fn an_enabled_api_backend_is_enough_without_being_named() {
|
||||
assert!(!saved.contains("agent_cli"), "no agent app may be saved when an API backend is enabled:\n{saved}");
|
||||
}
|
||||
|
||||
/// A named agent that is installed but not added yet is still registered, so the run can use it.
|
||||
#[test]
|
||||
fn a_named_installed_agent_is_added_when_missing() {
|
||||
let config = tempfile::tempdir().unwrap();
|
||||
let home = tempfile::tempdir().unwrap();
|
||||
let bin = tempfile::tempdir().unwrap();
|
||||
let project = tempfile::tempdir().unwrap();
|
||||
api_config(config.path());
|
||||
fake_claude(bin.path());
|
||||
|
||||
let args = stopped_run(project.path(), Some("Claude Code"));
|
||||
let refs: Vec<&str> = args.iter().map(String::as_str).collect();
|
||||
let out = cli(&refs, config.path(), home.path(), bin.path());
|
||||
let log = out.stderr_text();
|
||||
assert!(!out.success, "the empty project folder must stop the run:\n{log}");
|
||||
assert!(log.contains("craft.json"), "the run must stop at the missing project, before any build:\n{log}");
|
||||
assert!(log.contains("Using Claude Code"), "the named agent must still be registered:\n{log}");
|
||||
let saved = std::fs::read_to_string(config.path().join("config.toml")).unwrap();
|
||||
assert!(saved.contains("agent_cli") && saved.contains("name = \"Claude Code\""), "the named agent must be saved:\n{saved}");
|
||||
}
|
||||
|
||||
/// An unknown name must not add a different agent that happens to be installed.
|
||||
#[test]
|
||||
fn an_unknown_backend_does_not_add_an_agent() {
|
||||
let config = tempfile::tempdir().unwrap();
|
||||
let home = tempfile::tempdir().unwrap();
|
||||
let bin = tempfile::tempdir().unwrap();
|
||||
let project = tempfile::tempdir().unwrap();
|
||||
fake_claude(bin.path());
|
||||
|
||||
let args = stopped_run(project.path(), Some("No Such AI"));
|
||||
let refs: Vec<&str> = args.iter().map(String::as_str).collect();
|
||||
let out = cli(&refs, config.path(), home.path(), bin.path());
|
||||
let log = out.stderr_text();
|
||||
assert!(!out.success, "the empty project folder must stop the run:\n{log}");
|
||||
assert!(log.contains("craft.json"), "the run must stop at the missing project, before any build:\n{log}");
|
||||
assert!(!log.contains("Using "), "an unknown name must not pick an installed agent:\n{log}");
|
||||
let saved = std::fs::read_to_string(config.path().join("config.toml")).unwrap_or_default();
|
||||
assert!(!saved.contains("agent_cli"), "an unknown name must not save an agent:\n{saved}");
|
||||
}
|
||||
|
||||
/// A command refused before any build starts must not change the person's AI settings either.
|
||||
#[test]
|
||||
fn an_invalid_command_does_not_add_an_agent() {
|
||||
|
||||
+2
-2
@@ -319,8 +319,8 @@ Owner: open · Status: todo
|
||||
settings and prints "Using Claude Code (…)": `ensure_agent` (`rebuild_cmd.rs`) runs whenever no
|
||||
agent app is set up, though this run won't use one. Run it only when the build needs one
|
||||
— claimed: AlexJoaquimPereira, branch `bugfix/r5.5-no-agent-with-api-backend`
|
||||
- [ ] `--backend <an unknown or disabled name>` with no enabled AI still finds and saves an agent
|
||||
before the name is refused: check the name before `ensure_agent` runs
|
||||
- [ ] `--backend <a name that is turned off>` still builds with it: `builder_backend`'s named
|
||||
branch doesn't check `enabled`, while Settings treats an AI that is off as unusable
|
||||
- [x] Bound accumulated data across all lines of one server-sent event, so short lines cannot
|
||||
grow a streamed API response without limit
|
||||
— claimed: Afloat16, branch bugfix/r5.5-bounded-sse-events-20261103
|
||||
|
||||
Reference in New Issue
Block a user