feat(ai-agent): custom agent CRUD + two-step probe#211
Merged
Conversation
Custom agents rely solely on PRIMARY KEY(id) for uniqueness. Builtin rows keep their stable ids; dropping the name-level unique constraint lets users create multiple custom agents with the same display name (PRD F-CAGENT-12).
Rename paves the way for the two-step probe (which + ACP initialize) landing in Task 4. Response becomes a tagged enum over success/fail_cli/fail_acp, matching the three Alert variants the frontend already renders.
Step 1 uses which()/where to resolve the command head on PATH. Step 2 spawns the CLI via spawn_for_sdk and runs the full ACP initialize handshake through AcpProtocol::connect, then drops the protocol to shut the SDK cleanly. The same function drives both the manual 'Test connection' button and the implicit test-on-save path.
…andle Reloads the in-memory snapshot from the repo and re-resolves PATH in one call. Used by the upcoming custom-agent CRUD handlers so newly created or deleted rows are visible to GET /api/agents immediately. repo_handle exposes the underlying repository Arc to the service layer so custom-agent CRUD can bypass the cache for write paths.
create/update/delete/set_enabled + try_connect_custom_agent. Create and update run the two-step probe before hitting the DB so broken configs never land in agent_metadata. Registry is re-hydrated after every mutation so GET /api/agents reflects changes immediately. The AIONUI_BYPASS_PROBE env var lets integration tests skip the spawn-based probe when no ACP CLI is installed.
POST /api/agents/custom
PUT /api/agents/custom/{id}
DELETE /api/agents/custom/{id}
PATCH /api/agents/{id}/enabled
POST /api/agents/custom/try-connect
Removes the old /api/agents/test endpoint and the Step-1-only probe
it used — the new probe module (custom_agent_probe) already owns the
two-step logic and is wired through the service layer.
Also fixes a pre-existing !Send bug in AgentRegistry::hydrate where
`debug!(rows = self.by_id.read().await.len(), ...)` held a non-Send
format_args value across an await point; replaced with a local
`row_count` variable computed before the write-lock await.
Covers create/update/delete/enable happy paths, empty-field rejection, 404/403 error paths, and the test-on-save CLI-not-found guard. Uses AIONUI_BYPASS_PROBE (gated behind test-support feature) for paths that don't test the probe itself. Uses tokio::sync::Mutex to serialize env-var mutation across concurrent test threads. Also fixes a pre-existing failure in acp_e2e.rs: the try-connect endpoint was renamed from /api/agents/test to /api/agents/custom/try-connect in Task 2, and always returns HTTP 200 with a tagged-enum body rather than 400 for CLI-not-found.
Sweeping rustfmt pass over all files touched by the Custom Agent backend work (Tasks 1-8). Ensures 'just push' / CI format gate passes. No semantic changes.
Aligns custom agent row id generation with the project-wide id policy used elsewhere (conversations, etc.) instead of a raw UUID v4 string.
Race the ACP `initialize` handshake against the child process's exit watcher. A misconfigured CLI (e.g. `bun acp` with no script) prints to stderr and exits ~5ms after spawn, but `AcpProtocol::connect` has no visibility into that and blocks on its internal 30s timeout waiting for an `initialize` reply that will never arrive — by which point the HTTP request has already timed out at 30s in axum. With the race, we detect the exit immediately, surface the stderr tail alongside the exit status, and return `FailAcp` to the caller so the Settings panel can render its `testConnectionFailAcp` alert without hanging the form for half a minute.
Row-scoped rows (custom ACP / remote) are identified by agent_id; the frontend collapses them to a shared `custom` slot string for config namespacing purposes. When the caller supplies both agent_id and a slot string, the factory now trusts the catalog row's `backend` — even if it is None — so downstream consumers (MCP injection, preset-context composition) do not mistake the slot name for a real vendor label.
- Migration 012_agent_metadata_drop_name_unique.sql collided with main's 012_claude_behavior_policy_flags.sql (both version=12 for sqlx), causing "migration 12 was previously applied but has been modified" on existing DBs once both files are present. - Rename to 013_ and update the file header comment. - Also apply rustfmt to one block in webui_change_username_handler.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
/api/agents/*routes and backed by a newservice_custommodule.CustomAgentUpsertRequest,TryConnectCustomAgent, etc.) inaionui-api-typesand wires them throughaionui-ai-agentroutes/services.UNIQUE(agent_source, name)so users can create multiple custom agents sharing a display name (migration 013, renumbered from 012 to avoid collision with012_claude_behavior_policy_flagsalready on main).AgentRegistry::invalidate_and_rehydrate+repo_handle,generate_short_idfor custom agent IDs, fast-fail on probe when the CLI exits early, and agent_id overriding backend slot in ACP build extras.Commits
feat(db): dropUNIQUE(agent_source, name)on agent_metadatafeat(api-types):CustomAgentUpsertRequest+ renameTestCustomAgent→TryConnectCustomAgentfeat(ai-agent): two-step probe, CRUD service + routes, registry invalidate_and_rehydratetest(app): E2E for CRUD + try-connectfix(ai-agent): fail probe fast when CLI exits early, let agent_id override backend slotrefactor: short-id generator, rustfmt cleanupfix(db): renumber migration 012 → 013 to resolve collision with main's012_claude_behavior_policy_flagsTest plan
cargo test -p aionui-ai-agent -p aionui-api-types -p aionui-appcargo clippy --workspace -- -D warnings_sqlx_migrationscleanup; note in release notes)