dsh-novel-generator 0.1.13 → 0.1.14
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/index.js +45 -6
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -916,6 +916,15 @@ async function askAgent(ctx, root, project, prompt, onNote) {
|
|
|
916
916
|
const before = project.sessionId;
|
|
917
917
|
const { controller, agent } = await resolveAgent(ctx, root, project, attempt === 0 ? onNote : undefined);
|
|
918
918
|
if (project.sessionId !== before) rebuilt = true;
|
|
919
|
+
// Only messages added by THIS prompt are eligible: reading further back
|
|
920
|
+
// would hand back the previous turn's reply (a snapshot JSON, for one) when
|
|
921
|
+
// the model stays silent.
|
|
922
|
+
let seen = 0;
|
|
923
|
+
try {
|
|
924
|
+
seen = agent.session.deriveMessages().length;
|
|
925
|
+
} catch {
|
|
926
|
+
seen = 0;
|
|
927
|
+
}
|
|
919
928
|
const admission = new AbortController();
|
|
920
929
|
await withTimeout(controller.prompt({
|
|
921
930
|
sessionId: project.sessionId,
|
|
@@ -932,7 +941,7 @@ async function askAgent(ctx, root, project, prompt, onNote) {
|
|
|
932
941
|
}
|
|
933
942
|
});
|
|
934
943
|
const messages = agent.session.deriveMessages();
|
|
935
|
-
for (let index = messages.length - 1; index >=
|
|
944
|
+
for (let index = messages.length - 1; index >= seen; index -= 1) {
|
|
936
945
|
const message = messages[index];
|
|
937
946
|
if (!message || message.role !== 'assistant') continue;
|
|
938
947
|
const text = textOfMessage(message);
|
|
@@ -948,6 +957,36 @@ async function askAgent(ctx, root, project, prompt, onNote) {
|
|
|
948
957
|
: httpError(502, '模型没有返回任何正文');
|
|
949
958
|
}
|
|
950
959
|
|
|
960
|
+
/** True when a reply is structured data rather than prose (a snapshot JSON). */
|
|
961
|
+
function looksStructured(text) {
|
|
962
|
+
const head = String(text || '').trim();
|
|
963
|
+
if (head === '') return false;
|
|
964
|
+
if (!head.startsWith('{') && !head.startsWith('[')) {
|
|
965
|
+
return head.startsWith('```') && (head.includes('"world"') || head.includes('"characters"'));
|
|
966
|
+
}
|
|
967
|
+
try {
|
|
968
|
+
JSON.parse(head);
|
|
969
|
+
return true;
|
|
970
|
+
} catch {
|
|
971
|
+
return head.includes('"world"') || head.includes('"characters"') || head.includes('"synopsis"');
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* Ask for prose. A model that answers a writing prompt with JSON (it just
|
|
977
|
+
* produced a snapshot, say) gets one firm reminder instead of having structured
|
|
978
|
+
* text saved as the chapter body.
|
|
979
|
+
*/
|
|
980
|
+
async function askStory(ctx, root, project, prompt, onNote) {
|
|
981
|
+
const first = await askAgent(ctx, root, project, prompt, onNote);
|
|
982
|
+
if (!looksStructured(first)) return first;
|
|
983
|
+
if (onNote) onNote('模型返回了结构化数据,正在要求它改写为正文');
|
|
984
|
+
const retryPrompt = `${prompt}\n\n【纠正】上一轮你返回的是 JSON/结构化数据,那是错的。现在只输出这一章的小说正文(中文散文),不要 JSON、不要字段名、不要标题行。`;
|
|
985
|
+
const second = await askAgent(ctx, root, project, retryPrompt, onNote);
|
|
986
|
+
if (looksStructured(second)) throw httpError(502, '模型反复返回结构化数据而不是正文,请稍后重试或换一个模型');
|
|
987
|
+
return second;
|
|
988
|
+
}
|
|
989
|
+
|
|
951
990
|
function snapshotBlock(snapshot) {
|
|
952
991
|
if (!snapshot) return '(暂无世界快照,请依据本章梗概自行建立最基本的设定)';
|
|
953
992
|
const lines = [];
|
|
@@ -1241,7 +1280,7 @@ async function bootstrap(ctx, root) {
|
|
|
1241
1280
|
}
|
|
1242
1281
|
}
|
|
1243
1282
|
return {
|
|
1244
|
-
revision: '2026-10-20.
|
|
1283
|
+
revision: '2026-10-20.40',
|
|
1245
1284
|
projects,
|
|
1246
1285
|
models: {
|
|
1247
1286
|
default: modelCatalog.default || null,
|
|
@@ -1759,7 +1798,7 @@ async function startGenerate(ctx, root, registry, body) {
|
|
|
1759
1798
|
.filter((entry) => entry.chapterId !== chapter.chapterId && entry.startDay <= chapter.startDay)
|
|
1760
1799
|
.sort((a, b) => b.startDay - a.startDay)[0] || null;
|
|
1761
1800
|
job.message = '正在生成章节正文';
|
|
1762
|
-
const content = await
|
|
1801
|
+
const content = await askStory(ctx, root, project, chapterPrompt(project, chapter, previous), (note) => {
|
|
1763
1802
|
job.message = note;
|
|
1764
1803
|
});
|
|
1765
1804
|
const saved = await mutateProject(root, projectId, (draft) => {
|
|
@@ -1853,7 +1892,7 @@ async function startCompose(ctx, root, registry, body) {
|
|
|
1853
1892
|
job.message = note;
|
|
1854
1893
|
});
|
|
1855
1894
|
const deduced = parseJsonReply(deduceReply) || {};
|
|
1856
|
-
const synopsis = asText(firstOf(deduced, ['synopsis', '梗概', '
|
|
1895
|
+
const synopsis = asText(firstOf(deduced, ['synopsis', '梗概', '本章梗概']));
|
|
1857
1896
|
if (synopsis === '') throw httpError(502, '模型没有给出章节梗概');
|
|
1858
1897
|
const title = draft.title
|
|
1859
1898
|
|| asText(firstOf(deduced, ['title', '标题']))
|
|
@@ -1895,7 +1934,7 @@ async function startCompose(ctx, root, registry, body) {
|
|
|
1895
1934
|
const fresh = await readProject(root, projectId);
|
|
1896
1935
|
const target = fresh.chapters.find((entry) => entry.chapterId === chapterId);
|
|
1897
1936
|
if (!target) throw httpError(500, '章节写入失败');
|
|
1898
|
-
const content = await
|
|
1937
|
+
const content = await askStory(ctx, root, fresh, chapterPrompt(fresh, target, previous), (note) => {
|
|
1899
1938
|
job.message = note;
|
|
1900
1939
|
});
|
|
1901
1940
|
|
|
@@ -1950,7 +1989,7 @@ async function startDeduce(ctx, root, registry, body) {
|
|
|
1950
1989
|
});
|
|
1951
1990
|
const parsed = parseJsonReply(reply);
|
|
1952
1991
|
if (!parsed) throw httpError(502, '模型没有返回可解析的大纲 JSON');
|
|
1953
|
-
const synopsis = asText(firstOf(parsed, ['synopsis', '梗概', '
|
|
1992
|
+
const synopsis = asText(firstOf(parsed, ['synopsis', '梗概', '本章梗概']));
|
|
1954
1993
|
const durationDays = Math.max(1, Math.round(Number(firstOf(parsed, ['durationDays', 'duration', '时间长度', 'days'])) || project.timeline.unitDays));
|
|
1955
1994
|
const minWords = Math.max(1, Math.round(Number(firstOf(parsed, ['minWords', '最低字数'])) || 1200));
|
|
1956
1995
|
const maxWords = Math.max(minWords + 1, Math.round(Number(firstOf(parsed, ['maxWords', '最多字数'])) || 3500));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-novel-generator",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
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",
|