narraeon 0.5.4 → 0.5.6
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/V1Runtime.js +2 -0
- package/dist/node/runtime/history/HistoryBackground.js +84 -0
- package/dist/node/runtime/history/HistoryQuery.js +7 -2
- package/dist/node/runtime/history/HistoryRendering.js +31 -4
- package/dist/node/runtime/play/FileNativeContinuityCorrection.js +2 -0
- package/dist/node/runtime/play/FileNativePlayAdvanceStore.js +5 -0
- package/dist/node/runtime/play/FileNativePlayTimelineStore.js +11 -0
- package/dist/node/runtime/play/PlayCallChain.js +68 -9
- package/dist/node/runtime/play/PlayTraceRetention.js +7 -0
- package/dist/node/runtime/prompt/FileNativePromptCompiler.js +8 -37
- package/dist/node/runtime/prompt/FileNativeToolRegistry.js +2 -2
- package/dist/node/runtime/world/FileNativeAuthorityV3.js +7 -4
- package/dist/node/runtime/world/FileNativeHistoryProjection.js +11 -0
- package/dist/node/runtime/world/FileNativeWorldStore.js +78 -16
- package/dist/node/runtime/world/WorldYamlRendering.js +28 -0
- package/dist/node/shared/default-preset-host.js +12 -6
- package/dist/node/shared/default-setting-improvement-prompt.js +4 -0
- package/dist/node/shared/history-use-guidance.js +2 -2
- package/dist/node/shared/ordered-play-prompts.js +2 -2
- package/dist/web/assets/index-BH8NyuE4.js +1233 -0
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
- package/dist/web/assets/index-B1Kd7NCp.js +0 -1223
|
@@ -116,6 +116,7 @@ export class V1Runtime {
|
|
|
116
116
|
controlFingerprint: fingerprintControl(Object.fromEntries(snapshot.files.map(({ path, contents }) => [path, contents]))),
|
|
117
117
|
documentSnapshot: snapshot,
|
|
118
118
|
history: structuredClone(binding.history),
|
|
119
|
+
historyBackgrounds: structuredClone(binding.historyBackgrounds ?? {}),
|
|
119
120
|
narrativeCheckpoint: binding.narrativeCheckpoint,
|
|
120
121
|
...maintenance,
|
|
121
122
|
additionalMaterials: structuredClone(binding.additionalMaterials),
|
|
@@ -653,6 +654,7 @@ export class V1Runtime {
|
|
|
653
654
|
}),
|
|
654
655
|
additionalMaterials: structuredClone(binding.additionalMaterials),
|
|
655
656
|
history: structuredClone(binding.history),
|
|
657
|
+
historyBackgrounds: structuredClone(binding.historyBackgrounds ?? {}),
|
|
656
658
|
narrativeCheckpoint: baseline.narrativeCheckpoint,
|
|
657
659
|
historyAlreadyAppended: Object.keys(binding.history).filter((key) => !(key in baseline.history)),
|
|
658
660
|
...(await this.#worlds.inspectDocumentMaintenance(request.worldId, binding.parentHead)),
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { parseDocument } from "yaml";
|
|
2
|
+
import { renderWorldYamlSource } from "../world/WorldYamlRendering.js";
|
|
3
|
+
export function captureNarrativeOrigin(snapshot, firstEventId) {
|
|
4
|
+
const source = snapshot.files.find(({ path }) => path === "control/frame.yaml")?.contents;
|
|
5
|
+
if (source === undefined)
|
|
6
|
+
throw new Error("Current situation frame is missing");
|
|
7
|
+
const parsed = parseDocument(source, { schema: "core", uniqueKeys: true });
|
|
8
|
+
if (parsed.errors.length > 0)
|
|
9
|
+
throw new Error("Current situation frame is invalid");
|
|
10
|
+
const frame = parsed.toJS({ maxAliasCount: 0 });
|
|
11
|
+
const binding = record(frame) && record(frame.bindings)
|
|
12
|
+
? frame.bindings.currentSituation
|
|
13
|
+
: undefined;
|
|
14
|
+
if (typeof binding !== "string" || binding.length === 0)
|
|
15
|
+
throw new Error("Current situation binding is invalid");
|
|
16
|
+
const read = snapshot.query({
|
|
17
|
+
kind: "read_document",
|
|
18
|
+
document: binding.startsWith("@")
|
|
19
|
+
? { shortRef: binding.slice(1) }
|
|
20
|
+
: { documentId: binding },
|
|
21
|
+
});
|
|
22
|
+
if (read.kind !== "read_document")
|
|
23
|
+
throw new Error("Bound current situation cannot be read");
|
|
24
|
+
const documentId = read.document.documentId;
|
|
25
|
+
return {
|
|
26
|
+
firstEventId,
|
|
27
|
+
documentId,
|
|
28
|
+
value: readBackground(snapshot, documentId),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export function readBackground(snapshot, documentId) {
|
|
32
|
+
const selected = snapshot.query({
|
|
33
|
+
kind: "select_node",
|
|
34
|
+
document: { documentId },
|
|
35
|
+
locator: { yaml: ["背景"] },
|
|
36
|
+
});
|
|
37
|
+
if (selected.kind !== "select_node" || selected.node.codec !== "yaml")
|
|
38
|
+
return null;
|
|
39
|
+
const value = selected.node.value;
|
|
40
|
+
if (value === null ||
|
|
41
|
+
(typeof value === "string" && value.trim() === "") ||
|
|
42
|
+
(typeof value === "object" && Object.keys(value).length === 0))
|
|
43
|
+
return null;
|
|
44
|
+
// Quoted multiline scalars retain trailing newlines when display framing trims.
|
|
45
|
+
return renderWorldYamlSource(value, { blockQuote: false });
|
|
46
|
+
}
|
|
47
|
+
export function snapshotBackground(value) {
|
|
48
|
+
return value === null ? undefined : { kind: "snapshot", value };
|
|
49
|
+
}
|
|
50
|
+
export function narrativeBackground(origin, snapshot) {
|
|
51
|
+
// A released frozen request without an origin stays unrecorded on recovery.
|
|
52
|
+
if (origin === undefined)
|
|
53
|
+
return undefined;
|
|
54
|
+
const after = readBackground(snapshot, origin.documentId);
|
|
55
|
+
return origin.value === null && after === null
|
|
56
|
+
? undefined
|
|
57
|
+
: { kind: "narrative", before: origin.value, after };
|
|
58
|
+
}
|
|
59
|
+
export function isHistoryBackground(value) {
|
|
60
|
+
if (!record(value))
|
|
61
|
+
return false;
|
|
62
|
+
if (value.kind === "snapshot")
|
|
63
|
+
return Object.keys(value).length === 2 && nonempty(value.value);
|
|
64
|
+
return (value.kind === "narrative" &&
|
|
65
|
+
Object.keys(value).length === 3 &&
|
|
66
|
+
(value.before === null || nonempty(value.before)) &&
|
|
67
|
+
(value.after === null || nonempty(value.after)) &&
|
|
68
|
+
(value.before !== null || value.after !== null));
|
|
69
|
+
}
|
|
70
|
+
export function isNarrativeOrigin(value) {
|
|
71
|
+
return (record(value) &&
|
|
72
|
+
Object.keys(value).length === 3 &&
|
|
73
|
+
Number.isSafeInteger(value.firstEventId) &&
|
|
74
|
+
Number(value.firstEventId) > 0 &&
|
|
75
|
+
typeof value.documentId === "string" &&
|
|
76
|
+
value.documentId.length > 0 &&
|
|
77
|
+
(value.value === null || nonempty(value.value)));
|
|
78
|
+
}
|
|
79
|
+
function nonempty(value) {
|
|
80
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
81
|
+
}
|
|
82
|
+
function record(value) {
|
|
83
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
84
|
+
}
|
|
@@ -12,6 +12,9 @@ export class HistoryQuery {
|
|
|
12
12
|
const refs = inputs.map(({ id }) => historyMessageRef(id));
|
|
13
13
|
this.messages = inputs.map((message, index) => Object.freeze({
|
|
14
14
|
...message,
|
|
15
|
+
...(message.background === undefined
|
|
16
|
+
? {}
|
|
17
|
+
: { background: Object.freeze(structuredClone(message.background)) }),
|
|
15
18
|
ref: refs[index],
|
|
16
19
|
position: index + 1,
|
|
17
20
|
isOpening: message.isOpening === true,
|
|
@@ -20,11 +23,12 @@ export class HistoryQuery {
|
|
|
20
23
|
length: Array.from(message.text).length,
|
|
21
24
|
}));
|
|
22
25
|
this.snapshotId = createHash("sha256")
|
|
23
|
-
.update(JSON.stringify(inputs.map(({ id, role, isOpening, text }) => [
|
|
26
|
+
.update(JSON.stringify(inputs.map(({ id, role, isOpening, text, background }) => [
|
|
24
27
|
id,
|
|
25
28
|
role,
|
|
26
29
|
isOpening === true,
|
|
27
30
|
text,
|
|
31
|
+
background ?? null,
|
|
28
32
|
])))
|
|
29
33
|
.digest("hex");
|
|
30
34
|
}
|
|
@@ -380,11 +384,12 @@ export function historyMessageRef(id) {
|
|
|
380
384
|
}
|
|
381
385
|
/** Decode storage identity once at the boundary; never infer order from it. */
|
|
382
386
|
export function historyInputs(entries) {
|
|
383
|
-
return entries.map(({ path, contents }) => {
|
|
387
|
+
return entries.map(({ path, contents, background }) => {
|
|
384
388
|
const id = path.replace(/\.md$/u, "");
|
|
385
389
|
return {
|
|
386
390
|
id,
|
|
387
391
|
text: contents,
|
|
392
|
+
...(background === undefined ? {} : { background }),
|
|
388
393
|
role: /(?:\.|-)player(?:-|$)/u.test(id) ? "player" : "narrator",
|
|
389
394
|
isOpening: id.endsWith("message.genesis.narrator"),
|
|
390
395
|
};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
import { parseDocument } from "yaml";
|
|
1
3
|
/** Shared metadata keeps injected refs and tool results directly navigable. */
|
|
2
4
|
export function historyMessageLabel(message, locale) {
|
|
3
5
|
const zh = locale === "zh-CN";
|
|
@@ -12,7 +14,7 @@ export function historyRoleLabel(message, locale) {
|
|
|
12
14
|
return locale === "zh-CN" ? "主持叙事" : "Host narrative";
|
|
13
15
|
}
|
|
14
16
|
export function renderHistoryMessage(message, locale) {
|
|
15
|
-
return `## ${historyMessageLabel(message, locale)}\n\n${locale === "zh-CN" ? "[原文开始]" : "[Original text begins]"}\n${message.text}\n${locale === "zh-CN" ? "[原文结束;正文完整]" : "[Original text ends; body complete]"}`;
|
|
17
|
+
return `## ${historyMessageLabel(message, locale)}\n${renderHistoryBackground(message.background, locale)}\n${locale === "zh-CN" ? "[原文开始]" : "[Original text begins]"}\n${message.text}\n${locale === "zh-CN" ? "[原文结束;正文完整]" : "[Original text ends; body complete]"}`;
|
|
16
18
|
}
|
|
17
19
|
export function renderHistoryInjectionNotice(total, selected, kind, locale) {
|
|
18
20
|
const zh = locale === "zh-CN";
|
|
@@ -51,7 +53,7 @@ export function renderHistoryResult(result, locale = "en", legacyRead = false) {
|
|
|
51
53
|
if (result.kind === "legacy_list")
|
|
52
54
|
return {
|
|
53
55
|
ok: true,
|
|
54
|
-
markdown: `# Directory listing\n\n${header}\nOrder: ${result.order}\n${result.messages.map((message) => `- ${message.ref}, position ${message.position}, role=${message.role}${message.isOpening ? ", isOpening=true" : ""}, ${message.length} Unicode code points`).join("\n") || "(empty)"}\n\n---\nThis page: ${result.offset}..${result.offset + result.messages.length} / ${result.total} items\nComplete: ${result.nextCursor === null ? "yes" : "no"}${result.nextCursor === null ? "" : `\nNext-page cursor: ${result.nextCursor}`}`,
|
|
56
|
+
markdown: `# Directory listing\n\n${header}\nOrder: ${result.order}\n${result.messages.map((message) => `- ${message.ref}, position ${message.position}, role=${message.role}${message.isOpening ? ", isOpening=true" : ""}, ${message.length} Unicode code points${message.background === undefined ? "" : `\n${renderHistoryBackground(message.background, locale)}`}`).join("\n") || "(empty)"}\n\n---\nThis page: ${result.offset}..${result.offset + result.messages.length} / ${result.total} items\nComplete: ${result.nextCursor === null ? "yes" : "no"}${result.nextCursor === null ? "" : `\nNext-page cursor: ${result.nextCursor}`}`,
|
|
55
57
|
};
|
|
56
58
|
if (result.kind === "list")
|
|
57
59
|
return {
|
|
@@ -59,7 +61,7 @@ export function renderHistoryResult(result, locale = "en", legacyRead = false) {
|
|
|
59
61
|
markdown: `# ${zh ? "已提交历史列表" : "Committed history listing"}\n\n${header}\nOrder: oldest_first (${zh ? "页内始终由旧到新" : "every page oldest to newest"})\n${result.messages
|
|
60
62
|
.map((message) => {
|
|
61
63
|
const preview = Array.from(message.text).slice(0, 120).join("");
|
|
62
|
-
return `### ${historyMessageLabel(message, locale)}\n${message.length} Unicode code points\n[${zh ? "部分原文预览" : "Partial original preview"}]\n${preview}\n[${message.length > 120 ? (zh ? "预览有省略" : "preview omitted remaining text") : zh ? "预览结束" : "preview ends"}]`;
|
|
64
|
+
return `### ${historyMessageLabel(message, locale)}\n${message.length} Unicode code points\n${renderHistoryBackground(message.background, locale)}[${zh ? "部分原文预览" : "Partial original preview"}]\n${preview}\n[${message.length > 120 ? (zh ? "预览有省略" : "preview omitted remaining text") : zh ? "预览结束" : "preview ends"}]`;
|
|
63
65
|
})
|
|
64
66
|
.join("\n\n") || (zh ? "(空页)" : "(empty page)")}\n\n---\n${zh ? "本页位置/条数" : "Page positions / count"}: ${range(result.messages)} / ${result.messages.length}\n${cursorLine("earlierCursor", result.earlierCursor)}\n${cursorLine("laterCursor", result.laterCursor)}\n${zh ? "续页只传 {cursor}。" : "Continue with only {cursor}."}${result.messages.length === 0 && result.anchor !== null ? `\n${zh ? "可从锚点转向浏览" : "Browse from the anchor in the other direction"}: {before:"${result.anchor}"} / {after:"${result.anchor}"}` : ""}`,
|
|
65
67
|
};
|
|
@@ -70,7 +72,7 @@ export function renderHistoryResult(result, locale = "en", legacyRead = false) {
|
|
|
70
72
|
};
|
|
71
73
|
return {
|
|
72
74
|
ok: true,
|
|
73
|
-
markdown: `# ${zh ? "历史字面搜索" : "History literal search"}\n\n${header}${result.within === null ? "" : `\nLegacy selected scope: ${result.within}`}\n${zh ? "搜索位置范围(排除边界)" : "Search position range (boundaries excluded)"}: ${result.range.start === null ? "empty" : `${result.range.start}..${result.range.end}`}\nOrder: ${result.order}\nMatch: ${result.match} (${zh ? "同一消息内" : "within one message"})\nNormalization: NFKC${result.caseSensitive ? " (case sensitive)" : " + case folding (locale und)"}\n${zh ? "查询词" : "Queries"}: ${JSON.stringify(result.queries)}\n${zh ? "命中消息总数" : "Total matching messages"}: ${result.matches}\n\n${result.messages.map(({ message, hits, fragments, omittedMatches }) => `### ${historyMessageLabel(message, locale)}\n${message.length} Unicode code points\n${zh ? "逐词命中次数" : "Matches per query"}: ${hits.map(({ query, count }) => `${JSON.stringify(query)}: ${count}`).join("; ")}\n${fragments.map((fragment) => `[${zh ? "部分原文片段" : "Partial original fragment"}: ${fragment.start}..${fragment.end}${fragment.omittedBefore ? "; prefix omitted" : ""}${fragment.omittedAfter ? "; suffix omitted" : ""}]\n${fragment.text}\n[${zh ? "片段结束" : "fragment ends"}]`).join("\n")}\n${omittedMatches ? (zh ? "另有命中未展示;可完整读取核实。" : "Additional matches omitted; read the complete original to verify.") : zh ? "全部命中均在所示片段内。" : "All matches are covered by these fragments."}`).join("\n\n") || (result.legacy ? (zh ? "零字面命中不证明事件没有发生;可改用其他 query 或读取相邻原文。" : "Zero literal matches do not prove an event never happened. Try another query or read adjacent originals.") : zh ? "零字面命中不证明事件没有发生;可改词、从 all 放宽为 any 或读取相邻上下文。" : "Zero literal matches do not prove an event never happened. Try other words, relax all to any, or read nearby context.")}\n\n---\n${cursorLine(result.legacy ? "Next-page cursor" : "nextCursor", result.nextCursor)}\n${result.legacy ? (zh ? "续页保留原source=history、query、caseSensitive、within与limit并使用返回cursor。" : "Legacy continuation keeps the original source=history, query, caseSensitive, within and limit with the returned cursor.") : zh ? "续页只传 {cursor};已有原文足以判断时停止检索。all 仅限同一消息;相邻消息可改用 any 后读前后文。" : "Continue with only {cursor}; stop when the originals suffice. all applies to one message; use any then read context for terms in adjacent messages."}`,
|
|
75
|
+
markdown: `# ${zh ? "历史字面搜索" : "History literal search"}\n\n${header}${result.within === null ? "" : `\nLegacy selected scope: ${result.within}`}\n${zh ? "搜索位置范围(排除边界)" : "Search position range (boundaries excluded)"}: ${result.range.start === null ? "empty" : `${result.range.start}..${result.range.end}`}\nOrder: ${result.order}\nMatch: ${result.match} (${zh ? "同一消息内" : "within one message"})\nNormalization: NFKC${result.caseSensitive ? " (case sensitive)" : " + case folding (locale und)"}\n${zh ? "查询词" : "Queries"}: ${JSON.stringify(result.queries)}\n${zh ? "命中消息总数" : "Total matching messages"}: ${result.matches}\n\n${result.messages.map(({ message, hits, fragments, omittedMatches }) => `### ${historyMessageLabel(message, locale)}\n${message.length} Unicode code points\n${renderHistoryBackground(message.background, locale)}${zh ? "逐词命中次数" : "Matches per query"}: ${hits.map(({ query, count }) => `${JSON.stringify(query)}: ${count}`).join("; ")}\n${fragments.map((fragment) => `[${zh ? "部分原文片段" : "Partial original fragment"}: ${fragment.start}..${fragment.end}${fragment.omittedBefore ? "; prefix omitted" : ""}${fragment.omittedAfter ? "; suffix omitted" : ""}]\n${fragment.text}\n[${zh ? "片段结束" : "fragment ends"}]`).join("\n")}\n${omittedMatches ? (zh ? "另有命中未展示;可完整读取核实。" : "Additional matches omitted; read the complete original to verify.") : zh ? "全部命中均在所示片段内。" : "All matches are covered by these fragments."}`).join("\n\n") || (result.legacy ? (zh ? "零字面命中不证明事件没有发生;可改用其他 query 或读取相邻原文。" : "Zero literal matches do not prove an event never happened. Try another query or read adjacent originals.") : zh ? "零字面命中不证明事件没有发生;可改词、从 all 放宽为 any 或读取相邻上下文。" : "Zero literal matches do not prove an event never happened. Try other words, relax all to any, or read nearby context.")}\n\n---\n${cursorLine(result.legacy ? "Next-page cursor" : "nextCursor", result.nextCursor)}\n${result.legacy ? (zh ? "续页保留原source=history、query、caseSensitive、within与limit并使用返回cursor。" : "Legacy continuation keeps the original source=history, query, caseSensitive, within and limit with the returned cursor.") : zh ? "续页只传 {cursor};已有原文足以判断时停止检索。all 仅限同一消息;相邻消息可改用 any 后读前后文。" : "Continue with only {cursor}; stop when the originals suffice. all applies to one message; use any then read context for terms in adjacent messages."}`,
|
|
74
76
|
};
|
|
75
77
|
}
|
|
76
78
|
function localizedRecovery(result, zh) {
|
|
@@ -84,3 +86,28 @@ function localizedRecovery(result, zh) {
|
|
|
84
86
|
return "该 ref 不在当前时间线中,可能仍保存在旧端点;请重新查询当前历史。";
|
|
85
87
|
return `参数不符合要求,请按工具定义修正。${result.recovery}`;
|
|
86
88
|
}
|
|
89
|
+
/** Every result carries its own annotation; no implicit neighboring inheritance. */
|
|
90
|
+
export function renderHistoryBackground(background, locale) {
|
|
91
|
+
if (background === undefined)
|
|
92
|
+
return "";
|
|
93
|
+
const zh = locale === "zh-CN";
|
|
94
|
+
const node = (value) => value === null
|
|
95
|
+
? zh
|
|
96
|
+
? "未记录"
|
|
97
|
+
: "not recorded"
|
|
98
|
+
: `\n[${zh ? "背景值开始" : "Background value begins"}]\n${value}\n[${zh ? "背景值结束" : "Background value ends"}]`;
|
|
99
|
+
const notice = zh
|
|
100
|
+
? "历史背景仅说明采集位置的状态,不是当前世界状态;回忆、梦境或段内局部时间结合原文解释,不认证事实或授予角色知识。"
|
|
101
|
+
: "Historical background describes the captured state, not current world state; interpret memories, dreams and local times with the original text. It does not certify facts or grant character knowledge.";
|
|
102
|
+
if (background.kind === "snapshot" ||
|
|
103
|
+
equalBackground(background.before, background.after))
|
|
104
|
+
return `\n${zh ? "历史背景" : "Historical background"}:${node(background.kind === "snapshot" ? background.value : background.before)}\n${notice}\n`;
|
|
105
|
+
return `\n${zh ? "推进前背景" : "Background before advancement"}:${node(background.before)}\n${zh ? "推进后背景" : "Background after advancement"}:${node(background.after)}\n${notice}\n`;
|
|
106
|
+
}
|
|
107
|
+
function equalBackground(before, after) {
|
|
108
|
+
if (before === after)
|
|
109
|
+
return true;
|
|
110
|
+
if (before === null || after === null)
|
|
111
|
+
return false;
|
|
112
|
+
return isDeepStrictEqual(parseDocument(before).toJS({ maxAliasCount: 0 }), parseDocument(after).toJS({ maxAliasCount: 0 }));
|
|
113
|
+
}
|
|
@@ -83,6 +83,7 @@ export class FileNativeContinuityCorrection {
|
|
|
83
83
|
materials: structuredClone(binding.additionalMaterials),
|
|
84
84
|
originalMaterials: structuredClone(binding.additionalMaterials),
|
|
85
85
|
history: structuredClone(binding.history),
|
|
86
|
+
historyBackgrounds: structuredClone(binding.historyBackgrounds ?? {}),
|
|
86
87
|
reads: new Map(),
|
|
87
88
|
previewedVersion: null,
|
|
88
89
|
stateOperationClaim: claimed.handle,
|
|
@@ -236,6 +237,7 @@ export class FileNativeContinuityCorrection {
|
|
|
236
237
|
documentSnapshot: candidate.snapshot,
|
|
237
238
|
additionalMaterials: materials,
|
|
238
239
|
history: candidate.history,
|
|
240
|
+
historyBackgrounds: candidate.historyBackgrounds,
|
|
239
241
|
documentMaintenance: candidate.documentMaintenance,
|
|
240
242
|
documentMaintenanceUnavailableReason: candidate.documentMaintenanceUnavailableReason,
|
|
241
243
|
narrativeCheckpoint: candidate.narrativeCheckpoint,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isNarrativeOrigin, isHistoryBackground, } from "../history/HistoryBackground.js";
|
|
1
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
3
|
import { link, mkdir, open, readFile, rename, rm, writeFile, } from "node:fs/promises";
|
|
3
4
|
import { dirname, join, resolve } from "node:path";
|
|
@@ -119,6 +120,8 @@ function assertBase(value) {
|
|
|
119
120
|
value.schemaVersion !== 1 ||
|
|
120
121
|
value.kind !== "play_advance" ||
|
|
121
122
|
(value.advanceKind !== "player" && value.advanceKind !== "response") ||
|
|
123
|
+
(value.narrativeOrigin !== undefined &&
|
|
124
|
+
!isNarrativeOrigin(value.narrativeOrigin)) ||
|
|
122
125
|
typeof value.worldId !== "string" ||
|
|
123
126
|
typeof value.chainId !== "string" ||
|
|
124
127
|
typeof value.advanceId !== "string" ||
|
|
@@ -170,6 +173,8 @@ function assertSettlementPrepared(value) {
|
|
|
170
173
|
(value.schemaVersion !== 1 ||
|
|
171
174
|
value.stage !== "settlement_prepared" ||
|
|
172
175
|
!isRecord(value.settlement) ||
|
|
176
|
+
(value.settlement.background !== undefined &&
|
|
177
|
+
!isHistoryBackground(value.settlement.background)) ||
|
|
173
178
|
typeof value.recordedAt !== "number"))
|
|
174
179
|
throw new Error("Settlement-prepared play fact has an invalid shape");
|
|
175
180
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isNarrativeOrigin, } from "../history/HistoryBackground.js";
|
|
1
2
|
import { encodePlayPromptRun, decodePlayPromptRun, } from "./PlayPromptRun.js";
|
|
2
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
4
|
import { link, mkdir, open, readFile, rename, rm, writeFile, } from "node:fs/promises";
|
|
@@ -162,6 +163,9 @@ export class FileNativePlayTimelineStore {
|
|
|
162
163
|
chainId: value.chainId,
|
|
163
164
|
nextMaterials: structuredClone(value.nextMaterials),
|
|
164
165
|
lastRequestDigest,
|
|
166
|
+
...(value.narrativeOrigin === undefined
|
|
167
|
+
? {}
|
|
168
|
+
: { narrativeOrigin: structuredClone(value.narrativeOrigin) }),
|
|
165
169
|
};
|
|
166
170
|
const continuationDigest = createHash("sha256")
|
|
167
171
|
.update(JSON.stringify(continuation))
|
|
@@ -313,6 +317,11 @@ export class FileNativePlayTimelineStore {
|
|
|
313
317
|
promptRuns,
|
|
314
318
|
changedDocuments: structuredClone(stateValue.changedDocuments),
|
|
315
319
|
nextMaterials: structuredClone(continuationValue.nextMaterials),
|
|
320
|
+
...(continuationValue.narrativeOrigin === undefined
|
|
321
|
+
? {}
|
|
322
|
+
: {
|
|
323
|
+
narrativeOrigin: structuredClone(continuationValue.narrativeOrigin),
|
|
324
|
+
}),
|
|
316
325
|
nextEventId: stateValue.nextEventId,
|
|
317
326
|
exchange: stateValue.exchange,
|
|
318
327
|
lastRequest,
|
|
@@ -767,6 +776,8 @@ function assertContextContinuation(value) {
|
|
|
767
776
|
if (!isRecord(value) ||
|
|
768
777
|
value.schemaVersion !== 3 ||
|
|
769
778
|
value.kind !== "play_context_continuation" ||
|
|
779
|
+
(value.narrativeOrigin !== undefined &&
|
|
780
|
+
!isNarrativeOrigin(value.narrativeOrigin)) ||
|
|
770
781
|
typeof value.chainId !== "string" ||
|
|
771
782
|
!Array.isArray(value.nextMaterials) ||
|
|
772
783
|
(value.lastRequestDigest !== null &&
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { captureNarrativeOrigin, snapshotBackground, narrativeBackground, } from "../history/HistoryBackground.js";
|
|
2
|
+
import { isDeepStrictEqual } from "node:util";
|
|
1
3
|
import { hasLegacyHistoryTools } from "../prompt/FileNativeToolRegistry.js";
|
|
2
4
|
import { PlayCallChainError } from "./PlayCallChainError.js";
|
|
3
5
|
import { preparePlayerRevisionTrace, prepareForkTrace, restorePlayDocuments, independentContextCopy, } from "./PlayTraceRetention.js";
|
|
@@ -88,6 +90,7 @@ export class PlayCallChain {
|
|
|
88
90
|
documentSnapshot: documents.snapshot,
|
|
89
91
|
additionalMaterials: structuredClone(binding.additionalMaterials),
|
|
90
92
|
history: structuredClone(binding.history),
|
|
93
|
+
historyBackgrounds: structuredClone(binding.historyBackgrounds ?? {}),
|
|
91
94
|
narrativeCheckpoint: binding.narrativeCheckpoint,
|
|
92
95
|
documentMaintenance,
|
|
93
96
|
documentMaintenanceUnavailableReason: documents.maintenanceWarning,
|
|
@@ -144,7 +147,7 @@ export class PlayCallChain {
|
|
|
144
147
|
lastFailure: null,
|
|
145
148
|
updatedAt: now,
|
|
146
149
|
documents,
|
|
147
|
-
history: historyEntries(binding.history),
|
|
150
|
+
history: historyEntries(binding.history, binding.historyBackgrounds),
|
|
148
151
|
narrativeCheckpoint: binding.narrativeCheckpoint,
|
|
149
152
|
completedToolMap: new Map(),
|
|
150
153
|
persistenceCursor: {
|
|
@@ -182,8 +185,10 @@ export class PlayCallChain {
|
|
|
182
185
|
const last = session.transcript.at(-1);
|
|
183
186
|
if (last?.kind === "assistant" &&
|
|
184
187
|
last.toolCalls.length === 0 &&
|
|
185
|
-
last.text.trim().length > 0)
|
|
188
|
+
last.text.trim().length > 0) {
|
|
189
|
+
session.narrativeOrigin = await this.#captureOrigin(session);
|
|
186
190
|
session.transcript.push(continuationNotice(this.#compiler.locale));
|
|
191
|
+
}
|
|
187
192
|
return this.#dispatch(session, input.modelHost, createRequest(session, input.modelHost, session.exchange), 1, signal, input.observer);
|
|
188
193
|
}
|
|
189
194
|
if (duplicatePlayerExchange(session, input.exchangeId, input.playerText, "append"))
|
|
@@ -213,6 +218,7 @@ export class PlayCallChain {
|
|
|
213
218
|
documentSnapshot: session.documents.snapshot,
|
|
214
219
|
additionalMaterials: structuredClone(binding.additionalMaterials),
|
|
215
220
|
history: structuredClone(binding.history),
|
|
221
|
+
historyBackgrounds: structuredClone(binding.historyBackgrounds ?? {}),
|
|
216
222
|
narrativeCheckpoint: binding.narrativeCheckpoint,
|
|
217
223
|
},
|
|
218
224
|
playerInputPlacement: "append",
|
|
@@ -221,7 +227,7 @@ export class PlayCallChain {
|
|
|
221
227
|
}, playPreset);
|
|
222
228
|
// The native conversation already contains successful writes and exact reads.
|
|
223
229
|
// Rebinding initial coverage here would incorrectly reauthorize stale scopes.
|
|
224
|
-
session.history = historyEntries(binding.history);
|
|
230
|
+
session.history = historyEntries(binding.history, binding.historyBackgrounds);
|
|
225
231
|
session.narrativeCheckpoint = binding.narrativeCheckpoint;
|
|
226
232
|
session.nextMaterials = structuredClone(binding.additionalMaterials);
|
|
227
233
|
(session.promptRuns ??= []).push({
|
|
@@ -719,6 +725,7 @@ export class PlayCallChain {
|
|
|
719
725
|
documentSnapshot: documents.snapshot,
|
|
720
726
|
additionalMaterials: structuredClone(binding.additionalMaterials),
|
|
721
727
|
history: structuredClone(binding.history),
|
|
728
|
+
historyBackgrounds: structuredClone(binding.historyBackgrounds ?? {}),
|
|
722
729
|
narrativeCheckpoint: binding.narrativeCheckpoint,
|
|
723
730
|
documentMaintenance,
|
|
724
731
|
documentMaintenanceUnavailableReason: documents.maintenanceWarning,
|
|
@@ -788,6 +795,10 @@ export class PlayCallChain {
|
|
|
788
795
|
canRetry: false,
|
|
789
796
|
bootstrap: structuredClone(fresh.bootstrap),
|
|
790
797
|
tools: structuredClone(fresh.tools),
|
|
798
|
+
narrativeOrigin: captureNarrativeOrigin(WorldDocumentStore.open({
|
|
799
|
+
layout: "world_state",
|
|
800
|
+
files: Object.entries(input.binding.files).map(([path, contents]) => ({ path, contents })),
|
|
801
|
+
}), 1),
|
|
791
802
|
transcript: playerInputAppend({
|
|
792
803
|
history: fresh.binding.history,
|
|
793
804
|
checkpoint: fresh.binding.narrativeCheckpoint,
|
|
@@ -928,11 +939,24 @@ export class PlayCallChain {
|
|
|
928
939
|
this.#active.delete(activeId);
|
|
929
940
|
this.#worldChains.delete(worldId);
|
|
930
941
|
}
|
|
942
|
+
async #captureOrigin(session) {
|
|
943
|
+
const binding = await this.#worlds.bindPlayCallChain(session.worldId);
|
|
944
|
+
if (binding.parentHead !== session.parentHead)
|
|
945
|
+
throw new PlayCallChainError("World advanced before background capture; use a fresh context.");
|
|
946
|
+
return captureNarrativeOrigin(WorldDocumentStore.open({
|
|
947
|
+
layout: "world_state",
|
|
948
|
+
files: Object.entries(binding.files).map(([path, contents]) => ({
|
|
949
|
+
path,
|
|
950
|
+
contents,
|
|
951
|
+
})),
|
|
952
|
+
}), session.nextEventId);
|
|
953
|
+
}
|
|
931
954
|
async #submitPlayer(session, exchangeId, playerText, context, modelHost, signal, observer) {
|
|
932
955
|
if (duplicatePlayerExchange(session, exchangeId, playerText, context))
|
|
933
956
|
return projectView(session);
|
|
934
957
|
if (session.status === "running")
|
|
935
958
|
throw new PlayCallChainError("The call chain is waiting for the model to return.");
|
|
959
|
+
session.narrativeOrigin = await this.#captureOrigin(session);
|
|
936
960
|
const exchange = session.exchange + 1;
|
|
937
961
|
const nextRequest = createRequest(session, modelHost, exchange, [
|
|
938
962
|
...session.transcript,
|
|
@@ -950,6 +974,7 @@ export class PlayCallChain {
|
|
|
950
974
|
schemaVersion: 1,
|
|
951
975
|
kind: "play_advance",
|
|
952
976
|
advanceKind: "player",
|
|
977
|
+
narrativeOrigin: structuredClone(session.narrativeOrigin),
|
|
953
978
|
playContext: session.continuityContextId ?? session.chainId,
|
|
954
979
|
worldId: session.worldId,
|
|
955
980
|
chainId: session.chainId,
|
|
@@ -1003,7 +1028,17 @@ export class PlayCallChain {
|
|
|
1003
1028
|
operationId: advance.operationId,
|
|
1004
1029
|
worldId: session.worldId,
|
|
1005
1030
|
parentHead: advance.parentHead,
|
|
1006
|
-
historyAppend: [
|
|
1031
|
+
historyAppend: [
|
|
1032
|
+
{
|
|
1033
|
+
role: "player",
|
|
1034
|
+
exactText: advance.playerText,
|
|
1035
|
+
...(advance.narrativeOrigin?.value == null
|
|
1036
|
+
? {}
|
|
1037
|
+
: {
|
|
1038
|
+
background: snapshotBackground(advance.narrativeOrigin.value),
|
|
1039
|
+
}),
|
|
1040
|
+
},
|
|
1041
|
+
],
|
|
1007
1042
|
nextMaterials: structuredClone(session.nextMaterials),
|
|
1008
1043
|
stateChanges: [],
|
|
1009
1044
|
});
|
|
@@ -1031,6 +1066,10 @@ export class PlayCallChain {
|
|
|
1031
1066
|
session.nextEventId = advance.eventId + 1;
|
|
1032
1067
|
session.parentHead = outcome.head;
|
|
1033
1068
|
appendCommittedHistory(session, outcome.head, outcome.historyAppend);
|
|
1069
|
+
if (advance.narrativeOrigin === undefined)
|
|
1070
|
+
delete session.narrativeOrigin;
|
|
1071
|
+
else
|
|
1072
|
+
session.narrativeOrigin = structuredClone(advance.narrativeOrigin);
|
|
1034
1073
|
session.exchange = advance.exchange;
|
|
1035
1074
|
session.lastRequest = structuredClone(advance.nextRequest);
|
|
1036
1075
|
session.lastRequestAttempt = 0;
|
|
@@ -1167,7 +1206,11 @@ export class PlayCallChain {
|
|
|
1167
1206
|
: { providerState: response.providerState }),
|
|
1168
1207
|
toolCalls: structuredClone(calls),
|
|
1169
1208
|
};
|
|
1209
|
+
const background = visibleText
|
|
1210
|
+
? narrativeBackground(advance.narrativeOrigin, session.documents.snapshot)
|
|
1211
|
+
: undefined;
|
|
1170
1212
|
return {
|
|
1213
|
+
...(background === undefined ? {} : { background }),
|
|
1171
1214
|
assistantEvent,
|
|
1172
1215
|
playContext: session.continuityContextId ?? session.chainId,
|
|
1173
1216
|
...(worldClock === undefined || stateChanges.length === 0
|
|
@@ -1226,6 +1269,9 @@ export class PlayCallChain {
|
|
|
1226
1269
|
{
|
|
1227
1270
|
role: "narrator",
|
|
1228
1271
|
exactText: settlement.assistantEvent.text,
|
|
1272
|
+
...(settlement.background === undefined
|
|
1273
|
+
? {}
|
|
1274
|
+
: { background: settlement.background }),
|
|
1229
1275
|
},
|
|
1230
1276
|
]
|
|
1231
1277
|
: [],
|
|
@@ -1287,7 +1333,7 @@ export class PlayCallChain {
|
|
|
1287
1333
|
const binding = await this.#worlds.bindPlayCallChain(session.worldId);
|
|
1288
1334
|
session.documents = new FileNativePlayDocuments(binding.files);
|
|
1289
1335
|
session.documents.restoreAuthorizationCheckpoint(settlement.authorizationCheckpoint);
|
|
1290
|
-
session.history = historyEntries(binding.history);
|
|
1336
|
+
session.history = historyEntries(binding.history, binding.historyBackgrounds);
|
|
1291
1337
|
session.nextMaterials = structuredClone(binding.additionalMaterials);
|
|
1292
1338
|
}
|
|
1293
1339
|
mergeChangedDocuments(session, settlement.stateChanges);
|
|
@@ -1382,6 +1428,9 @@ export class PlayCallChain {
|
|
|
1382
1428
|
schemaVersion: 1,
|
|
1383
1429
|
kind: "play_advance",
|
|
1384
1430
|
advanceKind: "response",
|
|
1431
|
+
...(session.narrativeOrigin === undefined
|
|
1432
|
+
? {}
|
|
1433
|
+
: { narrativeOrigin: structuredClone(session.narrativeOrigin) }),
|
|
1385
1434
|
worldId: session.worldId,
|
|
1386
1435
|
chainId: session.chainId,
|
|
1387
1436
|
advanceId: responseOperationId,
|
|
@@ -1784,7 +1833,7 @@ export class PlayCallChain {
|
|
|
1784
1833
|
documentAuthorizationCheckpoints: persisted.documentAuthorizationCheckpoints?.map((checkpoint) => structuredClone(checkpoint)) ?? [],
|
|
1785
1834
|
nextMaterials: structuredClone(binding.additionalMaterials),
|
|
1786
1835
|
documents,
|
|
1787
|
-
history: historyEntries(binding.history),
|
|
1836
|
+
history: historyEntries(binding.history, binding.historyBackgrounds),
|
|
1788
1837
|
narrativeCheckpoint: binding.narrativeCheckpoint,
|
|
1789
1838
|
completedToolMap: new Map(persisted.completedTools.map((item) => [
|
|
1790
1839
|
item.key,
|
|
@@ -1935,11 +1984,18 @@ function appendCommittedHistory(session, head, messages) {
|
|
|
1935
1984
|
const path = `message.${sequence}.${index + 1}.${message.role}`;
|
|
1936
1985
|
const existing = session.history.find((item) => item.path === path);
|
|
1937
1986
|
if (existing !== undefined) {
|
|
1938
|
-
if (existing.contents !== message.exactText
|
|
1987
|
+
if (existing.contents !== message.exactText ||
|
|
1988
|
+
!isDeepStrictEqual(existing.background, message.background))
|
|
1939
1989
|
throw new PlayCallChainError("Recovered Authority history conflicts with the call-chain history.");
|
|
1940
1990
|
continue;
|
|
1941
1991
|
}
|
|
1942
|
-
session.history.push({
|
|
1992
|
+
session.history.push({
|
|
1993
|
+
path,
|
|
1994
|
+
contents: message.exactText,
|
|
1995
|
+
...(message.background === undefined
|
|
1996
|
+
? {}
|
|
1997
|
+
: { background: structuredClone(message.background) }),
|
|
1998
|
+
});
|
|
1943
1999
|
}
|
|
1944
2000
|
}
|
|
1945
2001
|
function prepareTool(session, working, exchange, call, locale) {
|
|
@@ -2219,10 +2275,13 @@ function playerRevisionTimelineGeneration(sourceGeneration, operationId, worldId
|
|
|
2219
2275
|
.digest("hex")
|
|
2220
2276
|
.slice(0, 40)}`;
|
|
2221
2277
|
}
|
|
2222
|
-
function historyEntries(history) {
|
|
2278
|
+
function historyEntries(history, backgrounds = {}) {
|
|
2223
2279
|
return Object.entries(history).map(([path, contents]) => ({
|
|
2224
2280
|
path,
|
|
2225
2281
|
contents,
|
|
2282
|
+
...(backgrounds[path] === undefined
|
|
2283
|
+
? {}
|
|
2284
|
+
: { background: structuredClone(backgrounds[path]) }),
|
|
2226
2285
|
}));
|
|
2227
2286
|
}
|
|
2228
2287
|
function operationId(chainId, key) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { captureNarrativeOrigin } from "../history/HistoryBackground.js";
|
|
1
2
|
import { FileNativePlayDocuments, } from "./PlayDocumentTools.js";
|
|
2
3
|
import { currentPlayPrompt, playPromptRunsThroughEvents, } from "./PlayPromptRun.js";
|
|
3
4
|
import { isPlayerRoundMarker, playerInputAppend } from "./PlayContinuity.js";
|
|
@@ -93,6 +94,7 @@ function assembleRetainedTrace(source, endpoint, selection) {
|
|
|
93
94
|
? null
|
|
94
95
|
: completedToolKeys(selection.toolEvents);
|
|
95
96
|
const documents = restorePlayDocuments(endpoint.binding.files, source, selection.authorizationEvents);
|
|
97
|
+
const lastEvent = selection.events.at(-1);
|
|
96
98
|
return {
|
|
97
99
|
continuityContextId: source.continuityContextId ?? source.chainId,
|
|
98
100
|
baselineHead: source.baselineHead,
|
|
@@ -111,6 +113,11 @@ function assembleRetainedTrace(source, endpoint, selection) {
|
|
|
111
113
|
...(source.modelBinding === undefined
|
|
112
114
|
? {}
|
|
113
115
|
: { modelBinding: structuredClone(source.modelBinding) }),
|
|
116
|
+
...(lastEvent?.kind === "player" || lastEvent?.kind === "tool_result"
|
|
117
|
+
? {
|
|
118
|
+
narrativeOrigin: captureNarrativeOrigin(documents.snapshot, lastEvent.kind === "player" ? lastEvent.id : lastEvent.id + 1),
|
|
119
|
+
}
|
|
120
|
+
: {}),
|
|
114
121
|
status: "ready",
|
|
115
122
|
canRetry: false,
|
|
116
123
|
bootstrap: structuredClone(source.bootstrap),
|
|
@@ -8,7 +8,9 @@ import { builtinAuthorPrompts, authoringMechanics, } from "../../shared/ordered-
|
|
|
8
8
|
import { builtinPlayPrompts, } from "../../shared/ordered-play-prompts.js";
|
|
9
9
|
import { parseOrderedPlayPrompts } from "../play/OrderedPlayPrompts.js";
|
|
10
10
|
import { renderDocumentWritePosition } from "./WorldMaintenanceReport.js";
|
|
11
|
-
import { parseDocument
|
|
11
|
+
import { parseDocument } from "yaml";
|
|
12
|
+
import { renderWorldYamlSource } from "../world/WorldYamlRendering.js";
|
|
13
|
+
export { renderWorldYamlSource } from "../world/WorldYamlRendering.js";
|
|
12
14
|
import { measureWorldMaterials, refreshMaintenanceTotals, } from "./WorldMaintenanceReport.js";
|
|
13
15
|
import { renderPlayerViewBindings } from "../world/PlayerViewBindings.js";
|
|
14
16
|
import { checkpointHistory, checkpointReplayBlocks, playerInputAppend, isPlayerRoundMarker, } from "../play/PlayContinuity.js";
|
|
@@ -52,7 +54,7 @@ ${historyUseGuidance("en")}`,
|
|
|
52
54
|
|
|
53
55
|
A nonempty tool-free response ends the model/tool loop started by the current player submission. A response that calls any tool is an intermediate step; its text has not yet been displayed or committed as final story. After all tool results, use the receipts to present this turn's still-unshown content in a later tool-free response consistent with committed state.
|
|
54
56
|
|
|
55
|
-
The player's next submission may choose a fresh context; the old model transcript will not enter that request. Fresh contexts include all committed original player inputs and final narratives after the last effective world_checkpoint, excluding tools, reasoning, and the opening. A checkpoint declaration takes effect only after the subsequent nonempty tool-free final prose successfully commits, including that prose in its boundary; further tool calls may follow registration.
|
|
57
|
+
The player's next submission may choose a fresh context; the old model transcript will not enter that request. Fresh contexts include all committed original player inputs and final narratives after the last effective world_checkpoint, excluding tools, reasoning, and the opening. A checkpoint declaration takes effect only after the subsequent nonempty tool-free final prose successfully commits, including that prose in its boundary; further tool calls may follow registration. Registration neither ends an in-world event nor switches context. Author instructions decide save timing.
|
|
56
58
|
|
|
57
59
|
Runtime executes only real tool definitions, file validation, and authority commits. This block does not define story content, point of view, style, player agency, or state semantics.
|
|
58
60
|
|
|
@@ -83,7 +85,7 @@ ${historyUseGuidance("zh-CN")}`,
|
|
|
83
85
|
|
|
84
86
|
非空且不调用工具的响应会结束本次玩家提交触发的模型/工具循环。只要响应调用了任何工具,它就是工具中间步,其中的文字尚未展示或提交为最终故事。收到全部工具结果后,根据回执在后续无工具响应中呈现本轮尚未展示的内容,并与已提交状态一致。
|
|
85
87
|
|
|
86
|
-
下一次玩家提交可以选择“全新上下文”;旧模型 transcript 不会进入那个请求。新上下文会补入最近一次已生效 world_checkpoint
|
|
88
|
+
下一次玩家提交可以选择“全新上下文”;旧模型 transcript 不会进入那个请求。新上下文会补入最近一次已生效 world_checkpoint 之后的全部已提交玩家原文与最终叙事,不含工具、推理和开场白。检查点登记仅在随后非空且无工具调用的最终正文成功提交后生效,边界包含该正文;登记后仍可继续工具调用。登记不结束世界内事件,也不自动切换上下文。具体保存时机由作者提示规定。
|
|
87
89
|
|
|
88
90
|
Runtime 只执行真实工具定义、文件校验和权威提交;本段不规定故事、人称、文风、玩家代理权或状态语义。
|
|
89
91
|
|
|
@@ -1250,45 +1252,11 @@ function renderSnapshotDocument(snapshot, selector) {
|
|
|
1250
1252
|
markdown: renderYamlDocument(read.descriptor, selected.node.value),
|
|
1251
1253
|
};
|
|
1252
1254
|
}
|
|
1253
|
-
/**
|
|
1254
|
-
* Serialize a YAML node exactly the way context_read does, so what bootstrap
|
|
1255
|
-
* injects is what a later read would hand back. Internal document ids become
|
|
1256
|
-
* the @shortRef handles the model can actually pass to a tool.
|
|
1257
|
-
*/
|
|
1258
|
-
export function renderWorldYamlSource(value) {
|
|
1259
|
-
return stringify(promptYamlValue(value), {
|
|
1260
|
-
indent: 2,
|
|
1261
|
-
lineWidth: 0,
|
|
1262
|
-
}).trimEnd();
|
|
1263
|
-
}
|
|
1264
1255
|
function renderYamlDocument(descriptor, value) {
|
|
1265
1256
|
if (!isRecord(value))
|
|
1266
1257
|
throw new PromptCompilationError("world_document_query_failed", "YAML world-document root node must be a map");
|
|
1267
1258
|
return `## ${descriptor.title} [ref: @${descriptor.shortRef} · YAML]\n\n> ${descriptor.summary}\n\n${renderWorldYamlSource(value)}`;
|
|
1268
1259
|
}
|
|
1269
|
-
function projectedReference(value) {
|
|
1270
|
-
if (!isRecord(value) ||
|
|
1271
|
-
Object.keys(value).length !== 2 ||
|
|
1272
|
-
typeof value.$ref !== "string" ||
|
|
1273
|
-
!isRecord(value.target))
|
|
1274
|
-
return null;
|
|
1275
|
-
return typeof value.target.shortRef === "string"
|
|
1276
|
-
? { shortRef: value.target.shortRef }
|
|
1277
|
-
: null;
|
|
1278
|
-
}
|
|
1279
|
-
function promptYamlValue(value) {
|
|
1280
|
-
const reference = projectedReference(value);
|
|
1281
|
-
if (reference !== null)
|
|
1282
|
-
return { $ref: `@${reference.shortRef}` };
|
|
1283
|
-
if (Array.isArray(value))
|
|
1284
|
-
return value.map(promptYamlValue);
|
|
1285
|
-
if (isRecord(value))
|
|
1286
|
-
return Object.fromEntries(Object.entries(value).map(([key, child]) => [
|
|
1287
|
-
key,
|
|
1288
|
-
promptYamlValue(child),
|
|
1289
|
-
]));
|
|
1290
|
-
return value;
|
|
1291
|
-
}
|
|
1292
1260
|
function throwQueryFailure(failure, operation) {
|
|
1293
1261
|
throw new PromptCompilationError("world_document_query_failed", `World-document snapshot could not complete the ${operation} query`, {
|
|
1294
1262
|
requestKind: failure.requestKind,
|
|
@@ -1310,6 +1278,9 @@ function resolveContext(input, worldFrame, snapshot, coverage, locale) {
|
|
|
1310
1278
|
const history = new HistoryQuery("prompt-compilation", historyInputs(Object.entries(input.world.history ?? {}).map(([path, contents]) => ({
|
|
1311
1279
|
path,
|
|
1312
1280
|
contents,
|
|
1281
|
+
...(input.world.historyBackgrounds?.[path] === undefined
|
|
1282
|
+
? {}
|
|
1283
|
+
: { background: input.world.historyBackgrounds[path] }),
|
|
1313
1284
|
}))));
|
|
1314
1285
|
const excluded = new Set(input.world.historyAlreadyAppended ?? []);
|
|
1315
1286
|
const checkpointEntries = input.world.replayHistory === true
|