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:
@ -19,7 +19,12 @@ import {
|
||||
fireEvent,
|
||||
} from "@testing-library/react";
|
||||
|
||||
import { MockAgentGateway, MockProfileGateway, MockTemplateGateway } from "@/adapters/mock";
|
||||
import {
|
||||
MockAgentGateway,
|
||||
MockProfileGateway,
|
||||
MockSystemGateway,
|
||||
MockTemplateGateway,
|
||||
} from "@/adapters/mock";
|
||||
import type { Gateways } from "@/ports";
|
||||
import { DIProvider } from "@/app/di";
|
||||
import { AgentsPanel } from "./AgentsPanel";
|
||||
@ -384,3 +389,45 @@ describe("MockAgentGateway (unit)", () => {
|
||||
expect(listB).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("AgentsPanel live refresh on domain events", () => {
|
||||
it("refreshes the list when an `agentLaunched` event fires (out-of-band creation)", async () => {
|
||||
const agent = new MockAgentGateway();
|
||||
const profile = new MockProfileGateway();
|
||||
const system = new MockSystemGateway();
|
||||
const template = new MockTemplateGateway(agent);
|
||||
const gateways = { agent, profile, system, template } as unknown as Gateways;
|
||||
|
||||
render(
|
||||
<DIProvider gateways={gateways}>
|
||||
<AgentsPanel projectId={PROJECT_ID} projectRoot="/home/me/proj" />
|
||||
</DIProvider>,
|
||||
);
|
||||
|
||||
// Initially empty.
|
||||
await waitFor(() => expect(screen.getByText("No agents yet.")).toBeTruthy());
|
||||
|
||||
// Simulate the orchestrator creating an agent directly in the store
|
||||
// (bypassing the UI's createAgent path), then emitting `agentLaunched`.
|
||||
const created = await agent.createAgent(PROJECT_ID, {
|
||||
name: "Orchestrated",
|
||||
profileId: "p1",
|
||||
});
|
||||
system.emit({
|
||||
type: "agentLaunched",
|
||||
agentId: created.id,
|
||||
sessionId: "sess-1",
|
||||
});
|
||||
|
||||
// The panel must reflect the new agent without any manual reload.
|
||||
expect(await screen.findByText("Orchestrated")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("does not crash when the system gateway is absent", async () => {
|
||||
// The existing renderPanel helper injects no `system` gateway; the
|
||||
// subscription effect must no-op rather than throw.
|
||||
renderPanel();
|
||||
await waitForIdle();
|
||||
expect(screen.getByText("No agents yet.")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user