narraeon 0.7.9 → 0.8.0
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/protocol/modelConnections.js +15 -0
- package/dist/node/protocol/modelUsage.js +4 -0
- package/dist/node/protocol/sillyTavernContent.js +1 -0
- package/dist/node/protocol/v1.js +75 -0
- package/dist/node/runtime/V1Runtime.js +71 -1
- package/dist/node/runtime/authoring/AuthoringRequestSnapshot.js +15 -3
- package/dist/node/runtime/authoring/DamagedGoogleContinuation.js +68 -0
- package/dist/node/runtime/authoring/ModelToolConversation.js +47 -22
- package/dist/node/runtime/content/ContentWorkspace.js +3 -0
- package/dist/node/runtime/content/SillyTavernContent.js +796 -0
- package/dist/node/runtime/model/AiFailureLog.js +8 -1
- package/dist/node/runtime/model/FileNativeModelAdapters.js +610 -18
- package/dist/node/runtime/model/GoogleHopJournal.js +90 -0
- package/dist/node/runtime/model/GoogleInteractionsStream.js +242 -0
- package/dist/node/runtime/model/GoogleRequestPreview.js +184 -0
- package/dist/node/runtime/model/ModelConnectionStore.js +4 -1
- package/dist/node/runtime/model/ProviderExchangeState.js +44 -1
- package/dist/node/runtime/model/ProviderStream.js +4 -2
- package/dist/node/runtime/play/FileNativePlayAdvanceStore.js +4 -4
- package/dist/node/runtime/play/FileNativePlayPresetStore.js +150 -78
- package/dist/node/runtime/play/FileNativePlayTimelineStore.js +150 -10
- package/dist/node/runtime/play/OrderedPlayPrompts.js +139 -178
- package/dist/node/runtime/play/PlayAdvanceLifecycle.js +33 -9
- package/dist/node/runtime/play/PlayCallChain.js +76 -4
- package/dist/node/runtime/play/PlayFollowupRequests.js +23 -3
- package/dist/node/runtime/prompt/PromptCompilationCodec.js +2 -1
- package/dist/node/runtime/setting/FileNativeSettingImprovementStore.js +112 -8
- package/dist/node/runtime/setting/SettingAuthoringTransaction.js +100 -41
- package/dist/node/runtime/setting/SettingImprovementSession.js +48 -7
- package/dist/node/runtime/world-revision/FileNativeWorldRevisionStore.js +84 -8
- package/dist/node/runtime/world-revision/WorldRevisionSession.js +125 -17
- package/dist/node/shared/application-preset-files.js +29 -0
- package/dist/node/shared/default-preset-host.js +50 -38
- package/dist/node/shared/first-party-action-choices.js +7 -11
- package/dist/node/shared/first-party-generic-panels.js +7 -11
- package/dist/node/shared/first-party-player-view.js +7 -11
- package/dist/node/shared/ordered-author-prompts.js +6 -4
- package/dist/node/shared/ordered-play-prompts.js +4 -2
- package/dist/node/shared/prompt-arrangement.js +205 -0
- package/dist/node/shared/sillytavern-preset.js +454 -0
- package/dist/web/assets/{index-DH2BVsYd.css → index-BKA1D0Rz.css} +1 -1
- package/dist/web/assets/index-C_Qta8UL.js +1432 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-B2kbaOGe.js +0 -1408
|
@@ -5,7 +5,7 @@ import { contentTreeFingerprint } from "../content/ContentTreeFingerprint.js";
|
|
|
5
5
|
import { equalModelHostBinding } from "../model/ModelHost.js";
|
|
6
6
|
import { settingImprovementPromptForBinding, } from "../play/FileNativePlayPresetStore.js";
|
|
7
7
|
import { WorldDocumentStore } from "../world/WorldDocumentStore.js";
|
|
8
|
-
import { appendAuthoringUserMessage, ModelToolConversation, rememberAuthoringRequest, resumePausedAuthoringConversation, } from "../authoring/ModelToolConversation.js";
|
|
8
|
+
import { appendAuthoringUserMessage, authoringExchange, ModelToolConversation, rememberAuthoringRequest, resumePausedAuthoringConversation, } from "../authoring/ModelToolConversation.js";
|
|
9
9
|
import { summarizeStoredSettingImprovementSession, } from "./FileNativeSettingImprovementStore.js";
|
|
10
10
|
import { SettingAuthoringTransaction, settingImprovementRuntimeContract, settingImprovementToolDefinitions, authoringPreloadAuthorization, buildAuthoringPreload, contentPackageAuthoringSurface, } from "./SettingAuthoringTransaction.js";
|
|
11
11
|
class RecoveryWriteConflictError extends Error {
|
|
@@ -62,7 +62,7 @@ export class SettingImprovementSession {
|
|
|
62
62
|
async overview(packageId) {
|
|
63
63
|
return this.#mutate(async () => {
|
|
64
64
|
let summaries = await this.#store.listSummariesByPackage(packageId);
|
|
65
|
-
const latestSummary = summaries
|
|
65
|
+
const latestSummary = summaries.find((summary) => !summary.damagedContinuation);
|
|
66
66
|
const latest = latestSummary === undefined
|
|
67
67
|
? null
|
|
68
68
|
: await this.#recoverIfNeeded(await this.#store.read(latestSummary.sessionId));
|
|
@@ -187,6 +187,8 @@ export class SettingImprovementSession {
|
|
|
187
187
|
assertPackage(session, input.packageId);
|
|
188
188
|
}
|
|
189
189
|
return this.#startRequest(session, requestId, async (next) => {
|
|
190
|
+
if (next.runStatus === "paused")
|
|
191
|
+
throw new Error("Resume the paused authoring request before sending another message");
|
|
190
192
|
const { playPreset, bootstrap, preload } = await this.#compileCurrent(input.packageId, next.modelBinding);
|
|
191
193
|
next.schemaVersion = 3;
|
|
192
194
|
next.requests ??= [];
|
|
@@ -199,6 +201,8 @@ export class SettingImprovementSession {
|
|
|
199
201
|
});
|
|
200
202
|
next.authorization = authoringPreloadAuthorization(next.authorization, preload.record);
|
|
201
203
|
appendAuthoringUserMessage(next, message, requestId);
|
|
204
|
+
if (next.modelBinding.provider === "google_interactions")
|
|
205
|
+
next.googleOutcomeUnknown = false;
|
|
202
206
|
});
|
|
203
207
|
});
|
|
204
208
|
return prepared.run ?? this.#view(prepared.session);
|
|
@@ -223,10 +227,16 @@ export class SettingImprovementSession {
|
|
|
223
227
|
throw new Error("Another message in this setting-improvement conversation is still running");
|
|
224
228
|
return { session, run: running.promise };
|
|
225
229
|
}
|
|
230
|
+
if (session.googleOutcomeUnknown)
|
|
231
|
+
throw new Error("The Google request outcome is unknown; start a fresh model context.");
|
|
226
232
|
const host = await this.#bindExistingModelHost(session.modelBinding);
|
|
227
233
|
if (!equalModelHostBinding(session.modelBinding, host.binding()))
|
|
228
234
|
throw new Error("No saved model connection matches this setting-improvement conversation");
|
|
229
235
|
await prepare(session);
|
|
236
|
+
if (session.modelBinding.provider === "google_interactions" &&
|
|
237
|
+
host.previewAuthoringRequest !== undefined &&
|
|
238
|
+
session.requests?.at(-1)?.requestId === requestId)
|
|
239
|
+
session.requests.at(-1).wireRequest = host.previewAuthoringRequest(authoringExchange(session, requestId));
|
|
230
240
|
await this.#store.save(session);
|
|
231
241
|
return { session, run: this.#startRun(session, host, requestId) };
|
|
232
242
|
}
|
|
@@ -317,9 +327,10 @@ export class SettingImprovementSession {
|
|
|
317
327
|
tools,
|
|
318
328
|
});
|
|
319
329
|
const now = Date.now();
|
|
330
|
+
const sessionId = this.#store.createId();
|
|
320
331
|
const session = {
|
|
321
332
|
schemaVersion: 2,
|
|
322
|
-
sessionId
|
|
333
|
+
sessionId,
|
|
323
334
|
packageId,
|
|
324
335
|
contentPackageTitle: package_.title,
|
|
325
336
|
locale,
|
|
@@ -327,6 +338,9 @@ export class SettingImprovementSession {
|
|
|
327
338
|
createdAt: now,
|
|
328
339
|
updatedAt: now,
|
|
329
340
|
creationRequestId,
|
|
341
|
+
...(modelBinding.provider === "google_interactions"
|
|
342
|
+
? { googleIdentity: { sessionId, packageId, creationRequestId } }
|
|
343
|
+
: {}),
|
|
330
344
|
bootstrap,
|
|
331
345
|
modelBinding,
|
|
332
346
|
playPreset: structuredClone(playPreset),
|
|
@@ -353,6 +367,7 @@ export class SettingImprovementSession {
|
|
|
353
367
|
host,
|
|
354
368
|
requestId,
|
|
355
369
|
save: (next) => this.#store.save(next),
|
|
370
|
+
recordGoogleHop: (id, hop) => this.#store.recordGoogleHop(session.sessionId, id, hop),
|
|
356
371
|
settleToolResponse: async (next, assistantItemIndex, toolCalls) => {
|
|
357
372
|
activeTransaction = await this.#settleToolResponse(next, assistantItemIndex, toolCalls, activeTransaction);
|
|
358
373
|
},
|
|
@@ -451,6 +466,8 @@ export class SettingImprovementSession {
|
|
|
451
466
|
return this.#recoverIfNeeded(await this.#store.read(session.sessionId));
|
|
452
467
|
}
|
|
453
468
|
if (session.pendingSettlement !== null) {
|
|
469
|
+
if (session.modelBinding.provider === "google_interactions")
|
|
470
|
+
session.googleOutcomeUnknown = false;
|
|
454
471
|
const lease = await this.#content.beginCurrentTreeContentPackageOperation(session.packageId);
|
|
455
472
|
let retry = false;
|
|
456
473
|
try {
|
|
@@ -510,7 +527,21 @@ export class SettingImprovementSession {
|
|
|
510
527
|
return this.#recoverIfNeeded(await this.#store.read(session.sessionId));
|
|
511
528
|
return session;
|
|
512
529
|
}
|
|
513
|
-
|
|
530
|
+
const lastItem = session.modelItems.at(-1);
|
|
531
|
+
const legacyUnknown = session.modelBinding.provider === "google_interactions" &&
|
|
532
|
+
session.runStatus === "interrupted" &&
|
|
533
|
+
session.googleOutcomeUnknown === undefined &&
|
|
534
|
+
(lastItem?.kind === "user" || lastItem?.kind === "tool");
|
|
535
|
+
if (legacyUnknown) {
|
|
536
|
+
session.googleOutcomeUnknown = true;
|
|
537
|
+
session.lastFailure =
|
|
538
|
+
"The Google request outcome is unknown; start a fresh model context.";
|
|
539
|
+
session.updatedAt = Date.now();
|
|
540
|
+
}
|
|
541
|
+
else if (session.runStatus === "running") {
|
|
542
|
+
if (session.modelBinding.provider === "google_interactions" &&
|
|
543
|
+
session.googleOutcomeUnknown !== false)
|
|
544
|
+
session.googleOutcomeUnknown = true;
|
|
514
545
|
markInterruptedAfterRestart(session);
|
|
515
546
|
session.updatedAt = Date.now();
|
|
516
547
|
}
|
|
@@ -606,12 +637,17 @@ function lastPendingAssistant(session) {
|
|
|
606
637
|
}
|
|
607
638
|
function markInterruptedAfterRestart(session) {
|
|
608
639
|
const requestId = session.activeRequestId;
|
|
609
|
-
session.runStatus =
|
|
640
|
+
session.runStatus =
|
|
641
|
+
session.modelBinding.provider === "google_interactions" &&
|
|
642
|
+
session.googleOutcomeUnknown === false
|
|
643
|
+
? "paused"
|
|
644
|
+
: "interrupted";
|
|
610
645
|
session.activeRequestId = null;
|
|
611
646
|
if (requestId !== null)
|
|
612
647
|
session.completedRequestIds = rememberAuthoringRequest(session.completedRequestIds, requestId);
|
|
613
|
-
session.lastFailure =
|
|
614
|
-
"The
|
|
648
|
+
session.lastFailure = session.googleOutcomeUnknown
|
|
649
|
+
? "The Google request outcome is unknown; start a fresh model context."
|
|
650
|
+
: "The previous model request stopped before its next complete response. Every settled current-tree change and complete tool result was retained; continue this conversation when ready.";
|
|
615
651
|
}
|
|
616
652
|
function requiredMessage(message) {
|
|
617
653
|
if (message.trim().length === 0)
|
|
@@ -750,6 +786,11 @@ function diffForRollback(files, change) {
|
|
|
750
786
|
function settingImprovementHistoryItem(summary) {
|
|
751
787
|
return {
|
|
752
788
|
sessionId: summary.sessionId,
|
|
789
|
+
...(summary.damagedContinuation
|
|
790
|
+
? {
|
|
791
|
+
unavailableReason: "Google continuation is damaged; start a fresh model context.",
|
|
792
|
+
}
|
|
793
|
+
: {}),
|
|
753
794
|
runStatus: summary.runStatus,
|
|
754
795
|
createdAt: summary.createdAt,
|
|
755
796
|
updatedAt: summary.updatedAt,
|
|
@@ -3,9 +3,12 @@ import { upgradeLegacyEpochs } from "./LegacyWorldRevisionEpochUpgrade.js";
|
|
|
3
3
|
import { isPackageFollowupControlPath } from "../content/PackageFollowups.js";
|
|
4
4
|
import { validAuthoringRequests, } from "../authoring/AuthoringRequestSnapshot.js";
|
|
5
5
|
import { createHash, randomUUID } from "node:crypto";
|
|
6
|
-
import {
|
|
6
|
+
import { readGoogleHops, writeGoogleHop } from "../model/GoogleHopJournal.js";
|
|
7
|
+
import { mkdir, open, readFile, readdir, rename, rm, stat, } from "node:fs/promises";
|
|
7
8
|
import { dirname, join, resolve } from "node:path";
|
|
8
9
|
import { gameplayPath } from "../../protocol/gameplay.js";
|
|
10
|
+
import { validGoogleInteractionsSteps } from "../model/ProviderExchangeState.js";
|
|
11
|
+
import { hasDamagedGoogleSteps, withoutDamagedGoogleSteps, } from "../authoring/DamagedGoogleContinuation.js";
|
|
9
12
|
import { contentTreeFingerprint } from "../content/ContentTreeFingerprint.js";
|
|
10
13
|
import { isPlayPresetBinding, } from "../play/FileNativePlayPresetStore.js";
|
|
11
14
|
const uuidV4Pattern = "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}";
|
|
@@ -46,6 +49,10 @@ export class FileNativeWorldRevisionStore {
|
|
|
46
49
|
createSessionId() {
|
|
47
50
|
return `world-session-${randomUUID()}`;
|
|
48
51
|
}
|
|
52
|
+
recordGoogleHop(sessionId, requestId, hop) {
|
|
53
|
+
assertSessionId(sessionId);
|
|
54
|
+
return writeGoogleHop(join(this.#sessionsRoot, ".google-hops", sessionId), requestId, hop, () => stat(this.#sessionPath(sessionId)).then(() => true, () => false));
|
|
55
|
+
}
|
|
49
56
|
createChangeSetId() {
|
|
50
57
|
return `change-set:${randomUUID()}`;
|
|
51
58
|
}
|
|
@@ -89,7 +96,20 @@ export class FileNativeWorldRevisionStore {
|
|
|
89
96
|
});
|
|
90
97
|
}
|
|
91
98
|
readSession(sessionId) {
|
|
92
|
-
return this.#afterMutations(() =>
|
|
99
|
+
return this.#afterMutations(async () => {
|
|
100
|
+
try {
|
|
101
|
+
const session = await this.#readSession(sessionId);
|
|
102
|
+
if (hasDamagedGoogleSteps(session) &&
|
|
103
|
+
(await this.#damagedSession(sessionId)))
|
|
104
|
+
throw new Error("The historical conversation has damaged Google continuation; start a fresh model context");
|
|
105
|
+
return session;
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
if (await this.#damagedSession(sessionId))
|
|
109
|
+
throw new Error("The historical conversation has damaged Google continuation; start a fresh model context", { cause: error });
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
});
|
|
93
113
|
}
|
|
94
114
|
saveSession(session) {
|
|
95
115
|
return this.#mutate(async () => {
|
|
@@ -110,7 +130,19 @@ export class FileNativeWorldRevisionStore {
|
|
|
110
130
|
for (const entry of entries) {
|
|
111
131
|
if (!entry.isFile() || !sessionName.test(entry.name))
|
|
112
132
|
continue;
|
|
113
|
-
const
|
|
133
|
+
const sessionId = entry.name.slice(0, -5);
|
|
134
|
+
let session;
|
|
135
|
+
try {
|
|
136
|
+
session = await this.#readSession(sessionId);
|
|
137
|
+
if (hasDamagedGoogleSteps(session))
|
|
138
|
+
session = (await this.#damagedSession(sessionId)) ?? session;
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
const damaged = await this.#damagedSession(sessionId);
|
|
142
|
+
if (damaged === null)
|
|
143
|
+
throw error;
|
|
144
|
+
session = damaged;
|
|
145
|
+
}
|
|
114
146
|
if (session.worldId === worldId)
|
|
115
147
|
sessions.push(session);
|
|
116
148
|
}
|
|
@@ -118,7 +150,10 @@ export class FileNativeWorldRevisionStore {
|
|
|
118
150
|
});
|
|
119
151
|
}
|
|
120
152
|
async findByCreationRequest(worldId, requestId) {
|
|
121
|
-
|
|
153
|
+
const match = (await this.listSessions(worldId)).find(({ creationRequestId }) => creationRequestId === requestId);
|
|
154
|
+
if (match?.damagedContinuation)
|
|
155
|
+
throw new Error("The historical conversation has damaged Google continuation; start a fresh model context with a new request ID");
|
|
156
|
+
return match ?? null;
|
|
122
157
|
}
|
|
123
158
|
deleteSession(worldId, sessionId) {
|
|
124
159
|
return this.#mutate(async () => {
|
|
@@ -126,6 +161,10 @@ export class FileNativeWorldRevisionStore {
|
|
|
126
161
|
if (session.worldId !== worldId)
|
|
127
162
|
throw new Error("World-revision conversation belongs to another world");
|
|
128
163
|
await rm(this.#sessionPath(sessionId));
|
|
164
|
+
await rm(join(this.#sessionsRoot, ".google-hops", sessionId), {
|
|
165
|
+
recursive: true,
|
|
166
|
+
force: true,
|
|
167
|
+
});
|
|
129
168
|
});
|
|
130
169
|
}
|
|
131
170
|
async #readEpoch(epochId) {
|
|
@@ -155,8 +194,34 @@ export class FileNativeWorldRevisionStore {
|
|
|
155
194
|
const session = validateSession(value);
|
|
156
195
|
if (session.sessionId !== sessionId)
|
|
157
196
|
throw new Error("World-revision conversation identity does not match its file");
|
|
197
|
+
for (const request of session.requests ?? [])
|
|
198
|
+
if (request.wireRequest !== undefined)
|
|
199
|
+
request.wireRequest = await readGoogleHops(join(this.#sessionsRoot, ".google-hops", sessionId), request.requestId, request.wireRequest);
|
|
158
200
|
return session;
|
|
159
201
|
}
|
|
202
|
+
async #damagedSession(sessionId) {
|
|
203
|
+
const raw = JSON.parse(await readFile(this.#sessionPath(sessionId), "utf8"));
|
|
204
|
+
const copy = withoutDamagedGoogleSteps(raw);
|
|
205
|
+
if (copy === null) {
|
|
206
|
+
if (hasDamagedGoogleSteps(raw))
|
|
207
|
+
throw new Error("Cannot safely identify the damaged historical conversation; settlement or identity is unverified");
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
const session = validateSession(copy);
|
|
211
|
+
if (session.sessionId !== sessionId ||
|
|
212
|
+
session.requests?.[0]?.requestId !== session.creationRequestId ||
|
|
213
|
+
!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/u.test(session.creationRequestId))
|
|
214
|
+
throw new Error("Cannot safely identify the damaged historical conversation; its creation identity is unverified");
|
|
215
|
+
const epoch = await this.#readEpoch(session.epochId);
|
|
216
|
+
if (epoch.worldId !== session.worldId)
|
|
217
|
+
throw new Error("Cannot safely identify the damaged historical conversation; its world is inconsistent");
|
|
218
|
+
return {
|
|
219
|
+
...session,
|
|
220
|
+
damagedContinuation: true,
|
|
221
|
+
runStatus: "interrupted",
|
|
222
|
+
lastFailure: "Google continuation is damaged; start a fresh model context.",
|
|
223
|
+
};
|
|
224
|
+
}
|
|
160
225
|
async #afterMutations(read) {
|
|
161
226
|
await this.#mutationTail;
|
|
162
227
|
return read();
|
|
@@ -274,7 +339,7 @@ function validateSession(value) {
|
|
|
274
339
|
"lastFailure",
|
|
275
340
|
"authorization",
|
|
276
341
|
"pendingSettlement",
|
|
277
|
-
], ["requests"]) ||
|
|
342
|
+
], ["requests", "googleOutcomeUnknown"]) ||
|
|
278
343
|
(value.schemaVersion !== 1 && value.schemaVersion !== 2) ||
|
|
279
344
|
(value.schemaVersion === 1
|
|
280
345
|
? value.requests !== undefined
|
|
@@ -311,6 +376,10 @@ function validateSession(value) {
|
|
|
311
376
|
!Array.isArray(value.completedRequestIds) ||
|
|
312
377
|
!value.completedRequestIds.every((item) => typeof item === "string") ||
|
|
313
378
|
(value.lastFailure !== null && typeof value.lastFailure !== "string") ||
|
|
379
|
+
(value.googleOutcomeUnknown !== undefined &&
|
|
380
|
+
(typeof value.googleOutcomeUnknown !== "boolean" ||
|
|
381
|
+
!isRecord(value.modelBinding) ||
|
|
382
|
+
value.modelBinding.provider !== "google_interactions")) ||
|
|
314
383
|
!validAuthorization(value.authorization) ||
|
|
315
384
|
!validPendingSettlement(value.pendingSettlement) ||
|
|
316
385
|
!validPendingForSession(value.pendingSettlement, value.epochId, value.runStatus) ||
|
|
@@ -452,6 +521,9 @@ function validModelToolCall(value) {
|
|
|
452
521
|
function validProviderState(value) {
|
|
453
522
|
if (!isRecord(value))
|
|
454
523
|
return false;
|
|
524
|
+
if (value.protocol === "google_interactions")
|
|
525
|
+
return (hasExactKeys(value, ["protocol", "steps"]) &&
|
|
526
|
+
validGoogleInteractionsSteps(value.steps));
|
|
455
527
|
if (value.protocol === "chat_completions")
|
|
456
528
|
return hasExactKeys(value, ["protocol", "assistantMessage"]);
|
|
457
529
|
if (value.protocol === "openai_responses")
|
|
@@ -509,7 +581,8 @@ function validModelBinding(value) {
|
|
|
509
581
|
], ["cacheStrategy"]) &&
|
|
510
582
|
(value.provider === "chat_completions" ||
|
|
511
583
|
value.provider === "openai_responses" ||
|
|
512
|
-
value.provider === "anthropic_messages"
|
|
584
|
+
value.provider === "anthropic_messages" ||
|
|
585
|
+
value.provider === "google_interactions") &&
|
|
513
586
|
typeof value.endpointFingerprint === "string" &&
|
|
514
587
|
typeof value.modelId === "string" &&
|
|
515
588
|
count(value.contextWindowTokens) &&
|
|
@@ -531,7 +604,10 @@ function validModelUsage(value) {
|
|
|
531
604
|
"totalTokens",
|
|
532
605
|
];
|
|
533
606
|
if (!isRecord(value) ||
|
|
534
|
-
!hasExactKeys(value, [...fields, "provenance"]) ||
|
|
607
|
+
!hasExactKeys(value, [...fields, "provenance"], ["providerRaw", "accounting"]) ||
|
|
608
|
+
(value.providerRaw !== undefined && !isRecord(value.providerRaw)) ||
|
|
609
|
+
(value.accounting !== undefined &&
|
|
610
|
+
value.accounting !== "google_interactions") ||
|
|
535
611
|
!fields.every((field) => value[field] === null || count(value[field])) ||
|
|
536
612
|
!isRecord(value.provenance) ||
|
|
537
613
|
!hasExactKeys(value.provenance, fields))
|
|
@@ -594,7 +670,7 @@ function validPendingSettlement(value) {
|
|
|
594
670
|
function validPendingForSession(pending, epochId, runStatus) {
|
|
595
671
|
return (pending === null ||
|
|
596
672
|
(isRecord(pending) &&
|
|
597
|
-
runStatus === "running" &&
|
|
673
|
+
(runStatus === "running" || runStatus === "interrupted") &&
|
|
598
674
|
pending.epochId === epochId));
|
|
599
675
|
}
|
|
600
676
|
function validToolResult(value) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { authoringRequestPreviews } from "../authoring/AuthoringRequestSnapshot.js";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
+
import { isDeepStrictEqual } from "node:util";
|
|
3
4
|
import { emptyAggregatedModelUsage } from "../../protocol/modelUsage.js";
|
|
4
|
-
import { appendAuthoringUserMessage, ModelToolConversation, rememberAuthoringRequest, resumePausedAuthoringConversation, } from "../authoring/ModelToolConversation.js";
|
|
5
|
+
import { appendAuthoringUserMessage, authoringExchange, ModelToolConversation, rememberAuthoringRequest, resumePausedAuthoringConversation, } from "../authoring/ModelToolConversation.js";
|
|
5
6
|
import { contentTreeFingerprint } from "../content/ContentTreeFingerprint.js";
|
|
6
7
|
import { equalModelHostBinding } from "../model/ModelHost.js";
|
|
7
8
|
import { settingImprovementPromptForBinding, } from "../play/FileNativePlayPresetStore.js";
|
|
@@ -60,7 +61,7 @@ export class WorldRevisionSession {
|
|
|
60
61
|
this.#recoverWorldSessions(worldId),
|
|
61
62
|
this.#workspace.sealed(worldId),
|
|
62
63
|
]);
|
|
63
|
-
const latest = sessions
|
|
64
|
+
const latest = sessions.find((session) => !session.damagedContinuation) ?? null;
|
|
64
65
|
const shownEpoch = activeEpoch ?? (await this.#workspace.latest(worldId));
|
|
65
66
|
return {
|
|
66
67
|
epoch: shownEpoch === null
|
|
@@ -188,15 +189,25 @@ export class WorldRevisionSession {
|
|
|
188
189
|
throw new Error("Another message in this world-revision conversation is still running");
|
|
189
190
|
return { session, run: running.promise };
|
|
190
191
|
}
|
|
192
|
+
if (session.googleOutcomeUnknown)
|
|
193
|
+
throw new Error("The Google request outcome is unknown; start a fresh model context.");
|
|
194
|
+
if (session.runStatus === "paused")
|
|
195
|
+
throw new Error("Resume the paused authoring request before sending another message");
|
|
191
196
|
}
|
|
192
197
|
await this.#assertNoRunningConversation(input.worldId, session?.sessionId);
|
|
198
|
+
let host;
|
|
199
|
+
if (session !== null) {
|
|
200
|
+
host = await this.#bindExistingModelHost(session.modelBinding);
|
|
201
|
+
if (!equalModelHostBinding(session.modelBinding, host.binding()))
|
|
202
|
+
throw new Error("No saved model connection matches this world-revision conversation");
|
|
203
|
+
}
|
|
193
204
|
const epoch = await this.#workspace.open(input.worldId);
|
|
194
205
|
session ??= await this.#create(input.worldId, epoch, requestId);
|
|
195
206
|
if (session.epochId !== epoch.epochId) {
|
|
196
207
|
retargetSession(session, epoch, this.#locale());
|
|
197
208
|
await this.#store.saveSession(session);
|
|
198
209
|
}
|
|
199
|
-
|
|
210
|
+
host ??= await this.#bindExistingModelHost(session.modelBinding);
|
|
200
211
|
if (!equalModelHostBinding(session.modelBinding, host.binding()))
|
|
201
212
|
throw new Error("No saved model connection matches this world-revision conversation");
|
|
202
213
|
const { playPreset, bootstrap, preload } = await this.#compileCurrent(input.worldId, session.modelBinding);
|
|
@@ -214,10 +225,18 @@ export class WorldRevisionSession {
|
|
|
214
225
|
if (preload !== null)
|
|
215
226
|
session.authorization = authoringPreloadAuthorization(session.authorization, preload.record);
|
|
216
227
|
appendAuthoringUserMessage(session, message, requestId);
|
|
228
|
+
if (session.modelBinding.provider === "google_interactions")
|
|
229
|
+
session.googleOutcomeUnknown = false;
|
|
230
|
+
const boundaries = host.binding().provider === "google_interactions"
|
|
231
|
+
? await this.#validatedEpochBoundaries(session)
|
|
232
|
+
: undefined;
|
|
233
|
+
if (boundaries !== undefined &&
|
|
234
|
+
host.previewAuthoringRequest !== undefined)
|
|
235
|
+
session.requests.at(-1).wireRequest = host.previewAuthoringRequest(authoringExchange(session, requestId, boundaries));
|
|
217
236
|
await this.#store.saveSession(session);
|
|
218
237
|
return {
|
|
219
238
|
session,
|
|
220
|
-
run: this.#startRun(session, host, requestId),
|
|
239
|
+
run: this.#startRun(session, host, requestId, boundaries),
|
|
221
240
|
};
|
|
222
241
|
});
|
|
223
242
|
return prepared.run ?? this.#view(prepared.session);
|
|
@@ -245,8 +264,14 @@ export class WorldRevisionSession {
|
|
|
245
264
|
if (!equalModelHostBinding(session.modelBinding, host.binding()))
|
|
246
265
|
throw new Error("No saved model connection matches this world-revision conversation");
|
|
247
266
|
resumePausedAuthoringConversation(session);
|
|
267
|
+
const boundaries = host.binding().provider === "google_interactions"
|
|
268
|
+
? await this.#validatedEpochBoundaries(session)
|
|
269
|
+
: undefined;
|
|
248
270
|
await this.#store.saveSession(session);
|
|
249
|
-
return {
|
|
271
|
+
return {
|
|
272
|
+
session,
|
|
273
|
+
run: this.#startRun(session, host, requestId, boundaries),
|
|
274
|
+
};
|
|
250
275
|
});
|
|
251
276
|
return prepared.run ?? this.#view(prepared.session);
|
|
252
277
|
}
|
|
@@ -388,14 +413,18 @@ export class WorldRevisionSession {
|
|
|
388
413
|
await this.#store.saveSession(session);
|
|
389
414
|
return session;
|
|
390
415
|
}
|
|
391
|
-
#startRun(session, host, requestId) {
|
|
416
|
+
#startRun(session, host, requestId, worldRevisionBoundaries) {
|
|
392
417
|
let activeTransaction = null;
|
|
393
418
|
return this.#conversation.run({
|
|
394
419
|
changed: (durable) => this.#changed(session.worldId, durable),
|
|
395
420
|
session,
|
|
396
421
|
host,
|
|
397
422
|
requestId,
|
|
423
|
+
...(worldRevisionBoundaries === undefined
|
|
424
|
+
? {}
|
|
425
|
+
: { worldRevisionBoundaries }),
|
|
398
426
|
save: (next) => this.#store.saveSession(next),
|
|
427
|
+
recordGoogleHop: (id, hop) => this.#store.recordGoogleHop(session.sessionId, id, hop),
|
|
399
428
|
settleToolResponse: async (next, assistantItemIndex, calls) => {
|
|
400
429
|
activeTransaction = await this.#settleToolResponse(next, assistantItemIndex, calls, activeTransaction);
|
|
401
430
|
},
|
|
@@ -406,6 +435,32 @@ export class WorldRevisionSession {
|
|
|
406
435
|
},
|
|
407
436
|
});
|
|
408
437
|
}
|
|
438
|
+
async #validatedEpochBoundaries(session) {
|
|
439
|
+
const entries = [];
|
|
440
|
+
for (const [itemIndex, item] of session.modelItems.entries()) {
|
|
441
|
+
if (item.kind !== "prompt_delta")
|
|
442
|
+
continue;
|
|
443
|
+
const source = item.logicalMessages[0]?.blocks[0]?.source;
|
|
444
|
+
if (source?.startsWith("runtime:world-revision-epoch:") !== true)
|
|
445
|
+
continue;
|
|
446
|
+
const epochId = source.slice("runtime:world-revision-epoch:".length);
|
|
447
|
+
const epoch = await this.#store.readEpoch(epochId);
|
|
448
|
+
if (epoch.worldId !== session.worldId)
|
|
449
|
+
throw new Error("World-revision epoch boundary belongs to another world");
|
|
450
|
+
const valid = ["en", "zh-CN"].some((locale) => isDeepStrictEqual(item, epochBoundaryItem(epoch, locale)));
|
|
451
|
+
if (!valid)
|
|
452
|
+
throw new Error("World-revision epoch boundary is damaged");
|
|
453
|
+
entries.push({
|
|
454
|
+
itemIndex,
|
|
455
|
+
epochId,
|
|
456
|
+
baseHead: epoch.baseHead,
|
|
457
|
+
markdown: item.logicalMessages[0].markdown,
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
if (entries.length > 0 && entries.at(-1)?.epochId !== session.epochId)
|
|
461
|
+
throw new Error("World-revision epoch boundary does not match the current epoch");
|
|
462
|
+
return { currentEpochId: session.epochId, entries };
|
|
463
|
+
}
|
|
409
464
|
async #settleToolResponse(session, assistantItemIndex, calls, active) {
|
|
410
465
|
const epoch = await this.#requiredActiveEpoch(session);
|
|
411
466
|
const beforeRevision = epoch.revision;
|
|
@@ -512,9 +567,49 @@ export class WorldRevisionSession {
|
|
|
512
567
|
}
|
|
513
568
|
}
|
|
514
569
|
async #recoverIfNeeded(session) {
|
|
570
|
+
if (session.damagedContinuation)
|
|
571
|
+
return session;
|
|
515
572
|
if (this.#conversation.isRunning(session.sessionId))
|
|
516
573
|
return session;
|
|
574
|
+
const lastItem = session.modelItems.at(-1);
|
|
575
|
+
if (session.pendingSettlement === null &&
|
|
576
|
+
session.modelBinding.provider === "google_interactions" &&
|
|
577
|
+
session.runStatus === "interrupted" &&
|
|
578
|
+
session.googleOutcomeUnknown === undefined &&
|
|
579
|
+
(lastItem?.kind === "user" || lastItem?.kind === "tool")) {
|
|
580
|
+
session.googleOutcomeUnknown = true;
|
|
581
|
+
session.lastFailure =
|
|
582
|
+
"The Google request outcome is unknown; start a fresh model context.";
|
|
583
|
+
session.updatedAt = Date.now();
|
|
584
|
+
await this.#store.saveSession(session);
|
|
585
|
+
return session;
|
|
586
|
+
}
|
|
587
|
+
if (session.pendingSettlement === null &&
|
|
588
|
+
lastItem?.kind === "assistant" &&
|
|
589
|
+
lastItem.toolCalls.length > 0) {
|
|
590
|
+
finalizeUnconfirmed(session, session.modelItems.length - 1, new Error("Runtime found a complete tool response without a persisted worktree settlement intent"));
|
|
591
|
+
if (session.modelBinding.provider === "google_interactions")
|
|
592
|
+
session.googleOutcomeUnknown = false;
|
|
593
|
+
const requestId = session.activeRequestId;
|
|
594
|
+
session.runStatus = "interrupted";
|
|
595
|
+
session.activeRequestId = null;
|
|
596
|
+
if (requestId !== null)
|
|
597
|
+
session.completedRequestIds = rememberAuthoringRequest(session.completedRequestIds, requestId);
|
|
598
|
+
session.lastFailure =
|
|
599
|
+
"The complete tool response could not be settled; its calls received failure receipts. Re-read the worktree before editing.";
|
|
600
|
+
session.updatedAt = Date.now();
|
|
601
|
+
await this.#store.saveSession(session);
|
|
602
|
+
return session;
|
|
603
|
+
}
|
|
604
|
+
if (session.runStatus === "running" &&
|
|
605
|
+
session.pendingSettlement === null &&
|
|
606
|
+
session.modelBinding.provider === "google_interactions" &&
|
|
607
|
+
session.googleOutcomeUnknown !== false)
|
|
608
|
+
session.googleOutcomeUnknown = true;
|
|
609
|
+
const recoveringSettlement = session.pendingSettlement !== null;
|
|
517
610
|
if (session.pendingSettlement !== null) {
|
|
611
|
+
if (session.modelBinding.provider === "google_interactions")
|
|
612
|
+
session.googleOutcomeUnknown = false;
|
|
518
613
|
const pending = session.pendingSettlement;
|
|
519
614
|
if (pending.phase === "publication_prepared") {
|
|
520
615
|
const active = await this.#workspace.active(session.worldId);
|
|
@@ -540,15 +635,20 @@ export class WorldRevisionSession {
|
|
|
540
635
|
else
|
|
541
636
|
finalizeUnconfirmed(session, pending.assistantItemIndex, new Error("Runtime restarted before the complete tool response was prepared"));
|
|
542
637
|
}
|
|
543
|
-
if (session.runStatus !== "running" &&
|
|
638
|
+
if (session.runStatus !== "running" && !recoveringSettlement)
|
|
544
639
|
return session;
|
|
545
640
|
const requestId = session.activeRequestId;
|
|
546
|
-
session.runStatus =
|
|
641
|
+
session.runStatus =
|
|
642
|
+
session.modelBinding.provider === "google_interactions" &&
|
|
643
|
+
session.googleOutcomeUnknown === false
|
|
644
|
+
? "paused"
|
|
645
|
+
: "interrupted";
|
|
547
646
|
session.activeRequestId = null;
|
|
548
647
|
if (requestId !== null)
|
|
549
648
|
session.completedRequestIds = rememberAuthoringRequest(session.completedRequestIds, requestId);
|
|
550
|
-
session.lastFailure =
|
|
551
|
-
"The
|
|
649
|
+
session.lastFailure = session.googleOutcomeUnknown
|
|
650
|
+
? "The Google request outcome is unknown; start a fresh model context."
|
|
651
|
+
: "The previous model request stopped after its last settled worktree change. Continue this conversation when ready.";
|
|
552
652
|
session.updatedAt = Date.now();
|
|
553
653
|
await this.#store.saveSession(session);
|
|
554
654
|
return session;
|
|
@@ -624,10 +724,18 @@ export class WorldRevisionSession {
|
|
|
624
724
|
}
|
|
625
725
|
}
|
|
626
726
|
function retargetSession(session, epoch, locale) {
|
|
727
|
+
session.modelItems.push(epochBoundaryItem(epoch, locale));
|
|
728
|
+
session.epochId = epoch.epochId;
|
|
729
|
+
session.authorization = null;
|
|
730
|
+
session.pendingSettlement = null;
|
|
731
|
+
session.lastFailure = null;
|
|
732
|
+
session.updatedAt = Date.now();
|
|
733
|
+
}
|
|
734
|
+
function epochBoundaryItem(epoch, locale) {
|
|
627
735
|
const markdown = locale === "zh-CN"
|
|
628
736
|
? `# 新的世界修订 epoch\n\n上一轮修订已经应用或放弃。当前工作树是 ${epoch.epochId},从世界端点 ${epoch.baseHead} 重新建立。此前全部 search cursor 和 read 写授权已失效;修改前必须重新读取。`
|
|
629
737
|
: `# New world-revision epoch\n\nThe previous revision was applied or discarded. Worktree ${epoch.epochId} was rebuilt from world endpoint ${epoch.baseHead}. Every prior search cursor and read authorization is invalid; re-read before writing.`;
|
|
630
|
-
|
|
738
|
+
return {
|
|
631
739
|
kind: "prompt_delta",
|
|
632
740
|
logicalMessages: [
|
|
633
741
|
{
|
|
@@ -641,12 +749,7 @@ function retargetSession(session, epoch, locale) {
|
|
|
641
749
|
],
|
|
642
750
|
},
|
|
643
751
|
],
|
|
644
|
-
}
|
|
645
|
-
session.epochId = epoch.epochId;
|
|
646
|
-
session.authorization = null;
|
|
647
|
-
session.pendingSettlement = null;
|
|
648
|
-
session.lastFailure = null;
|
|
649
|
-
session.updatedAt = Date.now();
|
|
752
|
+
};
|
|
650
753
|
}
|
|
651
754
|
function finalizeSettlement(session, pending) {
|
|
652
755
|
for (const result of pending.toolResults)
|
|
@@ -745,6 +848,11 @@ function historyItem(session) {
|
|
|
745
848
|
const changed = new Set(session.modelItems.flatMap((item) => item.kind === "tool" ? item.changes.map(({ path }) => path) : []));
|
|
746
849
|
return {
|
|
747
850
|
sessionId: session.sessionId,
|
|
851
|
+
...(session.damagedContinuation
|
|
852
|
+
? {
|
|
853
|
+
unavailableReason: "Google continuation is damaged; start a fresh model context.",
|
|
854
|
+
}
|
|
855
|
+
: {}),
|
|
748
856
|
epochId: session.epochId,
|
|
749
857
|
runStatus: session.runStatus,
|
|
750
858
|
createdAt: session.createdAt,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { stringify } from "yaml";
|
|
2
|
+
/** App-owned defaults contain declarations, never copies of the prompt catalog. */
|
|
3
|
+
export function applicationPresetFilesForLocale(locale) {
|
|
4
|
+
return {
|
|
5
|
+
"preset.yaml": stringify({
|
|
6
|
+
format: "narraeon.play-preset/v3",
|
|
7
|
+
name: locale === "zh-CN" ? "系统推荐" : "System recommended",
|
|
8
|
+
callChain: "call-chain.yaml",
|
|
9
|
+
playPrompts: {},
|
|
10
|
+
authorPrompts: {},
|
|
11
|
+
mergeConsecutiveMessages: false,
|
|
12
|
+
mounts: [],
|
|
13
|
+
extensions: [],
|
|
14
|
+
}),
|
|
15
|
+
"call-chain.yaml": "format: narraeon.play-call-chain/v1\nnarrative: []\nfollowups: []\n",
|
|
16
|
+
"frame.yaml": `format: narraeon.host-frame/v1
|
|
17
|
+
roles:
|
|
18
|
+
runtime_system:
|
|
19
|
+
- builtin: runtime.play-contract
|
|
20
|
+
- builtin: runtime.tool-contract
|
|
21
|
+
- builtin: runtime.operation-contract
|
|
22
|
+
author_instruction:
|
|
23
|
+
- include: world.instructions
|
|
24
|
+
world_context:
|
|
25
|
+
- include: world.context
|
|
26
|
+
- builtin: runtime.coverage
|
|
27
|
+
`,
|
|
28
|
+
};
|
|
29
|
+
}
|