@waterwx/dsh-novel-forge 2.1.5 → 2.3.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.
Files changed (42) hide show
  1. package/README.md +13 -9
  2. package/lib/client.js +3825 -2159
  3. package/lib/index.js +1935 -1300
  4. package/lib/types/assistant.d.ts +1 -1
  5. package/lib/types/client/api.d.ts +3 -1
  6. package/lib/types/client/llmLive.d.ts +7 -2
  7. package/lib/types/client/locales.d.ts +1 -0
  8. package/lib/types/client/panel/AuthorInbox.d.ts +23 -0
  9. package/lib/types/client/panel/CabinetSnapshot.d.ts +16 -0
  10. package/lib/types/client/panel/ChapterPlanPanel.d.ts +24 -0
  11. package/lib/types/client/panel/ConfirmDialog.d.ts +2 -0
  12. package/lib/types/client/panel/HomeHero.d.ts +60 -0
  13. package/lib/types/client/panel/ReviewIssueCard.d.ts +24 -0
  14. package/lib/types/client/panel/RunPanel.d.ts +3 -1
  15. package/lib/types/client/panel/Skeleton.d.ts +6 -0
  16. package/lib/types/client/panel/helpers.d.ts +12 -0
  17. package/lib/types/engine.d.ts +31 -14
  18. package/lib/types/llm-live.d.ts +2 -2
  19. package/lib/types/llm-retry.d.ts +11 -0
  20. package/lib/types/prompt-cache.d.ts +43 -0
  21. package/lib/types/prompts/adapt.d.ts +21 -0
  22. package/lib/types/prompts/audit.d.ts +15 -0
  23. package/lib/types/prompts/bible.d.ts +10 -0
  24. package/lib/types/prompts/catalog.d.ts +40 -0
  25. package/lib/types/prompts/characters.d.ts +19 -0
  26. package/lib/types/prompts/common.d.ts +9 -0
  27. package/lib/types/prompts/digest.d.ts +27 -0
  28. package/lib/types/prompts/facts.d.ts +26 -0
  29. package/lib/types/prompts/foreshadow.d.ts +10 -0
  30. package/lib/types/prompts/market.d.ts +18 -0
  31. package/lib/types/prompts/outline.d.ts +25 -0
  32. package/lib/types/prompts/plan.d.ts +22 -0
  33. package/lib/types/prompts/plotlines.d.ts +21 -0
  34. package/lib/types/prompts/polish.d.ts +22 -0
  35. package/lib/types/prompts/review.d.ts +30 -0
  36. package/lib/types/prompts/world.d.ts +13 -0
  37. package/lib/types/prompts/writing.d.ts +29 -0
  38. package/lib/types/protocol.d.ts +53 -2
  39. package/lib/types/routes.d.ts +6 -0
  40. package/lib/types/run-selection.d.ts +5 -2
  41. package/lib/types/stream-fallback.d.ts +8 -0
  42. package/package.json +2 -1
@@ -54,7 +54,7 @@ export declare function clearAssistantHistory(outputDir: string): void;
54
54
  */
55
55
  export declare function executeAction(ctx: Context, config: NovelConfig, project: ProjectState, outputDir: string, name: string, args: Record<string, unknown>): AsyncGenerator<string, string, unknown>;
56
56
  /** Run one user turn. Yields stream frames; persists history. */
57
- export declare function runAssistantTurn(ctx: Context, config: NovelConfig, project: ProjectState, outputDir: string, userMessage: string): AsyncGenerator<{
57
+ export declare function runAssistantTurn(ctx: Context, config: NovelConfig, project: ProjectState, outputDir: string, userMessage: string, signal?: AbortSignal): AsyncGenerator<{
58
58
  frame: 'delta';
59
59
  text: string;
60
60
  } | {
@@ -3,7 +3,7 @@
3
3
  * fetch, same origin; generation/rewrite/polish ride NDJSON streams read
4
4
  * incrementally.
5
5
  */
6
- import { type AssetsPatch, type AssetsResponse, type AddModelRequest, type AddModelResponse, type LlmModelsResponse, type LlmProvidersResponse, type LlmVendorsResponse, type RemoveProviderRequest, type RemoveProviderResponse, type BibleResponse, type ChapterResponse, type ConfigPatch, type ExportResponse, type ForeshadowRequest, type ForeshadowResponse, type JobFrame, type LoadOutlineResponse, type AdaptExecuteRequest, type AdaptRewriteFrame, type AdaptMaterializeSaveRequest, type AdaptMaterializeSaveResponse, type PluginUpdateResponse, type LlmTestResponse, type NovelConfig, type PlanResponse, type ReviewReport, type RevisionPlanResponse, type StatusResponse, type StyleEngineRequest, type StyleAsset, type VolumesResponse } from '../protocol.ts';
6
+ import { type AssetsPatch, type AssetsResponse, type AddModelRequest, type AddModelResponse, type LlmModelsResponse, type LlmProvidersResponse, type LlmVendorsResponse, type RemoveProviderRequest, type RemoveProviderResponse, type BibleResponse, type ChapterResponse, type ChapterPlanPatchResponse, type ConfigPatch, type ExportResponse, type ForeshadowRequest, type ForeshadowResponse, type JobFrame, type LoadOutlineResponse, type AdaptExecuteRequest, type AdaptRewriteFrame, type AdaptMaterializeSaveRequest, type AdaptMaterializeSaveResponse, type PluginUpdateResponse, type LlmTestResponse, type NovelConfig, type PlanResponse, type ReviewReport, type RevisionPlanResponse, type StatusResponse, type StyleEngineRequest, type StyleAsset, type VolumesResponse } from '../protocol.ts';
7
7
  /** Error carrying the route's JSON error message. */
8
8
  export declare class NovelApiError extends Error {
9
9
  constructor(message: string);
@@ -49,6 +49,8 @@ export declare class NovelApi {
49
49
  }): Promise<RevisionPlanResponse>;
50
50
  /** 保存手动编辑的正文(自动备份 .bak;带报告则沿用落盘,否则保存后自动审稿)。 */
51
51
  chapterSave(no: number, text: string, report?: ReviewReport): Promise<import('../protocol.ts').ChapterSaveResponse>;
52
+ /** 补写本章计划(目标 / 必达 / 保持 / 硬事实 / 结尾钩子 / 剧情推进):只覆盖传入字段。 */
53
+ chapterPlan(no: number, patch: import('../protocol.ts').ChapterPlanPatchRequest['patch']): Promise<ChapterPlanPatchResponse>;
52
54
  patchConfig(patch: ConfigPatch): Promise<{
53
55
  config: NovelConfig;
54
56
  settingsWarning?: string;
@@ -2,13 +2,14 @@
2
2
  * 浏览器端「AI 创作实况」钩子:连接 /llm-live/stream (SSE),按 LLM 调用分组会话。
3
3
  * 轻量实现,不持久化缓存(对齐上游 useLlmLiveFeed 的核心行为)。
4
4
  */
5
- import { type LlmLiveFrame, type LlmTokenUsage } from '../protocol.ts';
5
+ import { type LlmLiveFrame, type LlmLivePhase, type LlmTokenUsage } from '../protocol.ts';
6
6
  export type { LlmLiveFrame };
7
7
  export interface LlmLiveSession {
8
8
  sessionId: string;
9
9
  label: string;
10
10
  model?: string;
11
- phase: 'requesting' | 'streaming' | 'completed' | 'failed';
11
+ /** 阶段契约只有一份定义(protocol.ts),面板不再自行枚举。 */
12
+ phase: LlmLivePhase;
12
13
  phaseMessage: string;
13
14
  preview: string;
14
15
  totalChars: number;
@@ -25,6 +26,10 @@ export interface LlmLiveSession {
25
26
  /** Prompt 字符数(system + user)。 */
26
27
  promptChars?: number;
27
28
  }
29
+ /** 是否仍在进行中:cancelled 属于已结束,不能显示为「生成中」。 */
30
+ export declare function isActivePhase(phase: LlmLivePhase): boolean;
31
+ /** SSE 帧 → 会话表(纯函数,导出供单测覆盖 cancelled 结案)。 */
32
+ export declare function applyFrame(current: Record<string, LlmLiveSession>, frame: LlmLiveFrame): Record<string, LlmLiveSession>;
28
33
  export declare function useLlmLiveFeed(enabled?: boolean): {
29
34
  connected: boolean;
30
35
  sessions: LlmLiveSession[];
@@ -83,6 +83,7 @@ export declare const zh: {
83
83
  readonly 'plotlines.kindMystery': "悬念线";
84
84
  readonly 'plotlines.goal': "目标/终点";
85
85
  readonly 'plotlines.progress': "当前进度";
86
+ readonly 'plotlines.nextGoal': "下一目标";
86
87
  readonly 'plotlines.status': "状态";
87
88
  readonly 'plotlines.statusActive': "推进中";
88
89
  readonly 'plotlines.statusPaused': "暂停";
@@ -0,0 +1,23 @@
1
+ export type InboxTone = 'high' | 'warn' | 'info';
2
+ export interface InboxItem {
3
+ id: string;
4
+ /** 要做什么(动词开头的一句话)。 */
5
+ title: string;
6
+ /** 来源:AI 审稿 · 高优 / 时间线 / 张力 / 待审 … */
7
+ source: string;
8
+ /** 补充说明(原因、影响、原文摘录)。 */
9
+ detail?: string;
10
+ tone: InboxTone;
11
+ actionLabel: string;
12
+ onAction: () => void;
13
+ }
14
+ export declare function AuthorInbox({ items, total, shown, onOpenAll, empty }: {
15
+ items: InboxItem[];
16
+ /** 未截断的总条数(> items.length 时提示还有多少)。 */
17
+ total: number;
18
+ /** 列表区最大高度由外层控制时用的展示条数说明。 */
19
+ shown?: number;
20
+ onOpenAll?: () => void;
21
+ /** 空态文案。 */
22
+ empty: string;
23
+ }): JSX.Element;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * 资料侧柜内容(2.1.7):本书此刻的生成上下文。
3
+ *
4
+ * 从 demo 的资料侧柜取**内容组织**:四张「此刻事实 + 新鲜度」卡 ——
5
+ * 编年录最近锚点 / 人物志当前状态 / 剧情线最近推进 / 写作资产当前绑定。
6
+ * 与 demo 的差别:数据全部取自真实存档(timeline / roleStatus / plotlines /
7
+ * assets.styleAssets),缺数据时给**可执行的空态**而不是留空;
8
+ * 「剧情线进度 42%」这类没有数据源的百分比不编(改用「推进章节 · 第 N 章」)。
9
+ *
10
+ * 形态由调用方决定:宿主里是**面板内的右滑抽屉**(demo 的 fixed 全屏遮罩层
11
+ * 放进宿主会话列会盖住整个窗口)。
12
+ */
13
+ import type { ProjectState } from '../../protocol.ts';
14
+ export declare function CabinetSnapshot({ project }: {
15
+ project: ProjectState | null;
16
+ }): JSX.Element;
@@ -0,0 +1,24 @@
1
+ import type { NovelApi } from '../api.ts';
2
+ import type { ChapterPlan, Plotline } from '../../protocol.ts';
3
+ /** 把章节 beats 按结构标签渲染(本章目标 / 剧情要点 / 爽点 / 结尾钩子 等前缀加粗)。 */
4
+ export declare function renderBeats(beats: string): JSX.Element;
5
+ export interface ChapterPlanPanelProps {
6
+ chapter: ChapterPlan;
7
+ /** 全部剧情线:内部按 `chapters` 是否含本章来筛关联项。 */
8
+ plotlines: Plotline[];
9
+ api: NovelApi;
10
+ busy: boolean;
11
+ /** 保存成功后回传合并结果,由外层并入 project.chapters(工作台的唯一数据源)。 */
12
+ onSaved: (chapter: ChapterPlan) => void;
13
+ /** 截断声明汇入外层(不静默吞掉:作者写了 6 条只留 4 条时必须看见)。 */
14
+ onNotice: (message: string) => void;
15
+ /** 关联剧情线的动作入口:打开「长线管理」抽屉。 */
16
+ onLinkLines: () => void;
17
+ }
18
+ /**
19
+ * 左栏计划面板:六项固定分组 + 就地补写。
20
+ *
21
+ * 编辑态一次只开一个分组:`patch` 只覆盖传入字段,同时开两处会让两个
22
+ * 草稿互相看不见,保存时机也变得含混。
23
+ */
24
+ export declare function ChapterPlanPanel({ chapter, plotlines, api, busy, onSaved, onNotice, onLinkLines }: ChapterPlanPanelProps): JSX.Element;
@@ -1,4 +1,6 @@
1
1
  export type ConfirmTone = 'danger' | 'warn' | 'info';
2
+ /** 弹层共用键盘边界与焦点回归;只有最上层弹窗响应按键。 */
3
+ export declare function useDialogFocus(open: boolean, onClose: () => void, canClose?: boolean): import("react").MutableRefObject<HTMLElement | null>;
2
4
  export interface ConfirmSpec {
3
5
  /** 弹窗标题:一句话说清要做什么。 */
4
6
  title: string;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * 总编台 hero(2.1.7 / 取自 prototype/NovelForge_总编台_Demo.html 的信息结构)。
3
+ *
4
+ * 为什么要有它:此前的首屏是「4 张指标卡 + 工序轨道」,作者看到的是**仪表盘**,
5
+ * 得自己拼出"现在最该干什么"。hero 把这件事收敛成一条视线:
6
+ * 在哪一章 → 该做什么 → 为什么现在做 → 做它的代价 → 边界(不会自动改写正文)。
7
+ *
8
+ * 与 demo 的差别(刻意保留):demo 的大标题是一句文学化钩子("旧钥已经发热…"),
9
+ * 那需要"生成一句钩子"的数据源;这里用宿主**真实**的推荐动作标题,永远为真。
10
+ * 虚线右侧的进度/节点也全部来自真实状态,不编造百分比。
11
+ */
12
+ import type { ReactNode } from 'react';
13
+ export interface HomeHeroAction {
14
+ /** 眉标:推荐下一步 / 需要你确认 / 阶段:等待中 … */
15
+ eyebrow: string;
16
+ /** 主标题(一件具体的事)。 */
17
+ title: string;
18
+ /** 为什么现在做。 */
19
+ reason: string;
20
+ /** 主 CTA 文案(动词开头)。 */
21
+ actionLabel: string;
22
+ onClick: () => void;
23
+ }
24
+ export interface HomeHeroProps {
25
+ /** 当前对象(如「第 8 章 · 雨夜之后」)。 */
26
+ kicker: string;
27
+ /** 印章里的短标记(章号 / 阶段序号)。 */
28
+ seal: string;
29
+ /** 印章下方说明。 */
30
+ sealNote: string;
31
+ stageLabel?: string;
32
+ action: HomeHeroAction;
33
+ /** 次要动作(如「查看批注」)。 */
34
+ secondary?: {
35
+ label: string;
36
+ onClick: () => void;
37
+ };
38
+ busy: boolean;
39
+ busyLabel: string;
40
+ /** meta 行:只放**能真算出来**的事实。 */
41
+ meta: Array<{
42
+ label: string;
43
+ value: string;
44
+ tone?: 'ok' | 'warn' | 'bad';
45
+ }>;
46
+ /** 本批次进度;无批次传 null。 */
47
+ batch: {
48
+ label: string;
49
+ percent: number;
50
+ caption: string;
51
+ } | null;
52
+ /** 章节节点:上一章 / 现在 / 下一步。 */
53
+ nodes: Array<{
54
+ label: string;
55
+ text: string;
56
+ tone: 'done' | 'now' | 'next';
57
+ }>;
58
+ children?: ReactNode;
59
+ }
60
+ export declare function HomeHero({ kicker, seal, sealNote, stageLabel, action, secondary, busy, busyLabel, meta, batch, nodes, children, }: HomeHeroProps): JSX.Element;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * 审稿问题卡片(2.1.6 / 方案 §7「审稿问题卡片化」)。
3
+ *
4
+ * 为什么单独成卡而不是继续用 `<ul><li>`:
5
+ * 1. 此前每条意见被压成一行带色文字 —— 「问题」和「建议」用同一个色号、同一种排版,
6
+ * 作者扫读时无法快速分辨"哪句是诊断、哪句是处方"。
7
+ * 2. 勾选框与文字在同一行内联,窄容器下换行会把勾选框甩到行尾,勾错是常事。
8
+ * 3. 卡片把三件事固定成三层:**严重度徽章 + 维度** / **问题** / **建议**,
9
+ * 颜色只承担严重度(图形版语义色 → 文字版 -ink,保证对比度)。
10
+ */
11
+ import type { ReactNode } from 'react';
12
+ import type { ReviewIssue } from '../../protocol.ts';
13
+ /** 严重度的作者口径文案(与 Severity 类型一一对应)。 */
14
+ export declare const SEV_LABEL: Record<string, string>;
15
+ export declare function ReviewIssueCard({ issue, dimensionLabel, checked, onToggle, disabled }: {
16
+ issue: ReviewIssue;
17
+ /** 维度中文名(由调用方查表,避免把文案表搬进组件)。 */
18
+ dimensionLabel?: string;
19
+ checked: boolean;
20
+ onToggle: (next: boolean) => void;
21
+ disabled?: boolean;
22
+ /** 预留:卡片右上角的附加动作(如「单条重写」)。 */
23
+ actions?: ReactNode;
24
+ }): JSX.Element;
@@ -1,5 +1,7 @@
1
1
  import type { NovelApi } from '../api.ts';
2
- export declare function RunPanel({ api, totalChapters }: {
2
+ export declare function RunPanel({ api, totalChapters, onOpenChapter }: {
3
3
  api: NovelApi;
4
4
  totalChapters: number;
5
+ /** 打开某章的章节工作台(队列行的「打开工作台」与失败态的重试入口共用)。 */
6
+ onOpenChapter?: (no: number) => void;
5
7
  }): import("react").JSX.Element;
@@ -0,0 +1,6 @@
1
+ export declare function SkeletonLines({ label, lines }: {
2
+ /** 说清「在等什么」,如「正在读取写作资产…」。 */
3
+ label: string;
4
+ /** 占位行数。 */
5
+ lines?: number;
6
+ }): JSX.Element;
@@ -5,6 +5,7 @@
5
5
  * (键位不匹配会在编译期报错),保持客户端 bundle 自包含、不引入运行时语言服务。
6
6
  * 需要多语言时再接入 DSH 的 locale 服务,届时只需替换本函数实现。
7
7
  */
8
+ import type { KeyboardEvent } from 'react';
8
9
  import { type NovelKey } from '../locales.ts';
9
10
  /** Translate one key with optional {placeholder} substitution. */
10
11
  export declare function tt(key: NovelKey, params?: Record<string, string | number>): string;
@@ -18,3 +19,14 @@ export declare function kindLabel(kind: string): string;
18
19
  export declare function plotlineStatusLabel(status: string): string;
19
20
  /** 剧情线状态颜色。 */
20
21
  export declare function plotlineStatusColor(status: string): string;
22
+ /**
23
+ * 上下键在同类条目之间移动焦点(设计方案 §12「上下键移动章节列表 / 审稿问题」)。
24
+ *
25
+ * 只管焦点移动,不接管 Enter / 空格:条目保持原生 button 与 checkbox,
26
+ * 「空格勾选问题」「Enter 打开章节」继续由浏览器负责,不另造一套键盘语义。
27
+ * 焦点不在列表条目上时直接放行,方向键仍然用来滚动页面。
28
+ *
29
+ * @param event 列表容器上收到的 keydown。
30
+ * @param itemSelector 条目选择器,如 `.${css.wbIndexRow}` 或 `input[type="checkbox"]`。
31
+ */
32
+ export declare function moveFocusByArrow(event: KeyboardEvent<HTMLElement>, itemSelector: string): void;
@@ -4,13 +4,10 @@
4
4
  * auto-review + rewrite, polish (de-AI-ify), narrative summaries, foreshadow
5
5
  * tracking, project persistence, and whole-book export. Pure Node (no
6
6
  * web-server dependencies), so routes stay thin and logic is testable.
7
+ *
8
+ * 系统提示词全部住在 src/prompts/(按域拆分 + catalog 注册表),本文件只做
9
+ * 组装与调用——提示词文案的修改去对应域文件,不要在这里新起一段。
7
10
  */
8
- /**
9
- * 内容合规红线(平台硬性要求):所有书籍、所有章节无条件生效,
10
- * 优先级高于单书大纲/道藏中的任何设定与作者自定义红线。
11
- * 注入点:章节生成系统提示 + 审稿系统提示(命中即 high)。
12
- */
13
- export declare const COMPLIANCE_REDLINES: ReadonlyArray<string>;
14
11
  import type { Context } from '@deepseek-ai/cordis';
15
12
  import { type StyleFingerprint } from './style-fingerprint.ts';
16
13
  import { fullTextBudget } from './model-capability.ts';
@@ -26,13 +23,25 @@ export declare function inferBookName(outline: string): string;
26
23
  export declare function loadProject(outputDir: string): ProjectState | undefined;
27
24
  /** Persist the project state next to the chapters. */
28
25
  export declare function saveProject(outputDir: string, project: ProjectState): void;
26
+ /** `mergeVolatileFromDisk` 会覆盖的易变字段的全集(也是 `keep` 的合法取值域)。 */
27
+ declare const VOLATILE_FIELDS: readonly ["bible", "roles", "plotlines", "roleStatus", "blurb", "coverPath", "facts", "assets", "world", "volumes"];
28
+ /** 易变字段名。用于 {@link mergeVolatileFromDisk} 的 `keep`:声明「本次我改过它,别覆盖」。 */
29
+ export type VolatileField = (typeof VOLATILE_FIELDS)[number];
29
30
  /**
30
31
  * 并发保护:长任务(章节计划生成/正文生成)在内存中持有旧快照,
31
32
  * 期间其他请求可能修改了「易变字段」(道藏/角色库/剧情线/人物志存档/简介/封面)。
32
33
  * 保存前用磁盘最新版本合并这些字段,避免旧快照覆盖新修改(曾导致角色卡丢失)。
33
- * 注意:调用方若自己修改了这些字段,不要使用本函数。
34
+ *
35
+ * ⚠️ `keep` 不是可选的礼貌参数,而是本函数唯一的正确用法保证:
36
+ * 调用方只要自己写过这些字段中的任何一个(哪怕只是 `project.plotlines[0].chapters.push(no)`),
37
+ * **必须**把它列进 `keep`,否则本函数会用磁盘旧值把本轮成果整块覆盖掉。
38
+ * 历史上正是漏了 `facts` / `plotlines`,导致作者复盘回灌的事实与剧情线关联被静默丢弃。
39
+ *
40
+ * @param outputDir - 项目目录。
41
+ * @param project - 待保存的内存项目(原地修改)。
42
+ * @param keep - 本次调用方已自行修改过的易变字段,这些字段不从磁盘覆盖。
34
43
  */
35
- export declare function mergeVolatileFromDisk(outputDir: string, project: ProjectState): void;
44
+ export declare function mergeVolatileFromDisk(outputDir: string, project: ProjectState, keep?: readonly VolatileField[]): void;
36
45
  /** 对一段文本做违禁词硬匹配,返回命中(词/类别/次数)。 */
37
46
  export declare function checkSensitiveText(text: string): Array<{
38
47
  word: string;
@@ -45,6 +54,15 @@ export declare function listChapterFiles(outputDir: string): string[];
45
54
  export declare function syncProjectWithDisk(project: ProjectState, outputDir: string): void;
46
55
  /** Read a chapter's markdown body from disk (undefined when missing). */
47
56
  export declare function readChapterFile(outputDir: string, chapter: ChapterPlan): string | undefined;
57
+ /**
58
+ * 章节正文的**写盘时间**(文件 mtime,ISO)—— 面板「今日产出 / 日环比」的唯一本地事实来源。
59
+ *
60
+ * 为什么不把它写进 novel-project.json:它是**派生数据**。落盘后一旦文件被外部替换、
61
+ * 从备份回拷、或手工改名,存档里的时间戳就开始撒谎;mtime 永远跟着真实文件走。
62
+ * 代价是每次 /status 要对每个有正文的章 stat 一次 —— 本地磁盘上是微秒级,
63
+ * 相对它换来的"今天的产出是真的",这个代价可以接受。
64
+ */
65
+ export declare function chapterWrittenAt(outputDir: string, chapter: ChapterPlan): string | undefined;
48
66
  /** Create a fresh project from an outline. */
49
67
  export declare function createProject(outline: string, outlinePath?: string): ProjectState;
50
68
  /**
@@ -200,7 +218,7 @@ export declare function designPlotlinePlan(ctx: Context, config: NovelConfig, pr
200
218
  * untouched (local revision). Without `target`, the whole chapter is
201
219
  * rewritten. Yields delta text; persists when done.
202
220
  */
203
- export declare function rewriteChapterStream(ctx: Context, config: NovelConfig, project: ProjectState, outputDir: string, chapterNo: number, instructions: string, target?: string): AsyncGenerator<{
221
+ export declare function rewriteChapterStream(ctx: Context, config: NovelConfig, project: ProjectState, outputDir: string, chapterNo: number, instructions: string, target?: string, signal?: AbortSignal): AsyncGenerator<{
204
222
  frame: 'start';
205
223
  } | {
206
224
  frame: 'delta';
@@ -212,7 +230,7 @@ export declare function rewriteChapterStream(ctx: Context, config: NovelConfig,
212
230
  }, void, unknown>;
213
231
  /** Stream a chapter polish (de-AI-ify). Draft-mode: the polished body lands
214
232
  * in `chapter.pendingDraft` and is only applied on draft/apply. */
215
- export declare function polishChapterStream(ctx: Context, config: NovelConfig, project: ProjectState, outputDir: string, chapterNo: number): AsyncGenerator<{
233
+ export declare function polishChapterStream(ctx: Context, config: NovelConfig, project: ProjectState, outputDir: string, chapterNo: number, signal?: AbortSignal): AsyncGenerator<{
216
234
  frame: 'start';
217
235
  } | {
218
236
  frame: 'delta';
@@ -225,7 +243,7 @@ export declare function polishChapterStream(ctx: Context, config: NovelConfig, p
225
243
  /** 统计中文字符数(汉字),与「每章字数」红线口径一致(不含标点/换行)。 */
226
244
  export declare function countHanzi(text: string): number;
227
245
  /** Generate one chapter (streaming). Yields progress frames; persists when done. */
228
- export declare function generateChapterStream(ctx: Context, config: NovelConfig, project: ProjectState, outputDir: string, chapterNo: number): AsyncGenerator<{
246
+ export declare function generateChapterStream(ctx: Context, config: NovelConfig, project: ProjectState, outputDir: string, chapterNo: number, signal?: AbortSignal): AsyncGenerator<{
229
247
  frame: 'start';
230
248
  } | {
231
249
  frame: 'delta';
@@ -242,7 +260,7 @@ export declare function summarizeChapter(ctx: Context, config: NovelConfig, proj
242
260
  * 反向推大纲:从已写章节正文反推出全书总纲(分卷 + 章节要点 + 主线/人物弧线/伏笔清单)。
243
261
  * 两阶段:分批提取章节事件摘要 → 汇总生成大纲。不修改章节/设定,只返回大纲文本。
244
262
  */
245
- export declare function reverseOutlineFromChapters(ctx: Context, config: NovelConfig, project: ProjectState, outputDir: string, onProgress?: (done: number, total: number, phase: string) => void): Promise<string>;
263
+ export declare function reverseOutlineFromChapters(ctx: Context, config: NovelConfig, project: ProjectState, outputDir: string, onProgress?: (done: number, total: number, phase: string) => void, signal?: AbortSignal): Promise<string>;
246
264
  /**
247
265
  * 改编模式 P0:全文分析 → 原文设定卡片 / 可改范围矩阵。
248
266
  * 拆章统计 + 取样正文,让 LLM 一次输出结构化 JSON:
@@ -278,6 +296,7 @@ export declare function rewriteAdaptationBook(ctx: Context, config: NovelConfig,
278
296
  no: number;
279
297
  title: string;
280
298
  }) => void;
299
+ signal?: AbortSignal;
281
300
  }): Promise<{
282
301
  adaptedText: string;
283
302
  rewritten: Array<{
@@ -375,8 +394,6 @@ export interface ImpactItem {
375
394
  * 定位一次改动波及的所有位置。助手在修改后主动调用,做连锁维护。
376
395
  */
377
396
  export declare function analyzeImpact(ctx: Context, config: NovelConfig, project: ProjectState, outputDir: string, change: string): Promise<ImpactItem[]>;
378
- /** 把大世界结构化数据渲染成提示词块(境界体系按顺序强约束)。 */
379
- export declare function renderWorld(world: WorldState | undefined): string;
380
397
  /** 提炼大世界:从大纲 + 道藏生成结构化境界体系/区域/势力。 */
381
398
  export declare function extractWorld(ctx: Context, config: NovelConfig, project: ProjectState): Promise<WorldState>;
382
399
  /**
@@ -8,7 +8,7 @@
8
8
  * 3) **首字耗时**:调用方在拿到第一个文本增量时打点,用于观察模型响应速度。
9
9
  */
10
10
  import type { BlockAssembler, TokenUsage } from '@deepseek-ai/dsh-llm';
11
- import type { LlmLiveFrame, LlmPromptRecord, LlmTokenUsage, LlmUsageSummary } from './protocol.ts';
11
+ import type { LlmLiveFinalPhase, LlmLiveFrame, LlmPromptRecord, LlmTokenUsage, LlmUsageSummary } from './protocol.ts';
12
12
  export type { LlmLiveFrame };
13
13
  type Listener = (frame: LlmLiveFrame) => void;
14
14
  export declare function nextSessionId(): string;
@@ -65,6 +65,6 @@ export declare function markFirstToken(handle: LiveCallHandle): void;
65
65
  export declare function endLiveCall(handle: LiveCallHandle, assembler: BlockAssembler, extra?: {
66
66
  chars?: number;
67
67
  preview?: string;
68
- phase?: 'completed' | 'failed';
68
+ phase?: LlmLiveFinalPhase;
69
69
  error?: string;
70
70
  }): void;
@@ -15,6 +15,17 @@
15
15
  * @returns 可重试时为 true。
16
16
  */
17
17
  export declare function isRetryableLlmError(error: unknown): boolean;
18
+ /**
19
+ * 判断一次失败是「取消」(作者关页面 / 主动中断)还是模型故障。
20
+ *
21
+ * 取消有两种现场:适配器给出 `aborted` 收尾块,或抛出 `AbortError` / 本插件自己的
22
+ * 断开错误。它必须和真实失败分开记账——否则面板「本次运行失败 N 次」会把作者的正常
23
+ * 中断统计成模型不稳定,进而误导备用模型档位判断(与规则 1「用户取消不重试」同一口径)。
24
+ *
25
+ * @param input 收尾块 kind(字符串)或捕获到的异常。
26
+ * @returns 属于取消时为 true。
27
+ */
28
+ export declare function isCancellation(input: unknown): boolean;
18
29
  /**
19
30
  * 判断是否应当切换到备用模型。
20
31
  *
@@ -0,0 +1,43 @@
1
+ /**
2
+ * 提示词分层组装。
3
+ *
4
+ * 同一本书的连续多次调用(逐章正文、审稿、润色、修订…)里,输入的大部分是重复的,
5
+ * 但各块的变化频率差着量级:
6
+ *
7
+ * - **固定层(fixed)**:合规红线、写作硬性要求——永远不变,写死在代码里;
8
+ * - **本书层(book)**:道藏、世界、大纲、资产、提示词槽位——只随作者编辑变,
9
+ * 一次章节生产循环里不变;
10
+ * - **本章层(chapter)**:活跃伏笔、剧情线、时间线锚点、张力、章节骨架——
11
+ * 随创作进度逐章变。
12
+ *
13
+ * 厂商的输入缓存按**前缀**命中:只有排在前面的内容逐字节相同,后面的重复输入才有
14
+ * 机会整段命中。所以组装顺序必须是「固定 → 本书 → 本章」,把次次都变的挤到末尾。
15
+ * 反过来,若静态规则夹在书级内容中段,作者每改一次道藏,它后面的全部内容都跟着
16
+ * 作废、重新计费——分层就是把「不变的内容」尽量往前固定住。
17
+ *
18
+ * 三条纪律由本模块强制,不靠调用方自觉:
19
+ * 1. 三层顺序写死(LAYER_ORDER),调用方只标层、不决定顺序;
20
+ * 2. 同层内稳定排序,保持调用方的书写顺序;
21
+ * 3. 空白块直接丢弃——「无伏笔」时留一段空分隔,会让前后两次调用的前缀在这里分叉。
22
+ *
23
+ * 实话实说:分层只是给缓存命中**创造条件**。厂商是否命中、命中多少,以每次调用
24
+ * 实际上报的用量为准(见 llm-live 的缓存统计),本模块不做任何承诺,也不做
25
+ * 模型预热之类的玄学操作。
26
+ */
27
+ /** 提示词分层:越靠前的内容越稳定。 */
28
+ export type PromptLayer = 'fixed' | 'book' | 'chapter';
29
+ /** 一个待组装进系统提示词的文本块。 */
30
+ export interface PromptBlock {
31
+ /** 该块的变化频率归属哪一层。 */
32
+ layer: PromptLayer;
33
+ /** 块文本(内部可含多行)。 */
34
+ text: string;
35
+ }
36
+ /**
37
+ * 按「固定 → 本书 → 本章」组装系统提示词。
38
+ *
39
+ * 同层内保持书写顺序(稳定排序);空白块丢弃,不在结果里留空段。
40
+ * @param blocks 调用方按书写顺序给出的块(层标可以乱,组装后一定分层有序)。
41
+ * @param separator 块间分隔符(默认换行)。
42
+ */
43
+ export declare function assemblePromptBlocks(blocks: readonly PromptBlock[], separator?: string): string;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * 改编域:原文设定卡片分析、改编方案生成、按映射表重写章节。
3
+ *
4
+ * 改编是「结构性改写」而非换词:分析出的可改矩阵 → 用户勾选 → 映射表 +
5
+ * 保留/改变规则 → 逐章重写。提示词同域放一处,改口径时三份一起看。
6
+ *
7
+ * 本文件同时是提示词注册表的域模块之一(见 catalog.ts)。
8
+ */
9
+ import type { PromptEntry } from './catalog.ts';
10
+ /** 改编 P0 分析:system 侧只声明角色(指令主体在 user 侧,位置效应更不易被长上下文稀释)。 */
11
+ export declare function adaptationAnalyzeSystemPrompt(): string;
12
+ /** 改编 P0 分析:user 侧指令(原文设定卡片 + 可改范围矩阵)。 */
13
+ export declare function adaptationAnalyzeUser(sampleBodies: string[]): string;
14
+ /** 改编方案:system 侧角色声明(指令主体在 user 侧)。 */
15
+ export declare function adaptationProposeSystemPrompt(): string;
16
+ /** 改编方案:user 侧指令 + 用户勾选的改动与已分析的可改矩阵。 */
17
+ export declare function adaptationProposeUser(selLines: string, dimLines: string): string;
18
+ /** 改编重写:按映射表与规则把一章正文结构性重写(不只是换词)。 */
19
+ export declare function adaptationRewriteSystemPrompt(): string;
20
+ /** 本域提示词登记(注册表消费)。 */
21
+ export declare const entries: PromptEntry[];
@@ -0,0 +1,15 @@
1
+ /**
2
+ * 一致性质检域:全书分批质检、改动影响分析。
3
+ *
4
+ * 两者都是「拿道藏 + 事实库 + 正文节选找矛盾」的同族任务,差别在触发方式:
5
+ * 质检是定期全本扫,影响分析是作者改一处后主动扫波及面。
6
+ *
7
+ * 本文件同时是提示词注册表的域模块之一(见 catalog.ts)。
8
+ */
9
+ import type { PromptEntry } from './catalog.ts';
10
+ /** 单批质检:设定 + 事实库 + 该批章节节选 → 矛盾清单。 */
11
+ export declare function auditBatchSystemPrompt(): string;
12
+ /** 影响分析:一处改动 → 全书波及位置清单(设定/大纲/正文/事实库/简介)。 */
13
+ export declare function impactAnalysisSystemPrompt(): string;
14
+ /** 本域提示词登记(注册表消费)。 */
15
+ export declare const entries: PromptEntry[];
@@ -0,0 +1,10 @@
1
+ /**
2
+ * 道藏域:从大纲提炼结构化设定(题材/世界规则/角色卡/红线/风格)。
3
+ *
4
+ * 本文件同时是提示词注册表的域模块之一(见 catalog.ts)。
5
+ */
6
+ import type { PromptEntry } from './catalog.ts';
7
+ /** System prompt for story-bible extraction. */
8
+ export declare function bibleSystemPrompt(): string;
9
+ /** 本域提示词登记(注册表消费)。 */
10
+ export declare const entries: PromptEntry[];
@@ -0,0 +1,40 @@
1
+ /**
2
+ * 提示词注册表(轻量版)。
3
+ *
4
+ * ## 它解决什么问题
5
+ *
6
+ * 提示词散落在 engine.ts 里时,有三件事会慢慢腐烂:
7
+ * 1. **看不见总量**:没人说得清这个插件到底有多少份系统提示、各是干什么的;
8
+ * 2. **改一处忘三处**:合规红线有四份注入点,「反 AI 套话口径」在润色和局部
9
+ * 修订里各写一份,改一边漏一边,两个提示词悄悄漂移;
10
+ * 3. **新人不敢动**:想改审稿维度,先得在 5000 行里 grep 出所有相关文案。
11
+ *
12
+ * 注册表把「有哪些提示词、各自负责什么」变成一份可检索、可测试的清单。
13
+ * 域模块(`./*.ts`,catalog 自身除外)各自导出 `entries` 登记表,此处汇总。
14
+ *
15
+ * ## 纪律(由 tests/prompt-registry.test.ts 强制执行)
16
+ *
17
+ * 1. **目录完整**:src/prompts/ 下每个域文件都必须被本文件 import——
18
+ * 新建了域文件却忘了登记,测试直接红;
19
+ * 2. **双向完整**:登记表里的每条 id 必须以所在域的文件名开头(`域.名称`),
20
+ * 域名与文件域一一对应,没有挂靠的孤条;
21
+ * 3. **id 唯一**:全库不允许重复 id;
22
+ * 4. **短描述**:description 是给人看的索引,不是第二份提示词——必须非空、
23
+ * 不超长,且不得原文照抄提示词首句(那是重复维护两份)。
24
+ *
25
+ * 遥测(哪次调用用了哪条提示词)与导出器暂不做:那需要改调用点传 id,
26
+ * 等真有度量需求再接,避免为假想的功能付出复杂度。
27
+ */
28
+ /** 一条提示词登记。 */
29
+ export interface PromptEntry {
30
+ /** 稳定标识:`域.名称`,全库唯一(域名与 src/prompts/ 下的文件名对应)。 */
31
+ id: string;
32
+ /** 中文短描述:给人看的索引,不进提示词本身。 */
33
+ description: string;
34
+ }
35
+ /** 全库提示词清单(按域名字典序)。 */
36
+ export declare const PROMPT_CATALOG: readonly PromptEntry[];
37
+ /** 查一条提示词的中文描述;未登记返回 undefined。 */
38
+ export declare function describePrompt(id: string): string | undefined;
39
+ /** 全库清单(供测试与将来的导出器使用)。 */
40
+ export declare function listPromptEntries(): readonly PromptEntry[];
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 角色域:角色库提炼(含补漏后缀)、角色当前状态聚合。
3
+ *
4
+ * 本文件同时是提示词注册表的域模块之一(见 catalog.ts)。
5
+ */
6
+ import type { PromptEntry } from './catalog.ts';
7
+ /** AI 从全书提炼角色库:大纲 + 道藏 + 编年录 + 章节摘要 → 结构化角色清单。 */
8
+ export declare function extractRolesSystemPrompt(): string;
9
+ /**
10
+ * 角色补漏后缀(拼在 extractRolesSystemPrompt 之后)。
11
+ *
12
+ * 第一轮偶发漏掉「身份型/功能性角色」(站长/律师/警察等反复出现者),
13
+ * 用这一轮专项补捞;没有遗漏就输出 []。
14
+ */
15
+ export declare function roleCatchUpSuffix(names: string): string;
16
+ /** 角色当前状态聚合:角色名单 + 已确立事实库 → 每角色一句话状态。 */
17
+ export declare function roleStatusSystemPrompt(): string;
18
+ /** 本域提示词登记(注册表消费)。 */
19
+ export declare const entries: PromptEntry[];
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 提示词共享件:内容合规红线。
3
+ *
4
+ * 所有书籍、所有章节无条件生效,优先级高于单书大纲/道藏中的任何设定与作者
5
+ * 自定义红线。原来住在 engine.ts 里,随二期拆分成域模块后迁到此处——
6
+ * 各域模块(writing/review/polish…)都注入它,放共享件里避免循环导入。
7
+ */
8
+ /** 内容合规红线(平台硬性要求)。注入点:章节生成 + 审稿 + 润色 + 局部修订系统提示(命中即 high)。 */
9
+ export declare const COMPLIANCE_REDLINES: ReadonlyArray<string>;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * 拆书与摘要域:源片段笔记、分节分析、单章摘要、书分析。
3
+ *
4
+ * 本文件同时是提示词注册表的域模块之一(见 catalog.ts)。
5
+ */
6
+ import type { PromptEntry } from './catalog.ts';
7
+ /** 拆书阶段一:单章正文 → 结构化笔记(供后续章节级分析复用)。 */
8
+ export declare function breakdownNoteSystemPrompt(): string;
9
+ /** 拆书分节配置项。 */
10
+ export interface BreakdownSection {
11
+ key: string;
12
+ title: string;
13
+ focus: string;
14
+ system: string;
15
+ }
16
+ /**
17
+ * 拆书阶段二:分节分析配置。
18
+ *
19
+ * @param preset - quick(总览/剧情/人物/文风)| standard(+ 商业化卖点)。
20
+ */
21
+ export declare function breakdownSections(preset: 'quick' | 'standard'): BreakdownSection[];
22
+ /** 单章摘要(叙事记忆):供后续章节写作时保持连贯性。 */
23
+ export declare function chapterSummarySystemPrompt(): string;
24
+ /** 书分析/拆书:输入一本书/章节文本 → 卖点/结构/可借鉴点/风险。 */
25
+ export declare function bookAnalysisSystemPrompt(): string;
26
+ /** 本域提示词登记(注册表消费)。 */
27
+ export declare const entries: PromptEntry[];