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
@@ -0,0 +1,696 @@
1
+ import { isPackageFollowupControlPath } from "../content/PackageFollowups.js";
2
+ import { validAuthoringRequests, } from "../authoring/AuthoringRequestSnapshot.js";
3
+ import { createHash, randomUUID } from "node:crypto";
4
+ import { mkdir, open, readFile, readdir, rename, rm } from "node:fs/promises";
5
+ import { dirname, join, resolve } from "node:path";
6
+ import { contentTreeFingerprint } from "../content/ContentTreeFingerprint.js";
7
+ import { isPlayPresetBinding, } from "../play/FileNativePlayPresetStore.js";
8
+ const uuidV4Pattern = "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}";
9
+ const epochIdPattern = new RegExp(`^revision-${uuidV4Pattern}$`, "u");
10
+ const sessionIdPattern = new RegExp(`^world-session-${uuidV4Pattern}$`, "u");
11
+ const changeSetIdPattern = new RegExp(`^change-set:${uuidV4Pattern}$`, "u");
12
+ const fingerprintPattern = /^sha256:[0-9a-f]{64}$/u;
13
+ const treeRevisionPattern = /^[0-9a-f]{64}$/u;
14
+ const epochName = new RegExp(`^revision-${uuidV4Pattern}\\.json$`, "u");
15
+ const sessionName = new RegExp(`^world-session-${uuidV4Pattern}\\.json$`, "u");
16
+ /** File-native persistence for revision worktrees and their conversations. */
17
+ export class FileNativeWorldRevisionStore {
18
+ #epochsRoot;
19
+ #sessionsRoot;
20
+ #mutationTail = Promise.resolve();
21
+ constructor(dataRoot) {
22
+ const root = join(resolve(dataRoot), "world-revisions");
23
+ this.#epochsRoot = join(root, "epochs");
24
+ this.#sessionsRoot = join(root, "sessions");
25
+ }
26
+ createEpochId() {
27
+ return `revision-${randomUUID()}`;
28
+ }
29
+ createSessionId() {
30
+ return `world-session-${randomUUID()}`;
31
+ }
32
+ createChangeSetId() {
33
+ return `change-set:${randomUUID()}`;
34
+ }
35
+ readEpoch(epochId) {
36
+ return this.#afterMutations(() => this.#readEpoch(epochId));
37
+ }
38
+ async readEpochIfPresent(epochId) {
39
+ try {
40
+ return await this.readEpoch(epochId);
41
+ }
42
+ catch (error) {
43
+ if (isNodeError(error) && error.code === "ENOENT")
44
+ return null;
45
+ throw error;
46
+ }
47
+ }
48
+ saveEpoch(epoch) {
49
+ return this.#mutate(async () => {
50
+ validateEpoch(epoch);
51
+ await publishJson(this.#epochPath(epoch.epochId), epoch);
52
+ });
53
+ }
54
+ async listEpochs(worldId) {
55
+ return this.#afterMutations(async () => {
56
+ const entries = await readdir(this.#epochsRoot, {
57
+ withFileTypes: true,
58
+ }).catch((error) => {
59
+ if (isNodeError(error) && error.code === "ENOENT")
60
+ return [];
61
+ throw error;
62
+ });
63
+ const epochs = [];
64
+ for (const entry of entries) {
65
+ if (!entry.isFile() || !epochName.test(entry.name))
66
+ continue;
67
+ const epoch = await this.#readEpoch(entry.name.slice(0, -5));
68
+ if (epoch.worldId === worldId)
69
+ epochs.push(epoch);
70
+ }
71
+ return epochs.sort(compareUpdated);
72
+ });
73
+ }
74
+ readSession(sessionId) {
75
+ return this.#afterMutations(() => this.#readSession(sessionId));
76
+ }
77
+ saveSession(session) {
78
+ return this.#mutate(async () => {
79
+ validateSession(session);
80
+ await publishJson(this.#sessionPath(session.sessionId), session);
81
+ });
82
+ }
83
+ async listSessions(worldId) {
84
+ return this.#afterMutations(async () => {
85
+ const entries = await readdir(this.#sessionsRoot, {
86
+ withFileTypes: true,
87
+ }).catch((error) => {
88
+ if (isNodeError(error) && error.code === "ENOENT")
89
+ return [];
90
+ throw error;
91
+ });
92
+ const sessions = [];
93
+ for (const entry of entries) {
94
+ if (!entry.isFile() || !sessionName.test(entry.name))
95
+ continue;
96
+ const session = await this.#readSession(entry.name.slice(0, -5));
97
+ if (session.worldId === worldId)
98
+ sessions.push(session);
99
+ }
100
+ return sessions.sort(compareUpdated);
101
+ });
102
+ }
103
+ async findByCreationRequest(worldId, requestId) {
104
+ return ((await this.listSessions(worldId)).find(({ creationRequestId }) => creationRequestId === requestId) ?? null);
105
+ }
106
+ deleteSession(worldId, sessionId) {
107
+ return this.#mutate(async () => {
108
+ const session = await this.#readSession(sessionId);
109
+ if (session.worldId !== worldId)
110
+ throw new Error("World-revision conversation belongs to another world");
111
+ await rm(this.#sessionPath(sessionId));
112
+ await syncDirectory(this.#sessionsRoot);
113
+ });
114
+ }
115
+ async #readEpoch(epochId) {
116
+ assertEpochId(epochId);
117
+ const value = JSON.parse(await readFile(this.#epochPath(epochId), "utf8"));
118
+ const epoch = validateEpoch(value);
119
+ if (epoch.epochId !== epochId)
120
+ throw new Error("World-revision epoch identity does not match its file");
121
+ return epoch;
122
+ }
123
+ async #readSession(sessionId) {
124
+ assertSessionId(sessionId);
125
+ const value = JSON.parse(await readFile(this.#sessionPath(sessionId), "utf8"));
126
+ const session = validateSession(value);
127
+ if (session.sessionId !== sessionId)
128
+ throw new Error("World-revision conversation identity does not match its file");
129
+ return session;
130
+ }
131
+ async #afterMutations(read) {
132
+ await this.#mutationTail;
133
+ return read();
134
+ }
135
+ async #mutate(operation) {
136
+ const previous = this.#mutationTail;
137
+ let release = () => undefined;
138
+ this.#mutationTail = new Promise((resolveMutation) => {
139
+ release = resolveMutation;
140
+ });
141
+ await previous;
142
+ try {
143
+ return await operation();
144
+ }
145
+ finally {
146
+ release();
147
+ }
148
+ }
149
+ #epochPath(epochId) {
150
+ assertEpochId(epochId);
151
+ return join(this.#epochsRoot, `${epochId}.json`);
152
+ }
153
+ #sessionPath(sessionId) {
154
+ assertSessionId(sessionId);
155
+ return join(this.#sessionsRoot, `${sessionId}.json`);
156
+ }
157
+ }
158
+ function validateEpoch(value) {
159
+ if (!isRecord(value) ||
160
+ !hasExactKeys(value, [
161
+ "schemaVersion",
162
+ "epochId",
163
+ "worldId",
164
+ "lifecycle",
165
+ "baseHead",
166
+ "baseControlFingerprint",
167
+ "baseFiles",
168
+ "files",
169
+ "revision",
170
+ "changes",
171
+ "apply",
172
+ "createdAt",
173
+ "updatedAt",
174
+ "finishedAt",
175
+ ]) ||
176
+ value.schemaVersion !== 1 ||
177
+ typeof value.epochId !== "string" ||
178
+ !nonEmptyString(value.worldId) ||
179
+ !["active", "applying", "applied", "discarded"].includes(String(value.lifecycle)) ||
180
+ !nonEmptyString(value.baseHead) ||
181
+ typeof value.baseControlFingerprint !== "string" ||
182
+ !fingerprintPattern.test(value.baseControlFingerprint) ||
183
+ !validFiles(value.baseFiles) ||
184
+ revisionControlFingerprint(value.baseFiles) !==
185
+ value.baseControlFingerprint ||
186
+ !validFiles(value.files) ||
187
+ typeof value.revision !== "string" ||
188
+ !treeRevisionPattern.test(value.revision) ||
189
+ contentTreeFingerprint(value.files) !== value.revision ||
190
+ !Array.isArray(value.changes) ||
191
+ !value.changes.every(validChangeSet) ||
192
+ new Set(value.changes.flatMap((change) => isRecord(change) && typeof change.changeSetId === "string"
193
+ ? [change.changeSetId]
194
+ : [])).size !== value.changes.length ||
195
+ !validApply(value.apply) ||
196
+ !finite(value.createdAt) ||
197
+ !finite(value.updatedAt) ||
198
+ value.updatedAt < value.createdAt ||
199
+ (value.finishedAt !== null &&
200
+ (!finite(value.finishedAt) || value.finishedAt < value.createdAt)) ||
201
+ !validEpochLifecycle(value))
202
+ throw new Error("World-revision epoch does not match its durable schema");
203
+ assertEpochId(value.epochId);
204
+ return structuredClone(value);
205
+ }
206
+ function validateSession(value) {
207
+ if (!isRecord(value) ||
208
+ !hasExactKeys(value, [
209
+ "schemaVersion",
210
+ "sessionId",
211
+ "worldId",
212
+ "worldTitle",
213
+ "epochId",
214
+ "locale",
215
+ "runStatus",
216
+ "createdAt",
217
+ "updatedAt",
218
+ "creationRequestId",
219
+ "bootstrap",
220
+ "modelBinding",
221
+ "playPreset",
222
+ "modelItems",
223
+ "messages",
224
+ "usage",
225
+ "exchange",
226
+ "toolCalls",
227
+ "activeRequestId",
228
+ "completedRequestIds",
229
+ "lastFailure",
230
+ "authorization",
231
+ "pendingSettlement",
232
+ ], ["requests"]) ||
233
+ (value.schemaVersion !== 1 && value.schemaVersion !== 2) ||
234
+ (value.schemaVersion === 1
235
+ ? value.requests !== undefined
236
+ : !validAuthoringRequests(value.requests, validPromptCompilation, {
237
+ modelItems: value.modelItems,
238
+ activeRequestId: value.activeRequestId,
239
+ completedRequestIds: value.completedRequestIds,
240
+ })) ||
241
+ typeof value.sessionId !== "string" ||
242
+ !nonEmptyString(value.worldId) ||
243
+ typeof value.worldTitle !== "string" ||
244
+ value.worldTitle.trim().length === 0 ||
245
+ /[\r\n\0]/u.test(value.worldTitle) ||
246
+ typeof value.epochId !== "string" ||
247
+ (value.locale !== "en" && value.locale !== "zh-CN") ||
248
+ !["ready", "running", "interrupted"].includes(String(value.runStatus)) ||
249
+ !finite(value.createdAt) ||
250
+ !finite(value.updatedAt) ||
251
+ value.updatedAt < value.createdAt ||
252
+ !nonEmptyString(value.creationRequestId) ||
253
+ !validPromptCompilation(value.bootstrap) ||
254
+ !validModelBinding(value.modelBinding) ||
255
+ !isPlayPresetBinding(value.playPreset) ||
256
+ !Array.isArray(value.modelItems) ||
257
+ !value.modelItems.every(validModelItem) ||
258
+ !Array.isArray(value.messages) ||
259
+ !value.messages.every(validMessage) ||
260
+ !validModelUsage(value.usage) ||
261
+ !count(value.exchange) ||
262
+ !count(value.toolCalls) ||
263
+ (value.activeRequestId !== null &&
264
+ typeof value.activeRequestId !== "string") ||
265
+ (value.runStatus === "running") !== (value.activeRequestId !== null) ||
266
+ !Array.isArray(value.completedRequestIds) ||
267
+ !value.completedRequestIds.every((item) => typeof item === "string") ||
268
+ (value.lastFailure !== null && typeof value.lastFailure !== "string") ||
269
+ !validAuthorization(value.authorization) ||
270
+ !validPendingSettlement(value.pendingSettlement) ||
271
+ !validPendingForSession(value.pendingSettlement, value.epochId, value.runStatus) ||
272
+ !validPendingAssistant(value.pendingSettlement, value.modelItems))
273
+ throw new Error("World-revision conversation does not match its durable schema");
274
+ assertSessionId(value.sessionId);
275
+ assertEpochId(value.epochId);
276
+ return structuredClone(value);
277
+ }
278
+ function validFiles(value) {
279
+ if (!Array.isArray(value))
280
+ return false;
281
+ const paths = new Set();
282
+ for (const file of value) {
283
+ if (!isRecord(file) ||
284
+ !hasExactKeys(file, ["path", "contents"], ["encoding"]) ||
285
+ typeof file.path !== "string" ||
286
+ !validRevisionPath(file.path) ||
287
+ typeof file.contents !== "string" ||
288
+ file.encoding !== undefined)
289
+ return false;
290
+ const portablePath = file.path.normalize("NFC").toLocaleLowerCase("en-US");
291
+ if (paths.has(portablePath))
292
+ return false;
293
+ paths.add(portablePath);
294
+ }
295
+ return true;
296
+ }
297
+ function validChangeSet(value) {
298
+ return (isRecord(value) &&
299
+ hasExactKeys(value, ["changeSetId", "source", "createdAt", "changes"]) &&
300
+ typeof value.changeSetId === "string" &&
301
+ changeSetIdPattern.test(value.changeSetId) &&
302
+ (value.source === "manual" ||
303
+ value.source === "ai" ||
304
+ value.source === "rollback") &&
305
+ finite(value.createdAt) &&
306
+ Array.isArray(value.changes) &&
307
+ value.changes.length > 0 &&
308
+ value.changes.every(validDiff));
309
+ }
310
+ function validApply(value) {
311
+ return (value === null ||
312
+ (isRecord(value) &&
313
+ hasExactKeys(value, [
314
+ "operationId",
315
+ "expectedRevision",
316
+ "phase",
317
+ "committedHead",
318
+ ]) &&
319
+ nonEmptyString(value.operationId) &&
320
+ typeof value.expectedRevision === "string" &&
321
+ treeRevisionPattern.test(value.expectedRevision) &&
322
+ (value.phase === "prepared" ||
323
+ value.phase === "state_committed" ||
324
+ value.phase === "control_published") &&
325
+ (value.phase === "prepared"
326
+ ? value.committedHead === null
327
+ : nonEmptyString(value.committedHead))));
328
+ }
329
+ function validEpochLifecycle(value) {
330
+ if (value.lifecycle === "active")
331
+ return value.apply === null && value.finishedAt === null;
332
+ if (value.lifecycle === "applying")
333
+ return (value.apply !== null &&
334
+ value.finishedAt === null &&
335
+ isRecord(value.apply) &&
336
+ value.apply.expectedRevision === value.revision);
337
+ if (value.lifecycle === "applied")
338
+ return (isRecord(value.apply) &&
339
+ value.apply.phase === "control_published" &&
340
+ value.apply.expectedRevision === value.revision &&
341
+ value.finishedAt !== null);
342
+ return (value.lifecycle === "discarded" &&
343
+ value.apply === null &&
344
+ value.finishedAt !== null);
345
+ }
346
+ function validDiff(value) {
347
+ if (!isRecord(value) ||
348
+ !hasExactKeys(value, ["path", "kind", "before", "after"]) ||
349
+ typeof value.path !== "string" ||
350
+ !validRevisionPath(value.path) ||
351
+ (value.before !== null && typeof value.before !== "string") ||
352
+ (value.after !== null && typeof value.after !== "string"))
353
+ return false;
354
+ if (value.kind === "create")
355
+ return value.before === null && typeof value.after === "string";
356
+ if (value.kind === "delete")
357
+ return typeof value.before === "string" && value.after === null;
358
+ return (value.kind === "modify" &&
359
+ typeof value.before === "string" &&
360
+ typeof value.after === "string" &&
361
+ value.before !== value.after);
362
+ }
363
+ function validMessage(value) {
364
+ return (isRecord(value) &&
365
+ hasExactKeys(value, ["id", "role", "text", "createdAt"]) &&
366
+ nonEmptyString(value.id) &&
367
+ (value.role === "user" || value.role === "assistant") &&
368
+ typeof value.text === "string" &&
369
+ finite(value.createdAt));
370
+ }
371
+ function validModelItem(value) {
372
+ if (!isRecord(value))
373
+ return false;
374
+ if (value.kind === "prompt_delta")
375
+ return (hasExactKeys(value, ["kind", "logicalMessages"]) &&
376
+ validLogicalMessages(value.logicalMessages));
377
+ if (value.kind === "player" || value.kind === "user")
378
+ return (hasExactKeys(value, ["kind", "text"]) && typeof value.text === "string");
379
+ if (value.kind === "assistant")
380
+ return (hasExactKeys(value, ["kind", "text", "providerState", "toolCalls"], ["reasoningContent"]) &&
381
+ typeof value.text === "string" &&
382
+ (value.reasoningContent === undefined ||
383
+ typeof value.reasoningContent === "string") &&
384
+ validProviderState(value.providerState) &&
385
+ Array.isArray(value.toolCalls) &&
386
+ value.toolCalls.every(validModelToolCall));
387
+ return (value.kind === "tool" &&
388
+ hasExactKeys(value, ["kind", "toolCallId", "markdown", "changes", "changeSetId"], ["isError"]) &&
389
+ nonEmptyString(value.toolCallId) &&
390
+ typeof value.markdown === "string" &&
391
+ (value.isError === undefined || typeof value.isError === "boolean") &&
392
+ Array.isArray(value.changes) &&
393
+ value.changes.every(validDiff) &&
394
+ (value.changeSetId === null ||
395
+ (typeof value.changeSetId === "string" &&
396
+ changeSetIdPattern.test(value.changeSetId))) &&
397
+ (value.isError === true
398
+ ? value.changes.length === 0 && value.changeSetId === null
399
+ : value.changes.length === 0
400
+ ? value.changeSetId === null
401
+ : typeof value.changeSetId === "string"));
402
+ }
403
+ function validModelToolCall(value) {
404
+ return (isRecord(value) &&
405
+ hasExactKeys(value, ["id", "name", "arguments"]) &&
406
+ nonEmptyString(value.id) &&
407
+ nonEmptyString(value.name));
408
+ }
409
+ function validProviderState(value) {
410
+ if (!isRecord(value))
411
+ return false;
412
+ if (value.protocol === "chat_completions")
413
+ return hasExactKeys(value, ["protocol", "assistantMessage"]);
414
+ if (value.protocol === "openai_responses")
415
+ return (hasExactKeys(value, ["protocol", "output"], ["responseId"]) &&
416
+ Array.isArray(value.output) &&
417
+ (value.responseId === undefined || typeof value.responseId === "string"));
418
+ return (value.protocol === "anthropic_messages" &&
419
+ hasExactKeys(value, ["protocol", "content"], ["responseId", "model", "stopReason"]) &&
420
+ Array.isArray(value.content) &&
421
+ (value.responseId === undefined || typeof value.responseId === "string") &&
422
+ (value.model === undefined || typeof value.model === "string") &&
423
+ (value.stopReason === undefined ||
424
+ value.stopReason === null ||
425
+ typeof value.stopReason === "string"));
426
+ }
427
+ function validPromptCompilation(value) {
428
+ return (isRecord(value) &&
429
+ hasExactKeys(value, [
430
+ "logicalMessages",
431
+ "provider",
432
+ "tools",
433
+ "toolUniverse",
434
+ "toolStrategy",
435
+ "coverage",
436
+ "budget",
437
+ "cache",
438
+ ]) &&
439
+ validLogicalMessages(value.logicalMessages) &&
440
+ isRecord(value.provider) &&
441
+ validToolDefinitions(value.tools) &&
442
+ validToolDefinitions(value.toolUniverse) &&
443
+ (value.toolStrategy === "native_allowed_subset" ||
444
+ value.toolStrategy === "runtime_gate") &&
445
+ Array.isArray(value.coverage) &&
446
+ isRecord(value.budget) &&
447
+ isRecord(value.cache));
448
+ }
449
+ function validLogicalMessages(value) {
450
+ return (Array.isArray(value) &&
451
+ value.every((message) => isRecord(message) &&
452
+ hasExactKeys(message, ["role", "markdown", "blocks"]) &&
453
+ [
454
+ "runtime_system",
455
+ "author_instruction",
456
+ "world_context",
457
+ "player_input",
458
+ "assistant",
459
+ "tool",
460
+ ].includes(String(message.role)) &&
461
+ typeof message.markdown === "string" &&
462
+ Array.isArray(message.blocks) &&
463
+ message.blocks.every((block) => isRecord(block) &&
464
+ hasExactKeys(block, ["source", "markdown"]) &&
465
+ typeof block.source === "string" &&
466
+ typeof block.markdown === "string")));
467
+ }
468
+ function validToolDefinitions(value) {
469
+ return (Array.isArray(value) &&
470
+ value.every((tool) => isRecord(tool) &&
471
+ hasExactKeys(tool, ["name", "description", "inputSchema"]) &&
472
+ nonEmptyString(tool.name) &&
473
+ typeof tool.description === "string" &&
474
+ isRecord(tool.inputSchema)));
475
+ }
476
+ function validModelBinding(value) {
477
+ return (isRecord(value) &&
478
+ hasExactKeys(value, [
479
+ "provider",
480
+ "endpointFingerprint",
481
+ "modelId",
482
+ "contextWindowTokens",
483
+ "maxOutputTokens",
484
+ "protocolConfigFingerprint",
485
+ ], ["cacheStrategy"]) &&
486
+ (value.provider === "chat_completions" ||
487
+ value.provider === "openai_responses" ||
488
+ value.provider === "anthropic_messages") &&
489
+ typeof value.endpointFingerprint === "string" &&
490
+ typeof value.modelId === "string" &&
491
+ count(value.contextWindowTokens) &&
492
+ count(value.maxOutputTokens) &&
493
+ typeof value.protocolConfigFingerprint === "string" &&
494
+ (value.cacheStrategy === undefined ||
495
+ value.cacheStrategy === "explicit_anthropic_blocks" ||
496
+ value.cacheStrategy === "explicit_cliproxyapi_message" ||
497
+ value.cacheStrategy === "provider_managed"));
498
+ }
499
+ function validModelUsage(value) {
500
+ const fields = [
501
+ "inputTokens",
502
+ "uncachedInputTokens",
503
+ "cacheReadTokens",
504
+ "cacheWriteTokens",
505
+ "reasoningTokens",
506
+ "outputTokens",
507
+ "totalTokens",
508
+ ];
509
+ if (!isRecord(value) ||
510
+ !hasExactKeys(value, [...fields, "provenance"]) ||
511
+ !fields.every((field) => value[field] === null || count(value[field])) ||
512
+ !isRecord(value.provenance) ||
513
+ !hasExactKeys(value.provenance, fields))
514
+ return false;
515
+ return fields.every((field) => ["provider", "unavailable", "derived_provider_fields"].includes(String(value.provenance[field])));
516
+ }
517
+ function validAuthorization(value) {
518
+ return (value === null ||
519
+ (isRecord(value) &&
520
+ hasExactKeys(value, [
521
+ "revision",
522
+ "readWorldDocumentIds",
523
+ "readableDamagedWorldPaths",
524
+ "readOpaquePaths",
525
+ ]) &&
526
+ typeof value.revision === "string" &&
527
+ treeRevisionPattern.test(value.revision) &&
528
+ validStringArray(value.readWorldDocumentIds) &&
529
+ validStringArray(value.readableDamagedWorldPaths) &&
530
+ validStringArray(value.readOpaquePaths)));
531
+ }
532
+ function validPendingSettlement(value) {
533
+ if (value === null)
534
+ return true;
535
+ if (!isRecord(value) ||
536
+ !count(value.assistantItemIndex) ||
537
+ typeof value.epochId !== "string" ||
538
+ !epochIdPattern.test(value.epochId) ||
539
+ typeof value.beforeRevision !== "string" ||
540
+ !treeRevisionPattern.test(value.beforeRevision))
541
+ return false;
542
+ if (value.phase === "response_confirmed")
543
+ return hasExactKeys(value, [
544
+ "phase",
545
+ "assistantItemIndex",
546
+ "epochId",
547
+ "beforeRevision",
548
+ ]);
549
+ return (value.phase === "publication_prepared" &&
550
+ hasExactKeys(value, [
551
+ "phase",
552
+ "assistantItemIndex",
553
+ "epochId",
554
+ "beforeRevision",
555
+ "afterRevision",
556
+ "afterFiles",
557
+ "toolResults",
558
+ "authorization",
559
+ ]) &&
560
+ typeof value.afterRevision === "string" &&
561
+ treeRevisionPattern.test(value.afterRevision) &&
562
+ validFiles(value.afterFiles) &&
563
+ contentTreeFingerprint(value.afterFiles) === value.afterRevision &&
564
+ Array.isArray(value.toolResults) &&
565
+ value.toolResults.every(validToolResult) &&
566
+ validAuthorization(value.authorization) &&
567
+ isRecord(value.authorization) &&
568
+ value.authorization.revision === value.afterRevision);
569
+ }
570
+ function validPendingForSession(pending, epochId, runStatus) {
571
+ return (pending === null ||
572
+ (isRecord(pending) &&
573
+ runStatus === "running" &&
574
+ pending.epochId === epochId));
575
+ }
576
+ function validToolResult(value) {
577
+ return (isRecord(value) &&
578
+ hasExactKeys(value, [
579
+ "toolCallId",
580
+ "markdown",
581
+ "isError",
582
+ "changes",
583
+ "changeSetId",
584
+ ]) &&
585
+ nonEmptyString(value.toolCallId) &&
586
+ typeof value.markdown === "string" &&
587
+ typeof value.isError === "boolean" &&
588
+ Array.isArray(value.changes) &&
589
+ value.changes.every(validDiff) &&
590
+ (value.changeSetId === null ||
591
+ (typeof value.changeSetId === "string" &&
592
+ changeSetIdPattern.test(value.changeSetId))) &&
593
+ (value.isError
594
+ ? value.changes.length === 0 && value.changeSetId === null
595
+ : value.changes.length === 0
596
+ ? value.changeSetId === null
597
+ : typeof value.changeSetId === "string"));
598
+ }
599
+ function validPendingAssistant(pending, items) {
600
+ if (pending === null || !isRecord(pending))
601
+ return true;
602
+ const index = pending.assistantItemIndex;
603
+ if (!count(index))
604
+ return false;
605
+ const assistant = items[index];
606
+ return (isRecord(assistant) &&
607
+ assistant.kind === "assistant" &&
608
+ Array.isArray(assistant.toolCalls) &&
609
+ assistant.toolCalls.length > 0);
610
+ }
611
+ function validStringArray(value) {
612
+ return (Array.isArray(value) && value.every((item) => typeof item === "string"));
613
+ }
614
+ function validRevisionPath(path) {
615
+ const safe = path.length > 0 &&
616
+ !path.startsWith("/") &&
617
+ !path.includes("\\") &&
618
+ !path.includes("\0") &&
619
+ path
620
+ .split("/")
621
+ .every((segment) => segment.length > 0 && segment !== "." && segment !== "..");
622
+ return (safe &&
623
+ (isPackageFollowupControlPath(path) ||
624
+ /^state\/.+\.(?:ya?ml|md)$/u.test(path) ||
625
+ path === "control/frame.yaml" ||
626
+ path === "control/player-views.yaml" ||
627
+ /^control\/blocks\/.+\.md$/u.test(path)));
628
+ }
629
+ function revisionControlFingerprint(files) {
630
+ const hash = createHash("sha256");
631
+ for (const file of files
632
+ .filter(({ path }) => path.startsWith("control/"))
633
+ .map((file) => ({ ...file, path: file.path.slice("control/".length) }))
634
+ .sort((left, right) => left.path.localeCompare(right.path)))
635
+ hash.update(file.path).update("\0").update(file.contents).update("\0");
636
+ return `sha256:${hash.digest("hex")}`;
637
+ }
638
+ function compareUpdated(left, right) {
639
+ return right.updatedAt - left.updatedAt || right.createdAt - left.createdAt;
640
+ }
641
+ function assertEpochId(value) {
642
+ if (!epochIdPattern.test(value))
643
+ throw new Error("Invalid world-revision epoch ID");
644
+ }
645
+ function assertSessionId(value) {
646
+ if (!sessionIdPattern.test(value))
647
+ throw new Error("Invalid world-revision conversation ID");
648
+ }
649
+ async function publishJson(path, value) {
650
+ await mkdir(dirname(path), { recursive: true, mode: 0o700 });
651
+ const temporary = `${path}.${randomUUID()}.tmp`;
652
+ try {
653
+ const handle = await open(temporary, "wx", 0o600);
654
+ try {
655
+ await handle.writeFile(`${JSON.stringify(value, null, 2)}\n`, "utf8");
656
+ await handle.sync();
657
+ }
658
+ finally {
659
+ await handle.close();
660
+ }
661
+ await rename(temporary, path);
662
+ await syncDirectory(dirname(path));
663
+ }
664
+ finally {
665
+ await rm(temporary, { force: true });
666
+ }
667
+ }
668
+ async function syncDirectory(path) {
669
+ const handle = await open(path, "r");
670
+ try {
671
+ await handle.sync();
672
+ }
673
+ finally {
674
+ await handle.close();
675
+ }
676
+ }
677
+ function count(value) {
678
+ return Number.isSafeInteger(value) && Number(value) >= 0;
679
+ }
680
+ function finite(value) {
681
+ return typeof value === "number" && Number.isFinite(value);
682
+ }
683
+ function isRecord(value) {
684
+ return typeof value === "object" && value !== null && !Array.isArray(value);
685
+ }
686
+ function nonEmptyString(value) {
687
+ return typeof value === "string" && value.length > 0 && !value.includes("\0");
688
+ }
689
+ function hasExactKeys(value, required, optional = []) {
690
+ const allowed = new Set([...required, ...optional]);
691
+ return (required.every((key) => Object.hasOwn(value, key)) &&
692
+ Object.keys(value).every((key) => allowed.has(key)));
693
+ }
694
+ function isNodeError(error) {
695
+ return error instanceof Error && "code" in error;
696
+ }