dsh-cuigengji-plugin 0.4.3 → 0.4.5
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/LICENSE +21 -21
- package/README.md +1 -1
- package/cordis.patch.yml +3 -3
- package/docs/CHANGELOG.md +14 -0
- package/docs/agent-installation.md +20 -12
- package/docs/architecture.md +74 -74
- package/docs/desktop-migration-20260930.md +26 -26
- package/docs/development.md +7 -3
- package/docs/getting-started.md +17 -7
- package/docs/layout-progress.md +31 -31
- package/docs/market-listing.md +6 -4
- package/docs/npm-publishing.md +28 -12
- package/docs/package-name-migration.md +3 -3
- package/docs/planning-board.md +44 -44
- package/docs/presets.md +24 -24
- package/docs/release-process.md +18 -12
- package/docs/tavern-compatibility-audit-20261001.md +109 -109
- package/docs/test-report.md +147 -129
- package/docs/upgrade.md +3 -1
- package/docs/usage-review.md +13 -13
- package/lib/client.js +18 -14
- package/package.json +18 -14
- package/skills/cuigengji-maintenance/SKILL.md +2 -2
- package/src/adapters/dsh/prompt.js +36 -36
- package/src/adapters/dsh/rpc.js +96 -96
- package/src/adapters/dsh/skills.js +17 -17
- package/src/adapters/mcp/actions.js +6 -6
- package/src/adapters/mcp/client.js +172 -172
- package/src/adapters/mcp/server.js +40 -40
- package/src/application/bindings/session.js +72 -72
- package/src/application/queries/context.js +15 -15
- package/src/application/store.js +412 -412
- package/src/client/features/chapters/clipboard.ts +27 -27
- package/src/client/features/chapters/history.ts +11 -11
- package/src/client/features/chapters/index.jsx +118 -118
- package/src/client/features/chapters/sync.ts +155 -155
- package/src/client/features/chapters/useChapterSync.ts +39 -39
- package/src/client/features/context/index.jsx +22 -22
- package/src/client/features/memory/index.jsx +73 -73
- package/src/client/features/planning/Decorations.tsx +1 -1
- package/src/client/features/planning/Pages.tsx +11 -11
- package/src/client/features/planning/data.js +16 -16
- package/src/client/index.tsx +130 -130
- package/src/client/launcher.tsx +78 -78
- package/src/client/shared/creation.tsx +34 -34
- package/src/client/shared/detail-page.tsx +61 -61
- package/src/client/shared/drafts.js +17 -17
- package/src/client/shared/evidence.jsx +11 -11
- package/src/client/shared/groups.tsx +34 -34
- package/src/client/shared/preferences.tsx +104 -104
- package/src/client/shared/select-style.ts +25 -25
- package/src/client/shared/state.js +95 -95
- package/src/client/shared/ui.jsx +19 -19
- package/src/client/shared/workspace.tsx +11 -11
- package/src/client/style.ts +274 -274
- package/src/client/visual.ts +190 -190
- package/src/contracts/actions.js +19 -19
- package/src/contracts/planning.ts +52 -52
- package/src/domain/manuscript.ts +4 -4
- package/src/domain/planning/index.ts +113 -113
- package/src/domain/planning/model.ts +59 -59
- package/src/domain/preset/index.js +89 -89
- package/src/index.js +91 -91
- package/src/infrastructure/migrations/legacy.js +96 -96
- package/src/infrastructure/migrations/tavern.js +78 -78
- package/src/infrastructure/persistence/atomic-store.ts +80 -80
- package/src/infrastructure/persistence/project-layout.ts +1 -1
package/docs/usage-review.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
# 使用审阅
|
|
2
|
-
|
|
3
|
-
本项目的交互以正文、规划与设定为主。查看当前使用方式,请从 [README](../README.md) 和[安装说明](getting-started.md)开始;旧版本的自动参考、规划批准门槛和分离阅读/编辑模式不再是当前行为。
|
|
4
|
-
|
|
5
|
-
需要重点检查的写作体验:
|
|
6
|
-
|
|
7
|
-
- 长正文编辑、自动保存、目录收纳、版本恢复和冲突时草稿保留。
|
|
8
|
-
- 规划与设定的分组搜索、详情返回位置、拖拽连线、取消和删除。
|
|
9
|
-
- 规划独立页面、批注与背景框是否有助于作者和 AI 讨论推进。
|
|
10
|
-
- 酒馆角色卡、世界书和预设的预览、取消、导入及有效内容核对。
|
|
11
|
-
- 宽窄面板、深浅主题、键盘、下拉菜单与入口可达性。
|
|
12
|
-
|
|
13
|
-
浏览器交互验证使用隔离临时作品;接口测试不替代页面和模型实际使用。模型能否理解作者意图,还需要用真实创作场景持续观察。最新检查记录见[验收说明](test-report.md)。
|
|
1
|
+
# 使用审阅
|
|
2
|
+
|
|
3
|
+
本项目的交互以正文、规划与设定为主。查看当前使用方式,请从 [README](../README.md) 和[安装说明](getting-started.md)开始;旧版本的自动参考、规划批准门槛和分离阅读/编辑模式不再是当前行为。
|
|
4
|
+
|
|
5
|
+
需要重点检查的写作体验:
|
|
6
|
+
|
|
7
|
+
- 长正文编辑、自动保存、目录收纳、版本恢复和冲突时草稿保留。
|
|
8
|
+
- 规划与设定的分组搜索、详情返回位置、拖拽连线、取消和删除。
|
|
9
|
+
- 规划独立页面、批注与背景框是否有助于作者和 AI 讨论推进。
|
|
10
|
+
- 酒馆角色卡、世界书和预设的预览、取消、导入及有效内容核对。
|
|
11
|
+
- 宽窄面板、深浅主题、键盘、下拉菜单与入口可达性。
|
|
12
|
+
|
|
13
|
+
浏览器交互验证使用隔离临时作品;接口测试不替代页面和模型实际使用。模型能否理解作者意图,还需要用真实创作场景持续观察。最新检查记录见[验收说明](test-report.md)。
|
package/lib/client.js
CHANGED
|
@@ -45,7 +45,7 @@ var import_react = require("react");
|
|
|
45
45
|
// package.json
|
|
46
46
|
var package_default = {
|
|
47
47
|
name: "dsh-cuigengji-plugin",
|
|
48
|
-
version: "0.4.
|
|
48
|
+
version: "0.4.5",
|
|
49
49
|
description: "\u50AC\u66F4\u59EC\uFF1A\u7ED9\u5927\u80A5\u9C7C\u4E00\u4E2A\u5C0F\u8BF4\u5DE5\u4F5C\u53F0\uFF0C\u4E00\u8D77\u5199\u6B63\u6587\u3001\u8BA8\u8BBA\u60C5\u8282\u3001\u6574\u7406\u4EBA\u7269\u4E0E\u4E16\u754C\u8BBE\u5B9A\u3002",
|
|
50
50
|
type: "module",
|
|
51
51
|
main: "runtime/index.js",
|
|
@@ -89,6 +89,7 @@ var package_default = {
|
|
|
89
89
|
test: "node --test tests/*.test.js",
|
|
90
90
|
"test:upgrade": "node --test tests/upgrade.test.js",
|
|
91
91
|
"test:release": "node scripts/check-release-install.mjs",
|
|
92
|
+
"test:host-compat": "node scripts/check-host-compat.mjs",
|
|
92
93
|
"test:rpc": "node scripts/rpc-smoke.mjs",
|
|
93
94
|
"test:rpc-flow": "node scripts/rpc-flow.mjs",
|
|
94
95
|
"test:live": "node scripts/live-smoke.mjs",
|
|
@@ -112,34 +113,37 @@ var package_default = {
|
|
|
112
113
|
},
|
|
113
114
|
engines: {
|
|
114
115
|
node: ">=24",
|
|
115
|
-
dsh: "0.2.0-rc.2"
|
|
116
|
+
dsh: ">=0.2.0-rc.2"
|
|
116
117
|
},
|
|
117
118
|
dependencies: {
|
|
118
119
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
119
|
-
|
|
120
|
-
|
|
120
|
+
yaml: "2.9.0",
|
|
121
|
+
zod: "4.4.3"
|
|
121
122
|
},
|
|
122
123
|
peerDependencies: {
|
|
123
124
|
"@deepseek-ai/cordis": "~4.0.4",
|
|
124
|
-
"@deepseek-ai/dsh-
|
|
125
|
-
"@deepseek-ai/dsh-
|
|
125
|
+
"@deepseek-ai/dsh-atomic-write": ">=0.2.0-rc.2",
|
|
126
|
+
"@deepseek-ai/dsh-tools": ">=0.2.0-rc.2",
|
|
126
127
|
"@deepseek-ai/schemastery": "~3.18.4"
|
|
127
128
|
},
|
|
128
129
|
devDependencies: {
|
|
129
|
-
"@deepseek-ai/dsh": "0.2.
|
|
130
|
-
"@deepseek-ai/dsh-
|
|
131
|
-
"@deepseek-ai/dsh-client-
|
|
132
|
-
"@deepseek-ai/dsh-
|
|
133
|
-
"@deepseek-ai/dsh-
|
|
134
|
-
"@deepseek-ai/dsh-
|
|
130
|
+
"@deepseek-ai/dsh": "0.2.1-rc.2",
|
|
131
|
+
"@deepseek-ai/dsh-atomic-write": "0.2.1-rc.2",
|
|
132
|
+
"@deepseek-ai/dsh-client-connection": "0.2.1-rc.2",
|
|
133
|
+
"@deepseek-ai/dsh-client-store": "0.2.1-rc.2",
|
|
134
|
+
"@deepseek-ai/dsh-client-ui-sidebar-right": "0.2.1-rc.2",
|
|
135
|
+
"@deepseek-ai/dsh-mcp-client": "0.2.1-rc.2",
|
|
136
|
+
"@deepseek-ai/dsh-skill": "0.2.1-rc.2",
|
|
137
|
+
"@deepseek-ai/dsh-system-prompt": "0.2.1-rc.2",
|
|
138
|
+
"@deepseek-ai/dsh-tools": "0.2.1-rc.2",
|
|
135
139
|
"@playwright/test": "^1.55.0",
|
|
140
|
+
"@types/node": "^24.0.0",
|
|
136
141
|
"@types/react": "^18.3.31",
|
|
137
142
|
"@types/react-dom": "^18.3.7",
|
|
138
143
|
esbuild: "^0.25.0",
|
|
139
144
|
react: "^18.3.1",
|
|
140
145
|
"react-dom": "^18.3.1",
|
|
141
|
-
typescript: "^7.0.2"
|
|
142
|
-
"@types/node": "^24.0.0"
|
|
146
|
+
typescript: "^7.0.2"
|
|
143
147
|
},
|
|
144
148
|
license: "MIT",
|
|
145
149
|
repository: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-cuigengji-plugin",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"description": "催更姬:给大肥鱼一个小说工作台,一起写正文、讨论情节、整理人物与世界设定。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "runtime/index.js",
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"test": "node --test tests/*.test.js",
|
|
45
45
|
"test:upgrade": "node --test tests/upgrade.test.js",
|
|
46
46
|
"test:release": "node scripts/check-release-install.mjs",
|
|
47
|
+
"test:host-compat": "node scripts/check-host-compat.mjs",
|
|
47
48
|
"test:rpc": "node scripts/rpc-smoke.mjs",
|
|
48
49
|
"test:rpc-flow": "node scripts/rpc-flow.mjs",
|
|
49
50
|
"test:live": "node scripts/live-smoke.mjs",
|
|
@@ -67,34 +68,37 @@
|
|
|
67
68
|
},
|
|
68
69
|
"engines": {
|
|
69
70
|
"node": ">=24",
|
|
70
|
-
"dsh": "0.2.0-rc.2"
|
|
71
|
+
"dsh": ">=0.2.0-rc.2"
|
|
71
72
|
},
|
|
72
73
|
"dependencies": {
|
|
73
74
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
74
|
-
"
|
|
75
|
-
"
|
|
75
|
+
"yaml": "2.9.0",
|
|
76
|
+
"zod": "4.4.3"
|
|
76
77
|
},
|
|
77
78
|
"peerDependencies": {
|
|
78
79
|
"@deepseek-ai/cordis": "~4.0.4",
|
|
79
|
-
"@deepseek-ai/dsh-
|
|
80
|
-
"@deepseek-ai/dsh-
|
|
80
|
+
"@deepseek-ai/dsh-atomic-write": ">=0.2.0-rc.2",
|
|
81
|
+
"@deepseek-ai/dsh-tools": ">=0.2.0-rc.2",
|
|
81
82
|
"@deepseek-ai/schemastery": "~3.18.4"
|
|
82
83
|
},
|
|
83
84
|
"devDependencies": {
|
|
84
|
-
"@deepseek-ai/dsh": "0.2.
|
|
85
|
-
"@deepseek-ai/dsh-
|
|
86
|
-
"@deepseek-ai/dsh-client-
|
|
87
|
-
"@deepseek-ai/dsh-
|
|
88
|
-
"@deepseek-ai/dsh-
|
|
89
|
-
"@deepseek-ai/dsh-
|
|
85
|
+
"@deepseek-ai/dsh": "0.2.1-rc.2",
|
|
86
|
+
"@deepseek-ai/dsh-atomic-write": "0.2.1-rc.2",
|
|
87
|
+
"@deepseek-ai/dsh-client-connection": "0.2.1-rc.2",
|
|
88
|
+
"@deepseek-ai/dsh-client-store": "0.2.1-rc.2",
|
|
89
|
+
"@deepseek-ai/dsh-client-ui-sidebar-right": "0.2.1-rc.2",
|
|
90
|
+
"@deepseek-ai/dsh-mcp-client": "0.2.1-rc.2",
|
|
91
|
+
"@deepseek-ai/dsh-skill": "0.2.1-rc.2",
|
|
92
|
+
"@deepseek-ai/dsh-system-prompt": "0.2.1-rc.2",
|
|
93
|
+
"@deepseek-ai/dsh-tools": "0.2.1-rc.2",
|
|
90
94
|
"@playwright/test": "^1.55.0",
|
|
95
|
+
"@types/node": "^24.0.0",
|
|
91
96
|
"@types/react": "^18.3.31",
|
|
92
97
|
"@types/react-dom": "^18.3.7",
|
|
93
98
|
"esbuild": "^0.25.0",
|
|
94
99
|
"react": "^18.3.1",
|
|
95
100
|
"react-dom": "^18.3.1",
|
|
96
|
-
"typescript": "^7.0.2"
|
|
97
|
-
"@types/node": "^24.0.0"
|
|
101
|
+
"typescript": "^7.0.2"
|
|
98
102
|
},
|
|
99
103
|
"license": "MIT",
|
|
100
104
|
"repository": {
|
|
@@ -5,7 +5,7 @@ description: 安装或更新催更姬后,查询自动迁移、备份和完整
|
|
|
5
5
|
|
|
6
6
|
# 催更姬安装与更新维护
|
|
7
7
|
|
|
8
|
-
这份 Skill 只处理插件本身的安装、升级与数据验证,不参与小说创作。用户通常不会写代码,因此应使用 DSH 原生插件管理安装维护者发布的预构建 `.tgz
|
|
8
|
+
这份 Skill 只处理插件本身的安装、升级与数据验证,不参与小说创作。用户通常不会写代码,因此应使用 DSH 原生插件管理安装维护者发布的预构建 `.tgz`,按宿主返回完成重新加载,要求重启时正常退出再打开。不要让用户安装 Git、Node.js、pnpm,也不要让 Agent 直接改小说文件或手工重命名数据目录。
|
|
9
9
|
|
|
10
10
|
## 更新流程
|
|
11
11
|
|
|
@@ -25,7 +25,7 @@ description: 安装或更新催更姬后,查询自动迁移、备份和完整
|
|
|
25
25
|
返回当前插件版本、实际数据根、来源/目标存储版本、迁移阶段、完整备份路径、作品和正文数量,以及失败原因。它不要求会话绑定小说,迁移失败时仍可调用。
|
|
26
26
|
|
|
27
27
|
```json
|
|
28
|
-
{"action":"verify","args":{"expectedVersion":"0.4.
|
|
28
|
+
{"action":"verify","args":{"expectedVersion":"0.4.5","expectedDataRoot":"C:\\Users\\用户名\\AppData\\Local\\cuigengji"}}
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
它在锁内重新读取并校验工作库,返回计数、摘要哈希和每项检查结果。不要把摘要哈希当作正文内容,也不要把数据根替换成维护者电脑上的路径。
|
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
import { persona } from './skills.js';
|
|
2
|
-
|
|
3
|
-
const PERSONA_NAME = 'cuigengji:writing';
|
|
4
|
-
const OWN_NAMES = new Set([PERSONA_NAME, 'cuigengji:handoff', 'cuigengji:preset', 'cuigengji:novel']);
|
|
5
|
-
|
|
6
|
-
export function registerPrompt(ctx, { store, resolveBinding } = {}) {
|
|
7
|
-
// Only author-enabled writing requirements join the stable persona. Story
|
|
8
|
-
// content stays on demand; omit changing metadata to keep this text stable.
|
|
9
|
-
ctx.on('system-prompt/assemble', async (assembly, context, next) => {
|
|
10
|
-
const result = await next();
|
|
11
|
-
if (!context.agent?.id) return result;
|
|
12
|
-
context.signal?.throwIfAborted();
|
|
13
|
-
const actor = { kind: 'agent', sessionId: String(context.agent.id) };
|
|
14
|
-
let binding;
|
|
15
|
-
try {binding = store && await (resolveBinding
|
|
16
|
-
? resolveBinding(actor.sessionId)
|
|
17
|
-
: store.dispatch('binding.get', {}, actor));}
|
|
18
|
-
catch (error) {
|
|
19
|
-
if (error.code !== 'UPGRADE_REQUIRED') throw error;
|
|
20
|
-
// A broken project must not prevent the host agent from calling the
|
|
21
|
-
// unbound maintenance tool or the native plugin manager.
|
|
22
|
-
return {...result,sections:result.sections.filter(entry => !OWN_NAMES.has(entry.name)),contexts:result.contexts.filter(entry => !OWN_NAMES.has(entry.name))};
|
|
23
|
-
}
|
|
24
|
-
const preset = binding ? await store.dispatch('preset.read', {}, actor) : null;
|
|
25
|
-
context.signal?.throwIfAborted();
|
|
26
|
-
return {
|
|
27
|
-
...result,
|
|
28
|
-
sections: [
|
|
29
|
-
...result.sections.filter(entry => !OWN_NAMES.has(entry.name)),
|
|
30
|
-
{ name: PERSONA_NAME, text: persona, interpolate: false },
|
|
31
|
-
...(preset?.enabled && preset.content ? [{ name: 'cuigengji:preset', text: `作者的写作要求:\n${preset.content}`, interpolate: false }] : []),
|
|
32
|
-
],
|
|
33
|
-
contexts: result.contexts.filter(entry => !OWN_NAMES.has(entry.name)),
|
|
34
|
-
};
|
|
35
|
-
});
|
|
36
|
-
}
|
|
1
|
+
import { persona } from './skills.js';
|
|
2
|
+
|
|
3
|
+
const PERSONA_NAME = 'cuigengji:writing';
|
|
4
|
+
const OWN_NAMES = new Set([PERSONA_NAME, 'cuigengji:handoff', 'cuigengji:preset', 'cuigengji:novel']);
|
|
5
|
+
|
|
6
|
+
export function registerPrompt(ctx, { store, resolveBinding } = {}) {
|
|
7
|
+
// Only author-enabled writing requirements join the stable persona. Story
|
|
8
|
+
// content stays on demand; omit changing metadata to keep this text stable.
|
|
9
|
+
ctx.on('system-prompt/assemble', async (assembly, context, next) => {
|
|
10
|
+
const result = await next();
|
|
11
|
+
if (!context.agent?.id) return result;
|
|
12
|
+
context.signal?.throwIfAborted();
|
|
13
|
+
const actor = { kind: 'agent', sessionId: String(context.agent.id) };
|
|
14
|
+
let binding;
|
|
15
|
+
try {binding = store && await (resolveBinding
|
|
16
|
+
? resolveBinding(actor.sessionId)
|
|
17
|
+
: store.dispatch('binding.get', {}, actor));}
|
|
18
|
+
catch (error) {
|
|
19
|
+
if (error.code !== 'UPGRADE_REQUIRED') throw error;
|
|
20
|
+
// A broken project must not prevent the host agent from calling the
|
|
21
|
+
// unbound maintenance tool or the native plugin manager.
|
|
22
|
+
return {...result,sections:result.sections.filter(entry => !OWN_NAMES.has(entry.name)),contexts:result.contexts.filter(entry => !OWN_NAMES.has(entry.name))};
|
|
23
|
+
}
|
|
24
|
+
const preset = binding ? await store.dispatch('preset.read', {}, actor) : null;
|
|
25
|
+
context.signal?.throwIfAborted();
|
|
26
|
+
return {
|
|
27
|
+
...result,
|
|
28
|
+
sections: [
|
|
29
|
+
...result.sections.filter(entry => !OWN_NAMES.has(entry.name)),
|
|
30
|
+
{ name: PERSONA_NAME, text: persona, interpolate: false },
|
|
31
|
+
...(preset?.enabled && preset.content ? [{ name: 'cuigengji:preset', text: `作者的写作要求:\n${preset.content}`, interpolate: false }] : []),
|
|
32
|
+
],
|
|
33
|
+
contexts: result.contexts.filter(entry => !OWN_NAMES.has(entry.name)),
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
}
|
package/src/adapters/dsh/rpc.js
CHANGED
|
@@ -1,96 +1,96 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
-
import { HUMAN_ACTIONS } from '../../contracts/actions.js';
|
|
3
|
-
import { convertLegacyExport } from '../../infrastructure/migrations/legacy.js';
|
|
4
|
-
|
|
5
|
-
const manifest = JSON.parse(readFileSync(new URL('../../../package.json', import.meta.url), 'utf8'));
|
|
6
|
-
export const RPC_ENDPOINT = 'cuigengji/dispatch';
|
|
7
|
-
const RPC_PATH = `/api/${RPC_ENDPOINT}`;
|
|
8
|
-
const record = value => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
9
|
-
const invalid = message => Object.assign(new Error(message), { code: 'INVALID_INPUT' });
|
|
10
|
-
|
|
11
|
-
function failure(error) {
|
|
12
|
-
return {
|
|
13
|
-
ok: false,
|
|
14
|
-
error: {
|
|
15
|
-
code: error.code || 'CUIGENGJI_ERROR',
|
|
16
|
-
message: error.message || String(error),
|
|
17
|
-
details: {},
|
|
18
|
-
},
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function registerRpcRoute(ctx, { dispatch, dataRoot, workData, transfer, maintenance }) {
|
|
23
|
-
const ensureReady = () => maintenance?.ensureReady();
|
|
24
|
-
const dispatchRpc = async (payload, signal) => {
|
|
25
|
-
try {
|
|
26
|
-
signal.throwIfAborted();
|
|
27
|
-
if (!record(payload) || !HUMAN_ACTIONS.includes(payload.action)) throw invalid('无效的催更姬操作');
|
|
28
|
-
const { action, args = {}, sessionId } = payload;
|
|
29
|
-
if (!record(args)) throw invalid('args 必须是对象');
|
|
30
|
-
if (sessionId !== undefined && (typeof sessionId !== 'string' || !sessionId.trim() || sessionId.length > 512)) {
|
|
31
|
-
throw invalid('sessionId 必须是有效的会话标识');
|
|
32
|
-
}
|
|
33
|
-
if (!action.startsWith('transfer.') && !['novel.list', 'novel.create', 'novel.import', 'legacy.preview', 'settings.get', 'maintenance.status', 'maintenance.verify', 'workspace.status', 'workspace.export', 'workspace.preview', 'workspace.import'].includes(action) && !sessionId) {
|
|
34
|
-
throw Object.assign(new Error('请选择一个 DSH 会话'), { code: 'SESSION_REQUIRED' });
|
|
35
|
-
}
|
|
36
|
-
if (action === 'settings.get') {
|
|
37
|
-
return { ok: true, value: { dataRoot, pluginVersion: manifest.version, supportedDsh: manifest.peerDependencies['@deepseek-ai/dsh-tools'] } };
|
|
38
|
-
}
|
|
39
|
-
if (action === 'legacy.preview') return { ok: true, value: convertLegacyExport(args.input) };
|
|
40
|
-
if (action === 'maintenance.status') {
|
|
41
|
-
if (!maintenance) throw invalid('维护服务未初始化');
|
|
42
|
-
return { ok: true, value: await maintenance.status() };
|
|
43
|
-
}
|
|
44
|
-
if (action === 'maintenance.verify') {
|
|
45
|
-
if (!maintenance) throw invalid('维护服务未初始化');
|
|
46
|
-
return { ok: true, value: await maintenance.verify(args) };
|
|
47
|
-
}
|
|
48
|
-
if (action.startsWith('transfer.')) {
|
|
49
|
-
await ensureReady();
|
|
50
|
-
if (!transfer) throw invalid('导入 / 导出服务未初始化');
|
|
51
|
-
const method = {'transfer.catalog':'catalog','transfer.inspect':'inspect','transfer.preview':'preview','transfer.import':'import','transfer.exportPreview':'exportPreview','transfer.export':'export'}[action];
|
|
52
|
-
return {ok: true, value: await transfer[method](args, {sessionId})};
|
|
53
|
-
}
|
|
54
|
-
if (action.startsWith('workspace.')) {
|
|
55
|
-
await ensureReady();
|
|
56
|
-
if (!workData) throw invalid('工作数据服务未初始化');
|
|
57
|
-
const method = { 'workspace.status':'status', 'workspace.export':'export', 'workspace.preview':'preview', 'workspace.import':'import' }[action];
|
|
58
|
-
return { ok: true, value: await workData[method](args.backup) };
|
|
59
|
-
}
|
|
60
|
-
await ensureReady();
|
|
61
|
-
return { ok: true, value: await dispatch(action, args, { kind: 'human', sessionId }) };
|
|
62
|
-
} catch (error) {
|
|
63
|
-
return failure(error);
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
ctx.connection.fetch.register({
|
|
68
|
-
path: RPC_PATH,
|
|
69
|
-
methods: ['POST'],
|
|
70
|
-
requestBody: 'buffered',
|
|
71
|
-
fetch: async request => {
|
|
72
|
-
if (request.headers.get('content-type')?.split(';')[0].trim().toLowerCase() !== 'application/json') {
|
|
73
|
-
return new Response('content type must be application/json', { status: 415 });
|
|
74
|
-
}
|
|
75
|
-
let message;
|
|
76
|
-
try {
|
|
77
|
-
message = await request.json();
|
|
78
|
-
} catch {
|
|
79
|
-
return new Response('body is not JSON', { status: 400 });
|
|
80
|
-
}
|
|
81
|
-
const rpcId = typeof message?.rpcId === 'string' ? message.rpcId : 'invalid-request';
|
|
82
|
-
if (!record(message) || message.type !== 'client-request' || message.method !== RPC_ENDPOINT || typeof message.rpcId !== 'string' || !message.rpcId.trim()) {
|
|
83
|
-
return Response.json({
|
|
84
|
-
type: 'server-response',
|
|
85
|
-
rpcId,
|
|
86
|
-
result: failure(invalid('无效的 RPC 请求')),
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
return Response.json({
|
|
90
|
-
type: 'server-response',
|
|
91
|
-
rpcId,
|
|
92
|
-
result: await dispatchRpc(message.payload, request.signal),
|
|
93
|
-
});
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
}
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { HUMAN_ACTIONS } from '../../contracts/actions.js';
|
|
3
|
+
import { convertLegacyExport } from '../../infrastructure/migrations/legacy.js';
|
|
4
|
+
|
|
5
|
+
const manifest = JSON.parse(readFileSync(new URL('../../../package.json', import.meta.url), 'utf8'));
|
|
6
|
+
export const RPC_ENDPOINT = 'cuigengji/dispatch';
|
|
7
|
+
const RPC_PATH = `/api/${RPC_ENDPOINT}`;
|
|
8
|
+
const record = value => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
9
|
+
const invalid = message => Object.assign(new Error(message), { code: 'INVALID_INPUT' });
|
|
10
|
+
|
|
11
|
+
function failure(error) {
|
|
12
|
+
return {
|
|
13
|
+
ok: false,
|
|
14
|
+
error: {
|
|
15
|
+
code: error.code || 'CUIGENGJI_ERROR',
|
|
16
|
+
message: error.message || String(error),
|
|
17
|
+
details: {},
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function registerRpcRoute(ctx, { dispatch, dataRoot, workData, transfer, maintenance }) {
|
|
23
|
+
const ensureReady = () => maintenance?.ensureReady();
|
|
24
|
+
const dispatchRpc = async (payload, signal) => {
|
|
25
|
+
try {
|
|
26
|
+
signal.throwIfAborted();
|
|
27
|
+
if (!record(payload) || !HUMAN_ACTIONS.includes(payload.action)) throw invalid('无效的催更姬操作');
|
|
28
|
+
const { action, args = {}, sessionId } = payload;
|
|
29
|
+
if (!record(args)) throw invalid('args 必须是对象');
|
|
30
|
+
if (sessionId !== undefined && (typeof sessionId !== 'string' || !sessionId.trim() || sessionId.length > 512)) {
|
|
31
|
+
throw invalid('sessionId 必须是有效的会话标识');
|
|
32
|
+
}
|
|
33
|
+
if (!action.startsWith('transfer.') && !['novel.list', 'novel.create', 'novel.import', 'legacy.preview', 'settings.get', 'maintenance.status', 'maintenance.verify', 'workspace.status', 'workspace.export', 'workspace.preview', 'workspace.import'].includes(action) && !sessionId) {
|
|
34
|
+
throw Object.assign(new Error('请选择一个 DSH 会话'), { code: 'SESSION_REQUIRED' });
|
|
35
|
+
}
|
|
36
|
+
if (action === 'settings.get') {
|
|
37
|
+
return { ok: true, value: { dataRoot, pluginVersion: manifest.version, supportedDsh: manifest.peerDependencies['@deepseek-ai/dsh-tools'] } };
|
|
38
|
+
}
|
|
39
|
+
if (action === 'legacy.preview') return { ok: true, value: convertLegacyExport(args.input) };
|
|
40
|
+
if (action === 'maintenance.status') {
|
|
41
|
+
if (!maintenance) throw invalid('维护服务未初始化');
|
|
42
|
+
return { ok: true, value: await maintenance.status() };
|
|
43
|
+
}
|
|
44
|
+
if (action === 'maintenance.verify') {
|
|
45
|
+
if (!maintenance) throw invalid('维护服务未初始化');
|
|
46
|
+
return { ok: true, value: await maintenance.verify(args) };
|
|
47
|
+
}
|
|
48
|
+
if (action.startsWith('transfer.')) {
|
|
49
|
+
await ensureReady();
|
|
50
|
+
if (!transfer) throw invalid('导入 / 导出服务未初始化');
|
|
51
|
+
const method = {'transfer.catalog':'catalog','transfer.inspect':'inspect','transfer.preview':'preview','transfer.import':'import','transfer.exportPreview':'exportPreview','transfer.export':'export'}[action];
|
|
52
|
+
return {ok: true, value: await transfer[method](args, {sessionId})};
|
|
53
|
+
}
|
|
54
|
+
if (action.startsWith('workspace.')) {
|
|
55
|
+
await ensureReady();
|
|
56
|
+
if (!workData) throw invalid('工作数据服务未初始化');
|
|
57
|
+
const method = { 'workspace.status':'status', 'workspace.export':'export', 'workspace.preview':'preview', 'workspace.import':'import' }[action];
|
|
58
|
+
return { ok: true, value: await workData[method](args.backup) };
|
|
59
|
+
}
|
|
60
|
+
await ensureReady();
|
|
61
|
+
return { ok: true, value: await dispatch(action, args, { kind: 'human', sessionId }) };
|
|
62
|
+
} catch (error) {
|
|
63
|
+
return failure(error);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
ctx.connection.fetch.register({
|
|
68
|
+
path: RPC_PATH,
|
|
69
|
+
methods: ['POST'],
|
|
70
|
+
requestBody: 'buffered',
|
|
71
|
+
fetch: async request => {
|
|
72
|
+
if (request.headers.get('content-type')?.split(';')[0].trim().toLowerCase() !== 'application/json') {
|
|
73
|
+
return new Response('content type must be application/json', { status: 415 });
|
|
74
|
+
}
|
|
75
|
+
let message;
|
|
76
|
+
try {
|
|
77
|
+
message = await request.json();
|
|
78
|
+
} catch {
|
|
79
|
+
return new Response('body is not JSON', { status: 400 });
|
|
80
|
+
}
|
|
81
|
+
const rpcId = typeof message?.rpcId === 'string' ? message.rpcId : 'invalid-request';
|
|
82
|
+
if (!record(message) || message.type !== 'client-request' || message.method !== RPC_ENDPOINT || typeof message.rpcId !== 'string' || !message.rpcId.trim()) {
|
|
83
|
+
return Response.json({
|
|
84
|
+
type: 'server-response',
|
|
85
|
+
rpcId,
|
|
86
|
+
result: failure(invalid('无效的 RPC 请求')),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return Response.json({
|
|
90
|
+
type: 'server-response',
|
|
91
|
+
rpcId,
|
|
92
|
+
result: await dispatchRpc(message.payload, request.signal),
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
-
export const skillSpecs = [
|
|
3
|
-
['cuigengji-project', '接手催更姬小说项目,定位作者的协作目标,按需读取资料并安全保存正文、规划与设定。'],
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
export const skillSpecs = [
|
|
3
|
+
['cuigengji-project', '接手催更姬小说项目,定位作者的协作目标,按需读取资料并安全保存正文、规划与设定。'],
|
|
4
4
|
['cuigengji-plan', '讨论剧情走向、人物行动、因果或备选方案时使用;主动把结构性讨论维护到相关共享画布页,用事件、并行动线与分支帮助作者对齐思路。'],
|
|
5
|
-
['cuigengji-write', '按作者意图写作或续写小说正文,用章节工具保存,并按实际结果维护相关规划与设定。'],
|
|
6
|
-
['cuigengji-revise', '审阅或修订已保存正文,定位具体问题,用版本检查保留作者并行修改,并核对受影响的规划与设定。'],
|
|
5
|
+
['cuigengji-write', '按作者意图写作或续写小说正文,用章节工具保存,并按实际结果维护相关规划与设定。'],
|
|
6
|
+
['cuigengji-revise', '审阅或修订已保存正文,定位具体问题,用版本检查保留作者并行修改,并核对受影响的规划与设定。'],
|
|
7
7
|
['cuigengji-memory', '讨论、设计或完善人物动机、世界规则、阵营与关系时使用;主动复用设定卡与关系图,保留未决提案并区分既定事实。'],
|
|
8
|
-
['cuigengji-maintenance', '安装或更新催更姬后,查询自动迁移、备份和完整性校验结果;不要求绑定小说。'],
|
|
9
|
-
];
|
|
10
|
-
export function registerSkills(ctx) {
|
|
11
|
-
const shared = readFileSync(new URL('../../../skills/cuigengji-project/SKILL.md', import.meta.url), 'utf8');
|
|
12
|
-
const body = text => text.replace(/^---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/, '').trim();
|
|
13
|
-
for (const [name, description] of skillSpecs) {
|
|
14
|
-
const task = readFileSync(new URL(`../../../skills/${name}/SKILL.md`, import.meta.url), 'utf8');
|
|
15
|
-
ctx.skills.register({ name, description, source: 'cuigengji',
|
|
16
|
-
content: (name === 'cuigengji-project' || name === 'cuigengji-maintenance') ? task : `${task.trim()}\n\n${body(shared)}`,
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
}
|
|
8
|
+
['cuigengji-maintenance', '安装或更新催更姬后,查询自动迁移、备份和完整性校验结果;不要求绑定小说。'],
|
|
9
|
+
];
|
|
10
|
+
export function registerSkills(ctx) {
|
|
11
|
+
const shared = readFileSync(new URL('../../../skills/cuigengji-project/SKILL.md', import.meta.url), 'utf8');
|
|
12
|
+
const body = text => text.replace(/^---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/, '').trim();
|
|
13
|
+
for (const [name, description] of skillSpecs) {
|
|
14
|
+
const task = readFileSync(new URL(`../../../skills/${name}/SKILL.md`, import.meta.url), 'utf8');
|
|
15
|
+
ctx.skills.register({ name, description, source: 'cuigengji',
|
|
16
|
+
content: (name === 'cuigengji-project' || name === 'cuigengji-maintenance') ? task : `${task.trim()}\n\n${body(shared)}`,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
20
|
export const persona = `你是帮助用户创作和维护小说的写作助手。
|
|
21
21
|
当前共享协作规则为 shared-canvas-v2;旧会话首次处理相关任务时,已加载的指南若没有这个版本标记,重新加载对应 Skill 一次。
|
|
22
22
|
讨论剧情结构、走向和备选方案时加载 cuigengji-plan,主动维护相关共享画布页;设计人物与世界规则时加载 cuigengji-memory。结构性讨论默认包含记录未确认提案,不必等作者点名画布;明确只聊不保存、简单问答或只读审阅时不写入。
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { MEMORY_ACTIONS } from '../../contracts/actions.js';
|
|
2
2
|
export { MEMORY_ACTIONS };
|
|
3
|
-
|
|
4
|
-
export const toolName = action => `memory_${action.replaceAll('.', '_')}`;
|
|
5
|
-
|
|
6
|
-
export function assertMemoryAction(action) {
|
|
7
|
-
if (!MEMORY_ACTIONS.includes(action)) throw new Error(`不允许的记忆操作:${action}`);
|
|
8
|
-
}
|
|
3
|
+
|
|
4
|
+
export const toolName = action => `memory_${action.replaceAll('.', '_')}`;
|
|
5
|
+
|
|
6
|
+
export function assertMemoryAction(action) {
|
|
7
|
+
if (!MEMORY_ACTIONS.includes(action)) throw new Error(`不允许的记忆操作:${action}`);
|
|
8
|
+
}
|