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
@@ -13,11 +13,13 @@ export const registeredRuntimeToolNames = [
13
13
  "context_read",
14
14
  "world_patch",
15
15
  "world_create",
16
+ "world_retire",
17
+ "world_checkpoint",
16
18
  "artifact_emit",
17
19
  "artifact_clear",
18
20
  ];
19
21
  /** Changes only when the executable Runtime tool universe or schemas change. */
20
- export const runtimeToolRegistryRevision = "runtime-tools-v5";
22
+ export const runtimeToolRegistryRevision = "runtime-tools-v8";
21
23
  export function defaultRuntimeToolDefinitionStrategy(provider) {
22
24
  return provider === "openai_responses"
23
25
  ? "native_allowed_subset"
@@ -113,11 +115,7 @@ function createToolDefinitions(locale) {
113
115
  },
114
116
  context_read: {
115
117
  description: descriptions.context_read,
116
- inputSchema: object({
117
- ref: string,
118
- cursor: { type: ["string", "null"] },
119
- maxBytes: { type: "integer", minimum: 4, maximum: 8192 },
120
- }, ["ref"]),
118
+ inputSchema: object({ ref: string }, ["ref"]),
121
119
  },
122
120
  world_patch: {
123
121
  description: descriptions.world_patch,
@@ -149,12 +147,15 @@ function createToolDefinitions(locale) {
149
147
  title: string,
150
148
  }, ["op", "locator", "title"]),
151
149
  object({ op: { const: "remove_section" }, locator: markdownLocator }, ["op", "locator"]),
152
- object({
153
- op: { const: "set_metadata" },
154
- title: string,
155
- summary: string,
156
- aliases: { type: "array", items: { type: "string" } },
157
- }, ["op", "title", "summary", "aliases"]),
150
+ {
151
+ ...object({
152
+ op: { const: "set_metadata" },
153
+ title: string,
154
+ summary: string,
155
+ aliases: { type: "array", items: { type: "string" } },
156
+ }, ["op"]),
157
+ minProperties: 2,
158
+ },
158
159
  object({
159
160
  op: { enum: ["replace_body", "replace_preamble"] },
160
161
  markdown: { type: "string" },
@@ -177,9 +178,16 @@ function createToolDefinitions(locale) {
177
178
  maxItems: 16,
178
179
  items: { type: "string", minLength: 1, maxLength: 64 },
179
180
  },
180
- body: { type: "string", maxLength: 65536 },
181
+ body: { type: "string" },
181
182
  }, ["parent", "codec", "refHint", "title", "summary", "aliases", "body"]),
182
183
  },
184
+ world_retire: {
185
+ description: descriptions.world_retire,
186
+ inputSchema: object({
187
+ target: string,
188
+ retired: { type: "boolean" },
189
+ }, ["target", "retired"]),
190
+ },
183
191
  artifact_emit: {
184
192
  description: descriptions.artifact_emit,
185
193
  inputSchema: object({
@@ -187,6 +195,10 @@ function createToolDefinitions(locale) {
187
195
  payload: {},
188
196
  }, ["output", "payload"]),
189
197
  },
198
+ world_checkpoint: {
199
+ description: descriptions.world_checkpoint,
200
+ inputSchema: object({}, []),
201
+ },
190
202
  artifact_clear: {
191
203
  description: descriptions.artifact_clear,
192
204
  inputSchema: object({ output: { type: "string", minLength: 1, maxLength: 128 } }, ["output"]),
@@ -196,22 +208,26 @@ function createToolDefinitions(locale) {
196
208
  }
197
209
  const toolDescriptions = {
198
210
  en: {
199
- state_list: "List document and state directory handles known to Runtime. parent must be @dir-/ for the state root or an @dir-* handle returned by an earlier state_list result; descend only through returned directory handles. A cursor is valid only for the same state snapshot, parent, and limit. Pass returned document handles to context_read. This tool never lists committed history.",
211
+ world_checkpoint: "Declare that the necessary state maintenance for this turn is complete and suggest starting a fresh context. No arguments. Registration becomes effective only when this turn’s final tool-free response commits, including that response in the checkpoint. This is your declaration, not Runtime verification or an automatic context switch. Further tool calls are allowed before the final nonempty tool-free response.",
212
+ state_list: "List document and state directory handles known to Runtime. parent must be @dir-/ for the state root or an @dir-* handle returned by an earlier state_list result; descend only through returned directory handles. Catalog directories declared by the frozen world frame remain listed and usable when empty. A cursor is valid only for the same state snapshot, parent, and limit. Pass returned document handles to context_read. This tool never lists committed history.",
200
213
  history_list: "List handles for committed history messages known to Runtime. order must be newest_first or oldest_first. A cursor is valid only for the same committed-history snapshot, order, and limit. Pass returned @history-message-* handles to context_read. This tool never lists the mutable state directory.",
201
214
  context_search: "Search literal source text in state or history. Zero matches means only that the supplied text had no literal match; it does not prove that a world fact does not exist. Player-visible narrative must not recount the search process. within accepts only an @document or @dir-* handle returned by Runtime.",
202
- context_read: "Precisely read an @document, @node, or @history-message-* handle previously returned by Runtime. Read headings, page markers, metadata boundaries, and body boundaries in a whole-document result are not source text. Update metadata only as one set_metadata operation and update the writable body between the boundaries with locators. YAML bodies omit the $document technical header; Markdown bodies begin at the document's level-one heading. Never pass file paths, natural-language names, or an invented world/... value.",
203
- world_patch: 'Update a document that has been read precisely. target must be an @short-ref. When title, summary, or aliases are stale, update all three with {op:"set_metadata",title,summary,aliases}, copying unchanged values from the read result. A YAML edit uses locator:{yaml:["field","child"]}, for example {op:"replace",locator:{yaml:["status"]},value:"new value"}. A YAML value may reference a whole document only as {$ref:"@short-ref"}; the short reference must come from a Runtime list, read, or create result. Never invent a document id. If no handle is available, use ordinary text when semantically correct or call world_create first and use its returned @short-ref. To add an item to the end of an existing sequence, use append with the locator pointing to that sequence; add creates only a map key or list index that does not exist. A Markdown locator excludes the document level-one heading: {markdown:["Responsibilities"]} points exactly to ## Responsibilities, and replace_section.markdown must begin with the same heading at the same level. Use replace_preamble for text after the level-one heading and before the first level-two heading; send only that text. Use replace_body to replace the whole Markdown body and retain the original level-one heading. Success reports only whether the document changed and does not echo the body. Call context_read again only when a later decision depends on Runtime\'s exact serialized body. Do not use path, JSON Pointer, set, or a file name.',
204
- world_create: 'Create a document inside a state directory returned by Runtime. parent must be an @dir-* handle returned by state_list, such as @dir-/characters; use @dir-/ for the root. refHint is a lowercase ASCII short-reference hint, not a world/ path. A YAML body may reference a whole document only as {$ref:"@short-ref"}, and the short reference must come from a Runtime list, read, or create result. Never invent a document id.',
215
+ context_read: "Read the complete content of an @document, @node, or @history-message-* handle previously returned by Runtime. The read heading, metadata boundaries, body boundaries, scope, and completion marker in the result are not source text. set_metadata may include only the fields that must change; Runtime preserves omitted metadata fields. Update the writable body between the boundaries with locators. YAML bodies omit the $document technical header; Markdown bodies begin at the document's level-one heading. Results are never truncated or paginated. Never pass file paths, natural-language names, or an invented world/... value.",
216
+ world_patch: 'Update a document that has been read precisely or whose complete body was injected with write authorization. target must be an @short-ref. When title, summary, or aliases are stale, use set_metadata and include only the title, summary, or aliases that must change. At least one is required; Runtime preserves omitted fields from the current candidate document. A YAML edit uses locator:{yaml:["field","child"]}, for example {op:"replace",locator:{yaml:["status"]},value:"new value"}. Locator segments address map keys or a zero-based array index; remove deletes exactly the existing key or array item at its locator. A YAML value may reference a whole document only as {$ref:"@short-ref"}; the short reference must come from a Runtime list, read, or create result. Never invent a document id. If no handle is available, use ordinary text when semantically correct or call world_create first and use its returned @short-ref. To add an item to the end of an existing sequence, use append with the locator pointing to that sequence; add creates only a map key or list index that does not exist. A Markdown locator excludes the document level-one heading: {markdown:["Responsibilities"]} points exactly to ## Responsibilities, and replace_section.markdown must begin with the same heading at the same level. Use replace_preamble for text after the level-one heading and before the first level-two heading; send only that text. Use replace_body to replace the whole Markdown body and retain the original level-one heading. Success reports only whether the document changed and does not echo the body. Call context_read again only when a later decision depends on Runtime\'s exact serialized body or current metadata. Do not use path, JSON Pointer, set, or a file name.',
217
+ world_create: 'Create a document inside a Runtime-known state directory. parent must be an @dir-* handle listed by state_list, such as @dir-/characters; catalog directories declared by the frozen world frame remain available when empty, and @dir-/ is the root. A syntactically valid but unknown directory is rejected. refHint is a lowercase ASCII short-reference hint, not a world/ path. A YAML body may reference a whole document only as {$ref:"@short-ref"}, and the short reference must come from a Runtime list, read, or create result. Never invent a document id.',
218
+ world_retire: "Retire or restore an exact state document without deleting its narrative record. target must be an @short-ref that was read precisely or injected completely. retired:true removes the document from future frame catalog injection but keeps it listed, searchable, readable, referenceable, and restorable; retired:false restores catalog participation. This changes catalog participation, not explicit full-document injection or character activity; use world_patch for ordinary location or field changes.",
205
219
  artifact_emit: "Submit an artifact declared in advance for this follow-up request. Supply only the output name and payload; Runtime fixes the channel, key, content type, renderer, retention policy, and authority meaning in the contract.",
206
220
  artifact_clear: "Clear the projection of an artifact that this follow-up declared in advance and explicitly allows to be cleared. The call cannot choose a channel, key, or any other output contract.",
207
221
  },
208
222
  "zh-CN": {
209
- state_list: "列出 Runtime 已知的文档与状态目录句柄。状态根目录的 parent 使用 @dir-/;继续下级时只能使用先前 state_list 结果返回的 @dir-*。cursor 只对同一状态快照、parent 和 limit 有效。返回的文档句柄交给 context_read;本工具不列出已提交历史。",
223
+ world_checkpoint: "声明本轮必要状态已整理完成,并建议玩家在此开启全新上下文。参数为空对象。登记仅在本轮最终无工具响应提交后生效,检查点包含该响应。这是 AI 的整理声明,不是 Runtime 完整性认证,也不会自动切换上下文。最终非空且无工具调用的响应之前,仍可继续调用工具。",
224
+ state_list: "列出 Runtime 已知的文档与状态目录句柄。状态根目录的 parent 使用 @dir-/;继续下级时只能使用先前 state_list 结果返回的 @dir-*。冻结世界 frame 声明的 catalog 目录即使为空,也会继续列出并可用。cursor 只对同一状态快照、parent 和 limit 有效。返回的文档句柄交给 context_read;本工具不列出已提交历史。",
210
225
  history_list: "列出 Runtime 已知的已提交历史消息句柄。order 必须是 newest_first 或 oldest_first;cursor 只对同一历史快照、order 和 limit 有效。返回的 @history-message-* 交给 context_read;本工具不列出可变状态目录。",
211
226
  context_search: "在 state 或 history 的原文字面中搜索。0 命中只表示给定文本没有字面命中,不证明世界事实不存在;玩家可见叙事不得复述搜索过程。within 只能使用 Runtime 已返回的 @文档或 @dir-* 句柄。",
212
- context_read: "精确读取 Runtime 先前返回的 @文档、@节点或 @history-message-* 句柄。整文档结果中的读取标题、分页线、元信息边界和正文边界不属于源文;元信息只能用 set_metadata 整组更新,中间的可写正文用 locator 更新。YAML 正文不含 $document 技术头,Markdown 正文从文档 # 一级标题开始。不要传文件路径、自然语言名称或自行拼造的 world/...。",
213
- world_patch: '更新已精确读取的文档。target 必须使用 @短引用;文档 title、summary 或 aliases 过时时用 {op:"set_metadata",title,summary,aliases} 整组更新,未改项照抄读取结果。YAML edit 使用 locator:{yaml:["字段","子字段"]},例如 {op:"replace",locator:{yaml:["情况"]},value:"新值"}。YAML value 需要引用整份文档时只能写 {$ref:"@短引用"},短引用必须来自 Runtime 的 list、read 或 create 结果;不得自行编造文档 id。没有可用句柄时,根据语义使用普通文本,或先 world_create 后使用其返回的 @短引用。向已存在的 sequence 末尾加一项必须使用 append 并把 locator 指向该 sequence;add 只创建尚不存在的 map key 或 list index。Markdown locator 不包含文档 # 一级标题:{markdown:["职责"]} 精确指向 ## 职责,replace_section.markdown 必须从同级同名标题开始。修改 # 标题下、第一个 ## 前的文字用 replace_preamble(只传该段文字);替换整个 Markdown 正文用 replace_body,且 markdown 必须保留原 # 一级标题。成功结果只报告文档是否发生变化,不回显正文;只有后续决策依赖 Runtime 序列化后的精确正文时才重新 context_read。不要使用 path、JSON Pointer、set 或文件名。',
214
- world_create: '在 Runtime 返回的状态目录中创建文档。parent 必须是 state_list 返回的 @dir-*,例如 @dir-/characters;根目录使用 @dir-/。refHint 是小写 ASCII 短引用,不要传 world/ 路径。YAML body 需要引用整份文档时只能写 {$ref:"@短引用"},且短引用必须来自 Runtime 的 list、read 或 create 结果;不得自行编造文档 id。',
227
+ context_read: "完整读取 Runtime 先前返回的 @文档、@节点或 @history-message-* 句柄。结果中的读取标题、元信息边界、正文边界、范围和完整标记不属于源文;set_metadata 可以只提交需要改变的字段,Runtime 会保留未提供的元数据字段。中间的可写正文用 locator 更新。YAML 正文不含 $document 技术头,Markdown 正文从文档 # 一级标题开始。结果不会截断或分页。不要传文件路径、自然语言名称或自行拼造的 world/...。",
228
+ world_patch: '更新已精确读取,或已随写入资格注入完整正文的文档。target 必须使用 @短引用;文档 title、summary 或 aliases 过时时使用 set_metadata,只提供需要改变的 title、summary 或 aliases。至少提供一项;未提供的字段由 Runtime 从当前候选文档保留。YAML edit 使用 locator:{yaml:["字段","子字段"]},例如 {op:"replace",locator:{yaml:["情况"]},value:"新值"}。locator 的每一段可以定位 map key 或从 0 开始的数组下标;remove 只删除 locator 精确指向的已有键或数组项。YAML value 需要引用整份文档时只能写 {$ref:"@短引用"},短引用必须来自 Runtime 的 list、read 或 create 结果;不得自行编造文档 id。没有可用句柄时,根据语义使用普通文本,或先 world_create 后使用其返回的 @短引用。向已存在的 sequence 末尾加一项必须使用 append 并把 locator 指向该 sequence;add 只创建尚不存在的 map key 或 list index。Markdown locator 不包含文档 # 一级标题:{markdown:["职责"]} 精确指向 ## 职责,replace_section.markdown 必须从同级同名标题开始。修改 # 标题下、第一个 ## 前的文字用 replace_preamble(只传该段文字);替换整个 Markdown 正文用 replace_body,且 markdown 必须保留原 # 一级标题。成功结果只报告文档是否发生变化,不回显正文;只有后续决策依赖 Runtime 序列化后的精确正文或当前元数据时才重新 context_read。不要使用 path、JSON Pointer、set 或文件名。',
229
+ world_create: '在 Runtime 已知的状态目录中创建文档。parent 必须是 state_list 列出的 @dir-*,例如 @dir-/characters;冻结世界 frame 声明的 catalog 目录为空时仍然可用,根目录使用 @dir-/。只有句柄形式正确但 Runtime 并不知道的目录会被拒绝。refHint 是小写 ASCII 短引用,不要传 world/ 路径。YAML body 需要引用整份文档时只能写 {$ref:"@短引用"},且短引用必须来自 Runtime 的 list、read 或 create 结果;不得自行编造文档 id。',
230
+ world_retire: "让一份精确状态文档退役或恢复,而不销毁叙事记录。target 必须是已精确读取或已完整注入的 @短引用。retired:true 会让文档不再进入后续 frame catalog 注入,但仍可列出、搜索、读取、引用和恢复;retired:false 恢复 catalog 参与。它只改变 catalog 参与,不取消显式全文注入、不冻结人物活动;普通位置或字段变化仍用 world_patch。",
215
231
  artifact_emit: "提交本次后置请求预先声明的产物。只能传 output name 与 payload;频道、key、内容类型、renderer、保存策略和权威含义由 Runtime contract 固定。",
216
232
  artifact_clear: "清除本次后置请求预先声明且允许 clear 的产物投影;不能选择频道、key 或其他输出契约。",
217
233
  },
@@ -0,0 +1,322 @@
1
+ import { isWorldExtensionControl } from "../../protocol/worldExtensions.js";
2
+ import { isWorldPromptMaintenance } from "../../protocol/worldMaintenance.js";
3
+ /** Canonical validators for saved compiler output. Validate without projecting or rebuilding it. */
4
+ export function validPlayFollowup(value) {
5
+ return (isRecord(value) &&
6
+ hasExactKeys(value, [
7
+ "id",
8
+ "displayName",
9
+ "logicalMessages",
10
+ "tools",
11
+ "allowedTools",
12
+ "artifacts",
13
+ "maxArtifactBytes",
14
+ ], ["frozenResources", "extensionControl"]) &&
15
+ (value.extensionControl === undefined ||
16
+ isWorldExtensionControl(value.extensionControl)) &&
17
+ (value.frozenResources === undefined ||
18
+ (isRecord(value.frozenResources) &&
19
+ hasExactKeys(value.frozenResources, ["files", "mount"]) &&
20
+ isRecord(value.frozenResources.files) &&
21
+ Object.values(value.frozenResources.files).every((body) => typeof body === "string") &&
22
+ typeof value.frozenResources.mount === "string" &&
23
+ [
24
+ "story",
25
+ "sidebar",
26
+ "composer_above",
27
+ "composer_below",
28
+ "overlay",
29
+ "debug",
30
+ ].includes(String(value.frozenResources.mount)))) &&
31
+ typeof value.id === "string" &&
32
+ typeof value.displayName === "string" &&
33
+ validLogicalMessages(value.logicalMessages) &&
34
+ validPromptTools(value.tools) &&
35
+ validStringArray(value.allowedTools) &&
36
+ Array.isArray(value.artifacts) &&
37
+ value.artifacts.every(validPlayArtifact) &&
38
+ validCount(value.maxArtifactBytes));
39
+ }
40
+ function validPlayArtifact(value) {
41
+ return (isRecord(value) &&
42
+ hasExactKeys(value, [
43
+ "name",
44
+ "channel",
45
+ "strategy",
46
+ "contentType",
47
+ "save",
48
+ "invalidation",
49
+ "required",
50
+ "maxEmits",
51
+ ], [
52
+ "key",
53
+ "renderer",
54
+ "rendererRevision",
55
+ "rendererMode",
56
+ "regex",
57
+ "scripts",
58
+ "assets",
59
+ "payloadContract",
60
+ "displayName",
61
+ "purpose",
62
+ ]) &&
63
+ typeof value.name === "string" &&
64
+ (value.displayName === undefined ||
65
+ (typeof value.displayName === "string" &&
66
+ value.displayName.length <= 160 &&
67
+ !value.displayName.includes("\0"))) &&
68
+ (value.purpose === undefined ||
69
+ (typeof value.purpose === "string" &&
70
+ value.purpose.length <= 16000 &&
71
+ !value.purpose.includes("\0"))) &&
72
+ typeof value.channel === "string" &&
73
+ ["append", "replace", "upsert", "transient", "hidden"].includes(String(value.strategy)) &&
74
+ ["text/plain", "text/markdown", "application/json", "text/html"].includes(String(value.contentType)) &&
75
+ (value.key === undefined || typeof value.key === "string") &&
76
+ (value.renderer === undefined || typeof value.renderer === "string") &&
77
+ (value.rendererRevision === undefined ||
78
+ typeof value.rendererRevision === "string") &&
79
+ (value.rendererMode === undefined ||
80
+ value.rendererMode === "document" ||
81
+ value.rendererMode === "app") &&
82
+ (value.regex === undefined || typeof value.regex === "string") &&
83
+ (value.scripts === undefined || validStringArray(value.scripts)) &&
84
+ (value.assets === undefined || validStringArray(value.assets)) &&
85
+ ["none", "operation", "commit"].includes(String(value.save)) &&
86
+ [
87
+ "new_operation",
88
+ "head_change",
89
+ "operation_end",
90
+ "explicit_clear",
91
+ "never",
92
+ ].includes(String(value.invalidation)) &&
93
+ typeof value.required === "boolean" &&
94
+ validCount(value.maxEmits) &&
95
+ (value.payloadContract === undefined ||
96
+ validArtifactPayloadContract(value.payloadContract)));
97
+ }
98
+ function validArtifactPayloadContract(value) {
99
+ if (!isRecord(value) ||
100
+ !hasExactKeys(value, ["type"], [
101
+ "properties",
102
+ "required",
103
+ "additionalProperties",
104
+ "items",
105
+ "minItems",
106
+ "maxItems",
107
+ "minLength",
108
+ "maxLength",
109
+ "uniqueBy",
110
+ "maxBytes",
111
+ ]) ||
112
+ ![
113
+ "object",
114
+ "array",
115
+ "string",
116
+ "number",
117
+ "integer",
118
+ "boolean",
119
+ "null",
120
+ ].includes(String(value.type)) ||
121
+ (value.required !== undefined && !validStringArray(value.required)) ||
122
+ (value.additionalProperties !== undefined &&
123
+ typeof value.additionalProperties !== "boolean") ||
124
+ (value.items !== undefined && !validArtifactPayloadContract(value.items)) ||
125
+ (value.uniqueBy !== undefined && typeof value.uniqueBy !== "string"))
126
+ return false;
127
+ for (const key of [
128
+ "minItems",
129
+ "maxItems",
130
+ "minLength",
131
+ "maxLength",
132
+ "maxBytes",
133
+ ])
134
+ if (value[key] !== undefined && !validCount(value[key]))
135
+ return false;
136
+ if (value.properties === undefined)
137
+ return true;
138
+ return (isRecord(value.properties) &&
139
+ Object.values(value.properties).every(validArtifactPayloadContract));
140
+ }
141
+ export function validPromptCompilation(value) {
142
+ return (isRecord(value) &&
143
+ hasExactKeys(value, [
144
+ "logicalMessages",
145
+ "provider",
146
+ "tools",
147
+ "toolUniverse",
148
+ "toolStrategy",
149
+ "coverage",
150
+ "budget",
151
+ "cache",
152
+ ], ["maintenance"]) &&
153
+ (value.maintenance === undefined ||
154
+ isWorldPromptMaintenance(value.maintenance)) &&
155
+ validLogicalMessages(value.logicalMessages) &&
156
+ validPromptProvider(value.provider) &&
157
+ validPromptTools(value.tools) &&
158
+ validPromptTools(value.toolUniverse) &&
159
+ validToolStrategy(value.toolStrategy) &&
160
+ Array.isArray(value.coverage) &&
161
+ value.coverage.every(validPromptCoverage) &&
162
+ validPromptBudget(value.budget) &&
163
+ validPromptCache(value.cache));
164
+ }
165
+ export function validLogicalMessages(value) {
166
+ return Array.isArray(value) && value.every(validLogicalMessage);
167
+ }
168
+ function validLogicalMessage(value) {
169
+ return (isRecord(value) &&
170
+ hasExactKeys(value, ["role", "markdown", "blocks"]) &&
171
+ [
172
+ "runtime_system",
173
+ "author_instruction",
174
+ "world_context",
175
+ "player_input",
176
+ "assistant",
177
+ "tool",
178
+ ].includes(String(value.role)) &&
179
+ typeof value.markdown === "string" &&
180
+ Array.isArray(value.blocks) &&
181
+ value.blocks.every((block) => isRecord(block) &&
182
+ hasExactKeys(block, ["source", "markdown"]) &&
183
+ typeof block.source === "string" &&
184
+ typeof block.markdown === "string"));
185
+ }
186
+ function validPromptProvider(value) {
187
+ return (isRecord(value) &&
188
+ hasExactKeys(value, ["protocol", "messages"], ["system"]) &&
189
+ validProviderKind(value.protocol) &&
190
+ (value.system === undefined ||
191
+ (Array.isArray(value.system) && value.system.every(validSystemBlock))) &&
192
+ Array.isArray(value.messages) &&
193
+ value.messages.every((message) => isRecord(message) &&
194
+ hasExactKeys(message, ["role", "content"]) &&
195
+ (message.role === "system" || message.role === "user") &&
196
+ Object.hasOwn(message, "content")));
197
+ }
198
+ function validSystemBlock(value) {
199
+ return (isRecord(value) &&
200
+ hasExactKeys(value, ["type", "text"], ["cache_control"]) &&
201
+ value.type === "text" &&
202
+ typeof value.text === "string" &&
203
+ (value.cache_control === undefined ||
204
+ (isRecord(value.cache_control) &&
205
+ hasExactKeys(value.cache_control, ["type"]) &&
206
+ value.cache_control.type === "ephemeral")));
207
+ }
208
+ export function validPromptTools(value) {
209
+ return (Array.isArray(value) &&
210
+ value.every((tool) => isRecord(tool) &&
211
+ hasExactKeys(tool, ["name", "description", "inputSchema"]) &&
212
+ typeof tool.name === "string" &&
213
+ typeof tool.description === "string" &&
214
+ isRecord(tool.inputSchema)));
215
+ }
216
+ function validPromptCoverage(value) {
217
+ return (isRecord(value) &&
218
+ hasExactKeys(value, ["slot", "source", "status", "complete", "continuation"], ["readAuthorization", "catalogEntries"]) &&
219
+ typeof value.slot === "string" &&
220
+ typeof value.source === "string" &&
221
+ ["resolved", "optional_missing", "paged_catalog"].includes(String(value.status)) &&
222
+ typeof value.complete === "boolean" &&
223
+ (value.continuation === null || typeof value.continuation === "string") &&
224
+ (value.readAuthorization === undefined ||
225
+ validPromptReadAuthorization(value.readAuthorization)) &&
226
+ (value.catalogEntries === undefined ||
227
+ validStringArray(value.catalogEntries)));
228
+ }
229
+ function validPromptReadAuthorization(value) {
230
+ if (!isRecord(value) ||
231
+ !hasExactKeys(value, ["shortRef", "locator"]) ||
232
+ typeof value.shortRef !== "string")
233
+ return false;
234
+ if (value.locator === null)
235
+ return true;
236
+ if (!isRecord(value.locator))
237
+ return false;
238
+ if (hasExactKeys(value.locator, ["yaml"]))
239
+ return (Array.isArray(value.locator.yaml) &&
240
+ value.locator.yaml.every((part) => typeof part === "string" || validFiniteNumber(part)));
241
+ return (hasExactKeys(value.locator, ["markdown"]) &&
242
+ validStringArray(value.locator.markdown));
243
+ }
244
+ function validPromptBudget(value) {
245
+ return (isRecord(value) &&
246
+ hasExactKeys(value, [
247
+ "estimator",
248
+ "messageTokens",
249
+ "toolTokens",
250
+ "outputReserveTokens",
251
+ "forcedTailReserveTokens",
252
+ "safetyMarginTokens",
253
+ "requiredTokens",
254
+ "contextWindowTokens",
255
+ "status",
256
+ ]) &&
257
+ (value.estimator === "conservative_utf8_bytes" ||
258
+ value.estimator === "disabled") &&
259
+ [
260
+ value.messageTokens,
261
+ value.toolTokens,
262
+ value.outputReserveTokens,
263
+ value.forcedTailReserveTokens,
264
+ value.safetyMarginTokens,
265
+ value.requiredTokens,
266
+ value.contextWindowTokens,
267
+ ].every(validCount) &&
268
+ ["fits", "over_budget", "not_checked"].includes(String(value.status)));
269
+ }
270
+ function validPromptCache(value) {
271
+ return (isRecord(value) &&
272
+ hasExactKeys(value, [
273
+ "strategy",
274
+ "stablePrefixFingerprint",
275
+ "breakpoints",
276
+ "estimatedCacheableBytes",
277
+ "firstDynamicByte",
278
+ ]) &&
279
+ validCacheStrategy(value.strategy) &&
280
+ typeof value.stablePrefixFingerprint === "string" &&
281
+ Array.isArray(value.breakpoints) &&
282
+ value.breakpoints.every((role) => [
283
+ "runtime_system",
284
+ "author_instruction",
285
+ "world_context",
286
+ "player_input",
287
+ "assistant",
288
+ "tool",
289
+ ].includes(String(role))) &&
290
+ validCount(value.estimatedCacheableBytes) &&
291
+ validCount(value.firstDynamicByte));
292
+ }
293
+ export function validProviderKind(value) {
294
+ return (value === "chat_completions" ||
295
+ value === "openai_responses" ||
296
+ value === "anthropic_messages");
297
+ }
298
+ export function validCacheStrategy(value) {
299
+ return (value === "explicit_anthropic_blocks" ||
300
+ value === "explicit_cliproxyapi_message" ||
301
+ value === "provider_managed");
302
+ }
303
+ export function validToolStrategy(value) {
304
+ return value === "native_allowed_subset" || value === "runtime_gate";
305
+ }
306
+ function isRecord(value) {
307
+ return typeof value === "object" && value !== null && !Array.isArray(value);
308
+ }
309
+ function hasExactKeys(value, required, optional = []) {
310
+ const allowed = new Set([...required, ...optional]);
311
+ return (required.every((key) => Object.hasOwn(value, key)) &&
312
+ Object.keys(value).every((key) => allowed.has(key)));
313
+ }
314
+ function validCount(value) {
315
+ return Number.isSafeInteger(value) && Number(value) >= 0;
316
+ }
317
+ function validFiniteNumber(value) {
318
+ return typeof value === "number" && Number.isFinite(value);
319
+ }
320
+ function validStringArray(value) {
321
+ return (Array.isArray(value) && value.every((item) => typeof item === "string"));
322
+ }