narraeon 0.7.7 → 0.7.9
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/authoring/AuthoringRequestSnapshot.js +14 -1
- package/dist/node/runtime/history/HistoryRendering.js +25 -6
- package/dist/node/runtime/history/HistoryTools.js +31 -0
- package/dist/node/runtime/play/PlayCallChain.js +4 -9
- package/dist/node/runtime/play/PlayDocumentTools.js +35 -37
- package/dist/node/runtime/prompt/FileNativePromptCompiler.js +33 -17
- package/dist/node/runtime/prompt/FileNativeToolRegistry.js +5 -9
- package/dist/node/runtime/setting/SettingAuthoringTransaction.js +326 -62
- package/dist/node/runtime/setting/SettingImprovementSession.js +40 -3
- package/dist/node/runtime/world/FileNativeWorldStore.js +13 -3
- package/dist/node/runtime/world/WorldDocumentMaintenance.js +9 -2
- package/dist/node/runtime/world/WorldLedgerAuthority.js +18 -2
- package/dist/node/runtime/world-revision/FileNativeWorldRevisionStore.js +61 -17
- package/dist/node/runtime/world-revision/LegacyWorldRevisionEpochUpgrade.js +46 -0
- package/dist/node/runtime/world-revision/WorldRevisionAuthoringTransaction.js +83 -30
- package/dist/node/runtime/world-revision/WorldRevisionSession.js +64 -4
- package/dist/node/runtime/world-revision/WorldRevisionWorkspace.js +32 -11
- package/dist/node/shared/default-setting-improvement-prompt.js +23 -8
- package/dist/node/shared/ordered-author-prompts.js +9 -7
- package/dist/web/assets/index-B2kbaOGe.js +1408 -0
- package/dist/web/assets/{index-BjxBYYQi.css → index-DH2BVsYd.css} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-Da0tdDij.js +0 -1408
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import { isPlayPresetBinding, } from "../play/FileNativePlayPresetStore.js";
|
|
2
|
+
export function isAuthoringPreloadRecord(value) {
|
|
3
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
4
|
+
return false;
|
|
5
|
+
const record = value;
|
|
6
|
+
return (Object.keys(record).length === 3 &&
|
|
7
|
+
typeof record.revision === "string" &&
|
|
8
|
+
Array.isArray(record.worldDocumentIds) &&
|
|
9
|
+
record.worldDocumentIds.every((id) => typeof id === "string") &&
|
|
10
|
+
Array.isArray(record.opaquePaths) &&
|
|
11
|
+
record.opaquePaths.every((path) => typeof path === "string"));
|
|
12
|
+
}
|
|
2
13
|
export function validAuthoringRequests(value, validPrompt, session) {
|
|
3
14
|
if (!Array.isArray(value) ||
|
|
4
15
|
value.length === 0 ||
|
|
@@ -14,7 +25,9 @@ export function validAuthoringRequests(value, validPrompt, session) {
|
|
|
14
25
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
15
26
|
return false;
|
|
16
27
|
const item = raw;
|
|
17
|
-
|
|
28
|
+
const keys = Object.keys(item).length;
|
|
29
|
+
if ((keys !== 4 && !(keys === 5 && "preload" in item)) ||
|
|
30
|
+
(item.preload !== undefined && !isAuthoringPreloadRecord(item.preload)) ||
|
|
18
31
|
typeof item.requestId !== "string" ||
|
|
19
32
|
!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/u.test(item.requestId) ||
|
|
20
33
|
ids.has(item.requestId) ||
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from "node:util";
|
|
2
2
|
import { parseDocument } from "yaml";
|
|
3
|
+
/** Scope and total only; ordering and continuation rules live in tool descriptions. */
|
|
4
|
+
function compactHeader(total, locale) {
|
|
5
|
+
const zh = locale === "zh-CN";
|
|
6
|
+
return `Scope: ${zh ? "当前时间线" : "current timeline"}\n${zh ? "本次读取快照消息总数" : "Snapshot total messages"}: ${total}`;
|
|
7
|
+
}
|
|
8
|
+
/** Position, role and ref only; role=x would repeat the localized role label. */
|
|
9
|
+
function compactMessageLabel(message, locale) {
|
|
10
|
+
const zh = locale === "zh-CN";
|
|
11
|
+
return `${zh ? "位置" : "position"} ${message.position} · ${historyRoleLabel(message, locale)}${message.isOpening ? " · isOpening=true" : ""} · ${message.ref} · ${message.length} Unicode code points`;
|
|
12
|
+
}
|
|
3
13
|
/** Shared metadata keeps injected refs and tool results directly navigable. */
|
|
4
14
|
export function historyMessageLabel(message, locale) {
|
|
5
15
|
const zh = locale === "zh-CN";
|
|
@@ -58,18 +68,26 @@ export function renderHistoryResult(result, locale = "en", legacyRead = false) {
|
|
|
58
68
|
if (result.kind === "list")
|
|
59
69
|
return {
|
|
60
70
|
ok: true,
|
|
61
|
-
markdown: `# ${zh ? "已提交历史列表" : "Committed history listing"}\n\n${
|
|
71
|
+
markdown: `# ${zh ? "已提交历史列表" : "Committed history listing"}\n\n${compactHeader(result.total, locale)}\n${result.messages
|
|
62
72
|
.map((message) => {
|
|
63
73
|
const preview = Array.from(message.text).slice(0, 120).join("");
|
|
64
|
-
|
|
74
|
+
const omitted = message.length > 120
|
|
75
|
+
? `\n${zh ? "…(预览省略)" : "…(preview omitted)"}`
|
|
76
|
+
: "";
|
|
77
|
+
return `### ${compactMessageLabel(message, locale)}${renderHistoryBackground(message.background, locale, false)}\n${preview}${omitted}`;
|
|
65
78
|
})
|
|
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)}
|
|
79
|
+
.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)}${result.messages.length === 0 && result.anchor !== null ? `\n${zh ? "可从锚点转向浏览" : "Browse from the anchor in the other direction"}: {before:"${result.anchor}"} / {after:"${result.anchor}"}` : ""}`,
|
|
67
80
|
};
|
|
68
81
|
if (result.kind === "read")
|
|
69
82
|
return {
|
|
70
83
|
ok: true,
|
|
71
84
|
markdown: `# ${zh ? "完整历史原文" : "Complete history originals"}\n\n${header}\nMode: ${result.mode}${result.center === null ? "" : `\nCenter: ${result.center}`}\nOrder: oldest_first\n${zh ? "实际位置" : "Actual positions"}: ${range(result.messages)}\n${zh ? "每条正文完整,无摘要、无截断" : "Each body is complete, without summary or truncation"}\n\n${result.messages.map((message) => renderHistoryMessage(message, locale)).join("\n\n") || (zh ? "(历史为空)" : "(empty history)")}\n\n---\n${cursorLine("earlier", result.earlier)}\n${cursorLine("later", result.later)}\n${result.earlier === null ? (zh ? "已到历史开头。" : "At the history beginning.") : legacyRead ? `context_read {ref:"${result.earlier}"}` : zh ? "向前不重复读取:history_read {ref:earlier,before:N}。" : "Read earlier without repeating this window: history_read {ref:earlier,before:N}."}\n${result.later === null ? (zh ? "已到历史结尾。" : "At the history end.") : legacyRead ? `context_read {ref:"${result.later}"}` : zh ? "向后不重复读取:history_read {ref:later,after:N}。" : "Read later without repeating this window: history_read {ref:later,after:N}."}\n${legacyRead ? (zh ? "按需逐条读取相邻原文。" : "Read adjacent originals one at a time as needed.") : zh ? "按需小步扩展;21 条消息不代表固定输出体积。" : "Expand in small windows as needed; 21 messages do not imply a fixed output size."}`,
|
|
72
85
|
};
|
|
86
|
+
if (!result.legacy)
|
|
87
|
+
return {
|
|
88
|
+
ok: true,
|
|
89
|
+
markdown: `# ${zh ? "历史字面搜索" : "History literal search"}\n\n${compactHeader(result.total, locale)}\n${zh ? "搜索位置范围(排除边界)" : "Search position range (boundaries excluded)"}: ${result.range.start === null ? "empty" : `${result.range.start}..${result.range.end}`}\n${zh ? "查询词" : "Queries"}: ${JSON.stringify(result.queries)}\n${zh ? "命中消息总数" : "Total matching messages"}: ${result.matches}\n\n${result.messages.map(({ message, hits, fragments, omittedMatches }) => `### ${compactMessageLabel(message, locale)}${renderHistoryBackground(message.background, locale, false)}${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}`).join("\n")}${omittedMatches ? `\n${zh ? "另有命中未展示;可完整读取核实。" : "Additional matches omitted; read the complete original to verify."}` : ""}`).join("\n\n") || (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("nextCursor", result.nextCursor)}`,
|
|
90
|
+
};
|
|
73
91
|
return {
|
|
74
92
|
ok: true,
|
|
75
93
|
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."}`,
|
|
@@ -87,7 +105,7 @@ function localizedRecovery(result, zh) {
|
|
|
87
105
|
return `参数不符合要求,请按工具定义修正。${result.recovery}`;
|
|
88
106
|
}
|
|
89
107
|
/** Every result carries its own annotation; no implicit neighboring inheritance. */
|
|
90
|
-
export function renderHistoryBackground(background, locale) {
|
|
108
|
+
export function renderHistoryBackground(background, locale, withNotice = true) {
|
|
91
109
|
if (background === undefined)
|
|
92
110
|
return "";
|
|
93
111
|
const zh = locale === "zh-CN";
|
|
@@ -99,10 +117,11 @@ export function renderHistoryBackground(background, locale) {
|
|
|
99
117
|
const notice = zh
|
|
100
118
|
? "历史背景仅说明采集位置的状态,不是当前世界状态;回忆、梦境或段内局部时间结合原文解释,不认证事实或授予角色知识。"
|
|
101
119
|
: "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.";
|
|
120
|
+
const suffix = withNotice ? `\n${notice}` : "";
|
|
102
121
|
if (background.kind === "snapshot" ||
|
|
103
122
|
equalBackground(background.before, background.after))
|
|
104
|
-
return `\n${zh ? "历史背景" : "Historical background"}:${node(background.kind === "snapshot" ? background.value : background.before)}
|
|
105
|
-
return `\n${zh ? "推进前背景" : "Background before advancement"}:${node(background.before)}\n${zh ? "推进后背景" : "Background after advancement"}:${node(background.after)}
|
|
123
|
+
return `\n${zh ? "历史背景" : "Historical background"}:${node(background.kind === "snapshot" ? background.value : background.before)}${suffix}\n`;
|
|
124
|
+
return `\n${zh ? "推进前背景" : "Background before advancement"}:${node(background.before)}\n${zh ? "推进后背景" : "Background after advancement"}:${node(background.after)}${suffix}\n`;
|
|
106
125
|
}
|
|
107
126
|
function equalBackground(before, after) {
|
|
108
127
|
if (before === after)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { HistoryQuery, historyInputs } from "./HistoryQuery.js";
|
|
2
|
+
import { renderHistoryResult } from "./HistoryRendering.js";
|
|
3
|
+
export const historyToolNames = [
|
|
4
|
+
"history_list",
|
|
5
|
+
"history_search",
|
|
6
|
+
"history_read",
|
|
7
|
+
];
|
|
8
|
+
export function isHistoryToolName(name) {
|
|
9
|
+
return historyToolNames.includes(name);
|
|
10
|
+
}
|
|
11
|
+
export function historyEntries(history, backgrounds = {}) {
|
|
12
|
+
return Object.entries(history).map(([path, contents]) => ({
|
|
13
|
+
path,
|
|
14
|
+
contents,
|
|
15
|
+
...(backgrounds[path] === undefined
|
|
16
|
+
? {}
|
|
17
|
+
: { background: structuredClone(backgrounds[path]) }),
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
/** Read-only committed-history navigation shared by play and world revision. */
|
|
21
|
+
export function createHistoryQuery(scope, entries) {
|
|
22
|
+
return new HistoryQuery(scope, historyInputs(entries));
|
|
23
|
+
}
|
|
24
|
+
export function runHistoryTool(query, call, locale) {
|
|
25
|
+
const result = call.name === "history_search"
|
|
26
|
+
? query.search(call.arguments)
|
|
27
|
+
: call.name === "history_read"
|
|
28
|
+
? query.read(call.arguments)
|
|
29
|
+
: query.list(call.arguments);
|
|
30
|
+
return renderHistoryResult(result, locale);
|
|
31
|
+
}
|
|
@@ -2,6 +2,7 @@ import { projectLegacyModelBindingFailure } from "./LegacyModelBindingFailure.js
|
|
|
2
2
|
import { dispatchFailureEnding, emptyResponseEnding, lastEndingCancelled, noContinuationEnding, PlayAdvanceLifecycle, settlementFailureEnding, } from "./PlayAdvanceLifecycle.js";
|
|
3
3
|
import { isResolutionTool, resolutionMarkdown, resolutionToolNames, resolveTabletop, seededRandom, } from "./TabletopResolution.js";
|
|
4
4
|
import { captureNarrativeOrigin, snapshotBackground, narrativeBackground, } from "../history/HistoryBackground.js";
|
|
5
|
+
import { historyEntries } from "../history/HistoryTools.js";
|
|
5
6
|
import { isDeepStrictEqual } from "node:util";
|
|
6
7
|
import { hasLegacyHistoryTools } from "../prompt/FileNativeToolRegistry.js";
|
|
7
8
|
import { renderToolAliasNote, resolveToolName, } from "../prompt/ToolNameAlias.js";
|
|
@@ -2163,6 +2164,9 @@ function duplicatePlayerExchange(session, exchangeId, playerText, context) {
|
|
|
2163
2164
|
}
|
|
2164
2165
|
function mergeChangedDocuments(session, changes) {
|
|
2165
2166
|
for (const change of changes) {
|
|
2167
|
+
// Play and continuity writes never remove a document; only world-revision apply does.
|
|
2168
|
+
if (change.kind === "delete")
|
|
2169
|
+
continue;
|
|
2166
2170
|
const projected = {
|
|
2167
2171
|
kind: change.kind,
|
|
2168
2172
|
ref: `@${change.stableShortRef}`,
|
|
@@ -2346,15 +2350,6 @@ function revisionTimelineGeneration(sourceGeneration, operationId, worldId, cont
|
|
|
2346
2350
|
.digest("hex")
|
|
2347
2351
|
.slice(0, 40)}`;
|
|
2348
2352
|
}
|
|
2349
|
-
function historyEntries(history, backgrounds = {}) {
|
|
2350
|
-
return Object.entries(history).map(([path, contents]) => ({
|
|
2351
|
-
path,
|
|
2352
|
-
contents,
|
|
2353
|
-
...(backgrounds[path] === undefined
|
|
2354
|
-
? {}
|
|
2355
|
-
: { background: structuredClone(backgrounds[path]) }),
|
|
2356
|
-
}));
|
|
2357
|
-
}
|
|
2358
2353
|
function operationId(chainId, key) {
|
|
2359
2354
|
return `play-${createHash("sha256")
|
|
2360
2355
|
.update(`${chainId}\0${key}`)
|
|
@@ -671,15 +671,15 @@ function knownStateDirectory(snapshot, directory, declaredDirectories) {
|
|
|
671
671
|
return snapshot.files.some(({ path }) => path.startsWith(logicalPrefix));
|
|
672
672
|
}
|
|
673
673
|
function executeStateList(snapshot, args, declaredDirectories) {
|
|
674
|
+
// Frozen sessions may still send obsolete cursor/limit; directories are
|
|
675
|
+
// listed in full, so those keys are accepted and ignored.
|
|
674
676
|
if (!record(args) ||
|
|
675
677
|
!hasOnlyToolKeys(args, ["parent", "cursor", "limit"]) ||
|
|
676
678
|
typeof args.parent !== "string" ||
|
|
677
|
-
parseStateDirectoryHandle(args.parent) === null
|
|
678
|
-
(args.limit !== undefined && typeof args.limit !== "number") ||
|
|
679
|
-
!validOptionalCursor(args.cursor))
|
|
679
|
+
parseStateDirectoryHandle(args.parent) === null)
|
|
680
680
|
return {
|
|
681
681
|
ok: false,
|
|
682
|
-
markdown: "# Runtime argument error\n\nstate_list requires an @dir-* parent returned by Runtime
|
|
682
|
+
markdown: "# Runtime argument error\n\nstate_list requires an @dir-* parent returned by Runtime; use history_list for committed history.",
|
|
683
683
|
};
|
|
684
684
|
return executeContextList(snapshot, { ...args, source: "state" }, declaredDirectories);
|
|
685
685
|
}
|
|
@@ -699,19 +699,11 @@ function executeContextList(snapshot, args, declaredDirectories = []) {
|
|
|
699
699
|
(args.order !== "newest_first" && args.order !== "oldest_first"));
|
|
700
700
|
if (!hasOnlyToolKeys(args, allowedKeys) ||
|
|
701
701
|
invalidStateShape ||
|
|
702
|
-
invalidHistoryShape
|
|
703
|
-
(args.limit !== undefined && typeof args.limit !== "number") ||
|
|
704
|
-
!validOptionalCursor(args.cursor))
|
|
702
|
+
invalidHistoryShape)
|
|
705
703
|
return {
|
|
706
704
|
ok: false,
|
|
707
705
|
markdown: "# Runtime argument error\n\nstate must use an @dir-* parent returned by the Runtime and omit order; history must use newest_first or oldest_first order and omit parent.",
|
|
708
706
|
};
|
|
709
|
-
const limit = typeof args.limit === "number" ? args.limit : 20;
|
|
710
|
-
if (!Number.isInteger(limit) || limit < 1 || limit > 100)
|
|
711
|
-
return {
|
|
712
|
-
ok: false,
|
|
713
|
-
markdown: "# Runtime argument error\n\nlimit must be between 1 and 100.",
|
|
714
|
-
};
|
|
715
707
|
if (args.source === "state") {
|
|
716
708
|
const parent = args.parent;
|
|
717
709
|
const directory = parseStateDirectoryHandle(parent);
|
|
@@ -722,32 +714,38 @@ function executeContextList(snapshot, args, declaredDirectories = []) {
|
|
|
722
714
|
ok: false,
|
|
723
715
|
markdown: "# Runtime argument error\n\nThe parent is not a Runtime-known state directory; call state_list and descend only through its returned directory handles.",
|
|
724
716
|
};
|
|
725
|
-
const
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
717
|
+
const entries = [];
|
|
718
|
+
let complete = true;
|
|
719
|
+
let cursor = null;
|
|
720
|
+
do {
|
|
721
|
+
const result = snapshot.query({
|
|
722
|
+
kind: "catalog",
|
|
723
|
+
directory,
|
|
724
|
+
includeRetired: true,
|
|
725
|
+
declaredDirectories,
|
|
726
|
+
limit: 100,
|
|
727
|
+
...(cursor === null ? {} : { cursor }),
|
|
728
|
+
});
|
|
729
|
+
if (result.kind === "error")
|
|
730
|
+
return renderStateQueryFailure(result);
|
|
731
|
+
if (result.kind !== "catalog")
|
|
732
|
+
return unexpectedStateQueryResult();
|
|
733
|
+
if (result.coverage.status !== "complete")
|
|
734
|
+
complete = false;
|
|
735
|
+
entries.push(...result.entries.map((entry) => {
|
|
736
|
+
if (entry.kind === "directory") {
|
|
737
|
+
const relative = entry.logicalPath.slice(`${snapshot.logicalRoot}/`.length);
|
|
738
|
+
return `- Directory ${stateDirectoryHandle(relative)}`;
|
|
739
|
+
}
|
|
740
|
+
if (entry.document === undefined)
|
|
741
|
+
return "- Damaged document (currently not addressable)";
|
|
742
|
+
return `- Document @${entry.document.shortRef} · ${entry.document.title} · ${entry.document.codec.toUpperCase()} · ${entry.document.summary}${entry.document.retired ? " · retired" : ""}${entry.status === "damaged" ? " · needs repair" : ""}`;
|
|
743
|
+
}));
|
|
744
|
+
cursor = result.page.nextCursor;
|
|
745
|
+
} while (cursor !== null);
|
|
748
746
|
return {
|
|
749
747
|
ok: true,
|
|
750
|
-
markdown: `# Directory listing\n\nScope: state · ${parent}\nCoverage: ${
|
|
748
|
+
markdown: `# Directory listing\n\nScope: state · ${parent}\nCoverage: ${complete ? "complete" : "partial"}\n${entries.join("\n") || "(empty)"}\n\n---\nComplete: yes`,
|
|
751
749
|
};
|
|
752
750
|
}
|
|
753
751
|
return toolFailure("Use history_list for committed history.");
|
|
@@ -148,15 +148,13 @@ export class FileNativePromptCompiler {
|
|
|
148
148
|
playPreset: input.playPreset,
|
|
149
149
|
modelBinding: input.modelBinding,
|
|
150
150
|
tools: input.tools,
|
|
151
|
+
...(input.preload === undefined ? {} : { preload: input.preload }),
|
|
151
152
|
});
|
|
152
153
|
}
|
|
153
154
|
compileWorldRevision(input) {
|
|
154
155
|
const identity = this.#locale === "zh-CN"
|
|
155
156
|
? `# 当前修订世界\n\n${input.worldTitle}`
|
|
156
157
|
: `# World being revised\n\n${input.worldTitle}`;
|
|
157
|
-
const boundary = this.#locale === "zh-CN"
|
|
158
|
-
? "# 世界修订工作树边界\n\n随附工具只读写当前锁定 epoch 的 state/* 与 control/* 工作树。成功工具调用尚未进入世界 Authority;只有玩家点击应用才会整体提交并解锁。"
|
|
159
|
-
: "# World-revision worktree boundary\n\nThe attached tools read and write only state/* and control/* in the current locked epoch. Successful tool calls do not enter world Authority until the player applies the complete revision and unlocks it.";
|
|
160
158
|
return this.#compileAuthoringConversation({
|
|
161
159
|
identity: {
|
|
162
160
|
source: "world-revision:title",
|
|
@@ -165,10 +163,7 @@ export class FileNativePromptCompiler {
|
|
|
165
163
|
? `\n\nEpoch: ${input.epoch.id}\nBase: ${input.epoch.baseHead}`
|
|
166
164
|
: ""),
|
|
167
165
|
},
|
|
168
|
-
boundary:
|
|
169
|
-
source: "runtime:world-revision-worktree-boundary",
|
|
170
|
-
markdown: boundary,
|
|
171
|
-
},
|
|
166
|
+
boundary: null,
|
|
172
167
|
runtimeSource: "runtime:builtin/world-revision",
|
|
173
168
|
target: "world-revision",
|
|
174
169
|
gameplay: input.gameplay,
|
|
@@ -177,6 +172,7 @@ export class FileNativePromptCompiler {
|
|
|
177
172
|
playPreset: input.playPreset,
|
|
178
173
|
modelBinding: input.modelBinding,
|
|
179
174
|
tools: input.tools,
|
|
175
|
+
...(input.preload === undefined ? {} : { preload: input.preload }),
|
|
180
176
|
});
|
|
181
177
|
}
|
|
182
178
|
#compileAuthoringConversation(input) {
|
|
@@ -185,7 +181,7 @@ export class FileNativePromptCompiler {
|
|
|
185
181
|
const logicalMessages = [];
|
|
186
182
|
const ordered = input.playPreset.definition.authorPrompts ??
|
|
187
183
|
legacyAuthorPrompts(input.playPreset.definition);
|
|
188
|
-
const catalog = builtinAuthorPrompts(this.#locale);
|
|
184
|
+
const catalog = builtinAuthorPrompts(this.#locale, input.target);
|
|
189
185
|
for (const entry of ordered) {
|
|
190
186
|
if (entry.kind === "world" || !entry.enabled)
|
|
191
187
|
continue;
|
|
@@ -210,7 +206,7 @@ export class FileNativePromptCompiler {
|
|
|
210
206
|
if (builtin?.id === "author.target")
|
|
211
207
|
blocks = [
|
|
212
208
|
input.identity,
|
|
213
|
-
input.boundary,
|
|
209
|
+
...(input.boundary === null ? [] : [input.boundary]),
|
|
214
210
|
...(input.gameplay.mode === "tabletop"
|
|
215
211
|
? [
|
|
216
212
|
tabletopAuthoringRulesBlock(input.gameplay, input.target, this.#locale),
|
|
@@ -238,6 +234,20 @@ export class FileNativePromptCompiler {
|
|
|
238
234
|
blocks,
|
|
239
235
|
});
|
|
240
236
|
}
|
|
237
|
+
// Preloaded material follows every author prompt so the stable prefix stays byte-identical.
|
|
238
|
+
if (input.preload !== undefined && input.preload.length > 0) {
|
|
239
|
+
const preloadBlocks = input.preload.map(({ source, markdown }) => ({
|
|
240
|
+
source,
|
|
241
|
+
markdown,
|
|
242
|
+
}));
|
|
243
|
+
logicalMessages.push({
|
|
244
|
+
messageRole: "system",
|
|
245
|
+
promptId: "runtime.preload",
|
|
246
|
+
role: "world_context",
|
|
247
|
+
markdown: joinBlocks(preloadBlocks),
|
|
248
|
+
blocks: preloadBlocks,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
241
251
|
const tools = structuredClone(input.tools);
|
|
242
252
|
const toolStrategy = this.#toolStrategyOverride ??
|
|
243
253
|
defaultRuntimeToolDefinitionStrategy(input.modelBinding.provider);
|
|
@@ -870,9 +880,13 @@ function settingImprovementPresetReference(binding, locale, target, gameplay) {
|
|
|
870
880
|
return [
|
|
871
881
|
{
|
|
872
882
|
...worldInstructionPlaceholder,
|
|
873
|
-
markdown:
|
|
874
|
-
?
|
|
875
|
-
|
|
883
|
+
markdown: revision
|
|
884
|
+
? locale === "zh-CN"
|
|
885
|
+
? "世界指令在此展开;启用块原文见本请求的 Runtime 预载。"
|
|
886
|
+
: "World instructions expand here; enabled block text appears in this request's Runtime preload."
|
|
887
|
+
: locale === "zh-CN"
|
|
888
|
+
? "世界指令在此展开;frame 选定材料仅在全新游玩上下文中作为初始工具结果提供。请通过本轮创作工具检查实际内容。"
|
|
889
|
+
: "World instructions expand here; frame-selected material is supplied as an initial tool result only for a fresh play context. Inspect actual content through the current authoring tools.",
|
|
876
890
|
},
|
|
877
891
|
];
|
|
878
892
|
if (entry.kind === "tail" || !entry.enabled)
|
|
@@ -949,11 +963,13 @@ function settingImprovementPresetReference(binding, locale, target, gameplay) {
|
|
|
949
963
|
: "Player-visible narrative semantics", locale === "zh-CN"
|
|
950
964
|
? "这些块只在未来游玩生成玩家可见正文时生效;不得把它们当作本轮 YAML 或 Markdown 设定正文的范文。"
|
|
951
965
|
: "These blocks apply only when future play produces player-visible story. Do not use them as prose models for YAML or Markdown setting documents in this conversation.", narrativeBlocks),
|
|
952
|
-
|
|
953
|
-
?
|
|
954
|
-
:
|
|
955
|
-
|
|
956
|
-
|
|
966
|
+
revision
|
|
967
|
+
? null
|
|
968
|
+
: settingImprovementReferenceGroup(locale === "zh-CN"
|
|
969
|
+
? "本轮提示(每次发送的尾部)"
|
|
970
|
+
: "Turn guide (per-send suffix)", locale === "zh-CN"
|
|
971
|
+
? "这些行在每次游玩发送时追加在玩家输入之后,由已启用块的尾部行按同一顺序拼成;它们是提醒,不是世界事实。"
|
|
972
|
+
: "These lines are appended after the player input on every play send, assembled from the enabled blocks' turn-guide lines in the same order. They are reminders, not world facts.", turnGuideBlocks),
|
|
957
973
|
].filter((group) => group !== null);
|
|
958
974
|
return [
|
|
959
975
|
heading,
|
|
@@ -134,11 +134,7 @@ function createToolDefinitions(locale) {
|
|
|
134
134
|
...resolutionDefinitions(locale),
|
|
135
135
|
state_list: {
|
|
136
136
|
description: descriptions.state_list,
|
|
137
|
-
inputSchema: object({
|
|
138
|
-
parent: string,
|
|
139
|
-
cursor: { type: ["string", "null"] },
|
|
140
|
-
limit: { type: "integer", minimum: 1, maximum: 100 },
|
|
141
|
-
}, ["parent"]),
|
|
137
|
+
inputSchema: object({ parent: string }, ["parent"]),
|
|
142
138
|
},
|
|
143
139
|
history_list: {
|
|
144
140
|
description: descriptions.history_list,
|
|
@@ -294,8 +290,8 @@ function resolutionDefinitions(locale) {
|
|
|
294
290
|
const toolDescriptions = {
|
|
295
291
|
en: {
|
|
296
292
|
world_checkpoint: "Register a gap between events and suggest that the player start a fresh context here. Arguments must be an empty object. The effect and replay scope are defined by the tools-and-settlement rules; when to register is defined by the checkpoint policy. Consolidation and maintenance do not require this tool; do not advance the story to register a checkpoint.",
|
|
297
|
-
state_list: "List document and state directory handles known to Runtime. parent must be @dir-/ for the state root or an @dir-* handle from the initial directory index or an earlier state_list result; no preliminary listing is needed for an already supplied handle. Catalog directories declared by the frozen world frame remain listed and usable when empty.
|
|
298
|
-
history_list: "Browse committed history on the current timeline. Omit anchors for the latest page; use only before or after with a Runtime ref (including refs in injected history) for the immediately adjacent page, excluding the anchor. Every page is oldest to newest. limit defaults to 20 (1–100 messages). Previews are only the first 120 Unicode code points. Use independent earlierCursor/laterCursor with {cursor} alone; no first-query parameters on continuation. Cursors bind world, tool, query and immutable ordered history. After history_changed rerun the query. Opening messages are queryable. Read full originals with history_read; positions are snapshot locations, not identities or turns.",
|
|
293
|
+
state_list: "List document and state directory handles known to Runtime. parent must be @dir-/ for the state root or an @dir-* handle from the initial directory index or an earlier state_list result; no preliminary listing is needed for an already supplied handle. Catalog directories declared by the frozen world frame remain listed and usable when empty. Each call returns the whole directory with Complete: yes. Pass returned document handles to context_read. This tool never lists committed history.",
|
|
294
|
+
history_list: "Browse committed history on the current timeline. Omit anchors for the latest page; use only before or after with a Runtime ref (including refs in injected history) for the immediately adjacent page, excluding the anchor. Every page is oldest to newest in Authority acceptance order, not story dates. limit defaults to 20 (1–100 messages). Previews are only the first 120 Unicode code points. Historical background describes the captured state, not current world state; interpret memories, dreams and local times with the original text, and it does not certify facts or grant character knowledge. Use independent earlierCursor/laterCursor with {cursor} alone; no first-query parameters on continuation. Cursors bind world, tool, query and immutable ordered history. After history_changed rerun the query. Opening messages are queryable. Read full originals with history_read; positions are snapshot locations, not identities or turns.",
|
|
299
295
|
history_search: "Search committed originals on the current timeline with queries: 1–5 nonempty literal strings (max 256 code points each); no query alias or semantic filters. match defaults to any, or all within one message. NFKC normalization always applies; caseSensitive defaults to false (locale-und case folding), normalized duplicate queries collapse. order defaults to newest_first, or oldest_first. limit defaults to 10 (1–50 matching messages). Optional after/before refs exclude timeline boundaries independently of order; after must precede before. Each result has at most 3 partial original fragments, query occurrence counts and complete-original length. Read a hit and nearby context with history_read. Continue with {cursor} alone; history_changed requires a new query.",
|
|
300
296
|
history_read: "Read complete committed originals on the current timeline, never summarized, truncated or body-paginated. Use {latest:N} alone (1–21 messages), or {ref,before,after} with defaults before=after=0 and sum at most 20; do not mix modes. Ref may come directly from Runtime prompt injections or tools without listing first. Windows are always oldest to newest, including the anchor. earlier/later are immediately outside this window and can be the next ref with before/after to avoid rereading. Boundaries and empty history are normal. Counts are messages, not turns; 21 long messages can produce large output.",
|
|
301
297
|
context_search: "Search literal source text in state documents. Use source=state; committed history uses history_search. Zero matches means only that the supplied text had no literal match; it does not prove that a world fact does not exist. Player-visible narrative must not recount the search process. within accepts only an @document or @dir-* handle returned by Runtime.",
|
|
@@ -308,8 +304,8 @@ const toolDescriptions = {
|
|
|
308
304
|
},
|
|
309
305
|
"zh-CN": {
|
|
310
306
|
world_checkpoint: "登记事件间隙,向玩家建议从这里开始全新上下文。参数为空对象。生效条件与补入范围见“工具与响应结算”;何时登记见检查点政策。整理与归并不需要调用本工具;不要为了登记而推进剧情。",
|
|
311
|
-
state_list: "列出 Runtime 已知的文档与状态目录句柄。状态根目录的 parent 使用 @dir-/;继续下级时使用初始目录索引或 state_list 返回的 @dir-*;已有句柄无需再次列目录确认。冻结世界 frame 声明的 catalog
|
|
312
|
-
history_list: "浏览当前时间线已提交历史。无锚点时取最新一页;before 或 after 只能选一个,取锚点紧邻前/后的一页且不含锚点。页内始终由旧到新。limit 默认20,整数1–100
|
|
307
|
+
state_list: "列出 Runtime 已知的文档与状态目录句柄。状态根目录的 parent 使用 @dir-/;继续下级时使用初始目录索引或 state_list 返回的 @dir-*;已有句柄无需再次列目录确认。冻结世界 frame 声明的 catalog 目录即使为空,也会继续列出并可用。每次调用整份返回该目录,末尾 Complete: yes。返回的文档句柄交给 context_read;本工具不列出已提交历史。",
|
|
308
|
+
history_list: "浏览当前时间线已提交历史。无锚点时取最新一页;before 或 after 只能选一个,取锚点紧邻前/后的一页且不含锚点。页内始终由旧到新。limit 默认20,整数1–100。排序依据是 Authority 接受顺序,不代表故事内日期。历史背景仅说明采集位置的状态,不是当前世界状态;回忆、梦境或段内局部时间结合原文解释,不认证事实或授予角色知识。预览仅含前120个Unicode码点;完整原文用history_read。earlierCursor/laterCursor独立翻页,续页只传{cursor},不可混用首次参数。游标绑定世界、工具、查询和有序历史快照;history_changed须重新查询。开场白可查询。提示词注入中的ref可直接使用,无需再次列举;位置不是身份或回合数。",
|
|
313
309
|
history_search: "搜索当前时间线已提交原文。queries必填:1–5个非空字面词,每词最多256个Unicode码点,不接受query别名或语义筛选。match默认any,all限于同一消息。始终NFKC规范化,caseSensitive默认false(locale-und大小写折叠),规范化重复词合并。order默认newest_first,可选oldest_first;limit默认10,整数1–50条命中消息。after/before始终按时间线先后排除边界,after必须早于before,与结果排序无关。每项最多3个部分原文片段、逐词命中次数及完整原文长度;可用history_read读取前后文。续页只传{cursor};history_changed须重新查询。",
|
|
314
310
|
history_read: "完整读取当前时间线已提交原文,无摘要、无截断、无正文分页。{latest:N}单独读取最近1–21条;或{ref,before,after},before/after默认0且合计最多20;两模式互斥。ref可直接使用Runtime提示词注入或工具结果的历史句柄,不必先列举。窗口始终由旧到新且包含锚点。earlier/later是窗口外紧邻句柄,可作为下一次ref并配before/after避免重复读取;边界与空历史正常。数量单位是消息而非回合;21条长消息仍可能产生大量输出。",
|
|
315
311
|
context_search: "在状态文档原文字面中搜索,source只能为state;已提交历史使用history_search。0 命中只表示给定文本没有字面命中,不证明世界事实不存在;玩家可见叙事不得复述搜索过程。within 只能使用 Runtime 已返回的 @文档或 @dir-* 句柄。",
|