feat(orchestrator): create_skill action + wire per-project watchers (§14.3)
- domain: OrchestratorCommand::CreateSkill with optional scope field (defaults to Project, case-insensitive, UnknownScope on bad value) - application: dispatch CreateSkill through the CreateSkill use case - app-tauri: build OrchestratorService and start/stop per-project request watchers on open/create/close_project (ensure/stop_orchestrator_watch) - tests: domain validation, service dispatch, infra watcher e2e, app-tauri wiring lifecycle (idempotent, isolated, stop) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -32,8 +32,8 @@ use domain::{PtySize, SessionId};
|
||||
use uuid::Uuid;
|
||||
|
||||
use application::{
|
||||
CloseTerminal, CreateAgentFromScratch, LaunchAgent, ListAgents, OrchestratorService,
|
||||
TerminalSessions, UpdateAgentContext,
|
||||
CloseTerminal, CreateAgentFromScratch, CreateSkill, LaunchAgent, ListAgents,
|
||||
OrchestratorService, TerminalSessions, UpdateAgentContext,
|
||||
};
|
||||
use infrastructure::{process_request_file, OrchestratorResponse};
|
||||
|
||||
@ -305,12 +305,17 @@ fn build_service(contexts: FakeContexts) -> Arc<OrchestratorService> {
|
||||
let list = Arc::new(ListAgents::new(Arc::new(contexts.clone())));
|
||||
let close = Arc::new(CloseTerminal::new(Arc::new(FakePty), Arc::clone(&sessions)));
|
||||
let update = Arc::new(UpdateAgentContext::new(Arc::new(contexts)));
|
||||
let create_skill = Arc::new(CreateSkill::new(
|
||||
Arc::new(FakeSkills) as Arc<dyn SkillStore>,
|
||||
Arc::new(SeqIds(Mutex::new(1))),
|
||||
));
|
||||
Arc::new(OrchestratorService::new(
|
||||
create,
|
||||
launch,
|
||||
list,
|
||||
close,
|
||||
update,
|
||||
create_skill,
|
||||
Arc::clone(&profiles) as Arc<dyn ProfileStore>,
|
||||
sessions,
|
||||
))
|
||||
@ -350,6 +355,27 @@ async fn valid_spawn_request_succeeds_and_is_consumed() {
|
||||
assert_eq!(contexts.entries()[0].name, "dev-backend");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn valid_create_skill_request_succeeds_and_is_consumed() {
|
||||
let tmp = TempDir::new();
|
||||
let service = build_service(FakeContexts::new());
|
||||
|
||||
let req = tmp.0.join("skill-req.json");
|
||||
std::fs::write(
|
||||
&req,
|
||||
br##"{ "action": "create_skill", "name": "deploy", "context": "# Deploy steps" }"##,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let response = process_request_file(&req, &project(), &service).await;
|
||||
|
||||
assert!(response.ok, "expected ok, got {response:?}");
|
||||
assert_eq!(response.action.as_deref(), Some("create_skill"));
|
||||
// Request consumed; a response sibling written and marked ok.
|
||||
assert!(!req.exists(), "request file must be removed");
|
||||
assert!(read_response(&req).ok);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn invalid_json_request_yields_error_response() {
|
||||
let tmp = TempDir::new();
|
||||
|
||||
Reference in New Issue
Block a user