narraeon 0.7.4 → 0.7.5
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 +1 -1
- package/dist/node/runtime/content/CurrentTreeContentLibrary.js +64 -163
- package/dist/node/runtime/content/FileNativeContentTree.js +16 -13
- package/dist/node/runtime/play/FileNativePlayAdvanceStore.js +34 -18
- package/dist/node/runtime/play/PlayAdvanceLifecycle.js +211 -0
- package/dist/node/runtime/play/PlayCallChain.js +200 -374
- package/dist/node/runtime/setting/SettingAuthoringTransaction.js +312 -167
- package/dist/node/runtime/world/FileNativeWorldStore.js +151 -109
- package/dist/node/runtime/world/WorldDocumentMaintenance.js +2 -1
- package/dist/node/runtime/world/WorldDocumentStore.js +1 -1
- package/dist/node/runtime/world/WorldLedgerAuthority.js +37 -3
- package/dist/node/runtime/world/WorldTurnLedger.js +17 -4
- package/dist/node/runtime/world-revision/FileNativeWorldRevisionStore.js +2 -4
- package/dist/node/runtime/world-revision/WorldRevisionAuthoringTransaction.js +19 -241
- package/dist/node/runtime/world-revision/WorldRevisionWorkspace.js +8 -27
- package/dist/node/server/createServer.js +1 -4
- package/package.json +1 -1
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { ModelHostBindingMismatchError, ModelHostCancelledError, ModelHostContinuationError, ModelHostFailureError, ModelHostOutcomeUnknownError, modelHostFailureRequiresFreshContext, } from "../model/ModelHost.js";
|
|
2
|
+
import { classifyPlayFailure } from "./PlayFailure.js";
|
|
3
|
+
const messages = {
|
|
4
|
+
playerCommitted: "The player message is committed; the frozen model request is ready to continue.",
|
|
5
|
+
toolStepSettled: "The completed tool response is settled; its frozen continuation request is ready.",
|
|
6
|
+
cancelledBeforeDispatch: "The player cancelled before the next Provider dispatch; the frozen request can be resumed without replaying a completed model result.",
|
|
7
|
+
followupsCancelled: "The player cancelled the remaining follow-up generation after the narrative was committed.",
|
|
8
|
+
noContinuation: "The complete model response has no provider-native continuation payload; use a fresh context.",
|
|
9
|
+
noOutput: "The model returned neither text nor a complete tool call.",
|
|
10
|
+
serviceStoppedAfterHead: "The service stopped after the world was updated; use a fresh context.",
|
|
11
|
+
serviceStoppedSavingPlayer: "The service stopped while saving the player message; use a fresh context.",
|
|
12
|
+
serviceStoppedRetryable: "The service stopped while the model request was in progress; retry to call the model again.",
|
|
13
|
+
serviceStoppedNoRequest: "The service stopped before preparing a model request; use a fresh context.",
|
|
14
|
+
dispatchInterrupted: "The model request was interrupted.",
|
|
15
|
+
};
|
|
16
|
+
export class PlayAdvanceLifecycle {
|
|
17
|
+
#ports;
|
|
18
|
+
constructor(ports) {
|
|
19
|
+
this.#ports = ports;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Marks a Provider dispatch as in progress. `running` is not an ending: it is
|
|
23
|
+
* the state that every dispatch replaces with an ending before it returns.
|
|
24
|
+
*/
|
|
25
|
+
beginDispatch(session, request, attempt) {
|
|
26
|
+
session.status = "running";
|
|
27
|
+
session.canRetry = false;
|
|
28
|
+
session.lastFailure = null;
|
|
29
|
+
delete session.lastFailureKind;
|
|
30
|
+
session.lastRequest = structuredClone(request);
|
|
31
|
+
session.lastRequestAttempt = attempt;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Applies an ending, persists the session, then clears the advance marker
|
|
35
|
+
* and notifies. Every ending of an advance goes through here, so no path
|
|
36
|
+
* can persist without clearing the marker it settles.
|
|
37
|
+
*/
|
|
38
|
+
async end(session, ending, advance, notify) {
|
|
39
|
+
applyEnding(session, ending);
|
|
40
|
+
await this.#ports.persist(session);
|
|
41
|
+
if (advance !== null)
|
|
42
|
+
await this.#ports.clearMarker(advance);
|
|
43
|
+
notify?.();
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Ends an advance that a process exit cut off (ADR-0044). Nothing is
|
|
47
|
+
* replayed: the exchange is interrupted, and only the chain's own frozen
|
|
48
|
+
* request can be retried, and only while the world has not moved on.
|
|
49
|
+
*/
|
|
50
|
+
async recoverAbandoned(session, advance, headMoved) {
|
|
51
|
+
const playerPhase = advance?.advanceKind === "player";
|
|
52
|
+
const streaming = playerPhase
|
|
53
|
+
? undefined
|
|
54
|
+
: session.events.findLast((event) => event.kind === "assistant" &&
|
|
55
|
+
event.status === "streaming" &&
|
|
56
|
+
(advance === null || event.id === advance.eventId));
|
|
57
|
+
const canRetry = !headMoved && !playerPhase && session.lastRequest !== null;
|
|
58
|
+
const message = headMoved
|
|
59
|
+
? messages.serviceStoppedAfterHead
|
|
60
|
+
: playerPhase
|
|
61
|
+
? messages.serviceStoppedSavingPlayer
|
|
62
|
+
: canRetry
|
|
63
|
+
? messages.serviceStoppedRetryable
|
|
64
|
+
: messages.serviceStoppedNoRequest;
|
|
65
|
+
await this.end(session, {
|
|
66
|
+
kind: "failed",
|
|
67
|
+
message,
|
|
68
|
+
failureKind: canRetry ? "retryable" : "other",
|
|
69
|
+
canRetry,
|
|
70
|
+
streamingEventId: streaming?.id,
|
|
71
|
+
}, advance);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The ending of an exchange that raised an error before its response was
|
|
76
|
+
* settled. Cancellation after dispatch is named explicitly here and is never
|
|
77
|
+
* a retry, even though `ModelHostCancelledError` is also an outcome-unknown
|
|
78
|
+
* error (ADR-0030).
|
|
79
|
+
*/
|
|
80
|
+
export function dispatchFailureEnding(error, streamingEventId) {
|
|
81
|
+
const message = errorMessage(error, messages.dispatchInterrupted);
|
|
82
|
+
if (error instanceof ModelHostCancelledError)
|
|
83
|
+
return { kind: "cancelled_after_dispatch", message, streamingEventId };
|
|
84
|
+
const requiresFreshContext = error instanceof ModelHostBindingMismatchError ||
|
|
85
|
+
error instanceof ModelHostContinuationError ||
|
|
86
|
+
error instanceof ModelHostOutcomeUnknownError ||
|
|
87
|
+
modelHostFailureRequiresFreshContext(error);
|
|
88
|
+
const canRetry = !requiresFreshContext && error instanceof ModelHostFailureError;
|
|
89
|
+
return {
|
|
90
|
+
kind: "failed",
|
|
91
|
+
message,
|
|
92
|
+
failureKind: classifyPlayFailure(error, canRetry),
|
|
93
|
+
canRetry,
|
|
94
|
+
streamingEventId,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The ending of an error raised after a response was read or a player message
|
|
99
|
+
* was written. These failures never grant a retry: Authority may already have
|
|
100
|
+
* moved past the frozen request.
|
|
101
|
+
*/
|
|
102
|
+
export function settlementFailureEnding(error, fallbackMessage) {
|
|
103
|
+
return {
|
|
104
|
+
kind: "failed",
|
|
105
|
+
message: errorMessage(error, fallbackMessage),
|
|
106
|
+
failureKind: classifyPlayFailure(error, false),
|
|
107
|
+
canRetry: false,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/** A settled response with no provider-native continuation payload (ADR-0035). */
|
|
111
|
+
export function noContinuationEnding() {
|
|
112
|
+
return {
|
|
113
|
+
kind: "failed",
|
|
114
|
+
message: messages.noContinuation,
|
|
115
|
+
failureKind: "context_unavailable",
|
|
116
|
+
canRetry: false,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/** A settled response with neither text nor a complete tool call. */
|
|
120
|
+
export function emptyResponseEnding() {
|
|
121
|
+
return {
|
|
122
|
+
kind: "failed",
|
|
123
|
+
message: messages.noOutput,
|
|
124
|
+
failureKind: "other",
|
|
125
|
+
canRetry: false,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Whether a chain's last ending was the player's cancellation. Every
|
|
130
|
+
* cancellation ending writes its event last, so the tail identifies it; callers
|
|
131
|
+
* read the projected flag instead of the event list.
|
|
132
|
+
*/
|
|
133
|
+
export function lastEndingCancelled(session) {
|
|
134
|
+
return session.events.at(-1)?.kind === "cancellation";
|
|
135
|
+
}
|
|
136
|
+
function applyEnding(session, ending) {
|
|
137
|
+
switch (ending.kind) {
|
|
138
|
+
case "ready":
|
|
139
|
+
if (ending.followupsCancelled === true)
|
|
140
|
+
session.events.push({
|
|
141
|
+
id: session.nextEventId++,
|
|
142
|
+
kind: "cancellation",
|
|
143
|
+
message: messages.followupsCancelled,
|
|
144
|
+
});
|
|
145
|
+
session.status = "ready";
|
|
146
|
+
session.canRetry = false;
|
|
147
|
+
session.lastFailure = null;
|
|
148
|
+
delete session.lastFailureKind;
|
|
149
|
+
return;
|
|
150
|
+
case "resumable":
|
|
151
|
+
session.lastRequest = structuredClone(ending.request);
|
|
152
|
+
session.lastRequestAttempt = 0;
|
|
153
|
+
session.status = "interrupted";
|
|
154
|
+
session.canRetry = true;
|
|
155
|
+
session.lastFailure =
|
|
156
|
+
ending.reason === "player_committed"
|
|
157
|
+
? messages.playerCommitted
|
|
158
|
+
: messages.toolStepSettled;
|
|
159
|
+
session.lastFailureKind = "retryable";
|
|
160
|
+
return;
|
|
161
|
+
case "cancelled_before_dispatch":
|
|
162
|
+
interruptStreamingEvent(session, ending.streamingEventId);
|
|
163
|
+
session.events.push({
|
|
164
|
+
id: session.nextEventId++,
|
|
165
|
+
kind: "cancellation",
|
|
166
|
+
message: messages.cancelledBeforeDispatch,
|
|
167
|
+
});
|
|
168
|
+
session.status = "interrupted";
|
|
169
|
+
session.canRetry = true;
|
|
170
|
+
session.lastRequest = structuredClone(ending.request);
|
|
171
|
+
session.lastRequestAttempt = Math.max(0, ending.attempt - 1);
|
|
172
|
+
session.lastFailure = messages.cancelledBeforeDispatch;
|
|
173
|
+
delete session.lastFailureKind;
|
|
174
|
+
return;
|
|
175
|
+
case "cancelled_after_dispatch":
|
|
176
|
+
interruptStreamingEvent(session, ending.streamingEventId);
|
|
177
|
+
session.status = "interrupted";
|
|
178
|
+
session.canRetry = false;
|
|
179
|
+
session.lastFailure = ending.message;
|
|
180
|
+
delete session.lastFailureKind;
|
|
181
|
+
session.events.push({
|
|
182
|
+
id: session.nextEventId++,
|
|
183
|
+
kind: "cancellation",
|
|
184
|
+
message: ending.message,
|
|
185
|
+
});
|
|
186
|
+
return;
|
|
187
|
+
case "failed":
|
|
188
|
+
interruptStreamingEvent(session, ending.streamingEventId);
|
|
189
|
+
session.status = "interrupted";
|
|
190
|
+
session.canRetry = ending.canRetry;
|
|
191
|
+
session.lastFailure = ending.message;
|
|
192
|
+
session.lastFailureKind = ending.failureKind;
|
|
193
|
+
session.events.push({
|
|
194
|
+
id: session.nextEventId++,
|
|
195
|
+
kind: "failure",
|
|
196
|
+
message: ending.message,
|
|
197
|
+
failureKind: ending.failureKind,
|
|
198
|
+
});
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function interruptStreamingEvent(session, eventId) {
|
|
203
|
+
if (eventId === undefined)
|
|
204
|
+
return;
|
|
205
|
+
const event = session.events.find(({ id }) => id === eventId);
|
|
206
|
+
if (event?.kind === "assistant")
|
|
207
|
+
event.status = "interrupted";
|
|
208
|
+
}
|
|
209
|
+
function errorMessage(error, fallback) {
|
|
210
|
+
return error instanceof Error ? error.message : fallback;
|
|
211
|
+
}
|