narraeon 0.1.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/LICENSE +201 -0
- package/README.md +164 -0
- package/README.zh-CN.md +164 -0
- package/dist/node/cli/NarraeonCli.js +163 -0
- package/dist/node/cli/main.js +38 -0
- package/dist/node/protocol/appPreferences.js +5 -0
- package/dist/node/protocol/contentTree.js +14 -0
- package/dist/node/protocol/modelConnections.js +110 -0
- package/dist/node/protocol/playerViews.js +1 -0
- package/dist/node/protocol/v1.js +278 -0
- package/dist/node/runtime/V1Runtime.js +625 -0
- package/dist/node/runtime/artifact/FileNativeArtifactStore.js +1914 -0
- package/dist/node/runtime/config/AppPreferencesStore.js +92 -0
- package/dist/node/runtime/config/appPaths.js +19 -0
- package/dist/node/runtime/content/ContentLimits.js +1 -0
- package/dist/node/runtime/content/ContentTreeFile.js +1 -0
- package/dist/node/runtime/content/ContentWorkspace.js +601 -0
- package/dist/node/runtime/content/CurrentTreeContentLibrary.js +721 -0
- package/dist/node/runtime/content/FileNativeContentTree.js +512 -0
- package/dist/node/runtime/extension/FrontendExtensionBundle.js +229 -0
- package/dist/node/runtime/extension/PlayerViewPanelProjector.js +121 -0
- package/dist/node/runtime/model/AiFailureLog.js +332 -0
- package/dist/node/runtime/model/FileNativeModelAdapters.js +1246 -0
- package/dist/node/runtime/model/ModelConnectionStore.js +378 -0
- package/dist/node/runtime/model/ModelHost.js +89 -0
- package/dist/node/runtime/model/ModelHostStream.js +367 -0
- package/dist/node/runtime/model/ProviderExchangeState.js +1 -0
- package/dist/node/runtime/model/ProviderStream.js +90 -0
- package/dist/node/runtime/model/SettingAuthorStream.js +118 -0
- package/dist/node/runtime/play/FileNativeContinuityCorrection.js +304 -0
- package/dist/node/runtime/play/FileNativePlayPresetStore.js +1856 -0
- package/dist/node/runtime/play/PlayCallChain.js +1280 -0
- package/dist/node/runtime/play/PlayDocumentTools.js +1121 -0
- package/dist/node/runtime/play/PlayFollowupRequests.js +183 -0
- package/dist/node/runtime/play/PlayPresetWorkbench.js +185 -0
- package/dist/node/runtime/prompt/FileNativePromptCompiler.js +1425 -0
- package/dist/node/runtime/prompt/FileNativeToolRegistry.js +192 -0
- package/dist/node/runtime/prompt/MaterialSelection.js +95 -0
- package/dist/node/runtime/setting/DocumentCandidateSettingImprovement.js +1726 -0
- package/dist/node/runtime/world/FileNativeWorldStore.js +2028 -0
- package/dist/node/runtime/world/PlayerViewRenderer.js +322 -0
- package/dist/node/runtime/world/WorldDocumentStore.js +2808 -0
- package/dist/node/runtime/world/WorldOperationCoordinator.js +490 -0
- package/dist/node/runtime/world/WorldStateRevision.js +75 -0
- package/dist/node/server/createRuntime.js +6 -0
- package/dist/node/server/createServer.js +218 -0
- package/dist/node/server/main.js +25 -0
- package/dist/node/server/startWebServer.js +28 -0
- package/dist/node/shared/default-play-prompts.js +85 -0
- package/dist/node/shared/default-preset-host.js +538 -0
- package/dist/node/shared/default-setting-improvement-prompt.js +77 -0
- package/dist/node/shared/first-party-action-choices.js +164 -0
- package/dist/node/shared/first-party-generic-panels.js +105 -0
- package/dist/node/shared/first-party-play-preset-templates.js +41 -0
- package/dist/node/shared/first-party-player-view.js +179 -0
- package/dist/node/shared/play-preset-prompt-roles.js +4 -0
- package/dist/web/assets/index-CRGTZ9Ak.css +1 -0
- package/dist/web/assets/index-DDx1itzT.js +1192 -0
- package/dist/web/index.html +14 -0
- package/package.json +86 -0
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { maxPortableContentArchiveBase64Characters, maxPortableContentArchiveBytes, } from "../protocol/contentTree.js";
|
|
3
|
+
import { V1ProtocolError, v1Protocol, } from "../protocol/v1.js";
|
|
4
|
+
import { defaultAppLocale, } from "../protocol/appPreferences.js";
|
|
5
|
+
import { ContentPackageImportError, ContentWorkspace, InvalidContentTreeError, } from "./content/ContentWorkspace.js";
|
|
6
|
+
import { inspectContentPackageCurrentTree } from "./content/FileNativeContentTree.js";
|
|
7
|
+
import { FileNativeModelHost, FileNativeSettingAuthorProvider, } from "./model/FileNativeModelAdapters.js";
|
|
8
|
+
import {} from "./model/ModelHost.js";
|
|
9
|
+
import { ModelConnectionStore } from "./model/ModelConnectionStore.js";
|
|
10
|
+
import { FileNativeContinuityCorrection } from "./play/FileNativeContinuityCorrection.js";
|
|
11
|
+
import { PlayCallChain, PlayCallChainError, } from "./play/PlayCallChain.js";
|
|
12
|
+
import { FileNativePlayPresetError, FileNativePlayPresetStore, presetHostBinding, settingImprovementPromptForBinding, } from "./play/FileNativePlayPresetStore.js";
|
|
13
|
+
import { buildPlayPresetWorkbenchSnapshot } from "./play/PlayPresetWorkbench.js";
|
|
14
|
+
import { FileNativeArtifactStore } from "./artifact/FileNativeArtifactStore.js";
|
|
15
|
+
import { projectArtifactForFrontend, projectDebugArtifactForFrontend, } from "./extension/FrontendExtensionBundle.js";
|
|
16
|
+
import { projectPlayerViewPanels, } from "./extension/PlayerViewPanelProjector.js";
|
|
17
|
+
import { FileNativePromptCompiler } from "./prompt/FileNativePromptCompiler.js";
|
|
18
|
+
import { DocumentCandidateSettingImprovement } from "./setting/DocumentCandidateSettingImprovement.js";
|
|
19
|
+
import { FileNativeWorldStore } from "./world/FileNativeWorldStore.js";
|
|
20
|
+
import { WorldDocumentStore } from "./world/WorldDocumentStore.js";
|
|
21
|
+
import { FileNativeAiFailureLog } from "./model/AiFailureLog.js";
|
|
22
|
+
import { AppPreferencesStore } from "./config/AppPreferencesStore.js";
|
|
23
|
+
export class V1Runtime {
|
|
24
|
+
#content;
|
|
25
|
+
#playPresets;
|
|
26
|
+
#worlds;
|
|
27
|
+
#artifacts;
|
|
28
|
+
#models;
|
|
29
|
+
#corrections;
|
|
30
|
+
#playCallChains;
|
|
31
|
+
#failureLog;
|
|
32
|
+
#preferences;
|
|
33
|
+
#settingImprovements = new Map();
|
|
34
|
+
#compiler = new FileNativePromptCompiler();
|
|
35
|
+
#locale = defaultAppLocale;
|
|
36
|
+
constructor(input) {
|
|
37
|
+
this.#preferences = new AppPreferencesStore(input.configRoot);
|
|
38
|
+
this.#content = new ContentWorkspace(input.dataRoot, {
|
|
39
|
+
locale: () => this.#locale,
|
|
40
|
+
});
|
|
41
|
+
this.#playPresets = new FileNativePlayPresetStore(input.configRoot, {
|
|
42
|
+
locale: () => this.#locale,
|
|
43
|
+
});
|
|
44
|
+
this.#worlds = new FileNativeWorldStore(input.dataRoot);
|
|
45
|
+
this.#artifacts = new FileNativeArtifactStore(input.dataRoot);
|
|
46
|
+
this.#failureLog =
|
|
47
|
+
input.logRoot === undefined
|
|
48
|
+
? undefined
|
|
49
|
+
: new FileNativeAiFailureLog(join(input.logRoot, "ai-failures"));
|
|
50
|
+
this.#playCallChains = new PlayCallChain(this.#worlds, this.#compiler, this.#artifacts, this.#failureLog);
|
|
51
|
+
this.#models = new ModelConnectionStore(input.configRoot);
|
|
52
|
+
this.#corrections = new FileNativeContinuityCorrection(this.#worlds);
|
|
53
|
+
}
|
|
54
|
+
async initialize() {
|
|
55
|
+
this.#locale = (await this.#preferences.view()).locale;
|
|
56
|
+
this.#compiler.setLocale(this.#locale);
|
|
57
|
+
await this.#playPresets.initialize();
|
|
58
|
+
}
|
|
59
|
+
async handle(request, playCallChainObserver) {
|
|
60
|
+
const result = await this.#dispatch(request, playCallChainObserver);
|
|
61
|
+
return { protocol: v1Protocol, result };
|
|
62
|
+
}
|
|
63
|
+
async #dispatch(request, playCallChainObserver) {
|
|
64
|
+
switch (request.type) {
|
|
65
|
+
case "workspace.read":
|
|
66
|
+
return {
|
|
67
|
+
preferences: await this.#preferences.view(),
|
|
68
|
+
contentPackages: await this.#content.listCurrentTreeContentPackages(),
|
|
69
|
+
playPresets: await this.#playPresets.list(),
|
|
70
|
+
model: await this.#models.view(),
|
|
71
|
+
worlds: await this.#worlds.listWorlds(),
|
|
72
|
+
storageNotices: this.#storageNotices(),
|
|
73
|
+
};
|
|
74
|
+
case "preferences.read":
|
|
75
|
+
return this.#preferences.view();
|
|
76
|
+
case "preferences.save": {
|
|
77
|
+
const preferences = await this.#preferences.save(request.locale);
|
|
78
|
+
this.#locale = preferences.locale;
|
|
79
|
+
this.#compiler.setLocale(this.#locale);
|
|
80
|
+
await this.#playPresets.syncBuiltinDefaultLocale();
|
|
81
|
+
return preferences;
|
|
82
|
+
}
|
|
83
|
+
case "model.read":
|
|
84
|
+
return this.#models.view();
|
|
85
|
+
case "model.save":
|
|
86
|
+
return this.#models.save(request.connection);
|
|
87
|
+
case "model.select":
|
|
88
|
+
return this.#models.select(request.connectionId);
|
|
89
|
+
case "model.delete":
|
|
90
|
+
return this.#models.delete(request.connectionId);
|
|
91
|
+
case "model.models":
|
|
92
|
+
return this.#models.listModels(request);
|
|
93
|
+
case "content.create":
|
|
94
|
+
return this.#content.createCurrentTreeContentPackage();
|
|
95
|
+
case "content.read":
|
|
96
|
+
return this.#content.readCurrentTreeContentPackage(request.packageId);
|
|
97
|
+
case "content.replace":
|
|
98
|
+
return this.#content.replaceCurrentTreeContentPackage(request.packageId, request.files);
|
|
99
|
+
case "content.copy":
|
|
100
|
+
return this.#content.copyCurrentTreeContentPackage(request.packageId);
|
|
101
|
+
case "content.delete":
|
|
102
|
+
await this.#content.deleteCurrentTreeContentPackage(request.packageId);
|
|
103
|
+
return { deleted: true };
|
|
104
|
+
case "content.rename":
|
|
105
|
+
return this.#content.renameCurrentTreeContentPackage(request.packageId, request.name);
|
|
106
|
+
case "content.import": {
|
|
107
|
+
const archive = decodePortableContentArchive(request.archiveBase64);
|
|
108
|
+
try {
|
|
109
|
+
return await this.#content.importPortableContentPackageArchive(archive);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
if (error instanceof ContentPackageImportError ||
|
|
113
|
+
error instanceof InvalidContentTreeError)
|
|
114
|
+
throw new V1ProtocolError("invalid_request", error.message, {
|
|
115
|
+
cause: error,
|
|
116
|
+
});
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
case "content.export": {
|
|
121
|
+
const exported = await this.#content.exportCurrentTreeContentPackage(request.packageId);
|
|
122
|
+
return {
|
|
123
|
+
fileName: exported.fileName,
|
|
124
|
+
base64: exported.archive.toString("base64"),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
case "setting-improvement.start": {
|
|
128
|
+
if (this.#settingImprovements.has(request.improvementId))
|
|
129
|
+
throw new Error("Setting-improvement ID already exists");
|
|
130
|
+
const [package_, connection, preset] = await Promise.all([
|
|
131
|
+
this.#content.readCurrentTreeContentPackage(request.packageId),
|
|
132
|
+
this.#models.bind(),
|
|
133
|
+
this.#playPresets.bindCurrent(),
|
|
134
|
+
]);
|
|
135
|
+
const improvement = new DocumentCandidateSettingImprovement({
|
|
136
|
+
files: package_.files,
|
|
137
|
+
adapter: new FileNativeSettingAuthorProvider(connection, fetch, {
|
|
138
|
+
...(this.#failureLog === undefined
|
|
139
|
+
? {}
|
|
140
|
+
: { failureLog: this.#failureLog }),
|
|
141
|
+
operationId: request.improvementId,
|
|
142
|
+
locale: this.#locale,
|
|
143
|
+
}),
|
|
144
|
+
authorPrompt: settingImprovementPromptForBinding(preset, this.#locale),
|
|
145
|
+
locale: this.#locale,
|
|
146
|
+
...(this.#failureLog === undefined
|
|
147
|
+
? {}
|
|
148
|
+
: { failureLog: this.#failureLog }),
|
|
149
|
+
preview: (snapshot) => this.#compiler.preview({
|
|
150
|
+
endpoint: { id: "setting-candidate", commit: "candidate" },
|
|
151
|
+
hostBinding: {
|
|
152
|
+
hostPresetId: "setting-candidate",
|
|
153
|
+
files: defaultSettingPreviewHost(),
|
|
154
|
+
},
|
|
155
|
+
world: previewWorld(snapshot, "setting-candidate.message.genesis.narrator", "setting-candidate"),
|
|
156
|
+
playerInputPlacement: "bootstrap",
|
|
157
|
+
playerInput: this.#locale === "zh-CN"
|
|
158
|
+
? "预览设定候选。"
|
|
159
|
+
: "Preview the setting candidate.",
|
|
160
|
+
modelBinding: new FileNativeModelHost(connection).binding(),
|
|
161
|
+
}),
|
|
162
|
+
});
|
|
163
|
+
if (this.#settingImprovements.has(request.improvementId))
|
|
164
|
+
throw new Error("Setting-improvement ID already exists");
|
|
165
|
+
this.#settingImprovements.set(request.improvementId, {
|
|
166
|
+
packageId: request.packageId,
|
|
167
|
+
improvement,
|
|
168
|
+
});
|
|
169
|
+
try {
|
|
170
|
+
return await improvement.start({
|
|
171
|
+
goal: request.goal,
|
|
172
|
+
contextPaths: request.contextPaths,
|
|
173
|
+
mode: request.mode,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
this.#settingImprovements.delete(request.improvementId);
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
case "setting-improvement.confirm":
|
|
182
|
+
return this.#settingImprovement(request.improvementId).improvement.confirmPlan();
|
|
183
|
+
// Answers while start/confirm is still awaiting the provider: those calls
|
|
184
|
+
// yield the event loop, so this read observes the run in flight.
|
|
185
|
+
case "setting-improvement.progress":
|
|
186
|
+
return this.#settingImprovement(request.improvementId).improvement.progress();
|
|
187
|
+
case "setting-improvement.revise-plan":
|
|
188
|
+
return this.#settingImprovement(request.improvementId).improvement.revisePlan(request.feedback);
|
|
189
|
+
case "setting-improvement.revise-candidate":
|
|
190
|
+
return this.#settingImprovement(request.improvementId).improvement.reviseCandidate(request.feedback);
|
|
191
|
+
case "setting-improvement.apply": {
|
|
192
|
+
const active = this.#settingImprovement(request.improvementId);
|
|
193
|
+
await active.improvement.apply(async (files) => {
|
|
194
|
+
await this.#content.replaceCurrentTreeContentPackage(active.packageId, files);
|
|
195
|
+
});
|
|
196
|
+
this.#settingImprovements.delete(request.improvementId);
|
|
197
|
+
return { applied: true };
|
|
198
|
+
}
|
|
199
|
+
case "setting-improvement.discard":
|
|
200
|
+
this.#settingImprovement(request.improvementId).improvement.discard();
|
|
201
|
+
this.#settingImprovements.delete(request.improvementId);
|
|
202
|
+
return { discarded: true };
|
|
203
|
+
case "play.read":
|
|
204
|
+
return this.#playPresets.list();
|
|
205
|
+
case "play.create":
|
|
206
|
+
return this.#playPresets.create(request.name, request.files);
|
|
207
|
+
case "play.copy":
|
|
208
|
+
return this.#playPresets.copy(request.presetId);
|
|
209
|
+
case "play.save":
|
|
210
|
+
return this.#playPresets.save(request);
|
|
211
|
+
case "play.rename":
|
|
212
|
+
return this.#playPresets.rename(request.presetId, request.name);
|
|
213
|
+
case "play.delete":
|
|
214
|
+
return this.#playPresets.delete(request.presetId);
|
|
215
|
+
case "play.enable":
|
|
216
|
+
return this.#playPresets.setEnabled(request.presetId, request.enabled);
|
|
217
|
+
case "play.scripts":
|
|
218
|
+
return this.#playPresets.setScriptsEnabled(request.presetId, request.enabled);
|
|
219
|
+
case "play.select":
|
|
220
|
+
return this.#playPresets.select(request.presetId);
|
|
221
|
+
case "play.export":
|
|
222
|
+
return {
|
|
223
|
+
files: await this.#playPresets.exportPortable(request.presetId),
|
|
224
|
+
};
|
|
225
|
+
case "play.import":
|
|
226
|
+
return this.#playPresets.importPortable(request);
|
|
227
|
+
case "play.workbench.read": {
|
|
228
|
+
const binding = request.presetId === undefined
|
|
229
|
+
? await this.#playPresets.bindCurrent()
|
|
230
|
+
: await this.#playPresets.bindRevision(request.presetId, request.revision);
|
|
231
|
+
return buildPlayPresetWorkbenchSnapshot(binding);
|
|
232
|
+
}
|
|
233
|
+
case "prompt.preview": {
|
|
234
|
+
const [package_, connection, playPreset] = await Promise.all([
|
|
235
|
+
this.#content.readCurrentTreeContentPackage(request.packageId),
|
|
236
|
+
this.#models.bind(),
|
|
237
|
+
request.playPresetId === undefined
|
|
238
|
+
? this.#playPresets.bindCurrent()
|
|
239
|
+
: this.#playPresets.bindRevision(request.playPresetId, request.playPresetRevision),
|
|
240
|
+
]);
|
|
241
|
+
const hostBinding = presetHostBinding(playPreset);
|
|
242
|
+
return this.#compiler.preview({
|
|
243
|
+
endpoint: {
|
|
244
|
+
id: `content:${request.packageId}`,
|
|
245
|
+
commit: "current",
|
|
246
|
+
},
|
|
247
|
+
hostBinding,
|
|
248
|
+
world: previewWorld(WorldDocumentStore.open({
|
|
249
|
+
layout: "content_package",
|
|
250
|
+
files: package_.files,
|
|
251
|
+
}), `content:${request.packageId}.message.genesis.narrator`, "content-preview"),
|
|
252
|
+
playerInputPlacement: "append",
|
|
253
|
+
playerInput: request.playerInput,
|
|
254
|
+
modelBinding: new FileNativeModelHost(connection).binding(),
|
|
255
|
+
}, playPreset);
|
|
256
|
+
}
|
|
257
|
+
case "world.create": {
|
|
258
|
+
const [package_, connection, preset] = await Promise.all([
|
|
259
|
+
this.#content.readCurrentTreeContentPackage(request.packageId),
|
|
260
|
+
this.#models.bind(),
|
|
261
|
+
this.#playPresets.bindCurrent(),
|
|
262
|
+
]);
|
|
263
|
+
return this.#worlds.createFromContentPackage({
|
|
264
|
+
operationId: request.operationId,
|
|
265
|
+
sourcePackageId: request.packageId,
|
|
266
|
+
packageFiles: package_.files,
|
|
267
|
+
prompt: {
|
|
268
|
+
hostBinding: presetHostBinding(preset),
|
|
269
|
+
modelBinding: new FileNativeModelHost(connection).binding(),
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
case "world.creation-outcome":
|
|
274
|
+
return this.#worlds.getCreationOutcome(request.operationId);
|
|
275
|
+
case "world.rename":
|
|
276
|
+
return this.#worlds.renameWorld(request.worldId, request.name);
|
|
277
|
+
case "world.delete":
|
|
278
|
+
return playCall(async () => {
|
|
279
|
+
this.#playCallChains.forgetWorld(request.worldId);
|
|
280
|
+
return this.#worlds.deleteWorld(request.worldId);
|
|
281
|
+
});
|
|
282
|
+
case "world.read": {
|
|
283
|
+
await this.#reconcileArtifacts(request.worldId);
|
|
284
|
+
const head = await this.#worlds.currentHead(request.worldId);
|
|
285
|
+
const playerViews = await this.#worlds.renderPlayerViewsAtHead(request.worldId, head);
|
|
286
|
+
const [state, control, history, runtime, endpoint, authority, artifacts, extensions, playerViewPanels,] = await Promise.all([
|
|
287
|
+
this.#worlds.readSurface(request.worldId, "state"),
|
|
288
|
+
this.#worlds.readSurface(request.worldId, "control"),
|
|
289
|
+
this.#worlds.readSurface(request.worldId, "history"),
|
|
290
|
+
this.#worlds.readSurface(request.worldId, "runtime"),
|
|
291
|
+
this.#worlds.recoverEndpoint(request.worldId, head),
|
|
292
|
+
this.#worlds.readAuthorityHistory(request.worldId),
|
|
293
|
+
this.#frontendProjection(request.worldId),
|
|
294
|
+
this.#artifacts.readExtensionSummaries(request.worldId),
|
|
295
|
+
this.#frontendPlayerViewPanels(request.worldId, head, playerViews),
|
|
296
|
+
]);
|
|
297
|
+
return {
|
|
298
|
+
worldId: request.worldId,
|
|
299
|
+
head,
|
|
300
|
+
state,
|
|
301
|
+
control,
|
|
302
|
+
history,
|
|
303
|
+
runtime,
|
|
304
|
+
playerViews,
|
|
305
|
+
committedMessages: endpoint.history.map((message) => ({
|
|
306
|
+
...message,
|
|
307
|
+
head: authority.commits.find((commit) => commit.historyAppend.some(({ messageId }) => messageId === message.messageId))?.head ?? "genesis",
|
|
308
|
+
})),
|
|
309
|
+
artifacts,
|
|
310
|
+
extensions,
|
|
311
|
+
playerViewPanels,
|
|
312
|
+
playCallChain: await this.#playCallChains.inspectWorld(request.worldId),
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
case "artifacts.read":
|
|
316
|
+
await this.#reconcileArtifacts(request.worldId);
|
|
317
|
+
return this.#frontendProjection(request.worldId, request.channel);
|
|
318
|
+
case "artifacts.debug":
|
|
319
|
+
await this.#reconcileArtifacts(request.worldId);
|
|
320
|
+
return this.#frontendDebug(request.worldId, request.operationId);
|
|
321
|
+
case "world.repair-materialization": {
|
|
322
|
+
const result = await this.#worlds.repairMaterialization(request.worldId);
|
|
323
|
+
await this.#reconcileArtifacts(request.worldId);
|
|
324
|
+
return result;
|
|
325
|
+
}
|
|
326
|
+
case "world.control-draft.save":
|
|
327
|
+
return this.#worlds.saveControlDraft(request.worldId, request.files);
|
|
328
|
+
case "world.control-draft.preview": {
|
|
329
|
+
const prompt = {
|
|
330
|
+
...(await this.#promptBinding()),
|
|
331
|
+
playerInput: this.#locale === "zh-CN"
|
|
332
|
+
? "预览世界控制。"
|
|
333
|
+
: "Preview world control.",
|
|
334
|
+
};
|
|
335
|
+
return this.#worlds.previewControlDraft(request.worldId, prompt);
|
|
336
|
+
}
|
|
337
|
+
case "world.control-draft.apply": {
|
|
338
|
+
const prompt = {
|
|
339
|
+
...(await this.#promptBinding()),
|
|
340
|
+
playerInput: this.#locale === "zh-CN"
|
|
341
|
+
? "预览世界控制。"
|
|
342
|
+
: "Preview world control.",
|
|
343
|
+
};
|
|
344
|
+
return this.#worlds.applyControlDraft(request.worldId, prompt);
|
|
345
|
+
}
|
|
346
|
+
case "world.derive": {
|
|
347
|
+
const preset = await this.#playPresets.bindCurrent();
|
|
348
|
+
const result = await this.#worlds.deriveWorld({
|
|
349
|
+
operationId: request.operationId,
|
|
350
|
+
sourceWorldId: request.sourceWorldId,
|
|
351
|
+
sourceHead: request.sourceHead,
|
|
352
|
+
hostPresetId: preset.id,
|
|
353
|
+
});
|
|
354
|
+
await this.#playCallChains.forkToDerivedWorld({
|
|
355
|
+
sourceWorldId: request.sourceWorldId,
|
|
356
|
+
sourceHead: request.sourceHead,
|
|
357
|
+
targetWorldId: result.world.worldId,
|
|
358
|
+
});
|
|
359
|
+
await this.#reconcileResultArtifacts(result);
|
|
360
|
+
return result;
|
|
361
|
+
}
|
|
362
|
+
case "play.chain.revise-player": {
|
|
363
|
+
const result = await playCall(() => this.#playCallChains.revisePlayer({
|
|
364
|
+
operationId: request.operationId,
|
|
365
|
+
worldId: request.worldId,
|
|
366
|
+
chainId: request.chainId,
|
|
367
|
+
eventId: request.eventId,
|
|
368
|
+
replacementExchangeId: request.replacementExchangeId,
|
|
369
|
+
replacementText: request.replacementText,
|
|
370
|
+
}));
|
|
371
|
+
await this.#reconcileArtifacts(request.worldId);
|
|
372
|
+
return result;
|
|
373
|
+
}
|
|
374
|
+
case "play.chain.start": {
|
|
375
|
+
const { modelHost, hostBinding, playPreset, modelBinding } = await this.#continuousBinding();
|
|
376
|
+
return playCall(() => this.#playCallChains.start({
|
|
377
|
+
worldId: request.worldId,
|
|
378
|
+
chainId: request.chainId,
|
|
379
|
+
exchangeId: request.exchangeId,
|
|
380
|
+
playerText: request.playerText,
|
|
381
|
+
hostBinding,
|
|
382
|
+
playPreset,
|
|
383
|
+
modelBinding,
|
|
384
|
+
modelHost,
|
|
385
|
+
...(playCallChainObserver === undefined
|
|
386
|
+
? {}
|
|
387
|
+
: { observer: playCallChainObserver }),
|
|
388
|
+
}));
|
|
389
|
+
}
|
|
390
|
+
case "play.chain.append":
|
|
391
|
+
return playCall(async () => this.#playCallChains.append({
|
|
392
|
+
worldId: request.worldId,
|
|
393
|
+
chainId: request.chainId,
|
|
394
|
+
exchangeId: request.exchangeId,
|
|
395
|
+
playerText: request.playerText,
|
|
396
|
+
modelHost: await this.#modelHost(),
|
|
397
|
+
...(playCallChainObserver === undefined
|
|
398
|
+
? {}
|
|
399
|
+
: { observer: playCallChainObserver }),
|
|
400
|
+
}));
|
|
401
|
+
case "play.chain.inspect":
|
|
402
|
+
return this.#playCallChains.inspectWorld(request.worldId);
|
|
403
|
+
case "correction.begin":
|
|
404
|
+
return this.#corrections.begin({
|
|
405
|
+
worldId: request.worldId,
|
|
406
|
+
operationId: request.operationId,
|
|
407
|
+
mode: "documents",
|
|
408
|
+
});
|
|
409
|
+
case "correction.read":
|
|
410
|
+
return this.#corrections.readDocument(request.candidateId, request.document);
|
|
411
|
+
case "correction.patch":
|
|
412
|
+
return this.#corrections.patchDocument(request);
|
|
413
|
+
case "correction.replace":
|
|
414
|
+
return this.#corrections.replaceDocument(request);
|
|
415
|
+
case "correction.preview":
|
|
416
|
+
return this.#corrections.preview({
|
|
417
|
+
candidateId: request.candidateId,
|
|
418
|
+
expectedVersion: request.expectedVersion,
|
|
419
|
+
prompt: await this.#promptBinding(),
|
|
420
|
+
});
|
|
421
|
+
case "correction.apply": {
|
|
422
|
+
const result = await this.#corrections.apply(request);
|
|
423
|
+
await this.#reconcileResultArtifacts(result);
|
|
424
|
+
return result;
|
|
425
|
+
}
|
|
426
|
+
case "correction.cancel":
|
|
427
|
+
await this.#corrections.cancel(request.candidateId, request.expectedVersion);
|
|
428
|
+
return { cancelled: true };
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
async #modelHost() {
|
|
432
|
+
return new FileNativeModelHost(await this.#models.bind(), fetch, this.#failureLog);
|
|
433
|
+
}
|
|
434
|
+
async #reconcileArtifacts(worldId) {
|
|
435
|
+
const [head, currentOperationId] = await Promise.all([
|
|
436
|
+
this.#worlds.currentHead(worldId),
|
|
437
|
+
this.#worlds.currentHeadOperationId(worldId),
|
|
438
|
+
]);
|
|
439
|
+
await this.#artifacts.reconcileHead(worldId, head, currentOperationId ?? undefined);
|
|
440
|
+
}
|
|
441
|
+
async #frontendProjection(worldId, channel) {
|
|
442
|
+
const artifacts = await this.#artifacts.readActiveProjection(worldId, channel);
|
|
443
|
+
return Promise.all(artifacts.map(async (artifact) => {
|
|
444
|
+
const { renderer, ...safeArtifact } = artifact;
|
|
445
|
+
void renderer;
|
|
446
|
+
const resolved = await this.#frontendBinding(artifact.playPresetId, artifact.playPresetRevision);
|
|
447
|
+
return {
|
|
448
|
+
...safeArtifact,
|
|
449
|
+
frontend: projectArtifactForFrontend(artifact, resolved.binding, resolved.failure),
|
|
450
|
+
};
|
|
451
|
+
}));
|
|
452
|
+
}
|
|
453
|
+
async #frontendPlayerViewPanels(worldId, head, playerViews) {
|
|
454
|
+
try {
|
|
455
|
+
const binding = await this.#playPresets.bindCurrent();
|
|
456
|
+
return projectPlayerViewPanels({
|
|
457
|
+
worldId,
|
|
458
|
+
head,
|
|
459
|
+
playerViews,
|
|
460
|
+
binding,
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
catch (error) {
|
|
464
|
+
// Panels are decoration on top of a world read. Any unusable preset —
|
|
465
|
+
// disabled, deleted, or written by an older build and now missing a
|
|
466
|
+
// required file — costs the panels, never the world itself.
|
|
467
|
+
if (error instanceof FileNativePlayPresetError)
|
|
468
|
+
return [];
|
|
469
|
+
throw error;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
async #frontendDebug(worldId, operationId) {
|
|
473
|
+
const records = await this.#artifacts.readDebug(worldId, operationId);
|
|
474
|
+
return Promise.all(records.map(async (record) => {
|
|
475
|
+
const { renderer, ...safeRecord } = record;
|
|
476
|
+
void renderer;
|
|
477
|
+
const resolved = await this.#frontendBinding(record.playPresetId, record.playPresetRevision);
|
|
478
|
+
return {
|
|
479
|
+
...safeRecord,
|
|
480
|
+
frontend: projectDebugArtifactForFrontend(record, resolved.binding, resolved.failure),
|
|
481
|
+
};
|
|
482
|
+
}));
|
|
483
|
+
}
|
|
484
|
+
async #frontendBinding(id, revision) {
|
|
485
|
+
try {
|
|
486
|
+
return {
|
|
487
|
+
binding: await this.#playPresets.bindRevision(id, revision),
|
|
488
|
+
failure: "missing_revision",
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
catch (error) {
|
|
492
|
+
// A historical revision is intentionally not replaced with current.
|
|
493
|
+
if (!(error instanceof FileNativePlayPresetError) ||
|
|
494
|
+
error.code === "store_invalid")
|
|
495
|
+
throw error;
|
|
496
|
+
return {
|
|
497
|
+
binding: null,
|
|
498
|
+
failure: error.code === "revision_not_found" || error.code === "not_found"
|
|
499
|
+
? "missing_revision"
|
|
500
|
+
: "invalid_revision",
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
async #reconcileResultArtifacts(result, fallbackWorldId) {
|
|
505
|
+
const candidate = isRecord(result) ? result : undefined;
|
|
506
|
+
const nestedWorld = candidate !== undefined && isRecord(candidate.world)
|
|
507
|
+
? candidate.world
|
|
508
|
+
: undefined;
|
|
509
|
+
const worldId = (candidate !== undefined && typeof candidate.worldId === "string"
|
|
510
|
+
? candidate.worldId
|
|
511
|
+
: undefined) ??
|
|
512
|
+
(nestedWorld !== undefined && typeof nestedWorld.worldId === "string"
|
|
513
|
+
? nestedWorld.worldId
|
|
514
|
+
: undefined) ??
|
|
515
|
+
fallbackWorldId;
|
|
516
|
+
if (worldId === undefined)
|
|
517
|
+
return;
|
|
518
|
+
await this.#reconcileArtifacts(worldId);
|
|
519
|
+
}
|
|
520
|
+
#settingImprovement(improvementId) {
|
|
521
|
+
const active = this.#settingImprovements.get(improvementId);
|
|
522
|
+
if (active === undefined)
|
|
523
|
+
throw new Error("Setting-improvement session does not exist");
|
|
524
|
+
return active;
|
|
525
|
+
}
|
|
526
|
+
async #promptBinding() {
|
|
527
|
+
const [preset, connection] = await Promise.all([
|
|
528
|
+
this.#playPresets.bindCurrent(),
|
|
529
|
+
this.#models.bind(),
|
|
530
|
+
]);
|
|
531
|
+
const modelHost = new FileNativeModelHost(connection);
|
|
532
|
+
return {
|
|
533
|
+
hostBinding: presetHostBinding(preset),
|
|
534
|
+
modelBinding: modelHost.binding(),
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
async #continuousBinding() {
|
|
538
|
+
const [modelHost, playPreset] = await Promise.all([
|
|
539
|
+
this.#modelHost(),
|
|
540
|
+
this.#playPresets.freeze(),
|
|
541
|
+
]);
|
|
542
|
+
return {
|
|
543
|
+
modelHost,
|
|
544
|
+
hostBinding: presetHostBinding(playPreset),
|
|
545
|
+
playPreset,
|
|
546
|
+
modelBinding: modelHost.binding(),
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
#storageNotices() {
|
|
550
|
+
const notices = [];
|
|
551
|
+
const recovery = this.#playPresets.recovery;
|
|
552
|
+
if (recovery !== null)
|
|
553
|
+
notices.push({
|
|
554
|
+
surface: "Play-preset library",
|
|
555
|
+
message: `The play-preset library could not be read (${recovery.message}). A default library was rebuilt; the original file remains at ${recovery.quarantinedPath}`,
|
|
556
|
+
});
|
|
557
|
+
return notices;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
async function playCall(operation) {
|
|
561
|
+
try {
|
|
562
|
+
return await operation();
|
|
563
|
+
}
|
|
564
|
+
catch (error) {
|
|
565
|
+
if (error instanceof PlayCallChainError)
|
|
566
|
+
throw new V1ProtocolError("invalid_request", error.message, {
|
|
567
|
+
cause: error,
|
|
568
|
+
});
|
|
569
|
+
throw error;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
function decodePortableContentArchive(archiveBase64) {
|
|
573
|
+
if (archiveBase64.length === 0 ||
|
|
574
|
+
archiveBase64.length > maxPortableContentArchiveBase64Characters ||
|
|
575
|
+
archiveBase64.length % 4 !== 0)
|
|
576
|
+
throw invalidContentArchiveUpload();
|
|
577
|
+
const archive = Buffer.from(archiveBase64, "base64");
|
|
578
|
+
if (archive.byteLength > maxPortableContentArchiveBytes ||
|
|
579
|
+
archive.toString("base64") !== archiveBase64)
|
|
580
|
+
throw invalidContentArchiveUpload();
|
|
581
|
+
return archive;
|
|
582
|
+
}
|
|
583
|
+
function invalidContentArchiveUpload() {
|
|
584
|
+
return new V1ProtocolError("invalid_request", "content.import.archiveBase64 is not a supported content-package ZIP");
|
|
585
|
+
}
|
|
586
|
+
function previewWorld(snapshot, openingMessageId, controlFingerprint) {
|
|
587
|
+
const inspection = inspectContentPackageCurrentTree(snapshot.files, {
|
|
588
|
+
worldDocumentSnapshot: snapshot,
|
|
589
|
+
});
|
|
590
|
+
if (inspection.opening !== "valid")
|
|
591
|
+
throw new Error(`A real first-turn Prompt Preview requires a usable opening.md: ${inspection.issues
|
|
592
|
+
.filter(({ code }) => code === "missing_opening" || code === "invalid_opening")
|
|
593
|
+
.map(({ message }) => message)
|
|
594
|
+
.join(";")}`);
|
|
595
|
+
const opening = snapshot.files.find(({ path, encoding }) => path === "opening.md" && encoding === undefined);
|
|
596
|
+
if (opening === undefined)
|
|
597
|
+
throw new Error("A real first-turn Prompt Preview could not read opening.md");
|
|
598
|
+
return {
|
|
599
|
+
controlFingerprint,
|
|
600
|
+
documentSnapshot: snapshot,
|
|
601
|
+
history: { [openingMessageId]: opening.contents },
|
|
602
|
+
additionalMaterials: [
|
|
603
|
+
{ kind: "history_message", message: openingMessageId },
|
|
604
|
+
],
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
function defaultSettingPreviewHost() {
|
|
608
|
+
return {
|
|
609
|
+
"frame.yaml": `format: narraeon.host-frame/v1
|
|
610
|
+
roles:
|
|
611
|
+
runtime_system:
|
|
612
|
+
- builtin: runtime.play-contract
|
|
613
|
+
- builtin: runtime.tool-contract
|
|
614
|
+
- builtin: runtime.operation-contract
|
|
615
|
+
author_instruction:
|
|
616
|
+
- include: world.instructions
|
|
617
|
+
world_context:
|
|
618
|
+
- builtin: runtime.coverage
|
|
619
|
+
- include: world.context
|
|
620
|
+
`,
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
function isRecord(value) {
|
|
624
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
625
|
+
}
|