@waterwx/dsh-novel-forge 1.1.8 → 1.2.0-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.
- package/README.md +231 -349
- package/lib/client.js +54833 -9926
- package/lib/index.js +12137 -4734
- package/lib/types/action-guard.d.ts +61 -0
- package/lib/types/ai-scan.d.ts +36 -0
- package/lib/types/assets.d.ts +18 -3
- package/lib/types/assistant.d.ts +29 -0
- package/lib/types/author-assets.d.ts +25 -0
- package/lib/types/bookshelf.d.ts +11 -1
- package/lib/types/client/api.d.ts +153 -9
- package/lib/types/client/docx.d.ts +2 -0
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/llmLive.d.ts +33 -0
- package/lib/types/client/locales.d.ts +34 -9
- package/lib/types/client/panel/AdaptModeView.d.ts +5 -0
- package/lib/types/client/panel/AiTag.d.ts +1 -0
- package/lib/types/client/panel/AssistantTab.d.ts +4 -1
- package/lib/types/client/panel/AuthorAssetsView.d.ts +4 -0
- package/lib/types/client/panel/AuthorHome.d.ts +34 -0
- package/lib/types/client/panel/BookAnalysisView.d.ts +4 -0
- package/lib/types/client/panel/CmdPalette.d.ts +12 -0
- package/lib/types/client/panel/CreateBookView.d.ts +5 -1
- package/lib/types/client/panel/DirectorView.d.ts +7 -0
- package/lib/types/client/panel/GlobalAssetLibraryView.d.ts +4 -0
- package/lib/types/client/panel/IdeaInspirationView.d.ts +6 -0
- package/lib/types/client/panel/ImportModal.d.ts +7 -0
- package/lib/types/client/panel/KnowledgeBaseView.d.ts +4 -0
- package/lib/types/client/panel/LiveFeedLog.d.ts +8 -0
- package/lib/types/client/panel/MarketRadarView.d.ts +5 -0
- package/lib/types/client/panel/ModelManager.d.ts +14 -0
- package/lib/types/client/panel/NovelPanel.d.ts +6 -1
- package/lib/types/client/panel/PageHeader.d.ts +18 -0
- package/lib/types/client/panel/ProgressConsole.d.ts +17 -0
- package/lib/types/client/panel/ProgressHomeView.d.ts +5 -0
- package/lib/types/client/panel/ReasoningSection.d.ts +10 -0
- package/lib/types/client/panel/RunPanel.d.ts +5 -0
- package/lib/types/client/panel/SettingsView.d.ts +19 -0
- package/lib/types/client/panel/ShelfView.d.ts +12 -1
- package/lib/types/client/panel/SubPage.d.ts +27 -0
- package/lib/types/client/panel/toast.d.ts +2 -0
- package/lib/types/client/panel/views.d.ts +25 -7
- package/lib/types/client/run-status.d.ts +19 -0
- package/lib/types/client/text.d.ts +9 -0
- package/lib/types/docx-core.d.ts +16 -0
- package/lib/types/docx.d.ts +2 -3
- package/lib/types/engine.d.ts +152 -40
- package/lib/types/global-assets.d.ts +6 -0
- package/lib/types/index.d.ts +20 -2
- package/lib/types/llm-live.d.ts +44 -0
- package/lib/types/market-radar-scan.d.ts +19 -0
- package/lib/types/market-radar-sources.d.ts +31 -0
- package/lib/types/migrate-output.d.ts +25 -0
- package/lib/types/novel-context.d.ts +80 -0
- package/lib/types/prompt-utils.d.ts +35 -0
- package/lib/types/prompting.d.ts +10 -0
- package/lib/types/protocol.d.ts +1106 -123
- package/lib/types/review-policy.d.ts +97 -0
- package/lib/types/routes.d.ts +4 -0
- package/lib/types/run.d.ts +34 -0
- package/lib/types/stage-contract.d.ts +60 -0
- package/package.json +52 -27
- package/scripts/patch-schemastery.mjs +47 -0
- package/src/action-guard.ts +127 -0
- package/src/ai-scan.ts +179 -0
- package/src/assets.ts +1774 -520
- package/src/assistant.ts +1082 -770
- package/src/author-assets.ts +149 -0
- package/src/bookshelf.ts +187 -151
- package/src/client/api.ts +684 -387
- package/src/client/docx.ts +37 -69
- package/src/client/index.ts +3 -1
- package/src/client/llmLive.ts +101 -0
- package/src/client/locales.ts +418 -367
- package/src/client/mount.tsx +1 -1
- package/src/client/panel/AdaptModeView.tsx +535 -0
- package/src/client/panel/AiTag.tsx +7 -0
- package/src/client/panel/AssetsTab.tsx +448 -439
- package/src/client/panel/AssistantTab.tsx +355 -301
- package/src/client/panel/AuthorAssetsView.tsx +206 -0
- package/src/client/panel/AuthorHome.tsx +309 -0
- package/src/client/panel/BookAnalysisView.tsx +63 -0
- package/src/client/panel/CmdPalette.tsx +102 -0
- package/src/client/panel/CreateBookView.tsx +366 -348
- package/src/client/panel/DirectorView.tsx +200 -0
- package/src/client/panel/GlobalAssetLibraryView.tsx +168 -0
- package/src/client/panel/IdeaInspirationView.tsx +76 -0
- package/src/client/panel/ImportModal.tsx +303 -0
- package/src/client/panel/KnowledgeBaseView.tsx +125 -0
- package/src/client/panel/LiveFeedLog.tsx +67 -0
- package/src/client/panel/MarketRadarView.tsx +377 -0
- package/src/client/panel/ModelManager.tsx +205 -0
- package/src/client/panel/NovelPanel.tsx +5403 -4294
- package/src/client/panel/PageHeader.tsx +37 -0
- package/src/client/panel/ProgressConsole.tsx +63 -0
- package/src/client/panel/ReaderView.tsx +306 -306
- package/src/client/panel/ReasoningSection.tsx +31 -0
- package/src/client/panel/RunPanel.tsx +223 -0
- package/src/client/panel/SettingsView.tsx +305 -0
- package/src/client/panel/ShelfView.tsx +266 -235
- package/src/client/panel/SubPage.tsx +42 -0
- package/src/client/panel/WorldTab.tsx +202 -202
- package/src/client/panel/panel.module.css +5764 -2817
- package/src/client/panel/toast.ts +51 -0
- package/src/client/panel/views.tsx +401 -314
- package/src/client/run-status.ts +143 -0
- package/src/client/sidebar-entry.ts +1 -1
- package/src/client/text.ts +28 -0
- package/src/docx-core.ts +53 -0
- package/src/docx.ts +44 -83
- package/src/engine.ts +4231 -2746
- package/src/global-assets.ts +59 -0
- package/src/index.ts +311 -246
- package/src/llm-live.ts +68 -0
- package/src/market-radar-scan.ts +41 -0
- package/src/market-radar-sources.ts +173 -0
- package/src/migrate-output.ts +76 -0
- package/src/novel-context.ts +286 -0
- package/src/prompting.ts +26 -0
- package/src/protocol.ts +2072 -1104
- package/src/review-policy.ts +161 -0
- package/src/routes.ts +3405 -2050
- package/src/run.ts +342 -0
- package/src/stage-contract.ts +189 -0
- package/lib/client.js.map +0 -1
- package/lib/index.js.map +0 -1
- package/src/client/panel/BookshelfBar.tsx +0 -170
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 审稿规则注册表
|
|
3
|
+
*
|
|
4
|
+
* 生成用 compact 模式(核心规则),审稿用 full 模式(全部规则),
|
|
5
|
+
* 消除 reviewSystemPrompt 内嵌规则与 renderAllAssets() 两套打架的问题。
|
|
6
|
+
*/
|
|
7
|
+
/** 通用写作资产规则(兼容 ProjectAssets 中的各类规则) */
|
|
8
|
+
export interface WritingRule {
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
type?: string;
|
|
12
|
+
enabled?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface ReviewRule {
|
|
15
|
+
id: string;
|
|
16
|
+
/** 规则维度 */
|
|
17
|
+
dimension: 'character' | 'setting' | 'redline' | 'writing' | 'pacing' | 'logic' | 'anti-ai' | 'presentation' | 'compliance';
|
|
18
|
+
/** 严重度 */
|
|
19
|
+
severity: 'high' | 'medium' | 'low';
|
|
20
|
+
/** 规则类型:forbidden=禁止类(命中即问题),encourage=鼓励类(不命中不算错) */
|
|
21
|
+
type: 'forbidden' | 'encourage';
|
|
22
|
+
/** 规则描述 */
|
|
23
|
+
description: string;
|
|
24
|
+
/** 是否启用 */
|
|
25
|
+
enabled: boolean;
|
|
26
|
+
/** 作用域 */
|
|
27
|
+
scope: Array<'writing' | 'review' | 'polish' | 'all'>;
|
|
28
|
+
}
|
|
29
|
+
/** 内置审稿维度(结构化评分用) */
|
|
30
|
+
export declare const REVIEW_DIMENSIONS: readonly [{
|
|
31
|
+
readonly id: "character";
|
|
32
|
+
readonly name: "人设一致性";
|
|
33
|
+
readonly weight: 0.2;
|
|
34
|
+
}, {
|
|
35
|
+
readonly id: "setting";
|
|
36
|
+
readonly name: "设定一致性";
|
|
37
|
+
readonly weight: 0.15;
|
|
38
|
+
}, {
|
|
39
|
+
readonly id: "redline";
|
|
40
|
+
readonly name: "红线检查";
|
|
41
|
+
readonly weight: 0.15;
|
|
42
|
+
}, {
|
|
43
|
+
readonly id: "writing";
|
|
44
|
+
readonly name: "文笔质量";
|
|
45
|
+
readonly weight: 0.15;
|
|
46
|
+
}, {
|
|
47
|
+
readonly id: "pacing";
|
|
48
|
+
readonly name: "节奏与爽点";
|
|
49
|
+
readonly weight: 0.1;
|
|
50
|
+
}, {
|
|
51
|
+
readonly id: "logic";
|
|
52
|
+
readonly name: "逻辑漏洞";
|
|
53
|
+
readonly weight: 0.1;
|
|
54
|
+
}, {
|
|
55
|
+
readonly id: "anti-ai";
|
|
56
|
+
readonly name: "反 AI 规则";
|
|
57
|
+
readonly weight: 0.05;
|
|
58
|
+
}, {
|
|
59
|
+
readonly id: "presentation";
|
|
60
|
+
readonly name: "呈现方式";
|
|
61
|
+
readonly weight: 0.05;
|
|
62
|
+
}, {
|
|
63
|
+
readonly id: "compliance";
|
|
64
|
+
readonly name: "内容合规";
|
|
65
|
+
readonly weight: 0.05;
|
|
66
|
+
}];
|
|
67
|
+
export type ReviewDimensionId = typeof REVIEW_DIMENSIONS[number]['id'];
|
|
68
|
+
/** 维度结构化评分 */
|
|
69
|
+
export interface DimensionScore {
|
|
70
|
+
dimension: ReviewDimensionId;
|
|
71
|
+
score: number;
|
|
72
|
+
note: string;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 渲染审稿规则(compact 或 full 模式)
|
|
76
|
+
*
|
|
77
|
+
* compact: 只渲染禁止类 + high 严重度(用于生成时的约束)
|
|
78
|
+
* full: 渲染全部启用规则(用于审稿时的检查清单)
|
|
79
|
+
*/
|
|
80
|
+
export declare function renderReviewRules(assets: WritingRule[] | undefined, mode?: 'compact' | 'full'): string;
|
|
81
|
+
/**
|
|
82
|
+
* 从审稿 issues 中按维度统计
|
|
83
|
+
* 用于结构化评分和 UI 展示。
|
|
84
|
+
*/
|
|
85
|
+
export declare function groupIssuesByDimension(issues: Array<{
|
|
86
|
+
severity: string;
|
|
87
|
+
item: string;
|
|
88
|
+
dimension?: string;
|
|
89
|
+
}>): Record<string, Array<{
|
|
90
|
+
severity: string;
|
|
91
|
+
item: string;
|
|
92
|
+
}>>;
|
|
93
|
+
/**
|
|
94
|
+
* 计算维度加权总分
|
|
95
|
+
* 用于审稿报告的结构化评分。
|
|
96
|
+
*/
|
|
97
|
+
export declare function calculateWeightedScore(dimensions: DimensionScore[]): number;
|
package/lib/types/routes.d.ts
CHANGED
|
@@ -18,6 +18,10 @@ export interface NovelRoutesDeps {
|
|
|
18
18
|
getConfig: () => NovelConfig;
|
|
19
19
|
/** Persist a config patch through the settings seam. */
|
|
20
20
|
patchConfig: (patch: ConfigPatch) => Promise<NovelConfig>;
|
|
21
|
+
/** Raw settings config(未与激活书合并)——目录迁移判断默认目录是否需同步用。 */
|
|
22
|
+
rawConfig?: () => {
|
|
23
|
+
outputDir?: string;
|
|
24
|
+
};
|
|
21
25
|
}
|
|
22
26
|
/**
|
|
23
27
|
* Build every /api/dsh-novel-forge route.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import type { NovelConfig, RunState } from './protocol.ts';
|
|
3
|
+
export declare class ProductionRunner {
|
|
4
|
+
private deps;
|
|
5
|
+
private state;
|
|
6
|
+
private working;
|
|
7
|
+
private pauseRequested;
|
|
8
|
+
private stopRequested;
|
|
9
|
+
/** 生产单绑定目录(start 时快照;所有读写固定用它,防止运行中切书导致写错目录)。 */
|
|
10
|
+
private bookDir;
|
|
11
|
+
constructor(deps: {
|
|
12
|
+
ctx: Context;
|
|
13
|
+
getConfig: () => NovelConfig;
|
|
14
|
+
});
|
|
15
|
+
/** 当前生产单状态(内存优先;web 重启后从磁盘恢复)。 */
|
|
16
|
+
status(): RunState | null;
|
|
17
|
+
private persist;
|
|
18
|
+
private log;
|
|
19
|
+
/** 启动/续跑生产单:startNo..endNo 区间,endNo 超出计划时先自动补计划。 */
|
|
20
|
+
start(startNo: number, endNo: number, runDir?: string): Promise<RunState>;
|
|
21
|
+
pause(): void;
|
|
22
|
+
resume(): void;
|
|
23
|
+
stop(): void;
|
|
24
|
+
/** 是否有生产单正在执行(目录迁移等磁盘级操作前须为 false)。 */
|
|
25
|
+
isWorking(): boolean;
|
|
26
|
+
/** 主循环:从 currentNo 扫描到 endNo,逐章处理;支持暂停/停止。 */
|
|
27
|
+
private loop;
|
|
28
|
+
private processChapter;
|
|
29
|
+
/** 完整质量门:生成 → 摘要+事实 → 伏笔标记 → 审稿 → 作者复盘。 */
|
|
30
|
+
private produce;
|
|
31
|
+
/** 被拒分级处理:无 high 豁免;有 high 按意见修订(最多 2 轮)+ 验证模式;仍不过 → 待人工。 */
|
|
32
|
+
private handleRejected;
|
|
33
|
+
private applyDraft;
|
|
34
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 阶段契约(Stage Contract)——宿主计算的「当前阶段 → 本轮允许的动作」。
|
|
3
|
+
*
|
|
4
|
+
* 设计来源(借鉴 dsh-ai-novel-writer 的「作者请求中的当前阶段决定唯一允许命令」):
|
|
5
|
+
* 允许的动作必须由宿主根据真实项目状态算出,而不是指望模型记住或自律。
|
|
6
|
+
* 宿主计算结果同时用于三处:
|
|
7
|
+
* 1) 注入助手系统提示词:模型知道自己在哪一步、下一步该做什么;
|
|
8
|
+
* 2) 通用续写指令(「继续 / 下一步」)下的写操作白名单强约束(阶段即唯一命令);
|
|
9
|
+
* 3) 拒绝写操作时给出可执行的、阶段相关的理由,而不是一句「不允许」。
|
|
10
|
+
*
|
|
11
|
+
* 注意:作者显式点名的写操作永远优先于阶段白名单(作者终审权);
|
|
12
|
+
* 阶段强约束只在作者没有指定具体对象(通用续写)时生效。
|
|
13
|
+
*/
|
|
14
|
+
import type { ChapterPlan, ProjectState, StageInfo } from './protocol.ts';
|
|
15
|
+
/** 创作阶段标识(线上形状见 protocol.ts 的 StageInfo)。 */
|
|
16
|
+
export type BookStageId = StageInfo['id'];
|
|
17
|
+
/** 一个阶段契约。 */
|
|
18
|
+
export type BookStage = StageInfo;
|
|
19
|
+
/**
|
|
20
|
+
* 作者这一轮是否只说了「继续/下一步」这类没有具体对象的指令。
|
|
21
|
+
*
|
|
22
|
+
* @param message 本轮用户消息。
|
|
23
|
+
* @returns 是通用续写指令时为 true。
|
|
24
|
+
*/
|
|
25
|
+
export declare function isGenericContinue(message: string): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* 根据项目真实状态计算当前创作阶段。
|
|
28
|
+
*
|
|
29
|
+
* @param project 当前项目(未加载时为 undefined)。
|
|
30
|
+
* @returns 阶段契约(推荐动作 + 写操作白名单 + 判定理由)。
|
|
31
|
+
*/
|
|
32
|
+
export declare function computeBookStage(project: ProjectState | undefined): BookStage;
|
|
33
|
+
/**
|
|
34
|
+
* 判断某写操作是否在当前阶段的允许白名单内。
|
|
35
|
+
*
|
|
36
|
+
* @param stage 阶段契约。
|
|
37
|
+
* @param tool 助手工具名。
|
|
38
|
+
* @returns 允许时为 true。
|
|
39
|
+
*/
|
|
40
|
+
export declare function stageAllows(stage: BookStage, tool: string): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* 按阶段把一组动作分成「本阶段可用」与「阶段外」。
|
|
43
|
+
*
|
|
44
|
+
* @param stage 阶段契约。
|
|
45
|
+
* @param tools 动作名列表(通常是写操作登记表)。
|
|
46
|
+
* @returns 两个保持输入顺序的子集。
|
|
47
|
+
*/
|
|
48
|
+
export declare function splitByStage(stage: BookStage, tools: readonly string[]): {
|
|
49
|
+
available: string[];
|
|
50
|
+
outside: string[];
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* 渲染阶段契约为可注入提示词的一段文字。
|
|
54
|
+
*
|
|
55
|
+
* @param stage 阶段契约。
|
|
56
|
+
* @returns 多行文本。
|
|
57
|
+
*/
|
|
58
|
+
export declare function renderStageContract(stage: BookStage): string;
|
|
59
|
+
/** 汇总章节状态计数(供面板/提示词展示,避免各处重复统计)。 */
|
|
60
|
+
export declare function summarizeChapters(chapters: ChapterPlan[]): Record<string, number>;
|
package/package.json
CHANGED
|
@@ -1,8 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@waterwx/dsh-novel-forge",
|
|
3
|
-
"description": "AI
|
|
4
|
-
"version": "1.
|
|
3
|
+
"description": "AI 编译纯文本小说工作台 for the dsh web GUI: import a novel outline (docx/text), plan volumes & chapters with LLM, generate chapter by chapter (3000-4000 chars each), auto AI review & book audit, and export the full book as Markdown/TXT into your chosen output folder.",
|
|
4
|
+
"version": "1.2.0-alpha",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"author": "waterwx",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/watersxya/dsh-novel-forge.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/watersxya/dsh-novel-forge",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/watersxya/dsh-novel-forge/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"dsh",
|
|
17
|
+
"dsh-plugin",
|
|
18
|
+
"deepseek-harness",
|
|
19
|
+
"小说工坊",
|
|
20
|
+
"novel-forge",
|
|
21
|
+
"ai-novel-writing",
|
|
22
|
+
"novel-writing",
|
|
23
|
+
"写作助手",
|
|
24
|
+
"小说创作",
|
|
25
|
+
"网文写作",
|
|
26
|
+
"章节生成",
|
|
27
|
+
"AI审稿",
|
|
28
|
+
"story-writing"
|
|
29
|
+
],
|
|
6
30
|
"engines": {
|
|
7
31
|
"node": "^22.19.0 || >=24.0.0"
|
|
8
32
|
},
|
|
@@ -26,7 +50,6 @@
|
|
|
26
50
|
},
|
|
27
51
|
"client": {
|
|
28
52
|
"inject": [
|
|
29
|
-
"@deepseek-ai/dsh-client-runtime",
|
|
30
53
|
"@deepseek-ai/dsh-client-connection",
|
|
31
54
|
"@deepseek-ai/dsh-client-ui-settings"
|
|
32
55
|
],
|
|
@@ -35,36 +58,35 @@
|
|
|
35
58
|
},
|
|
36
59
|
"dependencies": {
|
|
37
60
|
"fflate": "^0.8.2",
|
|
61
|
+
"lucide-react": "^1.33.0",
|
|
38
62
|
"schemastery": "^3.18.0"
|
|
39
63
|
},
|
|
40
64
|
"peerDependencies": {
|
|
41
|
-
"@deepseek-ai/dsh-client-connection": "
|
|
42
|
-
"@deepseek-ai/dsh-client-locale": "
|
|
43
|
-
"@deepseek-ai/dsh-client-
|
|
44
|
-
"@deepseek-ai/dsh-client-ui-
|
|
45
|
-
"@deepseek-ai/dsh-
|
|
46
|
-
"@deepseek-ai/dsh-
|
|
47
|
-
"@deepseek-ai/dsh-
|
|
48
|
-
"@deepseek-ai/dsh-
|
|
49
|
-
"@deepseek-ai/dsh-
|
|
50
|
-
"@deepseek-ai/dsh-
|
|
51
|
-
"@deepseek-ai/dsh-tools": "^0.1.0-rc.5",
|
|
65
|
+
"@deepseek-ai/dsh-client-connection": "0.1.2-alpha.3",
|
|
66
|
+
"@deepseek-ai/dsh-client-locale": "0.1.2-alpha.3",
|
|
67
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.1.2-alpha.3",
|
|
68
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.1.2-alpha.3",
|
|
69
|
+
"@deepseek-ai/dsh-host-webserver": "0.1.2-alpha.3",
|
|
70
|
+
"@deepseek-ai/dsh-llm": "0.1.2-alpha.3",
|
|
71
|
+
"@deepseek-ai/dsh-settings": "0.1.2-alpha.3",
|
|
72
|
+
"@deepseek-ai/dsh-skill": "0.1.2-alpha.3",
|
|
73
|
+
"@deepseek-ai/dsh-system-prompt": "0.1.2-alpha.3",
|
|
74
|
+
"@deepseek-ai/dsh-tools": "0.1.2-alpha.3",
|
|
52
75
|
"react": "^18.2.0",
|
|
53
76
|
"react-dom": "^18.2.0"
|
|
54
77
|
},
|
|
55
78
|
"devDependencies": {
|
|
56
79
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
57
|
-
"@deepseek-ai/dsh-client-connection": "
|
|
58
|
-
"@deepseek-ai/dsh-client-locale": "
|
|
59
|
-
"@deepseek-ai/dsh-client-
|
|
60
|
-
"@deepseek-ai/dsh-client-ui-
|
|
61
|
-
"@deepseek-ai/dsh-
|
|
62
|
-
"@deepseek-ai/dsh-
|
|
63
|
-
"@deepseek-ai/dsh-
|
|
64
|
-
"@deepseek-ai/dsh-
|
|
65
|
-
"@deepseek-ai/dsh-
|
|
66
|
-
"@deepseek-ai/dsh-
|
|
67
|
-
"@deepseek-ai/dsh-tools": "^0.1.0-rc.5",
|
|
80
|
+
"@deepseek-ai/dsh-client-connection": "0.1.2-alpha.3",
|
|
81
|
+
"@deepseek-ai/dsh-client-locale": "0.1.2-alpha.3",
|
|
82
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.1.2-alpha.3",
|
|
83
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.1.2-alpha.3",
|
|
84
|
+
"@deepseek-ai/dsh-host-webserver": "0.1.2-alpha.3",
|
|
85
|
+
"@deepseek-ai/dsh-llm": "0.1.2-alpha.3",
|
|
86
|
+
"@deepseek-ai/dsh-settings": "0.1.2-alpha.3",
|
|
87
|
+
"@deepseek-ai/dsh-skill": "0.1.2-alpha.3",
|
|
88
|
+
"@deepseek-ai/dsh-system-prompt": "0.1.2-alpha.3",
|
|
89
|
+
"@deepseek-ai/dsh-tools": "0.1.2-alpha.3",
|
|
68
90
|
"@types/node": "^22.20.0",
|
|
69
91
|
"@types/react": "~18.3.1",
|
|
70
92
|
"@types/react-dom": "^18.3.5",
|
|
@@ -77,19 +99,22 @@
|
|
|
77
99
|
},
|
|
78
100
|
"files": [
|
|
79
101
|
"lib/**/*.js",
|
|
80
|
-
"lib/**/*.js.map",
|
|
81
102
|
"lib/**/*.d.ts",
|
|
82
|
-
"lib/**/*.d.ts.map",
|
|
83
103
|
"src",
|
|
104
|
+
"scripts/patch-schemastery.mjs",
|
|
84
105
|
"cordis.patch.yml",
|
|
85
106
|
"README.md"
|
|
86
107
|
],
|
|
87
108
|
"license": "Apache-2.0",
|
|
88
109
|
"scripts": {
|
|
110
|
+
"postinstall": "node scripts/patch-schemastery.mjs",
|
|
89
111
|
"build": "tsc -p tsconfig.build.json && tsdown",
|
|
90
112
|
"bundle": "tsdown",
|
|
91
113
|
"watch": "tsdown --watch",
|
|
92
114
|
"typecheck": "tsc --noEmit",
|
|
115
|
+
"check:styles": "node scripts/check-theme-sizes.mjs",
|
|
116
|
+
"release": "node scripts/release.mjs",
|
|
117
|
+
"release:dry": "node scripts/release.mjs --dry-run",
|
|
93
118
|
"test": "vitest run"
|
|
94
119
|
}
|
|
95
120
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Durable local fix: schemastery@3.18.x from npm has no "exports" field, which
|
|
2
|
+
// breaks tsx/Node ESM resolution when dsh loads novel-forge's lib/index.js.
|
|
3
|
+
// pnpm re-installs can overwrite the manual node_modules patch, so this script
|
|
4
|
+
// re-applies it on every `pnpm install` (postinstall).
|
|
5
|
+
import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
6
|
+
import { join } from 'node:path'
|
|
7
|
+
import { fileURLToPath } from 'node:url'
|
|
8
|
+
|
|
9
|
+
const root = join(fileURLToPath(new URL('..', import.meta.url)))
|
|
10
|
+
const pnpmDir = join(root, 'node_modules', '.pnpm')
|
|
11
|
+
|
|
12
|
+
if (!existsSync(pnpmDir)) {
|
|
13
|
+
console.warn('[dsh-novel-forge] no node_modules/.pnpm found; skip schemastery patch')
|
|
14
|
+
process.exit(0)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const candidates = readdirSync(pnpmDir)
|
|
18
|
+
.filter((name) => name.startsWith('schemastery@'))
|
|
19
|
+
.map((name) => join(pnpmDir, name, 'node_modules', 'schemastery', 'package.json'))
|
|
20
|
+
.filter((file) => existsSync(file))
|
|
21
|
+
|
|
22
|
+
if (candidates.length === 0) {
|
|
23
|
+
console.warn('[dsh-novel-forge] schemastery package not found; skip patch')
|
|
24
|
+
process.exit(0)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let patched = 0
|
|
28
|
+
for (const file of candidates) {
|
|
29
|
+
const pkg = JSON.parse(readFileSync(file, 'utf8'))
|
|
30
|
+
if (pkg.exports) {
|
|
31
|
+
console.log(`[dsh-novel-forge] schemastery exports already present: ${file}`)
|
|
32
|
+
continue
|
|
33
|
+
}
|
|
34
|
+
pkg.exports = {
|
|
35
|
+
'.': {
|
|
36
|
+
types: './lib/index.d.ts',
|
|
37
|
+
import: './lib/index.mjs',
|
|
38
|
+
require: './lib/index.cjs',
|
|
39
|
+
},
|
|
40
|
+
'./package.json': './package.json',
|
|
41
|
+
}
|
|
42
|
+
writeFileSync(file, `${JSON.stringify(pkg, null, 2)}\n`, 'utf8')
|
|
43
|
+
console.log(`[dsh-novel-forge] patched schemastery exports: ${file}`)
|
|
44
|
+
patched++
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (patched === 0) console.log('[dsh-novel-forge] schemastery patch: nothing to do')
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 动作失败分级(fail-stop 规则)——不依赖 LLM/宿主服务的纯逻辑模块,可离线单测。
|
|
3
|
+
*
|
|
4
|
+
* 规则来源(借鉴 dsh-ai-novel-writer):
|
|
5
|
+
* - 参数/契约类失败**不得重复同一调用**,解释一次即停;
|
|
6
|
+
* - 瞬时故障允许有限重试,同样禁止无限重试。
|
|
7
|
+
* 规则必须可测试,否则只是文档。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 动作失败分级:
|
|
12
|
+
* - `contract` 参数/契约类(参数缺失、片段未找到、章节不存在、非法取值…):
|
|
13
|
+
* 同一调用只允许修正一次,禁止原样重复。
|
|
14
|
+
* - `transient` 瞬时故障(网络、模型中断、磁盘临时不可用…):允许重试一次。
|
|
15
|
+
*/
|
|
16
|
+
export type ActionErrorKind = 'contract' | 'transient'
|
|
17
|
+
|
|
18
|
+
/** 带分级的动作失败(executeAction 抛出,供 fail-stop 判定)。 */
|
|
19
|
+
export class NovelActionError extends Error {
|
|
20
|
+
readonly kind: ActionErrorKind
|
|
21
|
+
constructor(kind: ActionErrorKind, message: string) {
|
|
22
|
+
super(message)
|
|
23
|
+
this.name = 'NovelActionError'
|
|
24
|
+
this.kind = kind
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** 瞬时故障特征(先判,避免被下面的契约词误伤)。 */
|
|
29
|
+
const TRANSIENT_PATTERNS: readonly RegExp[] = [
|
|
30
|
+
/助手调用失败/,
|
|
31
|
+
/生成失败|请求失败|调用失败/,
|
|
32
|
+
/network|fetch failed|ECONNRESET|ECONNREFUSED|ETIMEDOUT|EPIPE|EAI_AGAIN|socket hang up|aborted|terminated/i,
|
|
33
|
+
/超时|timeout/i,
|
|
34
|
+
/频繁|限流|rate\s*limit|too many requests|\b429\b/i,
|
|
35
|
+
/ENOSPC|EACCES|EBUSY|EPERM|EMFILE/,
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 参数/契约类失败特征(兜底识别)。
|
|
40
|
+
*
|
|
41
|
+
* 宿主自己的前置条件失败已在抛出点标成 {@link NovelActionError}(引擎与助手动作层),
|
|
42
|
+
* 这里的正则只兜住漏标的历史消息与第三方错误。
|
|
43
|
+
*/
|
|
44
|
+
const CONTRACT_PATTERNS: readonly RegExp[] = [
|
|
45
|
+
/动作参数不是合法 JSON/,
|
|
46
|
+
/未知工具/,
|
|
47
|
+
/需要/,
|
|
48
|
+
/不存在/,
|
|
49
|
+
/尚未生成/,
|
|
50
|
+
/未找到/,
|
|
51
|
+
/不在计划中/,
|
|
52
|
+
/正文文件不存在/,
|
|
53
|
+
/还没有已写章节/,
|
|
54
|
+
/没有可分析的已写章节/,
|
|
55
|
+
/非法/,
|
|
56
|
+
/无效/,
|
|
57
|
+
/必须是/,
|
|
58
|
+
/无法识别/,
|
|
59
|
+
/请先/,
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 判定一次动作失败的类别。
|
|
64
|
+
*
|
|
65
|
+
* @param error 捕获到的异常。
|
|
66
|
+
* @returns `contract`(参数/契约,不重试原样调用)或 `transient`(瞬时,可重试一次)。
|
|
67
|
+
*/
|
|
68
|
+
export function classifyActionError(error: unknown): ActionErrorKind {
|
|
69
|
+
if (error instanceof NovelActionError) return error.kind
|
|
70
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
71
|
+
if (TRANSIENT_PATTERNS.some(re => re.test(message))) return 'transient'
|
|
72
|
+
if (CONTRACT_PATTERNS.some(re => re.test(message))) return 'contract'
|
|
73
|
+
// 未知错误默认按瞬时处理:允许一次重试,但同一调用签名仍有次数上限(见 fail-stop)。
|
|
74
|
+
return 'transient'
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 同一调用签名允许的总尝试次数(含首次)。
|
|
79
|
+
*
|
|
80
|
+
* @param kind 失败类别。
|
|
81
|
+
* @returns `contract` = 1(禁止原样重复),`transient` = 2(允许一次重试)。
|
|
82
|
+
*/
|
|
83
|
+
export function actionRetryLimit(kind: ActionErrorKind): number {
|
|
84
|
+
return kind === 'contract' ? 1 : 2
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 把动作参数规范化为稳定签名(键排序),用于识别「同一个调用」。
|
|
89
|
+
*
|
|
90
|
+
* @param args 动作参数。
|
|
91
|
+
* @returns 稳定字符串签名。
|
|
92
|
+
*/
|
|
93
|
+
export function safeArgsKey(args: Record<string, unknown> | undefined): string {
|
|
94
|
+
if (args === undefined) return ''
|
|
95
|
+
try {
|
|
96
|
+
return JSON.stringify(Object.keys(args).sort().map(key => [key, args[key]]))
|
|
97
|
+
} catch {
|
|
98
|
+
return String(args)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 已失败调用记账:同一签名达到重试上限即 fail-stop。
|
|
104
|
+
*/
|
|
105
|
+
export class FailureLedger {
|
|
106
|
+
private readonly counts = new Map<string, number>()
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 记录一次失败并判断是否应当停止本轮。
|
|
110
|
+
*
|
|
111
|
+
* @param tool 工具名。
|
|
112
|
+
* @param args 该次调用参数。
|
|
113
|
+
* @param kind 失败类别。
|
|
114
|
+
* @returns `attempts` 本次是第几次失败;`stop` 是否达到上限(true = 立即停止,不再喂回模型)。
|
|
115
|
+
*/
|
|
116
|
+
record(tool: string, args: Record<string, unknown> | undefined, kind: ActionErrorKind): { attempts: number; stop: boolean } {
|
|
117
|
+
const signature = `${tool}:${safeArgsKey(args)}`
|
|
118
|
+
const attempts = (this.counts.get(signature) ?? 0) + 1
|
|
119
|
+
this.counts.set(signature, attempts)
|
|
120
|
+
return { attempts, stop: attempts >= actionRetryLimit(kind) }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** 已登记的失败签名数量(测试/诊断用)。 */
|
|
124
|
+
get size(): number {
|
|
125
|
+
return this.counts.size
|
|
126
|
+
}
|
|
127
|
+
}
|