@waterwx/dsh-novel-forge 1.3.2-alpha → 1.3.3-alpha

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 (53) hide show
  1. package/lib/client.js +555 -466
  2. package/lib/index.js +531 -93
  3. package/lib/types/ai-scan.d.ts +0 -6
  4. package/lib/types/assets.d.ts +0 -2
  5. package/lib/types/author-assets.d.ts +1 -3
  6. package/lib/types/bookshelf.d.ts +8 -3
  7. package/lib/types/client/api.d.ts +1 -0
  8. package/lib/types/client/docx.d.ts +0 -2
  9. package/lib/types/client/locales.d.ts +1 -0
  10. package/lib/types/client/mount.d.ts +0 -2
  11. package/lib/types/client/panel/market-radar-state.d.ts +56 -0
  12. package/lib/types/client/panel/views.d.ts +1 -33
  13. package/lib/types/client/sidebar-entry.d.ts +0 -2
  14. package/lib/types/home.d.ts +11 -0
  15. package/lib/types/index.d.ts +15 -3
  16. package/lib/types/llm-live.d.ts +0 -5
  17. package/lib/types/market-radar-scan.d.ts +0 -1
  18. package/lib/types/model-capability.d.ts +93 -0
  19. package/lib/types/output-budget.d.ts +86 -0
  20. package/lib/types/protocol.d.ts +21 -5
  21. package/lib/types/routes.d.ts +5 -3
  22. package/lib/types/settings-persist.d.ts +59 -0
  23. package/lib/types/stage-contract.d.ts +1 -3
  24. package/package.json +13 -11
  25. package/src/ai-scan.ts +0 -24
  26. package/src/assets.ts +0 -5
  27. package/src/author-assets.ts +4 -22
  28. package/src/bookshelf.ts +13 -7
  29. package/src/client/api.ts +2 -2
  30. package/src/client/docx.ts +0 -5
  31. package/src/client/locales.ts +1 -0
  32. package/src/client/mount.tsx +3 -2
  33. package/src/client/panel/AuthorAssetsView.tsx +1 -1
  34. package/src/client/panel/ImportModal.tsx +1 -1
  35. package/src/client/panel/MarketRadarView.tsx +55 -17
  36. package/src/client/panel/SettingsView.tsx +2 -1
  37. package/src/client/panel/market-radar-state.ts +79 -0
  38. package/src/client/panel/panel.module.css +4 -4
  39. package/src/client/panel/views.tsx +1 -86
  40. package/src/client/sidebar-entry.ts +3 -3
  41. package/src/engine.ts +103 -16
  42. package/src/global-assets.ts +3 -4
  43. package/src/home.ts +33 -0
  44. package/src/index.ts +136 -51
  45. package/src/llm-live.ts +0 -15
  46. package/src/market-radar-scan.ts +2 -8
  47. package/src/model-capability.ts +215 -0
  48. package/src/output-budget.ts +147 -0
  49. package/src/protocol.ts +22 -5
  50. package/src/routes.ts +34 -11
  51. package/src/settings-persist.ts +114 -0
  52. package/src/stage-contract.ts +1 -4
  53. package/src/review-policy.ts +0 -161
@@ -28,9 +28,3 @@ export interface AiScanResult {
28
28
  summary: string;
29
29
  }
30
30
  export declare function scanAiFlavor(text: string): AiScanResult;
31
- /** 扫描前后对比(润色时用) */
32
- export declare function compareAiScan(before: AiScanResult, after: AiScanResult): {
33
- improved: boolean;
34
- deltaScore: number;
35
- details: string[];
36
- };
@@ -23,8 +23,6 @@ export declare const BUILTIN_PLOT_BEATS: PlotBeatTemplate[];
23
23
  export declare function emptyProjectAssets(): ProjectAssets;
24
24
  /** 合并项目资产与内置库:返回「生效的反 AI 规则」(内置全局 + 项目自定义)。 */
25
25
  export declare function effectiveAntiAiRules(assets: ProjectAssets | undefined): AntiAiRule[];
26
- /** 安定 key:内置规则全局基线(globalBaselineEnabled)在新书/旧书一律生效。 */
27
- export declare function ensureGlobalBaseline(assets: ProjectAssets | undefined): ProjectAssets;
28
26
  /** 内置库种子化 upsert(对齐上游 SystemResourceBootstrapService 精神)。
29
27
  * missing_only 仅补齐缺失的全局基线规则;sync_existing 还会按 key 刷新已内置规则的结构化字段。 */
30
28
  export declare function ensureBuiltinAssets(assets: ProjectAssets | undefined, mode?: 'missing_only' | 'sync_existing'): ProjectAssets;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * 作者资产库/总数据(跨书)— 按作者维度聚合的可复用资产。
3
- * 与书架同惯例持久化到 ~/.dsh/dsh-novel-forge-author-assets.json。
3
+ * 与书架同惯例持久化到 <DSH_HOME>/dsh-novel-forge-author-assets.json。
4
4
  * 提供读取/持久化/新增或更新/删除,以及「导入默认」:把书架书的写作资产/角色 +
5
5
  * 内置全局库(题材/反AI规则/风格模板/推进模式)批量沉淀成资产条目(默认库)。
6
6
  */
@@ -11,8 +11,6 @@ export declare function authorAssetsFile(): string;
11
11
  export declare function loadAuthorAssets(): AuthorAssetLibrary;
12
12
  /** 持久化作者资产库。 */
13
13
  export declare function saveAuthorAssets(library: AuthorAssetLibrary): void;
14
- /** 新建一条资产(自动生成 id 与时间戳)。 */
15
- export declare function createAuthorAsset(input: Omit<AuthorStyleAsset, 'id' | 'createdAt' | 'updatedAt'>): AuthorStyleAsset;
16
14
  /** 按 id 更新一条资产;不存在则追加。 */
17
15
  export declare function upsertAuthorAsset(input: AuthorStyleAsset): AuthorAssetLibrary;
18
16
  /** 删除一条资产;返回更新后的资产库。 */
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * 书架(Bookshelf)— 多书管理:一本书记录一个独立输出目录。
3
- * 状态持久化到 ~/.dsh/dsh-novel-forge-bookshelf.json(跟随 dsh 配置惯例)。
3
+ * 状态持久化到 <DSH_HOME>/dsh-novel-forge-bookshelf.json(跟随 dsh 配置惯例)。
4
4
  */
5
5
  import type { BookEntry, BookshelfSnapshot } from './protocol.ts';
6
6
  /** 书架配置文件路径。 */
@@ -42,6 +42,11 @@ export declare function activeBookOutputDir(): string | undefined;
42
42
  * @returns 重指向的条目数。
43
43
  */
44
44
  export declare function repointBookDirs(from: string, to: string): number;
45
- /** 默认输出目录推断:~/.dsh/novels/书名。 */
46
- export declare function defaultOutputDirFor(bookName: string): string;
45
+ /**
46
+ * 新书默认输出目录:`<配置的输出目录>/书名`;未配置时回退 `<DSH_HOME>/novels/书名`。
47
+ * @param bookName - 书名(剔除文件系统非法字符并截断到 40 字)。
48
+ * @param baseDir - settings 里配置的输出目录(未经激活书合并的原始值)。
49
+ * @returns 该书应写入的绝对目录。
50
+ */
51
+ export declare function defaultOutputDirFor(bookName: string, baseDir?: string): string;
47
52
  export {};
@@ -51,6 +51,7 @@ export declare class NovelApi {
51
51
  chapterSave(no: number, text: string, report?: ReviewReport): Promise<import('../protocol.ts').ChapterSaveResponse>;
52
52
  patchConfig(patch: ConfigPatch): Promise<{
53
53
  config: NovelConfig;
54
+ settingsWarning?: string;
54
55
  }>;
55
56
  /** 输出目录迁移:dryRun=true 仅预览目录内容;确认时传 to 搬迁。 */
56
57
  moveOutputDir(to?: string, dryRun?: boolean): Promise<import('../protocol.ts').MoveOutputDirResponse>;
@@ -13,5 +13,3 @@
13
13
  */
14
14
  /** Extract plain text from a docx buffer: one line per <w:p> paragraph. */
15
15
  export declare function extractDocxTextFromBuffer(buffer: ArrayBuffer | Uint8Array): string;
16
- /** Read a File as ArrayBuffer. */
17
- export declare function readFileAsArrayBuffer(file: File): Promise<ArrayBuffer>;
@@ -135,6 +135,7 @@ export declare const zh: {
135
135
  readonly 'settings.autoReviewAfterReviseHint': "按意见修订/润色产出草稿后自动附带一次 AI 审查,直接显示新稿评分与剩余问题(每章约 2-3k token,可关)";
136
136
  readonly 'settings.save': "保存设置";
137
137
  readonly 'settings.saved': "设置已保存";
138
+ readonly 'settings.savedUnpersisted': "设置已生效,但未能持久化(宿主未登记本插件的配置段),重启后会回到原值。";
138
139
  readonly 'settings.openFolder': "打开输出文件夹";
139
140
  readonly 'settings.exportTxt': "导出 TXT";
140
141
  readonly 'settings.exportMd': "导出 Markdown";
@@ -1,7 +1,5 @@
1
1
  import type { NovelApi } from './api.ts';
2
2
  import type { PanelController } from './panel/controller.ts';
3
- /** The injected panel container. */
4
- export declare const PANEL_VIEW_SELECTOR = "[data-dsh-novelforge-view]";
5
3
  /**
6
4
  * Mount the panel React tree into the center column and bind visibility to
7
5
  * the controller.
@@ -0,0 +1,56 @@
1
+ /**
2
+ * 热门题材雷达的界面状态规则。
3
+ *
4
+ * 职责边界:本文件只回答「界面上此刻能不能点」,不持有任何数据。抽成纯函数的
5
+ * 理由是它可以离线测——组件渲染不好测,判定逻辑好测。
6
+ *
7
+ * 下面先用**独立表述的验收条件**写清楚要什么,再由两个函数实现;这样即使日后
8
+ * 需求变化,改动方向也是照着条件走,而不是照着某个外部实现的形状走。
9
+ *
10
+ * R1 扫榜或分析进行中,不得改动作品勾选(否则分析输入与界面不一致);
11
+ * R2 未勾选任何作品时不能开始分析;
12
+ * R3 已经有报告之后,仍允许改选作品并**再次**分析 —— 不得退化成"只能查看";
13
+ * R4 信号勾选只在确实换了一份报告时复位;同一份报告的重渲染不得冲掉作者手选。
14
+ */
15
+ /** 雷达三个入口此刻的可用性,以及分析按钮该显示哪种文案。 */
16
+ export interface RadarControls {
17
+ /** 扫榜按钮是否可点。 */
18
+ canScan: boolean;
19
+ /** 作品勾选(含全选 / 清空)是否可改。 */
20
+ canSelectCandidates: boolean;
21
+ /** 分析按钮是否可点。 */
22
+ canAnalyze: boolean;
23
+ /**
24
+ * 分析按钮的文案标识:
25
+ * - `wait`:还在扫榜,先等榜单;
26
+ * - `pick`:还没勾选作品;
27
+ * - `first`:首次分析;
28
+ * - `again`:已有报告,这次是再出一份。
29
+ */
30
+ analyzeLabel: 'wait' | 'pick' | 'first' | 'again';
31
+ }
32
+ /**
33
+ * 实现 R1-R3:判定三个入口的可用性。
34
+ * @param state - 当前界面状态。
35
+ * @returns 各入口可用性与分析按钮文案标识。
36
+ */
37
+ export declare function radarControls(state: {
38
+ /** 正在抓榜单。 */
39
+ scanning: boolean;
40
+ /** 正在做 AI 分析。 */
41
+ analyzing: boolean;
42
+ /** 已勾选的作品数。 */
43
+ pickedCount: number;
44
+ /** 界面上是否已有可查看的报告。 */
45
+ hasReport: boolean;
46
+ }): RadarControls;
47
+ /**
48
+ * 实现 R4:本次报告是否**不是**勾选当前所依据的那一份。
49
+ *
50
+ * 传入的是上一次已经据以复位过的报告标识:相同 → 不动作者勾选;不同 → 勾选所依据
51
+ * 的信号集合已经变了,需要按新报告重新给出推荐勾选。
52
+ * @param selectionBasis - 勾选当前依据的报告标识(空串表示尚未复位过)。
53
+ * @param incoming - 本次拿到的报告标识(空串表示旧版产物没有该标识)。
54
+ * @returns 需要按新报告重置勾选时为 true。
55
+ */
56
+ export declare function isDifferentReport(selectionBasis: string, incoming: string): boolean;
@@ -1,5 +1,5 @@
1
1
  import type { ReactElement } from 'react';
2
- import type { AuditIssue, Plotline, PlotlineHealthReport, PlotlinePlan, RoleRecord, RoleStatusCard } from '../../protocol.ts';
2
+ import type { Plotline, PlotlineHealthReport, PlotlinePlan, RoleRecord, RoleStatusCard } from '../../protocol.ts';
3
3
  /** 滑轨胶囊导航(B5-5):active 高亮为滑动指示块,随选中项平移。 */
4
4
  export declare function SlideNav<T extends string>(props: {
5
5
  ariaLabel: string;
@@ -10,33 +10,6 @@ export declare function SlideNav<T extends string>(props: {
10
10
  active: T;
11
11
  onSelect: (id: T) => void;
12
12
  }): JSX.Element;
13
- /** 统计格:状态摘要条 / 资产健康通用。 */
14
- export declare function StatCell(props: {
15
- label: string;
16
- value: string;
17
- detail: string;
18
- /** 值颜色(可选)。 */
19
- valueColor?: string;
20
- /** 值字号覆盖(如长文本用 13)。 */
21
- valueFontSize?: number;
22
- /** detail 悬浮提示(可选)。 */
23
- detailTitle?: string;
24
- }): ReactElement;
25
- /** 待办队列行。 */
26
- export declare function TodoRow(props: {
27
- tone: 'danger' | 'warning' | 'info' | 'success';
28
- title: string;
29
- description: string;
30
- actionLabel: string;
31
- disabled: boolean;
32
- onAction: () => void;
33
- }): ReactElement;
34
- /** 全书质检问题行。 */
35
- export declare function AuditIssueRow(props: {
36
- issue: AuditIssue;
37
- disabled: boolean;
38
- onFix: () => void;
39
- }): ReactElement;
40
13
  /** 剧情线卡片(列表主体)。 */
41
14
  export declare function PlotlineCard(props: {
42
15
  line: Plotline;
@@ -88,8 +61,3 @@ export declare function EmptyState(props: {
88
61
  hint?: string;
89
62
  compact?: boolean;
90
63
  }): ReactElement;
91
- /** 骨架屏基元:rows 条微光占位行;widths 指定各行宽度档位,循环取用。 */
92
- export declare function SkeletonLines(props: {
93
- rows?: number;
94
- widths?: Array<'full' | 'wide' | 'half'>;
95
- }): ReactElement;
@@ -4,8 +4,6 @@
4
4
  * sibling plugin entries and self-heals via MutationObserver.
5
5
  */
6
6
  import type { PanelController } from './panel/controller.ts';
7
- /** Stable data attribute identifying the injected entry row. */
8
- export declare const ENTRY_SELECTOR = "[data-dsh-novelforge-entry]";
9
7
  /**
10
8
  * Mount the sidebar entry, waiting for the shell and self-healing on
11
9
  * re-renders.
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Absolute Harness home for this process.
3
+ * @returns the normalized home directory.
4
+ */
5
+ export declare function dshHome(): string;
6
+ /**
7
+ * Join path segments onto the Harness home.
8
+ * @param segments - path segments appended to the home.
9
+ * @returns the normalized absolute joined path.
10
+ */
11
+ export declare function dshHomePath(...segments: string[]): string;
@@ -15,8 +15,14 @@ export declare const name = "novel-forge";
15
15
  export declare const inject: string[];
16
16
  /**
17
17
  * Settings namespace of the novel-forge capability — the section the web
18
- * settings surface edits. Spelled here rather than imported: the browser half
19
- * spells the same value and must not depend on a Host package.
18
+ * settings surface edits.
19
+ *
20
+ * Legacy fallback only: from Harness 0.2 the Loader derives the section from this
21
+ * plugin's exported `Config`, and addresses it by the **entry id** it assigned in
22
+ * the active profile. That id is not guaranteed to be this name (bundle rows may
23
+ * be given a generated id), so the live id is read from the context instead —
24
+ * see {@link novelSettingsNamespace}. The constant stays for the browser half and
25
+ * for installations whose entry id really is this spelling.
20
26
  */
21
27
  export declare const NOVEL_SETTINGS_NAMESPACE: "dsh-novel-forge";
22
28
  /** Plugin config, validated by the same-named schemastery schema. */
@@ -68,9 +74,15 @@ export interface Config {
68
74
  /** 是否启用改编模式(默认关闭)。 */
69
75
  enableAdaptMode?: boolean;
70
76
  }
77
+ /**
78
+ * Plugin config schema.
79
+ *
80
+ * Every field is live-editable (see {@link live}): the plugin re-reads the
81
+ * settings section on every request, so a change must apply without remounting.
82
+ */
71
83
  export declare const Config: z<Config>;
72
84
  /** Model-facing announcement: plugin presence, capabilities, and limits. */
73
- export declare const NOVEL_GUIDANCE = "\u672C\u673A\u5DF2\u5B89\u88C5 dsh-novel-forge \u63D2\u4EF6\uFF08AI \u7F16\u8BD1\u5C0F\u8BF4\u5DE5\u4F5C\u53F0\uFF09\uFF1A\u4FA7\u8FB9\u680F\u300C\u5C0F\u8BF4\u5DE5\u574A\u300D\u5165\u53E3\u3002\u80FD\u529B\uFF1A\u8BFB\u53D6 docx \u5927\u7EB2\u3001\u7C98\u8D34\u5927\u7EB2\u6587\u672C\u6216\u628A txt \u5168\u672C\u62C6\u7AE0\u5BFC\u5165\uFF1B\u7528 LLM \u63D0\u70BC\u9053\u85CF\uFF08\u4EBA\u8BBE/\u4E16\u754C\u89C2/\u91D1\u624B\u6307\u89C4\u5219/\u5199\u4F5C\u7EA2\u7EBF\uFF09\u4E0E\u5927\u4E16\u754C\uFF08\u5883\u754C/\u533A\u57DF/\u52BF\u529B\uFF09\uFF1B\u751F\u6210\u5377\u8BA1\u5212\u4E0E\u7ED3\u6784\u5316\u7AE0\u8282\u8BA1\u5212\uFF08\u672C\u7AE0\u76EE\u6807/\u5267\u60C5\u8981\u70B9/\u5FC5\u8FBE\u9879/\u4E49\u52A1\u5408\u7EA6/\u7AE0\u672B\u94A9\u5B50\uFF09\uFF1B\u9010\u7AE0\u751F\u6210 3000-4000 \u5B57\u6B63\u6587\u5E76\u4FDD\u5B58\u4E3A Markdown\uFF08\u9ED8\u8BA4\u8F93\u51FA\u5230\u7528\u6237\u4E3B\u76EE\u5F55 ~/.dsh/novels\uFF09\uFF1B\u6BCF\u7AE0\u81EA\u52A8\u751F\u6210\u6458\u8981\u4E0E\u7F16\u5E74\u5F55\u4E8B\u5B9E\u3001\u62BD\u53D6\u6545\u4E8B\u65F6\u95F4\u7EBF\uFF08\u6545\u4E8B\u5185\u65F6\u95F4/\u5730\u70B9/\u5728\u573A\u89D2\u8272/\u4E8B\u4EF6\uFF09\u3001\u81EA\u52A8 AI \u5BA1\u7A3F\uFF08\u4EBA\u8BBE/\u8BBE\u5B9A/\u7EA2\u7EBF/\u6587\u7B14/\u723D\u70B9/\u903B\u8F91/\u53CD AI/\u5448\u73B0/\u5408\u89C4\u4E5D\u7EF4\uFF09\u4E0E\u4F5C\u8005\u590D\u76D8\uFF1B\u652F\u6301\u6309\u5BA1\u7A3F\u610F\u89C1\u5408\u5E76\u4FEE\u8BA2\uFF08\u5BA1\u7A3F high + \u65F6\u95F4\u7EBF\u77DB\u76FE + \u5F20\u529B\u504F\u5DEE\u5408\u6210\u4E00\u8F6E\uFF0C\u6BCF\u7AE0\u6700\u591A\u4E24\u8F6E\u540E\u8F6C\u4EBA\u5DE5\uFF09\u3001\u53BB AI \u5473\u6DA6\u8272\u3001\u7AE0\u8282\u5386\u53F2\u7248\u672C\u56DE\u6EDA\u3001\u6697\u7EBF\uFF08\u4F0F\u7B14\uFF09\u7BA1\u7406\u3001\u5267\u60C5\u7EBF\u4E0E\u89D2\u8272\u5E93\u3001\u5F20\u529B\u66F2\u7EBF\u3001\u5168\u4E66\u4E00\u81F4\u6027\u8D28\u68C0\u4E0E\u654F\u611F\u8BCD\u626B\u63CF\u3001\u5206\u8303\u56F4\u5BFC\u51FA\u4E0E\u9879\u76EE\u5907\u4EFD\u3001\u6279\u91CF\u8FDE\u5199\uFF08\u751F\u4EA7\u5355\uFF0C\u53EF\u6682\u505C/\u7EED\u8DD1\uFF09\u3002\u9650\u5236\uFF1A\u6240\u6709 AI \u64CD\u4F5C\u6D88\u8017 LLM API \u989D\u5EA6\uFF1B\u8F93\u51FA\u76EE\u5F55\u4E0E\u6A21\u578B\u53EF\u5728\u63D2\u4EF6\u8BBE\u7F6E\u4E2D\u4FEE\u6539\uFF1B\u7AE0\u8282\u6B63\u6587\u8D28\u91CF\u53D6\u51B3\u4E8E\u5927\u7EB2\u5B8C\u6574\u5EA6\uFF1B\u5F20\u529B/\u65F6\u95F4\u7EBF\u95EE\u9898\u9ED8\u8BA4\u53EA\u662F\u5EFA\u8BAE\uFF08\u5199\u65E5\u5FD7\u4E0E\u5F85\u529E\uFF09\uFF0C\u4E0D\u4F1A\u81EA\u52A8\u6539\u6B63\u6587\u3002\u7528\u6237\u63D0\u5230\u300C\u5C0F\u8BF4 / \u5927\u7EB2 / \u5199\u5C0F\u8BF4 / \u7AE0\u8282 / \u5BA1\u7A3F / \u6DA6\u8272\u300D\u65F6\u5373\u6307\u672C\u63D2\u4EF6\uFF0C\u8BF7\u636E\u6B64\u534F\u4F5C\u3002";
85
+ export declare const NOVEL_GUIDANCE = "\u672C\u673A\u5DF2\u5B89\u88C5 dsh-novel-forge \u63D2\u4EF6\uFF08AI \u7F16\u8BD1\u5C0F\u8BF4\u5DE5\u4F5C\u53F0\uFF09\uFF1A\u4FA7\u8FB9\u680F\u300C\u5C0F\u8BF4\u5DE5\u574A\u300D\u5165\u53E3\u3002\u80FD\u529B\uFF1A\u8BFB\u53D6 docx \u5927\u7EB2\u3001\u7C98\u8D34\u5927\u7EB2\u6587\u672C\u6216\u628A txt \u5168\u672C\u62C6\u7AE0\u5BFC\u5165\uFF1B\u7528 LLM \u63D0\u70BC\u9053\u85CF\uFF08\u4EBA\u8BBE/\u4E16\u754C\u89C2/\u91D1\u624B\u6307\u89C4\u5219/\u5199\u4F5C\u7EA2\u7EBF\uFF09\u4E0E\u5927\u4E16\u754C\uFF08\u5883\u754C/\u533A\u57DF/\u52BF\u529B\uFF09\uFF1B\u751F\u6210\u5377\u8BA1\u5212\u4E0E\u7ED3\u6784\u5316\u7AE0\u8282\u8BA1\u5212\uFF08\u672C\u7AE0\u76EE\u6807/\u5267\u60C5\u8981\u70B9/\u5FC5\u8FBE\u9879/\u4E49\u52A1\u5408\u7EA6/\u7AE0\u672B\u94A9\u5B50\uFF09\uFF1B\u9010\u7AE0\u751F\u6210 3000-4000 \u5B57\u6B63\u6587\u5E76\u4FDD\u5B58\u4E3A Markdown\uFF08\u9ED8\u8BA4\u8F93\u51FA\u5230\u63D2\u4EF6\u8BBE\u7F6E\u91CC\u300C\u8F93\u51FA\u76EE\u5F55\u300D\u6307\u5B9A\u7684\u4F4D\u7F6E\uFF0C\u672A\u914D\u7F6E\u65F6\u843D\u5230 Harness \u6570\u636E\u76EE\u5F55\u4E0B\u7684 novels/\u4E66\u540D\uFF09\uFF1B\u6BCF\u7AE0\u81EA\u52A8\u751F\u6210\u6458\u8981\u4E0E\u7F16\u5E74\u5F55\u4E8B\u5B9E\u3001\u62BD\u53D6\u6545\u4E8B\u65F6\u95F4\u7EBF\uFF08\u6545\u4E8B\u5185\u65F6\u95F4/\u5730\u70B9/\u5728\u573A\u89D2\u8272/\u4E8B\u4EF6\uFF09\u3001\u81EA\u52A8 AI \u5BA1\u7A3F\uFF08\u4EBA\u8BBE/\u8BBE\u5B9A/\u7EA2\u7EBF/\u6587\u7B14/\u723D\u70B9/\u903B\u8F91/\u53CD AI/\u5448\u73B0/\u5408\u89C4\u4E5D\u7EF4\uFF09\u4E0E\u4F5C\u8005\u590D\u76D8\uFF1B\u652F\u6301\u6309\u5BA1\u7A3F\u610F\u89C1\u5408\u5E76\u4FEE\u8BA2\uFF08\u5BA1\u7A3F high + \u65F6\u95F4\u7EBF\u77DB\u76FE + \u5F20\u529B\u504F\u5DEE\u5408\u6210\u4E00\u8F6E\uFF0C\u6BCF\u7AE0\u6700\u591A\u4E24\u8F6E\u540E\u8F6C\u4EBA\u5DE5\uFF09\u3001\u53BB AI \u5473\u6DA6\u8272\u3001\u7AE0\u8282\u5386\u53F2\u7248\u672C\u56DE\u6EDA\u3001\u6697\u7EBF\uFF08\u4F0F\u7B14\uFF09\u7BA1\u7406\u3001\u5267\u60C5\u7EBF\u4E0E\u89D2\u8272\u5E93\u3001\u5F20\u529B\u66F2\u7EBF\u3001\u5168\u4E66\u4E00\u81F4\u6027\u8D28\u68C0\u4E0E\u654F\u611F\u8BCD\u626B\u63CF\u3001\u5206\u8303\u56F4\u5BFC\u51FA\u4E0E\u9879\u76EE\u5907\u4EFD\u3001\u6279\u91CF\u8FDE\u5199\uFF08\u751F\u4EA7\u5355\uFF0C\u53EF\u6682\u505C/\u7EED\u8DD1\uFF09\u3002\u9650\u5236\uFF1A\u6240\u6709 AI \u64CD\u4F5C\u6D88\u8017 LLM API \u989D\u5EA6\uFF1B\u8F93\u51FA\u76EE\u5F55\u4E0E\u6A21\u578B\u53EF\u5728\u63D2\u4EF6\u8BBE\u7F6E\u4E2D\u4FEE\u6539\uFF1B\u7AE0\u8282\u6B63\u6587\u8D28\u91CF\u53D6\u51B3\u4E8E\u5927\u7EB2\u5B8C\u6574\u5EA6\uFF1B\u5F20\u529B/\u65F6\u95F4\u7EBF\u95EE\u9898\u9ED8\u8BA4\u53EA\u662F\u5EFA\u8BAE\uFF08\u5199\u65E5\u5FD7\u4E0E\u5F85\u529E\uFF09\uFF0C\u4E0D\u4F1A\u81EA\u52A8\u6539\u6B63\u6587\u3002\u7528\u6237\u63D0\u5230\u300C\u5C0F\u8BF4 / \u5927\u7EB2 / \u5199\u5C0F\u8BF4 / \u7AE0\u8282 / \u5BA1\u7A3F / \u6DA6\u8272\u300D\u65F6\u5373\u6307\u672C\u63D2\u4EF6\uFF0C\u8BF7\u636E\u6B64\u534F\u4F5C\u3002";
74
86
  /** Resolve a config-like value into the full runtime config. */
75
87
  export declare function resolveConfig(value: Partial<Config> | undefined): NovelConfig;
76
88
  /**
@@ -10,13 +10,10 @@
10
10
  import type { BlockAssembler, TokenUsage } from '@deepseek-ai/dsh-llm';
11
11
  import type { LlmLiveFrame, LlmPromptRecord, LlmTokenUsage, LlmUsageSummary } from './protocol.ts';
12
12
  export type { LlmLiveFrame };
13
- export type LlmLivePhase = 'requesting' | 'streaming' | 'completed' | 'failed';
14
13
  type Listener = (frame: LlmLiveFrame) => void;
15
14
  export declare function nextSessionId(): string;
16
15
  export declare function subscribeLiveFeed(listener: Listener): () => void;
17
16
  export declare function emitLive(frame: LlmLiveFrame): void;
18
- export declare function liveLatest(): LlmLiveFrame[];
19
- export declare function clearLiveFeed(): void;
20
17
  /** 把 dsh-llm 的 usage 转成线上形状。 */
21
18
  export declare function toLiveUsage(usage: TokenUsage | undefined): LlmTokenUsage | undefined;
22
19
  /** 本次运行的用量汇总(按耗时降序排列用途,便于看谁最贵)。 */
@@ -33,8 +30,6 @@ export declare function recordPrompt(input: {
33
30
  }): LlmPromptRecord;
34
31
  /** 取一条 Prompt 记录(不存在返回 undefined)。 */
35
32
  export declare function livePrompt(sessionId: string): LlmPromptRecord | undefined;
36
- /** 清空 Prompt 记录。 */
37
- export declare function clearLivePrompts(): void;
38
33
  /** 一次调用的打点句柄。 */
39
34
  export interface LiveCallHandle {
40
35
  sessionId: string;
@@ -15,5 +15,4 @@ export interface MarketScanResult {
15
15
  scannedAt: string;
16
16
  groups: MarketRadarGroup[];
17
17
  }
18
- export declare function getLastMarketScan(): MarketScanResult | null;
19
18
  export declare function scanMarketRanking(platforms?: string[]): Promise<MarketScanResult>;
@@ -0,0 +1,93 @@
1
+ /**
2
+ * 模型参数能力兼容层。
3
+ *
4
+ * 设计原则:**界面上保存的温度/思考档位是用户偏好,不是最终请求值。** 构造请求前
5
+ * 必须经过本层收敛到厂商允许的范围 —— 否则换模型、走自定义中转、或厂商改了约束,
6
+ * 就会出现「换个 provider 就报参数错误」这类故障。
7
+ *
8
+ * 两条硬要求:
9
+ * 1. **按模型身份判断,不按 provider 判断**。同一个模型经由自定义中转调用时,
10
+ * provider 名可能完全自定义,但参数约束属于模型本身;把判断写成
11
+ * `provider === '某个厂商' && ...` 会让走中转的调用绕过约束。
12
+ * 2. **约束要有出处**。每条规则的来源写在规则上,不靠猜。下列规则来自厂商官方
13
+ * 《模型参数参考》(https://platform.kimi.com/docs/api/models-overview.md):
14
+ * · kimi-k3 : temperature 固定 1.0;支持顶层 reasoning_effort
15
+ * · kimi-k2.7-code : temperature 固定 1.0;不支持 reasoning_effort(用 thinking,且不可禁用)
16
+ * · kimi-k2.6 : thinking 固定 1.0 / 非 thinking 固定 0.6;不支持 reasoning_effort
17
+ * 官方同时建议:这类模型**不要显式传入 temperature**。本层因此优先「不传」,
18
+ * 仅在调用方无法省略该参数时才钳到允许值。
19
+ */
20
+ /** 一次请求的参数约束。 */
21
+ export interface ModelCapability {
22
+ /** 该模型只接受这一个温度值(如 Kimi 全系),缺省表示不约束。 */
23
+ fixedTemperature?: number;
24
+ /** 温度允许的下界(默认 0)。 */
25
+ minTemperature?: number;
26
+ /** 温度允许的上界(默认 2,主流 OpenAI 兼容区间)。 */
27
+ maxTemperature?: number;
28
+ /**
29
+ * 是否支持 OpenAI 兼容的 `reasoning_effort`。
30
+ * false 表示**不得下发**该参数(如 Kimi K2.x 用的是 `thinking`),下发会被厂商拒绝。
31
+ */
32
+ supportsReasoning?: boolean;
33
+ /**
34
+ * 推理控制方式:`reasoning_effort`(OpenAI 兼容顶层参数)或 `thinking`(Kimi K2.x 专属)。
35
+ * 运行时若适配层支持,应按此选择参数名;仅为诊断与后续适配保留。
36
+ */
37
+ reasoningControl?: 'reasoning-effort' | 'thinking' | 'none';
38
+ /** 严格结构化输出必须关闭思考(覆盖用户偏好)。 */
39
+ reasoningForcedOff?: boolean;
40
+ /** 命中的规则名,用于日志与测试断言。 */
41
+ rule: string;
42
+ /** 约束出处(文档 URL 或说明),便于复核与更新。 */
43
+ source: string;
44
+ }
45
+ /**
46
+ * 把模型 id 归一化为「模型身份」。
47
+ *
48
+ * 处理:去空白与首尾斜杠、小写、剥掉供应商前缀(`kimi/kimi-k3` → `kimi-k3`)。
49
+ * @param model - 原始模型 id。
50
+ * @returns 归一化后的身份(小写、无前缀)。
51
+ */
52
+ export declare function canonicalModelId(model: string | undefined): string;
53
+ /**
54
+ * 解析一条模型的能力约束。
55
+ * @param model - 模型 id(可能带供应商前缀)。
56
+ * @returns 命中的能力;无约束时返回 undefined。
57
+ */
58
+ export declare function resolveModelCapability(model: string | undefined): ModelCapability | undefined;
59
+ /** 一次待收敛的请求参数。 */
60
+ export interface RequestParams {
61
+ temperature: number;
62
+ /** 请求方(含用户偏好)希望使用的思考档位;undefined 表示不涉及。 */
63
+ reasoningEffort?: 'off' | 'low' | 'high' | 'max';
64
+ }
65
+ /** 收敛结果:最终请求值 + 发生了哪些调整(便于日志与诊断)。 */
66
+ export interface ConvergedParams {
67
+ /**
68
+ * 最终要下发的温度;`undefined` 表示**应当省略该参数**。
69
+ * 厂商建议这类模型不要显式传 temperature 时,本层返回 undefined。
70
+ */
71
+ temperature: number | undefined;
72
+ reasoningEffort?: 'off' | 'low' | 'high' | 'max';
73
+ /** 被本层改掉的项,形如 `temperature: 0.3 -> 省略`。 */
74
+ adjustments: string[];
75
+ /** 命中的规则名。 */
76
+ rule?: string;
77
+ /** 约束出处。 */
78
+ source?: string;
79
+ }
80
+ /**
81
+ * 把请求参数收敛到模型允许的范围。
82
+ *
83
+ * @param params - 调用方希望使用的参数。
84
+ * @param model - 实际要调用的模型 id(应传**实际模型**;换备用模型后需重算)。
85
+ * @param options - `structured: true` 表示这是严格结构化输出任务;
86
+ * `omitFixedTemperature: true` 表示调用方可以不传温度(推荐,厂商建议如此),
87
+ * 此时固定温度会收敛为 undefined 而不是钳到固定值。
88
+ * @returns 最终请求值与被调整项。
89
+ */
90
+ export declare function convergeRequestParams(params: RequestParams, model: string | undefined, options?: {
91
+ structured?: boolean;
92
+ omitFixedTemperature?: boolean;
93
+ }): ConvergedParams;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * 结构化输出预算与字段长度合同。
3
+ *
4
+ * 背景:本插件此前给每个 LLM 调用写死一个 `Math.max(config.maxTokens, N)`。短任务
5
+ * 与长任务拿到同一个额度,长任务(分卷、拆章、逐章规划)**额度不随规模增长**,
6
+ * 截断后只能抛「请增大 maxTokens」。这里把额度改成按规模推算,并给结构化字段
7
+ * 定长度上限,让预算花在结构上而不是被自由发挥吃光。
8
+ *
9
+ * 设计取舍:结构化输出的额度应当**随本次要产出的条目数增长**,并由硬上限兜住,
10
+ * 否则要么短任务浪费额度,要么长任务写到一半被截断。字段长度上限同时写进提示词
11
+ * 与解析端,两处共用一张表,避免漂移。
12
+ *
13
+ * 一条硬纪律:**不要用 JSON repair 去补被截断的结构**。repair 只能修已有结构,
14
+ * 补不出尚未输出的条目;在服务层手工补齐缺失项,等于把不完整的规划伪装成可用
15
+ * 资产。预算给够 + 输出够紧凑,才是正解。
16
+ */
17
+ /** 一次调用的输出规模描述:给出能决定输出体量的那个量。 */
18
+ export interface OutputScope {
19
+ /** 预期卷数(分卷/卷战略类)。 */
20
+ volumes?: number;
21
+ /** 预期章节数(章节列表/批量拆章类)。 */
22
+ chapters?: number;
23
+ /** 预期角色数(角色阵容/资产提炼类)。 */
24
+ characters?: number;
25
+ /** 预期条目数(伏笔、题材、资产等清单类)。 */
26
+ items?: number;
27
+ /** 需要覆盖的正文规模(字),用于拆书/反推这类按输入体量走的任务。 */
28
+ sourceChars?: number;
29
+ }
30
+ /** 一类结构化任务的预算规格。 */
31
+ export interface OutputBudgetSpec {
32
+ /** 无视规模也要给到的下限。 */
33
+ floor: number;
34
+ /** 规模为 0 时的起步额度。 */
35
+ base: number;
36
+ /** 每单位规模追加的额度。 */
37
+ perUnit: number;
38
+ /** 硬上限:超过它只会让模型啰嗦,不会让结构更完整。 */
39
+ ceiling: number;
40
+ }
41
+ /** 结构化任务分组——按"输出里每个单位要写多少字段"分档,而不是按业务名。 */
42
+ export type OutputBudgetKind = 'volume-strategy' | 'chapter-list' | 'character-cast' | 'item-list' | 'analysis';
43
+ /**
44
+ * 取出与任务类型相关的那一个规模量。
45
+ * @param kind - 任务类型。
46
+ * @param scope - 规模描述。
47
+ * @returns 单位数;分析类返回千字单位(向上取整)。
48
+ */
49
+ export declare function scopeUnits(kind: OutputBudgetKind, scope: OutputScope): number;
50
+ /**
51
+ * 按规模推算输出额度。
52
+ * @param kind - 结构化任务类型。
53
+ * @param scope - 本次任务的规模(缺省时按最小规模处理)。
54
+ * @returns 建议的 `maxTokens`,落在该类型的 [floor, ceiling] 内。
55
+ */
56
+ export declare function resolveOutputBudget(kind: OutputBudgetKind, scope?: OutputScope): number;
57
+ /** 读取某类任务的规格(供诊断与测试断言)。 */
58
+ export declare function outputBudgetSpec(kind: OutputBudgetKind): OutputBudgetSpec;
59
+ /**
60
+ * 结构化字段长度合同 —— 与 prompt 里的长度声明必须逐项一致。
61
+ *
62
+ * 两边同步的理由:只写进 schema,模型不知道就会超长被拒;只写进 prompt,模型
63
+ * 偶尔超长没人拦。本表是单一数据源。
64
+ */
65
+ export declare const STRUCTURED_FIELD_LIMITS: {
66
+ /** 分卷战略:书级阶梯类字段。 */
67
+ readonly volumeLadder: 160;
68
+ /** 分卷战略:每卷定位标签。 */
69
+ readonly volumeRoleLabel: 32;
70
+ /** 分卷战略:每卷核心回报 / 升级焦点。 */
71
+ readonly volumeReward: 64;
72
+ /** 分卷战略:不确定项原因。 */
73
+ readonly volumeUncertaintyReason: 64;
74
+ /** 章节计划:单章标题。 */
75
+ readonly chapterTitle: 40;
76
+ /** 章节计划:单章摘要。 */
77
+ readonly chapterSummary: 300;
78
+ /** 章节计划:本章目标。 */
79
+ readonly chapterGoal: 200;
80
+ };
81
+ /**
82
+ * 把长度合同渲染成可直接塞进 system prompt 的一句话,避免 prompt 与表漂移。
83
+ * @param entries - 要声明的字段(描述 + 上限),按声明顺序渲染。
84
+ * @returns 形如「为保证输出完整:A 不超过 160 字;B 不超过 64 字。」的中文说明。
85
+ */
86
+ export declare function renderLengthContract(entries: ReadonlyArray<readonly [string, number]>): string;
@@ -225,7 +225,7 @@ export interface BookImportTextRequest {
225
225
  text?: string;
226
226
  /** 模式二:原文件名(txt/md),用于推断书名与显示。 */
227
227
  fileName?: string;
228
- /** 输出目录;缺省为 ~/.dsh/novels/书名。 */
228
+ /** 输出目录;缺省为「输出目录」设置下的书名子目录。 */
229
229
  outputDir?: string;
230
230
  }
231
231
  /** POST /bookshelf/import-text 响应。 */
@@ -340,7 +340,7 @@ export interface AuthorReview {
340
340
  /** 复盘时间。 */
341
341
  reviewedAt: string;
342
342
  }
343
- /** 审稿维度(结构化定位问题类别;与 review-policy.ts 的 REVIEW_DIMENSIONS 对齐)。 */
343
+ /** 审稿维度(结构化定位问题类别)。权威清单是 reviewSystemPrompt 里的九条维度。 */
344
344
  export type ReviewDimension = 'character' | 'setting' | 'redline' | 'writing' | 'pacing' | 'logic' | 'anti-ai' | 'presentation' | 'compliance';
345
345
  /** One review finding. */
346
346
  /** 问题严重度(审稿 / 时间线 / 张力共用)。 */
@@ -531,6 +531,13 @@ export interface MarketRadarResult {
531
531
  productionFoundation: ProductionFoundation;
532
532
  /** 开书创意简报(可选;用「用信号创作」接口单独生成)。 */
533
533
  creativeBrief?: MarketCreativeBrief;
534
+ /**
535
+ * 本次分析的报告 id(服务端生成)。
536
+ *
537
+ * 前端据此判断「是否换了一份新报告」:只有报告变了才重置信号勾选,否则重渲染/
538
+ * 轮询会把作者手选的信号冲掉。缺省(旧版产物)时前端按「首次看到」处理。
539
+ */
540
+ reportId?: string;
534
541
  }
535
542
  /** POST /market-radar request。 */
536
543
  export interface MarketRadarRequest {
@@ -1759,6 +1766,15 @@ export interface ConfigPatch {
1759
1766
  /** 手动添加的模型库(完整替换保存)。 */
1760
1767
  savedModels?: SavedModel[];
1761
1768
  }
1769
+ /** POST /config response: the config the host will read next, plus persistence caveats. */
1770
+ export interface ConfigResponse {
1771
+ config: NovelConfig;
1772
+ /**
1773
+ * 已生效但**未持久化**时的说明(宿主 settings 段不可寻址:段 id 未知或宿主
1774
+ * 未登记)。有值时设置只在本次进程内有效,重启回退,面板照此提示用户。
1775
+ */
1776
+ settingsWarning?: string;
1777
+ }
1762
1778
  /** One assistant conversation message (persisted per project). */
1763
1779
  export interface AssistantMessage {
1764
1780
  role: 'user' | 'assistant' | 'tool';
@@ -2274,7 +2290,7 @@ export interface AdaptSaveRequest {
2274
2290
  text: string;
2275
2291
  /** 新书名(缺省为「改编新书」;建议用「<原著>·改编版」)。 */
2276
2292
  bookName?: string;
2277
- /** 新书输出目录(缺省 ~/.dsh/novels/书名)。 */
2293
+ /** 新书输出目录(缺省为「输出目录」设置下的书名子目录)。 */
2278
2294
  outputDir?: string;
2279
2295
  /** 可附带反推大纲(写进新项目,便于后续续写/编辑)。 */
2280
2296
  outline?: string;
@@ -2298,7 +2314,7 @@ export interface AdaptMaterializeRequest {
2298
2314
  text: string;
2299
2315
  /** 新书名(缺省为「<源书名>·改编版」)。 */
2300
2316
  bookName?: string;
2301
- /** 新书输出目录(缺省 ~/.dsh/novels/书名)。 */
2317
+ /** 新书输出目录(缺省为「输出目录」设置下的书名子目录)。 */
2302
2318
  outputDir?: string;
2303
2319
  /** 反推大纲(来自分析;缺少时用源文章题兜底)。 */
2304
2320
  outline?: string;
@@ -2332,7 +2348,7 @@ export interface AdaptMaterializeResponse {
2332
2348
  export interface AdaptMaterializeSaveRequest {
2333
2349
  /** 新书名(缺省为「改编版」)。 */
2334
2350
  bookName?: string;
2335
- /** 新书输出目录(缺省 ~/.dsh/novels/书名)。 */
2351
+ /** 新书输出目录(缺省为「输出目录」设置下的书名子目录)。 */
2336
2352
  outputDir?: string;
2337
2353
  /** 改编后总纲。 */
2338
2354
  outline: string;
@@ -9,19 +9,21 @@
9
9
  */
10
10
  import type { WebRoute } from '@deepseek-ai/dsh-host-webserver';
11
11
  import type { Context } from '@deepseek-ai/cordis';
12
- import { type BibleResponse, type ConfigPatch, type ExportResponse, type LoadOutlineResponse, type NovelConfig, type PlanResponse, type StatusResponse, type VolumesResponse } from './protocol.ts';
12
+ import { type BibleResponse, type ConfigPatch, type ConfigResponse, type ExportResponse, type LoadOutlineResponse, type NovelConfig, type PlanResponse, type StatusResponse, type VolumesResponse } from './protocol.ts';
13
13
  import { chapterFileName } from './engine.ts';
14
14
  /** Route deps. */
15
15
  export interface NovelRoutesDeps {
16
16
  ctx: Context;
17
17
  /** Resolve the live plugin config (settings-aware). */
18
18
  getConfig: () => NovelConfig;
19
- /** Persist a config patch through the settings seam. */
20
- patchConfig: (patch: ConfigPatch) => Promise<NovelConfig>;
19
+ /** Persist a config patch through the settings seam (memory fallback included). */
20
+ patchConfig: (patch: ConfigPatch) => Promise<ConfigResponse>;
21
21
  /** Raw settings config(未与激活书合并)——目录迁移判断默认目录是否需同步用。 */
22
22
  rawConfig?: () => {
23
23
  outputDir?: string;
24
24
  };
25
+ /** Loader entry id the settings section is addressed by (undefined when unknown). */
26
+ settingsNs?: string;
25
27
  }
26
28
  /**
27
29
  * Build every /api/dsh-novel-forge route.
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Config-patch persistence for the novel-forge plugin.
3
+ *
4
+ * Historically the plugin only knew how to write through the host settings
5
+ * service (`ctx.settings.update`) and treated the section's name as a constant.
6
+ * From Harness 0.2 the section is inferred from this plugin's exported `Config`
7
+ * and addressed by the **entry id** the Loader assigned — which the plugin does
8
+ * not control. A wrong id makes every save fail, so this module keeps the split
9
+ * explicit: apply the patch to the live config, then try to persist it, and
10
+ * report (rather than throw) when the host cannot.
11
+ */
12
+ /** Minimal structural view of the host settings service this plugin touches. */
13
+ export interface SettingsLike {
14
+ /** Persist one section patch; absent on hosts that expose read APIs only. */
15
+ update?: (ns: string, patch: Record<string, unknown>) => Promise<unknown>;
16
+ /** Live section registry; absence means the host cannot enumerate sections. */
17
+ describe?: () => unknown;
18
+ }
19
+ /** Where a config patch ended up. */
20
+ export interface ConfigPersistResult {
21
+ /** The merged config (live in memory regardless of persistence). */
22
+ config: Record<string, unknown>;
23
+ /**
24
+ * Why persistence did not happen, when it did not. Callers surface this to the
25
+ * author: a save that silently evaporates on restart is worse than a loud one.
26
+ */
27
+ settingsWarning?: string;
28
+ }
29
+ /**
30
+ * The Loader entry id the settings section is addressed by.
31
+ * @param ctx - plugin context (structurally typed: only `fiber.entry.options.id` is read).
32
+ * @returns the entry id, or undefined when the host exposes none.
33
+ */
34
+ export declare function novelSettingsNamespace(ctx: unknown): string | undefined;
35
+ /**
36
+ * Pick the settings section to write through.
37
+ *
38
+ * The entry id is authoritative; the declared legacy name is used only when the
39
+ * host actually registered a section under it. Guessing an id is never allowed:
40
+ * a write to a wrong section would persist another plugin's config.
41
+ * @param settings - the settings service, when present.
42
+ * @param entryNs - entry id resolved from the context, when available.
43
+ * @param legacyNs - this plugin's declared section name (0.1.x wiring).
44
+ * @returns the section id to write to, or undefined when none is addressable.
45
+ */
46
+ export declare function resolveSettingsTarget(settings: SettingsLike | undefined, entryNs: string | undefined, legacyNs: string): string | undefined;
47
+ /**
48
+ * Merge one patch into the live config and persist it when the host allows.
49
+ *
50
+ * Never throws for a persistence failure: the edit is valid and already applied,
51
+ * so failing the request would lose it and misreport a working save as an error.
52
+ * @param current - the config as currently resolved.
53
+ * @param patch - the subset to merge (`undefined` values are ignored by callers).
54
+ * @param settings - the settings service, when present.
55
+ * @param entryNs - entry id resolved from the context, when available.
56
+ * @param legacyNs - this plugin's declared section name.
57
+ * @returns the merged config plus a warning when it could not be persisted.
58
+ */
59
+ export declare function persistConfigPatch(current: Record<string, unknown>, patch: Record<string, unknown>, settings: SettingsLike | undefined, entryNs: string | undefined, legacyNs: string): Promise<ConfigPersistResult>;