Remove Scenes, group beats by multiple characters; strip comments repo-wide
Drop the Scene entity/grouping in favor of chapters carrying prose directly and beats belonging to many characters. Add markdown editor + character multi-select components to the web client. Remove all XML doc and inline comments across the touched C#/TS/CSS files in favor of self-documenting names, and record that convention in CLAUDE.md. Add .mcp.json (local MCP server config, no secrets) and ignore .idea/.
This commit is contained in:
@@ -10,10 +10,6 @@ using Novelly.Api.Projects;
|
||||
|
||||
namespace Novelly.Api.Agent;
|
||||
|
||||
/// <summary>
|
||||
/// The embedded writing agent. Runs the tool-use loop against the model, persists the
|
||||
/// conversation, and returns the finished turn together with a record of what it changed.
|
||||
/// </summary>
|
||||
public class NovelAgentService(
|
||||
INovelDbContext db,
|
||||
IAgentModelClient model,
|
||||
@@ -41,7 +37,6 @@ public class NovelAgentService(
|
||||
.ToListAsync(ct);
|
||||
}
|
||||
|
||||
/// <summary>Null when no conversation has this id — a lookup miss is expected, not exceptional.</summary>
|
||||
public async Task<AgentConversation?> GetConversationAsync(Guid conversationId, CancellationToken ct = default)
|
||||
{
|
||||
Guard.Default(conversationId, nameof(conversationId));
|
||||
@@ -51,7 +46,6 @@ public class NovelAgentService(
|
||||
return await FindConversationAsync(conversationId, ct);
|
||||
}
|
||||
|
||||
/// <summary>True if a conversation was deleted; false if no conversation had this id.</summary>
|
||||
public async Task<bool> DeleteConversationAsync(Guid conversationId, CancellationToken ct = default)
|
||||
{
|
||||
Guard.Default(conversationId, nameof(conversationId));
|
||||
@@ -69,12 +63,6 @@ public class NovelAgentService(
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a message to the agent and runs it to completion, executing any tools it
|
||||
/// calls along the way. Returns the assistant's final turn. Null when no project has
|
||||
/// this id, or the request names a conversation that does not exist — a lookup miss
|
||||
/// is expected, not exceptional.
|
||||
/// </summary>
|
||||
public async Task<AgentMessage?> SendMessageAsync(
|
||||
Guid projectId, SendAgentMessageRequest request, CancellationToken ct = default)
|
||||
{
|
||||
@@ -96,8 +84,6 @@ public class NovelAgentService(
|
||||
AgentConversation conversation;
|
||||
if (request.ConversationId is { } id)
|
||||
{
|
||||
// The id came from the request body, not the route — an unknown id here
|
||||
// is bad input to this call, not a direct "fetch conversation" lookup.
|
||||
var found = await FindConversationAsync(id, ct);
|
||||
if (found is null)
|
||||
{
|
||||
@@ -111,9 +97,6 @@ public class NovelAgentService(
|
||||
conversation = StartConversation(projectId, request.Message);
|
||||
}
|
||||
|
||||
// Persist the user's turn before running the loop. The tools save through the
|
||||
// same DbContext, so leaving this pending would entangle it with their writes —
|
||||
// and recording the question even if the model call fails is the behaviour we want.
|
||||
await AppendMessageAsync(conversation, AgentRole.User, request.Message, null, ct);
|
||||
|
||||
var systemPrompt = BuildSystemPrompt(project);
|
||||
@@ -141,9 +124,6 @@ public class NovelAgentService(
|
||||
break;
|
||||
}
|
||||
|
||||
// Echo the assistant's turn back verbatim, then answer every tool_use block in a
|
||||
// single user turn — splitting the results would train the model out of
|
||||
// requesting tools in parallel.
|
||||
transcript.Add(AgentChatMessage.Assistant(response.Content));
|
||||
|
||||
var results = new List<AgentContentBlock>();
|
||||
@@ -182,11 +162,6 @@ public class NovelAgentService(
|
||||
return reply;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends a turn and commits it. Messages are added to the set directly rather than
|
||||
/// through the parent's collection so their insert never depends on EF discovering
|
||||
/// the graph change at an inconvenient moment.
|
||||
/// </summary>
|
||||
private async Task<AgentMessage> AppendMessageAsync(
|
||||
AgentConversation conversation, AgentRole role, string content, string? toolCallsJson, CancellationToken ct)
|
||||
{
|
||||
@@ -206,9 +181,6 @@ public class NovelAgentService(
|
||||
|
||||
await db.SaveChangesAsync(ct);
|
||||
|
||||
// EF's relationship fixup normally puts the message into the parent's collection
|
||||
// once both are tracked. Guard rather than assume, since the sequence number of
|
||||
// the next turn is derived from it.
|
||||
if (!conversation.Messages.Contains(message))
|
||||
{
|
||||
conversation.Messages.Add(message);
|
||||
@@ -247,11 +219,6 @@ public class NovelAgentService(
|
||||
return conversation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replays the stored conversation as plain text turns. Tool calls are not replayed —
|
||||
/// the agent re-reads current state through its tools, which is more reliable than
|
||||
/// trusting a transcript of edits that may since have been changed in the UI.
|
||||
/// </summary>
|
||||
private static List<AgentChatMessage> BuildTranscript(AgentConversation conversation) =>
|
||||
[
|
||||
.. conversation.Messages
|
||||
@@ -273,8 +240,8 @@ public class NovelAgentService(
|
||||
return $"""
|
||||
You are a developmental editor and writing partner embedded in the software the
|
||||
writer is using to plan their novel. You have tools that read and write the
|
||||
project's real data: the brief, character dossiers, the outline tree, chapters
|
||||
and scenes.
|
||||
project's real data: the brief, character dossiers, the outline (beats) and each
|
||||
chapter's drafted prose.
|
||||
|
||||
The project you are working on:
|
||||
{brief}
|
||||
@@ -291,8 +258,8 @@ public class NovelAgentService(
|
||||
- Prefer structural help — where a beat lands, whether a want and a need are
|
||||
genuinely in tension, what the outline is missing — over line-level polish,
|
||||
unless the writer asks for prose.
|
||||
- When drafting prose into a scene, match the voice already established in the
|
||||
project. Write the scene, then stop; do not append notes about your choices.
|
||||
- When drafting a chapter's prose, match the voice already established in the
|
||||
project. Write the chapter, then stop; do not append notes about your choices.
|
||||
- Destructive operations (deleting outline nodes) need the writer's explicit
|
||||
go-ahead first.
|
||||
|
||||
@@ -300,7 +267,6 @@ public class NovelAgentService(
|
||||
""";
|
||||
}
|
||||
|
||||
/// <summary>Derives a conversation title from its opening message.</summary>
|
||||
private static string Summarise(string message)
|
||||
{
|
||||
var trimmed = message.Trim().ReplaceLineEndings(" ");
|
||||
|
||||
Reference in New Issue
Block a user