narraeon 0.5.2 → 0.5.3

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.
@@ -1,7 +1,10 @@
1
+ import { hasLegacyHistoryTools } from "../prompt/FileNativeToolRegistry.js";
2
+ import { PlayCallChainError } from "./PlayCallChainError.js";
3
+ import { preparePlayerRevisionTrace, prepareForkTrace, restorePlayDocuments, independentContextCopy, } from "./PlayTraceRetention.js";
1
4
  import { currentPlayPrompt, recordedPlayPrompt, playPromptRunsThroughEvents, } from "./PlayPromptRun.js";
2
5
  import { comparePromptPrefixes } from "../prompt/WorldPromptDiagnostics.js";
3
6
  import { createHash } from "node:crypto";
4
- import { completedPlayerRounds, continuationNotice, isPlayerRoundMarker, playerInputAppend, toolStepNotice, } from "./PlayContinuity.js";
7
+ import { completedPlayerRounds, continuationNotice, playerInputAppend, toolStepNotice, } from "./PlayContinuity.js";
5
8
  import { renderFreshContextCoverage } from "../prompt/WorldMaterialCoverage.js";
6
9
  import { renderWriteSizeAdvice, readDeclaredWorldClock, } from "../prompt/WorldMaintenanceReport.js";
7
10
  import { WorldDocumentStore } from "../world/WorldDocumentStore.js";
@@ -13,6 +16,8 @@ import { FileNativePlayDocuments, fingerprintControl, } from "./PlayDocumentTool
13
16
  const callChainToolNames = new Set([
14
17
  "state_list",
15
18
  "history_list",
19
+ "history_search",
20
+ "history_read",
16
21
  // Kept executable only for contexts whose frozen tool universe contains it.
17
22
  "context_list",
18
23
  "context_search",
@@ -23,12 +28,7 @@ const callChainToolNames = new Set([
23
28
  "world_checkpoint",
24
29
  ]);
25
30
  const projectedEventTail = 40;
26
- export class PlayCallChainError extends Error {
27
- constructor(message) {
28
- super(message);
29
- this.name = "PlayCallChainError";
30
- }
31
- }
31
+ export { PlayCallChainError } from "./PlayCallChainError.js";
32
32
  /**
33
33
  * The production play loop: one durable logical context, no Runtime-authored
34
34
  * adjudication/narration phases. Responses with tool calls are intermediate
@@ -625,8 +625,7 @@ export class PlayCallChain {
625
625
  : null;
626
626
  const timelineGeneration = playerRevisionTimelineGeneration(input.source.timelineGeneration, request.operationId, request.worldId, request.continuation);
627
627
  const requestFingerprint = playerRevisionRequestFingerprint(request, input.selectedEvent.committedHead, restoresHead);
628
- const prefixEvents = structuredClone(sourceContext.events.slice(0, input.selectedEventIndex));
629
- const transcript = transcriptThroughEvents(sourceContext.transcript, prefixEvents);
628
+ const traceRetention = preparePlayerRevisionTrace(sourceContext, input.selectedEventIndex);
630
629
  const outcome = await this.#worlds.reviseTimeline({
631
630
  operationId: request.operationId,
632
631
  worldId: request.worldId,
@@ -636,33 +635,17 @@ export class PlayCallChain {
636
635
  replacementText: request.replacementText,
637
636
  requestFingerprint,
638
637
  });
639
- const events = [
640
- ...prefixEvents,
641
- {
642
- id: input.selectedEvent.id,
643
- kind: "player",
644
- exchangeId: request.replacementExchangeId,
645
- text: request.replacementText,
646
- context: input.selectedEvent.context,
647
- committedHead: outcome.head,
648
- },
649
- ];
650
- const completedKeys = completedToolKeys(prefixEvents);
651
638
  const [baseline, selected, binding] = await Promise.all([
652
639
  this.#worlds.recoverEndpoint(request.worldId, sourceContext.baselineHead),
653
640
  this.#worlds.recoverEndpoint(request.worldId, restoresHead),
654
641
  this.#worlds.bindPlayCallChain(request.worldId),
655
642
  ]);
656
- transcript.push(...playerInputAppend({
657
- history: Object.fromEntries(selected.history.map(({ messageId, exactText }) => [
658
- messageId,
659
- exactText,
660
- ])),
661
- checkpoint: selected.narrativeCheckpoint,
643
+ const continuedTrace = traceRetention.withReplacement(selected, {
644
+ head: outcome.head,
645
+ exchangeId: request.replacementExchangeId,
662
646
  text: request.replacementText,
663
647
  locale: this.#compiler.locale,
664
- checkpointAvailable: sourceContext.tools.some(({ name }) => name === "world_checkpoint"),
665
- }));
648
+ });
666
649
  if (binding.parentHead !== outcome.head)
667
650
  throw new PlayCallChainError("The timeline revision was committed, but current-world materialization has not reached the new endpoint.");
668
651
  if (request.continuation === "fresh_context") {
@@ -673,7 +656,7 @@ export class PlayCallChain {
673
656
  sourceContext,
674
657
  sourceContexts: input.sourceContexts,
675
658
  selectedContextIndex: input.selectedContextIndex,
676
- prefixEvents,
659
+ traceRetention,
677
660
  baseline,
678
661
  selected,
679
662
  binding,
@@ -694,9 +677,8 @@ export class PlayCallChain {
694
677
  playCallChain: projectView(revised),
695
678
  };
696
679
  }
697
- const documents = restorePlayDocuments(binding.files, sourceContext, prefixEvents);
698
680
  const revised = {
699
- continuityContextId: sourceContext.continuityContextId ?? sourceContext.chainId,
681
+ ...continuedTrace.atEndpoint({ baseline, selected, binding }),
700
682
  schemaVersion: 3,
701
683
  kind: "play_call_chain",
702
684
  chainId: playerRevisionChainId(sourceContext.chainId, request.operationId, request.worldId, request.continuation),
@@ -706,44 +688,7 @@ export class PlayCallChain {
706
688
  ? null
707
689
  : input.sourceContexts[input.selectedContextIndex - 1].chainId,
708
690
  timelineGeneration,
709
- baselineHead: sourceContext.baselineHead,
710
- baselineHistoryLength: sourceContext.baselineHistoryLength ?? baseline.history.length,
711
691
  parentHead: outcome.head,
712
- promptRuns: playPromptRunsThroughEvents(sourceContext, events),
713
- ...(sourceContext.presetFiles === undefined
714
- ? {}
715
- : { presetFiles: structuredClone(sourceContext.presetFiles) }),
716
- playPreset: structuredClone(sourceContext.playPreset),
717
- ...(sourceContext.followups === undefined
718
- ? {}
719
- : { followups: structuredClone(sourceContext.followups) }),
720
- ...(sourceContext.playPresetScriptsEnabled === undefined
721
- ? {}
722
- : {
723
- playPresetScriptsEnabled: sourceContext.playPresetScriptsEnabled,
724
- }),
725
- ...(sourceContext.modelBinding === undefined
726
- ? {}
727
- : { modelBinding: structuredClone(sourceContext.modelBinding) }),
728
- status: "ready",
729
- canRetry: false,
730
- bootstrap: structuredClone(sourceContext.bootstrap),
731
- tools: structuredClone(sourceContext.tools),
732
- transcript,
733
- events,
734
- completedTools: sourceContext.completedTools
735
- .filter(({ key }) => completedKeys.has(key))
736
- .map((item) => structuredClone(item)),
737
- documentAuthorizationCheckpoints: authorizationCheckpointsThroughEvents(sourceContext, events, documents.authorizationCheckpoint()),
738
- changedDocuments: changedDocumentsAtHead(sourceContext.changedDocuments, baseline.state, selected.state),
739
- nextMaterials: structuredClone(binding.additionalMaterials),
740
- nextEventId: Math.max(0, ...events.map(({ id }) => id)) + 1,
741
- exchange: Math.max(0, ...prefixEvents
742
- .filter((event) => event.kind === "assistant" && event.status === "completed")
743
- .map(({ exchange }) => exchange)),
744
- lastRequest: null,
745
- lastRequestAttempt: 0,
746
- lastFailure: null,
747
692
  updatedAt: Date.now(),
748
693
  };
749
694
  await this.#worlds.playTimeline.persist(structuredClone(revised));
@@ -797,12 +742,14 @@ export class PlayCallChain {
797
742
  let previousChainId = input.selectedContextIndex === 0
798
743
  ? null
799
744
  : input.sourceContexts[input.selectedContextIndex - 1].chainId;
800
- if (input.prefixEvents.length > 0) {
801
- const prefixTranscript = transcriptThroughEvents(sourceContext.transcript, input.prefixEvents);
802
- const completedKeys = completedToolKeys(input.prefixEvents);
803
- const documents = restorePlayDocuments(input.binding.files, sourceContext, input.prefixEvents);
745
+ const retainedPrefix = input.traceRetention.pagePrefixAtEndpoint({
746
+ baseline: input.baseline,
747
+ selected: input.selected,
748
+ binding: input.binding,
749
+ });
750
+ if (retainedPrefix !== null) {
804
751
  const prefix = {
805
- continuityContextId: sourceContext.continuityContextId ?? sourceContext.chainId,
752
+ ...retainedPrefix,
806
753
  schemaVersion: 3,
807
754
  kind: "play_call_chain",
808
755
  chainId: derivedChainId(sourceContext.chainId, "timeline-revision-prefix:" + request.operationId, request.worldId),
@@ -810,42 +757,7 @@ export class PlayCallChain {
810
757
  previousContexts: [],
811
758
  previousChainId,
812
759
  timelineGeneration: input.timelineGeneration,
813
- baselineHead: sourceContext.baselineHead,
814
- baselineHistoryLength: sourceContext.baselineHistoryLength ?? input.baseline.history.length,
815
760
  parentHead: input.restoresHead,
816
- promptRuns: playPromptRunsThroughEvents(sourceContext, input.prefixEvents),
817
- ...(sourceContext.presetFiles === undefined
818
- ? {}
819
- : { presetFiles: structuredClone(sourceContext.presetFiles) }),
820
- playPreset: structuredClone(sourceContext.playPreset),
821
- ...(sourceContext.followups === undefined
822
- ? {}
823
- : { followups: structuredClone(sourceContext.followups) }),
824
- ...(sourceContext.playPresetScriptsEnabled === undefined
825
- ? {}
826
- : {
827
- playPresetScriptsEnabled: sourceContext.playPresetScriptsEnabled,
828
- }),
829
- ...(sourceContext.modelBinding === undefined
830
- ? {}
831
- : { modelBinding: structuredClone(sourceContext.modelBinding) }),
832
- status: "ready",
833
- canRetry: false,
834
- bootstrap: structuredClone(sourceContext.bootstrap),
835
- tools: structuredClone(sourceContext.tools),
836
- transcript: prefixTranscript,
837
- events: structuredClone(input.prefixEvents),
838
- completedTools: sourceContext.completedTools
839
- .filter(({ key }) => completedKeys.has(key))
840
- .map((item) => structuredClone(item)),
841
- documentAuthorizationCheckpoints: authorizationCheckpointsThroughEvents(sourceContext, input.prefixEvents, documents.authorizationCheckpoint()),
842
- changedDocuments: changedDocumentsAtHead(sourceContext.changedDocuments, input.baseline.state, input.selected.state),
843
- nextMaterials: structuredClone(input.selected.additionalMaterials),
844
- nextEventId: Math.max(0, ...input.prefixEvents.map(({ id }) => id)) + 1,
845
- exchange: completedAssistantExchange(input.prefixEvents),
846
- lastRequest: null,
847
- lastRequestAttempt: 0,
848
- lastFailure: null,
849
761
  updatedAt: Date.now(),
850
762
  };
851
763
  await this.#worlds.playTimeline.persistDetached(structuredClone(prefix));
@@ -928,20 +840,14 @@ export class PlayCallChain {
928
840
  ? input.targetWorldId
929
841
  : input.sourceWorldId, input.sourceHead),
930
842
  ]);
931
- const selectsCompleteContext = input.sourceEvents.length === sourceContext.events.length;
932
- // A complete context already defines the selected trace closure. Preserve
933
- // its model transcript verbatim instead of reconstructing it from the
934
- // independently persisted page-event projection.
935
- const transcript = selectsCompleteContext
936
- ? structuredClone(sourceContext.transcript)
937
- : transcriptThroughEvents(sourceContext.transcript, input.sourceEvents);
938
- const completedKeys = selectsCompleteContext
939
- ? null
940
- : completedToolKeys(input.sourceEvents);
941
- const events = structuredClone(input.sourceEvents);
843
+ const traceRetention = prepareForkTrace(sourceContext, input.sourceEvents);
942
844
  const derivedBinding = input.targetBinding ??
943
845
  (await this.#worlds.bindPlayCallChain(input.targetWorldId));
944
- const derivedDocuments = restorePlayDocuments(derivedBinding.files, sourceContext, input.sourceEvents);
846
+ const retainedTrace = traceRetention.atEndpoint({
847
+ baseline,
848
+ selected,
849
+ binding: derivedBinding,
850
+ });
945
851
  const now = Date.now();
946
852
  const timelineGeneration = this.#worlds.playTimeline.newGeneration();
947
853
  let previousChainId = null;
@@ -971,7 +877,7 @@ export class PlayCallChain {
971
877
  previousChainId = chainId;
972
878
  }
973
879
  const derived = {
974
- continuityContextId: sourceContext.continuityContextId ?? sourceContext.chainId,
880
+ ...retainedTrace,
975
881
  schemaVersion: 3,
976
882
  kind: "play_call_chain",
977
883
  chainId: derivedChainId(sourceContext.chainId, input.branchIdentity, input.targetWorldId),
@@ -979,44 +885,7 @@ export class PlayCallChain {
979
885
  previousContexts: [],
980
886
  previousChainId,
981
887
  timelineGeneration,
982
- baselineHead: sourceContext.baselineHead,
983
- baselineHistoryLength: sourceContext.baselineHistoryLength ?? baseline.history.length,
984
888
  parentHead: input.sourceHead,
985
- promptRuns: playPromptRunsThroughEvents(sourceContext, events),
986
- ...(sourceContext.presetFiles === undefined
987
- ? {}
988
- : { presetFiles: structuredClone(sourceContext.presetFiles) }),
989
- playPreset: structuredClone(sourceContext.playPreset),
990
- ...(sourceContext.followups === undefined
991
- ? {}
992
- : { followups: structuredClone(sourceContext.followups) }),
993
- ...(sourceContext.playPresetScriptsEnabled === undefined
994
- ? {}
995
- : {
996
- playPresetScriptsEnabled: sourceContext.playPresetScriptsEnabled,
997
- }),
998
- ...(sourceContext.modelBinding === undefined
999
- ? {}
1000
- : { modelBinding: structuredClone(sourceContext.modelBinding) }),
1001
- status: "ready",
1002
- canRetry: false,
1003
- bootstrap: structuredClone(sourceContext.bootstrap),
1004
- tools: structuredClone(sourceContext.tools),
1005
- transcript,
1006
- events,
1007
- completedTools: sourceContext.completedTools
1008
- .filter(({ key }) => completedKeys === null || completedKeys.has(key))
1009
- .map((item) => structuredClone(item)),
1010
- documentAuthorizationCheckpoints: authorizationCheckpointsThroughEvents(sourceContext, events, derivedDocuments.authorizationCheckpoint()),
1011
- changedDocuments: changedDocumentsAtHead(sourceContext.changedDocuments, baseline.state, selected.state),
1012
- nextMaterials: structuredClone(derivedBinding.additionalMaterials),
1013
- nextEventId: Math.max(0, ...events.map(({ id }) => id)) + 1,
1014
- exchange: Math.max(0, ...events
1015
- .filter((event) => event.kind === "assistant" && event.status === "completed")
1016
- .map(({ exchange }) => exchange)),
1017
- lastRequest: null,
1018
- lastRequestAttempt: 0,
1019
- lastFailure: null,
1020
889
  updatedAt: now,
1021
890
  };
1022
891
  if (input.targetWorldRoot === undefined)
@@ -2117,7 +1986,11 @@ function prepareTool(session, working, exchange, call, locale) {
2117
1986
  failureKind: "protocol",
2118
1987
  markdown: "world_checkpoint requires an empty argument object.",
2119
1988
  }
2120
- : session.documents.execute(call, session.history);
1989
+ : session.documents.execute(call, session.history, {
1990
+ historyScope: session.worldId,
1991
+ locale,
1992
+ legacyHistoryTools: hasLegacyHistoryTools(currentPlayPrompt(session).tools),
1993
+ });
2121
1994
  }
2122
1995
  catch (error) {
2123
1996
  result = {
@@ -2178,53 +2051,6 @@ function mergeChangedDocuments(session, changes) {
2178
2051
  session.changedDocuments[index] = projected;
2179
2052
  }
2180
2053
  }
2181
- function restorePlayDocuments(files, context, events) {
2182
- const documents = new FileNativePlayDocuments(files);
2183
- try {
2184
- const checkpoint = documentAuthorizationThroughEvents(context, events);
2185
- const runs = playPromptRunsThroughEvents(context, events);
2186
- if (checkpoint === undefined || runs.length > 0)
2187
- documents.bindBootstrap(currentPlayPrompt({ ...context, promptRuns: runs }).bootstrap);
2188
- if (checkpoint !== undefined)
2189
- documents.restoreAuthorizationCheckpoint(checkpoint.authorization);
2190
- }
2191
- catch (error) {
2192
- throw new PlayCallChainError(`Call-chain document authorization could not be restored: ${error instanceof Error ? error.message : "invalid checkpoint"}。`);
2193
- }
2194
- return documents;
2195
- }
2196
- function documentAuthorizationThroughEvents(context, events) {
2197
- const checkpoints = context.documentAuthorizationCheckpoints;
2198
- if (checkpoints === undefined || checkpoints.length === 0)
2199
- return undefined;
2200
- const selectedEventIds = new Set(events.map(({ id }) => id));
2201
- const selected = checkpoints.findLast(({ afterEventId }) => afterEventId === 0 || selectedEventIds.has(afterEventId));
2202
- // Legacy V1 records had no durable dynamic authorization. If the selected
2203
- // prefix predates their first lazily-written checkpoint, retain the exact
2204
- // old recovery behavior and rebuild only bootstrap authorization.
2205
- return selected === undefined ? undefined : structuredClone(selected);
2206
- }
2207
- function independentContextCopy(source) {
2208
- const context = structuredClone(source);
2209
- context.continuityContextId ??= source.chainId;
2210
- delete context.derivedFrom;
2211
- delete context.branchedBeforePlayer;
2212
- return context;
2213
- }
2214
- function authorizationCheckpointsThroughEvents(context, events, fallback) {
2215
- const eventIds = new Set(events.map(({ id }) => id));
2216
- const selected = (context.documentAuthorizationCheckpoints ?? [])
2217
- .filter(({ afterEventId }) => afterEventId === 0 || eventIds.has(afterEventId))
2218
- .map((checkpoint) => structuredClone(checkpoint));
2219
- if (selected.length > 0)
2220
- return selected;
2221
- return [
2222
- {
2223
- afterEventId: Math.max(0, ...events.map(({ id }) => id)),
2224
- authorization: structuredClone(fallback),
2225
- },
2226
- ];
2227
- }
2228
2054
  function projectContext(context) {
2229
2055
  return {
2230
2056
  chainId: context.chainId,
@@ -2343,86 +2169,6 @@ function eventsThroughHead(events, head) {
2343
2169
  end += 1;
2344
2170
  return structuredClone(events.slice(0, end));
2345
2171
  }
2346
- function transcriptThroughEvents(transcript, events) {
2347
- const result = [];
2348
- let cursor = 0;
2349
- for (const [index, event] of events.entries()) {
2350
- if (event.kind === "player") {
2351
- const marker = transcript[cursor];
2352
- if (isPlayerRoundMarker(marker)) {
2353
- result.push(structuredClone(marker));
2354
- cursor += 1;
2355
- }
2356
- const item = transcript[cursor];
2357
- if (item?.kind !== "player" || item.text !== event.text)
2358
- throw new PlayCallChainError("Source call-chain events do not match the model transcript, so a fork cannot be created safely.");
2359
- result.push(structuredClone(item));
2360
- cursor += 1;
2361
- continue;
2362
- }
2363
- if (event.kind === "assistant" && event.status === "completed") {
2364
- const notice = transcript[cursor];
2365
- const hasContinuationNotice = notice?.kind === "runtime_notice" && notice.notice === "continuation";
2366
- const item = transcript[cursor + (hasContinuationNotice ? 1 : 0)];
2367
- const hasToolCall = events
2368
- .slice(index + 1)
2369
- .find((candidate) => candidate.kind === "player" ||
2370
- candidate.kind === "assistant" ||
2371
- candidate.kind === "tool_call");
2372
- const recorded = event.text.trim().length > 0 ||
2373
- hasToolCall?.kind === "tool_call" ||
2374
- item?.kind === "assistant";
2375
- if (!recorded)
2376
- continue;
2377
- if (item?.kind !== "assistant" || item.text !== event.text)
2378
- throw new PlayCallChainError("Source call-chain responses do not match the model transcript, so a fork cannot be created safely.");
2379
- // A continuation notice belongs to the generation it precedes, never
2380
- // to a fork ending at the previously completed narrative.
2381
- if (hasContinuationNotice) {
2382
- result.push(structuredClone(notice));
2383
- cursor += 1;
2384
- }
2385
- result.push(structuredClone(item));
2386
- cursor += 1;
2387
- continue;
2388
- }
2389
- if (event.kind === "tool_result") {
2390
- const item = transcript[cursor];
2391
- if (item?.kind !== "tool" || item.toolCallId !== event.callId)
2392
- throw new PlayCallChainError("Source call-chain tool results do not match the model transcript, so a fork cannot be created safely.");
2393
- result.push(structuredClone(item));
2394
- cursor += 1;
2395
- // A tool-step notice follows the entire settled batch and remains part
2396
- // of its closure even when the selected endpoint stops at that batch.
2397
- const notice = transcript[cursor];
2398
- if (notice?.kind === "runtime_notice" && notice.notice === "tool_step") {
2399
- result.push(structuredClone(notice));
2400
- cursor += 1;
2401
- }
2402
- }
2403
- }
2404
- return result;
2405
- }
2406
- function completedToolKeys(events) {
2407
- const keys = new Set();
2408
- let exchange = null;
2409
- for (const event of events) {
2410
- if (event.kind === "player")
2411
- exchange = null;
2412
- else if (event.kind === "assistant")
2413
- exchange = event.exchange;
2414
- else if (event.kind === "tool_call" && exchange !== null)
2415
- keys.add(`${exchange}:${event.callId}`);
2416
- }
2417
- return keys;
2418
- }
2419
- function changedDocumentsAtHead(changes, baseline, selected) {
2420
- const before = new Map(baseline.map((file) => [file.path, file.contents]));
2421
- const after = new Map(selected.map((file) => [file.path, file.contents]));
2422
- return changes
2423
- .filter(({ path }) => after.has(path) && before.get(path) !== after.get(path))
2424
- .map((change) => structuredClone(change));
2425
- }
2426
2172
  function derivedChainId(sourceChainId, branchIdentity, targetWorldId) {
2427
2173
  return `play-branch-${createHash("sha256")
2428
2174
  .update(`${sourceChainId}\0${branchIdentity}\0${targetWorldId}`)
@@ -2473,11 +2219,6 @@ function playerRevisionTimelineGeneration(sourceGeneration, operationId, worldId
2473
2219
  .digest("hex")
2474
2220
  .slice(0, 40)}`;
2475
2221
  }
2476
- function completedAssistantExchange(events) {
2477
- return Math.max(0, ...events
2478
- .filter((event) => event.kind === "assistant" && event.status === "completed")
2479
- .map(({ exchange }) => exchange));
2480
- }
2481
2222
  function historyEntries(history) {
2482
2223
  return Object.entries(history).map(([path, contents]) => ({
2483
2224
  path,
@@ -0,0 +1,6 @@
1
+ export class PlayCallChainError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = "PlayCallChainError";
5
+ }
6
+ }
@@ -1,3 +1,5 @@
1
+ import { HistoryQuery, historyInputs } from "../history/HistoryQuery.js";
2
+ import { renderHistoryMessage, renderHistoryInjectionNotice, } from "../history/HistoryRendering.js";
1
3
  /** Narrative history contains only committed original inputs and final prose. */
2
4
  export function completedPlayerRounds(history) {
3
5
  let pending = false;
@@ -68,21 +70,24 @@ export function playerInputAppend(input) {
68
70
  { kind: "player", text: input.text },
69
71
  ];
70
72
  }
71
- export function checkpointReplayBlocks(history, checkpoint, locale) {
72
- const entries = checkpointHistory(history, checkpoint);
73
- if (entries.length === 0)
73
+ export function checkpointReplayBlocks(history, checkpoint, locale, excluded = new Set(), snapshot = new HistoryQuery("checkpoint-replay", historyInputs(Object.entries(history).map(([path, contents]) => ({ path, contents }))))) {
74
+ const eligible = checkpointHistory(history, checkpoint);
75
+ if (eligible.length === 0)
74
76
  return [];
77
+ const ids = new Set(eligible.filter(([id]) => !excluded.has(id)).map(([id]) => id));
78
+ const entries = snapshot.messages.filter(({ id }) => ids.has(id));
75
79
  const zh = locale === "zh-CN";
76
80
  return [
77
81
  {
78
82
  source: "runtime:checkpoint-history:notice",
79
- markdown: zh
83
+ markdown: (zh
80
84
  ? "# 检查点后的已提交原文\n\n以下是当前时间线上最近一次已生效检查点之后的玩家原文与最终主持叙事;尚无检查点时从世界起点选取,开场白除外。部分结果可能已写入当前文档;这些记录用于核对连续性,不代表需要再次执行其中的事件。当前明确的世界修订优先,不能用旧叙事推翻修订。"
81
- : "# Committed history after the checkpoint\n\nThese are the current timeline’s original player inputs and final narratives after its last effective checkpoint (or the world origin if none, excluding the opening). Some results may already be in current documents. These records support continuity, not repeated execution. Explicit current world corrections take precedence over old narrative.",
85
+ : "# Committed history after the checkpoint\n\nThese are the current timeline’s original player inputs and final narratives after its last effective checkpoint (or the world origin if none, excluding the opening). Some results may already be in current documents. These records support continuity, not repeated execution. Explicit current world corrections take precedence over old narrative.") +
86
+ `\n\n${renderHistoryInjectionNotice(snapshot.messages.length, entries, "checkpoint", locale)}`,
82
87
  },
83
- ...entries.map(([id, text]) => ({
84
- source: `runtime:checkpoint-history:${id}`,
85
- markdown: `## ${id.endsWith(".player") ? (zh ? "玩家原文" : "Player input") : zh ? "主持叙事" : "Host narrative"}\n\n${text}`,
88
+ ...entries.map((message) => ({
89
+ source: `runtime:checkpoint-history:${message.id}`,
90
+ markdown: renderHistoryMessage(message, locale),
86
91
  })),
87
92
  ];
88
93
  }