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,304 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { FileNativePromptCompiler, } from "../prompt/FileNativePromptCompiler.js";
|
|
3
|
+
import { validateMaterialList } from "../prompt/MaterialSelection.js";
|
|
4
|
+
import { FileNativeWorldCreationError, } from "../world/FileNativeWorldStore.js";
|
|
5
|
+
import { WorldOperationBusyError, } from "../world/WorldOperationCoordinator.js";
|
|
6
|
+
import { beginWorldStateRevision, fileNativeStateChanges, reviseWorldState, worldDocumentRevisionFailureMessage, worldStateRevisionChanges, } from "../world/WorldStateRevision.js";
|
|
7
|
+
const claimHeartbeatIntervalMilliseconds = 20_000;
|
|
8
|
+
export class FileNativeContinuityCorrection {
|
|
9
|
+
#worlds;
|
|
10
|
+
#compiler;
|
|
11
|
+
#candidates = new Map();
|
|
12
|
+
constructor(worlds, options = {}) {
|
|
13
|
+
this.#worlds = worlds;
|
|
14
|
+
this.#compiler = options.compiler ?? new FileNativePromptCompiler();
|
|
15
|
+
}
|
|
16
|
+
async begin(input) {
|
|
17
|
+
let claimed;
|
|
18
|
+
try {
|
|
19
|
+
claimed = await this.#worlds.operations.claimCorrectionWorld(input.worldId, input.operationId, async () => {
|
|
20
|
+
if (!(await this.#worlds.reserveOperation(input.operationId)))
|
|
21
|
+
return null;
|
|
22
|
+
try {
|
|
23
|
+
const frozen = await this.#worlds.bindPlayCallChain(input.worldId);
|
|
24
|
+
this.#worlds.freezeControl(input.worldId, input.operationId);
|
|
25
|
+
return frozen;
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
this.#worlds.releaseControl(input.worldId, input.operationId);
|
|
29
|
+
await this.#worlds.releaseOperationReservation(input.operationId);
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (error instanceof WorldOperationBusyError)
|
|
36
|
+
throw new FileNativeWorldCreationError("operation_conflict", "A durable-state operation is already running for this world");
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
if (claimed.kind === "busy")
|
|
40
|
+
throw new FileNativeWorldCreationError("operation_conflict", "A durable-state operation is already running for this world");
|
|
41
|
+
const binding = claimed.value;
|
|
42
|
+
if (binding === null) {
|
|
43
|
+
await this.#worlds.operations.releaseCorrectionWorld(claimed.handle);
|
|
44
|
+
const previous = await this.#worlds.getOperationOutcome(input.operationId);
|
|
45
|
+
throw new Error(`The correction operation already exists and cannot be reused: ${previous.outcome}`);
|
|
46
|
+
}
|
|
47
|
+
let revision;
|
|
48
|
+
try {
|
|
49
|
+
revision = beginWorldStateRevision(binding.files);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
this.#worlds.releaseControl(input.worldId, input.operationId);
|
|
53
|
+
await Promise.allSettled([
|
|
54
|
+
this.#worlds.releaseOperationReservation(input.operationId),
|
|
55
|
+
this.#worlds.operations.releaseCorrectionWorld(claimed.handle),
|
|
56
|
+
]);
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
if (revision.snapshot.status !== "usable") {
|
|
60
|
+
this.#worlds.releaseControl(input.worldId, input.operationId);
|
|
61
|
+
await this.#worlds.failOperation(input.operationId, "failed");
|
|
62
|
+
await this.#worlds.operations.releaseCorrectionWorld(claimed.handle);
|
|
63
|
+
throw new Error("The correction candidate's world-state snapshot is unavailable");
|
|
64
|
+
}
|
|
65
|
+
const claimHeartbeat = setInterval(() => {
|
|
66
|
+
void this.#worlds.operations
|
|
67
|
+
.renewCorrectionWorld(claimed.handle)
|
|
68
|
+
.catch(() => undefined);
|
|
69
|
+
}, claimHeartbeatIntervalMilliseconds);
|
|
70
|
+
claimHeartbeat.unref();
|
|
71
|
+
const candidate = {
|
|
72
|
+
...revision,
|
|
73
|
+
id: `correction-${randomUUID()}`,
|
|
74
|
+
operationId: input.operationId,
|
|
75
|
+
worldId: input.worldId,
|
|
76
|
+
parentHead: binding.parentHead,
|
|
77
|
+
mode: input.mode,
|
|
78
|
+
version: 1,
|
|
79
|
+
materials: structuredClone(binding.additionalMaterials),
|
|
80
|
+
originalMaterials: structuredClone(binding.additionalMaterials),
|
|
81
|
+
history: structuredClone(binding.history),
|
|
82
|
+
reads: new Map(),
|
|
83
|
+
previewedVersion: null,
|
|
84
|
+
stateOperationClaim: claimed.handle,
|
|
85
|
+
claimHeartbeat,
|
|
86
|
+
};
|
|
87
|
+
this.#candidates.set(candidate.id, candidate);
|
|
88
|
+
return {
|
|
89
|
+
candidateId: candidate.id,
|
|
90
|
+
version: candidate.version,
|
|
91
|
+
parentHead: candidate.parentHead,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
readDocument(candidateId, handle) {
|
|
95
|
+
const candidate = this.#candidate(candidateId);
|
|
96
|
+
const document = readCompleteDocument(candidate.snapshot, handle);
|
|
97
|
+
const contents = candidate.files[document.logicalPath];
|
|
98
|
+
if (contents === undefined)
|
|
99
|
+
throw new Error(`Correction document ${handle} has no corresponding candidate source`);
|
|
100
|
+
const hash = sha256(contents);
|
|
101
|
+
candidate.reads.set(document.documentId, {
|
|
102
|
+
snapshotId: candidate.snapshot.id,
|
|
103
|
+
hash,
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
documentId: document.documentId,
|
|
107
|
+
ref: document.shortRef,
|
|
108
|
+
path: document.logicalPath,
|
|
109
|
+
codec: document.codec,
|
|
110
|
+
hash,
|
|
111
|
+
contents,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
patchDocument(input) {
|
|
115
|
+
const candidate = this.#atVersion(input.candidateId, input.expectedVersion);
|
|
116
|
+
if (candidate.mode !== "documents")
|
|
117
|
+
throw new Error("A material-list-only correction cannot modify world documents");
|
|
118
|
+
const target = this.#writableDocument(candidate, input.target, input.expectedHash);
|
|
119
|
+
this.#reviseDocument(candidate, {
|
|
120
|
+
kind: "patch",
|
|
121
|
+
document: { documentId: target.documentId },
|
|
122
|
+
edits: input.edits,
|
|
123
|
+
});
|
|
124
|
+
return this.#advance(candidate);
|
|
125
|
+
}
|
|
126
|
+
replaceDocument(input) {
|
|
127
|
+
const candidate = this.#atVersion(input.candidateId, input.expectedVersion);
|
|
128
|
+
if (candidate.mode !== "documents")
|
|
129
|
+
throw new Error("A material-list-only correction cannot modify world documents");
|
|
130
|
+
const target = this.#writableDocument(candidate, input.target, input.expectedHash);
|
|
131
|
+
this.#reviseDocument(candidate, {
|
|
132
|
+
kind: "replace",
|
|
133
|
+
document: { documentId: target.documentId },
|
|
134
|
+
contents: input.contents,
|
|
135
|
+
});
|
|
136
|
+
return this.#advance(candidate);
|
|
137
|
+
}
|
|
138
|
+
replaceMaterials(input) {
|
|
139
|
+
const candidate = this.#atVersion(input.candidateId, input.expectedVersion);
|
|
140
|
+
if (candidate.mode !== "materials")
|
|
141
|
+
throw new Error("A document correction cannot also replace the additional-materials list");
|
|
142
|
+
const nextMaterials = validateMaterialList(input.nextMaterials);
|
|
143
|
+
this.#compile(candidate, input.prompt, nextMaterials);
|
|
144
|
+
candidate.materials = structuredClone(nextMaterials);
|
|
145
|
+
return this.#advance(candidate);
|
|
146
|
+
}
|
|
147
|
+
preview(input) {
|
|
148
|
+
const candidate = this.#atVersion(input.candidateId, input.expectedVersion);
|
|
149
|
+
const nextPrompt = this.#compile(candidate, input.prompt, candidate.materials);
|
|
150
|
+
candidate.previewedVersion = candidate.version;
|
|
151
|
+
return {
|
|
152
|
+
parentHead: candidate.parentHead,
|
|
153
|
+
candidateVersion: candidate.version,
|
|
154
|
+
diffs: worldStateRevisionChanges(candidate).map((change) => ({
|
|
155
|
+
documentId: change.documentId,
|
|
156
|
+
path: change.after.logicalPath.slice("state/".length),
|
|
157
|
+
beforeHash: change.before?.mechanicalHash ?? "missing",
|
|
158
|
+
afterHash: change.after.mechanicalHash,
|
|
159
|
+
before: change.before?.contents ?? "missing",
|
|
160
|
+
after: change.after.contents,
|
|
161
|
+
})),
|
|
162
|
+
materials: {
|
|
163
|
+
before: structuredClone(candidate.originalMaterials),
|
|
164
|
+
after: structuredClone(candidate.materials),
|
|
165
|
+
},
|
|
166
|
+
nextPrompt,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
async apply(input) {
|
|
170
|
+
const candidate = this.#atVersion(input.candidateId, input.expectedVersion);
|
|
171
|
+
if (candidate.previewedVersion !== candidate.version)
|
|
172
|
+
throw new Error("Preview the current candidate version before applying a correction");
|
|
173
|
+
if (candidate.snapshot.status !== "usable")
|
|
174
|
+
throw new Error("Only a usable revision can commit a correction");
|
|
175
|
+
try {
|
|
176
|
+
return await this.#worlds.commitCorrection({
|
|
177
|
+
operationId: candidate.operationId,
|
|
178
|
+
worldId: candidate.worldId,
|
|
179
|
+
parentHead: candidate.parentHead,
|
|
180
|
+
nextMaterials: candidate.materials,
|
|
181
|
+
stateChanges: fileNativeStateChanges(candidate),
|
|
182
|
+
stateOperationClaim: candidate.stateOperationClaim,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
await this.#worlds.failOperation(candidate.operationId, "failed");
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
clearInterval(candidate.claimHeartbeat);
|
|
191
|
+
this.#worlds.releaseControl(candidate.worldId, candidate.operationId);
|
|
192
|
+
this.#candidates.delete(candidate.id);
|
|
193
|
+
await this.#worlds.operations
|
|
194
|
+
.releaseCorrectionWorld(candidate.stateOperationClaim)
|
|
195
|
+
.catch(() => undefined);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async cancel(candidateId, expectedVersion) {
|
|
199
|
+
const candidate = this.#atVersion(candidateId, expectedVersion);
|
|
200
|
+
clearInterval(candidate.claimHeartbeat);
|
|
201
|
+
this.#candidates.delete(candidateId);
|
|
202
|
+
this.#worlds.releaseControl(candidate.worldId, candidate.operationId);
|
|
203
|
+
try {
|
|
204
|
+
await this.#worlds.failOperation(candidate.operationId, "cancelled");
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
await this.#worlds.operations.releaseCorrectionWorld(candidate.stateOperationClaim);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
#compile(candidate, prompt, materials) {
|
|
211
|
+
return this.#compiler.preview({
|
|
212
|
+
endpoint: {
|
|
213
|
+
id: `${candidate.worldId}:${candidate.parentHead}`,
|
|
214
|
+
commit: candidate.parentHead,
|
|
215
|
+
operationId: candidate.operationId,
|
|
216
|
+
},
|
|
217
|
+
hostBinding: structuredClone(prompt.hostBinding),
|
|
218
|
+
world: {
|
|
219
|
+
controlFingerprint: fingerprintControl(candidate.files),
|
|
220
|
+
documentSnapshot: candidate.snapshot,
|
|
221
|
+
additionalMaterials: materials,
|
|
222
|
+
history: candidate.history,
|
|
223
|
+
},
|
|
224
|
+
playerInputPlacement: "bootstrap",
|
|
225
|
+
playerInput: "",
|
|
226
|
+
modelBinding: prompt.modelBinding,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
#candidate(id) {
|
|
230
|
+
const candidate = this.#candidates.get(id);
|
|
231
|
+
if (candidate === undefined)
|
|
232
|
+
throw new Error("The correction candidate does not exist or has ended");
|
|
233
|
+
return candidate;
|
|
234
|
+
}
|
|
235
|
+
#atVersion(id, expected) {
|
|
236
|
+
const candidate = this.#candidate(id);
|
|
237
|
+
if (candidate.version !== expected)
|
|
238
|
+
throw new Error("The correction candidate version has changed");
|
|
239
|
+
return candidate;
|
|
240
|
+
}
|
|
241
|
+
#writableDocument(candidate, handle, expectedHash) {
|
|
242
|
+
const document = readCompleteDocument(candidate.snapshot, handle);
|
|
243
|
+
const read = candidate.reads.get(document.documentId);
|
|
244
|
+
if (read?.snapshotId !== candidate.snapshot.id ||
|
|
245
|
+
read.hash !== expectedHash)
|
|
246
|
+
throw new Error("A correction document must be read in full first and carry the same pre-write hash");
|
|
247
|
+
const contents = candidate.files[document.logicalPath];
|
|
248
|
+
if (contents === undefined || sha256(contents) !== expectedHash)
|
|
249
|
+
throw new Error("The correction document's pre-write hash has changed");
|
|
250
|
+
return document;
|
|
251
|
+
}
|
|
252
|
+
#reviseDocument(candidate, command) {
|
|
253
|
+
const revised = reviseWorldState(candidate, { commands: [command] });
|
|
254
|
+
if (!revised.ok || revised.snapshotStatus !== "usable")
|
|
255
|
+
throw new Error(worldDocumentRevisionFailureMessage(revised.diagnostics));
|
|
256
|
+
candidate.reads.clear();
|
|
257
|
+
}
|
|
258
|
+
#advance(candidate) {
|
|
259
|
+
candidate.version += 1;
|
|
260
|
+
candidate.previewedVersion = null;
|
|
261
|
+
return { version: candidate.version };
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function readCompleteDocument(snapshot, handle) {
|
|
265
|
+
const requested = handle.replace(/^@/u, "");
|
|
266
|
+
const selectors = handle.startsWith("@")
|
|
267
|
+
? [{ shortRef: requested }]
|
|
268
|
+
: [{ documentId: requested }, { shortRef: requested }];
|
|
269
|
+
for (const selector of selectors) {
|
|
270
|
+
let cursor = null;
|
|
271
|
+
let descriptor = null;
|
|
272
|
+
do {
|
|
273
|
+
const result = snapshot.query({
|
|
274
|
+
kind: "read_document",
|
|
275
|
+
document: selector,
|
|
276
|
+
maxBytes: 65_536,
|
|
277
|
+
cursor,
|
|
278
|
+
});
|
|
279
|
+
if (result.kind === "error") {
|
|
280
|
+
if (result.diagnostics.every(({ code }) => code === "document_not_found"))
|
|
281
|
+
break;
|
|
282
|
+
throw new Error(worldDocumentRevisionFailureMessage(result.diagnostics));
|
|
283
|
+
}
|
|
284
|
+
if (result.kind !== "read_document")
|
|
285
|
+
throw new Error("The correction document's exact read returned the wrong query result");
|
|
286
|
+
descriptor = result.document;
|
|
287
|
+
cursor = result.page.nextCursor;
|
|
288
|
+
} while (cursor !== null);
|
|
289
|
+
if (descriptor !== null)
|
|
290
|
+
return descriptor;
|
|
291
|
+
}
|
|
292
|
+
throw new Error(`Correction document ${handle} does not exist`);
|
|
293
|
+
}
|
|
294
|
+
function fingerprintControl(files) {
|
|
295
|
+
const hash = createHash("sha256");
|
|
296
|
+
for (const [path, contents] of Object.entries(files)
|
|
297
|
+
.filter(([path]) => path.startsWith("control/"))
|
|
298
|
+
.sort(([left], [right]) => left.localeCompare(right)))
|
|
299
|
+
hash.update(path).update("\0").update(contents).update("\0");
|
|
300
|
+
return `sha256:${hash.digest("hex")}`;
|
|
301
|
+
}
|
|
302
|
+
function sha256(contents) {
|
|
303
|
+
return `sha256:${createHash("sha256").update(contents).digest("hex")}`;
|
|
304
|
+
}
|