dsh-cuigengji-plugin 0.4.2 → 0.4.4
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 +15 -0
- package/docs/agent-installation.md +19 -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 +5 -3
- package/docs/npm-publishing.md +35 -11
- 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 +45 -10
- package/docs/tavern-compatibility-audit-20261001.md +109 -109
- package/docs/test-report.md +139 -129
- package/docs/upgrade.md +3 -1
- package/docs/usage-review.md +13 -13
- package/lib/client.js +19 -14
- package/package.json +19 -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/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.4",
|
|
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",
|
|
@@ -85,9 +85,11 @@ var package_default = {
|
|
|
85
85
|
build: "node scripts/build.mjs",
|
|
86
86
|
"check:build": "node scripts/build.mjs --check",
|
|
87
87
|
"pack:release": "npm run typecheck && npm run build && npm run check:build && npm pack --ignore-scripts",
|
|
88
|
+
release: "node scripts/release.mjs",
|
|
88
89
|
test: "node --test tests/*.test.js",
|
|
89
90
|
"test:upgrade": "node --test tests/upgrade.test.js",
|
|
90
91
|
"test:release": "node scripts/check-release-install.mjs",
|
|
92
|
+
"test:host-compat": "node scripts/check-host-compat.mjs",
|
|
91
93
|
"test:rpc": "node scripts/rpc-smoke.mjs",
|
|
92
94
|
"test:rpc-flow": "node scripts/rpc-flow.mjs",
|
|
93
95
|
"test:live": "node scripts/live-smoke.mjs",
|
|
@@ -111,34 +113,37 @@ var package_default = {
|
|
|
111
113
|
},
|
|
112
114
|
engines: {
|
|
113
115
|
node: ">=24",
|
|
114
|
-
dsh: "0.2.0-rc.2"
|
|
116
|
+
dsh: "0.2.0-rc.2 || 0.2.1-rc.2"
|
|
115
117
|
},
|
|
116
118
|
dependencies: {
|
|
117
119
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
118
|
-
|
|
119
|
-
|
|
120
|
+
yaml: "2.9.0",
|
|
121
|
+
zod: "4.4.3"
|
|
120
122
|
},
|
|
121
123
|
peerDependencies: {
|
|
122
124
|
"@deepseek-ai/cordis": "~4.0.4",
|
|
123
|
-
"@deepseek-ai/dsh-
|
|
124
|
-
"@deepseek-ai/dsh-
|
|
125
|
+
"@deepseek-ai/dsh-atomic-write": "0.2.0-rc.2 || 0.2.1-rc.2",
|
|
126
|
+
"@deepseek-ai/dsh-tools": "0.2.0-rc.2 || 0.2.1-rc.2",
|
|
125
127
|
"@deepseek-ai/schemastery": "~3.18.4"
|
|
126
128
|
},
|
|
127
129
|
devDependencies: {
|
|
128
|
-
"@deepseek-ai/dsh": "0.2.
|
|
129
|
-
"@deepseek-ai/dsh-
|
|
130
|
-
"@deepseek-ai/dsh-client-
|
|
131
|
-
"@deepseek-ai/dsh-
|
|
132
|
-
"@deepseek-ai/dsh-
|
|
133
|
-
"@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",
|
|
134
139
|
"@playwright/test": "^1.55.0",
|
|
140
|
+
"@types/node": "^24.0.0",
|
|
135
141
|
"@types/react": "^18.3.31",
|
|
136
142
|
"@types/react-dom": "^18.3.7",
|
|
137
143
|
esbuild: "^0.25.0",
|
|
138
144
|
react: "^18.3.1",
|
|
139
145
|
"react-dom": "^18.3.1",
|
|
140
|
-
typescript: "^7.0.2"
|
|
141
|
-
"@types/node": "^24.0.0"
|
|
146
|
+
typescript: "^7.0.2"
|
|
142
147
|
},
|
|
143
148
|
license: "MIT",
|
|
144
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.4",
|
|
4
4
|
"description": "催更姬:给大肥鱼一个小说工作台,一起写正文、讨论情节、整理人物与世界设定。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "runtime/index.js",
|
|
@@ -40,9 +40,11 @@
|
|
|
40
40
|
"build": "node scripts/build.mjs",
|
|
41
41
|
"check:build": "node scripts/build.mjs --check",
|
|
42
42
|
"pack:release": "npm run typecheck && npm run build && npm run check:build && npm pack --ignore-scripts",
|
|
43
|
+
"release": "node scripts/release.mjs",
|
|
43
44
|
"test": "node --test tests/*.test.js",
|
|
44
45
|
"test:upgrade": "node --test tests/upgrade.test.js",
|
|
45
46
|
"test:release": "node scripts/check-release-install.mjs",
|
|
47
|
+
"test:host-compat": "node scripts/check-host-compat.mjs",
|
|
46
48
|
"test:rpc": "node scripts/rpc-smoke.mjs",
|
|
47
49
|
"test:rpc-flow": "node scripts/rpc-flow.mjs",
|
|
48
50
|
"test:live": "node scripts/live-smoke.mjs",
|
|
@@ -66,34 +68,37 @@
|
|
|
66
68
|
},
|
|
67
69
|
"engines": {
|
|
68
70
|
"node": ">=24",
|
|
69
|
-
"dsh": "0.2.0-rc.2"
|
|
71
|
+
"dsh": "0.2.0-rc.2 || 0.2.1-rc.2"
|
|
70
72
|
},
|
|
71
73
|
"dependencies": {
|
|
72
74
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
+
"yaml": "2.9.0",
|
|
76
|
+
"zod": "4.4.3"
|
|
75
77
|
},
|
|
76
78
|
"peerDependencies": {
|
|
77
79
|
"@deepseek-ai/cordis": "~4.0.4",
|
|
78
|
-
"@deepseek-ai/dsh-
|
|
79
|
-
"@deepseek-ai/dsh-
|
|
80
|
+
"@deepseek-ai/dsh-atomic-write": "0.2.0-rc.2 || 0.2.1-rc.2",
|
|
81
|
+
"@deepseek-ai/dsh-tools": "0.2.0-rc.2 || 0.2.1-rc.2",
|
|
80
82
|
"@deepseek-ai/schemastery": "~3.18.4"
|
|
81
83
|
},
|
|
82
84
|
"devDependencies": {
|
|
83
|
-
"@deepseek-ai/dsh": "0.2.
|
|
84
|
-
"@deepseek-ai/dsh-
|
|
85
|
-
"@deepseek-ai/dsh-client-
|
|
86
|
-
"@deepseek-ai/dsh-
|
|
87
|
-
"@deepseek-ai/dsh-
|
|
88
|
-
"@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",
|
|
89
94
|
"@playwright/test": "^1.55.0",
|
|
95
|
+
"@types/node": "^24.0.0",
|
|
90
96
|
"@types/react": "^18.3.31",
|
|
91
97
|
"@types/react-dom": "^18.3.7",
|
|
92
98
|
"esbuild": "^0.25.0",
|
|
93
99
|
"react": "^18.3.1",
|
|
94
100
|
"react-dom": "^18.3.1",
|
|
95
|
-
"typescript": "^7.0.2"
|
|
96
|
-
"@types/node": "^24.0.0"
|
|
101
|
+
"typescript": "^7.0.2"
|
|
97
102
|
},
|
|
98
103
|
"license": "MIT",
|
|
99
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.4","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
|
+
}
|