narraeon 0.3.1 → 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 +188 -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 +447 -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 +44 -10
  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-D5VqDvjY.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,11 +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
+ "model.copy": { connectionId: "string", name: "string" },
24
25
  "model.select": { connectionId: "string" },
25
26
  "model.delete": { connectionId: "string" },
26
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" },
27
32
  "content.read": { packageId: "string" },
28
33
  "content.replace": { packageId: "string", files: "array" },
29
34
  "content.copy": { packageId: "string" },
@@ -31,25 +36,31 @@ const requiredFields = {
31
36
  "content.rename": { packageId: "string", name: "string" },
32
37
  "content.import": { archiveBase64: "string" },
33
38
  "content.export": { packageId: "string" },
34
- "setting-improvement.start": {
35
- 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": {
36
44
  packageId: "string",
37
- goal: "string",
38
- mode: "string",
39
- contextPaths: "array",
45
+ sessionId: "string",
40
46
  },
41
- "setting-improvement.confirm": { improvementId: "string" },
42
- "setting-improvement.progress": { improvementId: "string" },
43
- "setting-improvement.revise-plan": {
44
- improvementId: "string",
45
- 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",
46
56
  },
47
- "setting-improvement.revise-candidate": {
48
- improvementId: "string",
49
- feedback: "string",
57
+ "setting-improvement.message": {
58
+ packageId: "string",
59
+ requestId: "string",
60
+ message: "string",
61
+ continuation: "object",
50
62
  },
51
- "setting-improvement.apply": { improvementId: "string" },
52
- "setting-improvement.discard": { improvementId: "string" },
63
+ "setting-improvement.cancel": { sessionId: "string" },
53
64
  "play.create": { name: "string" },
54
65
  "play.copy": { presetId: "string" },
55
66
  "play.save": { presetId: "string", name: "string", files: "object" },
@@ -81,6 +92,43 @@ const requiredFields = {
81
92
  "world.control-draft.save": { worldId: "string", files: "array" },
82
93
  "world.control-draft.preview": { worldId: "string" },
83
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" },
84
132
  "world.derive": {
85
133
  operationId: "string",
86
134
  sourceWorldId: "string",
@@ -93,6 +141,7 @@ const requiredFields = {
93
141
  eventId: "number",
94
142
  replacementExchangeId: "string",
95
143
  replacementText: "string",
144
+ continuation: "string",
96
145
  },
97
146
  "play.chain.start": {
98
147
  worldId: "string",
@@ -118,7 +167,10 @@ const requiredFields = {
118
167
  chainId: "string",
119
168
  eventId: "number",
120
169
  },
170
+ "world.play-context.read": { worldId: "string" },
121
171
  "world.surface.read": { worldId: "string", surface: "string" },
172
+ "world.extensions.read": { worldId: "string" },
173
+ "world.extensions.set": { worldId: "string", key: "string", value: "string" },
122
174
  "world.play-decorations.read": { worldId: "string" },
123
175
  "correction.begin": { worldId: "string", operationId: "string" },
124
176
  "correction.read": { candidateId: "string", document: "string" },
@@ -152,17 +204,33 @@ function validateRequestFields(request) {
152
204
  if (!valid || (kind === "string" && value === "" && !allowsEmptyString))
153
205
  throw new V1ProtocolError("invalid_request", `${String(request.type)}.${field} is invalid`);
154
206
  }
155
- if (request.type === "preferences.save" &&
156
- request.locale !== "en" &&
157
- request.locale !== "zh-CN")
158
- 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
+ }
159
222
  if (request.type === "content.import") {
160
223
  const archiveBase64 = request.archiveBase64;
161
224
  if (archiveBase64.length > maxPortableContentArchiveBase64Characters ||
162
225
  archiveBase64.length % 4 !== 0 ||
163
226
  !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(archiveBase64))
164
227
  throw new V1ProtocolError("invalid_request", "content.import.archiveBase64 is not a supported content-package ZIP");
165
- 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");
166
234
  if (unexpectedFields.length > 0)
167
235
  throw new V1ProtocolError("invalid_request", `content.import contains unsupported fields: ${unexpectedFields.join(", ")}`);
168
236
  return;
@@ -193,6 +261,17 @@ function validateRequestFields(request) {
193
261
  !isRecord(request.structure))
194
262
  throw new V1ProtocolError("invalid_request", "play.save.structure must be a structured map");
195
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");
196
275
  if (request.presetId !== undefined &&
197
276
  (typeof request.presetId !== "string" || request.presetId === ""))
198
277
  throw new V1ProtocolError("invalid_request", "play.workbench.read.presetId is invalid");
@@ -229,18 +308,53 @@ function validateRequestFields(request) {
229
308
  if (request.type === "play.timeline.detail" &&
230
309
  (!Number.isSafeInteger(request.eventId) || Number(request.eventId) < 1))
231
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");
232
315
  if (request.type === "world.surface.read" &&
233
316
  request.surface !== "state" &&
234
317
  request.surface !== "control" &&
235
318
  request.surface !== "history" &&
236
319
  request.surface !== "runtime")
237
320
  throw new V1ProtocolError("invalid_request", "world.surface.read.surface is invalid");
238
- if (request.type !== "setting-improvement.start")
239
- return;
240
- if (request.mode !== "plan_first" && request.mode !== "direct_candidate")
241
- throw new V1ProtocolError("invalid_request", "setting-improvement.start.mode is invalid");
242
- if (!request.contextPaths.every((path) => typeof path === "string" && path.length > 0))
243
- 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");
244
358
  }
245
359
  const requestTypes = new Set([
246
360
  "workspace.read",
@@ -248,10 +362,15 @@ const requestTypes = new Set([
248
362
  "preferences.save",
249
363
  "model.read",
250
364
  "model.save",
365
+ "model.copy",
251
366
  "model.select",
252
367
  "model.delete",
253
368
  "model.models",
254
369
  "content.create",
370
+ "content.scripts.read",
371
+ "content.scripts.set",
372
+ "world.package-scripts.read",
373
+ "world.package-scripts.set",
255
374
  "content.read",
256
375
  "content.replace",
257
376
  "content.copy",
@@ -259,13 +378,15 @@ const requestTypes = new Set([
259
378
  "content.rename",
260
379
  "content.import",
261
380
  "content.export",
262
- "setting-improvement.start",
263
- "setting-improvement.confirm",
264
- "setting-improvement.progress",
265
- "setting-improvement.revise-plan",
266
- "setting-improvement.revise-candidate",
267
- "setting-improvement.apply",
268
- "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",
269
390
  "play.read",
270
391
  "play.create",
271
392
  "play.copy",
@@ -290,6 +411,18 @@ const requestTypes = new Set([
290
411
  "world.control-draft.save",
291
412
  "world.control-draft.preview",
292
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",
293
426
  "world.derive",
294
427
  "play.chain.revise-player",
295
428
  "play.chain.start",
@@ -298,7 +431,10 @@ const requestTypes = new Set([
298
431
  "play.chain.inspect",
299
432
  "play.timeline.page",
300
433
  "play.timeline.detail",
434
+ "world.play-context.read",
301
435
  "world.surface.read",
436
+ "world.extensions.read",
437
+ "world.extensions.set",
302
438
  "world.play-decorations.read",
303
439
  "correction.begin",
304
440
  "correction.read",
@@ -311,3 +447,21 @@ const requestTypes = new Set([
311
447
  function isRecord(value) {
312
448
  return typeof value === "object" && value !== null && !Array.isArray(value);
313
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
+ }