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.
Files changed (76) hide show
  1. package/dist/node/protocol/appPreferences.js +25 -0
  2. package/dist/node/protocol/conversationObservation.js +67 -0
  3. package/dist/node/protocol/v1.js +186 -34
  4. package/dist/node/protocol/worldExtensions.js +16 -0
  5. package/dist/node/protocol/worldMaintenance.js +77 -0
  6. package/dist/node/protocol/worldPromptDiagnostics.js +1 -0
  7. package/dist/node/runtime/ConversationChanges.js +24 -0
  8. package/dist/node/runtime/V1Runtime.js +445 -121
  9. package/dist/node/runtime/artifact/FileNativeArtifactStore.js +326 -12
  10. package/dist/node/runtime/authoring/AuthoringRequestSnapshot.js +63 -0
  11. package/dist/node/runtime/authoring/ModelToolConversation.js +218 -0
  12. package/dist/node/runtime/config/AppPreferencesStore.js +46 -17
  13. package/dist/node/runtime/content/ContentTreeFingerprint.js +13 -0
  14. package/dist/node/runtime/content/ContentWorkspace.js +43 -13
  15. package/dist/node/runtime/content/CurrentTreeContentLibrary.js +128 -46
  16. package/dist/node/runtime/content/FileNativeContentTree.js +28 -10
  17. package/dist/node/runtime/content/PackageFollowups.js +80 -0
  18. package/dist/node/runtime/extension/FrontendExtensionBundle.js +19 -14
  19. package/dist/node/runtime/extension/PackageScriptPermissions.js +70 -0
  20. package/dist/node/runtime/extension/WorldExtensionControls.js +205 -0
  21. package/dist/node/runtime/extension/WorldExtensionRequests.js +64 -0
  22. package/dist/node/runtime/model/FileNativeModelAdapters.js +21 -466
  23. package/dist/node/runtime/model/ModelConnectionStore.js +13 -0
  24. package/dist/node/runtime/model/ModelHost.js +31 -1
  25. package/dist/node/runtime/model/ModelHostStream.js +51 -5
  26. package/dist/node/runtime/play/FileNativeContinuityCorrection.js +55 -45
  27. package/dist/node/runtime/play/FileNativePlayPresetStore.js +256 -238
  28. package/dist/node/runtime/play/FileNativePlayTimelineStore.js +118 -9
  29. package/dist/node/runtime/play/OrderedFollowups.js +71 -0
  30. package/dist/node/runtime/play/OrderedPlayPrompts.js +180 -0
  31. package/dist/node/runtime/play/PlayCallChain.js +814 -61
  32. package/dist/node/runtime/play/PlayContinuity.js +88 -0
  33. package/dist/node/runtime/play/PlayDocumentTools.js +205 -287
  34. package/dist/node/runtime/play/PlayFollowupRequests.js +34 -3
  35. package/dist/node/runtime/play/PlayPresetWorkbench.js +45 -54
  36. package/dist/node/runtime/play/PlayPromptRun.js +86 -0
  37. package/dist/node/runtime/prompt/FileNativePromptCompiler.js +670 -93
  38. package/dist/node/runtime/prompt/FileNativeToolRegistry.js +37 -21
  39. package/dist/node/runtime/prompt/PromptCompilationCodec.js +322 -0
  40. package/dist/node/runtime/prompt/WorldMaintenanceReport.js +251 -0
  41. package/dist/node/runtime/prompt/WorldMaterialCoverage.js +83 -0
  42. package/dist/node/runtime/prompt/WorldPromptDiagnostics.js +185 -0
  43. package/dist/node/runtime/setting/FileNativeSettingImprovementStore.js +1027 -0
  44. package/dist/node/runtime/setting/SettingAuthoringTransaction.js +1339 -0
  45. package/dist/node/runtime/setting/SettingImprovementSession.js +714 -0
  46. package/dist/node/runtime/world/FileNativeAuthorityV3.js +88 -1
  47. package/dist/node/runtime/world/FileNativeWorldStore.js +292 -27
  48. package/dist/node/runtime/world/PlayerViewBindings.js +30 -0
  49. package/dist/node/runtime/world/PlayerViewRenderer.js +27 -34
  50. package/dist/node/runtime/world/WorldDocumentMaintenance.js +174 -0
  51. package/dist/node/runtime/world/WorldDocumentStore.js +166 -96
  52. package/dist/node/runtime/world/WorldOperationCoordinator.js +103 -2
  53. package/dist/node/runtime/world-revision/FileNativeWorldRevisionStore.js +696 -0
  54. package/dist/node/runtime/world-revision/WorldRevisionAuthoringTransaction.js +380 -0
  55. package/dist/node/runtime/world-revision/WorldRevisionSession.js +709 -0
  56. package/dist/node/runtime/world-revision/WorldRevisionWorkspace.js +494 -0
  57. package/dist/node/server/ConversationStream.js +109 -0
  58. package/dist/node/server/createServer.js +5 -1
  59. package/dist/node/shared/artifact-payload-contract.js +123 -0
  60. package/dist/node/shared/default-play-prompts.js +25 -45
  61. package/dist/node/shared/default-preset-host.js +114 -138
  62. package/dist/node/shared/default-setting-improvement-prompt.js +52 -38
  63. package/dist/node/shared/display-regex.js +114 -0
  64. package/dist/node/shared/first-party-action-choices.js +1 -6
  65. package/dist/node/shared/first-party-player-view.js +1 -6
  66. package/dist/node/shared/ordered-author-prompts.js +64 -0
  67. package/dist/node/shared/ordered-followups.js +42 -0
  68. package/dist/node/shared/ordered-play-prompts.js +73 -0
  69. package/dist/web/assets/index-DG85hpWC.css +1 -0
  70. package/dist/web/assets/index-DfxvCjb6.js +1207 -0
  71. package/dist/web/index.html +2 -2
  72. package/package.json +1 -1
  73. package/dist/node/runtime/model/SettingAuthorStream.js +0 -21
  74. package/dist/node/runtime/setting/DocumentCandidateSettingImprovement.js +0 -1731
  75. package/dist/web/assets/index-D8YKfmNG.js +0 -1192
  76. 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) || staleAttempt;
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) || currentAttempt !== record.requestAttempt;
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) || staleAttempt) {
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 selected = selectProjection(active);
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(join(this.#root, "locks", identityHash(worldId)), () => this.#mutationContext.run(new Set([...(active ?? []), worldId]), action));
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.worldId === right.worldId &&
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
- ], ["key", "renderer", "rendererRevision"]) ||
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
+ }