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,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
|
* Ordinary copyable examples for the generic artifact seam. Runtime does not
|
|
6
4
|
* privilege these output names or channels; authors may replace every file.
|
|
@@ -9,11 +7,13 @@ export function firstPartyGenericPanelsPresetFilesForLocale(locale) {
|
|
|
9
7
|
const displayName = locale === "zh-CN" ? "发布通用面板" : "Publish generic panels";
|
|
10
8
|
const panelTitle = locale === "zh-CN" ? "通用 HTML 面板" : "Generic HTML panel";
|
|
11
9
|
return {
|
|
12
|
-
|
|
10
|
+
...applicationPresetFilesForLocale(locale),
|
|
11
|
+
"preset.yaml": `format: narraeon.play-preset/v3
|
|
13
12
|
name: generic-panels-recommended
|
|
14
13
|
callChain: call-chain.yaml
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
playPrompts: {}
|
|
15
|
+
authorPrompts: {}
|
|
16
|
+
mergeConsecutiveMessages: false
|
|
17
17
|
mounts:
|
|
18
18
|
generic.markdown: story
|
|
19
19
|
generic.html: sidebar
|
|
@@ -24,8 +24,7 @@ extensions:
|
|
|
24
24
|
- assets/generic-html.css
|
|
25
25
|
`,
|
|
26
26
|
"call-chain.yaml": `format: narraeon.play-call-chain/v1
|
|
27
|
-
narrative:
|
|
28
|
-
- markdown: prompts/narrate.md
|
|
27
|
+
narrative: []
|
|
29
28
|
followups:
|
|
30
29
|
- id: publish_panels
|
|
31
30
|
displayName: ${displayName}
|
|
@@ -66,9 +65,6 @@ followups:
|
|
|
66
65
|
required: false
|
|
67
66
|
maxEmits: 8
|
|
68
67
|
`,
|
|
69
|
-
...defaultPresetHostFilesForLocale(locale),
|
|
70
|
-
"prompts/narrate.md": defaultNarrationPromptForLocale(locale),
|
|
71
|
-
[defaultSettingImprovementPromptPath]: defaultSettingImprovementPromptForLocale(locale),
|
|
72
68
|
"prompts/panels.md": locale === "zh-CN"
|
|
73
69
|
? `# 通用面板
|
|
74
70
|
|
|
@@ -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
|
* Ordinary, copyable player-view panel assets. Runtime understands only the
|
|
6
4
|
* generic playerViewPanels contract; this is not a privileged status system.
|
|
@@ -12,11 +10,13 @@ export function firstPartyStatusPanelPresetFilesForLocale(locale) {
|
|
|
12
10
|
const emptyPrefix = locale === "zh-CN" ? "暂无项目:" : "No items: ";
|
|
13
11
|
const diagnosticsSuffix = locale === "zh-CN" ? " 项视图诊断" : " player-view diagnostics";
|
|
14
12
|
return {
|
|
15
|
-
|
|
13
|
+
...applicationPresetFilesForLocale(locale),
|
|
14
|
+
"preset.yaml": `format: narraeon.play-preset/v3
|
|
16
15
|
name: status-panel-recommended
|
|
17
16
|
callChain: call-chain.yaml
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
playPrompts: {}
|
|
18
|
+
authorPrompts: {}
|
|
19
|
+
mergeConsecutiveMessages: false
|
|
20
20
|
mounts: []
|
|
21
21
|
playerViewPanels:
|
|
22
22
|
- id: current_view
|
|
@@ -46,13 +46,9 @@ extensions:
|
|
|
46
46
|
- assets/player-view-status.css
|
|
47
47
|
`,
|
|
48
48
|
"call-chain.yaml": `format: narraeon.play-call-chain/v1
|
|
49
|
-
narrative:
|
|
50
|
-
- markdown: prompts/narrate.md
|
|
49
|
+
narrative: []
|
|
51
50
|
followups: []
|
|
52
51
|
`,
|
|
53
|
-
...defaultPresetHostFilesForLocale(locale),
|
|
54
|
-
"prompts/narrate.md": defaultNarrationPromptForLocale(locale),
|
|
55
|
-
[defaultSettingImprovementPromptPath]: defaultSettingImprovementPromptForLocale(locale),
|
|
56
52
|
"renderers/player-view-status.html": `<!doctype html>
|
|
57
53
|
<html><head><meta charset="utf-8"><title>${currentStatus}</title></head><body>
|
|
58
54
|
<main id="player-view-panel-root" aria-label="${currentStatus}"></main>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { defaultSettingImprovementPromptForLocale, revisionSettingImprovementPromptForLocale, } from "./default-setting-improvement-prompt.js";
|
|
2
2
|
import { tabletopAuthorBody } from "./tabletop-guidance.js";
|
|
3
|
+
import { defaultEnabledBuiltin, } from "./ordered-play-prompts.js";
|
|
3
4
|
/** Authoring uses the same permissions as play, with target-specific dynamic references. */
|
|
4
5
|
export function builtinAuthorPrompts(locale, target = "setting") {
|
|
5
6
|
const zh = locale === "zh-CN";
|
|
@@ -70,10 +71,11 @@ export function authoringMechanics(locale, target) {
|
|
|
70
71
|
return `# ${zh ? "创作工具与结算" : "Authoring tools and settlement"}\n\n${common}\n\n${boundary}\n\n${views}`;
|
|
71
72
|
}
|
|
72
73
|
export function defaultOrderedAuthorPrompts() {
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
const catalog = builtinAuthorPrompts("en");
|
|
75
|
+
return catalog.map((item) => ({
|
|
76
|
+
id: item.id,
|
|
75
77
|
kind: "builtin",
|
|
76
|
-
builtin: id,
|
|
77
|
-
enabled:
|
|
78
|
+
builtin: item.id,
|
|
79
|
+
enabled: defaultEnabledBuiltin(item, catalog),
|
|
78
80
|
}));
|
|
79
81
|
}
|
|
@@ -102,7 +102,7 @@ export function builtinPlayPrompts(locale) {
|
|
|
102
102
|
|
|
103
103
|
world_patch 的 YAML locator 可以包含 map key 和从零开始的数组下标;add 与 replace 是 upsert,无论目标是否存在都写入,下标达到或超过长度时追加;remove 删除一个精确位置,目标不存在时为无操作。每条 edit 独立结算:失败的 edit 被跳过,其余照常生效;回执列出失败项时,已生效的 edit 不要重发,只补发列出的 edit。未提供的元数据保持原值。world_retire 从目录注入中退役文档,文档仍可读取、引用和恢复。工具响应的成功状态变化与后续最终叙事分别提交,后续失败不回滚已提交变化。成功写入回执表示当前值已改变;以真实回执的结算状态为准,模型文字不能提交世界。
|
|
104
104
|
|
|
105
|
-
包含工具调用的响应属于中间步,其中的文字尚未展示或提交为最终叙事。收到全部工具结果后,根据回执在最终正文中呈现本轮尚未展示的内容,并与已提交状态一致;没有工具调用的非空正文结束本次循环并由 Runtime 提交。world_checkpoint
|
|
105
|
+
包含工具调用的响应属于中间步,其中的文字尚未展示或提交为最终叙事。收到全部工具结果后,根据回执在最终正文中呈现本轮尚未展示的内容,并与已提交状态一致;没有工具调用的非空正文结束本次循环并由 Runtime 提交。world_checkpoint 登记历史重放边界并向玩家建议从此开始全新上下文;在随后非空且无工具调用的最终正文成功提交后生效,边界包含该正文;登记后仍可继续工具调用。登记不结束世界内事件,也不自动切换上下文;界面预选下一次全新上下文,玩家可以取消预选。全新上下文完整补入边界后的已提交玩家原文与最终叙事,非空时替代 frame 的 recent;为空时沿用 frame 的 recent 声明,可以带入检查点所在正文。两种选择都排除开场白、工具和推理。何时登记由检查点政策块规定。
|
|
106
106
|
|
|
107
107
|
${historyUseGuidance(locale)}`
|
|
108
108
|
: `# Tools and response settlement
|
|
@@ -111,7 +111,7 @@ Use only attached tools. Directories use @dir-* handles from the preload or tool
|
|
|
111
111
|
|
|
112
112
|
YAML world_patch locators accept map keys and zero-based array indexes; add and replace are upserts that write whether or not the target exists, and an index past the end appends; remove deletes one exact location and is a no-op when it is absent. Each edit settles on its own: a failed edit is skipped and the others still apply, so when a receipt lists failed edits, send again only those. Omitted metadata remains unchanged. world_retire removes a document from catalog injection while keeping it readable, referenceable and restorable. Successful tool-response state changes and later final narrative commit separately; later failure does not roll back committed changes. Successful write receipts change current values; the actual receipt determines settlement, not model prose.
|
|
113
113
|
|
|
114
|
-
Responses containing tool calls are intermediate steps; their text has not yet been displayed or committed as final narrative. After all tool results, use the receipts to present this turn's still-unshown content in final prose consistent with committed state. A nonempty tool-free response ends this loop and Runtime commits it. world_checkpoint registers the history-replay boundary and suggests a fresh context from that point to the player; it takes effect after the subsequent nonempty tool-free final prose successfully commits, including that prose, and further tool calls may follow registration. Registration neither ends an in-world event nor switches context,
|
|
114
|
+
Responses containing tool calls are intermediate steps; their text has not yet been displayed or committed as final narrative. After all tool results, use the receipts to present this turn's still-unshown content in final prose consistent with committed state. A nonempty tool-free response ends this loop and Runtime commits it. world_checkpoint registers the history-replay boundary and suggests a fresh context from that point to the player; it takes effect after the subsequent nonempty tool-free final prose successfully commits, including that prose, and further tool calls may follow registration. Registration neither ends an in-world event nor switches context; the interface preselects a fresh context for the next send, and the player can cancel the preselection. A fresh context replays all committed player originals and final narratives after the boundary; nonempty replay replaces the frame's recent selection. When it is empty, the frame's recent declaration applies and may include the checkpoint narrative. Both selections exclude the opening, tools and reasoning. The checkpoint policy block decides when to register.
|
|
115
115
|
|
|
116
116
|
${historyUseGuidance(locale)}`,
|
|
117
117
|
},
|
|
@@ -243,6 +243,8 @@ ${historyUseGuidance(locale)}`,
|
|
|
243
243
|
}
|
|
244
244
|
/** Ids of the builtins that ship enabled: each group's first member and every policy. */
|
|
245
245
|
export function defaultEnabledBuiltin(item, catalog) {
|
|
246
|
+
if (item.defaultEnabled !== undefined)
|
|
247
|
+
return item.defaultEnabled;
|
|
246
248
|
if (item.category === "style")
|
|
247
249
|
return false;
|
|
248
250
|
if (item.group === undefined)
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { defaultEnabledBuiltin, } from "./ordered-play-prompts.js";
|
|
2
|
+
export const retiredBuiltinReplacements = {
|
|
3
|
+
"play.style": [{ builtin: "play.prose" }, { builtin: "play.scene" }],
|
|
4
|
+
"play.adjudication": [
|
|
5
|
+
{ builtin: "play.agency-default" },
|
|
6
|
+
{ builtin: "play.agency-strict", selected: false },
|
|
7
|
+
{ builtin: "play.agency-open", selected: false },
|
|
8
|
+
{ builtin: "play.facts" },
|
|
9
|
+
{ builtin: "play.npc" },
|
|
10
|
+
],
|
|
11
|
+
"play.narrative": [
|
|
12
|
+
{ builtin: "play.stop-beat" },
|
|
13
|
+
{ builtin: "play.stop-scene", selected: false },
|
|
14
|
+
{ builtin: "play.ending" },
|
|
15
|
+
{ builtin: "play.output" },
|
|
16
|
+
],
|
|
17
|
+
"play.state": [
|
|
18
|
+
{ builtin: "play.save-timing" },
|
|
19
|
+
{ builtin: "play.ownership" },
|
|
20
|
+
{ builtin: "play.situation" },
|
|
21
|
+
{ builtin: "play.checkpoint-policy" },
|
|
22
|
+
],
|
|
23
|
+
};
|
|
24
|
+
export function promptArrangementKey(entry) {
|
|
25
|
+
return entry.kind === "builtin"
|
|
26
|
+
? `builtin:${entry.builtin}`
|
|
27
|
+
: `entry:${entry.id}`;
|
|
28
|
+
}
|
|
29
|
+
function reserveBuiltinPromptId(preferred, builtin, used) {
|
|
30
|
+
let id = preferred;
|
|
31
|
+
let suffix = 0;
|
|
32
|
+
while (used.has(id))
|
|
33
|
+
id = `builtin-${builtin}-${++suffix}`;
|
|
34
|
+
used.add(id);
|
|
35
|
+
return id;
|
|
36
|
+
}
|
|
37
|
+
/** Expand retired identities in place; an explicit successor always wins. */
|
|
38
|
+
export function expandRetiredPrompts(entries) {
|
|
39
|
+
const explicit = new Set(entries.flatMap((e) => (e.kind === "builtin" ? [e.builtin] : [])));
|
|
40
|
+
const usedIds = new Set(entries
|
|
41
|
+
.filter((entry) => entry.kind !== "builtin" ||
|
|
42
|
+
!retiredBuiltinReplacements[entry.builtin])
|
|
43
|
+
.map((entry) => entry.id));
|
|
44
|
+
return entries.flatMap((entry) => {
|
|
45
|
+
if (entry.kind !== "builtin")
|
|
46
|
+
return [entry];
|
|
47
|
+
const replacements = retiredBuiltinReplacements[entry.builtin];
|
|
48
|
+
if (!replacements)
|
|
49
|
+
return [entry];
|
|
50
|
+
return replacements
|
|
51
|
+
.filter((r) => !explicit.has(r.builtin))
|
|
52
|
+
.map(({ builtin, selected }) => {
|
|
53
|
+
const id = reserveBuiltinPromptId(builtin, builtin, usedIds);
|
|
54
|
+
return {
|
|
55
|
+
...entry,
|
|
56
|
+
id,
|
|
57
|
+
builtin,
|
|
58
|
+
enabled: selected === false ? false : entry.enabled,
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Missing defaults follow the nearest preceding catalog neighbor, otherwise the
|
|
65
|
+
* nearest following neighbor. Existing entries never move relative to each other.
|
|
66
|
+
*/
|
|
67
|
+
export function resolvePromptArrangement(arrangement, catalog, worldRequired = true) {
|
|
68
|
+
const defaults = catalog.map((item) => ({
|
|
69
|
+
id: item.id,
|
|
70
|
+
kind: "builtin",
|
|
71
|
+
builtin: item.id,
|
|
72
|
+
enabled: defaultEnabledBuiltin(item, catalog),
|
|
73
|
+
}));
|
|
74
|
+
if (worldRequired)
|
|
75
|
+
defaults.push({ id: "world", kind: "world" });
|
|
76
|
+
const overrides = expandRetiredPrompts((arrangement.overrides ?? []).map((o) => ({
|
|
77
|
+
...o,
|
|
78
|
+
id: o.id ?? o.builtin,
|
|
79
|
+
kind: "builtin",
|
|
80
|
+
enabled: o.enabled ??
|
|
81
|
+
(catalog.some((c) => c.id === o.builtin)
|
|
82
|
+
? defaultEnabledBuiltin(catalog.find((c) => c.id === o.builtin), catalog)
|
|
83
|
+
: true),
|
|
84
|
+
})));
|
|
85
|
+
const values = defaults.map((entry) => {
|
|
86
|
+
if (entry.kind !== "builtin")
|
|
87
|
+
return entry;
|
|
88
|
+
const override = overrides.find((o) => o.kind === "builtin" && o.builtin === entry.builtin);
|
|
89
|
+
const item = catalog.find((c) => c.id === entry.builtin);
|
|
90
|
+
const selection = item.group === undefined ? undefined : arrangement.groups?.[item.group];
|
|
91
|
+
const selected = selection?.flatMap((id) => retiredBuiltinReplacements[id]
|
|
92
|
+
?.filter((r) => r.selected !== false)
|
|
93
|
+
.map((r) => r.builtin) ?? [id]);
|
|
94
|
+
return {
|
|
95
|
+
...entry,
|
|
96
|
+
...override,
|
|
97
|
+
...(selected === undefined
|
|
98
|
+
? {}
|
|
99
|
+
: { enabled: selected.includes(item.id) }),
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
const custom = structuredClone(arrangement.entries ?? []);
|
|
103
|
+
const world = custom.find((e) => e.kind === "world");
|
|
104
|
+
if (world !== undefined)
|
|
105
|
+
values.splice(values.findIndex((e) => e.kind === "world"), 1, world);
|
|
106
|
+
values.push(...custom.filter((e) => e.kind !== "world"));
|
|
107
|
+
// A newly shipped identity must not collide with an existing user's identity.
|
|
108
|
+
const aliases = new Map((arrangement.overrides ?? []).flatMap((override) => override.id === undefined ||
|
|
109
|
+
!catalog.some((item) => item.id === override.builtin)
|
|
110
|
+
? []
|
|
111
|
+
: [[override.builtin, override.id]]));
|
|
112
|
+
const usedIds = new Set([...custom.map((e) => e.id), ...aliases.values()]);
|
|
113
|
+
for (const entry of values) {
|
|
114
|
+
if (entry.kind !== "builtin")
|
|
115
|
+
continue;
|
|
116
|
+
entry.id =
|
|
117
|
+
aliases.get(entry.builtin) ??
|
|
118
|
+
reserveBuiltinPromptId(entry.id, entry.builtin, usedIds);
|
|
119
|
+
}
|
|
120
|
+
const byKey = new Map(values.map((e) => [promptArrangementKey(e), e]));
|
|
121
|
+
const explicitOrder = new Set(arrangement.order ?? []);
|
|
122
|
+
const order = (arrangement.order ?? []).flatMap((key) => {
|
|
123
|
+
const old = key.startsWith("builtin:")
|
|
124
|
+
? retiredBuiltinReplacements[key.slice(8)]
|
|
125
|
+
: undefined;
|
|
126
|
+
return old
|
|
127
|
+
? old
|
|
128
|
+
.filter((r) => !explicitOrder.has(`builtin:${r.builtin}`))
|
|
129
|
+
.map((r) => `builtin:${r.builtin}`)
|
|
130
|
+
: [key];
|
|
131
|
+
});
|
|
132
|
+
const result = [];
|
|
133
|
+
for (const key of order) {
|
|
134
|
+
const entry = byKey.get(key);
|
|
135
|
+
if (entry && !result.includes(entry))
|
|
136
|
+
result.push(entry);
|
|
137
|
+
}
|
|
138
|
+
const defaultKeys = defaults.map(promptArrangementKey);
|
|
139
|
+
for (const entry of values) {
|
|
140
|
+
if (result.includes(entry))
|
|
141
|
+
continue;
|
|
142
|
+
const position = defaultKeys.indexOf(promptArrangementKey(entry));
|
|
143
|
+
const previous = defaultKeys
|
|
144
|
+
.slice(0, position)
|
|
145
|
+
.reverse()
|
|
146
|
+
.map((key) => result.findIndex((e) => promptArrangementKey(e) === key))
|
|
147
|
+
.find((i) => i >= 0);
|
|
148
|
+
const following = defaultKeys
|
|
149
|
+
.slice(position + 1)
|
|
150
|
+
.map((key) => result.findIndex((e) => promptArrangementKey(e) === key))
|
|
151
|
+
.find((i) => i >= 0);
|
|
152
|
+
const at = position < 0
|
|
153
|
+
? result.length
|
|
154
|
+
: previous !== undefined
|
|
155
|
+
? previous + 1
|
|
156
|
+
: (following ?? result.length);
|
|
157
|
+
result.splice(at, 0, entry);
|
|
158
|
+
}
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
export function customizePromptArrangement(input, catalog, worldRequired = true) {
|
|
162
|
+
const entries = expandRetiredPrompts(input);
|
|
163
|
+
const arrangement = {};
|
|
164
|
+
const custom = entries.filter((e) => e.kind !== "builtin" &&
|
|
165
|
+
!(e.kind === "world" && e.id === "world" && e.messageRole === undefined));
|
|
166
|
+
if (custom.length)
|
|
167
|
+
arrangement.entries = structuredClone(custom);
|
|
168
|
+
const overrides = [];
|
|
169
|
+
for (const entry of entries) {
|
|
170
|
+
if (entry.kind !== "builtin")
|
|
171
|
+
continue;
|
|
172
|
+
const item = catalog.find((c) => c.id === entry.builtin);
|
|
173
|
+
if (!item)
|
|
174
|
+
throw new Error(`Unknown builtin: ${entry.builtin}`);
|
|
175
|
+
const override = {
|
|
176
|
+
builtin: entry.builtin,
|
|
177
|
+
};
|
|
178
|
+
if (entry.id !== entry.builtin)
|
|
179
|
+
override.id = entry.id;
|
|
180
|
+
if (item.group === undefined &&
|
|
181
|
+
(!entry.enabled || entry.enabled !== defaultEnabledBuiltin(item, catalog)))
|
|
182
|
+
override.enabled = entry.enabled;
|
|
183
|
+
if (entry.messageRole !== undefined)
|
|
184
|
+
override.messageRole = entry.messageRole;
|
|
185
|
+
if (entry.turnGuide !== undefined)
|
|
186
|
+
override.turnGuide = entry.turnGuide;
|
|
187
|
+
if (Object.keys(override).length > 1)
|
|
188
|
+
overrides.push(override);
|
|
189
|
+
if (item.group !== undefined) {
|
|
190
|
+
arrangement.groups ??= {};
|
|
191
|
+
arrangement.groups[item.group] ??= [];
|
|
192
|
+
if (entry.enabled)
|
|
193
|
+
arrangement.groups[item.group].push(item.id);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (overrides.length)
|
|
197
|
+
arrangement.overrides = overrides;
|
|
198
|
+
const keys = entries.map(promptArrangementKey);
|
|
199
|
+
const effective = resolvePromptArrangement(arrangement, catalog, worldRequired);
|
|
200
|
+
const existing = new Set(keys);
|
|
201
|
+
if (JSON.stringify(keys) !==
|
|
202
|
+
JSON.stringify(effective.map(promptArrangementKey).filter((key) => existing.has(key))))
|
|
203
|
+
arrangement.order = keys;
|
|
204
|
+
return arrangement;
|
|
205
|
+
}
|