narraeon 0.5.2 → 0.5.4
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/dist/node/runtime/history/HistoryQuery.js +550 -0
- package/dist/node/runtime/history/HistoryRendering.js +86 -0
- package/dist/node/runtime/play/PlayCallChain.js +35 -294
- package/dist/node/runtime/play/PlayCallChainError.js +6 -0
- package/dist/node/runtime/play/PlayContinuity.js +19 -14
- package/dist/node/runtime/play/PlayDocumentTools.js +58 -125
- package/dist/node/runtime/play/PlayTraceRetention.js +264 -0
- package/dist/node/runtime/prompt/FileNativePromptCompiler.js +97 -68
- package/dist/node/runtime/prompt/FileNativeToolRegistry.js +58 -14
- package/dist/node/shared/default-play-prompts.js +10 -6
- package/dist/node/shared/default-preset-host.js +24 -16
- package/dist/node/shared/history-use-guidance.js +6 -0
- package/dist/node/shared/ordered-play-prompts.js +7 -2
- package/dist/web/assets/index-B1Kd7NCp.js +1223 -0
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
- package/dist/web/assets/index-CVllMgyP.js +0 -1207
|
@@ -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,
|
|
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
|
|
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
|
|
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
|
-
|
|
657
|
-
|
|
658
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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)
|
|
@@ -2109,15 +1978,19 @@ function prepareTool(session, working, exchange, call, locale) {
|
|
|
2109
1978
|
? {
|
|
2110
1979
|
ok: true,
|
|
2111
1980
|
markdown: locale === "zh-CN"
|
|
2112
|
-
? "
|
|
2113
|
-
: "Checkpoint registered. It
|
|
1981
|
+
? "检查点已登记;随后非空且无工具调用的最终正文成功提交后生效,边界包含该正文。它仅标记历史重放边界,不结束世界内事件;登记后仍可继续调用工具。玩家随后可自行选择全新上下文。"
|
|
1982
|
+
: "Checkpoint registered. It takes effect after the subsequent nonempty tool-free final prose successfully commits, including that prose in its boundary. It marks a history-replay boundary without ending an in-world event; further tool calls may follow registration. The player may then choose a fresh context.",
|
|
2114
1983
|
}
|
|
2115
1984
|
: {
|
|
2116
1985
|
ok: false,
|
|
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,
|
|
@@ -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;
|
|
@@ -28,8 +30,8 @@ export function checkpointHistory(history, checkpoint) {
|
|
|
28
30
|
}
|
|
29
31
|
export function playerRoundMarker(rounds, hasCheckpoint, locale) {
|
|
30
32
|
const markdown = locale === "zh-CN"
|
|
31
|
-
? `距上次检查点已完成 ${rounds} 回合。${hasCheckpoint ? "" : "当前尚无检查点,以世界起点计数。"}
|
|
32
|
-
: `Completed player rounds since the last checkpoint: ${rounds}
|
|
33
|
+
? `距上次检查点已完成 ${rounds} 回合。${hasCheckpoint ? "" : "当前尚无检查点,以世界起点计数。"}`
|
|
34
|
+
: `Completed player rounds since the last checkpoint: ${rounds}.${hasCheckpoint ? "" : " No checkpoint yet; counting from the world origin."}`;
|
|
33
35
|
return {
|
|
34
36
|
kind: "runtime_notice",
|
|
35
37
|
notice: "checkpoint_rounds",
|
|
@@ -44,8 +46,8 @@ export function continuationNotice(locale) {
|
|
|
44
46
|
kind: "runtime_notice",
|
|
45
47
|
notice: "continuation",
|
|
46
48
|
text: locale === "zh-CN"
|
|
47
|
-
? "[Runtime 续写提示]\n
|
|
48
|
-
: "[Runtime continuation]\nThis send adds no new player input.
|
|
49
|
+
? "[Runtime 续写提示]\n本次发送没有追加新的玩家原文。已有的最终主持正文已展示并提交为历史;本次响应接在其后,不重新提交旧正文。回复提交不表示世界内活动已经结束。"
|
|
50
|
+
: "[Runtime continuation]\nThis send adds no new player input. Existing final narrator prose has been displayed and committed as history; this response follows it without resubmitting it. A committed reply does not mean an in-world activity has ended.",
|
|
49
51
|
};
|
|
50
52
|
}
|
|
51
53
|
export function toolStepNotice(locale) {
|
|
@@ -53,8 +55,8 @@ export function toolStepNotice(locale) {
|
|
|
53
55
|
kind: "runtime_notice",
|
|
54
56
|
notice: "tool_step",
|
|
55
57
|
text: locale === "zh-CN"
|
|
56
|
-
? "[Runtime 结算提示]\n
|
|
57
|
-
: "[Runtime settlement]\nThe previous response called tools, so its text
|
|
58
|
+
? "[Runtime 结算提示]\n上一响应调用了工具,其中的文字尚未作为最终故事展示或提交。工具已按回执结算;请根据回执,用无工具调用的非空最终正文呈现本轮尚未展示的内容,并与已提交状态一致。工具结算不要求在这些内容之后再推进剧情。"
|
|
59
|
+
: "[Runtime settlement]\nThe previous response called tools, so its text has not yet been displayed or committed as final story. The tools have settled as their receipts state. Use those receipts to present this turn’s still-unshown content in nonempty final prose with no tool calls, consistent with committed state. Tool settlement does not require advancing the story beyond that content.",
|
|
58
60
|
};
|
|
59
61
|
}
|
|
60
62
|
export function playerInputAppend(input) {
|
|
@@ -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
|
|
73
|
-
if (
|
|
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((
|
|
84
|
-
source: `runtime:checkpoint-history:${id}`,
|
|
85
|
-
markdown:
|
|
88
|
+
...entries.map((message) => ({
|
|
89
|
+
source: `runtime:checkpoint-history:${message.id}`,
|
|
90
|
+
markdown: renderHistoryMessage(message, locale),
|
|
86
91
|
})),
|
|
87
92
|
];
|
|
88
93
|
}
|