narraeon 0.5.6 → 0.5.7
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.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/node/protocol/appPreferences.js +4 -2
- package/dist/node/protocol/playFailure.js +13 -0
- package/dist/node/protocol/protagonistTemplates.js +13 -0
- package/dist/node/protocol/v1.js +25 -19
- package/dist/node/protocol/worldArchives.js +7 -0
- package/dist/node/runtime/V1Runtime.js +46 -6
- package/dist/node/runtime/archive/WorldArchiveZip.js +202 -0
- package/dist/node/runtime/archive/WorldArchives.js +389 -0
- package/dist/node/runtime/config/AppPreferencesStore.js +23 -10
- package/dist/node/runtime/config/ProtagonistTemplateStore.js +134 -0
- package/dist/node/runtime/content/ContentWorkspace.js +5 -0
- package/dist/node/runtime/history/StoryExport.js +33 -0
- package/dist/node/runtime/model/FileNativeModelAdapters.js +34 -2
- package/dist/node/runtime/model/ModelHost.js +12 -1
- package/dist/node/runtime/model/ModelHostStream.js +11 -3
- package/dist/node/runtime/play/FileNativePlayTimelineStore.js +80 -4
- package/dist/node/runtime/play/LegacyModelBindingFailure.js +10 -0
- package/dist/node/runtime/play/PlayCallChain.js +144 -52
- package/dist/node/runtime/play/PlayCallChainError.js +3 -1
- package/dist/node/runtime/play/PlayContinuity.js +3 -20
- package/dist/node/runtime/play/PlayFailure.js +20 -0
- package/dist/node/runtime/play/PlayTraceRetention.js +3 -11
- package/dist/node/runtime/prompt/FileNativePromptCompiler.js +4 -22
- package/dist/node/runtime/prompt/FileNativeToolRegistry.js +2 -2
- package/dist/node/runtime/world/FileNativeWorldStore.js +172 -39
- package/dist/node/runtime/world/WorldActivity.js +143 -0
- package/dist/node/server/createServer.js +48 -0
- package/dist/node/shared/default-preset-host.js +10 -6
- package/dist/node/shared/ordered-play-prompts.js +2 -2
- package/dist/node/shared/sample-packages/index.js +10 -0
- package/dist/node/shared/sample-packages/rainy-inn-en.js +194 -0
- package/dist/node/shared/sample-packages/rainy-inn-zh.js +194 -0
- package/dist/web/assets/index-DxNtxzLR.js +1255 -0
- package/dist/web/assets/{index-CVN48xrw.css → index-DzOnu9uv.css} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +6 -2
- package/dist/web/assets/index-BH8NyuE4.js +0 -1233
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
var _a;
|
|
1
2
|
import { createHash } from "node:crypto";
|
|
2
3
|
import { appendFile, chmod, mkdir, stat } from "node:fs/promises";
|
|
3
4
|
import { dirname } from "node:path";
|
|
4
5
|
import { hasCLIProxyThinkingSuffix, modelReasoningPolicyIssue, } from "../../protocol/modelConnections.js";
|
|
5
|
-
import { ModelHostBindingMismatchError, ModelHostCancelledError, ModelHostContinuationError, ModelHostFailureError, ModelHostOutcomeUnknownError, } from "./ModelHost.js";
|
|
6
|
+
import { assertModelOutputComplete, ModelHostBindingMismatchError, ModelHostCancelledError, ModelHostContinuationError, ModelHostFailureError, ModelHostOutcomeUnknownError, } from "./ModelHost.js";
|
|
6
7
|
import { renderPromptDeltaMessage, } from "../prompt/FileNativePromptCompiler.js";
|
|
7
8
|
import { aggregateAnthropicModelStream, aggregateChatModelStream, aggregateResponsesModelStream, } from "./ModelHostStream.js";
|
|
8
9
|
import { defaultRuntimeToolDefinitionStrategy, portableRuntimeToolInputSchema, } from "../prompt/FileNativeToolRegistry.js";
|
|
@@ -77,7 +78,31 @@ export class FileNativeModelHost {
|
|
|
77
78
|
.digest("hex"),
|
|
78
79
|
};
|
|
79
80
|
}
|
|
80
|
-
|
|
81
|
+
freezeRequest(request) {
|
|
82
|
+
const frozen = structuredClone(request);
|
|
83
|
+
if (frozen.modelConfig === undefined) {
|
|
84
|
+
const { baseUrl: _baseUrl, apiKey: _apiKey, ...config } = this.#connection;
|
|
85
|
+
void _baseUrl;
|
|
86
|
+
void _apiKey;
|
|
87
|
+
frozen.modelConfig = config;
|
|
88
|
+
}
|
|
89
|
+
return frozen;
|
|
90
|
+
}
|
|
91
|
+
#requestHost(request) {
|
|
92
|
+
if (request.modelConfig === undefined)
|
|
93
|
+
return this;
|
|
94
|
+
if (request.modelConfig.provider !== this.#connection.provider)
|
|
95
|
+
throw new ModelHostBindingMismatchError("The request protocol does not match the selected model host");
|
|
96
|
+
return new _a({
|
|
97
|
+
...request.modelConfig,
|
|
98
|
+
baseUrl: this.#connection.baseUrl,
|
|
99
|
+
apiKey: this.#connection.apiKey,
|
|
100
|
+
}, this.#fetch, this.#failureLog);
|
|
101
|
+
}
|
|
102
|
+
exchange(request, observer) {
|
|
103
|
+
return this.#requestHost(request).#exchange(request, observer);
|
|
104
|
+
}
|
|
105
|
+
async #exchange(request, observer) {
|
|
81
106
|
assertExchangeCompatibility(this.#connection, request);
|
|
82
107
|
await trace("model_host_exchange", modelHostTrace(request));
|
|
83
108
|
const signal = observer?.signal;
|
|
@@ -99,6 +124,9 @@ export class FileNativeModelHost {
|
|
|
99
124
|
}
|
|
100
125
|
/** Uses the production encoders and deliberately omits credentials. */
|
|
101
126
|
previewRequest(request) {
|
|
127
|
+
return this.#requestHost(request).#previewRequest(request);
|
|
128
|
+
}
|
|
129
|
+
#previewRequest(request) {
|
|
102
130
|
assertExchangeCompatibility(this.#connection, request);
|
|
103
131
|
const provider = this.#connection.provider;
|
|
104
132
|
const body = provider === "chat_completions"
|
|
@@ -183,6 +211,7 @@ export class FileNativeModelHost {
|
|
|
183
211
|
await trace("chat_response", payload);
|
|
184
212
|
const message = payload.choices?.[0]?.message;
|
|
185
213
|
const finishReason = payload.choices?.[0]?.finish_reason;
|
|
214
|
+
assertModelOutputComplete(finishReason);
|
|
186
215
|
if (message === undefined)
|
|
187
216
|
throw unknownProviderResponse("Chat Completions");
|
|
188
217
|
if (finishReason !== undefined &&
|
|
@@ -271,6 +300,7 @@ export class FileNativeModelHost {
|
|
|
271
300
|
if (!isRecord(rawPayload) || !Array.isArray(rawPayload.content))
|
|
272
301
|
throw unknownProviderResponse("Anthropic Messages");
|
|
273
302
|
const payload = rawPayload;
|
|
303
|
+
assertModelOutputComplete(payload.stop_reason);
|
|
274
304
|
const content = payload.content;
|
|
275
305
|
if (!validAnthropicContent(content))
|
|
276
306
|
throw unknownProviderResponse("Anthropic Messages");
|
|
@@ -310,6 +340,7 @@ export class FileNativeModelHost {
|
|
|
310
340
|
});
|
|
311
341
|
}
|
|
312
342
|
}
|
|
343
|
+
_a = FileNativeModelHost;
|
|
313
344
|
function assertExchangeCompatibility(connection, request) {
|
|
314
345
|
if (request.bootstrap.provider.protocol !== connection.provider)
|
|
315
346
|
throw new ModelHostBindingMismatchError("The compiled prompt protocol does not match the selected model host");
|
|
@@ -1048,6 +1079,7 @@ async function openAIResponsesRequest(input) {
|
|
|
1048
1079
|
await trace(`${input.tracePrefix}_response`, payload);
|
|
1049
1080
|
if (!isRecord(payload) || !Array.isArray(payload.output))
|
|
1050
1081
|
throw unknownProviderResponse("Responses API");
|
|
1082
|
+
assertModelOutputComplete(responsesStopReason(payload));
|
|
1051
1083
|
const output = cloneResponseOutput(payload.output);
|
|
1052
1084
|
if (!validResponsesOutput(output))
|
|
1053
1085
|
throw unknownProviderResponse("Responses API");
|
|
@@ -33,10 +33,21 @@ export class ModelHostFailureError extends Error {
|
|
|
33
33
|
this.details = options.details;
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
-
/**
|
|
36
|
+
/** Reject provider-confirmed truncation before accepting text or tool calls. */
|
|
37
|
+
export function assertModelOutputComplete(stopReason) {
|
|
38
|
+
if (stopReason === "length" ||
|
|
39
|
+
stopReason === "max_tokens" ||
|
|
40
|
+
stopReason === "max_output_tokens" ||
|
|
41
|
+
stopReason === "incomplete:max_output_tokens") {
|
|
42
|
+
throw new ModelHostFailureError("The model response reached the output token limit before completion. Increase maxOutputTokens and start a fresh context; reasoning may also consume the output budget.", { details: { code: "model_output_limit", stopReason } });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Replaying the frozen request cannot recover a context or output limit. */
|
|
37
46
|
export function modelHostFailureRequiresFreshContext(error) {
|
|
38
47
|
if (!(error instanceof ModelHostFailureError))
|
|
39
48
|
return false;
|
|
49
|
+
if (failureDetailStrings(error.details).includes("model_output_limit"))
|
|
50
|
+
return true;
|
|
40
51
|
const evidence = [error.message, ...failureDetailStrings(error.details)]
|
|
41
52
|
.join("\n")
|
|
42
53
|
.toLocaleLowerCase("und");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ModelHostFailureError } from "./ModelHost.js";
|
|
1
|
+
import { assertModelOutputComplete, ModelHostFailureError, } from "./ModelHost.js";
|
|
2
2
|
import { providerStreamEvents } from "./ProviderStream.js";
|
|
3
3
|
export async function aggregateChatModelStream(body, onDelta) {
|
|
4
4
|
let content = "";
|
|
@@ -110,6 +110,7 @@ export async function aggregateChatModelStream(body, onDelta) {
|
|
|
110
110
|
}
|
|
111
111
|
if (!completed || !sawPayload)
|
|
112
112
|
throw new Error("Chat SSE ended before a complete response");
|
|
113
|
+
assertModelOutputComplete(stopReason);
|
|
113
114
|
const toolCalls = [...calls.entries()]
|
|
114
115
|
.sort(([left], [right]) => left - right)
|
|
115
116
|
.map(([, call]) => {
|
|
@@ -259,8 +260,6 @@ export async function aggregateAnthropicModelStream(body, onDelta) {
|
|
|
259
260
|
if (block === undefined || block.stopped)
|
|
260
261
|
throw new Error("Anthropic SSE content block stop is invalid");
|
|
261
262
|
block.stopped = true;
|
|
262
|
-
if (block.value.type === "tool_use" && block.partialJson !== "")
|
|
263
|
-
block.value.input = parseStreamJson(block.partialJson);
|
|
264
263
|
break;
|
|
265
264
|
}
|
|
266
265
|
case "message_delta": {
|
|
@@ -289,11 +288,14 @@ export async function aggregateAnthropicModelStream(body, onDelta) {
|
|
|
289
288
|
}
|
|
290
289
|
if (!started || !stopped)
|
|
291
290
|
throw new Error("Anthropic SSE ended before a complete response");
|
|
291
|
+
assertModelOutputComplete(stopReason);
|
|
292
292
|
const content = [...blocks.entries()]
|
|
293
293
|
.sort(([left], [right]) => left - right)
|
|
294
294
|
.map(([, block]) => {
|
|
295
295
|
if (!block.stopped)
|
|
296
296
|
throw new Error("Anthropic SSE content block did not end");
|
|
297
|
+
if (block.value.type === "tool_use" && block.partialJson !== "")
|
|
298
|
+
block.value.input = parseStreamJson(block.partialJson);
|
|
297
299
|
return block.value;
|
|
298
300
|
});
|
|
299
301
|
const text = content
|
|
@@ -364,6 +366,12 @@ export async function aggregateResponsesModelStream(body, onDelta) {
|
|
|
364
366
|
payload.type === "response.incomplete" ||
|
|
365
367
|
payload.type === "response.cancelled" ||
|
|
366
368
|
payload.type === "error") {
|
|
369
|
+
if (payload.type === "response.incomplete" &&
|
|
370
|
+
isRecord(payload.response)) {
|
|
371
|
+
const details = payload.response.incomplete_details;
|
|
372
|
+
if (isRecord(details))
|
|
373
|
+
assertModelOutputComplete(details.reason);
|
|
374
|
+
}
|
|
367
375
|
throw new ModelHostFailureError("Responses SSE returned an explicit failure");
|
|
368
376
|
}
|
|
369
377
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { legacyModelBindingFailure, projectLegacyModelBindingFailure, } from "./LegacyModelBindingFailure.js";
|
|
2
|
+
import { isPlayFailureKind, } from "../../protocol/playFailure.js";
|
|
1
3
|
import { isNarrativeOrigin, } from "../history/HistoryBackground.js";
|
|
2
4
|
import { encodePlayPromptRun, decodePlayPromptRun, } from "./PlayPromptRun.js";
|
|
3
5
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -71,7 +73,8 @@ export class FileNativePlayTimelineStore {
|
|
|
71
73
|
const eventCount = prefixLength(input.source.events, input.target.events);
|
|
72
74
|
const completedToolCount = prefixLength(input.source.completedTools, input.target.completedTools);
|
|
73
75
|
const authorizationCheckpointCount = prefixLength(input.source.documentAuthorizationCheckpoints ?? [], input.target.documentAuthorizationCheckpoints ?? []);
|
|
74
|
-
if (input.
|
|
76
|
+
if (input.retainCompleteContext ||
|
|
77
|
+
input.target.events.some((event) => event.kind === "assistant" && event.status === "completed")) {
|
|
75
78
|
const encoding = await this.readInitialEncoding(input.sourceWorldId, input.source.chainId).catch(() => null);
|
|
76
79
|
if (encoding !== null)
|
|
77
80
|
await publishImmutableJson(join(targetRoot, "initial-request-encoding.json"), encoding);
|
|
@@ -207,6 +210,9 @@ export class FileNativePlayTimelineStore {
|
|
|
207
210
|
continuationDigest,
|
|
208
211
|
lastRequestAttempt: value.lastRequestAttempt,
|
|
209
212
|
lastFailure: value.lastFailure,
|
|
213
|
+
...(value.lastFailureKind === undefined
|
|
214
|
+
? {}
|
|
215
|
+
: { lastFailureKind: value.lastFailureKind }),
|
|
210
216
|
updatedAt: value.updatedAt,
|
|
211
217
|
};
|
|
212
218
|
await publishJson(join(root, "state.json"), state);
|
|
@@ -235,6 +241,20 @@ export class FileNativePlayTimelineStore {
|
|
|
235
241
|
throw new Error("Play timeline head generation is inconsistent");
|
|
236
242
|
return loaded;
|
|
237
243
|
}
|
|
244
|
+
/** Read only the small active context index, never its transcript or bootstrap. */
|
|
245
|
+
async currentContextRequiresFreshContext(worldId) {
|
|
246
|
+
assertIdentity(worldId, "World ID");
|
|
247
|
+
const head = await this.#readTimelineHead(worldId);
|
|
248
|
+
if (head === null)
|
|
249
|
+
return false;
|
|
250
|
+
const index = await readJson(join(this.#contextRoot(worldId, head.chainId), "index.json"));
|
|
251
|
+
assertContextIndex(index);
|
|
252
|
+
if (index.worldId !== worldId ||
|
|
253
|
+
index.chainId !== head.chainId ||
|
|
254
|
+
index.timelineGeneration !== head.generation)
|
|
255
|
+
throw new Error("Play context durable identity is inconsistent");
|
|
256
|
+
return index.importedFromArchive === true;
|
|
257
|
+
}
|
|
238
258
|
async readContext(worldId, chainId) {
|
|
239
259
|
assertIdentity(worldId, "World ID");
|
|
240
260
|
assertIdentity(chainId, "Call-chain ID");
|
|
@@ -277,6 +297,7 @@ export class FileNativePlayTimelineStore {
|
|
|
277
297
|
chainId,
|
|
278
298
|
previousChainId: indexValue.previousChainId,
|
|
279
299
|
timelineGeneration: indexValue.timelineGeneration,
|
|
300
|
+
...(indexValue.importedFromArchive ? { importedFromArchive: true } : {}),
|
|
280
301
|
...(baseValue.continuityContextId === undefined
|
|
281
302
|
? {}
|
|
282
303
|
: { continuityContextId: baseValue.continuityContextId }),
|
|
@@ -327,6 +348,9 @@ export class FileNativePlayTimelineStore {
|
|
|
327
348
|
lastRequest,
|
|
328
349
|
lastRequestAttempt: stateValue.lastRequestAttempt,
|
|
329
350
|
lastFailure: stateValue.lastFailure,
|
|
351
|
+
...(stateValue.lastFailureKind === undefined
|
|
352
|
+
? {}
|
|
353
|
+
: { lastFailureKind: stateValue.lastFailureKind }),
|
|
330
354
|
updatedAt: stateValue.updatedAt,
|
|
331
355
|
};
|
|
332
356
|
return {
|
|
@@ -445,7 +469,10 @@ export class FileNativePlayTimelineStore {
|
|
|
445
469
|
kind: "event",
|
|
446
470
|
chainId: pointer.chainId,
|
|
447
471
|
current: pointer.chainId === head?.chainId,
|
|
448
|
-
|
|
472
|
+
...(context.index.importedFromArchive
|
|
473
|
+
? { requiresFreshContext: true }
|
|
474
|
+
: {}),
|
|
475
|
+
event: projectLegacyModelBindingFailure(summary),
|
|
449
476
|
});
|
|
450
477
|
pointer = { ...pointer, eventId: pointer.eventId - 1 };
|
|
451
478
|
continue;
|
|
@@ -498,6 +525,9 @@ export class FileNativePlayTimelineStore {
|
|
|
498
525
|
activeStatus: active?.state.status ?? null,
|
|
499
526
|
activeCanRetry: active?.state.canRetry ?? false,
|
|
500
527
|
activeLastFailure: active?.state.lastFailure ?? null,
|
|
528
|
+
...(active?.state.lastFailureKind === undefined
|
|
529
|
+
? {}
|
|
530
|
+
: { activeLastFailureKind: active.state.lastFailureKind }),
|
|
501
531
|
items: reverse.reverse(),
|
|
502
532
|
nextCursor: pointer.chainId === null && pointer.genesisIndex < 0
|
|
503
533
|
? null
|
|
@@ -516,7 +546,7 @@ export class FileNativePlayTimelineStore {
|
|
|
516
546
|
assertPlayEvent(event);
|
|
517
547
|
if (event.id !== eventId)
|
|
518
548
|
throw new Error("Play timeline event identity is inconsistent");
|
|
519
|
-
return normalizePlayEvent(event);
|
|
549
|
+
return projectLegacyModelBindingFailure(normalizePlayEvent(event));
|
|
520
550
|
}
|
|
521
551
|
newGeneration() {
|
|
522
552
|
return `timeline:${randomUUID()}`;
|
|
@@ -597,11 +627,48 @@ export class FileNativePlayTimelineStore {
|
|
|
597
627
|
indexValue.chainId !== chainId ||
|
|
598
628
|
stateValue.chainId !== chainId)
|
|
599
629
|
throw new Error("Play context durable identity is inconsistent");
|
|
630
|
+
// Old versions persisted a configuration preflight failure as a terminal
|
|
631
|
+
// context failure. Recover only the disposition proven by its event prefix.
|
|
632
|
+
// Archive contexts and genuine provider failures retain their restrictions.
|
|
633
|
+
if (!indexValue.importedFromArchive &&
|
|
634
|
+
stateValue.status === "interrupted" &&
|
|
635
|
+
stateValue.lastFailure === legacyModelBindingFailure &&
|
|
636
|
+
(stateValue.lastFailureKind === undefined ||
|
|
637
|
+
stateValue.lastFailureKind === "context_unavailable")) {
|
|
638
|
+
for (let id = stateValue.eventCount; id > 0; id -= 1) {
|
|
639
|
+
const event = await readJson(join(root, "events", numbered(id)));
|
|
640
|
+
assertPlayEvent(event);
|
|
641
|
+
if (event.kind === "followup" ||
|
|
642
|
+
(event.kind === "failure" &&
|
|
643
|
+
event.message === legacyModelBindingFailure))
|
|
644
|
+
continue;
|
|
645
|
+
if (event.kind === "assistant" &&
|
|
646
|
+
event.status === "completed" &&
|
|
647
|
+
event.responseKind === "narrative" &&
|
|
648
|
+
event.continuation === "available" &&
|
|
649
|
+
event.committedHead === stateValue.parentHead) {
|
|
650
|
+
stateValue.status = "ready";
|
|
651
|
+
stateValue.canRetry = false;
|
|
652
|
+
stateValue.lastFailure = null;
|
|
653
|
+
delete stateValue.lastFailureKind;
|
|
654
|
+
}
|
|
655
|
+
else if (event.kind === "failure") {
|
|
656
|
+
stateValue.canRetry = event.failureKind === "retryable";
|
|
657
|
+
stateValue.lastFailure = event.message;
|
|
658
|
+
if (event.failureKind === undefined)
|
|
659
|
+
delete stateValue.lastFailureKind;
|
|
660
|
+
else
|
|
661
|
+
stateValue.lastFailureKind = event.failureKind;
|
|
662
|
+
}
|
|
663
|
+
break;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
600
666
|
return { root, index: indexValue, state: stateValue };
|
|
601
667
|
}
|
|
602
668
|
}
|
|
603
669
|
function contextIndex(value) {
|
|
604
670
|
return {
|
|
671
|
+
...(value.importedFromArchive ? { importedFromArchive: true } : {}),
|
|
605
672
|
schemaVersion: 3,
|
|
606
673
|
kind: "play_context_index",
|
|
607
674
|
worldId: value.worldId,
|
|
@@ -724,6 +791,8 @@ function assertContextIndex(value) {
|
|
|
724
791
|
if (!isRecord(value) ||
|
|
725
792
|
value.schemaVersion !== 3 ||
|
|
726
793
|
value.kind !== "play_context_index" ||
|
|
794
|
+
(value.importedFromArchive !== undefined &&
|
|
795
|
+
value.importedFromArchive !== true) ||
|
|
727
796
|
typeof value.worldId !== "string" ||
|
|
728
797
|
typeof value.chainId !== "string" ||
|
|
729
798
|
(value.previousChainId !== null &&
|
|
@@ -769,6 +838,8 @@ function assertContextState(value) {
|
|
|
769
838
|
!/^[a-f0-9]{64}$/u.test(value.continuationDigest) ||
|
|
770
839
|
!validCount(value.lastRequestAttempt) ||
|
|
771
840
|
(value.lastFailure !== null && typeof value.lastFailure !== "string") ||
|
|
841
|
+
(value.lastFailureKind !== undefined &&
|
|
842
|
+
!isPlayFailureKind(value.lastFailureKind)) ||
|
|
772
843
|
typeof value.updatedAt !== "number")
|
|
773
844
|
throw new Error("Play context state has an invalid shape");
|
|
774
845
|
}
|
|
@@ -839,6 +910,8 @@ function isReleasedPlayContext(value) {
|
|
|
839
910
|
(value.lastRequest !== null && !isRecord(value.lastRequest)) ||
|
|
840
911
|
!validCount(value.lastRequestAttempt) ||
|
|
841
912
|
(value.lastFailure !== null && typeof value.lastFailure !== "string") ||
|
|
913
|
+
(value.lastFailureKind !== undefined &&
|
|
914
|
+
!isPlayFailureKind(value.lastFailureKind)) ||
|
|
842
915
|
typeof value.updatedAt !== "number")
|
|
843
916
|
return false;
|
|
844
917
|
try {
|
|
@@ -976,7 +1049,10 @@ function assertPlayEvent(value) {
|
|
|
976
1049
|
return;
|
|
977
1050
|
}
|
|
978
1051
|
if ((value.kind !== "failure" && value.kind !== "cancellation") ||
|
|
979
|
-
typeof value.message !== "string"
|
|
1052
|
+
typeof value.message !== "string" ||
|
|
1053
|
+
(value.kind === "failure" &&
|
|
1054
|
+
value.failureKind !== undefined &&
|
|
1055
|
+
!isPlayFailureKind(value.failureKind)))
|
|
980
1056
|
throw new Error("Play timeline event has an invalid kind");
|
|
981
1057
|
}
|
|
982
1058
|
function assertTimelineEventSummary(value) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const legacyModelBindingFailure = "The selected model connection does not match the frozen model binding; start a fresh context.";
|
|
2
|
+
/** Presentation only: immutable historical events remain unchanged on disk. */
|
|
3
|
+
export function projectLegacyModelBindingFailure(event) {
|
|
4
|
+
if (event.kind === "failure" &&
|
|
5
|
+
event.message === legacyModelBindingFailure &&
|
|
6
|
+
(event.failureKind === undefined ||
|
|
7
|
+
event.failureKind === "context_unavailable"))
|
|
8
|
+
return { ...event, failureKind: "configuration_mismatch" };
|
|
9
|
+
return event;
|
|
10
|
+
}
|