narraeon 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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-BXfelCi9.js +1207 -0
  70. package/dist/web/assets/index-BYnhUxlk.css +1 -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,5 +1,30 @@
1
1
  export const appLocales = ["en", "zh-CN"];
2
+ export const readingDensities = ["compact", "standard", "relaxed"];
2
3
  export const defaultAppLocale = "en";
4
+ export const defaultAppReadingPreferences = {
5
+ density: "compact",
6
+ fontSize: 17,
7
+ lineHeight: 1.7,
8
+ letterSpacing: 0.01,
9
+ measure: 48,
10
+ };
3
11
  export function isAppLocale(value) {
4
12
  return appLocales.includes(value);
5
13
  }
14
+ export function isAppReadingPreferences(value) {
15
+ if (typeof value !== "object" || value === null || Array.isArray(value))
16
+ return false;
17
+ const candidate = value;
18
+ return (Object.keys(candidate).length === 5 &&
19
+ readingDensities.includes(candidate.density) &&
20
+ isNumberInRange(candidate.fontSize, 15, 24) &&
21
+ isNumberInRange(candidate.lineHeight, 1.4, 2.4) &&
22
+ isNumberInRange(candidate.letterSpacing, 0, 0.12) &&
23
+ isNumberInRange(candidate.measure, 32, 72));
24
+ }
25
+ function isNumberInRange(value, minimum, maximum) {
26
+ return (typeof value === "number" &&
27
+ Number.isFinite(value) &&
28
+ value >= minimum &&
29
+ value <= maximum);
30
+ }
@@ -0,0 +1,67 @@
1
+ /** Only append-only live text uses deltas. Every other transition resets from
2
+ * the Runtime projection, including settlement, context changes and recovery. */
3
+ export function conversationUpdate(previous, next) {
4
+ if (previous?.kind === "play" &&
5
+ next.kind === "play" &&
6
+ previous.extensionsRevision === next.extensionsRevision &&
7
+ previous.value !== null &&
8
+ next.value !== null &&
9
+ previous.value.chainId === next.value.chainId) {
10
+ const candidate = structuredClone(previous.value);
11
+ const deltas = [];
12
+ for (const event of next.value.events) {
13
+ const old = candidate.events.find((item) => item.id === event.id);
14
+ if (event.kind !== "assistant" ||
15
+ event.status !== "streaming" ||
16
+ old?.kind !== "assistant")
17
+ continue;
18
+ for (const field of ["text", "reasoning", "toolFragment"]) {
19
+ const before = old[field] ?? "";
20
+ const after = event[field];
21
+ if (!after?.startsWith(before))
22
+ continue;
23
+ if (after !== before)
24
+ deltas.push({
25
+ eventId: event.id,
26
+ field,
27
+ text: after.slice(before.length),
28
+ });
29
+ old[field] = after;
30
+ }
31
+ }
32
+ candidate.updatedAt = next.value.updatedAt;
33
+ if (next.value.activeInvocation === undefined)
34
+ delete candidate.activeInvocation;
35
+ else
36
+ candidate.activeInvocation = next.value.activeInvocation;
37
+ if (JSON.stringify(candidate) === JSON.stringify(next.value)) {
38
+ return {
39
+ kind: "play_delta",
40
+ chainId: candidate.chainId,
41
+ updatedAt: candidate.updatedAt,
42
+ progress: candidate.activeInvocation ?? null,
43
+ deltas,
44
+ };
45
+ }
46
+ }
47
+ return { kind: "snapshot", value: next };
48
+ }
49
+ export function applyConversationUpdate(previous, update) {
50
+ if (update.kind === "snapshot")
51
+ return update.value;
52
+ if (previous?.kind !== "play" || previous.value?.chainId !== update.chainId)
53
+ throw new Error("Conversation observation needs a fresh snapshot");
54
+ const value = structuredClone(previous.value);
55
+ for (const delta of update.deltas) {
56
+ const event = value.events.find((item) => item.id === delta.eventId);
57
+ if (event?.kind !== "assistant" || event.status !== "streaming")
58
+ throw new Error("Conversation observation has an unknown live event");
59
+ event[delta.field] = `${event[delta.field] ?? ""}${delta.text}`;
60
+ }
61
+ value.updatedAt = update.updatedAt;
62
+ if (update.progress === null)
63
+ delete value.activeInvocation;
64
+ else
65
+ value.activeInvocation = update.progress;
66
+ return { ...previous, kind: "play", value };
67
+ }
@@ -19,12 +19,16 @@ export function parseV1Envelope(value) {
19
19
  return value;
20
20
  }
21
21
  const requiredFields = {
22
- "preferences.save": { locale: "string" },
22
+ "preferences.save": {},
23
23
  "model.save": { connection: "object" },
24
24
  "model.copy": { connectionId: "string", name: "string" },
25
25
  "model.select": { connectionId: "string" },
26
26
  "model.delete": { connectionId: "string" },
27
27
  "model.models": { provider: "string", baseUrl: "string" },
28
+ "content.scripts.read": { packageId: "string" },
29
+ "content.scripts.set": { packageId: "string", enabled: "boolean" },
30
+ "world.package-scripts.read": { worldId: "string" },
31
+ "world.package-scripts.set": { worldId: "string", enabled: "boolean" },
28
32
  "content.read": { packageId: "string" },
29
33
  "content.replace": { packageId: "string", files: "array" },
30
34
  "content.copy": { packageId: "string" },
@@ -32,25 +36,31 @@ const requiredFields = {
32
36
  "content.rename": { packageId: "string", name: "string" },
33
37
  "content.import": { archiveBase64: "string" },
34
38
  "content.export": { packageId: "string" },
35
- "setting-improvement.start": {
36
- improvementId: "string",
39
+ "setting-improvement.read": { packageId: "string" },
40
+ "setting-improvement.preview": { packageId: "string" },
41
+ "setting-improvement.status": { packageId: "string" },
42
+ "setting-improvement.overview": { packageId: "string" },
43
+ "setting-improvement.session.read": {
37
44
  packageId: "string",
38
- goal: "string",
39
- mode: "string",
40
- contextPaths: "array",
45
+ sessionId: "string",
41
46
  },
42
- "setting-improvement.confirm": { improvementId: "string" },
43
- "setting-improvement.progress": { improvementId: "string" },
44
- "setting-improvement.revise-plan": {
45
- improvementId: "string",
46
- feedback: "string",
47
+ "setting-improvement.session.delete": {
48
+ packageId: "string",
49
+ sessionId: "string",
50
+ },
51
+ "setting-improvement.rollback": {
52
+ packageId: "string",
53
+ sessionId: "string",
54
+ changeSetId: "string",
55
+ path: "string",
47
56
  },
48
- "setting-improvement.revise-candidate": {
49
- improvementId: "string",
50
- feedback: "string",
57
+ "setting-improvement.message": {
58
+ packageId: "string",
59
+ requestId: "string",
60
+ message: "string",
61
+ continuation: "object",
51
62
  },
52
- "setting-improvement.apply": { improvementId: "string" },
53
- "setting-improvement.discard": { improvementId: "string" },
63
+ "setting-improvement.cancel": { sessionId: "string" },
54
64
  "play.create": { name: "string" },
55
65
  "play.copy": { presetId: "string" },
56
66
  "play.save": { presetId: "string", name: "string", files: "object" },
@@ -82,6 +92,43 @@ const requiredFields = {
82
92
  "world.control-draft.save": { worldId: "string", files: "array" },
83
93
  "world.control-draft.preview": { worldId: "string" },
84
94
  "world.control-draft.apply": { worldId: "string" },
95
+ "world.revision.open": { worldId: "string" },
96
+ "world.revision.overview": { worldId: "string" },
97
+ "world.revision.preview": { worldId: "string" },
98
+ "world.revision.status": { worldId: "string" },
99
+ "world.revision.session.read": {
100
+ worldId: "string",
101
+ sessionId: "string",
102
+ },
103
+ "world.revision.session.delete": {
104
+ worldId: "string",
105
+ sessionId: "string",
106
+ },
107
+ "world.revision.message": {
108
+ worldId: "string",
109
+ requestId: "string",
110
+ message: "string",
111
+ continuation: "object",
112
+ },
113
+ "world.revision.cancel": { sessionId: "string" },
114
+ "world.revision.files.replace": {
115
+ worldId: "string",
116
+ epochId: "string",
117
+ expectedRevision: "string",
118
+ files: "array",
119
+ },
120
+ "world.revision.rollback": {
121
+ worldId: "string",
122
+ epochId: "string",
123
+ changeSetId: "string",
124
+ path: "string",
125
+ },
126
+ "world.revision.apply": {
127
+ worldId: "string",
128
+ epochId: "string",
129
+ expectedRevision: "string",
130
+ },
131
+ "world.revision.discard": { worldId: "string", epochId: "string" },
85
132
  "world.derive": {
86
133
  operationId: "string",
87
134
  sourceWorldId: "string",
@@ -94,6 +141,7 @@ const requiredFields = {
94
141
  eventId: "number",
95
142
  replacementExchangeId: "string",
96
143
  replacementText: "string",
144
+ continuation: "string",
97
145
  },
98
146
  "play.chain.start": {
99
147
  worldId: "string",
@@ -119,7 +167,10 @@ const requiredFields = {
119
167
  chainId: "string",
120
168
  eventId: "number",
121
169
  },
170
+ "world.play-context.read": { worldId: "string" },
122
171
  "world.surface.read": { worldId: "string", surface: "string" },
172
+ "world.extensions.read": { worldId: "string" },
173
+ "world.extensions.set": { worldId: "string", key: "string", value: "string" },
123
174
  "world.play-decorations.read": { worldId: "string" },
124
175
  "correction.begin": { worldId: "string", operationId: "string" },
125
176
  "correction.read": { candidateId: "string", document: "string" },
@@ -153,17 +204,33 @@ function validateRequestFields(request) {
153
204
  if (!valid || (kind === "string" && value === "" && !allowsEmptyString))
154
205
  throw new V1ProtocolError("invalid_request", `${String(request.type)}.${field} is invalid`);
155
206
  }
156
- if (request.type === "preferences.save" &&
157
- request.locale !== "en" &&
158
- request.locale !== "zh-CN")
159
- throw new V1ProtocolError("invalid_request", "preferences.save.locale must be en or zh-CN");
207
+ if (request.type === "world.extensions.set" &&
208
+ !["on", "off", "default"].includes(String(request.value)))
209
+ throw new V1ProtocolError("invalid_request", "Invalid world extension choice");
210
+ if (request.type === "preferences.save") {
211
+ if ((request.locale === undefined && request.reading === undefined) ||
212
+ Object.keys(request).some((key) => key !== "type" && key !== "locale" && key !== "reading"))
213
+ throw new V1ProtocolError("invalid_request", "preferences.save must update locale or reading preferences");
214
+ if (request.locale !== undefined &&
215
+ request.locale !== "en" &&
216
+ request.locale !== "zh-CN")
217
+ throw new V1ProtocolError("invalid_request", "preferences.save.locale must be en or zh-CN");
218
+ if (request.reading !== undefined &&
219
+ !isValidReadingPreferences(request.reading))
220
+ throw new V1ProtocolError("invalid_request", "preferences.save.reading is invalid");
221
+ }
160
222
  if (request.type === "content.import") {
161
223
  const archiveBase64 = request.archiveBase64;
162
224
  if (archiveBase64.length > maxPortableContentArchiveBase64Characters ||
163
225
  archiveBase64.length % 4 !== 0 ||
164
226
  !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(archiveBase64))
165
227
  throw new V1ProtocolError("invalid_request", "content.import.archiveBase64 is not a supported content-package ZIP");
166
- const unexpectedFields = Object.keys(request).filter((field) => field !== "type" && field !== "archiveBase64");
228
+ if (request.title !== undefined &&
229
+ (typeof request.title !== "string" ||
230
+ request.title.trim().length === 0 ||
231
+ !/^[^\r\n]{1,160}$/u.test(request.title.trim())))
232
+ throw new V1ProtocolError("invalid_request", "content.import.title is invalid");
233
+ const unexpectedFields = Object.keys(request).filter((field) => field !== "type" && field !== "archiveBase64" && field !== "title");
167
234
  if (unexpectedFields.length > 0)
168
235
  throw new V1ProtocolError("invalid_request", `content.import contains unsupported fields: ${unexpectedFields.join(", ")}`);
169
236
  return;
@@ -194,6 +261,17 @@ function validateRequestFields(request) {
194
261
  !isRecord(request.structure))
195
262
  throw new V1ProtocolError("invalid_request", "play.save.structure must be a structured map");
196
263
  if (request.type === "play.workbench.read") {
264
+ if (request.draft !== undefined &&
265
+ (!isRecord(request.draft) ||
266
+ !isRecord(request.draft.files) ||
267
+ !Object.values(request.draft.files).every((value) => typeof value === "string") ||
268
+ (request.draft.structure !== undefined &&
269
+ !isRecord(request.draft.structure)) ||
270
+ request.presetId === undefined))
271
+ throw new V1ProtocolError("invalid_request", "play.workbench.read.draft requires presetId, text files and an optional structured map");
272
+ if (request.worldId !== undefined &&
273
+ (typeof request.worldId !== "string" || request.worldId === ""))
274
+ throw new V1ProtocolError("invalid_request", "play.workbench.read.worldId is invalid");
197
275
  if (request.presetId !== undefined &&
198
276
  (typeof request.presetId !== "string" || request.presetId === ""))
199
277
  throw new V1ProtocolError("invalid_request", "play.workbench.read.presetId is invalid");
@@ -230,18 +308,53 @@ function validateRequestFields(request) {
230
308
  if (request.type === "play.timeline.detail" &&
231
309
  (!Number.isSafeInteger(request.eventId) || Number(request.eventId) < 1))
232
310
  throw new V1ProtocolError("invalid_request", "play.timeline.detail.eventId is invalid");
311
+ if (request.type === "play.chain.revise-player" &&
312
+ request.continuation !== "continue_context" &&
313
+ request.continuation !== "fresh_context")
314
+ throw new V1ProtocolError("invalid_request", "play.chain.revise-player.continuation is invalid");
233
315
  if (request.type === "world.surface.read" &&
234
316
  request.surface !== "state" &&
235
317
  request.surface !== "control" &&
236
318
  request.surface !== "history" &&
237
319
  request.surface !== "runtime")
238
320
  throw new V1ProtocolError("invalid_request", "world.surface.read.surface is invalid");
239
- if (request.type !== "setting-improvement.start")
240
- return;
241
- if (request.mode !== "plan_first" && request.mode !== "direct_candidate")
242
- throw new V1ProtocolError("invalid_request", "setting-improvement.start.mode is invalid");
243
- if (!request.contextPaths.every((path) => typeof path === "string" && path.length > 0))
244
- throw new V1ProtocolError("invalid_request", "setting-improvement.start.contextPaths is invalid");
321
+ if ((request.type === "setting-improvement.preview" ||
322
+ request.type === "world.revision.preview") &&
323
+ request.sessionId !== undefined &&
324
+ (typeof request.sessionId !== "string" || request.sessionId.length === 0))
325
+ throw new V1ProtocolError("invalid_request", "Authoring preview sessionId is invalid");
326
+ if (request.type === "world.revision.status" &&
327
+ request.sessionId !== undefined &&
328
+ (typeof request.sessionId !== "string" || request.sessionId.length === 0))
329
+ throw new V1ProtocolError("invalid_request", "world.revision.status.sessionId is invalid");
330
+ if (request.type === "setting-improvement.message" ||
331
+ request.type === "world.revision.message") {
332
+ const continuation = request.continuation;
333
+ if (!isRecord(continuation) ||
334
+ (continuation.kind !== "fresh_context" &&
335
+ continuation.kind !== "continue_context") ||
336
+ (continuation.kind === "continue_context" &&
337
+ typeof continuation.sessionId !== "string") ||
338
+ (continuation.kind === "fresh_context" && "sessionId" in continuation))
339
+ throw new V1ProtocolError("invalid_request", `${request.type}.continuation is invalid`);
340
+ }
341
+ if (request.type === "setting-improvement.rollback" &&
342
+ (typeof request.changeSetId !== "string" ||
343
+ !/^change-set:(0|[1-9][0-9]*)$/u.test(request.changeSetId)))
344
+ throw new V1ProtocolError("invalid_request", "setting-improvement.rollback.changeSetId is invalid");
345
+ if ((request.type === "world.revision.files.replace" ||
346
+ request.type === "world.revision.rollback" ||
347
+ request.type === "world.revision.apply" ||
348
+ request.type === "world.revision.discard") &&
349
+ (typeof request.epochId !== "string" ||
350
+ !/^revision-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u.test(request.epochId)))
351
+ throw new V1ProtocolError("invalid_request", `${request.type}.epochId is invalid`);
352
+ if (request.type === "world.revision.rollback" &&
353
+ (typeof request.changeSetId !== "string" ||
354
+ !/^change-set:[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u.test(request.changeSetId) ||
355
+ typeof request.path !== "string" ||
356
+ request.path.length === 0))
357
+ throw new V1ProtocolError("invalid_request", "world.revision.rollback target is invalid");
245
358
  }
246
359
  const requestTypes = new Set([
247
360
  "workspace.read",
@@ -254,6 +367,10 @@ const requestTypes = new Set([
254
367
  "model.delete",
255
368
  "model.models",
256
369
  "content.create",
370
+ "content.scripts.read",
371
+ "content.scripts.set",
372
+ "world.package-scripts.read",
373
+ "world.package-scripts.set",
257
374
  "content.read",
258
375
  "content.replace",
259
376
  "content.copy",
@@ -261,13 +378,15 @@ const requestTypes = new Set([
261
378
  "content.rename",
262
379
  "content.import",
263
380
  "content.export",
264
- "setting-improvement.start",
265
- "setting-improvement.confirm",
266
- "setting-improvement.progress",
267
- "setting-improvement.revise-plan",
268
- "setting-improvement.revise-candidate",
269
- "setting-improvement.apply",
270
- "setting-improvement.discard",
381
+ "setting-improvement.read",
382
+ "setting-improvement.preview",
383
+ "setting-improvement.status",
384
+ "setting-improvement.overview",
385
+ "setting-improvement.session.read",
386
+ "setting-improvement.session.delete",
387
+ "setting-improvement.rollback",
388
+ "setting-improvement.message",
389
+ "setting-improvement.cancel",
271
390
  "play.read",
272
391
  "play.create",
273
392
  "play.copy",
@@ -292,6 +411,18 @@ const requestTypes = new Set([
292
411
  "world.control-draft.save",
293
412
  "world.control-draft.preview",
294
413
  "world.control-draft.apply",
414
+ "world.revision.open",
415
+ "world.revision.overview",
416
+ "world.revision.preview",
417
+ "world.revision.status",
418
+ "world.revision.session.read",
419
+ "world.revision.session.delete",
420
+ "world.revision.message",
421
+ "world.revision.cancel",
422
+ "world.revision.files.replace",
423
+ "world.revision.rollback",
424
+ "world.revision.apply",
425
+ "world.revision.discard",
295
426
  "world.derive",
296
427
  "play.chain.revise-player",
297
428
  "play.chain.start",
@@ -300,7 +431,10 @@ const requestTypes = new Set([
300
431
  "play.chain.inspect",
301
432
  "play.timeline.page",
302
433
  "play.timeline.detail",
434
+ "world.play-context.read",
303
435
  "world.surface.read",
436
+ "world.extensions.read",
437
+ "world.extensions.set",
304
438
  "world.play-decorations.read",
305
439
  "correction.begin",
306
440
  "correction.read",
@@ -313,3 +447,21 @@ const requestTypes = new Set([
313
447
  function isRecord(value) {
314
448
  return typeof value === "object" && value !== null && !Array.isArray(value);
315
449
  }
450
+ function isValidReadingPreferences(value) {
451
+ if (!isRecord(value))
452
+ return false;
453
+ return (Object.keys(value).length === 5 &&
454
+ (value.density === "compact" ||
455
+ value.density === "standard" ||
456
+ value.density === "relaxed") &&
457
+ isFiniteRange(value.fontSize, 15, 24) &&
458
+ isFiniteRange(value.lineHeight, 1.4, 2.4) &&
459
+ isFiniteRange(value.letterSpacing, 0, 0.12) &&
460
+ isFiniteRange(value.measure, 32, 72));
461
+ }
462
+ function isFiniteRange(value, minimum, maximum) {
463
+ return (typeof value === "number" &&
464
+ Number.isFinite(value) &&
465
+ value >= minimum &&
466
+ value <= maximum);
467
+ }
@@ -0,0 +1,16 @@
1
+ export function isWorldExtensionControl(value) {
2
+ return (typeof value === "object" &&
3
+ value !== null &&
4
+ "key" in value &&
5
+ typeof value.key === "string" &&
6
+ value.key.length > 0 &&
7
+ "generation" in value &&
8
+ Number.isSafeInteger(value.generation) &&
9
+ Number(value.generation) >= 0 &&
10
+ Object.keys(value).length === 2);
11
+ }
12
+ export function requestControlKey(presetId, requestId) {
13
+ return requestId.startsWith("package:")
14
+ ? requestId
15
+ : `preset:${presetId}:request:${requestId}`;
16
+ }
@@ -0,0 +1,77 @@
1
+ export function isWorldPromptMaintenance(value) {
2
+ if (!record(value) ||
3
+ value.unit !== "utf8_bytes" ||
4
+ (value.unavailableReason !== undefined &&
5
+ typeof value.unavailableReason !== "string") ||
6
+ ![
7
+ "worldMaterialsBytes",
8
+ "checkpointHistoryBytes",
9
+ "authorInstructionBytes",
10
+ "runtimeInstructionBytes",
11
+ "toolDefinitionBytes",
12
+ ].every((key) => count(value[key])) ||
13
+ !Array.isArray(value.documents) ||
14
+ !Array.isArray(value.slots))
15
+ return false;
16
+ return (value.documents.every((item) => record(item) &&
17
+ typeof item.shortRef === "string" &&
18
+ typeof item.title === "string" &&
19
+ count(item.bodyBytes) &&
20
+ count(item.injectedBytes) &&
21
+ ["world_origin", "document_creation", "unavailable"].includes(String(item.baselineKind)) &&
22
+ nullableCount(item.baselineBytes) &&
23
+ nullableNumber(item.growthBytes) &&
24
+ nullableNumber(item.growthRatio) &&
25
+ nullableCount(item.advisoryBytes) &&
26
+ typeof item.overAdvisory === "boolean" &&
27
+ position(item.lastBodyWrite) &&
28
+ position(item.lastMetadataWrite) &&
29
+ (item.observedContexts === undefined || count(item.observedContexts)) &&
30
+ count(item.bodyChangedContexts) &&
31
+ count(item.metadataChangedContexts)) &&
32
+ value.slots.every((slot) => record(slot) &&
33
+ typeof slot.id === "string" &&
34
+ count(slot.bytes) &&
35
+ nullableCount(slot.advisoryBytes) &&
36
+ typeof slot.overAdvisory === "boolean" &&
37
+ Array.isArray(slot.documents) &&
38
+ slot.documents.every((ref) => typeof ref === "string")));
39
+ }
40
+ function position(value) {
41
+ return (value === null ||
42
+ (record(value) &&
43
+ ["world_origin", "play", "correction", "history"].includes(String(value.kind)) &&
44
+ nullableCount(value.context) &&
45
+ count(value.round) &&
46
+ (value.worldTime === null || typeof value.worldTime === "string")));
47
+ }
48
+ function record(value) {
49
+ return value !== null && typeof value === "object" && !Array.isArray(value);
50
+ }
51
+ function count(value) {
52
+ return Number.isSafeInteger(value) && Number(value) >= 0;
53
+ }
54
+ function nullableCount(value) {
55
+ return value === null || count(value);
56
+ }
57
+ function nullableNumber(value) {
58
+ return (value === null || (typeof value === "number" && Number.isFinite(value)));
59
+ }
60
+ /** Display only recorded provenance; a write position is not character knowledge. */
61
+ export function worldWritePositionText(position, locale) {
62
+ const zh = locale === "zh-CN";
63
+ if (position === null)
64
+ return zh ? "未记录" : "Not recorded";
65
+ const place = position.kind === "world_origin"
66
+ ? zh
67
+ ? "世界创建时"
68
+ : "World creation"
69
+ : position.kind === "play" && position.context !== null
70
+ ? zh
71
+ ? `上下文 ${position.context} · 第 ${position.round} 回合`
72
+ : `Context ${position.context} · round ${position.round}`
73
+ : zh
74
+ ? `${position.kind === "correction" ? "连续性修订" : "历史提交"} · 玩家输入位置 ${position.round}`
75
+ : `${position.kind === "correction" ? "Continuity correction" : "History commit"} · player input position ${position.round}`;
76
+ return `${place} · ${zh ? "写入时世界时间" : "World time at write"}: ${position.worldTime ?? (zh ? "未记录" : "not recorded")}`;
77
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ /** Observation failure never participates in a Runtime mutation. */
2
+ export class ConversationChanges {
3
+ #listeners = new Map();
4
+ subscribe(scope, listener) {
5
+ const listeners = this.#listeners.get(scope) ?? new Set();
6
+ listeners.add(listener);
7
+ this.#listeners.set(scope, listeners);
8
+ return () => {
9
+ listeners.delete(listener);
10
+ if (listeners.size === 0)
11
+ this.#listeners.delete(scope);
12
+ };
13
+ }
14
+ publish(scope, durable = true) {
15
+ for (const listener of this.#listeners.get(scope) ?? []) {
16
+ try {
17
+ listener(durable);
18
+ }
19
+ catch {
20
+ /* Observers cannot affect settlement. */
21
+ }
22
+ }
23
+ }
24
+ }