Add outline import feature; drop Dto naming, map entities at the API boundary
Services now return entities; endpoints (and the agent toolsets) map to *Response records instead of services building wire DTOs themselves. Also brings in the outline-import agent, MCP tool, ledger and web dialog that were already in progress on disk.
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
# Web-triggered outline import — implementation summary
|
||||
|
||||
Implements the plan in `outline_import_agent_plan.md`.
|
||||
|
||||
## Backend (`src/Novelly.Api/Imports/`)
|
||||
|
||||
- `ImportJob` (+ EF migration `AddImportJobs`), `ImportPaths` (ledger read/write, root-containment path resolution, chapter-file counting — shared by the service and the toolset), `ImportDtos`, `ImportService`, `ImportEndpoints` (`POST /api/imports/inspect`, `POST /api/imports`, `GET /api/imports/{id}`).
|
||||
- `ImportAgentToolset` — root-scoped `list_source_files`/`read_source_file`/`read_ledger`/`write_ledger` (the only write capability, enforced at the tool layer, not just the prompt) plus `create_project`/`update_project_brief`/`create_character`/`update_character`/`create_chapter`/`update_chapter`/`create_beat`/`add_arc_stage` wrapping the same application services the REST API and chat agent use.
|
||||
- `ImportAgentService` — system prompt ported from `.claude/agents/outline-importer.md`'s passes; runs bounded turns (`AgentOptions.ImportMaxIterationsPerTurn` = 40 tool calls/turn, `ImportMaxTurns` = 8 turns), re-reading the ledger after each turn as ground truth for "done" rather than trusting the model.
|
||||
- `ImportJobRunner : BackgroundService` — the app's first background-job infra, a `Channel<Guid>`-backed queue drained in its own DI scope per job.
|
||||
|
||||
Wired into `NovellyServiceRegistration`/`Program.cs`. All 98 backend tests pass, including 14 new ones (`ImportServiceTests`, `ImportAgentToolsetTests`) covering inspect states, job dedup, force-restart's project+ledger deletion, path-traversal rejection, and the ledger-only write scope.
|
||||
|
||||
## Frontend (`src/Novelly.Web`)
|
||||
|
||||
- `api/types.ts` / `api/hooks.ts`: `ImportJob`/`ImportInspection` types, `useInspectImport`/`useStartImport`/`useImportJob` (polling, stops on terminal status).
|
||||
- `components/ImportDialog.tsx`: path input → Check → Start/Resume/Delete-and-reimport → progress polling → done. Wired into `OverviewPage.tsx` (new aside card) and `ProjectsPage.tsx` (button next to "New novel").
|
||||
|
||||
**Scope note:** import always creates its own project (it never populates the project you're already viewing) — the Overview card's copy says so explicitly and navigates to the new project on completion, since threading an "import into this existing project" mode through the toolset/ledger format wasn't part of the approved plan.
|
||||
|
||||
## Verified live (not just tests)
|
||||
|
||||
- Restarted the Aspire AppHost to pick up the migration + new code.
|
||||
- `POST /api/imports/inspect` against the real `examples/blade-itself` folder (partially imported by the CLI subagent earlier this session) correctly reported `Resumable`, 5/46 chapters, `["project","characters"]` passes — matches the CLI's own ledger exactly, confirming ledger-format compatibility between the two entry points.
|
||||
- `POST /api/imports` + polling against a synthetic one-chapter outline exercised the full endpoint → queue → background-job → status-transition path for real over HTTP; no `ANTHROPIC_API_KEY` is configured in this environment, so it terminated as `Failed` with the expected `AgentNotConfiguredException` message rather than a real import — this is the correct behavior for an unconfigured key, but it means the model-driven happy path (actually calling Claude and writing chapters/characters) has **not** been verified live. That needs a configured key and is worth a manual pass before considering this done-done.
|
||||
Reference in New Issue
Block a user