@actalk/inkos-core 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/architect.d.ts +6 -1
- package/dist/agents/architect.d.ts.map +1 -1
- package/dist/agents/architect.js +362 -83
- package/dist/agents/architect.js.map +1 -1
- package/dist/agents/chapter-analyzer.d.ts +6 -0
- package/dist/agents/chapter-analyzer.d.ts.map +1 -1
- package/dist/agents/chapter-analyzer.js +220 -17
- package/dist/agents/chapter-analyzer.js.map +1 -1
- package/dist/agents/composer.d.ts +28 -0
- package/dist/agents/composer.d.ts.map +1 -0
- package/dist/agents/composer.js +154 -0
- package/dist/agents/composer.js.map +1 -0
- package/dist/agents/consolidator.d.ts.map +1 -1
- package/dist/agents/consolidator.js +17 -8
- package/dist/agents/consolidator.js.map +1 -1
- package/dist/agents/continuity.d.ts +10 -0
- package/dist/agents/continuity.d.ts.map +1 -1
- package/dist/agents/continuity.js +312 -133
- package/dist/agents/continuity.js.map +1 -1
- package/dist/agents/en-prompt-sections.d.ts.map +1 -1
- package/dist/agents/en-prompt-sections.js +1 -0
- package/dist/agents/en-prompt-sections.js.map +1 -1
- package/dist/agents/length-normalizer.d.ts +32 -0
- package/dist/agents/length-normalizer.d.ts.map +1 -0
- package/dist/agents/length-normalizer.js +156 -0
- package/dist/agents/length-normalizer.js.map +1 -0
- package/dist/agents/planner.d.ts +42 -0
- package/dist/agents/planner.d.ts.map +1 -0
- package/dist/agents/planner.js +382 -0
- package/dist/agents/planner.js.map +1 -0
- package/dist/agents/post-write-validator.d.ts +6 -1
- package/dist/agents/post-write-validator.d.ts.map +1 -1
- package/dist/agents/post-write-validator.js +88 -2
- package/dist/agents/post-write-validator.js.map +1 -1
- package/dist/agents/reviser.d.ts +10 -1
- package/dist/agents/reviser.d.ts.map +1 -1
- package/dist/agents/reviser.js +151 -36
- package/dist/agents/reviser.js.map +1 -1
- package/dist/agents/rules-reader.d.ts +1 -0
- package/dist/agents/rules-reader.d.ts.map +1 -1
- package/dist/agents/rules-reader.js +13 -0
- package/dist/agents/rules-reader.js.map +1 -1
- package/dist/agents/settler-delta-parser.d.ts +7 -0
- package/dist/agents/settler-delta-parser.d.ts.map +1 -0
- package/dist/agents/settler-delta-parser.js +35 -0
- package/dist/agents/settler-delta-parser.js.map +1 -0
- package/dist/agents/settler-prompts.d.ts +2 -0
- package/dist/agents/settler-prompts.d.ts.map +1 -1
- package/dist/agents/settler-prompts.js +77 -63
- package/dist/agents/settler-prompts.js.map +1 -1
- package/dist/agents/writer-parser.d.ts +3 -2
- package/dist/agents/writer-parser.d.ts.map +1 -1
- package/dist/agents/writer-parser.js +44 -13
- package/dist/agents/writer-parser.js.map +1 -1
- package/dist/agents/writer-prompts.d.ts +2 -1
- package/dist/agents/writer-prompts.d.ts.map +1 -1
- package/dist/agents/writer-prompts.js +65 -21
- package/dist/agents/writer-prompts.js.map +1 -1
- package/dist/agents/writer.d.ts +28 -1
- package/dist/agents/writer.d.ts.map +1 -1
- package/dist/agents/writer.js +426 -67
- package/dist/agents/writer.js.map +1 -1
- package/dist/index.d.ts +18 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -2
- package/dist/index.js.map +1 -1
- package/dist/llm/provider.d.ts +1 -0
- package/dist/llm/provider.d.ts.map +1 -1
- package/dist/llm/provider.js +19 -6
- package/dist/llm/provider.js.map +1 -1
- package/dist/models/chapter.d.ts +71 -0
- package/dist/models/chapter.d.ts.map +1 -1
- package/dist/models/chapter.js +3 -0
- package/dist/models/chapter.js.map +1 -1
- package/dist/models/input-governance.d.ts +351 -0
- package/dist/models/input-governance.d.ts.map +1 -0
- package/dist/models/input-governance.js +78 -0
- package/dist/models/input-governance.js.map +1 -0
- package/dist/models/length-governance.d.ts +93 -0
- package/dist/models/length-governance.d.ts.map +1 -0
- package/dist/models/length-governance.js +34 -0
- package/dist/models/length-governance.js.map +1 -0
- package/dist/models/project.d.ts +5 -0
- package/dist/models/project.d.ts.map +1 -1
- package/dist/models/project.js +2 -0
- package/dist/models/project.js.map +1 -1
- package/dist/models/runtime-state.d.ts +521 -0
- package/dist/models/runtime-state.d.ts.map +1 -0
- package/dist/models/runtime-state.js +78 -0
- package/dist/models/runtime-state.js.map +1 -0
- package/dist/pipeline/agent.d.ts +2 -1
- package/dist/pipeline/agent.d.ts.map +1 -1
- package/dist/pipeline/agent.js +90 -5
- package/dist/pipeline/agent.js.map +1 -1
- package/dist/pipeline/runner.d.ts +65 -1
- package/dist/pipeline/runner.d.ts.map +1 -1
- package/dist/pipeline/runner.js +1029 -73
- package/dist/pipeline/runner.js.map +1 -1
- package/dist/state/manager.d.ts +14 -0
- package/dist/state/manager.d.ts.map +1 -1
- package/dist/state/manager.js +114 -0
- package/dist/state/manager.js.map +1 -1
- package/dist/state/memory-db.d.ts +15 -0
- package/dist/state/memory-db.d.ts.map +1 -1
- package/dist/state/memory-db.js +119 -10
- package/dist/state/memory-db.js.map +1 -1
- package/dist/state/runtime-state-store.d.ts +23 -0
- package/dist/state/runtime-state-store.d.ts.map +1 -0
- package/dist/state/runtime-state-store.js +100 -0
- package/dist/state/runtime-state-store.js.map +1 -0
- package/dist/state/state-bootstrap.d.ts +19 -0
- package/dist/state/state-bootstrap.d.ts.map +1 -0
- package/dist/state/state-bootstrap.js +394 -0
- package/dist/state/state-bootstrap.js.map +1 -0
- package/dist/state/state-projections.d.ts +5 -0
- package/dist/state/state-projections.d.ts.map +1 -0
- package/dist/state/state-projections.js +164 -0
- package/dist/state/state-projections.js.map +1 -0
- package/dist/state/state-reducer.d.ts +12 -0
- package/dist/state/state-reducer.d.ts.map +1 -0
- package/dist/state/state-reducer.js +146 -0
- package/dist/state/state-reducer.js.map +1 -0
- package/dist/state/state-validator.d.ts +12 -0
- package/dist/state/state-validator.d.ts.map +1 -0
- package/dist/state/state-validator.js +56 -0
- package/dist/state/state-validator.js.map +1 -0
- package/dist/utils/chapter-splitter.d.ts +2 -0
- package/dist/utils/chapter-splitter.d.ts.map +1 -1
- package/dist/utils/chapter-splitter.js +22 -4
- package/dist/utils/chapter-splitter.js.map +1 -1
- package/dist/utils/config-loader.d.ts +3 -1
- package/dist/utils/config-loader.d.ts.map +1 -1
- package/dist/utils/config-loader.js +14 -3
- package/dist/utils/config-loader.js.map +1 -1
- package/dist/utils/context-filter.js +1 -1
- package/dist/utils/context-filter.js.map +1 -1
- package/dist/utils/governed-context.d.ts +7 -0
- package/dist/utils/governed-context.d.ts.map +1 -0
- package/dist/utils/governed-context.js +22 -0
- package/dist/utils/governed-context.js.map +1 -0
- package/dist/utils/governed-working-set.d.ts +18 -0
- package/dist/utils/governed-working-set.d.ts.map +1 -0
- package/dist/utils/governed-working-set.js +295 -0
- package/dist/utils/governed-working-set.js.map +1 -0
- package/dist/utils/hook-governance.d.ts +26 -0
- package/dist/utils/hook-governance.d.ts.map +1 -0
- package/dist/utils/hook-governance.js +128 -0
- package/dist/utils/hook-governance.js.map +1 -0
- package/dist/utils/hook-health.d.ts +14 -0
- package/dist/utils/hook-health.d.ts.map +1 -0
- package/dist/utils/hook-health.js +68 -0
- package/dist/utils/hook-health.js.map +1 -0
- package/dist/utils/length-metrics.d.ts +10 -0
- package/dist/utils/length-metrics.d.ts.map +1 -0
- package/dist/utils/length-metrics.js +85 -0
- package/dist/utils/length-metrics.js.map +1 -0
- package/dist/utils/long-span-fatigue.d.ts +28 -0
- package/dist/utils/long-span-fatigue.d.ts.map +1 -0
- package/dist/utils/long-span-fatigue.js +359 -0
- package/dist/utils/long-span-fatigue.js.map +1 -0
- package/dist/utils/memory-retrieval.d.ts +39 -0
- package/dist/utils/memory-retrieval.d.ts.map +1 -0
- package/dist/utils/memory-retrieval.js +574 -0
- package/dist/utils/memory-retrieval.js.map +1 -0
- package/dist/utils/spot-fix-patches.d.ts +14 -0
- package/dist/utils/spot-fix-patches.d.ts.map +1 -0
- package/dist/utils/spot-fix-patches.js +75 -0
- package/dist/utils/spot-fix-patches.js.map +1 -0
- package/package.json +1 -1
package/dist/agents/writer.js
CHANGED
|
@@ -2,19 +2,36 @@ import { BaseAgent } from "./base.js";
|
|
|
2
2
|
import { buildWriterSystemPrompt } from "./writer-prompts.js";
|
|
3
3
|
import { buildSettlerSystemPrompt, buildSettlerUserPrompt } from "./settler-prompts.js";
|
|
4
4
|
import { buildObserverSystemPrompt, buildObserverUserPrompt } from "./observer-prompts.js";
|
|
5
|
+
import { parseSettlerDeltaOutput } from "./settler-delta-parser.js";
|
|
5
6
|
import { parseSettlementOutput } from "./settler-parser.js";
|
|
6
7
|
import { readGenreProfile, readBookRules } from "./rules-reader.js";
|
|
7
|
-
import { validatePostWrite } from "./post-write-validator.js";
|
|
8
|
+
import { validatePostWrite, detectCrossChapterRepetition } from "./post-write-validator.js";
|
|
8
9
|
import { analyzeAITells } from "./ai-tells.js";
|
|
10
|
+
import { buildLengthSpec } from "../utils/length-metrics.js";
|
|
9
11
|
import { filterHooks, filterSummaries, filterSubplots, filterEmotionalArcs, filterCharacterMatrix } from "../utils/context-filter.js";
|
|
12
|
+
import { buildGovernedMemoryEvidenceBlocks } from "../utils/governed-context.js";
|
|
13
|
+
import { buildGovernedCharacterMatrixWorkingSet, buildGovernedHookWorkingSet, mergeCharacterMatrixMarkdown, mergeTableMarkdownByKey, } from "../utils/governed-working-set.js";
|
|
10
14
|
import { extractPOVFromOutline, filterMatrixByPOV, filterHooksByPOV } from "../utils/pov-filter.js";
|
|
11
15
|
import { parseCreativeOutput } from "./writer-parser.js";
|
|
16
|
+
import { buildRuntimeStateArtifacts, saveRuntimeStateSnapshot } from "../state/runtime-state-store.js";
|
|
17
|
+
import { parsePendingHooksMarkdown } from "../utils/memory-retrieval.js";
|
|
18
|
+
import { analyzeHookHealth } from "../utils/hook-health.js";
|
|
19
|
+
import { buildEnglishVarianceBrief } from "../utils/long-span-fatigue.js";
|
|
12
20
|
import { readFile, writeFile, mkdir, readdir } from "node:fs/promises";
|
|
13
21
|
import { join } from "node:path";
|
|
14
22
|
export class WriterAgent extends BaseAgent {
|
|
15
23
|
get name() {
|
|
16
24
|
return "writer";
|
|
17
25
|
}
|
|
26
|
+
localize(language, messages) {
|
|
27
|
+
return language === "en" ? messages.en : messages.zh;
|
|
28
|
+
}
|
|
29
|
+
logInfo(language, messages) {
|
|
30
|
+
this.ctx.logger?.info(this.localize(language, messages));
|
|
31
|
+
}
|
|
32
|
+
logWarn(language, messages) {
|
|
33
|
+
this.ctx.logger?.warn(this.localize(language, messages));
|
|
34
|
+
}
|
|
18
35
|
async writeChapter(input) {
|
|
19
36
|
const { book, bookDir, chapterNumber } = input;
|
|
20
37
|
const [storyBible, volumeOutline, styleGuide, currentState, ledger, hooks, chapterSummaries, subplotBoard, emotionalArcs, characterMatrix, styleProfileRaw, parentCanon, fanficCanonRaw,] = await Promise.all([
|
|
@@ -45,6 +62,18 @@ export class WriterAgent extends BaseAgent {
|
|
|
45
62
|
const relevantSummaries = this.findRelevantSummaries(chapterSummaries, volumeOutline, chapterNumber);
|
|
46
63
|
const hasParentCanon = parentCanon !== "(文件尚未创建)";
|
|
47
64
|
const hasFanficCanon = fanficCanonRaw !== "(文件尚未创建)";
|
|
65
|
+
const resolvedLanguage = book.language ?? genreProfile.language;
|
|
66
|
+
const targetWords = input.lengthSpec?.target ?? input.wordCountOverride ?? book.chapterWordCount;
|
|
67
|
+
const resolvedLengthSpec = input.lengthSpec ?? buildLengthSpec(targetWords, resolvedLanguage);
|
|
68
|
+
const governedMemoryBlocks = input.contextPackage
|
|
69
|
+
? buildGovernedMemoryEvidenceBlocks(input.contextPackage)
|
|
70
|
+
: undefined;
|
|
71
|
+
const englishVarianceBrief = resolvedLanguage === "en"
|
|
72
|
+
? await buildEnglishVarianceBrief({
|
|
73
|
+
bookDir,
|
|
74
|
+
chapterNumber,
|
|
75
|
+
})
|
|
76
|
+
: null;
|
|
48
77
|
// Build fanfic context if fanfic_canon.md exists
|
|
49
78
|
const fanficContext = hasFanficCanon && bookRules?.fanficMode
|
|
50
79
|
? {
|
|
@@ -54,54 +83,95 @@ export class WriterAgent extends BaseAgent {
|
|
|
54
83
|
}
|
|
55
84
|
: undefined;
|
|
56
85
|
// ── Phase 1: Creative writing (temperature 0.7) ──
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
86
|
+
const creativeSystemPrompt = buildWriterSystemPrompt(book, genreProfile, bookRules, bookRulesBody, genreBody, styleGuide, styleFingerprint, chapterNumber, "creative", fanficContext, resolvedLanguage, input.chapterIntent ? "governed" : "legacy", resolvedLengthSpec);
|
|
87
|
+
const creativeUserPrompt = input.chapterIntent && input.contextPackage && input.ruleStack
|
|
88
|
+
? this.buildGovernedUserPrompt({
|
|
89
|
+
chapterNumber,
|
|
90
|
+
chapterIntent: input.chapterIntent,
|
|
91
|
+
contextPackage: input.contextPackage,
|
|
92
|
+
ruleStack: input.ruleStack,
|
|
93
|
+
trace: input.trace,
|
|
94
|
+
lengthSpec: resolvedLengthSpec,
|
|
95
|
+
language: book.language ?? genreProfile.language,
|
|
96
|
+
varianceBrief: englishVarianceBrief?.text,
|
|
97
|
+
})
|
|
98
|
+
: (() => {
|
|
99
|
+
// Smart context filtering: inject only relevant parts of truth files
|
|
100
|
+
const filteredHooks = filterHooks(hooks);
|
|
101
|
+
const filteredSummaries = filterSummaries(chapterSummaries, chapterNumber);
|
|
102
|
+
const filteredSubplots = filterSubplots(subplotBoard);
|
|
103
|
+
const filteredArcs = filterEmotionalArcs(emotionalArcs, chapterNumber);
|
|
104
|
+
const filteredMatrix = filterCharacterMatrix(characterMatrix, volumeOutline, bookRules?.protagonist?.name);
|
|
105
|
+
// POV-aware filtering: limit context to what the POV character knows
|
|
106
|
+
const povCharacter = extractPOVFromOutline(volumeOutline, chapterNumber);
|
|
107
|
+
const povFilteredMatrix = povCharacter
|
|
108
|
+
? filterMatrixByPOV(filteredMatrix, povCharacter)
|
|
109
|
+
: filteredMatrix;
|
|
110
|
+
const povFilteredHooks = povCharacter
|
|
111
|
+
? filterHooksByPOV(filteredHooks, povCharacter, chapterSummaries)
|
|
112
|
+
: filteredHooks;
|
|
113
|
+
return this.buildUserPrompt({
|
|
114
|
+
chapterNumber,
|
|
115
|
+
storyBible,
|
|
116
|
+
volumeOutline,
|
|
117
|
+
currentState,
|
|
118
|
+
ledger: genreProfile.numericalSystem ? ledger : "",
|
|
119
|
+
hooks: povFilteredHooks,
|
|
120
|
+
recentChapters,
|
|
121
|
+
lengthSpec: resolvedLengthSpec,
|
|
122
|
+
externalContext: input.externalContext,
|
|
123
|
+
chapterSummaries: filteredSummaries,
|
|
124
|
+
subplotBoard: filteredSubplots,
|
|
125
|
+
emotionalArcs: filteredArcs,
|
|
126
|
+
characterMatrix: povFilteredMatrix,
|
|
127
|
+
dialogueFingerprints,
|
|
128
|
+
relevantSummaries,
|
|
129
|
+
parentCanon: hasParentCanon ? parentCanon : undefined,
|
|
130
|
+
language: book.language ?? genreProfile.language,
|
|
131
|
+
});
|
|
132
|
+
})();
|
|
92
133
|
const creativeTemperature = input.temperatureOverride ?? 0.7;
|
|
93
|
-
this.
|
|
134
|
+
this.logInfo(resolvedLanguage, {
|
|
135
|
+
zh: `阶段 1:创作正文(第${chapterNumber}章)`,
|
|
136
|
+
en: `Phase 1: creative writing for chapter ${chapterNumber}`,
|
|
137
|
+
});
|
|
94
138
|
// Scale maxTokens to chapter word count (Chinese ≈ 1.5 tokens/char)
|
|
95
|
-
const targetWords = input.wordCountOverride ?? book.chapterWordCount;
|
|
96
139
|
const creativeMaxTokens = Math.max(8192, Math.ceil(targetWords * 2));
|
|
97
140
|
const creativeResponse = await this.chat([
|
|
98
141
|
{ role: "system", content: creativeSystemPrompt },
|
|
99
142
|
{ role: "user", content: creativeUserPrompt },
|
|
100
143
|
], { maxTokens: creativeMaxTokens, temperature: creativeTemperature });
|
|
101
144
|
const creativeUsage = creativeResponse.usage;
|
|
102
|
-
const creative = parseCreativeOutput(chapterNumber, creativeResponse.content);
|
|
145
|
+
const creative = parseCreativeOutput(chapterNumber, creativeResponse.content, resolvedLengthSpec.countingMode);
|
|
103
146
|
// ── Phase 2: State settlement (temperature 0.3) ──
|
|
104
|
-
this.
|
|
147
|
+
this.logInfo(resolvedLanguage, {
|
|
148
|
+
zh: `阶段 2:状态结算(第${chapterNumber}章,${creative.wordCount}字)`,
|
|
149
|
+
en: `Phase 2: state settlement for chapter ${chapterNumber} (${creative.wordCount} words)`,
|
|
150
|
+
});
|
|
151
|
+
const isGovernedSettlement = Boolean(input.chapterIntent && input.contextPackage && input.ruleStack);
|
|
152
|
+
const filteredHooksForSettlement = isGovernedSettlement && input.contextPackage
|
|
153
|
+
? buildGovernedHookWorkingSet({
|
|
154
|
+
hooksMarkdown: hooks,
|
|
155
|
+
contextPackage: input.contextPackage,
|
|
156
|
+
chapterIntent: input.chapterIntent,
|
|
157
|
+
chapterNumber,
|
|
158
|
+
language: resolvedLanguage,
|
|
159
|
+
})
|
|
160
|
+
: hooks;
|
|
161
|
+
const filteredSubplotsForSettlement = isGovernedSettlement
|
|
162
|
+
? filterSubplots(subplotBoard)
|
|
163
|
+
: subplotBoard;
|
|
164
|
+
const filteredArcsForSettlement = isGovernedSettlement
|
|
165
|
+
? filterEmotionalArcs(emotionalArcs, chapterNumber)
|
|
166
|
+
: emotionalArcs;
|
|
167
|
+
const filteredMatrixForSettlement = isGovernedSettlement
|
|
168
|
+
? buildGovernedCharacterMatrixWorkingSet({
|
|
169
|
+
matrixMarkdown: characterMatrix,
|
|
170
|
+
chapterIntent: input.chapterIntent ?? volumeOutline,
|
|
171
|
+
contextPackage: input.contextPackage,
|
|
172
|
+
protagonistName: bookRules?.protagonist?.name,
|
|
173
|
+
})
|
|
174
|
+
: characterMatrix;
|
|
105
175
|
const settleResult = await this.settle({
|
|
106
176
|
book,
|
|
107
177
|
genreProfile,
|
|
@@ -111,32 +181,78 @@ export class WriterAgent extends BaseAgent {
|
|
|
111
181
|
content: creative.content,
|
|
112
182
|
currentState,
|
|
113
183
|
ledger: genreProfile.numericalSystem ? ledger : "",
|
|
114
|
-
hooks,
|
|
115
|
-
chapterSummaries,
|
|
116
|
-
subplotBoard,
|
|
117
|
-
emotionalArcs,
|
|
118
|
-
characterMatrix,
|
|
184
|
+
hooks: filteredHooksForSettlement,
|
|
185
|
+
chapterSummaries: input.contextPackage ? filterSummaries(chapterSummaries, chapterNumber) : chapterSummaries,
|
|
186
|
+
subplotBoard: filteredSubplotsForSettlement,
|
|
187
|
+
emotionalArcs: filteredArcsForSettlement,
|
|
188
|
+
characterMatrix: filteredMatrixForSettlement,
|
|
119
189
|
volumeOutline,
|
|
190
|
+
selectedEvidenceBlock: governedMemoryBlocks
|
|
191
|
+
? [
|
|
192
|
+
governedMemoryBlocks.hooksBlock,
|
|
193
|
+
governedMemoryBlocks.summariesBlock,
|
|
194
|
+
governedMemoryBlocks.volumeSummariesBlock,
|
|
195
|
+
]
|
|
196
|
+
.filter(Boolean)
|
|
197
|
+
.join("\n")
|
|
198
|
+
: undefined,
|
|
199
|
+
chapterIntent: input.chapterIntent,
|
|
200
|
+
contextPackage: input.contextPackage,
|
|
201
|
+
ruleStack: input.ruleStack,
|
|
202
|
+
originalHooks: hooks,
|
|
203
|
+
originalSubplots: subplotBoard,
|
|
204
|
+
originalEmotionalArcs: emotionalArcs,
|
|
205
|
+
originalCharacterMatrix: characterMatrix,
|
|
120
206
|
});
|
|
121
207
|
const settlement = settleResult.settlement;
|
|
122
208
|
const settleUsage = settleResult.usage;
|
|
209
|
+
const runtimeStateArtifacts = await this.buildRuntimeStateArtifactsIfPresent(bookDir, settlement.runtimeStateDelta, resolvedLanguage);
|
|
210
|
+
const priorHookIds = new Set(parsePendingHooksMarkdown(hooks).map((hook) => hook.hookId));
|
|
211
|
+
const hookHealthIssues = settlement.runtimeStateDelta
|
|
212
|
+
&& (runtimeStateArtifacts?.snapshot ?? settlement.runtimeStateSnapshot)
|
|
213
|
+
? analyzeHookHealth({
|
|
214
|
+
language: resolvedLanguage,
|
|
215
|
+
chapterNumber,
|
|
216
|
+
hooks: (runtimeStateArtifacts?.snapshot ?? settlement.runtimeStateSnapshot).hooks.hooks,
|
|
217
|
+
delta: settlement.runtimeStateDelta,
|
|
218
|
+
existingHookIds: [...priorHookIds],
|
|
219
|
+
})
|
|
220
|
+
: [];
|
|
123
221
|
// ── Post-write validation (regex + rule-based, zero LLM cost) ──
|
|
124
|
-
const ruleViolations =
|
|
222
|
+
const ruleViolations = [
|
|
223
|
+
...validatePostWrite(creative.content, genreProfile, bookRules, resolvedLanguage),
|
|
224
|
+
...detectCrossChapterRepetition(creative.content, fingerprintChapters, resolvedLanguage),
|
|
225
|
+
];
|
|
125
226
|
const aiTellIssues = analyzeAITells(creative.content).issues;
|
|
126
227
|
const postWriteErrors = ruleViolations.filter(v => v.severity === "error");
|
|
127
228
|
const postWriteWarnings = ruleViolations.filter(v => v.severity === "warning");
|
|
128
229
|
if (ruleViolations.length > 0) {
|
|
129
|
-
this.
|
|
230
|
+
this.logWarn(resolvedLanguage, {
|
|
231
|
+
zh: `后写校验:第${chapterNumber}章 ${postWriteErrors.length} 个错误,${postWriteWarnings.length} 个警告`,
|
|
232
|
+
en: `Post-write: ${postWriteErrors.length} errors, ${postWriteWarnings.length} warnings in chapter ${chapterNumber}`,
|
|
233
|
+
});
|
|
130
234
|
for (const v of ruleViolations) {
|
|
131
235
|
this.ctx.logger?.warn(`[${v.severity}] ${v.rule}: ${v.description}`);
|
|
132
236
|
}
|
|
133
237
|
}
|
|
134
238
|
if (aiTellIssues.length > 0) {
|
|
135
|
-
this.
|
|
239
|
+
this.logWarn(resolvedLanguage, {
|
|
240
|
+
zh: `AI 味检查:第${chapterNumber}章发现 ${aiTellIssues.length} 个问题`,
|
|
241
|
+
en: `AI-tell check: ${aiTellIssues.length} issues in chapter ${chapterNumber}`,
|
|
242
|
+
});
|
|
136
243
|
for (const issue of aiTellIssues) {
|
|
137
244
|
this.ctx.logger?.warn(`[${issue.severity}] ${issue.category}: ${issue.description}`);
|
|
138
245
|
}
|
|
139
246
|
}
|
|
247
|
+
if (hookHealthIssues.length > 0) {
|
|
248
|
+
this.logWarn(resolvedLanguage, {
|
|
249
|
+
zh: `伏笔健康:第${chapterNumber}章发现 ${hookHealthIssues.length} 条警告`,
|
|
250
|
+
en: `Hook health: ${hookHealthIssues.length} warning(s) in chapter ${chapterNumber}`,
|
|
251
|
+
});
|
|
252
|
+
for (const issue of hookHealthIssues) {
|
|
253
|
+
this.ctx.logger?.warn(`[${issue.severity}] ${issue.category}: ${issue.description}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
140
256
|
// ── Merge into WriteChapterOutput ──
|
|
141
257
|
const tokenUsage = {
|
|
142
258
|
promptTokens: creativeUsage.promptTokens + settleUsage.promptTokens,
|
|
@@ -150,15 +266,21 @@ export class WriterAgent extends BaseAgent {
|
|
|
150
266
|
wordCount: creative.wordCount,
|
|
151
267
|
preWriteCheck: creative.preWriteCheck,
|
|
152
268
|
postSettlement: settlement.postSettlement,
|
|
153
|
-
|
|
269
|
+
runtimeStateDelta: settlement.runtimeStateDelta,
|
|
270
|
+
runtimeStateSnapshot: runtimeStateArtifacts?.snapshot ?? settlement.runtimeStateSnapshot,
|
|
271
|
+
updatedState: runtimeStateArtifacts?.currentStateMarkdown ?? settlement.updatedState,
|
|
154
272
|
updatedLedger: settlement.updatedLedger,
|
|
155
|
-
updatedHooks: settlement.updatedHooks,
|
|
156
|
-
chapterSummary: settlement.
|
|
273
|
+
updatedHooks: runtimeStateArtifacts?.hooksMarkdown ?? settlement.updatedHooks,
|
|
274
|
+
chapterSummary: settlement.runtimeStateDelta
|
|
275
|
+
? this.renderDeltaSummaryRow(settlement.runtimeStateDelta)
|
|
276
|
+
: settlement.chapterSummary,
|
|
277
|
+
updatedChapterSummaries: runtimeStateArtifacts?.chapterSummariesMarkdown,
|
|
157
278
|
updatedSubplots: settlement.updatedSubplots,
|
|
158
279
|
updatedEmotionalArcs: settlement.updatedEmotionalArcs,
|
|
159
280
|
updatedCharacterMatrix: settlement.updatedCharacterMatrix,
|
|
160
281
|
postWriteErrors,
|
|
161
282
|
postWriteWarnings,
|
|
283
|
+
hookHealthIssues,
|
|
162
284
|
tokenUsage,
|
|
163
285
|
};
|
|
164
286
|
}
|
|
@@ -167,15 +289,24 @@ export class WriterAgent extends BaseAgent {
|
|
|
167
289
|
const resolvedLang = params.book.language ?? params.genreProfile.language;
|
|
168
290
|
const observerSystem = buildObserverSystemPrompt(params.book, params.genreProfile, resolvedLang);
|
|
169
291
|
const observerUser = buildObserverUserPrompt(params.chapterNumber, params.title, params.content, resolvedLang);
|
|
170
|
-
this.
|
|
292
|
+
this.logInfo(resolvedLang, {
|
|
293
|
+
zh: `阶段 2a:提取第${params.chapterNumber}章事实`,
|
|
294
|
+
en: `Phase 2a: observing facts for chapter ${params.chapterNumber}`,
|
|
295
|
+
});
|
|
171
296
|
const observerResponse = await this.chat([
|
|
172
297
|
{ role: "system", content: observerSystem },
|
|
173
298
|
{ role: "user", content: observerUser },
|
|
174
299
|
], { maxTokens: 4096, temperature: 0.5 });
|
|
175
300
|
const observations = observerResponse.content;
|
|
176
301
|
// Phase 2b: Reflector — merge observations into truth files
|
|
177
|
-
this.
|
|
302
|
+
this.logInfo(resolvedLang, {
|
|
303
|
+
zh: "阶段 2b:把观察结果回写到真相文件",
|
|
304
|
+
en: "Phase 2b: reflecting observations into truth files",
|
|
305
|
+
});
|
|
178
306
|
const settlerSystem = buildSettlerSystemPrompt(params.book, params.genreProfile, params.bookRules, resolvedLang);
|
|
307
|
+
const governedControlBlock = params.chapterIntent && params.contextPackage && params.ruleStack
|
|
308
|
+
? this.buildSettlerGovernedControlBlock(params.chapterIntent, params.contextPackage, params.ruleStack, resolvedLang)
|
|
309
|
+
: undefined;
|
|
179
310
|
const settlerUser = buildSettlerUserPrompt({
|
|
180
311
|
chapterNumber: params.chapterNumber,
|
|
181
312
|
title: params.title,
|
|
@@ -189,6 +320,8 @@ export class WriterAgent extends BaseAgent {
|
|
|
189
320
|
characterMatrix: params.characterMatrix,
|
|
190
321
|
volumeOutline: params.volumeOutline,
|
|
191
322
|
observations,
|
|
323
|
+
selectedEvidenceBlock: params.selectedEvidenceBlock,
|
|
324
|
+
governedControlBlock,
|
|
192
325
|
});
|
|
193
326
|
// Settler outputs all truth files — scale with content size
|
|
194
327
|
const settlerMaxTokens = Math.max(8192, Math.ceil(params.content.length * 0.8));
|
|
@@ -196,8 +329,41 @@ export class WriterAgent extends BaseAgent {
|
|
|
196
329
|
{ role: "system", content: settlerSystem },
|
|
197
330
|
{ role: "user", content: settlerUser },
|
|
198
331
|
], { maxTokens: settlerMaxTokens, temperature: 0.3 });
|
|
332
|
+
let mergedSettlement;
|
|
333
|
+
try {
|
|
334
|
+
const deltaOutput = parseSettlerDeltaOutput(response.content);
|
|
335
|
+
mergedSettlement = {
|
|
336
|
+
postSettlement: deltaOutput.postSettlement,
|
|
337
|
+
runtimeStateDelta: deltaOutput.runtimeStateDelta,
|
|
338
|
+
updatedState: "",
|
|
339
|
+
updatedLedger: "",
|
|
340
|
+
updatedHooks: "",
|
|
341
|
+
chapterSummary: "",
|
|
342
|
+
updatedSubplots: "",
|
|
343
|
+
updatedEmotionalArcs: "",
|
|
344
|
+
updatedCharacterMatrix: "",
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
const settlement = parseSettlementOutput(response.content, params.genreProfile);
|
|
349
|
+
mergedSettlement = governedControlBlock
|
|
350
|
+
? {
|
|
351
|
+
...settlement,
|
|
352
|
+
updatedHooks: mergeTableMarkdownByKey(params.originalHooks, settlement.updatedHooks, [0]),
|
|
353
|
+
updatedSubplots: settlement.updatedSubplots
|
|
354
|
+
? mergeTableMarkdownByKey(params.originalSubplots, settlement.updatedSubplots, [0])
|
|
355
|
+
: settlement.updatedSubplots,
|
|
356
|
+
updatedEmotionalArcs: settlement.updatedEmotionalArcs
|
|
357
|
+
? mergeTableMarkdownByKey(params.originalEmotionalArcs, settlement.updatedEmotionalArcs, [0, 1])
|
|
358
|
+
: settlement.updatedEmotionalArcs,
|
|
359
|
+
updatedCharacterMatrix: settlement.updatedCharacterMatrix
|
|
360
|
+
? mergeCharacterMatrixMarkdown(params.originalCharacterMatrix, settlement.updatedCharacterMatrix)
|
|
361
|
+
: settlement.updatedCharacterMatrix,
|
|
362
|
+
}
|
|
363
|
+
: settlement;
|
|
364
|
+
}
|
|
199
365
|
return {
|
|
200
|
-
settlement:
|
|
366
|
+
settlement: mergedSettlement,
|
|
201
367
|
usage: response.usage,
|
|
202
368
|
};
|
|
203
369
|
}
|
|
@@ -215,11 +381,18 @@ export class WriterAgent extends BaseAgent {
|
|
|
215
381
|
"",
|
|
216
382
|
output.content,
|
|
217
383
|
].join("\n");
|
|
384
|
+
const runtimeStateArtifacts = await this.resolveRuntimeStateArtifactsForOutput(bookDir, output, language);
|
|
218
385
|
const writes = [
|
|
219
386
|
writeFile(join(chaptersDir, filename), chapterContent, "utf-8"),
|
|
220
|
-
writeFile(join(storyDir, "current_state.md"), output.updatedState, "utf-8"),
|
|
221
|
-
writeFile(join(storyDir, "pending_hooks.md"), output.updatedHooks, "utf-8"),
|
|
387
|
+
writeFile(join(storyDir, "current_state.md"), runtimeStateArtifacts?.currentStateMarkdown ?? output.updatedState, "utf-8"),
|
|
388
|
+
writeFile(join(storyDir, "pending_hooks.md"), runtimeStateArtifacts?.hooksMarkdown ?? output.updatedHooks, "utf-8"),
|
|
222
389
|
];
|
|
390
|
+
if (runtimeStateArtifacts?.chapterSummariesMarkdown) {
|
|
391
|
+
writes.push(writeFile(join(storyDir, "chapter_summaries.md"), runtimeStateArtifacts.chapterSummariesMarkdown, "utf-8"));
|
|
392
|
+
}
|
|
393
|
+
if (runtimeStateArtifacts?.snapshot ?? output.runtimeStateSnapshot) {
|
|
394
|
+
writes.push(saveRuntimeStateSnapshot(bookDir, runtimeStateArtifacts?.snapshot ?? output.runtimeStateSnapshot));
|
|
395
|
+
}
|
|
223
396
|
if (numericalSystem) {
|
|
224
397
|
writes.push(writeFile(join(storyDir, "particle_ledger.md"), output.updatedLedger, "utf-8"));
|
|
225
398
|
}
|
|
@@ -255,6 +428,7 @@ export class WriterAgent extends BaseAgent {
|
|
|
255
428
|
本书是番外作品。以下正典约束不可违反,角色不得引用超出其信息边界的信息。
|
|
256
429
|
${params.parentCanon}\n`
|
|
257
430
|
: "";
|
|
431
|
+
const lengthRequirementBlock = this.buildLengthRequirementBlock(params.lengthSpec, params.language ?? "zh");
|
|
258
432
|
if (params.language === "en") {
|
|
259
433
|
return `Write chapter ${params.chapterNumber}.
|
|
260
434
|
${contextBlock}
|
|
@@ -279,8 +453,7 @@ ${params.volumeOutline}
|
|
|
279
453
|
- Pacing must match the outline's chapter span: if 5 chapters are planned for an arc, do not compress into 1-2.
|
|
280
454
|
- PRE_WRITE_CHECK must identify which outline node this chapter covers.
|
|
281
455
|
|
|
282
|
-
|
|
283
|
-
- Chapter body must be at least ${params.wordCount} words
|
|
456
|
+
${lengthRequirementBlock}
|
|
284
457
|
- Output PRE_WRITE_CHECK first, then the chapter
|
|
285
458
|
- Output only PRE_WRITE_CHECK, CHAPTER_TITLE, and CHAPTER_CONTENT blocks`;
|
|
286
459
|
}
|
|
@@ -307,10 +480,129 @@ ${params.volumeOutline}
|
|
|
307
480
|
- 剧情推进速度必须与卷纲规划的章节跨度匹配:如果卷纲规划某段剧情跨5章,不得在1-2章内讲完
|
|
308
481
|
- PRE_WRITE_CHECK中必须明确标注本章对应的卷纲节点
|
|
309
482
|
|
|
310
|
-
|
|
311
|
-
-
|
|
483
|
+
${lengthRequirementBlock}
|
|
484
|
+
- 先输出写作自检表,再写正文
|
|
485
|
+
- 只需输出 PRE_WRITE_CHECK、CHAPTER_TITLE、CHAPTER_CONTENT 三个区块`;
|
|
486
|
+
}
|
|
487
|
+
buildGovernedUserPrompt(params) {
|
|
488
|
+
const contextSections = params.contextPackage.selectedContext
|
|
489
|
+
.map((entry) => [
|
|
490
|
+
`### ${entry.source}`,
|
|
491
|
+
`- reason: ${entry.reason}`,
|
|
492
|
+
entry.excerpt ? `- excerpt: ${entry.excerpt}` : "",
|
|
493
|
+
].filter(Boolean).join("\n"))
|
|
494
|
+
.join("\n\n");
|
|
495
|
+
const overrideLines = params.ruleStack.activeOverrides.length > 0
|
|
496
|
+
? params.ruleStack.activeOverrides
|
|
497
|
+
.map((override) => `- ${override.from} -> ${override.to}: ${override.reason} (${override.target})`)
|
|
498
|
+
.join("\n")
|
|
499
|
+
: "- none";
|
|
500
|
+
const diagnosticLines = params.ruleStack.sections.diagnostic.length > 0
|
|
501
|
+
? params.ruleStack.sections.diagnostic.join(", ")
|
|
502
|
+
: "none";
|
|
503
|
+
const traceNotes = params.trace && params.trace.notes.length > 0
|
|
504
|
+
? params.trace.notes.map((note) => `- ${note}`).join("\n")
|
|
505
|
+
: "- none";
|
|
506
|
+
const lengthRequirementBlock = this.buildLengthRequirementBlock(params.lengthSpec, params.language ?? "zh");
|
|
507
|
+
const varianceBlock = params.varianceBrief
|
|
508
|
+
? `\n${params.varianceBrief}\n`
|
|
509
|
+
: "";
|
|
510
|
+
if (params.language === "en") {
|
|
511
|
+
return `Write chapter ${params.chapterNumber}.
|
|
512
|
+
|
|
513
|
+
## Chapter Intent
|
|
514
|
+
${params.chapterIntent}
|
|
515
|
+
|
|
516
|
+
## Selected Context
|
|
517
|
+
${contextSections || "(none)"}
|
|
518
|
+
|
|
519
|
+
## Rule Stack
|
|
520
|
+
- Hard: ${params.ruleStack.sections.hard.join(", ") || "(none)"}
|
|
521
|
+
- Soft: ${params.ruleStack.sections.soft.join(", ") || "(none)"}
|
|
522
|
+
- Diagnostic: ${diagnosticLines}
|
|
523
|
+
|
|
524
|
+
## Active Overrides
|
|
525
|
+
${overrideLines}
|
|
526
|
+
|
|
527
|
+
## Trace Notes
|
|
528
|
+
${traceNotes}
|
|
529
|
+
|
|
530
|
+
${varianceBlock}
|
|
531
|
+
${lengthRequirementBlock}
|
|
532
|
+
- Output PRE_WRITE_CHECK first, then the chapter
|
|
533
|
+
- Output only PRE_WRITE_CHECK, CHAPTER_TITLE, and CHAPTER_CONTENT blocks`;
|
|
534
|
+
}
|
|
535
|
+
return `请续写第${params.chapterNumber}章。
|
|
536
|
+
|
|
537
|
+
## 本章意图
|
|
538
|
+
${params.chapterIntent}
|
|
539
|
+
|
|
540
|
+
## 已选上下文
|
|
541
|
+
${contextSections || "(无)"}
|
|
542
|
+
|
|
543
|
+
## 规则栈
|
|
544
|
+
- 硬护栏:${params.ruleStack.sections.hard.join("、") || "(无)"}
|
|
545
|
+
- 软约束:${params.ruleStack.sections.soft.join("、") || "(无)"}
|
|
546
|
+
- 诊断规则:${diagnosticLines}
|
|
547
|
+
|
|
548
|
+
## 当前覆盖
|
|
549
|
+
${overrideLines}
|
|
550
|
+
|
|
551
|
+
## 追踪说明
|
|
552
|
+
${traceNotes}
|
|
553
|
+
|
|
554
|
+
${varianceBlock}
|
|
555
|
+
${lengthRequirementBlock}
|
|
312
556
|
- 先输出写作自检表,再写正文
|
|
313
557
|
- 只需输出 PRE_WRITE_CHECK、CHAPTER_TITLE、CHAPTER_CONTENT 三个区块`;
|
|
558
|
+
}
|
|
559
|
+
buildSettlerGovernedControlBlock(chapterIntent, contextPackage, ruleStack, language) {
|
|
560
|
+
const selectedContext = contextPackage.selectedContext
|
|
561
|
+
.map((entry) => `- ${entry.source}: ${entry.reason}${entry.excerpt ? ` | ${entry.excerpt}` : ""}`)
|
|
562
|
+
.join("\n");
|
|
563
|
+
const overrides = ruleStack.activeOverrides.length > 0
|
|
564
|
+
? ruleStack.activeOverrides
|
|
565
|
+
.map((override) => `- ${override.from} -> ${override.to}: ${override.reason} (${override.target})`)
|
|
566
|
+
.join("\n")
|
|
567
|
+
: "- none";
|
|
568
|
+
if (language === "en") {
|
|
569
|
+
return `\n## Chapter Control Inputs
|
|
570
|
+
${chapterIntent}
|
|
571
|
+
|
|
572
|
+
### Selected Context
|
|
573
|
+
${selectedContext || "- none"}
|
|
574
|
+
|
|
575
|
+
### Rule Stack
|
|
576
|
+
- Hard guardrails: ${ruleStack.sections.hard.join(", ") || "(none)"}
|
|
577
|
+
- Soft constraints: ${ruleStack.sections.soft.join(", ") || "(none)"}
|
|
578
|
+
- Diagnostic rules: ${ruleStack.sections.diagnostic.join(", ") || "(none)"}
|
|
579
|
+
|
|
580
|
+
### Active Overrides
|
|
581
|
+
${overrides}\n`;
|
|
582
|
+
}
|
|
583
|
+
return `\n## 本章控制输入
|
|
584
|
+
${chapterIntent}
|
|
585
|
+
|
|
586
|
+
### 已选上下文
|
|
587
|
+
${selectedContext || "- none"}
|
|
588
|
+
|
|
589
|
+
### 规则栈
|
|
590
|
+
- 硬护栏:${ruleStack.sections.hard.join("、") || "(无)"}
|
|
591
|
+
- 软约束:${ruleStack.sections.soft.join("、") || "(无)"}
|
|
592
|
+
- 诊断规则:${ruleStack.sections.diagnostic.join("、") || "(无)"}
|
|
593
|
+
|
|
594
|
+
### 当前覆盖
|
|
595
|
+
${overrides}\n`;
|
|
596
|
+
}
|
|
597
|
+
buildLengthRequirementBlock(lengthSpec, language) {
|
|
598
|
+
if (language === "en") {
|
|
599
|
+
return `Requirements:
|
|
600
|
+
- Target length: ${lengthSpec.target} words
|
|
601
|
+
- Acceptable range: ${lengthSpec.softMin}-${lengthSpec.softMax} words`;
|
|
602
|
+
}
|
|
603
|
+
return `要求:
|
|
604
|
+
- 目标字数:${lengthSpec.target}字
|
|
605
|
+
- 允许区间:${lengthSpec.softMin}-${lengthSpec.softMax}字`;
|
|
314
606
|
}
|
|
315
607
|
async loadRecentChapters(bookDir, currentChapter, count = 1) {
|
|
316
608
|
const chaptersDir = join(bookDir, "chapters");
|
|
@@ -341,12 +633,15 @@ ${params.volumeOutline}
|
|
|
341
633
|
}
|
|
342
634
|
}
|
|
343
635
|
/** Save new truth files (summaries, subplots, emotional arcs, character matrix). */
|
|
344
|
-
async saveNewTruthFiles(bookDir, output) {
|
|
636
|
+
async saveNewTruthFiles(bookDir, output, language = "zh") {
|
|
345
637
|
const storyDir = join(bookDir, "story");
|
|
346
638
|
const writes = [];
|
|
347
639
|
// Append chapter summary to chapter_summaries.md
|
|
348
|
-
if (output.
|
|
349
|
-
writes.push(
|
|
640
|
+
if (!output.runtimeStateDelta && output.updatedChapterSummaries) {
|
|
641
|
+
writes.push(writeFile(join(storyDir, "chapter_summaries.md"), output.updatedChapterSummaries, "utf-8"));
|
|
642
|
+
}
|
|
643
|
+
else if (!output.runtimeStateDelta && output.chapterSummary) {
|
|
644
|
+
writes.push(this.appendChapterSummary(storyDir, output.chapterSummary, language));
|
|
350
645
|
}
|
|
351
646
|
// Overwrite subplot board
|
|
352
647
|
if (output.updatedSubplots) {
|
|
@@ -362,7 +657,52 @@ ${params.volumeOutline}
|
|
|
362
657
|
}
|
|
363
658
|
await Promise.all(writes);
|
|
364
659
|
}
|
|
365
|
-
|
|
660
|
+
renderDeltaSummaryRow(delta) {
|
|
661
|
+
if (!delta.chapterSummary)
|
|
662
|
+
return "";
|
|
663
|
+
const summary = delta.chapterSummary;
|
|
664
|
+
const row = [
|
|
665
|
+
summary.chapter,
|
|
666
|
+
summary.title,
|
|
667
|
+
summary.characters,
|
|
668
|
+
summary.events,
|
|
669
|
+
summary.stateChanges,
|
|
670
|
+
summary.hookActivity,
|
|
671
|
+
summary.mood,
|
|
672
|
+
summary.chapterType,
|
|
673
|
+
].map((value) => String(value).replace(/\|/g, "\\|").trim()).join(" | ");
|
|
674
|
+
return `| ${row} |`;
|
|
675
|
+
}
|
|
676
|
+
async buildRuntimeStateArtifactsIfPresent(bookDir, delta, language) {
|
|
677
|
+
if (!delta)
|
|
678
|
+
return null;
|
|
679
|
+
return buildRuntimeStateArtifacts({
|
|
680
|
+
bookDir,
|
|
681
|
+
delta,
|
|
682
|
+
language,
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
async resolveRuntimeStateArtifactsForOutput(bookDir, output, language) {
|
|
686
|
+
if (!output.runtimeStateDelta)
|
|
687
|
+
return null;
|
|
688
|
+
if (output.runtimeStateSnapshot
|
|
689
|
+
&& output.updatedChapterSummaries
|
|
690
|
+
&& output.updatedState
|
|
691
|
+
&& output.updatedHooks) {
|
|
692
|
+
return {
|
|
693
|
+
snapshot: output.runtimeStateSnapshot,
|
|
694
|
+
currentStateMarkdown: output.updatedState,
|
|
695
|
+
hooksMarkdown: output.updatedHooks,
|
|
696
|
+
chapterSummariesMarkdown: output.updatedChapterSummaries,
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
return buildRuntimeStateArtifacts({
|
|
700
|
+
bookDir,
|
|
701
|
+
delta: output.runtimeStateDelta,
|
|
702
|
+
language,
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
async appendChapterSummary(storyDir, summary, language) {
|
|
366
706
|
const summaryPath = join(storyDir, "chapter_summaries.md");
|
|
367
707
|
let existing = "";
|
|
368
708
|
try {
|
|
@@ -370,15 +710,34 @@ ${params.volumeOutline}
|
|
|
370
710
|
}
|
|
371
711
|
catch {
|
|
372
712
|
// File doesn't exist yet — start with header
|
|
373
|
-
existing =
|
|
713
|
+
existing = language === "en"
|
|
714
|
+
? "# Chapter Summaries\n\n| Chapter | Title | Characters | Key Events | State Changes | Hook Activity | Mood | Chapter Type |\n| --- | --- | --- | --- | --- | --- | --- | --- |\n"
|
|
715
|
+
: "# 章节摘要\n\n| 章节 | 标题 | 出场人物 | 关键事件 | 状态变化 | 伏笔动态 | 情绪基调 | 章节类型 |\n|------|------|----------|----------|----------|----------|----------|----------|\n";
|
|
374
716
|
}
|
|
375
717
|
// Extract only the data row(s) from the summary (skip header lines)
|
|
376
718
|
const dataRows = summary
|
|
377
719
|
.split("\n")
|
|
378
|
-
.filter((line) => line.startsWith("|")
|
|
720
|
+
.filter((line) => line.startsWith("|")
|
|
721
|
+
&& !line.startsWith("| 章节")
|
|
722
|
+
&& !line.startsWith("| Chapter")
|
|
723
|
+
&& !line.startsWith("|--")
|
|
724
|
+
&& !line.startsWith("| ---"))
|
|
379
725
|
.join("\n");
|
|
380
726
|
if (dataRows) {
|
|
381
|
-
|
|
727
|
+
// Deduplicate: remove existing rows with the same chapter number before appending
|
|
728
|
+
const newChapterNums = new Set(dataRows.split("\n")
|
|
729
|
+
.map((line) => line.split("|")[1]?.trim())
|
|
730
|
+
.filter((ch) => ch && /^\d+$/.test(ch)));
|
|
731
|
+
const deduped = existing
|
|
732
|
+
.split("\n")
|
|
733
|
+
.filter((line) => {
|
|
734
|
+
if (!line.startsWith("|"))
|
|
735
|
+
return true;
|
|
736
|
+
const chNum = line.split("|")[1]?.trim();
|
|
737
|
+
return !chNum || !newChapterNums.has(chNum);
|
|
738
|
+
})
|
|
739
|
+
.join("\n");
|
|
740
|
+
await writeFile(summaryPath, `${deduped.trimEnd()}\n${dataRows}\n`, "utf-8");
|
|
382
741
|
}
|
|
383
742
|
}
|
|
384
743
|
buildStyleFingerprint(styleProfileRaw) {
|