narraeon 0.7.4 → 0.7.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.
Files changed (30) hide show
  1. package/dist/node/protocol/v1.js +4 -0
  2. package/dist/node/runtime/V1Runtime.js +13 -1
  3. package/dist/node/runtime/content/CurrentTreeContentLibrary.js +64 -163
  4. package/dist/node/runtime/content/FileNativeContentTree.js +16 -13
  5. package/dist/node/runtime/model/ToolArgumentCoercion.js +138 -0
  6. package/dist/node/runtime/play/FileNativePlayAdvanceStore.js +34 -18
  7. package/dist/node/runtime/play/FileNativePlayTimelineStore.js +6 -2
  8. package/dist/node/runtime/play/OrderedPlayPrompts.js +3 -6
  9. package/dist/node/runtime/play/PlayAdvanceLifecycle.js +211 -0
  10. package/dist/node/runtime/play/PlayCallChain.js +276 -398
  11. package/dist/node/runtime/play/PlayDocumentTools.js +19 -0
  12. package/dist/node/runtime/prompt/FileNativePromptCompiler.js +3 -3
  13. package/dist/node/runtime/prompt/FileNativeToolRegistry.js +15 -0
  14. package/dist/node/runtime/prompt/ToolNameAlias.js +14 -0
  15. package/dist/node/runtime/setting/SettingAuthoringTransaction.js +370 -169
  16. package/dist/node/runtime/world/FileNativeWorldStore.js +151 -109
  17. package/dist/node/runtime/world/WorldDocumentMaintenance.js +2 -1
  18. package/dist/node/runtime/world/WorldDocumentStore.js +1 -1
  19. package/dist/node/runtime/world/WorldLedgerAuthority.js +37 -3
  20. package/dist/node/runtime/world/WorldTurnLedger.js +17 -4
  21. package/dist/node/runtime/world-revision/FileNativeWorldRevisionStore.js +2 -4
  22. package/dist/node/runtime/world-revision/WorldRevisionAuthoringTransaction.js +19 -241
  23. package/dist/node/runtime/world-revision/WorldRevisionWorkspace.js +8 -27
  24. package/dist/node/server/createServer.js +1 -4
  25. package/dist/node/shared/ordered-play-prompts.js +5 -0
  26. package/dist/node/shared/turn-guide.js +27 -2
  27. package/dist/web/assets/index-Cj2JYCia.js +1408 -0
  28. package/dist/web/index.html +1 -1
  29. package/package.json +1 -1
  30. package/dist/web/assets/index-9N9kO8kY.js +0 -1408
@@ -948,7 +948,8 @@ function assertPlayEvent(value) {
948
948
  if (value.kind === "tool_call") {
949
949
  if (typeof value.callId !== "string" ||
950
950
  typeof value.name !== "string" ||
951
- typeof value.replayed !== "boolean")
951
+ typeof value.replayed !== "boolean" ||
952
+ !validOptionalString(value.executedName))
952
953
  throw new Error("Tool-call timeline event has an invalid shape");
953
954
  return;
954
955
  }
@@ -960,7 +961,8 @@ function assertPlayEvent(value) {
960
961
  typeof value.name !== "string" ||
961
962
  typeof value.ok !== "boolean" ||
962
963
  typeof value.markdown !== "string" ||
963
- typeof value.replayed !== "boolean")
964
+ typeof value.replayed !== "boolean" ||
965
+ !validOptionalString(value.executedName))
964
966
  throw new Error("Tool-result timeline event has an invalid shape");
965
967
  return;
966
968
  }
@@ -1014,6 +1016,7 @@ function assertTimelineEventSummary(value) {
1014
1016
  if (typeof value.callId !== "string" ||
1015
1017
  typeof value.name !== "string" ||
1016
1018
  typeof value.replayed !== "boolean" ||
1019
+ !validOptionalString(value.executedName) ||
1017
1020
  value.detailsAvailable !== true)
1018
1021
  throw new Error("Tool-call timeline summary has an invalid shape");
1019
1022
  return;
@@ -1026,6 +1029,7 @@ function assertTimelineEventSummary(value) {
1026
1029
  typeof value.name !== "string" ||
1027
1030
  typeof value.ok !== "boolean" ||
1028
1031
  typeof value.replayed !== "boolean" ||
1032
+ !validOptionalString(value.executedName) ||
1029
1033
  value.detailsAvailable !== true)
1030
1034
  throw new Error("Tool-result timeline summary has an invalid shape");
1031
1035
  return;
@@ -1,8 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { isMap, isSeq, parseDocument } from "yaml";
3
- import { builtinPlayPrompts, isPromptMessageRole, promptGroups, } from "../../shared/ordered-play-prompts.js";
4
- /** One turn-guide line: no line breaks, bounded length; empty clears a builtin default. */
5
- const turnGuideLine = /^[^\r\n\0]{0,1000}$/u;
3
+ import { builtinPlayPrompts, isPromptMessageRole, isTurnGuideLine, promptGroups, } from "../../shared/ordered-play-prompts.js";
6
4
  const promptName = /^[^\r\n\0]{1,160}$/u;
7
5
  /** The same strict permission gate is used by portable imports and structured saves. */
8
6
  export function parseOrderedPlayPrompts(value, catalog = builtinPlayPrompts("en"), worldRequired = true) {
@@ -33,8 +31,7 @@ export function parseOrderedPlayPrompts(value, catalog = builtinPlayPrompts("en"
33
31
  key === "id" ||
34
32
  key === "kind") && keys.every((key) => Object.hasOwn(item, key));
35
33
  const validTurnGuide = item.turnGuide === undefined ||
36
- (typeof item.turnGuide === "string" &&
37
- turnGuideLine.test(item.turnGuide));
34
+ (typeof item.turnGuide === "string" && isTurnGuideLine(item.turnGuide));
38
35
  const turnGuide = typeof item.turnGuide === "string" ? { turnGuide: item.turnGuide } : {};
39
36
  if (item.kind === "world" && exact(["id", "kind"])) {
40
37
  worlds++;
@@ -47,7 +44,7 @@ export function parseOrderedPlayPrompts(value, catalog = builtinPlayPrompts("en"
47
44
  item.name.trim().length > 0 &&
48
45
  promptName.test(item.name) &&
49
46
  typeof item.body === "string" &&
50
- turnGuideLine.test(item.body) &&
47
+ isTurnGuideLine(item.body) &&
51
48
  typeof item.enabled === "boolean")
52
49
  return [
53
50
  {
@@ -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
+ }