narraeon 0.3.1 → 0.5.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/appPreferences.js +25 -0
- package/dist/node/protocol/conversationObservation.js +67 -0
- package/dist/node/protocol/v1.js +188 -34
- package/dist/node/protocol/worldExtensions.js +16 -0
- package/dist/node/protocol/worldMaintenance.js +77 -0
- package/dist/node/protocol/worldPromptDiagnostics.js +1 -0
- package/dist/node/runtime/ConversationChanges.js +24 -0
- package/dist/node/runtime/V1Runtime.js +447 -121
- package/dist/node/runtime/artifact/FileNativeArtifactStore.js +326 -12
- package/dist/node/runtime/authoring/AuthoringRequestSnapshot.js +63 -0
- package/dist/node/runtime/authoring/ModelToolConversation.js +218 -0
- package/dist/node/runtime/config/AppPreferencesStore.js +46 -17
- package/dist/node/runtime/content/ContentTreeFingerprint.js +13 -0
- package/dist/node/runtime/content/ContentWorkspace.js +43 -13
- package/dist/node/runtime/content/CurrentTreeContentLibrary.js +128 -46
- package/dist/node/runtime/content/FileNativeContentTree.js +28 -10
- package/dist/node/runtime/content/PackageFollowups.js +80 -0
- package/dist/node/runtime/extension/FrontendExtensionBundle.js +19 -14
- package/dist/node/runtime/extension/PackageScriptPermissions.js +70 -0
- package/dist/node/runtime/extension/WorldExtensionControls.js +205 -0
- package/dist/node/runtime/extension/WorldExtensionRequests.js +64 -0
- package/dist/node/runtime/model/FileNativeModelAdapters.js +21 -466
- package/dist/node/runtime/model/ModelConnectionStore.js +44 -10
- package/dist/node/runtime/model/ModelHost.js +31 -1
- package/dist/node/runtime/model/ModelHostStream.js +51 -5
- package/dist/node/runtime/play/FileNativeContinuityCorrection.js +55 -45
- package/dist/node/runtime/play/FileNativePlayPresetStore.js +256 -238
- package/dist/node/runtime/play/FileNativePlayTimelineStore.js +118 -9
- package/dist/node/runtime/play/OrderedFollowups.js +71 -0
- package/dist/node/runtime/play/OrderedPlayPrompts.js +180 -0
- package/dist/node/runtime/play/PlayCallChain.js +814 -61
- package/dist/node/runtime/play/PlayContinuity.js +88 -0
- package/dist/node/runtime/play/PlayDocumentTools.js +205 -287
- package/dist/node/runtime/play/PlayFollowupRequests.js +34 -3
- package/dist/node/runtime/play/PlayPresetWorkbench.js +45 -54
- package/dist/node/runtime/play/PlayPromptRun.js +86 -0
- package/dist/node/runtime/prompt/FileNativePromptCompiler.js +670 -93
- package/dist/node/runtime/prompt/FileNativeToolRegistry.js +37 -21
- package/dist/node/runtime/prompt/PromptCompilationCodec.js +322 -0
- package/dist/node/runtime/prompt/WorldMaintenanceReport.js +251 -0
- package/dist/node/runtime/prompt/WorldMaterialCoverage.js +83 -0
- package/dist/node/runtime/prompt/WorldPromptDiagnostics.js +185 -0
- package/dist/node/runtime/setting/FileNativeSettingImprovementStore.js +1027 -0
- package/dist/node/runtime/setting/SettingAuthoringTransaction.js +1339 -0
- package/dist/node/runtime/setting/SettingImprovementSession.js +714 -0
- package/dist/node/runtime/world/FileNativeAuthorityV3.js +88 -1
- package/dist/node/runtime/world/FileNativeWorldStore.js +292 -27
- package/dist/node/runtime/world/PlayerViewBindings.js +30 -0
- package/dist/node/runtime/world/PlayerViewRenderer.js +27 -34
- package/dist/node/runtime/world/WorldDocumentMaintenance.js +174 -0
- package/dist/node/runtime/world/WorldDocumentStore.js +166 -96
- package/dist/node/runtime/world/WorldOperationCoordinator.js +103 -2
- package/dist/node/runtime/world-revision/FileNativeWorldRevisionStore.js +696 -0
- package/dist/node/runtime/world-revision/WorldRevisionAuthoringTransaction.js +380 -0
- package/dist/node/runtime/world-revision/WorldRevisionSession.js +709 -0
- package/dist/node/runtime/world-revision/WorldRevisionWorkspace.js +494 -0
- package/dist/node/server/ConversationStream.js +109 -0
- package/dist/node/server/createServer.js +5 -1
- package/dist/node/shared/artifact-payload-contract.js +123 -0
- package/dist/node/shared/default-play-prompts.js +25 -45
- package/dist/node/shared/default-preset-host.js +114 -138
- package/dist/node/shared/default-setting-improvement-prompt.js +52 -38
- package/dist/node/shared/display-regex.js +114 -0
- package/dist/node/shared/first-party-action-choices.js +1 -6
- package/dist/node/shared/first-party-player-view.js +1 -6
- package/dist/node/shared/ordered-author-prompts.js +64 -0
- package/dist/node/shared/ordered-followups.js +42 -0
- package/dist/node/shared/ordered-play-prompts.js +73 -0
- package/dist/web/assets/index-BXfelCi9.js +1207 -0
- package/dist/web/assets/index-BYnhUxlk.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/node/runtime/model/SettingAuthorStream.js +0 -21
- package/dist/node/runtime/setting/DocumentCandidateSettingImprovement.js +0 -1731
- package/dist/web/assets/index-D5VqDvjY.js +0 -1192
- package/dist/web/assets/index-Xa0s35CJ.css +0 -1
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import { isPackageFollowupControlPath } from "../content/PackageFollowups.js";
|
|
2
|
+
import { defaultAppLocale } from "../../protocol/appPreferences.js";
|
|
3
|
+
import { inspectContentPackageCurrentTree } from "../content/FileNativeContentTree.js";
|
|
4
|
+
import { SettingAuthoringTransaction, settingImprovementToolDefinitions, } from "../setting/SettingAuthoringTransaction.js";
|
|
5
|
+
import { WorldDocumentStore } from "../world/WorldDocumentStore.js";
|
|
6
|
+
const syntheticOpening = {
|
|
7
|
+
path: "opening.md",
|
|
8
|
+
contents: "# Existing world\n\nThe genesis opening is immutable in a world revision.\n",
|
|
9
|
+
};
|
|
10
|
+
const settingToRevisionTool = new Map([
|
|
11
|
+
["setting_list", "world_revision_list"],
|
|
12
|
+
["setting_search", "world_revision_search"],
|
|
13
|
+
["setting_read", "world_revision_read"],
|
|
14
|
+
["setting_create", "world_revision_create"],
|
|
15
|
+
["setting_write_file", "world_revision_write_file"],
|
|
16
|
+
["setting_patch", "world_revision_patch"],
|
|
17
|
+
["setting_move", "world_revision_move"],
|
|
18
|
+
["setting_delete", "world_revision_delete"],
|
|
19
|
+
]);
|
|
20
|
+
const revisionToSettingTool = new Map([...settingToRevisionTool].map(([setting, revision]) => [revision, setting]));
|
|
21
|
+
/**
|
|
22
|
+
* Adapter that reuses the content-package authoring mechanics while exposing
|
|
23
|
+
* the current world's state/control worktree as its own logical target.
|
|
24
|
+
*/
|
|
25
|
+
export class WorldRevisionAuthoringTransaction {
|
|
26
|
+
#inner;
|
|
27
|
+
#locale;
|
|
28
|
+
constructor(input) {
|
|
29
|
+
this.#locale = input.locale ?? defaultAppLocale;
|
|
30
|
+
this.#inner = new SettingAuthoringTransaction({
|
|
31
|
+
baseFiles: toProjectedFiles(input.baseFiles),
|
|
32
|
+
locale: this.#locale,
|
|
33
|
+
...(input.revision === undefined ? {} : { revision: input.revision }),
|
|
34
|
+
...(input.authorization === undefined
|
|
35
|
+
? {}
|
|
36
|
+
: { authorization: input.authorization }),
|
|
37
|
+
validateFiles: (files) => {
|
|
38
|
+
const candidate = fromProjectedFiles(files);
|
|
39
|
+
assertUsableWorldRevisionFiles(candidate, input.immutableBaseFiles);
|
|
40
|
+
},
|
|
41
|
+
preview: (snapshot) => input.preview(WorldDocumentStore.open({
|
|
42
|
+
layout: "world_state",
|
|
43
|
+
files: fromProjectedFiles(snapshot.files),
|
|
44
|
+
})),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
files() {
|
|
48
|
+
return fromProjectedFiles(this.#inner.files());
|
|
49
|
+
}
|
|
50
|
+
review() {
|
|
51
|
+
return mapReview(this.#inner.review());
|
|
52
|
+
}
|
|
53
|
+
authorization(revision) {
|
|
54
|
+
return this.#inner.authorization(revision);
|
|
55
|
+
}
|
|
56
|
+
execute(calls) {
|
|
57
|
+
const mapped = calls.map(mapToolCall);
|
|
58
|
+
const executed = this.#inner.execute(mapped.flatMap((item) => ("call" in item ? [item.call] : [])));
|
|
59
|
+
let executedIndex = 0;
|
|
60
|
+
return mapped.map((item, index) => {
|
|
61
|
+
if ("error" in item)
|
|
62
|
+
return rejectedToolResult(calls[index].id, item.error, this.#locale);
|
|
63
|
+
const result = executed[executedIndex++];
|
|
64
|
+
if (result === undefined)
|
|
65
|
+
throw new Error("World-revision authoring returned no tool result");
|
|
66
|
+
return {
|
|
67
|
+
...result,
|
|
68
|
+
markdown: publicWorldRevisionMarkdown(result.markdown),
|
|
69
|
+
changes: result.changes.map(mapDiff),
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export function worldRevisionToolDefinitions(locale = defaultAppLocale) {
|
|
75
|
+
return settingImprovementToolDefinitions(locale).map((definition) => {
|
|
76
|
+
const name = settingToRevisionTool.get(definition.name);
|
|
77
|
+
if (name === undefined)
|
|
78
|
+
throw new Error(`Unsupported setting authoring tool: ${definition.name}`);
|
|
79
|
+
return {
|
|
80
|
+
...structuredClone(definition),
|
|
81
|
+
name,
|
|
82
|
+
description: publicWorldRevisionMarkdown(definition.description),
|
|
83
|
+
inputSchema: mapSchema(definition.inputSchema),
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
export function worldRevisionRuntimeContract(locale) {
|
|
88
|
+
return locale === "zh-CN"
|
|
89
|
+
? `# Runtime 世界修订对话契约
|
|
90
|
+
|
|
91
|
+
- 你正在修改一个已经开始游玩的世界,而不是创建世界所用的内容包。修订期间 Runtime 持有持久独占锁,游玩和其他世界写入均被禁止。
|
|
92
|
+
- state/* 与 control/* 共同组成唯一的修订工作树;手动编辑和 AI 工具写入落到同一棵树,也进入同一份可回滚改动历史。opening.md 已经作为创世叙事提交,不能读取或修改。
|
|
93
|
+
- 工具调用修改的是尚未应用的工作树。只有玩家点击“应用修订”才会把当前整棵树提交为新的世界状态和控制并解除锁;“放弃修订”会丢弃工作树并解除锁。你不得宣称自己已经应用或放弃修订。
|
|
94
|
+
- 每个完整工具响应按调用顺序结算。成功调用留下逐文件 before-image,可以由玩家单独回滚;失败调用不会撤销同一响应内其他成功调用。
|
|
95
|
+
- 修改既有文件前必须完整读取。list/search cursor 和 read 授权只属于产生它们的工作树 revision;工作树发生任何手动、AI 或回滚写入后,继续修改前必须重新读取。
|
|
96
|
+
- state/* 只接受 .yaml 或 .md 世界文档;control/* 接受 frame.yaml、player-views.yaml、blocks/*.md,以及 followups.yaml 与后置请求引用的 prompts/*.md、renderers/*.html、scripts/*.js、regex/*.yaml、assets/* 文本资源。可以创建新状态文档;既有状态文档不能删除或移动,因为 Authority 保留其稳定身份。
|
|
97
|
+
- 对话跨越“应用”或“放弃”继续时,Runtime 会明确宣布一个新修订 epoch。此前读取授权全部失效;先重新读取新世界,再继续修改。`
|
|
98
|
+
: `# Runtime world-revision conversation contract
|
|
99
|
+
|
|
100
|
+
- You are revising a world that is already in play, not its source content package. Runtime holds a durable exclusive lock during the revision, blocking play and every other world write.
|
|
101
|
+
- state/* and control/* form one revision worktree. Manual edits and AI tool writes share that tree and one rollback history. opening.md was already committed as genesis narrative and cannot be read or changed.
|
|
102
|
+
- Tools change the unapplied worktree. Only the player can Apply the complete tree to world state/control and unlock it, or Discard the tree and unlock it. Never claim that you applied or discarded the revision yourself.
|
|
103
|
+
- Calls in each complete tool response settle in order. Every successful call retains per-file before-images for selective rollback; a failed sibling does not undo successful calls.
|
|
104
|
+
- Completely read an existing file before changing it. List/search cursors and read authorization belong only to the worktree revision that produced them. Re-read after any manual, AI, or rollback write.
|
|
105
|
+
- state/* accepts only YAML or Markdown world documents. control/* accepts frame.yaml, player-views.yaml, blocks/*.md, plus followups.yaml and its referenced prompts/*.md, renderers/*.html, scripts/*.js, regex/*.yaml and assets/* text resources. New state documents are allowed; existing state documents cannot be deleted or moved because Authority retains their stable identity.
|
|
106
|
+
- When a conversation continues after Apply or Discard, Runtime announces a new revision epoch and invalidates every prior read authorization. Re-read the new world before editing.`;
|
|
107
|
+
}
|
|
108
|
+
export function inspectWorldRevisionFiles(files, immutableBaseFiles) {
|
|
109
|
+
const diagnostics = [];
|
|
110
|
+
const paths = new Set();
|
|
111
|
+
for (const file of files) {
|
|
112
|
+
if (paths.has(file.path))
|
|
113
|
+
diagnostics.push({
|
|
114
|
+
code: "logical_path_duplicate",
|
|
115
|
+
path: file.path,
|
|
116
|
+
message: "World revision contains a duplicate logical path",
|
|
117
|
+
});
|
|
118
|
+
paths.add(file.path);
|
|
119
|
+
if (file.encoding !== undefined)
|
|
120
|
+
diagnostics.push({
|
|
121
|
+
code: "binary_not_supported",
|
|
122
|
+
path: file.path,
|
|
123
|
+
message: "World revision accepts text files only",
|
|
124
|
+
});
|
|
125
|
+
if (!isWritableWorldRevisionPath(file.path))
|
|
126
|
+
diagnostics.push({
|
|
127
|
+
code: "path_not_writable",
|
|
128
|
+
path: file.path,
|
|
129
|
+
message: "Only state/* and supported control/* files are writable",
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
for (const base of immutableBaseFiles.filter(({ path }) => path.startsWith("state/"))) {
|
|
133
|
+
if (!paths.has(base.path))
|
|
134
|
+
diagnostics.push({
|
|
135
|
+
code: "existing_state_delete_forbidden",
|
|
136
|
+
path: base.path,
|
|
137
|
+
message: "An existing world-state document cannot be deleted",
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
const baseSnapshot = WorldDocumentStore.open({
|
|
141
|
+
layout: "world_state",
|
|
142
|
+
files: immutableBaseFiles.filter(({ path }) => path.startsWith("state/")),
|
|
143
|
+
});
|
|
144
|
+
const candidateSnapshot = WorldDocumentStore.open({
|
|
145
|
+
layout: "world_state",
|
|
146
|
+
files: files.filter(({ path }) => path.startsWith("state/")),
|
|
147
|
+
});
|
|
148
|
+
for (const base of immutableBaseFiles.filter(({ path }) => path.startsWith("state/"))) {
|
|
149
|
+
const before = baseSnapshot.query({
|
|
150
|
+
kind: "read_document",
|
|
151
|
+
document: { logicalPath: base.path },
|
|
152
|
+
});
|
|
153
|
+
const after = candidateSnapshot.query({
|
|
154
|
+
kind: "read_document",
|
|
155
|
+
document: { logicalPath: base.path },
|
|
156
|
+
});
|
|
157
|
+
if (before.kind === "read_document" &&
|
|
158
|
+
before.ok &&
|
|
159
|
+
after.kind === "read_document" &&
|
|
160
|
+
after.ok &&
|
|
161
|
+
(before.document.documentId !== after.document.documentId ||
|
|
162
|
+
before.document.shortRef !== after.document.shortRef))
|
|
163
|
+
diagnostics.push({
|
|
164
|
+
code: "existing_state_identity_changed",
|
|
165
|
+
path: base.path,
|
|
166
|
+
message: "An existing world-state document must keep its document ID and short reference",
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
const projected = files.map((file) => ({
|
|
170
|
+
...structuredClone(file),
|
|
171
|
+
path: file.path.startsWith("state/")
|
|
172
|
+
? `world/${file.path.slice("state/".length)}`
|
|
173
|
+
: file.path,
|
|
174
|
+
}));
|
|
175
|
+
const inspection = inspectContentPackageCurrentTree(projected, {
|
|
176
|
+
requireOpening: false,
|
|
177
|
+
});
|
|
178
|
+
if (inspection.status !== "usable")
|
|
179
|
+
diagnostics.push(...inspection.issues.map(({ code, path, message }) => ({
|
|
180
|
+
code,
|
|
181
|
+
path: path.startsWith("world/")
|
|
182
|
+
? `state/${path.slice("world/".length)}`
|
|
183
|
+
: path,
|
|
184
|
+
message,
|
|
185
|
+
})));
|
|
186
|
+
return {
|
|
187
|
+
status: diagnostics.length === 0 ? "usable" : "needs_repair",
|
|
188
|
+
diagnostics,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
export function assertUsableWorldRevisionFiles(files, immutableBaseFiles) {
|
|
192
|
+
const inspection = inspectWorldRevisionFiles(files, immutableBaseFiles);
|
|
193
|
+
if (inspection.status !== "usable")
|
|
194
|
+
throw new Error(inspection.diagnostics
|
|
195
|
+
.map(({ path, message }) => `${path}: ${message}`)
|
|
196
|
+
.join("; "));
|
|
197
|
+
}
|
|
198
|
+
function toProjectedFiles(files) {
|
|
199
|
+
return [
|
|
200
|
+
syntheticOpening,
|
|
201
|
+
...files.map((file) => ({
|
|
202
|
+
...structuredClone(file),
|
|
203
|
+
path: file.path.startsWith("state/")
|
|
204
|
+
? `world/${file.path.slice("state/".length)}`
|
|
205
|
+
: file.path,
|
|
206
|
+
})),
|
|
207
|
+
];
|
|
208
|
+
}
|
|
209
|
+
function fromProjectedFiles(files) {
|
|
210
|
+
const opening = files.find(({ path }) => path === "opening.md");
|
|
211
|
+
if (opening?.contents !== syntheticOpening.contents ||
|
|
212
|
+
opening.encoding !== undefined)
|
|
213
|
+
throw new Error("opening.md is immutable in a world revision");
|
|
214
|
+
return files
|
|
215
|
+
.filter(({ path }) => path !== "opening.md")
|
|
216
|
+
.map((file) => ({
|
|
217
|
+
...structuredClone(file),
|
|
218
|
+
path: file.path.startsWith("world/")
|
|
219
|
+
? `state/${file.path.slice("world/".length)}`
|
|
220
|
+
: file.path,
|
|
221
|
+
}))
|
|
222
|
+
.sort((left, right) => left.path.localeCompare(right.path));
|
|
223
|
+
}
|
|
224
|
+
function mapToolCall(call) {
|
|
225
|
+
const name = revisionToSettingTool.get(call.name);
|
|
226
|
+
if (name === undefined)
|
|
227
|
+
return {
|
|
228
|
+
call: { ...structuredClone(call), name: `unsupported:${call.name}` },
|
|
229
|
+
};
|
|
230
|
+
const args = isRecord(call.arguments)
|
|
231
|
+
? structuredClone(call.arguments)
|
|
232
|
+
: call.arguments;
|
|
233
|
+
if (isRecord(args)) {
|
|
234
|
+
for (const field of [
|
|
235
|
+
"path",
|
|
236
|
+
"directory",
|
|
237
|
+
"within",
|
|
238
|
+
"from",
|
|
239
|
+
"to",
|
|
240
|
+
"document",
|
|
241
|
+
])
|
|
242
|
+
if (typeof args[field] === "string") {
|
|
243
|
+
if (args[field] === "opening.md")
|
|
244
|
+
return { error: "opening" };
|
|
245
|
+
if (args[field] === "world" || args[field].startsWith("world/"))
|
|
246
|
+
return { error: "namespace" };
|
|
247
|
+
args[field] = toProjectedPath(args[field]);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
call: { ...structuredClone(call), name, arguments: args },
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
function toProjectedPath(path) {
|
|
255
|
+
if (path === "state")
|
|
256
|
+
return "world";
|
|
257
|
+
return path.startsWith("state/")
|
|
258
|
+
? `world/${path.slice("state/".length)}`
|
|
259
|
+
: path;
|
|
260
|
+
}
|
|
261
|
+
function mapDiff(diff) {
|
|
262
|
+
return {
|
|
263
|
+
...structuredClone(diff),
|
|
264
|
+
path: diff.path.startsWith("world/")
|
|
265
|
+
? `state/${diff.path.slice("world/".length)}`
|
|
266
|
+
: diff.path,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function mapReview(review) {
|
|
270
|
+
return {
|
|
271
|
+
...structuredClone(review),
|
|
272
|
+
diff: review.diff.filter(({ path }) => path !== "opening.md").map(mapDiff),
|
|
273
|
+
diagnostics: review.diagnostics.map((diagnostic) => ({
|
|
274
|
+
...diagnostic,
|
|
275
|
+
path: diagnostic.path.startsWith("world/")
|
|
276
|
+
? `state/${diagnostic.path.slice("world/".length)}`
|
|
277
|
+
: diagnostic.path,
|
|
278
|
+
})),
|
|
279
|
+
playCoverage: review.playCoverage === null
|
|
280
|
+
? null
|
|
281
|
+
: {
|
|
282
|
+
...structuredClone(review.playCoverage),
|
|
283
|
+
changed: review.playCoverage.changed
|
|
284
|
+
.filter(({ path }) => path !== "opening.md")
|
|
285
|
+
.map((item) => ({ ...item, path: mapPublicPath(item.path) })),
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function mapPublicPath(path) {
|
|
290
|
+
return path.startsWith("world/")
|
|
291
|
+
? `state/${path.slice("world/".length)}`
|
|
292
|
+
: path;
|
|
293
|
+
}
|
|
294
|
+
function publicWorldRevisionMarkdown(markdown) {
|
|
295
|
+
let fenced = false;
|
|
296
|
+
let documentBody = false;
|
|
297
|
+
let opaqueBody = false;
|
|
298
|
+
return markdown
|
|
299
|
+
.split("\n")
|
|
300
|
+
.flatMap((line) => {
|
|
301
|
+
if (line.trim() === "[body]") {
|
|
302
|
+
documentBody = true;
|
|
303
|
+
return [line];
|
|
304
|
+
}
|
|
305
|
+
if (line.trim() === "[/body]") {
|
|
306
|
+
documentBody = false;
|
|
307
|
+
return [line];
|
|
308
|
+
}
|
|
309
|
+
if (line.trimStart().startsWith("```")) {
|
|
310
|
+
fenced = !fenced;
|
|
311
|
+
return [line];
|
|
312
|
+
}
|
|
313
|
+
if (line.startsWith("# Special-file source ") ||
|
|
314
|
+
line.startsWith("# 专用文件原文 "))
|
|
315
|
+
opaqueBody = true;
|
|
316
|
+
else if (opaqueBody && line === "---")
|
|
317
|
+
opaqueBody = false;
|
|
318
|
+
if (fenced ||
|
|
319
|
+
documentBody ||
|
|
320
|
+
(opaqueBody &&
|
|
321
|
+
!line.startsWith("# Special-file source ") &&
|
|
322
|
+
!line.startsWith("# 专用文件原文 ")) ||
|
|
323
|
+
/^\s+"/u.test(line))
|
|
324
|
+
return [line];
|
|
325
|
+
const mapped = line
|
|
326
|
+
.replaceAll("setting_", "world_revision_")
|
|
327
|
+
.replaceAll("content package", "world revision")
|
|
328
|
+
.replaceAll("Content package", "World revision")
|
|
329
|
+
.replaceAll("内容包当前树", "世界修订工作树")
|
|
330
|
+
.replaceAll("内容包", "世界修订")
|
|
331
|
+
.replaceAll("opening and control", "control")
|
|
332
|
+
.replaceAll("opening/control", "control")
|
|
333
|
+
.replaceAll("opening.md, ", "")
|
|
334
|
+
.replaceAll("opening 和 control", "control")
|
|
335
|
+
.replaceAll("opening.md、", "")
|
|
336
|
+
.replaceAll("world/", "state/");
|
|
337
|
+
return /^- \[(?:special|binary)\] opening\.md$/u.test(mapped.trim())
|
|
338
|
+
? []
|
|
339
|
+
: [mapped];
|
|
340
|
+
})
|
|
341
|
+
.join("\n");
|
|
342
|
+
}
|
|
343
|
+
function rejectedToolResult(toolCallId, error, locale) {
|
|
344
|
+
const message = error === "opening"
|
|
345
|
+
? locale === "zh-CN"
|
|
346
|
+
? "世界创建后 opening.md 已成为不可变创世叙事。请改写 state/* 或 control/*。"
|
|
347
|
+
: "opening.md is immutable after a world has been created. Edit state/* or control/* instead."
|
|
348
|
+
: locale === "zh-CN"
|
|
349
|
+
? "世界修订只公开 state/* 与 control/* 逻辑路径;不能使用内容包的 world/* 命名空间。"
|
|
350
|
+
: "World revision exposes only state/* and control/* logical paths; the content-package world/* namespace is unavailable.";
|
|
351
|
+
return {
|
|
352
|
+
toolCallId,
|
|
353
|
+
markdown: `# Runtime tool rejected\n\n${message}`,
|
|
354
|
+
isError: true,
|
|
355
|
+
changes: [],
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
function mapSchema(schema) {
|
|
359
|
+
const map = (value) => {
|
|
360
|
+
if (typeof value === "string")
|
|
361
|
+
return value.replaceAll("world", "state");
|
|
362
|
+
if (Array.isArray(value))
|
|
363
|
+
return value.map(map);
|
|
364
|
+
if (!isRecord(value))
|
|
365
|
+
return value;
|
|
366
|
+
return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, map(child)]));
|
|
367
|
+
};
|
|
368
|
+
return map(schema);
|
|
369
|
+
}
|
|
370
|
+
function isWritableWorldRevisionPath(path) {
|
|
371
|
+
if (/^state\/.+\.(?:ya?ml|md)$/u.test(path))
|
|
372
|
+
return true;
|
|
373
|
+
return (isPackageFollowupControlPath(path) ||
|
|
374
|
+
path === "control/frame.yaml" ||
|
|
375
|
+
path === "control/player-views.yaml" ||
|
|
376
|
+
/^control\/blocks\/.+\.md$/u.test(path));
|
|
377
|
+
}
|
|
378
|
+
function isRecord(value) {
|
|
379
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
380
|
+
}
|