dsh-novel-generator 0.1.26 → 0.1.27

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 (3) hide show
  1. package/README.md +9 -0
  2. package/index.js +31 -4
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -81,6 +81,15 @@ dsh plugin --profile web add dsh-novel-generator
81
81
  - 正文为空时按钮不可点,接口也会拒绝(400);修改意见为空同样拒绝。
82
82
  - **重写结果只放进编辑区、不自动保存**,状态变成「未保存」,你确认后再点「保存」——避免 AI 的改动直接覆盖原稿。
83
83
 
84
+ ## 附属子插件:小说工坊 · 作者预设
85
+
86
+ 另有一个**独立包** `dsh-novel-author-preset`,只负责往 DSH 注册一个 Agent 预设「小说工坊 · 作者」:
87
+
88
+ - 装上它(`dsh plugin --profile web add dsh-novel-author-preset`),作品列表的「预设」与「设置 → 预设」里就多出这一项,**新建作品默认用它**;
89
+ - 卸掉它,预设立刻消失;已绑定的作品不会因此损坏——重建会话时检测到预设已不在,会自动退回 profile 的默认预设。
90
+
91
+ 预设内容刻意做得很薄:三句人设(你是小说写作者/只输出正文/文风视角篇幅以用户要求为准)+ 关闭运行时上下文 + **不挂任何工具** + 只保留上下文压缩。所有文学倾向都交给作品级「前置提示词」。
92
+
84
93
  ## 迁移、导入与会话
85
94
 
86
95
  - **改预设**:作品列表每行的「预设」改的是「下次建会话时用哪个预设」。会话在创建时就固定了预设,所以保存后会提示「重建会话后生效」——要立刻换,点同一行的「重建会话」。
package/index.js CHANGED
@@ -922,6 +922,20 @@ function normalizeSnapshot(raw) {
922
922
 
923
923
  /* ------------------------------------------------------------ agent work */
924
924
 
925
+ /** The preset a work records, but only when the deployment still offers it. */
926
+ async function usablePreset(ctx, presetId) {
927
+ if (!presetId) return false;
928
+ const presets = ctx.get('agentPresets');
929
+ if (!presets || typeof presets.list !== 'function') return true;
930
+ try {
931
+ const list = await presets.list();
932
+ if (!Array.isArray(list) || list.length === 0) return true;
933
+ return list.some((entry) => entry.id === presetId);
934
+ } catch {
935
+ return true;
936
+ }
937
+ }
938
+
925
939
  /**
926
940
  * Bind a fresh session to a work whose old one disappeared (deleted in the
927
941
  * sidebar, or gone from the session store). The work's memory — snapshots,
@@ -931,7 +945,12 @@ function normalizeSnapshot(raw) {
931
945
  async function rebindSession(ctx, root, project) {
932
946
  const controller = ctx.get('sessionController');
933
947
  if (controller === undefined) throw httpError(503, '当前部署缺少 sessionController 服务,无法为作品重建会话');
934
- const created = await controller.create(project.agentPreset ? { cwd: project.cwd, agentPreset: project.agentPreset } : { cwd: project.cwd });
948
+ // A preset can disappear when its plugin is uninstalled; falling back to the
949
+ // profile default keeps the work usable instead of failing every rebuild.
950
+ const presetOk = await usablePreset(ctx, project.agentPreset);
951
+ const created = await controller.create(presetOk && project.agentPreset
952
+ ? { cwd: project.cwd, agentPreset: project.agentPreset }
953
+ : { cwd: project.cwd });
935
954
  const sessionId = created && created.sessionId ? created.sessionId : null;
936
955
  if (!sessionId) throw httpError(502, '没能为作品重建会话');
937
956
  if (project.model) {
@@ -1477,7 +1496,7 @@ async function bootstrap(ctx, root) {
1477
1496
  }
1478
1497
  }
1479
1498
  return {
1480
- revision: '2026-10-27.10',
1499
+ revision: '2026-10-27.11',
1481
1500
  projects,
1482
1501
  models: {
1483
1502
  default: modelCatalog.default || null,
@@ -1511,6 +1530,11 @@ async function createProject(ctx, root, body) {
1511
1530
  const cwd = asText(body.cwd);
1512
1531
  if (cwd === '') throw httpError(400, '请先选择作品的工作目录');
1513
1532
  const agentPreset = asText(body.agentPreset) || undefined;
1533
+ // New works prefer the bundled author preset when its plugin is installed, and
1534
+ // otherwise fall back to the profile default instead of failing.
1535
+ const chosenPreset = agentPreset !== undefined && await usablePreset(ctx, agentPreset)
1536
+ ? agentPreset
1537
+ : (await usablePreset(ctx, AUTHOR_PRESET_ID) ? AUTHOR_PRESET_ID : undefined);
1514
1538
  const model = body.model && asText(body.model.provider) && asText(body.model.model)
1515
1539
  ? {
1516
1540
  provider: asText(body.model.provider),
@@ -1529,7 +1553,7 @@ async function createProject(ctx, root, body) {
1529
1553
  throw httpError(400, `无法在工作目录下创建作品文件夹:${error instanceof Error ? error.message : String(error)}`);
1530
1554
  }
1531
1555
 
1532
- const created = await controller.create(agentPreset === undefined ? { cwd } : { cwd, agentPreset });
1556
+ const created = await controller.create(chosenPreset === undefined ? { cwd } : { cwd, agentPreset: chosenPreset });
1533
1557
  const sessionId = created && created.sessionId ? created.sessionId : null;
1534
1558
  let warning = null;
1535
1559
  if (sessionId && model) {
@@ -1546,7 +1570,7 @@ async function createProject(ctx, root, body) {
1546
1570
  projectId,
1547
1571
  title,
1548
1572
  cwd,
1549
- agentPreset: agentPreset || created.agentPreset || null,
1573
+ agentPreset: chosenPreset || created.agentPreset || null,
1550
1574
  model,
1551
1575
  // Every new work starts with the default narrative preface; the author can
1552
1576
  // edit or clear it from the work list.
@@ -1914,6 +1938,9 @@ const PRELUDE_PRESETS = [
1914
1938
  /** The preface a fresh work starts with. */
1915
1939
  const DEFAULT_PRELUDE_MODE = 'thirdOmniscient';
1916
1940
 
1941
+ /** Preset id contributed by the optional `dsh-novel-author-preset` sub-plugin. */
1942
+ const AUTHOR_PRESET_ID = 'novel-studio-author';
1943
+
1917
1944
  function defaultPrelude() {
1918
1945
  const preset = PRELUDE_PRESETS.find((entry) => entry.id === DEFAULT_PRELUDE_MODE);
1919
1946
  return preset ? { mode: preset.id, text: preset.text, updatedAt: Date.now() } : null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-novel-generator",
3
- "version": "0.1.26",
3
+ "version": "0.1.27",
4
4
  "type": "module",
5
5
  "description": "Novel studio for DeepSeek Harness: a draggable story timeline, world snapshots, chapter outlines, and chapters written by the work's own agent.",
6
6
  "license": "MIT",