narraeon 0.4.0 → 0.5.1
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 +186 -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 +445 -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 +13 -0
- 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-DG85hpWC.css +1 -0
- package/dist/web/assets/index-DfxvCjb6.js +1207 -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-D8YKfmNG.js +0 -1192
- package/dist/web/assets/index-Xa0s35CJ.css +0 -1
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { WorldExtensionRequests } from "../extension/WorldExtensionRequests.js";
|
|
2
|
+
import { isWorldExtensionControl, } from "../../protocol/worldExtensions.js";
|
|
1
3
|
import { Buffer } from "node:buffer";
|
|
2
4
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
5
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
6
|
import { mkdir, open, readdir, readFile, readlink, rename, rm, stat, } from "node:fs/promises";
|
|
5
7
|
import { dirname, join, resolve } from "node:path";
|
|
6
8
|
import { tmpdir } from "node:os";
|
|
7
|
-
import { validatePlayPresetArtifactPayload, } from "../play/FileNativePlayPresetStore.js";
|
|
9
|
+
import { validatePlayPresetArtifactPayload, isFrozenArtifactPresentation, } from "../play/FileNativePlayPresetStore.js";
|
|
8
10
|
export class ArtifactStoreInvariantError extends Error {
|
|
9
11
|
constructor(message, options) {
|
|
10
12
|
super(message, options);
|
|
@@ -26,11 +28,163 @@ const maxPayloadBytes = 4 * 1024 * 1024;
|
|
|
26
28
|
*/
|
|
27
29
|
export class FileNativeArtifactStore {
|
|
28
30
|
#root;
|
|
31
|
+
#extensionControls;
|
|
32
|
+
#worldsRoot;
|
|
29
33
|
#memoryRecords = new Map();
|
|
30
34
|
#memoryEvents = new Map();
|
|
31
35
|
#mutationContext = new AsyncLocalStorage();
|
|
32
36
|
constructor(dataRoot) {
|
|
33
37
|
this.#root = join(resolve(dataRoot), "artifact-store");
|
|
38
|
+
this.#extensionControls = new WorldExtensionRequests(join(resolve(dataRoot), "worlds-file-native"));
|
|
39
|
+
this.#worldsRoot = join(resolve(dataRoot), "worlds-file-native");
|
|
40
|
+
}
|
|
41
|
+
/** Control acceptance and artifact publication share one persistent lock.
|
|
42
|
+
* Callers holding the Authority lock always acquire it before this lock. */
|
|
43
|
+
static withWorldControlsMutation(dataRoot, worldId, action) {
|
|
44
|
+
return withArtifactWorldLock(artifactWorldLockPath(dataRoot, worldId), action);
|
|
45
|
+
}
|
|
46
|
+
/** The immutable closure publishes atomically with the derived world. */
|
|
47
|
+
async stageFork(input) {
|
|
48
|
+
await this.#withWorldMutation(input.sourceWorldId, async () => {
|
|
49
|
+
const projection = await this.readActiveProjection(input.sourceWorldId, undefined, input.timeline);
|
|
50
|
+
const retained = new Set(projection
|
|
51
|
+
.filter((item) => item.save === "commit" && item.attachment !== undefined)
|
|
52
|
+
.map((item) => item.recordId));
|
|
53
|
+
const effective = (await this.#effectiveRecords(input.sourceWorldId)).filter(({ record }) => retained.has(record.recordId));
|
|
54
|
+
const operations = [];
|
|
55
|
+
const records = [];
|
|
56
|
+
const events = [];
|
|
57
|
+
let sequence = Math.max(0, ...effective.map(({ record }) => record.sequence));
|
|
58
|
+
for (const { record, head } of effective) {
|
|
59
|
+
const source = (await this.#readOperation(record.operationId));
|
|
60
|
+
const operationId = `artifact-fork:${input.targetWorldId}:${identityHash(record.operationId)}`;
|
|
61
|
+
if (!operations.some((operation) => operation.context.operationId === operationId)) {
|
|
62
|
+
operations.push({
|
|
63
|
+
...source,
|
|
64
|
+
context: {
|
|
65
|
+
...source.context,
|
|
66
|
+
worldId: input.targetWorldId,
|
|
67
|
+
operationId,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
const copy = {
|
|
72
|
+
...record,
|
|
73
|
+
worldId: input.targetWorldId,
|
|
74
|
+
operationId,
|
|
75
|
+
sequence: record.sequence,
|
|
76
|
+
};
|
|
77
|
+
copy.recordFingerprint = rawRecordFingerprint(copy);
|
|
78
|
+
records.push(copy);
|
|
79
|
+
events.push({
|
|
80
|
+
schemaVersion: 1,
|
|
81
|
+
kind: "activate",
|
|
82
|
+
sequence: ++sequence,
|
|
83
|
+
operationId,
|
|
84
|
+
recordId: copy.recordId,
|
|
85
|
+
});
|
|
86
|
+
events.push({
|
|
87
|
+
schemaVersion: 1,
|
|
88
|
+
kind: "bind_head",
|
|
89
|
+
sequence: ++sequence,
|
|
90
|
+
operationId,
|
|
91
|
+
recordId: copy.recordId,
|
|
92
|
+
head: head,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
const snapshot = {
|
|
96
|
+
schemaVersion: 1,
|
|
97
|
+
worldId: input.targetWorldId,
|
|
98
|
+
operations,
|
|
99
|
+
records,
|
|
100
|
+
events,
|
|
101
|
+
};
|
|
102
|
+
await mkdir(join(input.targetWorldRoot, "runtime"), { recursive: true });
|
|
103
|
+
await writeDurableJson(join(input.targetWorldRoot, "runtime", "artifact-fork.json"), snapshot, true);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
async #ensureForkRestored(worldId) {
|
|
107
|
+
if (dirname(resolve(this.#worldsRoot, worldId)) !== this.#worldsRoot)
|
|
108
|
+
return;
|
|
109
|
+
try {
|
|
110
|
+
await stat(join(this.#worldRoot(worldId), "fork-restored.json"));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (!isMissing(error))
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
await stat(join(this.#worldsRoot, worldId, "runtime", "artifact-fork.json"));
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
if (isMissing(error))
|
|
122
|
+
return;
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
await this.#withWorldMutation(worldId, () => Promise.resolve());
|
|
126
|
+
}
|
|
127
|
+
async #restoreFork(worldId) {
|
|
128
|
+
if (dirname(resolve(this.#worldsRoot, worldId)) !== this.#worldsRoot)
|
|
129
|
+
return;
|
|
130
|
+
const marker = join(this.#worldRoot(worldId), "fork-restored.json");
|
|
131
|
+
try {
|
|
132
|
+
await readFile(marker);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
if (!isMissing(error))
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
let snapshot;
|
|
140
|
+
try {
|
|
141
|
+
snapshot = parseArtifactJson(await readFile(join(this.#worldsRoot, worldId, "runtime", "artifact-fork.json"), "utf8"), "artifact fork");
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
if (isMissing(error))
|
|
145
|
+
return;
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
if (!isRecord(snapshot) ||
|
|
149
|
+
!hasExactKeys(snapshot, [
|
|
150
|
+
"schemaVersion",
|
|
151
|
+
"worldId",
|
|
152
|
+
"operations",
|
|
153
|
+
"records",
|
|
154
|
+
"events",
|
|
155
|
+
]) ||
|
|
156
|
+
snapshot.schemaVersion !== 1 ||
|
|
157
|
+
snapshot.worldId !== worldId ||
|
|
158
|
+
!Array.isArray(snapshot.operations) ||
|
|
159
|
+
!Array.isArray(snapshot.records) ||
|
|
160
|
+
!Array.isArray(snapshot.events))
|
|
161
|
+
throw new ArtifactStoreCorruptionError("Artifact fork snapshot is invalid");
|
|
162
|
+
for (const operation of snapshot.operations) {
|
|
163
|
+
assertOperation(operation);
|
|
164
|
+
if (operation.context.worldId !== worldId)
|
|
165
|
+
throw new ArtifactStoreCorruptionError("Artifact fork world mismatch");
|
|
166
|
+
}
|
|
167
|
+
for (const record of snapshot.records) {
|
|
168
|
+
assertRawRecord(record);
|
|
169
|
+
if (record.worldId !== worldId ||
|
|
170
|
+
!snapshot.operations.some((operation) => operation.context.operationId === record.operationId))
|
|
171
|
+
throw new ArtifactStoreCorruptionError("Artifact fork record mismatch");
|
|
172
|
+
}
|
|
173
|
+
for (const event of snapshot.events) {
|
|
174
|
+
assertEvent(event);
|
|
175
|
+
if (!snapshot.records.some((record) => record.recordId === event.recordId &&
|
|
176
|
+
record.operationId === event.operationId))
|
|
177
|
+
throw new ArtifactStoreCorruptionError("Artifact fork event mismatch");
|
|
178
|
+
}
|
|
179
|
+
// No mutation can start until this idempotent restore finishes under the world lock.
|
|
180
|
+
for (const operation of snapshot.operations)
|
|
181
|
+
await this.#writeOperation(operation);
|
|
182
|
+
for (const record of snapshot.records)
|
|
183
|
+
await writeDurableJson(join(this.#worldRoot(worldId), "records", artifactSequenceFileName(record.sequence)), record, false);
|
|
184
|
+
for (const event of snapshot.events)
|
|
185
|
+
await writeDurableJson(join(this.#worldRoot(worldId), "events", artifactSequenceFileName(event.sequence)), event, false);
|
|
186
|
+
await mkdir(this.#worldRoot(worldId), { recursive: true });
|
|
187
|
+
await writeDurableJson(marker, { schemaVersion: 1 }, false);
|
|
34
188
|
}
|
|
35
189
|
async beginOperation(context) {
|
|
36
190
|
return this.#withWorldMutation(context.worldId, () => this.#beginOperation(context));
|
|
@@ -106,6 +260,22 @@ export class FileNativeArtifactStore {
|
|
|
106
260
|
operation.status === "completed" ||
|
|
107
261
|
operation.artifactProjectionStatus === "superseded")
|
|
108
262
|
return;
|
|
263
|
+
const mounts = [
|
|
264
|
+
...new Set(Object.values(context.frozenPresentations ?? {})
|
|
265
|
+
.map((item) => item.mount)
|
|
266
|
+
.concat(context.frozenResources?.mount === undefined
|
|
267
|
+
? []
|
|
268
|
+
: [context.frozenResources.mount])),
|
|
269
|
+
];
|
|
270
|
+
operation.requests = [
|
|
271
|
+
...(operation.requests ?? []).filter((item) => item.requestId !== context.requestId),
|
|
272
|
+
{
|
|
273
|
+
requestId: context.requestId,
|
|
274
|
+
displayName: context.displayName ?? context.requestId,
|
|
275
|
+
mounts,
|
|
276
|
+
status: "running",
|
|
277
|
+
},
|
|
278
|
+
];
|
|
109
279
|
const current = operation.requestAttempts[context.requestId];
|
|
110
280
|
if (current !== undefined && context.requestAttempt < current)
|
|
111
281
|
return;
|
|
@@ -139,6 +309,19 @@ export class FileNativeArtifactStore {
|
|
|
139
309
|
await this.#appendRecordEvent(record, event);
|
|
140
310
|
}
|
|
141
311
|
}
|
|
312
|
+
async finishRequest(context, failed) {
|
|
313
|
+
await this.#withWorldMutation(context.worldId, async () => {
|
|
314
|
+
const operation = await this.#readOperation(context.operationId);
|
|
315
|
+
if (operation?.requestAttempts[context.requestId] !== context.requestAttempt)
|
|
316
|
+
return;
|
|
317
|
+
await this.#writeOperation({
|
|
318
|
+
...operation,
|
|
319
|
+
requests: (operation.requests ?? []).map((item) => item.requestId === context.requestId
|
|
320
|
+
? { ...item, status: failed ? "failed" : "completed" }
|
|
321
|
+
: item),
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
}
|
|
142
325
|
async reconcileHead(worldId, head, exceptOperationId) {
|
|
143
326
|
return this.#withWorldMutation(worldId, () => this.#reconcileHead(worldId, head, exceptOperationId));
|
|
144
327
|
}
|
|
@@ -257,7 +440,9 @@ export class FileNativeArtifactStore {
|
|
|
257
440
|
if (totalBytes + payloadBytes > input.context.maxArtifactBytes)
|
|
258
441
|
return artifactFailure("Follow-up request artifacts exceed maxArtifactBytes");
|
|
259
442
|
const record = await this.#newRecord(input, declaration, payload.value, fingerprint, callFingerprint);
|
|
260
|
-
const stale = isStaleOperation(operation) ||
|
|
443
|
+
const stale = isStaleOperation(operation) ||
|
|
444
|
+
staleAttempt ||
|
|
445
|
+
!(await this.#extensionControls.visible(record.worldId, record));
|
|
261
446
|
if (declaration.save === "none") {
|
|
262
447
|
const records = this.#memoryRecords.get(input.context.operationId) ?? [];
|
|
263
448
|
records.push(record);
|
|
@@ -337,7 +522,9 @@ export class FileNativeArtifactStore {
|
|
|
337
522
|
event.operationId === record.operationId &&
|
|
338
523
|
event.recordId === record.recordId &&
|
|
339
524
|
(head === undefined || event.head === head));
|
|
340
|
-
const stale = isStaleOperation(operation) ||
|
|
525
|
+
const stale = isStaleOperation(operation) ||
|
|
526
|
+
currentAttempt !== record.requestAttempt ||
|
|
527
|
+
!(await this.#extensionControls.visible(record.worldId, record));
|
|
341
528
|
if (stale) {
|
|
342
529
|
if (!hasEvent("supersede"))
|
|
343
530
|
await this.#appendRecordEvent(record, {
|
|
@@ -416,7 +603,9 @@ export class FileNativeArtifactStore {
|
|
|
416
603
|
};
|
|
417
604
|
}
|
|
418
605
|
const staleAttempt = currentAttempt !== input.context.requestAttempt;
|
|
419
|
-
if (isStaleOperation(operation) ||
|
|
606
|
+
if (isStaleOperation(operation) ||
|
|
607
|
+
staleAttempt ||
|
|
608
|
+
!(await this.#extensionControls.visible(input.context.worldId, input.context))) {
|
|
420
609
|
const event = {
|
|
421
610
|
schemaVersion: 1,
|
|
422
611
|
kind: "ignored_call",
|
|
@@ -707,6 +896,7 @@ export class FileNativeArtifactStore {
|
|
|
707
896
|
return operation === null ? null : summary(operation);
|
|
708
897
|
}
|
|
709
898
|
async readExtensionSummaries(worldId) {
|
|
899
|
+
await this.#ensureForkRestored(worldId);
|
|
710
900
|
return (await this.#operationsForWorld(worldId))
|
|
711
901
|
.filter((operation) => operation.status !== "core_failed" &&
|
|
712
902
|
!(operation.status === "completed" &&
|
|
@@ -744,7 +934,8 @@ export class FileNativeArtifactStore {
|
|
|
744
934
|
await this.#writeOperation(next);
|
|
745
935
|
return summary(next);
|
|
746
936
|
}
|
|
747
|
-
async readActiveProjection(worldId, channel) {
|
|
937
|
+
async readActiveProjection(worldId, channel, timeline) {
|
|
938
|
+
await this.#ensureForkRestored(worldId);
|
|
748
939
|
const effective = await this.#effectiveRecords(worldId);
|
|
749
940
|
const operationStates = new Map();
|
|
750
941
|
for (const item of effective)
|
|
@@ -762,6 +953,12 @@ export class FileNativeArtifactStore {
|
|
|
762
953
|
if (operation === undefined ||
|
|
763
954
|
operation.artifactProjectionStatus === "superseded")
|
|
764
955
|
return false;
|
|
956
|
+
if (timeline !== undefined &&
|
|
957
|
+
operation.context.attachment !== undefined &&
|
|
958
|
+
!timeline.some((target) => target.contextId === operation.context.attachment?.contextId &&
|
|
959
|
+
target.eventId === operation.context.attachment?.eventId &&
|
|
960
|
+
target.head === operation.context.attachment?.head))
|
|
961
|
+
return false;
|
|
765
962
|
if (record.projection === "transient" && !isActiveInteraction(operation))
|
|
766
963
|
return false;
|
|
767
964
|
if (record.save === "operation" && operation?.status === "completed")
|
|
@@ -770,8 +967,17 @@ export class FileNativeArtifactStore {
|
|
|
770
967
|
return false;
|
|
771
968
|
return true;
|
|
772
969
|
});
|
|
773
|
-
const
|
|
970
|
+
const visible = await Promise.all(active.map(async (item) => (await this.#extensionControls.visible(worldId, item.record))
|
|
971
|
+
? item
|
|
972
|
+
: null));
|
|
973
|
+
const selected = selectProjection(visible.filter((item) => item !== null));
|
|
774
974
|
return selected.map(({ record, head }) => ({
|
|
975
|
+
...(operationStates.get(record.operationId)?.context.attachment ===
|
|
976
|
+
undefined
|
|
977
|
+
? {}
|
|
978
|
+
: {
|
|
979
|
+
attachment: structuredClone(operationStates.get(record.operationId).context.attachment),
|
|
980
|
+
}),
|
|
775
981
|
recordId: record.recordId,
|
|
776
982
|
worldId: record.worldId,
|
|
777
983
|
operationId: record.operationId,
|
|
@@ -788,6 +994,12 @@ export class FileNativeArtifactStore {
|
|
|
788
994
|
...(record.rendererRevision === undefined
|
|
789
995
|
? {}
|
|
790
996
|
: { rendererRevision: record.rendererRevision }),
|
|
997
|
+
...(record.extensionControl === undefined
|
|
998
|
+
? {}
|
|
999
|
+
: { extensionControl: structuredClone(record.extensionControl) }),
|
|
1000
|
+
...(record.frozenPresentation === undefined
|
|
1001
|
+
? {}
|
|
1002
|
+
: { frozenPresentation: structuredClone(record.frozenPresentation) }),
|
|
791
1003
|
payload: structuredClone(record.payload),
|
|
792
1004
|
projection: record.projection,
|
|
793
1005
|
save: record.save,
|
|
@@ -796,11 +1008,21 @@ export class FileNativeArtifactStore {
|
|
|
796
1008
|
}));
|
|
797
1009
|
}
|
|
798
1010
|
async readDebug(worldId, operationId) {
|
|
1011
|
+
await this.#ensureForkRestored(worldId);
|
|
799
1012
|
const effective = await this.#effectiveRecords(worldId);
|
|
1013
|
+
const operations = new Map((await this.#operationsForWorld(worldId)).map((operation) => [
|
|
1014
|
+
operation.context.operationId,
|
|
1015
|
+
operation,
|
|
1016
|
+
]));
|
|
800
1017
|
return effective
|
|
801
1018
|
.filter(({ record }) => operationId === undefined || record.operationId === operationId)
|
|
802
1019
|
.sort((left, right) => left.record.sequence - right.record.sequence)
|
|
803
1020
|
.map(({ record, status, head }) => ({
|
|
1021
|
+
...(operations.get(record.operationId)?.context.attachment === undefined
|
|
1022
|
+
? {}
|
|
1023
|
+
: {
|
|
1024
|
+
attachment: structuredClone(operations.get(record.operationId).context.attachment),
|
|
1025
|
+
}),
|
|
804
1026
|
recordId: record.recordId,
|
|
805
1027
|
sequence: record.sequence,
|
|
806
1028
|
operationId: record.operationId,
|
|
@@ -823,6 +1045,12 @@ export class FileNativeArtifactStore {
|
|
|
823
1045
|
save: record.save,
|
|
824
1046
|
projection: record.projection,
|
|
825
1047
|
payloadBytes: byteLength(record.payload, record.contentType),
|
|
1048
|
+
...(record.extensionControl === undefined
|
|
1049
|
+
? {}
|
|
1050
|
+
: { extensionControl: structuredClone(record.extensionControl) }),
|
|
1051
|
+
...(record.frozenPresentation === undefined
|
|
1052
|
+
? {}
|
|
1053
|
+
: { frozenPresentation: structuredClone(record.frozenPresentation) }),
|
|
826
1054
|
payload: structuredClone(record.payload),
|
|
827
1055
|
payloadFingerprint: record.payloadFingerprint,
|
|
828
1056
|
status,
|
|
@@ -854,6 +1082,23 @@ export class FileNativeArtifactStore {
|
|
|
854
1082
|
...(declaration.rendererRevision === undefined
|
|
855
1083
|
? {}
|
|
856
1084
|
: { rendererRevision: declaration.rendererRevision }),
|
|
1085
|
+
...(input.context.extensionControl === undefined
|
|
1086
|
+
? {}
|
|
1087
|
+
: {
|
|
1088
|
+
extensionControl: structuredClone(input.context.extensionControl),
|
|
1089
|
+
}),
|
|
1090
|
+
...(input.context.frozenPresentations?.[declaration.name] !== undefined
|
|
1091
|
+
? {
|
|
1092
|
+
frozenPresentation: structuredClone(input.context.frozenPresentations[declaration.name]),
|
|
1093
|
+
}
|
|
1094
|
+
: input.context.frozenResources === undefined
|
|
1095
|
+
? {}
|
|
1096
|
+
: {
|
|
1097
|
+
frozenPresentation: {
|
|
1098
|
+
...structuredClone(input.context.frozenResources),
|
|
1099
|
+
declaration: structuredClone(declaration),
|
|
1100
|
+
},
|
|
1101
|
+
}),
|
|
857
1102
|
payload: structuredClone(payload),
|
|
858
1103
|
save: declaration.save,
|
|
859
1104
|
projection: declaration.strategy,
|
|
@@ -1178,12 +1423,18 @@ export class FileNativeArtifactStore {
|
|
|
1178
1423
|
const active = this.#mutationContext.getStore();
|
|
1179
1424
|
if (active?.has(worldId) === true)
|
|
1180
1425
|
return action();
|
|
1181
|
-
return withArtifactWorldLock(
|
|
1426
|
+
return withArtifactWorldLock(artifactWorldLockPath(dirname(this.#root), worldId), () => this.#mutationContext.run(new Set([...(active ?? []), worldId]), async () => {
|
|
1427
|
+
await this.#restoreFork(worldId);
|
|
1428
|
+
return action();
|
|
1429
|
+
}));
|
|
1182
1430
|
}
|
|
1183
1431
|
#worldRoot(worldId) {
|
|
1184
1432
|
return join(this.#root, "worlds", identityHash(worldId));
|
|
1185
1433
|
}
|
|
1186
1434
|
}
|
|
1435
|
+
function artifactWorldLockPath(dataRoot, worldId) {
|
|
1436
|
+
return join(resolve(dataRoot), "runtime-locks", "artifacts", identityHash(worldId));
|
|
1437
|
+
}
|
|
1187
1438
|
const artifactWorldLockStaleMilliseconds = 30_000;
|
|
1188
1439
|
const artifactWorldLockWaitMilliseconds = 30_000;
|
|
1189
1440
|
async function withArtifactWorldLock(path, action) {
|
|
@@ -1489,6 +1740,9 @@ function artifactCallFingerprint(toolName, context, parameters) {
|
|
|
1489
1740
|
playPresetId: context.playPresetId,
|
|
1490
1741
|
playPresetRevision: context.playPresetRevision,
|
|
1491
1742
|
playPresetScriptsEnabled: context.playPresetScriptsEnabled,
|
|
1743
|
+
...(context.extensionControl === undefined
|
|
1744
|
+
? {}
|
|
1745
|
+
: { extensionControl: context.extensionControl }),
|
|
1492
1746
|
requestId: context.requestId,
|
|
1493
1747
|
requestAttempt: context.requestAttempt,
|
|
1494
1748
|
parameters,
|
|
@@ -1570,6 +1824,12 @@ function artifactSequenceFileName(sequence) {
|
|
|
1570
1824
|
}
|
|
1571
1825
|
function summary(operation) {
|
|
1572
1826
|
return {
|
|
1827
|
+
...(operation.context.attachment === undefined
|
|
1828
|
+
? {}
|
|
1829
|
+
: { attachment: structuredClone(operation.context.attachment) }),
|
|
1830
|
+
...(operation.requests === undefined
|
|
1831
|
+
? {}
|
|
1832
|
+
: { requests: structuredClone(operation.requests) }),
|
|
1573
1833
|
operationId: operation.context.operationId,
|
|
1574
1834
|
status: operation.extensionStatus,
|
|
1575
1835
|
...(operation.message === undefined ? {} : { message: operation.message }),
|
|
@@ -1604,8 +1864,10 @@ function assertContext(context) {
|
|
|
1604
1864
|
"playPresetId",
|
|
1605
1865
|
"playPresetRevision",
|
|
1606
1866
|
"playPresetScriptsEnabled",
|
|
1607
|
-
]))
|
|
1867
|
+
], ["attachment"]))
|
|
1608
1868
|
throw new ArtifactStoreInvariantError("Artifact operation context has an invalid structure");
|
|
1869
|
+
if (context.attachment !== undefined && !validAttachment(context.attachment))
|
|
1870
|
+
throw new ArtifactStoreInvariantError("Artifact reply attachment is invalid");
|
|
1609
1871
|
for (const [key, value] of Object.entries({
|
|
1610
1872
|
worldId: context.worldId,
|
|
1611
1873
|
parentHead: context.parentHead,
|
|
@@ -1629,7 +1891,13 @@ function assertRequestContext(context) {
|
|
|
1629
1891
|
playPresetId: context.playPresetId,
|
|
1630
1892
|
playPresetRevision: context.playPresetRevision,
|
|
1631
1893
|
playPresetScriptsEnabled: context.playPresetScriptsEnabled,
|
|
1894
|
+
...(context.attachment === undefined
|
|
1895
|
+
? {}
|
|
1896
|
+
: { attachment: context.attachment }),
|
|
1632
1897
|
});
|
|
1898
|
+
if (context.extensionControl !== undefined &&
|
|
1899
|
+
!isWorldExtensionControl(context.extensionControl))
|
|
1900
|
+
throw new ArtifactStoreInvariantError("Invalid extension control generation");
|
|
1633
1901
|
if (context.requestId.trim() === "" ||
|
|
1634
1902
|
!Number.isInteger(context.requestAttempt))
|
|
1635
1903
|
throw new ArtifactStoreInvariantError("Artifact request attempt is invalid");
|
|
@@ -1637,7 +1905,9 @@ function assertRequestContext(context) {
|
|
|
1637
1905
|
throw new ArtifactStoreInvariantError("Artifact request-attempt budget is invalid");
|
|
1638
1906
|
}
|
|
1639
1907
|
function sameContext(left, right) {
|
|
1640
|
-
return (left.
|
|
1908
|
+
return (stableSerialize(left.attachment ?? null) ===
|
|
1909
|
+
stableSerialize(right.attachment ?? null) &&
|
|
1910
|
+
left.worldId === right.worldId &&
|
|
1641
1911
|
left.parentHead === right.parentHead &&
|
|
1642
1912
|
left.operationId === right.operationId &&
|
|
1643
1913
|
left.playPresetId === right.playPresetId &&
|
|
@@ -1672,7 +1942,17 @@ function assertRawRecord(record) {
|
|
|
1672
1942
|
"payloadFingerprint",
|
|
1673
1943
|
"recordFingerprint",
|
|
1674
1944
|
"status",
|
|
1675
|
-
], [
|
|
1945
|
+
], [
|
|
1946
|
+
"key",
|
|
1947
|
+
"renderer",
|
|
1948
|
+
"rendererRevision",
|
|
1949
|
+
"frozenPresentation",
|
|
1950
|
+
"extensionControl",
|
|
1951
|
+
]) ||
|
|
1952
|
+
(record.extensionControl !== undefined &&
|
|
1953
|
+
!isWorldExtensionControl(record.extensionControl)) ||
|
|
1954
|
+
(record.frozenPresentation !== undefined &&
|
|
1955
|
+
!isFrozenArtifactPresentation(record.frozenPresentation)) ||
|
|
1676
1956
|
record.schemaVersion !== 1 ||
|
|
1677
1957
|
!isNonEmptyString(record.recordId) ||
|
|
1678
1958
|
!Number.isSafeInteger(record.sequence) ||
|
|
@@ -1733,7 +2013,29 @@ function assertOperation(operation) {
|
|
|
1733
2013
|
"completedRequests",
|
|
1734
2014
|
"artifactProjectionStatus",
|
|
1735
2015
|
"requestAttempts",
|
|
1736
|
-
], ["message", "head"]) ||
|
|
2016
|
+
], ["message", "head", "requests"]) ||
|
|
2017
|
+
(operation.requests !== undefined &&
|
|
2018
|
+
(!Array.isArray(operation.requests) ||
|
|
2019
|
+
!operation.requests.every((item) => isRecord(item) &&
|
|
2020
|
+
hasExactKeys(item, [
|
|
2021
|
+
"requestId",
|
|
2022
|
+
"displayName",
|
|
2023
|
+
"mounts",
|
|
2024
|
+
"status",
|
|
2025
|
+
]) &&
|
|
2026
|
+
isNonEmptyString(item.requestId) &&
|
|
2027
|
+
isNonEmptyString(item.displayName) &&
|
|
2028
|
+
Array.isArray(item.mounts) &&
|
|
2029
|
+
item.mounts.every((mount) => typeof mount === "string" &&
|
|
2030
|
+
[
|
|
2031
|
+
"story",
|
|
2032
|
+
"sidebar",
|
|
2033
|
+
"composer_above",
|
|
2034
|
+
"composer_below",
|
|
2035
|
+
"overlay",
|
|
2036
|
+
"debug",
|
|
2037
|
+
].includes(mount)) &&
|
|
2038
|
+
["running", "completed", "failed"].includes(item.status)))) ||
|
|
1737
2039
|
operation.schemaVersion !== 1 ||
|
|
1738
2040
|
!isRecord(context) ||
|
|
1739
2041
|
!hasExactKeys(context, [
|
|
@@ -1743,7 +2045,9 @@ function assertOperation(operation) {
|
|
|
1743
2045
|
"playPresetId",
|
|
1744
2046
|
"playPresetRevision",
|
|
1745
2047
|
"playPresetScriptsEnabled",
|
|
1746
|
-
]) ||
|
|
2048
|
+
], ["attachment"]) ||
|
|
2049
|
+
(context.attachment !== undefined &&
|
|
2050
|
+
!validAttachment(context.attachment)) ||
|
|
1747
2051
|
!isNonEmptyString(context.worldId) ||
|
|
1748
2052
|
!isNonEmptyString(context.parentHead) ||
|
|
1749
2053
|
!isNonEmptyString(context.operationId) ||
|
|
@@ -1912,3 +2216,13 @@ function isMissing(error) {
|
|
|
1912
2216
|
function isAlreadyExists(error) {
|
|
1913
2217
|
return isRecord(error) && error.code === "EEXIST";
|
|
1914
2218
|
}
|
|
2219
|
+
function validAttachment(value) {
|
|
2220
|
+
return (isRecord(value) &&
|
|
2221
|
+
hasExactKeys(value, ["contextId", "eventId", "runId", "head"]) &&
|
|
2222
|
+
isNonEmptyString(value.head) &&
|
|
2223
|
+
isNonEmptyString(value.contextId) &&
|
|
2224
|
+
isNonEmptyString(value.runId) &&
|
|
2225
|
+
typeof value.eventId === "number" &&
|
|
2226
|
+
Number.isSafeInteger(value.eventId) &&
|
|
2227
|
+
value.eventId > 0);
|
|
2228
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { isPlayPresetBinding, } from "../play/FileNativePlayPresetStore.js";
|
|
2
|
+
export function validAuthoringRequests(value, validPrompt, session) {
|
|
3
|
+
if (!Array.isArray(value) ||
|
|
4
|
+
value.length === 0 ||
|
|
5
|
+
!Array.isArray(session.modelItems) ||
|
|
6
|
+
!Array.isArray(session.completedRequestIds))
|
|
7
|
+
return false;
|
|
8
|
+
const items = session.modelItems;
|
|
9
|
+
const completed = session.completedRequestIds;
|
|
10
|
+
const ids = new Set();
|
|
11
|
+
let previous = -1;
|
|
12
|
+
let lastRequestId;
|
|
13
|
+
const valid = value.every((raw) => {
|
|
14
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
15
|
+
return false;
|
|
16
|
+
const item = raw;
|
|
17
|
+
if (Object.keys(item).length !== 4 ||
|
|
18
|
+
typeof item.requestId !== "string" ||
|
|
19
|
+
!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/u.test(item.requestId) ||
|
|
20
|
+
ids.has(item.requestId) ||
|
|
21
|
+
!Number.isSafeInteger(item.modelItemStart) ||
|
|
22
|
+
item.modelItemStart <= previous ||
|
|
23
|
+
!(item.requestId === session.activeRequestId ||
|
|
24
|
+
completed.includes(item.requestId)) ||
|
|
25
|
+
!isUserItem(items[item.modelItemStart]) ||
|
|
26
|
+
!validPrompt(item.bootstrap) ||
|
|
27
|
+
!isPlayPresetBinding(item.playPreset))
|
|
28
|
+
return false;
|
|
29
|
+
ids.add(item.requestId);
|
|
30
|
+
lastRequestId = item.requestId;
|
|
31
|
+
previous = item.modelItemStart;
|
|
32
|
+
return true;
|
|
33
|
+
});
|
|
34
|
+
return (valid &&
|
|
35
|
+
(session.activeRequestId === null ||
|
|
36
|
+
lastRequestId === session.activeRequestId));
|
|
37
|
+
}
|
|
38
|
+
function isUserItem(value) {
|
|
39
|
+
return (typeof value === "object" &&
|
|
40
|
+
value !== null &&
|
|
41
|
+
"kind" in value &&
|
|
42
|
+
value.kind === "user");
|
|
43
|
+
}
|
|
44
|
+
export function authoringRequestPreviews(session) {
|
|
45
|
+
const requests = session.requests ?? [];
|
|
46
|
+
const legacy = requests.length === 0 || requests[0].modelItemStart > 0;
|
|
47
|
+
return [
|
|
48
|
+
...(legacy
|
|
49
|
+
? [
|
|
50
|
+
{
|
|
51
|
+
requestId: session.creationRequestId,
|
|
52
|
+
compilation: structuredClone(session.bootstrap),
|
|
53
|
+
legacyBootstrap: true,
|
|
54
|
+
},
|
|
55
|
+
]
|
|
56
|
+
: []),
|
|
57
|
+
...requests.map(({ requestId, bootstrap }) => ({
|
|
58
|
+
requestId,
|
|
59
|
+
compilation: structuredClone(bootstrap),
|
|
60
|
+
legacyBootstrap: false,
|
|
61
|
+
})),
|
|
62
|
+
];
|
|
63
|
+
}
|