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.
- 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 +33 -292
- package/dist/node/runtime/play/PlayCallChainError.js +6 -0
- package/dist/node/runtime/play/PlayContinuity.js +13 -8
- 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 +91 -62
- package/dist/node/runtime/prompt/FileNativeToolRegistry.js +56 -12
- 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-EIOVZb8X.js +1211 -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)
|
|
@@ -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,
|
|
@@ -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
|
|
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
|
}
|