dsh-novel-generator 0.1.14 → 0.1.15

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/client.js +18 -0
  2. package/index.js +47 -2
  3. package/package.json +1 -1
package/client.js CHANGED
@@ -599,6 +599,24 @@ body[data-ds-dark-theme] .dsn-shell{color-scheme:dark}
599
599
  onEditModel(project);
600
600
  },
601
601
  }, t('模型', "Model")),
602
+ h('button', {
603
+ className: 'dsn-btn ghost',
604
+ type: 'button',
605
+ title: t('给这部作品换一个新的创作会话(会话被归档或删掉、生成时提示无回复时使用)', 'Give this work a fresh session (use it after archiving or deleting the old one, or when generation reports no reply)'),
606
+ onClick: async (event) => {
607
+ event.stopPropagation();
608
+ if (!window.confirm(t('为「{title}」新建一个创作会话?旧会话会留在侧栏,作品正文与快照不受影响。', 'Create a fresh session for “{title}”? The old one stays in the sidebar; chapters and snapshots are untouched.', { title: project.title }))) return;
609
+ try {
610
+ const result = await api('/project/rebind', { projectId: project.projectId });
611
+ await onReload();
612
+ setError(result.agentReady
613
+ ? t('已为作品新建创作会话', 'A fresh session is bound to this work')
614
+ : t('会话已新建,但还无法启动;请稍后再试或重开面板', 'The session was created but could not start yet — try again shortly.'));
615
+ } catch (rebindError) {
616
+ setError(messageOf(rebindError));
617
+ }
618
+ },
619
+ }, t('重建会话', "New session")),
602
620
  h('button', {
603
621
  className: 'dsn-btn danger ghost',
604
622
  type: 'button',
package/index.js CHANGED
@@ -877,6 +877,21 @@ async function resolveAgent(ctx, root, project, onNote) {
877
877
  if (onNote) onNote('作品没有绑定会话,已新建一个会话');
878
878
  return rebuilt;
879
879
  }
880
+ // Archived sessions still resolve, so visibility decides instead: a session
881
+ // the user archived is no longer one of the plugin's working sessions.
882
+ let archived = false;
883
+ try {
884
+ const list = await controller.list({}, new AbortController().signal);
885
+ const items = list && Array.isArray(list.items) ? list.items : null;
886
+ if (items) archived = !items.some((entry) => entry.sessionId === project.sessionId);
887
+ } catch {
888
+ archived = false;
889
+ }
890
+ if (archived) {
891
+ const rebuilt = await rebindSession(ctx, root, project);
892
+ if (onNote) onNote('作品的会话已被归档,已自动新建一个会话继续');
893
+ return rebuilt;
894
+ }
880
895
  let result = null;
881
896
  try {
882
897
  result = await controller.resolveAgent(project.sessionId);
@@ -953,7 +968,7 @@ async function askAgent(ctx, root, project, prompt, onNote) {
953
968
  }
954
969
  }
955
970
  throw rebuilt
956
- ? httpError(502, '作品的旧会话已不存在,已为它新建一个会话,但模型没有回复;请在侧栏打开这部作品的会话(「小说工坊」分组里)后再试一次')
971
+ ? httpError(502, '作品的旧会话已不存在,已为它新建一个会话,但模型没有回复;请先打开这部作品的会话(「小说工坊」分组里),或在作品列表点「重建会话」后再试')
957
972
  : httpError(502, '模型没有返回任何正文');
958
973
  }
959
974
 
@@ -1280,7 +1295,7 @@ async function bootstrap(ctx, root) {
1280
1295
  }
1281
1296
  }
1282
1297
  return {
1283
- revision: '2026-10-20.40',
1298
+ revision: '2026-10-20.42',
1284
1299
  projects,
1285
1300
  models: {
1286
1301
  default: modelCatalog.default || null,
@@ -1376,6 +1391,34 @@ async function createProject(ctx, root, body) {
1376
1391
  };
1377
1392
  }
1378
1393
 
1394
+ /**
1395
+ * Give the work a brand-new Session on demand.
1396
+ *
1397
+ * Archiving leaves a session resolvable but unusable for new turns, and the
1398
+ * registry exposes no "is archived" query, so the user gets an explicit button
1399
+ * instead of the plugin guessing.
1400
+ */
1401
+ async function rebindProject(ctx, root, body) {
1402
+ const projectId = asText(body.projectId);
1403
+ if (projectId === '') throw httpError(400, '缺少 projectId');
1404
+ const project = await readProject(root, projectId);
1405
+ const previous = project.sessionId;
1406
+ const { controller } = await rebindSession(ctx, root, project);
1407
+ let agentReady = true;
1408
+ try {
1409
+ const result = await controller.resolveAgent(project.sessionId);
1410
+ agentReady = Boolean(result && result.agent);
1411
+ } catch {
1412
+ agentReady = false;
1413
+ }
1414
+ return {
1415
+ sessionId: project.sessionId,
1416
+ previousSessionId: previous,
1417
+ agentReady,
1418
+ project: summarize(await readProject(root, projectId)),
1419
+ };
1420
+ }
1421
+
1379
1422
  /**
1380
1423
  * Change a project's model and carry it into the project's own Session, the
1381
1424
  * same switch the chat UI's model picker performs.
@@ -2065,6 +2108,8 @@ async function dispatch(ctx, root, registry, method, path, url, body) {
2065
2108
  return updateTimeline(root, body);
2066
2109
  case '/project/model':
2067
2110
  return updateModel(ctx, root, body);
2111
+ case '/project/rebind':
2112
+ return rebindProject(ctx, root, body);
2068
2113
  case '/outline/save':
2069
2114
  return saveOutline(root, body);
2070
2115
  case '/chapter/save':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-novel-generator",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
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",