package tools import ( "context" "encoding/json" "fmt" "log" "github.com/voocel/agentcore/schema" "github.com/voocel/ainovel-cli/domain" "github.com/voocel/ainovel-cli/state" ) // CommitChapterTool 提交章节:加载正文 → 保存终稿 → 生成摘要 → 更新状态 → 更新进度。 type CommitChapterTool struct { store *state.Store } func NewCommitChapterTool(store *state.Store) *CommitChapterTool { return &CommitChapterTool{store: store} } func (t *CommitChapterTool) Name() string { return "commit_chapter" } func (t *CommitChapterTool) Description() string { return "提交章节终稿。加载草稿正文,保存为终稿,同时更新时间线、伏笔、关系、角色状态。返回结构化信号" } func (t *CommitChapterTool) Label() string { return "提交章节" } func (t *CommitChapterTool) Schema() map[string]any { timelineSchema := schema.Object( schema.Property("time", schema.String("故事内时间")).Required(), schema.Property("event", schema.String("事件描述")).Required(), schema.Property("characters", schema.Array("涉及角色", schema.String(""))), ) foreshadowSchema := schema.Object( schema.Property("id", schema.String("伏笔 ID")).Required(), schema.Property("action", schema.Enum("操作", "plant", "advance", "resolve")).Required(), schema.Property("description", schema.String("伏笔描述(仅 plant 时必需)")), ) relationshipSchema := schema.Object( schema.Property("character_a", schema.String("角色 A")).Required(), schema.Property("character_b", schema.String("角色 B")).Required(), schema.Property("relation", schema.String("当前关系描述")).Required(), ) stateChangeSchema := schema.Object( schema.Property("entity", schema.String("角色名或实体名")).Required(), schema.Property("field", schema.String("变化属性")).Required(), schema.Property("old_value", schema.String("变化前的值")), schema.Property("new_value", schema.String("变化后的值")).Required(), schema.Property("reason", schema.String("变化原因")), ) feedbackSchema := schema.Object( schema.Property("deviation", schema.String("偏离大纲的描述")).Required(), schema.Property("suggestion", schema.String("对后续大纲的调整建议")).Required(), ) return schema.Object( schema.Property("chapter", schema.Int("章节号")).Required(), schema.Property("summary", schema.String("本章内容摘要(200字以内)")).Required(), schema.Property("characters", schema.Array("本章出场角色名", schema.String(""))).Required(), schema.Property("key_events", schema.Array("本章关键事件", schema.String(""))).Required(), schema.Property("timeline_events", schema.Array("本章时间线事件", timelineSchema)), schema.Property("foreshadow_updates", schema.Array("伏笔操作", foreshadowSchema)), schema.Property("relationship_changes", schema.Array("关系变化", relationshipSchema)), schema.Property("state_changes", schema.Array("角色/实体状态变化", stateChangeSchema)), schema.Property("hook_type", schema.Enum("章末钩子类型", "crisis", "mystery", "desire", "emotion", "choice")), schema.Property("dominant_strand", schema.Enum("本章主导叙事线", "quest", "fire", "constellation")), schema.Property("feedback", feedbackSchema), ) } func (t *CommitChapterTool) Execute(_ context.Context, args json.RawMessage) (json.RawMessage, error) { var a struct { Chapter int `json:"chapter"` Summary string `json:"summary"` Characters []string `json:"characters"` KeyEvents []string `json:"key_events"` TimelineEvents []domain.TimelineEvent `json:"timeline_events"` ForeshadowUpdates []domain.ForeshadowUpdate `json:"foreshadow_updates"` RelationshipChanges []domain.RelationshipEntry `json:"relationship_changes"` StateChanges []domain.StateChange `json:"state_changes"` HookType string `json:"hook_type"` DominantStrand string `json:"dominant_strand"` Feedback *domain.OutlineFeedback `json:"feedback"` } if err := json.Unmarshal(args, &a); err != nil { return nil, fmt.Errorf("invalid args: %w", err) } if a.Chapter <= 0 { return nil, fmt.Errorf("chapter must be > 0") } if err := t.store.ValidateChapterCommit(a.Chapter); err != nil { return nil, err } // 1. 加载章节正文 content, wordCount, err := t.store.LoadChapterContent(a.Chapter) if err != nil { return nil, fmt.Errorf("load chapter content: %w", err) } if content == "" { return nil, fmt.Errorf("no content found for chapter %d", a.Chapter) } // 2. 保存终稿 if err := t.store.SaveFinalChapter(a.Chapter, content); err != nil { return nil, fmt.Errorf("save final chapter: %w", err) } // 3. 保存摘要 summary := domain.ChapterSummary{ Chapter: a.Chapter, Summary: a.Summary, Characters: a.Characters, KeyEvents: a.KeyEvents, } if err := t.store.SaveSummary(summary); err != nil { return nil, fmt.Errorf("save summary: %w", err) } // 4. 更新状态增量 if len(a.TimelineEvents) > 0 { for i := range a.TimelineEvents { a.TimelineEvents[i].Chapter = a.Chapter } if err := t.store.AppendTimelineEvents(a.TimelineEvents); err != nil { return nil, fmt.Errorf("append timeline: %w", err) } } if len(a.ForeshadowUpdates) > 0 { if err := t.store.UpdateForeshadow(a.Chapter, a.ForeshadowUpdates); err != nil { return nil, fmt.Errorf("update foreshadow: %w", err) } } if len(a.RelationshipChanges) > 0 { for i := range a.RelationshipChanges { a.RelationshipChanges[i].Chapter = a.Chapter } if err := t.store.UpdateRelationships(a.RelationshipChanges); err != nil { return nil, fmt.Errorf("update relationships: %w", err) } } if len(a.StateChanges) > 0 { for i := range a.StateChanges { a.StateChanges[i].Chapter = a.Chapter } if err := t.store.AppendStateChanges(a.StateChanges); err != nil { return nil, fmt.Errorf("append state changes: %w", err) } } // 5. 更新进度 if err := t.store.MarkChapterComplete(a.Chapter, wordCount, a.HookType, a.DominantStrand); err != nil { return nil, fmt.Errorf("mark chapter complete: %w", err) } // 6. 判断是否需要审阅 progress, err := t.store.LoadProgress() if err != nil { return nil, fmt.Errorf("load progress: %w", err) } completedCount := 0 if progress != nil { completedCount = len(progress.CompletedChapters) } // 6b. 长篇模式:弧级边界检测 var arcEnd, volumeEnd bool var vol, arc int if progress != nil && progress.Layered { boundary, bErr := t.store.CheckArcBoundary(a.Chapter) if bErr != nil { log.Printf("[commit] 弧边界检测失败(chapter=%d): %v", a.Chapter, bErr) } else if boundary != nil { arcEnd = boundary.IsArcEnd volumeEnd = boundary.IsVolumeEnd vol = boundary.Volume arc = boundary.Arc _ = t.store.UpdateVolumeArc(vol, arc) } } var reviewRequired bool var reviewReason string if progress != nil && progress.Layered { reviewRequired, reviewReason = domain.ShouldArcReview(arcEnd, volumeEnd, vol, arc) } else { reviewRequired, reviewReason = domain.ShouldReview(completedCount) } // 7. 构造结构化信号 result := domain.CommitResult{ Chapter: a.Chapter, Committed: true, WordCount: wordCount, NextChapter: a.Chapter + 1, ReviewRequired: reviewRequired, ReviewReason: reviewReason, HookType: a.HookType, DominantStrand: a.DominantStrand, Feedback: a.Feedback, ArcEnd: arcEnd, VolumeEnd: volumeEnd, Volume: vol, Arc: arc, } // 8. 写入信号文件 if err := t.store.SaveLastCommit(result); err != nil { return nil, fmt.Errorf("save commit signal: %w", err) } // 9. 清除进度中间状态 if err := t.store.ClearInProgress(); err != nil { return nil, fmt.Errorf("clear in-progress: %w", err) } return json.Marshal(result) }