narraeon 0.7.10 → 0.8.0
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/dist/node/protocol/modelConnections.js +15 -0
- package/dist/node/protocol/modelUsage.js +4 -0
- package/dist/node/protocol/sillyTavernContent.js +1 -0
- package/dist/node/protocol/v1.js +75 -0
- package/dist/node/runtime/V1Runtime.js +71 -1
- package/dist/node/runtime/authoring/AuthoringRequestSnapshot.js +15 -3
- package/dist/node/runtime/authoring/DamagedGoogleContinuation.js +68 -0
- package/dist/node/runtime/authoring/ModelToolConversation.js +47 -22
- package/dist/node/runtime/content/ContentWorkspace.js +3 -0
- package/dist/node/runtime/content/SillyTavernContent.js +796 -0
- package/dist/node/runtime/model/AiFailureLog.js +8 -1
- package/dist/node/runtime/model/FileNativeModelAdapters.js +610 -18
- package/dist/node/runtime/model/GoogleHopJournal.js +90 -0
- package/dist/node/runtime/model/GoogleInteractionsStream.js +242 -0
- package/dist/node/runtime/model/GoogleRequestPreview.js +184 -0
- package/dist/node/runtime/model/ModelConnectionStore.js +4 -1
- package/dist/node/runtime/model/ProviderExchangeState.js +44 -1
- package/dist/node/runtime/model/ProviderStream.js +4 -2
- package/dist/node/runtime/play/FileNativePlayAdvanceStore.js +4 -4
- package/dist/node/runtime/play/FileNativePlayPresetStore.js +150 -78
- package/dist/node/runtime/play/FileNativePlayTimelineStore.js +150 -10
- package/dist/node/runtime/play/OrderedPlayPrompts.js +139 -178
- package/dist/node/runtime/play/PlayAdvanceLifecycle.js +33 -9
- package/dist/node/runtime/play/PlayCallChain.js +76 -4
- package/dist/node/runtime/play/PlayFollowupRequests.js +23 -3
- package/dist/node/runtime/prompt/PromptCompilationCodec.js +2 -1
- package/dist/node/runtime/setting/FileNativeSettingImprovementStore.js +112 -8
- package/dist/node/runtime/setting/SettingImprovementSession.js +48 -7
- package/dist/node/runtime/world-revision/FileNativeWorldRevisionStore.js +84 -8
- package/dist/node/runtime/world-revision/WorldRevisionSession.js +125 -17
- package/dist/node/shared/application-preset-files.js +29 -0
- package/dist/node/shared/default-preset-host.js +50 -38
- package/dist/node/shared/first-party-action-choices.js +7 -11
- package/dist/node/shared/first-party-generic-panels.js +7 -11
- package/dist/node/shared/first-party-player-view.js +7 -11
- package/dist/node/shared/ordered-author-prompts.js +6 -4
- package/dist/node/shared/ordered-play-prompts.js +4 -2
- package/dist/node/shared/prompt-arrangement.js +205 -0
- package/dist/node/shared/sillytavern-preset.js +454 -0
- package/dist/web/assets/{index-DH2BVsYd.css → index-BKA1D0Rz.css} +1 -1
- package/dist/web/assets/index-C_Qta8UL.js +1432 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-B2kbaOGe.js +0 -1408
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { authoringRequestPreviews } from "../authoring/AuthoringRequestSnapshot.js";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
+
import { isDeepStrictEqual } from "node:util";
|
|
3
4
|
import { emptyAggregatedModelUsage } from "../../protocol/modelUsage.js";
|
|
4
|
-
import { appendAuthoringUserMessage, ModelToolConversation, rememberAuthoringRequest, resumePausedAuthoringConversation, } from "../authoring/ModelToolConversation.js";
|
|
5
|
+
import { appendAuthoringUserMessage, authoringExchange, ModelToolConversation, rememberAuthoringRequest, resumePausedAuthoringConversation, } from "../authoring/ModelToolConversation.js";
|
|
5
6
|
import { contentTreeFingerprint } from "../content/ContentTreeFingerprint.js";
|
|
6
7
|
import { equalModelHostBinding } from "../model/ModelHost.js";
|
|
7
8
|
import { settingImprovementPromptForBinding, } from "../play/FileNativePlayPresetStore.js";
|
|
@@ -60,7 +61,7 @@ export class WorldRevisionSession {
|
|
|
60
61
|
this.#recoverWorldSessions(worldId),
|
|
61
62
|
this.#workspace.sealed(worldId),
|
|
62
63
|
]);
|
|
63
|
-
const latest = sessions
|
|
64
|
+
const latest = sessions.find((session) => !session.damagedContinuation) ?? null;
|
|
64
65
|
const shownEpoch = activeEpoch ?? (await this.#workspace.latest(worldId));
|
|
65
66
|
return {
|
|
66
67
|
epoch: shownEpoch === null
|
|
@@ -188,15 +189,25 @@ export class WorldRevisionSession {
|
|
|
188
189
|
throw new Error("Another message in this world-revision conversation is still running");
|
|
189
190
|
return { session, run: running.promise };
|
|
190
191
|
}
|
|
192
|
+
if (session.googleOutcomeUnknown)
|
|
193
|
+
throw new Error("The Google request outcome is unknown; start a fresh model context.");
|
|
194
|
+
if (session.runStatus === "paused")
|
|
195
|
+
throw new Error("Resume the paused authoring request before sending another message");
|
|
191
196
|
}
|
|
192
197
|
await this.#assertNoRunningConversation(input.worldId, session?.sessionId);
|
|
198
|
+
let host;
|
|
199
|
+
if (session !== null) {
|
|
200
|
+
host = await this.#bindExistingModelHost(session.modelBinding);
|
|
201
|
+
if (!equalModelHostBinding(session.modelBinding, host.binding()))
|
|
202
|
+
throw new Error("No saved model connection matches this world-revision conversation");
|
|
203
|
+
}
|
|
193
204
|
const epoch = await this.#workspace.open(input.worldId);
|
|
194
205
|
session ??= await this.#create(input.worldId, epoch, requestId);
|
|
195
206
|
if (session.epochId !== epoch.epochId) {
|
|
196
207
|
retargetSession(session, epoch, this.#locale());
|
|
197
208
|
await this.#store.saveSession(session);
|
|
198
209
|
}
|
|
199
|
-
|
|
210
|
+
host ??= await this.#bindExistingModelHost(session.modelBinding);
|
|
200
211
|
if (!equalModelHostBinding(session.modelBinding, host.binding()))
|
|
201
212
|
throw new Error("No saved model connection matches this world-revision conversation");
|
|
202
213
|
const { playPreset, bootstrap, preload } = await this.#compileCurrent(input.worldId, session.modelBinding);
|
|
@@ -214,10 +225,18 @@ export class WorldRevisionSession {
|
|
|
214
225
|
if (preload !== null)
|
|
215
226
|
session.authorization = authoringPreloadAuthorization(session.authorization, preload.record);
|
|
216
227
|
appendAuthoringUserMessage(session, message, requestId);
|
|
228
|
+
if (session.modelBinding.provider === "google_interactions")
|
|
229
|
+
session.googleOutcomeUnknown = false;
|
|
230
|
+
const boundaries = host.binding().provider === "google_interactions"
|
|
231
|
+
? await this.#validatedEpochBoundaries(session)
|
|
232
|
+
: undefined;
|
|
233
|
+
if (boundaries !== undefined &&
|
|
234
|
+
host.previewAuthoringRequest !== undefined)
|
|
235
|
+
session.requests.at(-1).wireRequest = host.previewAuthoringRequest(authoringExchange(session, requestId, boundaries));
|
|
217
236
|
await this.#store.saveSession(session);
|
|
218
237
|
return {
|
|
219
238
|
session,
|
|
220
|
-
run: this.#startRun(session, host, requestId),
|
|
239
|
+
run: this.#startRun(session, host, requestId, boundaries),
|
|
221
240
|
};
|
|
222
241
|
});
|
|
223
242
|
return prepared.run ?? this.#view(prepared.session);
|
|
@@ -245,8 +264,14 @@ export class WorldRevisionSession {
|
|
|
245
264
|
if (!equalModelHostBinding(session.modelBinding, host.binding()))
|
|
246
265
|
throw new Error("No saved model connection matches this world-revision conversation");
|
|
247
266
|
resumePausedAuthoringConversation(session);
|
|
267
|
+
const boundaries = host.binding().provider === "google_interactions"
|
|
268
|
+
? await this.#validatedEpochBoundaries(session)
|
|
269
|
+
: undefined;
|
|
248
270
|
await this.#store.saveSession(session);
|
|
249
|
-
return {
|
|
271
|
+
return {
|
|
272
|
+
session,
|
|
273
|
+
run: this.#startRun(session, host, requestId, boundaries),
|
|
274
|
+
};
|
|
250
275
|
});
|
|
251
276
|
return prepared.run ?? this.#view(prepared.session);
|
|
252
277
|
}
|
|
@@ -388,14 +413,18 @@ export class WorldRevisionSession {
|
|
|
388
413
|
await this.#store.saveSession(session);
|
|
389
414
|
return session;
|
|
390
415
|
}
|
|
391
|
-
#startRun(session, host, requestId) {
|
|
416
|
+
#startRun(session, host, requestId, worldRevisionBoundaries) {
|
|
392
417
|
let activeTransaction = null;
|
|
393
418
|
return this.#conversation.run({
|
|
394
419
|
changed: (durable) => this.#changed(session.worldId, durable),
|
|
395
420
|
session,
|
|
396
421
|
host,
|
|
397
422
|
requestId,
|
|
423
|
+
...(worldRevisionBoundaries === undefined
|
|
424
|
+
? {}
|
|
425
|
+
: { worldRevisionBoundaries }),
|
|
398
426
|
save: (next) => this.#store.saveSession(next),
|
|
427
|
+
recordGoogleHop: (id, hop) => this.#store.recordGoogleHop(session.sessionId, id, hop),
|
|
399
428
|
settleToolResponse: async (next, assistantItemIndex, calls) => {
|
|
400
429
|
activeTransaction = await this.#settleToolResponse(next, assistantItemIndex, calls, activeTransaction);
|
|
401
430
|
},
|
|
@@ -406,6 +435,32 @@ export class WorldRevisionSession {
|
|
|
406
435
|
},
|
|
407
436
|
});
|
|
408
437
|
}
|
|
438
|
+
async #validatedEpochBoundaries(session) {
|
|
439
|
+
const entries = [];
|
|
440
|
+
for (const [itemIndex, item] of session.modelItems.entries()) {
|
|
441
|
+
if (item.kind !== "prompt_delta")
|
|
442
|
+
continue;
|
|
443
|
+
const source = item.logicalMessages[0]?.blocks[0]?.source;
|
|
444
|
+
if (source?.startsWith("runtime:world-revision-epoch:") !== true)
|
|
445
|
+
continue;
|
|
446
|
+
const epochId = source.slice("runtime:world-revision-epoch:".length);
|
|
447
|
+
const epoch = await this.#store.readEpoch(epochId);
|
|
448
|
+
if (epoch.worldId !== session.worldId)
|
|
449
|
+
throw new Error("World-revision epoch boundary belongs to another world");
|
|
450
|
+
const valid = ["en", "zh-CN"].some((locale) => isDeepStrictEqual(item, epochBoundaryItem(epoch, locale)));
|
|
451
|
+
if (!valid)
|
|
452
|
+
throw new Error("World-revision epoch boundary is damaged");
|
|
453
|
+
entries.push({
|
|
454
|
+
itemIndex,
|
|
455
|
+
epochId,
|
|
456
|
+
baseHead: epoch.baseHead,
|
|
457
|
+
markdown: item.logicalMessages[0].markdown,
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
if (entries.length > 0 && entries.at(-1)?.epochId !== session.epochId)
|
|
461
|
+
throw new Error("World-revision epoch boundary does not match the current epoch");
|
|
462
|
+
return { currentEpochId: session.epochId, entries };
|
|
463
|
+
}
|
|
409
464
|
async #settleToolResponse(session, assistantItemIndex, calls, active) {
|
|
410
465
|
const epoch = await this.#requiredActiveEpoch(session);
|
|
411
466
|
const beforeRevision = epoch.revision;
|
|
@@ -512,9 +567,49 @@ export class WorldRevisionSession {
|
|
|
512
567
|
}
|
|
513
568
|
}
|
|
514
569
|
async #recoverIfNeeded(session) {
|
|
570
|
+
if (session.damagedContinuation)
|
|
571
|
+
return session;
|
|
515
572
|
if (this.#conversation.isRunning(session.sessionId))
|
|
516
573
|
return session;
|
|
574
|
+
const lastItem = session.modelItems.at(-1);
|
|
575
|
+
if (session.pendingSettlement === null &&
|
|
576
|
+
session.modelBinding.provider === "google_interactions" &&
|
|
577
|
+
session.runStatus === "interrupted" &&
|
|
578
|
+
session.googleOutcomeUnknown === undefined &&
|
|
579
|
+
(lastItem?.kind === "user" || lastItem?.kind === "tool")) {
|
|
580
|
+
session.googleOutcomeUnknown = true;
|
|
581
|
+
session.lastFailure =
|
|
582
|
+
"The Google request outcome is unknown; start a fresh model context.";
|
|
583
|
+
session.updatedAt = Date.now();
|
|
584
|
+
await this.#store.saveSession(session);
|
|
585
|
+
return session;
|
|
586
|
+
}
|
|
587
|
+
if (session.pendingSettlement === null &&
|
|
588
|
+
lastItem?.kind === "assistant" &&
|
|
589
|
+
lastItem.toolCalls.length > 0) {
|
|
590
|
+
finalizeUnconfirmed(session, session.modelItems.length - 1, new Error("Runtime found a complete tool response without a persisted worktree settlement intent"));
|
|
591
|
+
if (session.modelBinding.provider === "google_interactions")
|
|
592
|
+
session.googleOutcomeUnknown = false;
|
|
593
|
+
const requestId = session.activeRequestId;
|
|
594
|
+
session.runStatus = "interrupted";
|
|
595
|
+
session.activeRequestId = null;
|
|
596
|
+
if (requestId !== null)
|
|
597
|
+
session.completedRequestIds = rememberAuthoringRequest(session.completedRequestIds, requestId);
|
|
598
|
+
session.lastFailure =
|
|
599
|
+
"The complete tool response could not be settled; its calls received failure receipts. Re-read the worktree before editing.";
|
|
600
|
+
session.updatedAt = Date.now();
|
|
601
|
+
await this.#store.saveSession(session);
|
|
602
|
+
return session;
|
|
603
|
+
}
|
|
604
|
+
if (session.runStatus === "running" &&
|
|
605
|
+
session.pendingSettlement === null &&
|
|
606
|
+
session.modelBinding.provider === "google_interactions" &&
|
|
607
|
+
session.googleOutcomeUnknown !== false)
|
|
608
|
+
session.googleOutcomeUnknown = true;
|
|
609
|
+
const recoveringSettlement = session.pendingSettlement !== null;
|
|
517
610
|
if (session.pendingSettlement !== null) {
|
|
611
|
+
if (session.modelBinding.provider === "google_interactions")
|
|
612
|
+
session.googleOutcomeUnknown = false;
|
|
518
613
|
const pending = session.pendingSettlement;
|
|
519
614
|
if (pending.phase === "publication_prepared") {
|
|
520
615
|
const active = await this.#workspace.active(session.worldId);
|
|
@@ -540,15 +635,20 @@ export class WorldRevisionSession {
|
|
|
540
635
|
else
|
|
541
636
|
finalizeUnconfirmed(session, pending.assistantItemIndex, new Error("Runtime restarted before the complete tool response was prepared"));
|
|
542
637
|
}
|
|
543
|
-
if (session.runStatus !== "running" &&
|
|
638
|
+
if (session.runStatus !== "running" && !recoveringSettlement)
|
|
544
639
|
return session;
|
|
545
640
|
const requestId = session.activeRequestId;
|
|
546
|
-
session.runStatus =
|
|
641
|
+
session.runStatus =
|
|
642
|
+
session.modelBinding.provider === "google_interactions" &&
|
|
643
|
+
session.googleOutcomeUnknown === false
|
|
644
|
+
? "paused"
|
|
645
|
+
: "interrupted";
|
|
547
646
|
session.activeRequestId = null;
|
|
548
647
|
if (requestId !== null)
|
|
549
648
|
session.completedRequestIds = rememberAuthoringRequest(session.completedRequestIds, requestId);
|
|
550
|
-
session.lastFailure =
|
|
551
|
-
"The
|
|
649
|
+
session.lastFailure = session.googleOutcomeUnknown
|
|
650
|
+
? "The Google request outcome is unknown; start a fresh model context."
|
|
651
|
+
: "The previous model request stopped after its last settled worktree change. Continue this conversation when ready.";
|
|
552
652
|
session.updatedAt = Date.now();
|
|
553
653
|
await this.#store.saveSession(session);
|
|
554
654
|
return session;
|
|
@@ -624,10 +724,18 @@ export class WorldRevisionSession {
|
|
|
624
724
|
}
|
|
625
725
|
}
|
|
626
726
|
function retargetSession(session, epoch, locale) {
|
|
727
|
+
session.modelItems.push(epochBoundaryItem(epoch, locale));
|
|
728
|
+
session.epochId = epoch.epochId;
|
|
729
|
+
session.authorization = null;
|
|
730
|
+
session.pendingSettlement = null;
|
|
731
|
+
session.lastFailure = null;
|
|
732
|
+
session.updatedAt = Date.now();
|
|
733
|
+
}
|
|
734
|
+
function epochBoundaryItem(epoch, locale) {
|
|
627
735
|
const markdown = locale === "zh-CN"
|
|
628
736
|
? `# 新的世界修订 epoch\n\n上一轮修订已经应用或放弃。当前工作树是 ${epoch.epochId},从世界端点 ${epoch.baseHead} 重新建立。此前全部 search cursor 和 read 写授权已失效;修改前必须重新读取。`
|
|
629
737
|
: `# New world-revision epoch\n\nThe previous revision was applied or discarded. Worktree ${epoch.epochId} was rebuilt from world endpoint ${epoch.baseHead}. Every prior search cursor and read authorization is invalid; re-read before writing.`;
|
|
630
|
-
|
|
738
|
+
return {
|
|
631
739
|
kind: "prompt_delta",
|
|
632
740
|
logicalMessages: [
|
|
633
741
|
{
|
|
@@ -641,12 +749,7 @@ function retargetSession(session, epoch, locale) {
|
|
|
641
749
|
],
|
|
642
750
|
},
|
|
643
751
|
],
|
|
644
|
-
}
|
|
645
|
-
session.epochId = epoch.epochId;
|
|
646
|
-
session.authorization = null;
|
|
647
|
-
session.pendingSettlement = null;
|
|
648
|
-
session.lastFailure = null;
|
|
649
|
-
session.updatedAt = Date.now();
|
|
752
|
+
};
|
|
650
753
|
}
|
|
651
754
|
function finalizeSettlement(session, pending) {
|
|
652
755
|
for (const result of pending.toolResults)
|
|
@@ -745,6 +848,11 @@ function historyItem(session) {
|
|
|
745
848
|
const changed = new Set(session.modelItems.flatMap((item) => item.kind === "tool" ? item.changes.map(({ path }) => path) : []));
|
|
746
849
|
return {
|
|
747
850
|
sessionId: session.sessionId,
|
|
851
|
+
...(session.damagedContinuation
|
|
852
|
+
? {
|
|
853
|
+
unavailableReason: "Google continuation is damaged; start a fresh model context.",
|
|
854
|
+
}
|
|
855
|
+
: {}),
|
|
748
856
|
epochId: session.epochId,
|
|
749
857
|
runStatus: session.runStatus,
|
|
750
858
|
createdAt: session.createdAt,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { stringify } from "yaml";
|
|
2
|
+
/** App-owned defaults contain declarations, never copies of the prompt catalog. */
|
|
3
|
+
export function applicationPresetFilesForLocale(locale) {
|
|
4
|
+
return {
|
|
5
|
+
"preset.yaml": stringify({
|
|
6
|
+
format: "narraeon.play-preset/v3",
|
|
7
|
+
name: locale === "zh-CN" ? "系统推荐" : "System recommended",
|
|
8
|
+
callChain: "call-chain.yaml",
|
|
9
|
+
playPrompts: {},
|
|
10
|
+
authorPrompts: {},
|
|
11
|
+
mergeConsecutiveMessages: false,
|
|
12
|
+
mounts: [],
|
|
13
|
+
extensions: [],
|
|
14
|
+
}),
|
|
15
|
+
"call-chain.yaml": "format: narraeon.play-call-chain/v1\nnarrative: []\nfollowups: []\n",
|
|
16
|
+
"frame.yaml": `format: narraeon.host-frame/v1
|
|
17
|
+
roles:
|
|
18
|
+
runtime_system:
|
|
19
|
+
- builtin: runtime.play-contract
|
|
20
|
+
- builtin: runtime.tool-contract
|
|
21
|
+
- builtin: runtime.operation-contract
|
|
22
|
+
author_instruction:
|
|
23
|
+
- include: world.instructions
|
|
24
|
+
world_context:
|
|
25
|
+
- include: world.context
|
|
26
|
+
- builtin: runtime.coverage
|
|
27
|
+
`,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -51,23 +51,23 @@ roles:
|
|
|
51
51
|
`,
|
|
52
52
|
"blocks/person-second.md": `# 人称:第二人称
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
玩家可见叙事以第二人称"你"称呼玩家角色。玩家的知觉、动作和话语都以"你"写出;其他人物按名字或第三人称称呼。观察视角跟随玩家角色,不写他无法感知的事。
|
|
55
55
|
`,
|
|
56
56
|
"blocks/person-first.md": `# 人称:第一人称
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
玩家可见叙事以第一人称"我"写玩家角色,像玩家角色自己在讲述。知觉、判断和情绪只能是"我"能感知到的;其他人物按名字或第三人称称呼。叙述口吻贴近玩家角色已经建立的性格,不替他下结论或表态。本块启用时取代第二人称的要求。
|
|
59
59
|
`,
|
|
60
60
|
"blocks/person-third.md": `# 人称:第三人称
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
玩家可见叙事以第三人称写玩家角色:已知名字时用名字,否则用与其性别一致的"他/她",并与其他人物明确区分。观察视角仍跟随玩家角色,不越入其他人物未表露的内心。玩家原文里的"我""你"在正文中转为相应的第三人称。本块启用时取代第二人称的要求。
|
|
63
63
|
`,
|
|
64
64
|
"blocks/continue-stage.md": `# 承接:先演玩家行动
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
玩家原文是玩家对本轮行动和台词的简写,不是已经成立的小说正文。玩家页面上它以"你"的身份单独显示,正文里还没有它。
|
|
67
67
|
|
|
68
68
|
## 先写玩家做了什么
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
每段正文先把玩家这次的行动和台词以叙事方式写出来:动作怎样完成、话怎样说出口、周围的人和环境此刻的样子,然后才写世界的反应。台词可以保留原话,也可以按人物口吻微调措辞,不改变意思、立场和承诺。不用"你说要……"式的旁白转述代替演出。
|
|
71
71
|
|
|
72
72
|
## 分量
|
|
73
73
|
|
|
@@ -75,7 +75,7 @@ roles:
|
|
|
75
75
|
`,
|
|
76
76
|
"blocks/continue-advance.md": `# 承接:直接推进
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
玩家原文已经是正文的一部分:玩家页面上它以"你"的身份显示,玩家刚读过。不要把它再演一遍。本块启用时取代"先演玩家行动"的要求。
|
|
79
79
|
|
|
80
80
|
## 从下一件事写起
|
|
81
81
|
|
|
@@ -83,7 +83,7 @@ roles:
|
|
|
83
83
|
|
|
84
84
|
## 必要的衔接
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
输入只是简写("我去找他")、不交代过程就无法呈现结果时,用最短的衔接把它落到场景里,不扩写玩家的动作和话语本身。
|
|
87
87
|
`,
|
|
88
88
|
"blocks/agency-default.md": `# 代理权:默认
|
|
89
89
|
|
|
@@ -91,11 +91,11 @@ roles:
|
|
|
91
91
|
|
|
92
92
|
## 可以替玩家补什么
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
在已表达的选择内补足动作衔接、不改变实质的措辞和自然感官。"我去找他"这类日常简写允许开始并完成通常的连带动作:穿过走廊、敲门、把东西递过去不必逐项确认。寒暄可以替玩家接,但不能暗中承诺新事项。
|
|
95
95
|
|
|
96
96
|
## 在哪里交回玩家
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
途中出现玩家没有表达过的新风险、交易或关键选择时,停在需要玩家决定的位置。不能把"去找他"扩展成提出新请求、答应他的条件或交出物品;NPC 的提议或追问也不构成玩家尚未表达的目标与话语。拿不准时保留决定空间;不能以"下次还能改口"为理由替玩家确立当前选择。
|
|
99
99
|
|
|
100
100
|
## 感官不是态度
|
|
101
101
|
|
|
@@ -105,7 +105,7 @@ roles:
|
|
|
105
105
|
|
|
106
106
|
正文只演玩家原文明确写出的动作和台词。本块启用时取代其他代理权块。
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
连带动作只做到原文说到的地方:说"去找他"就写到走到门口,敲门和开口都交回玩家;说"把信递过去"就写到递出,对方接不接和玩家接下来说什么留给下一轮。不替玩家补台词、寒暄或回应,不替玩家做任何选择。
|
|
109
109
|
|
|
110
110
|
玩家角色的自然感官和已有事实造成的身体经验可以直接写;心跳、脸红、僵住或发不出声不能替他确立爱慕、恐惧、认同、同意、拒绝或新的行动选择。不替玩家生成未表达的感情。世界外查询或文档维护不构成角色的新决定。
|
|
111
111
|
`,
|
|
@@ -132,6 +132,8 @@ roles:
|
|
|
132
132
|
"blocks/ending.md": `# 结尾
|
|
133
133
|
|
|
134
134
|
结尾停在实际发生的最后一句话、一个动作或一个环境变化上。不写“轮到你了”“你打算怎么做”及“众人等你开口”一类的交接句,不在结尾列选项,不重复总结局势,不为了收尾让 NPC 硬添动作、邀请或承诺。真正发生的注视或等待可以描写。
|
|
135
|
+
|
|
136
|
+
一件事自然结束时,也可以就停在那里,不必再添一句话或一个动作来等玩家接续。
|
|
135
137
|
`,
|
|
136
138
|
"blocks/output.md": `# 输出边界
|
|
137
139
|
|
|
@@ -139,7 +141,7 @@ roles:
|
|
|
139
141
|
|
|
140
142
|
本轮新发生且玩家能感知的结果要成为具体可读的事:物品怎样递接、人物如何离开、态度变化怎样在话语或行动中显露。界面数值不能代替事件表现,叙事也不能与当前值冲突。文档中补录旧结果、去重和整理不要求再次演出;未向玩家表露的判断和场外事实无需用旁白揭露。
|
|
141
143
|
|
|
142
|
-
工具、目录、文档、句柄、检索、命中、Runtime
|
|
144
|
+
工具、目录、文档、句柄、检索、命中、Runtime 及失败过程只供私下裁决;正文不得出现"没搜到/没找到资料"等内部措辞,信息不足时保持世界内的不确定性。
|
|
143
145
|
|
|
144
146
|
本次只有世界外查询或维护时,按请求简洁回答或说明整理结果,不为满足小说形式添加动作、对话或时间推进。混合请求中的维护要求不成为角色台词或行动。
|
|
145
147
|
`,
|
|
@@ -206,9 +208,13 @@ summary 是目录简介,用于识别文档和判断是否需要读取。只写
|
|
|
206
208
|
`,
|
|
207
209
|
"blocks/checkpoint-policy.md": `# 检查点政策
|
|
208
210
|
|
|
209
|
-
world_checkpoint
|
|
211
|
+
world_checkpoint 只用于事件之间的间隙。一次对话、交易、冲突或活动自然结束,或场景已经转换、前一段事情告一段落时,可以登记检查点。地点改变、时间推进、在场者更换都是典型线索,但追逐穿过街道仍是同一场活动。事件结束不意味着无话可接,告别后还可以道谢,一件事办完后也可以接着谈下一件事。
|
|
212
|
+
|
|
213
|
+
登记前核对上次检查点后的交互,把后续仍需要的结果保存下来,包括当前状态、重要认知、承诺与待办和界面值,让接下来的互动不必依赖前一阶段的全部过程。新上下文按现有历史配置承接近期叙事,更早仍需要的信息应已保存。
|
|
214
|
+
|
|
215
|
+
玩家通过本轮要求表达「在此收尾」时,结合玩家输入按这个意图完成收束和保存,再登记检查点,不代玩家作出无关决定。
|
|
210
216
|
|
|
211
|
-
|
|
217
|
+
持续活动尚在进行,或当前事情还等着玩家作出决定、给出关键回应时,不因普通停笔或整理完成而登记。整理随时可做,不等于检查点。检查点不是本轮目标,不要为了登记而加快节奏、压缩过程、替玩家完成未表达的步骤或提前结束事件。登记后若又有需要保存的结果,继续工具调用补齐,再提交最终正文。
|
|
212
218
|
`,
|
|
213
219
|
"blocks/style-intimate.md": `# 文风:情感与亲密
|
|
214
220
|
|
|
@@ -216,15 +222,15 @@ world_checkpoint 只用于事件之间的间隙:上一事件已在本轮停笔
|
|
|
216
222
|
|
|
217
223
|
## 把那一刻拆开
|
|
218
224
|
|
|
219
|
-
|
|
225
|
+
不要跳过过程直接给结论。"你们牵起了手"把整段最值钱的东西省掉了。详写本轮停笔位置以内的那一刻:手伸过去的半路、指尖碰到哪里、对方是迎上来还是没有动、已经握住时的力度。
|
|
220
226
|
|
|
221
|
-
|
|
227
|
+
一个动作可以占三四行。这里的"慢"不是拖,是把玩家真正在意的那几秒补足。
|
|
222
228
|
|
|
223
229
|
## 触觉优先
|
|
224
230
|
|
|
225
231
|
这类场面里触觉排在视觉前面:温度(他的手比你想的凉)、干湿、粗糙或细腻、力度、脉搏、布料隔着的厚度。然后才是气味、呼吸的声音、贴近时听见的动静。
|
|
226
232
|
|
|
227
|
-
|
|
233
|
+
写具体的身体感觉,别只丢一个现成比喻。"像电流窜过"所有人都读过,它替代了描写而不是完成描写;如果要用,也让它落回具体位置——从指尖窜到手肘,然后停在那儿发麻。
|
|
228
234
|
|
|
229
235
|
## 身体不听话
|
|
230
236
|
|
|
@@ -240,7 +246,7 @@ world_checkpoint 只用于事件之间的间隙:上一事件已在本轮停笔
|
|
|
240
246
|
|
|
241
247
|
## 时间会变形
|
|
242
248
|
|
|
243
|
-
|
|
249
|
+
紧张或动情的时候,一瞬间在感觉里会被拉长:你会注意到平时根本不会注意的东西——他睫毛的影子、空调的声音、自己手心出汗。用这种被放大的细节表示时间慢下来了,比直接写"时间仿佛静止"有效得多。
|
|
244
250
|
`,
|
|
245
251
|
"blocks/style-noir.md": `# 文风:冷硬与推理
|
|
246
252
|
|
|
@@ -250,7 +256,7 @@ world_checkpoint 只用于事件之间的间隙:上一事件已在本轮停笔
|
|
|
250
256
|
|
|
251
257
|
摄影机式的叙述:写人物做了什么、说了什么、现场有什么,不写他心里怎么想。情绪从动作的偏差里渗出来——手抖了一下、烟点了两次才着、答话比正常慢了半秒。
|
|
252
258
|
|
|
253
|
-
|
|
259
|
+
不替角色下判断。不写"他显然在撒谎",写他回答之前先看了一眼门。
|
|
254
260
|
|
|
255
261
|
## 细节是证据
|
|
256
262
|
|
|
@@ -342,7 +348,7 @@ NPC 的回答可以短、冷、有停顿;对话的紧张不要求玩家连续
|
|
|
342
348
|
|
|
343
349
|
## 具体的小事
|
|
344
350
|
|
|
345
|
-
|
|
351
|
+
抽象的感受落到具体的东西上:不写"他很孤独",写他多摆了一副碗筷。不写"时间过得很久",写窗台上那盆花换了一茬。
|
|
346
352
|
|
|
347
353
|
## 对话有下文
|
|
348
354
|
|
|
@@ -395,23 +401,23 @@ Show a scene through ongoing activity. Do not inventory everyone by name, and no
|
|
|
395
401
|
`,
|
|
396
402
|
"blocks/person-second.md": `# Person: second person
|
|
397
403
|
|
|
398
|
-
Address the player character as
|
|
404
|
+
Address the player character as "you" in all player-visible narrative. Their perceptions, actions and words are written as "you"; other characters are named or referred to in the third person. The viewpoint follows the player character and does not describe what they cannot perceive.
|
|
399
405
|
`,
|
|
400
406
|
"blocks/person-first.md": `# Person: first person
|
|
401
407
|
|
|
402
|
-
Write the player character in the first person,
|
|
408
|
+
Write the player character in the first person, "I", as if the player character were telling the story. Perceptions, judgments and feelings are limited to what "I" can perceive; other characters are named or referred to in the third person. Keep the voice close to the player character's established personality without drawing conclusions or taking stances on their behalf. When this block is enabled it replaces the second-person requirement.
|
|
403
409
|
`,
|
|
404
410
|
"blocks/person-third.md": `# Person: third person
|
|
405
411
|
|
|
406
|
-
Write the player character in the third person: by name when known, otherwise with a pronoun that matches their gender, clearly distinguished from other characters. The viewpoint still follows the player character and does not enter other characters' unexpressed thoughts.
|
|
412
|
+
Write the player character in the third person: by name when known, otherwise with a pronoun that matches their gender, clearly distinguished from other characters. The viewpoint still follows the player character and does not enter other characters' unexpressed thoughts. "I" and "you" in the player's text become the corresponding third person in the prose. When this block is enabled it replaces the second-person requirement.
|
|
407
413
|
`,
|
|
408
414
|
"blocks/continue-stage.md": `# Continuation: stage the player's action first
|
|
409
415
|
|
|
410
|
-
The player's text is shorthand for this turn's action and dialogue, not finished prose of the novel. The player page shows it separately under
|
|
416
|
+
The player's text is shorthand for this turn's action and dialogue, not finished prose of the novel. The player page shows it separately under "you"; the narrative does not contain it yet.
|
|
411
417
|
|
|
412
418
|
## Write what the player does first
|
|
413
419
|
|
|
414
|
-
Each passage first stages the player's action and dialogue as narrative: how the movement is carried out, how the words come out, what the people and surroundings look like at that moment. Only then write the world's reaction. Dialogue may keep the original words or be lightly adjusted to the character's voice without changing its meaning, stance or commitments. Do not replace the staging with a narrated summary such as
|
|
420
|
+
Each passage first stages the player's action and dialogue as narrative: how the movement is carried out, how the words come out, what the people and surroundings look like at that moment. Only then write the world's reaction. Dialogue may keep the original words or be lightly adjusted to the character's voice without changing its meaning, stance or commitments. Do not replace the staging with a narrated summary such as "You said you would…".
|
|
415
421
|
|
|
416
422
|
## Weight
|
|
417
423
|
|
|
@@ -419,7 +425,7 @@ The length of the player's own action follows the pacing rules: an ordinary acti
|
|
|
419
425
|
`,
|
|
420
426
|
"blocks/continue-advance.md": `# Continuation: advance directly
|
|
421
427
|
|
|
422
|
-
The player's text is already part of the narrative: the player page shows it under
|
|
428
|
+
The player's text is already part of the narrative: the player page shows it under "you" and the player has just read it. Do not perform it again. When this block is enabled it replaces the requirement to stage the player's action first.
|
|
423
429
|
|
|
424
430
|
## Start with the next thing
|
|
425
431
|
|
|
@@ -427,7 +433,7 @@ Begin the passage with the first thing that happens after the player's input: th
|
|
|
427
433
|
|
|
428
434
|
## Minimal bridging
|
|
429
435
|
|
|
430
|
-
When the input is shorthand (
|
|
436
|
+
When the input is shorthand ("I go find him") and the result cannot be shown without some process, use the shortest bridge that places it in the scene, without expanding the player's own action or words.
|
|
431
437
|
`,
|
|
432
438
|
"blocks/agency-default.md": `# Agency: default
|
|
433
439
|
|
|
@@ -435,7 +441,7 @@ The player's goals, destination, stance, attitude, promises, giving or accepting
|
|
|
435
441
|
|
|
436
442
|
## What may be filled in for the player
|
|
437
443
|
|
|
438
|
-
Within an expressed choice, fill in supporting movements, wording that preserves the meaning, and natural sensory experience. Everyday shorthand such as
|
|
444
|
+
Within an expressed choice, fill in supporting movements, wording that preserves the meaning, and natural sensory experience. Everyday shorthand such as "I go find him" allows starting and completing ordinary supporting actions: walking the corridor, knocking, handing something over needs no separate confirmation. Small talk may be answered for the player, but it must not silently create a commitment.
|
|
439
445
|
|
|
440
446
|
## Where to hand back
|
|
441
447
|
|
|
@@ -449,7 +455,7 @@ The player character's natural sensory and bodily experience may be stated direc
|
|
|
449
455
|
|
|
450
456
|
The prose performs only the actions and dialogue the player's text states explicitly. When this block is enabled it replaces the other agency blocks.
|
|
451
457
|
|
|
452
|
-
Supporting actions go only as far as the text says:
|
|
458
|
+
Supporting actions go only as far as the text says: "go find him" is written up to reaching the door, and knocking or speaking returns to the player; "hand over the letter" is written up to the handover, and whether it is taken and what the player says next belong to the next turn. Do not fill in lines, small talk or replies for the player, and do not make any choice for them.
|
|
453
459
|
|
|
454
460
|
The player character's natural sensory and bodily experience may be stated directly when supported by established facts; heartbeat, blushing, freezing or loss of speech must not establish attraction, fear, agreement, consent, refusal or a new choice for the player. Do not invent unexpressed player feelings. Out-of-world inquiry or document maintenance constitutes no new character decision.
|
|
455
461
|
`,
|
|
@@ -475,7 +481,9 @@ Ending a reply does not require ending the in-world event.
|
|
|
475
481
|
`,
|
|
476
482
|
"blocks/ending.md": `# Ending
|
|
477
483
|
|
|
478
|
-
End on the last line
|
|
484
|
+
End on the last line, action or change in the surroundings that actually occurred. Do not write handoffs such as “Your turn”, “What do you do?” or “Everyone waits for you to speak”; do not end by listing options or repeating a situation summary; do not invent an NPC action, invitation or promise to close the passage. Actual attention or waiting may be described.
|
|
485
|
+
|
|
486
|
+
When an event naturally ends, you can stop there without adding another line or action to invite continuation.
|
|
479
487
|
`,
|
|
480
488
|
"blocks/output.md": `# Output boundaries
|
|
481
489
|
|
|
@@ -483,7 +491,7 @@ In-world narrative uses only the novel's prose, in several natural paragraphs if
|
|
|
483
491
|
|
|
484
492
|
Make new perceivable outcomes this turn concrete: how an object is handed over, how a person leaves, how an attitude shows in words or action. Interface values do not replace dramatization, and prose must agree with current values. Delayed recording, deduplication and maintenance do not require performing old events again; unexpressed judgments and offstage facts need not be revealed in narration.
|
|
485
493
|
|
|
486
|
-
Tools, directories, documents, handles, searches, matches, Runtime and failed procedures serve private adjudication only. The prose must not contain internal wording such as
|
|
494
|
+
Tools, directories, documents, handles, searches, matches, Runtime and failed procedures serve private adjudication only. The prose must not contain internal wording such as "nothing was found in the records"; keep uncertainty in-world when information is insufficient.
|
|
487
495
|
|
|
488
496
|
If this request is solely out-of-world inquiry or maintenance, answer or explain the consolidation briefly as requested without adding actions, dialogue or time passage to satisfy novel form. Maintenance instructions within a mixed request do not become character speech or actions.
|
|
489
497
|
`,
|
|
@@ -550,9 +558,13 @@ Preserve the visible meaning of player-view targets. Containers display subtrees
|
|
|
550
558
|
`,
|
|
551
559
|
"blocks/checkpoint-policy.md": `# Checkpoint policy
|
|
552
560
|
|
|
553
|
-
Use world_checkpoint only for gaps between events
|
|
561
|
+
Use world_checkpoint only for gaps between events. You may register when a conversation, transaction, conflict or activity naturally ends, or when the scene has changed and the preceding event has concluded. Typical clues include a change of location, passage of time or change of participants, but a chase through another street is still the same activity. An event can end while leaving room to reply: the player can still thank someone after a farewell, or start another topic after completing a task.
|
|
562
|
+
|
|
563
|
+
Before registration, reconcile interactions since the previous checkpoint and save outcomes that will still be needed, including current state, important knowledge, commitments, pending work and interface values, so the next interaction can continue without depending on the previous phase's entire process. A fresh context carries recent narrative according to the existing history configuration; earlier information that still matters should already be saved.
|
|
564
|
+
|
|
565
|
+
When the player's request for this turn says “Wrap up here”, follow that intent together with their input to conclude and save, then register a checkpoint, without making unrelated decisions for them.
|
|
554
566
|
|
|
555
|
-
|
|
567
|
+
While an activity is still ongoing, or the current event still needs the player to decide or give a key response, do not register just because you stop writing or finish consolidation. Consolidation can happen at any time; it is not a checkpoint. A checkpoint is not a goal for this turn. Do not accelerate pace, compress process, complete steps the player has not expressed, or end an event early to register one. If further outcomes need saving after registration, continue tool calls to save them, then submit the final prose.
|
|
556
568
|
`,
|
|
557
569
|
"blocks/style-intimate.md": `# Style: emotion and intimacy
|
|
558
570
|
|
|
@@ -560,15 +572,15 @@ In these scenes, the center is not what happened but what the body and mind expe
|
|
|
560
572
|
|
|
561
573
|
## Separate the moment
|
|
562
574
|
|
|
563
|
-
Do not skip the process and state only the conclusion.
|
|
575
|
+
Do not skip the process and state only the conclusion. "You held hands" discards the most valuable part. Detail the moment within this reply's stopping point: the hand crossing the distance, what the fingertips touch, whether the other person meets it or stays still, and the pressure if the grip has closed.
|
|
564
576
|
|
|
565
|
-
One action may take three or four lines.
|
|
577
|
+
One action may take three or four lines. "Slow" here does not mean dragging; it means fully rendering the few seconds the player cares about.
|
|
566
578
|
|
|
567
579
|
## Put touch first
|
|
568
580
|
|
|
569
581
|
In an intimate scene, touch comes before sight: temperature (their hand is cooler than expected), dryness or dampness, roughness or softness, pressure, pulse, and the thickness of fabric between bodies. Then bring in scent, the sound of breathing, and noises audible only at close range.
|
|
570
582
|
|
|
571
|
-
Describe the actual bodily sensation instead of reaching immediately for a familiar comparison. Everyone has read
|
|
583
|
+
Describe the actual bodily sensation instead of reaching immediately for a familiar comparison. Everyone has read "a jolt of electricity"; it replaces description instead of completing it. If you use it, return it to a specific place—from fingertips to elbow, leaving numbness there.
|
|
572
584
|
|
|
573
585
|
## The body does not obey
|
|
574
586
|
|
|
@@ -584,7 +596,7 @@ Whether the other person accepts is always determined by their present character
|
|
|
584
596
|
|
|
585
597
|
## Time changes shape
|
|
586
598
|
|
|
587
|
-
Under tension or emotion, an instant stretches in perception. Details normally ignored become vivid—the shadow of eyelashes, the air conditioner's hum, sweat in one's palm. Use such magnified detail to show time slowing instead of saying
|
|
599
|
+
Under tension or emotion, an instant stretches in perception. Details normally ignored become vivid—the shadow of eyelashes, the air conditioner's hum, sweat in one's palm. Use such magnified detail to show time slowing instead of saying "time seemed to stop."
|
|
588
600
|
`,
|
|
589
601
|
"blocks/style-noir.md": `# Style: hard-boiled investigation
|
|
590
602
|
|
|
@@ -594,7 +606,7 @@ Short sentences. End on verbs. Remove every adjective you can.
|
|
|
594
606
|
|
|
595
607
|
Use a camera-like narration: write what people do, what they say, and what is present. Do not state what they think. Let emotion leak through deviations in action—a shaking hand, a cigarette that takes two attempts to light, an answer half a second too slow.
|
|
596
608
|
|
|
597
|
-
Do not judge for a character. Do not write
|
|
609
|
+
Do not judge for a character. Do not write "they are obviously lying"; write that they look at the door before answering.
|
|
598
610
|
|
|
599
611
|
## Details are evidence
|
|
600
612
|
|
|
@@ -686,7 +698,7 @@ Leave emotional interpretation to the reader, but make established outcomes clea
|
|
|
686
698
|
|
|
687
699
|
## Small, concrete things
|
|
688
700
|
|
|
689
|
-
Anchor abstract feeling in an object or action. Do not write
|
|
701
|
+
Anchor abstract feeling in an object or action. Do not write "he is lonely"; write that he sets out an extra bowl and pair of chopsticks. Do not write "a long time passed"; write that the plant on the sill has bloomed and been replaced.
|
|
690
702
|
|
|
691
703
|
## Dialogue has another layer
|
|
692
704
|
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { defaultPresetHostFilesForLocale } from "./default-preset-host.js";
|
|
3
|
-
import { defaultSettingImprovementPromptForLocale, defaultSettingImprovementPromptPath, } from "./default-setting-improvement-prompt.js";
|
|
1
|
+
import { applicationPresetFilesForLocale } from "./application-preset-files.js";
|
|
4
2
|
/**
|
|
5
3
|
* An ordinary, portable play-preset asset. Runtime treats this exactly like
|
|
6
4
|
* any author-supplied file map. The export is only a convenient workbench
|
|
@@ -9,11 +7,13 @@ import { defaultSettingImprovementPromptForLocale, defaultSettingImprovementProm
|
|
|
9
7
|
export function firstPartyActionChoicesPresetFilesForLocale(locale) {
|
|
10
8
|
const displayName = locale === "zh-CN" ? "下一步建议" : "Next-step ideas";
|
|
11
9
|
return {
|
|
12
|
-
|
|
10
|
+
...applicationPresetFilesForLocale(locale),
|
|
11
|
+
"preset.yaml": `format: narraeon.play-preset/v3
|
|
13
12
|
name: action-choices-recommended
|
|
14
13
|
callChain: call-chain.yaml
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
playPrompts: {}
|
|
15
|
+
authorPrompts: {}
|
|
16
|
+
mergeConsecutiveMessages: false
|
|
17
17
|
mounts:
|
|
18
18
|
player.options: composer_below
|
|
19
19
|
extensions:
|
|
@@ -22,8 +22,7 @@ extensions:
|
|
|
22
22
|
- assets/player-options.css
|
|
23
23
|
`,
|
|
24
24
|
"call-chain.yaml": `format: narraeon.play-call-chain/v1
|
|
25
|
-
narrative:
|
|
26
|
-
- markdown: prompts/narrate.md
|
|
25
|
+
narrative: []
|
|
27
26
|
followups:
|
|
28
27
|
- id: player_options
|
|
29
28
|
displayName: ${displayName}
|
|
@@ -76,9 +75,6 @@ followups:
|
|
|
76
75
|
minLength: 1
|
|
77
76
|
maxLength: 1000
|
|
78
77
|
`,
|
|
79
|
-
...defaultPresetHostFilesForLocale(locale),
|
|
80
|
-
"prompts/narrate.md": defaultNarrationPromptForLocale(locale),
|
|
81
|
-
[defaultSettingImprovementPromptPath]: defaultSettingImprovementPromptForLocale(locale),
|
|
82
78
|
"prompts/options.md": locale === "zh-CN"
|
|
83
79
|
? `# 提交后下一步建议
|
|
84
80
|
|