@momoyu-ink/kit 0.5.4 → 0.6.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/lib.mjs CHANGED
@@ -33,23 +33,33 @@ __export(lib_exports, {
33
33
  createStaticNavigation: () => createStaticNavigation,
34
34
  executeNodeCommand: () => executeNodeCommand,
35
35
  executePluginCommand: () => executePluginCommand,
36
+ fastForwardState: () => fastForwardState,
37
+ getAppStateAdapter: () => getAppStateAdapter,
36
38
  getNavigator: () => getNavigator,
39
+ getSeekingType: () => getSeekingType,
37
40
  getStageSize: () => getStageSize,
38
41
  globalEventListeners: () => globalEventListeners,
39
42
  globalRequestAnimationFrameListeners: () => globalRequestAnimationFrameListeners,
40
43
  moyu: () => moyu_exports,
41
44
  nextLine: () => nextLine,
45
+ registerAppStateAdapter: () => registerAppStateAdapter,
42
46
  setDefaultAutoTailMs: () => setDefaultAutoTailMs,
43
47
  setWaiting: () => setWaiting,
44
48
  skipState: () => skipState,
49
+ startDebugSession: () => startDebugSession,
50
+ startRuntimeDebugSession: () => startRuntimeDebugSession,
51
+ stopDebugSession: () => stopDebugSession,
52
+ stopRuntimeDebugSession: () => stopRuntimeDebugSession,
45
53
  useAutoBlocker: () => useAutoBlocker,
46
54
  useAutoTicket: () => useAutoTicket,
47
55
  useBeforeHandleCommandCallback: () => useBeforeHandleCommandCallback,
56
+ useDebugSession: () => useDebugSession,
48
57
  useFadeIn: () => useFadeIn,
49
58
  useFadeInOut: () => useFadeInOut,
50
59
  useFadeOut: () => useFadeOut,
51
60
  useInterruptCallback: () => useInterruptCallback,
52
61
  useIsAutoing: () => useIsAutoing,
62
+ useIsSeeking: () => useIsSeeking,
53
63
  useIsSkipping: () => useIsSkipping,
54
64
  useNavigation: () => useNavigation,
55
65
  useNavigationParams: () => useNavigationParams,
@@ -58,7 +68,8 @@ __export(lib_exports, {
58
68
  useSkipBlocker: () => useSkipBlocker,
59
69
  useSkipCallback: () => useSkipCallback,
60
70
  useSoundEffect: () => useSoundEffect,
61
- useStageContext: () => useStageContext
71
+ useStageContext: () => useStageContext,
72
+ warpState: () => warpState
62
73
  });
63
74
 
64
75
  // src/react.ts
@@ -199,7 +210,8 @@ var hostConfig = {
199
210
  * @required
200
211
  */
201
212
  createInstance(type, props, _root, _hostContext, _internalInstanceHandle) {
202
- const node = Node.create(props.label ?? "", type, props);
213
+ const { ref, ...restProps } = props;
214
+ const node = Node.create(props.label ?? "", type, restProps);
203
215
  return node;
204
216
  },
205
217
  /**
@@ -269,7 +281,7 @@ var hostConfig = {
269
281
  },
270
282
  commitUpdate(instance, _type, prevProps, nextProps, _internalHandle) {
271
283
  const { label: _oldLabel, children: _oldChildren, ...oldProps } = prevProps;
272
- const { label, children, ...newProps } = nextProps;
284
+ const { label, children, ref, ...newProps } = nextProps;
273
285
  const changedProps = {};
274
286
  for (const key in newProps) {
275
287
  if (newProps[key] !== oldProps[key]) {
@@ -436,7 +448,7 @@ var globalVariables = new Proxy(
436
448
  }
437
449
  );
438
450
  function safeEval(code, sandbox) {
439
- const proxy3 = new Proxy(sandbox, {
451
+ const proxy4 = new Proxy(sandbox, {
440
452
  has(target, key) {
441
453
  if (typeof key === "string" && JS_GLOBAL_KEYS.has(key)) {
442
454
  return false;
@@ -444,7 +456,7 @@ function safeEval(code, sandbox) {
444
456
  return key in target || key === "ARCHIVE" || key === "GLOBAL" || key === "window" || key === "globalThis";
445
457
  },
446
458
  get(target, key) {
447
- if (key === "window" || key === "globalThis") return proxy3;
459
+ if (key === "window" || key === "globalThis") return proxy4;
448
460
  if (key === "ARCHIVE") return archiveVariables;
449
461
  if (key === "GLOBAL") return globalVariables;
450
462
  if (key === Symbol.unscopables) return void 0;
@@ -455,7 +467,7 @@ function safeEval(code, sandbox) {
455
467
  return true;
456
468
  }
457
469
  });
458
- return new Function("sandbox", `with(sandbox) { return (${code}) }`).bind(proxy3)(proxy3);
470
+ return new Function("sandbox", `with(sandbox) { return (${code}) }`).bind(proxy4)(proxy4);
459
471
  }
460
472
  globalThis.__moyu_eval_sandbox = (code) => {
461
473
  try {
@@ -638,9 +650,9 @@ function getStageSize() {
638
650
  }
639
651
 
640
652
  // src/components/navigation.tsx
641
- import React2 from "react";
642
653
  import { createContext, useContext } from "react";
643
654
  import { proxy, useSnapshot } from "valtio";
655
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
644
656
  var PageParamsContext = createContext(void 0);
645
657
  var OverlayParamsContext = createContext(void 0);
646
658
  var overlayIdCounter = 0;
@@ -696,6 +708,9 @@ function createStackNavigator(options) {
696
708
  delete navigationState.pageParams[page];
697
709
  }
698
710
  },
711
+ hasPage: (page) => {
712
+ return normalizedPages[page] !== void 0;
713
+ },
699
714
  goBack: () => {
700
715
  console.warn("goBack() not implemented yet");
701
716
  },
@@ -751,11 +766,14 @@ function createStaticNavigation(navigator) {
751
766
  const { pages, overlays } = navigator._descriptors;
752
767
  const pageDescriptor = pages[navState.currentPage];
753
768
  const Page = pageDescriptor?.component || (() => null);
754
- return /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(PageParamsContext.Provider, { value: navState.pageParams[navState.currentPage] }, /* @__PURE__ */ React2.createElement(Page, null)), navState.overlayStack.map((overlay) => {
755
- const overlayDescriptor = overlays[overlay.type];
756
- const OverlayComponent = overlayDescriptor?.component || (() => null);
757
- return /* @__PURE__ */ React2.createElement(OverlayParamsContext.Provider, { key: overlay.id, value: navState.overlayParams[overlay.id] }, /* @__PURE__ */ React2.createElement(OverlayComponent, null));
758
- }));
769
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
770
+ /* @__PURE__ */ jsx(PageParamsContext.Provider, { value: navState.pageParams[navState.currentPage], children: /* @__PURE__ */ jsx(Page, {}) }),
771
+ navState.overlayStack.map((overlay) => {
772
+ const overlayDescriptor = overlays[overlay.type];
773
+ const OverlayComponent = overlayDescriptor?.component || (() => null);
774
+ return /* @__PURE__ */ jsx(OverlayParamsContext.Provider, { value: navState.overlayParams[overlay.id], children: /* @__PURE__ */ jsx(OverlayComponent, {}) }, overlay.id);
775
+ })
776
+ ] });
759
777
  };
760
778
  }
761
779
  function getNavigator() {
@@ -774,6 +792,624 @@ function useNavigationParams() {
774
792
  return overlayParams ?? pageParams ?? {};
775
793
  }
776
794
 
795
+ // src/debug/session.ts
796
+ import { useSnapshot as useSnapshot2, proxy as proxy2 } from "valtio";
797
+ var debugState = proxy2({
798
+ active: false,
799
+ checkpoints: {},
800
+ currentCursor: null,
801
+ restoring: false,
802
+ lastError: null
803
+ });
804
+ var appStateAdapter = null;
805
+ var currentConfig = {
806
+ onMarkerEnter: () => {
807
+ },
808
+ onError: () => {
809
+ }
810
+ };
811
+ var disposeMarkerListener = null;
812
+ var disposeFinishedListener = null;
813
+ var disposeWarpFinishedListener = null;
814
+ var debugOperationQueue = Promise.resolve();
815
+ var activeFastForward = null;
816
+ var activeWarp = null;
817
+ function enqueueDebugOperation(task) {
818
+ const nextTask = debugOperationQueue.then(task, task);
819
+ debugOperationQueue = nextTask.then(
820
+ () => void 0,
821
+ () => void 0
822
+ );
823
+ return nextTask;
824
+ }
825
+ function executeScenarioCommand(payload) {
826
+ return Promise.resolve(executePluginCommand("scenario", payload));
827
+ }
828
+ function toErrorMessage(error) {
829
+ if (error instanceof Error) {
830
+ return error.message;
831
+ }
832
+ return String(error);
833
+ }
834
+ function isPromiseLike(value) {
835
+ return !!value && typeof value.then === "function";
836
+ }
837
+ function reportDebugSessionError(error) {
838
+ debugState.lastError = toErrorMessage(error);
839
+ void Promise.resolve(currentConfig.onError(error));
840
+ }
841
+ function clearLocalRuntimeState() {
842
+ debugState.checkpoints = {};
843
+ debugState.currentCursor = null;
844
+ debugState.restoring = false;
845
+ debugState.lastError = null;
846
+ }
847
+ async function doSettleActiveSeek(op, clearSlot, error) {
848
+ if (!op) {
849
+ return;
850
+ }
851
+ clearSlot();
852
+ try {
853
+ await appStateAdapter?.exitFastForwardMode?.();
854
+ } catch (exitError) {
855
+ if (error === void 0) {
856
+ error = exitError;
857
+ }
858
+ }
859
+ if (error === void 0) {
860
+ op.resolve();
861
+ return;
862
+ }
863
+ op.reject(error);
864
+ }
865
+ async function settleActiveFastForward(error) {
866
+ await doSettleActiveSeek(activeFastForward, () => {
867
+ activeFastForward = null;
868
+ }, error);
869
+ }
870
+ async function settleActiveWarp(error) {
871
+ await doSettleActiveSeek(activeWarp, () => {
872
+ activeWarp = null;
873
+ }, error);
874
+ }
875
+ async function clearRemoteCheckpoints() {
876
+ await executeScenarioCommand({ subCommand: "clearCheckpoints" });
877
+ }
878
+ async function syncLocalWarpCheckpoint(targetMarkerId) {
879
+ const cursor = await executeScenarioCommand({
880
+ subCommand: "getExecutionCursor"
881
+ });
882
+ const checkpointCursor = cursor ? {
883
+ story: cursor.story,
884
+ paragraph: cursor.paragraph,
885
+ markerId: targetMarkerId
886
+ } : debugState.currentCursor ? {
887
+ story: debugState.currentCursor.story,
888
+ paragraph: debugState.currentCursor.paragraph,
889
+ markerId: targetMarkerId
890
+ } : null;
891
+ if (!checkpointCursor) {
892
+ return;
893
+ }
894
+ const checkpoint = {
895
+ cursor: checkpointCursor,
896
+ appState: appStateAdapter ? await appStateAdapter.capture() : void 0
897
+ };
898
+ debugState.currentCursor = checkpointCursor;
899
+ debugState.checkpoints[targetMarkerId] = checkpoint;
900
+ await currentConfig.onMarkerEnter(checkpoint);
901
+ }
902
+ function captureCheckpointAppStateSync() {
903
+ if (!appStateAdapter) {
904
+ return void 0;
905
+ }
906
+ const appState = appStateAdapter.capture();
907
+ if (isPromiseLike(appState)) {
908
+ throw new Error("Async app state capture is not supported for debug checkpoints");
909
+ }
910
+ return appState;
911
+ }
912
+ function notifyMarkerEnter(checkpoint) {
913
+ try {
914
+ const result = currentConfig.onMarkerEnter(checkpoint);
915
+ if (isPromiseLike(result)) {
916
+ void result.catch(reportDebugSessionError);
917
+ }
918
+ } catch (error) {
919
+ reportDebugSessionError(error);
920
+ }
921
+ }
922
+ async function prepareJumpStart(markerId) {
923
+ const checkpoint = debugState.checkpoints[markerId];
924
+ if (checkpoint) {
925
+ const restored2 = await executeScenarioCommand({
926
+ subCommand: "restoreCheckpoint",
927
+ key: markerId
928
+ });
929
+ if (!restored2) {
930
+ throw new Error(`Failed to restore checkpoint ${markerId}`);
931
+ }
932
+ if (appStateAdapter) {
933
+ await appStateAdapter.restore(checkpoint.appState);
934
+ }
935
+ debugState.currentCursor = checkpoint.cursor;
936
+ return false;
937
+ }
938
+ const lookup = await executeScenarioCommand({
939
+ subCommand: "getFastForwardCheckpoint",
940
+ key: markerId
941
+ });
942
+ if (!lookup.markerExists) {
943
+ throw new Error(`Marker ${markerId} is not available in the current story`);
944
+ }
945
+ const startCheckpointKey = lookup.checkpointKey ?? null;
946
+ if (!startCheckpointKey) {
947
+ if (!appStateAdapter?.restartCurrentStoryFromHead) {
948
+ throw new Error("Restarting the current story from head is not supported by the app state adapter");
949
+ }
950
+ await appStateAdapter.restartCurrentStoryFromHead();
951
+ debugState.checkpoints = {};
952
+ debugState.currentCursor = null;
953
+ return true;
954
+ }
955
+ const startCheckpoint = debugState.checkpoints[startCheckpointKey];
956
+ if (!startCheckpoint) {
957
+ throw new Error(`Local checkpoint ${startCheckpointKey} not found`);
958
+ }
959
+ const restored = await executeScenarioCommand({
960
+ subCommand: "restoreCheckpoint",
961
+ key: startCheckpointKey
962
+ });
963
+ if (!restored) {
964
+ throw new Error(`Failed to restore checkpoint ${startCheckpointKey}`);
965
+ }
966
+ if (appStateAdapter) {
967
+ await appStateAdapter.restore(startCheckpoint.appState);
968
+ }
969
+ debugState.currentCursor = startCheckpoint.cursor;
970
+ return true;
971
+ }
972
+ function handleMarkerEnter(marker) {
973
+ const checkpoint = {
974
+ cursor: {
975
+ story: marker.story,
976
+ paragraph: marker.paragraph,
977
+ markerId: marker.markerId
978
+ },
979
+ appState: captureCheckpointAppStateSync()
980
+ };
981
+ debugState.currentCursor = checkpoint.cursor;
982
+ debugState.checkpoints[marker.markerId] = checkpoint;
983
+ notifyMarkerEnter(checkpoint);
984
+ if (activeFastForward?.targetMarkerId === marker.markerId) {
985
+ void settleActiveFastForward();
986
+ }
987
+ }
988
+ async function runFastForwardToMarker(markerId) {
989
+ if (!appStateAdapter?.enterFastForwardMode || !appStateAdapter?.exitFastForwardMode) {
990
+ throw new Error("Fast-forward mode is not supported by the app state adapter");
991
+ }
992
+ if (activeFastForward !== null || activeWarp !== null) {
993
+ throw new Error("A debug seek operation is already active");
994
+ }
995
+ await appStateAdapter.enterFastForwardMode({
996
+ onAbort: (error) => {
997
+ void settleActiveFastForward(error);
998
+ }
999
+ });
1000
+ const completion = new Promise((resolve, reject) => {
1001
+ activeFastForward = {
1002
+ targetMarkerId: markerId,
1003
+ resolve,
1004
+ reject
1005
+ };
1006
+ });
1007
+ try {
1008
+ await executeScenarioCommand({ subCommand: "nextLine" });
1009
+ } catch (error) {
1010
+ await settleActiveFastForward(error);
1011
+ }
1012
+ await completion;
1013
+ }
1014
+ async function runWarp(options) {
1015
+ if (!appStateAdapter?.enterFastForwardMode || !appStateAdapter?.exitFastForwardMode) {
1016
+ throw new Error("Warp mode is not supported by the app state adapter");
1017
+ }
1018
+ if (activeFastForward !== null || activeWarp !== null) {
1019
+ throw new Error("A debug seek operation is already active");
1020
+ }
1021
+ const boundary = options.boundary ?? "before";
1022
+ await appStateAdapter.enterFastForwardMode({ warp: true });
1023
+ const completion = new Promise((resolve, reject) => {
1024
+ activeWarp = {
1025
+ targetMarkerId: options.markerId,
1026
+ resolve,
1027
+ reject
1028
+ };
1029
+ });
1030
+ try {
1031
+ await executeScenarioCommand({
1032
+ subCommand: "warp",
1033
+ markerId: options.markerId,
1034
+ boundary
1035
+ });
1036
+ } catch (error) {
1037
+ await settleActiveWarp(error);
1038
+ }
1039
+ await completion;
1040
+ }
1041
+ var debugSessionController = {
1042
+ async restoreCheckpoint(markerId) {
1043
+ debugState.restoring = true;
1044
+ debugState.lastError = null;
1045
+ try {
1046
+ const needsFastForward = await enqueueDebugOperation(async () => prepareJumpStart(markerId));
1047
+ if (needsFastForward) {
1048
+ await runFastForwardToMarker(markerId);
1049
+ }
1050
+ return true;
1051
+ } catch (error) {
1052
+ debugState.lastError = toErrorMessage(error);
1053
+ throw error;
1054
+ } finally {
1055
+ debugState.restoring = false;
1056
+ }
1057
+ },
1058
+ async warp(options) {
1059
+ debugState.restoring = true;
1060
+ debugState.lastError = null;
1061
+ try {
1062
+ const needsWarp = await enqueueDebugOperation(async () => prepareJumpStart(options.markerId));
1063
+ if (needsWarp) {
1064
+ await runWarp(options);
1065
+ }
1066
+ } catch (error) {
1067
+ debugState.lastError = toErrorMessage(error);
1068
+ throw error;
1069
+ } finally {
1070
+ debugState.restoring = false;
1071
+ }
1072
+ },
1073
+ async switchPage(page, params) {
1074
+ return enqueueDebugOperation(async () => {
1075
+ debugState.lastError = null;
1076
+ try {
1077
+ if (!appStateAdapter?.switchPage) {
1078
+ throw new Error("Page switching is not supported by the app state adapter");
1079
+ }
1080
+ await appStateAdapter.switchPage(page, params);
1081
+ } catch (error) {
1082
+ debugState.lastError = toErrorMessage(error);
1083
+ throw error;
1084
+ }
1085
+ });
1086
+ }
1087
+ };
1088
+ function registerAppStateAdapter(adapter) {
1089
+ appStateAdapter = adapter;
1090
+ }
1091
+ function getAppStateAdapter() {
1092
+ return appStateAdapter;
1093
+ }
1094
+ async function startDebugSession(config = {}) {
1095
+ await stopDebugSession();
1096
+ currentConfig = {
1097
+ onMarkerEnter: config.onMarkerEnter ?? (() => {
1098
+ }),
1099
+ onError: config.onError ?? (() => {
1100
+ })
1101
+ };
1102
+ clearLocalRuntimeState();
1103
+ debugState.active = true;
1104
+ disposeMarkerListener = addEventListener("scenariomarkerenter", (event) => {
1105
+ const marker = event;
1106
+ try {
1107
+ handleMarkerEnter(marker);
1108
+ } catch (error) {
1109
+ reportDebugSessionError(error);
1110
+ }
1111
+ });
1112
+ disposeFinishedListener = addEventListener("scenariofinished", () => {
1113
+ debugState.currentCursor = null;
1114
+ if (activeFastForward) {
1115
+ void settleActiveFastForward(
1116
+ new Error(`Marker ${activeFastForward.targetMarkerId} was not reached before the scenario finished`)
1117
+ );
1118
+ }
1119
+ if (activeWarp) {
1120
+ const targetMarkerId = activeWarp.targetMarkerId;
1121
+ void settleActiveWarp(new Error(`Marker ${targetMarkerId} was not reached before the scenario finished`));
1122
+ }
1123
+ });
1124
+ disposeWarpFinishedListener = addEventListener("scenariowarpfinished", () => {
1125
+ const warp = activeWarp;
1126
+ if (!warp) {
1127
+ return;
1128
+ }
1129
+ void enqueueDebugOperation(async () => {
1130
+ try {
1131
+ await syncLocalWarpCheckpoint(warp.targetMarkerId);
1132
+ await settleActiveWarp();
1133
+ } catch (error) {
1134
+ debugState.lastError = toErrorMessage(error);
1135
+ await settleActiveWarp(error);
1136
+ await currentConfig.onError(error);
1137
+ }
1138
+ });
1139
+ });
1140
+ try {
1141
+ await clearRemoteCheckpoints();
1142
+ debugState.currentCursor = await executeScenarioCommand({
1143
+ subCommand: "getExecutionCursor"
1144
+ });
1145
+ } catch (error) {
1146
+ debugState.active = false;
1147
+ clearLocalRuntimeState();
1148
+ disposeMarkerListener?.();
1149
+ disposeFinishedListener?.();
1150
+ disposeWarpFinishedListener?.();
1151
+ disposeMarkerListener = null;
1152
+ disposeFinishedListener = null;
1153
+ disposeWarpFinishedListener = null;
1154
+ await currentConfig.onError(error);
1155
+ throw error;
1156
+ }
1157
+ return debugSessionController;
1158
+ }
1159
+ async function stopDebugSession() {
1160
+ disposeMarkerListener?.();
1161
+ disposeFinishedListener?.();
1162
+ disposeWarpFinishedListener?.();
1163
+ disposeMarkerListener = null;
1164
+ disposeFinishedListener = null;
1165
+ disposeWarpFinishedListener = null;
1166
+ await settleActiveFastForward(new Error("Debug session stopped"));
1167
+ await settleActiveWarp(new Error("Debug session stopped"));
1168
+ if (debugState.active) {
1169
+ await enqueueDebugOperation(async () => {
1170
+ try {
1171
+ await clearRemoteCheckpoints();
1172
+ } catch (error) {
1173
+ debugState.lastError = toErrorMessage(error);
1174
+ await currentConfig.onError(error);
1175
+ }
1176
+ });
1177
+ }
1178
+ debugState.active = false;
1179
+ clearLocalRuntimeState();
1180
+ }
1181
+ function clearDebugSessionRuntimeState() {
1182
+ clearLocalRuntimeState();
1183
+ void settleActiveFastForward(new Error("Debug session runtime state cleared"));
1184
+ void settleActiveWarp(new Error("Debug session runtime state cleared"));
1185
+ if (!debugState.active) {
1186
+ return;
1187
+ }
1188
+ void enqueueDebugOperation(async () => {
1189
+ try {
1190
+ await clearRemoteCheckpoints();
1191
+ } catch (error) {
1192
+ debugState.lastError = toErrorMessage(error);
1193
+ await currentConfig.onError(error);
1194
+ }
1195
+ });
1196
+ }
1197
+ function useDebugSession() {
1198
+ const snapshot = useSnapshot2(debugState);
1199
+ return {
1200
+ ...snapshot,
1201
+ startDebugSession,
1202
+ stopDebugSession,
1203
+ restoreCheckpoint: debugSessionController.restoreCheckpoint,
1204
+ warp: debugSessionController.warp,
1205
+ switchPage: debugSessionController.switchPage
1206
+ };
1207
+ }
1208
+
1209
+ // src/debug/runtime.ts
1210
+ var currentConnection = null;
1211
+ var currentController = null;
1212
+ var bufferedRequestMessages = [];
1213
+ function getRuntimeWebSocket() {
1214
+ return globalThis.WebSocket ?? null;
1215
+ }
1216
+ function isJumpRequestMessage(message) {
1217
+ return message.type === "jump:request" && typeof message.sessionId === "string" && typeof message.requestId === "number" && typeof message.markerId === "string" && (message.boundary === void 0 || message.boundary === "before" || message.boundary === "after") && (message.strategy === void 0 || message.strategy === "fast-forward" || message.strategy === "warp");
1218
+ }
1219
+ function isRouteRequestMessage(message) {
1220
+ return message.type === "route:request" && typeof message.sessionId === "string" && typeof message.requestId === "number" && typeof message.page === "string" && (message.params === void 0 || typeof message.params === "object" && message.params !== null && !Array.isArray(message.params));
1221
+ }
1222
+ function sendRuntimeDebugMessage(socket, message) {
1223
+ const RuntimeWebSocket = getRuntimeWebSocket();
1224
+ if (!RuntimeWebSocket || socket.readyState !== RuntimeWebSocket.OPEN) {
1225
+ return;
1226
+ }
1227
+ socket.send(JSON.stringify(message));
1228
+ }
1229
+ function clearBufferedRequestMessages() {
1230
+ bufferedRequestMessages = [];
1231
+ }
1232
+ function bufferRequestMessage(message) {
1233
+ bufferedRequestMessages.push(message);
1234
+ }
1235
+ function flushBufferedRequestMessages(socket, sessionId) {
1236
+ const messages = bufferedRequestMessages;
1237
+ bufferedRequestMessages = [];
1238
+ for (const message of messages) {
1239
+ handleRuntimeDebugRequest(socket, sessionId, message);
1240
+ }
1241
+ }
1242
+ function handleRuntimeDebugRequest(socket, sessionId, message) {
1243
+ const controller = currentController;
1244
+ if (!controller) {
1245
+ bufferRequestMessage(message);
1246
+ return;
1247
+ }
1248
+ if (isJumpRequestMessage(message)) {
1249
+ const runJump = message.strategy === "warp" ? controller.warp({
1250
+ markerId: message.markerId,
1251
+ boundary: message.boundary
1252
+ }).then(() => true) : controller.restoreCheckpoint(message.markerId);
1253
+ void runJump.then((restored) => {
1254
+ if (!restored) {
1255
+ sendRuntimeDebugMessage(socket, {
1256
+ type: "jump:error",
1257
+ sessionId,
1258
+ requestId: message.requestId,
1259
+ markerId: message.markerId,
1260
+ error: "Checkpoint not found"
1261
+ });
1262
+ return;
1263
+ }
1264
+ sendRuntimeDebugMessage(socket, {
1265
+ type: "jump:done",
1266
+ sessionId,
1267
+ requestId: message.requestId,
1268
+ markerId: message.markerId
1269
+ });
1270
+ }).catch((error) => {
1271
+ sendRuntimeDebugMessage(socket, {
1272
+ type: "jump:error",
1273
+ sessionId,
1274
+ requestId: message.requestId,
1275
+ markerId: message.markerId,
1276
+ error: error instanceof Error ? error.message : String(error)
1277
+ });
1278
+ });
1279
+ return;
1280
+ }
1281
+ void controller.switchPage(message.page, message.params).then(() => {
1282
+ sendRuntimeDebugMessage(socket, {
1283
+ type: "route:done",
1284
+ sessionId,
1285
+ requestId: message.requestId,
1286
+ page: message.page
1287
+ });
1288
+ }).catch((error) => {
1289
+ sendRuntimeDebugMessage(socket, {
1290
+ type: "route:error",
1291
+ sessionId,
1292
+ requestId: message.requestId,
1293
+ page: message.page,
1294
+ error: error instanceof Error ? error.message : String(error)
1295
+ });
1296
+ });
1297
+ }
1298
+ async function readDebugParams() {
1299
+ const rawParams = await executePluginCommand("system", {
1300
+ subCommand: "getParams"
1301
+ });
1302
+ if (!rawParams) {
1303
+ return {};
1304
+ }
1305
+ try {
1306
+ const parsed = JSON.parse(rawParams);
1307
+ return {
1308
+ debug: parsed.debug === true,
1309
+ debugSessionId: typeof parsed.debugSessionId === "string" ? parsed.debugSessionId : void 0,
1310
+ debugWsUrl: typeof parsed.debugWsUrl === "string" ? parsed.debugWsUrl : void 0
1311
+ };
1312
+ } catch {
1313
+ return {};
1314
+ }
1315
+ }
1316
+ async function stopRuntimeDebugSession() {
1317
+ const connection = currentConnection;
1318
+ currentConnection = null;
1319
+ currentController = null;
1320
+ clearBufferedRequestMessages();
1321
+ const RuntimeWebSocket = getRuntimeWebSocket();
1322
+ await stopDebugSession();
1323
+ if (!connection) {
1324
+ return;
1325
+ }
1326
+ if (RuntimeWebSocket && (connection.socket.readyState === RuntimeWebSocket.OPEN || connection.socket.readyState === RuntimeWebSocket.CONNECTING)) {
1327
+ connection.socket.close();
1328
+ }
1329
+ }
1330
+ async function startRuntimeDebugSession() {
1331
+ await stopRuntimeDebugSession();
1332
+ const params = await readDebugParams();
1333
+ if (params.debug !== true || !params.debugSessionId || !params.debugWsUrl) {
1334
+ return;
1335
+ }
1336
+ const RuntimeWebSocket = getRuntimeWebSocket();
1337
+ if (!RuntimeWebSocket) {
1338
+ return;
1339
+ }
1340
+ const socket = new RuntimeWebSocket(params.debugWsUrl);
1341
+ const sessionId = params.debugSessionId;
1342
+ currentConnection = { sessionId, socket };
1343
+ socket.onopen = () => {
1344
+ if (currentConnection?.socket !== socket) {
1345
+ socket.close();
1346
+ return;
1347
+ }
1348
+ void startDebugSession({
1349
+ onMarkerEnter(checkpoint) {
1350
+ if (!checkpoint.cursor.markerId) {
1351
+ return;
1352
+ }
1353
+ sendRuntimeDebugMessage(socket, {
1354
+ type: "marker:enter",
1355
+ sessionId,
1356
+ markerId: checkpoint.cursor.markerId,
1357
+ story: checkpoint.cursor.story,
1358
+ paragraph: checkpoint.cursor.paragraph
1359
+ });
1360
+ },
1361
+ onError(error) {
1362
+ console.error("[debug] runtime debug session error:", error);
1363
+ }
1364
+ }).then((controller) => {
1365
+ currentController = controller;
1366
+ flushBufferedRequestMessages(socket, sessionId);
1367
+ }).catch((error) => {
1368
+ console.error("[debug] failed to start runtime debug session:", error);
1369
+ socket.close();
1370
+ });
1371
+ };
1372
+ socket.onclose = () => {
1373
+ if (currentConnection?.socket !== socket) {
1374
+ return;
1375
+ }
1376
+ currentConnection = null;
1377
+ currentController = null;
1378
+ clearBufferedRequestMessages();
1379
+ void stopDebugSession();
1380
+ };
1381
+ socket.onerror = (event) => {
1382
+ if (currentConnection?.socket !== socket) {
1383
+ return;
1384
+ }
1385
+ console.error("[debug] runtime websocket error:", event);
1386
+ };
1387
+ socket.onmessage = (event) => {
1388
+ if (currentConnection?.socket !== socket) {
1389
+ return;
1390
+ }
1391
+ try {
1392
+ const message = JSON.parse(String(event.data));
1393
+ if (message.sessionId && message.sessionId !== sessionId) {
1394
+ return;
1395
+ }
1396
+ if (isJumpRequestMessage(message)) {
1397
+ handleRuntimeDebugRequest(socket, sessionId, message);
1398
+ return;
1399
+ }
1400
+ if (isRouteRequestMessage(message)) {
1401
+ handleRuntimeDebugRequest(socket, sessionId, message);
1402
+ return;
1403
+ }
1404
+ if (message.type && message.type !== "session:init") {
1405
+ console.debug("[debug] runtime received message:", message);
1406
+ }
1407
+ } catch (error) {
1408
+ console.error("[debug] failed to parse runtime debug message:", error);
1409
+ }
1410
+ };
1411
+ }
1412
+
777
1413
  // src/hooks/useFadeInOut.ts
778
1414
  function useFadeInOut(inTime, _keepTime, _outTime, pause = false, onFinished) {
779
1415
  const [style, api] = (0, spring_exports.useSpring)(
@@ -876,11 +1512,13 @@ async function terminateCurrentScenario() {
876
1512
  await executePluginCommand("scenario", {
877
1513
  subCommand: "terminateStory"
878
1514
  });
1515
+ clearDebugSessionRuntimeState();
879
1516
  } catch (error) {
880
1517
  console.error("Failed to terminate scenario:", error);
881
1518
  }
882
1519
  }
883
1520
  async function startScenarioSession(session, config) {
1521
+ clearDebugSessionRuntimeState();
884
1522
  for (const story of config.stories) {
885
1523
  if (!isCurrentScenarioSession(session)) return;
886
1524
  try {
@@ -1047,18 +1685,30 @@ function useSoundEffect(src) {
1047
1685
 
1048
1686
  // src/hooks/useStage.ts
1049
1687
  import { createContext as createContext2, useContext as useContext2, useEffect as useEffect3, createElement, useCallback as useCallback2 } from "react";
1050
- import { proxy as proxy2, useSnapshot as useSnapshot2 } from "valtio";
1688
+ import { proxy as proxy3, useSnapshot as useSnapshot3 } from "valtio";
1051
1689
  var SKIP_INTERVAL_MS = 80;
1690
+ var DEFAULT_FAST_FORWARD_STEP_LIMIT = 1e4;
1691
+ var DEFAULT_FAST_FORWARD_TIME_LIMIT_MS = 3e3;
1052
1692
  var autoRuntime = {
1053
1693
  defaultTailMs: 0
1054
1694
  };
1055
- var skipState = proxy2({ active: false });
1056
- var autoState = proxy2({ active: false });
1695
+ var skipState = proxy3({ active: false });
1696
+ var autoState = proxy3({ active: false });
1697
+ var fastForwardState = proxy3({ active: false });
1698
+ var warpState = proxy3({ active: false });
1057
1699
  function useIsSkipping() {
1058
- return useSnapshot2(skipState).active;
1700
+ return useSnapshot3(skipState).active;
1059
1701
  }
1060
1702
  function useIsAutoing() {
1061
- return useSnapshot2(autoState).active;
1703
+ return useSnapshot3(autoState).active;
1704
+ }
1705
+ function useIsSeeking() {
1706
+ return useSnapshot3(fastForwardState).active;
1707
+ }
1708
+ function getSeekingType() {
1709
+ if (warpState.active) return "warp";
1710
+ if (fastForwardState.active) return "fast-forward";
1711
+ return null;
1062
1712
  }
1063
1713
  function setDefaultAutoTailMs(ms) {
1064
1714
  if (!Number.isFinite(ms)) {
@@ -1166,6 +1816,101 @@ function createStage() {
1166
1816
  let _autoBarrierSeq = 0;
1167
1817
  let _autoTicketSeq = 0;
1168
1818
  const _pendingAutoTickets = /* @__PURE__ */ new Map();
1819
+ let _fastForwardRuntime = null;
1820
+ let _managedAdvanceInFlight = false;
1821
+ let _managedAdvancePending = false;
1822
+ function resetManagedAdvanceState() {
1823
+ _managedAdvanceInFlight = false;
1824
+ _managedAdvancePending = false;
1825
+ }
1826
+ function clearFastForwardRuntime() {
1827
+ _fastForwardRuntime = null;
1828
+ resetManagedAdvanceState();
1829
+ }
1830
+ function stopFastForwardInternal() {
1831
+ fastForwardState.active = false;
1832
+ warpState.active = false;
1833
+ clearFastForwardRuntime();
1834
+ }
1835
+ function abortFastForward(error) {
1836
+ const runtime = _fastForwardRuntime;
1837
+ stopFastForwardInternal();
1838
+ if (!runtime?.onAbort) {
1839
+ return;
1840
+ }
1841
+ try {
1842
+ runtime.onAbort(error);
1843
+ } catch (abortError) {
1844
+ console.error("Error in fast-forward abort callback:", abortError);
1845
+ }
1846
+ }
1847
+ function canAdvanceFastForward() {
1848
+ if (!fastForwardState.active || _fastForwardRuntime === null) {
1849
+ return true;
1850
+ }
1851
+ const elapsedMs = Date.now() - _fastForwardRuntime.startedAt;
1852
+ if (elapsedMs > _fastForwardRuntime.timeLimitMs) {
1853
+ abortFastForward(new Error(`Fast forward exceeded time limit (${_fastForwardRuntime.timeLimitMs}ms)`));
1854
+ return false;
1855
+ }
1856
+ _fastForwardRuntime.stepCount += 1;
1857
+ if (_fastForwardRuntime.stepCount > _fastForwardRuntime.stepLimit) {
1858
+ abortFastForward(new Error(`Fast forward exceeded step limit (${_fastForwardRuntime.stepLimit})`));
1859
+ return false;
1860
+ }
1861
+ return true;
1862
+ }
1863
+ function normalizeAdvanceError(error) {
1864
+ return error instanceof Error ? error : new Error(String(error));
1865
+ }
1866
+ function isManagedAdvanceActive() {
1867
+ return fastForwardState.active || skipState.active || autoState.active;
1868
+ }
1869
+ function canScheduleManagedAdvance() {
1870
+ if (fastForwardState.active) {
1871
+ return canAdvanceFastForward();
1872
+ }
1873
+ return true;
1874
+ }
1875
+ function handleManagedAdvanceError(error) {
1876
+ console.error("Failed to advance scenario:", error);
1877
+ if (fastForwardState.active) {
1878
+ abortFastForward(normalizeAdvanceError(error));
1879
+ }
1880
+ }
1881
+ function scheduleManagedAdvance() {
1882
+ if (!isManagedAdvanceActive()) {
1883
+ _managedAdvancePending = false;
1884
+ return;
1885
+ }
1886
+ if (_managedAdvanceInFlight) {
1887
+ _managedAdvancePending = true;
1888
+ return;
1889
+ }
1890
+ if (!canScheduleManagedAdvance()) {
1891
+ _managedAdvancePending = false;
1892
+ return;
1893
+ }
1894
+ _managedAdvanceInFlight = true;
1895
+ _managedAdvancePending = false;
1896
+ void Promise.resolve(nextLine()).catch(handleManagedAdvanceError).finally(() => {
1897
+ _managedAdvanceInFlight = false;
1898
+ if (!isManagedAdvanceActive()) {
1899
+ _managedAdvancePending = false;
1900
+ return;
1901
+ }
1902
+ if (_managedAdvancePending) {
1903
+ scheduleManagedAdvance();
1904
+ }
1905
+ });
1906
+ }
1907
+ function requestNextLine() {
1908
+ if (isManagedAdvanceActive()) {
1909
+ scheduleManagedAdvance();
1910
+ return;
1911
+ }
1912
+ void Promise.resolve(nextLine()).catch(handleManagedAdvanceError);
1913
+ }
1169
1914
  function clearAutoResumeTimer() {
1170
1915
  if (_autoResumeTimer !== null) {
1171
1916
  clearTimeout(_autoResumeTimer);
@@ -1200,7 +1945,7 @@ function createStage() {
1200
1945
  if (!autoState.active) return;
1201
1946
  if (_autoBarrier === null || _autoBarrier.id !== barrierId) return;
1202
1947
  _autoBarrier = null;
1203
- void nextLine();
1948
+ requestNextLine();
1204
1949
  }, safeDelayMs);
1205
1950
  }
1206
1951
  function trySettleAutoBarrier(barrier) {
@@ -1336,7 +2081,7 @@ function createStage() {
1336
2081
  return;
1337
2082
  }
1338
2083
  if (_autoBarrier === null) {
1339
- void nextLine();
2084
+ requestNextLine();
1340
2085
  }
1341
2086
  }, autoRuntime.defaultTailMs);
1342
2087
  }
@@ -1344,11 +2089,12 @@ function createStage() {
1344
2089
  if (_skipTimer !== null) clearTimeout(_skipTimer);
1345
2090
  _skipTimer = setTimeout(() => {
1346
2091
  _skipTimer = null;
1347
- if (skipState.active) nextLine();
2092
+ if (skipState.active) requestNextLine();
1348
2093
  }, SKIP_INTERVAL_MS);
1349
2094
  }
1350
2095
  function startSkip() {
1351
2096
  if (skipState.active) return;
2097
+ if (fastForwardState.active) return;
1352
2098
  if (isAnyBlockerActive()) return;
1353
2099
  skipState.active = true;
1354
2100
  invokeSkipCallbacks();
@@ -1360,7 +2106,7 @@ function createStage() {
1360
2106
  if (_skipTimer !== null) {
1361
2107
  clearTimeout(_skipTimer);
1362
2108
  _skipTimer = null;
1363
- nextLine();
2109
+ requestNextLine();
1364
2110
  }
1365
2111
  }
1366
2112
  function isSkipping() {
@@ -1368,6 +2114,7 @@ function createStage() {
1368
2114
  }
1369
2115
  function startAuto() {
1370
2116
  if (autoState.active) return;
2117
+ if (fastForwardState.active) return;
1371
2118
  if (isAnyAutoBlockerActive()) return;
1372
2119
  autoState.active = true;
1373
2120
  tryInterrupt();
@@ -1385,12 +2132,58 @@ function createStage() {
1385
2132
  function isAutoing() {
1386
2133
  return autoState.active;
1387
2134
  }
2135
+ function startFastForward(options) {
2136
+ if (fastForwardState.active) return;
2137
+ resetRuntimeState();
2138
+ _fastForwardRuntime = {
2139
+ stepLimit: Math.max(1, options?.stepLimit ?? DEFAULT_FAST_FORWARD_STEP_LIMIT),
2140
+ timeLimitMs: Math.max(1, options?.timeLimitMs ?? DEFAULT_FAST_FORWARD_TIME_LIMIT_MS),
2141
+ stepCount: 0,
2142
+ startedAt: Date.now(),
2143
+ onAbort: options?.onAbort,
2144
+ warp: options?.warp === true
2145
+ };
2146
+ fastForwardState.active = true;
2147
+ warpState.active = _fastForwardRuntime.warp;
2148
+ }
2149
+ function stopFastForward() {
2150
+ stopFastForwardInternal();
2151
+ }
2152
+ function isFastForwarding() {
2153
+ return fastForwardState.active;
2154
+ }
2155
+ function resetRuntimeState() {
2156
+ skipState.active = false;
2157
+ autoState.active = false;
2158
+ fastForwardState.active = false;
2159
+ warpState.active = false;
2160
+ clearFastForwardRuntime();
2161
+ if (_skipTimer !== null) {
2162
+ clearTimeout(_skipTimer);
2163
+ _skipTimer = null;
2164
+ }
2165
+ if (_autoTimer !== null) {
2166
+ clearTimeout(_autoTimer);
2167
+ _autoTimer = null;
2168
+ }
2169
+ clearAutoBarrier();
2170
+ clearPendingAutoTickets();
2171
+ }
1388
2172
  function buildControl() {
1389
- let handled = false;
2173
+ const warpActive = warpState.active;
2174
+ let handled = warpActive;
1390
2175
  let unskippableFlag = false;
2176
+ const inertRecordId = "__warp__";
1391
2177
  const control = {
1392
2178
  setWaiting(time, skippable) {
1393
2179
  handled = true;
2180
+ if (warpActive) {
2181
+ return;
2182
+ }
2183
+ if (fastForwardState.active) {
2184
+ requestNextLine();
2185
+ return;
2186
+ }
1394
2187
  if (skipState.active) {
1395
2188
  invokeSkipCallbacks();
1396
2189
  scheduleSkipNextLine();
@@ -1407,6 +2200,13 @@ function createStage() {
1407
2200
  },
1408
2201
  hold() {
1409
2202
  handled = true;
2203
+ if (warpActive) {
2204
+ return;
2205
+ }
2206
+ if (fastForwardState.active) {
2207
+ requestNextLine();
2208
+ return;
2209
+ }
1410
2210
  if (skipState.active && !unskippableFlag) {
1411
2211
  if (isAnyBlockerActive()) {
1412
2212
  stopSkip();
@@ -1425,12 +2225,21 @@ function createStage() {
1425
2225
  },
1426
2226
  nextLine() {
1427
2227
  handled = true;
1428
- void nextLine();
2228
+ if (warpActive) {
2229
+ return;
2230
+ }
2231
+ requestNextLine();
1429
2232
  },
1430
2233
  unskippable() {
2234
+ if (warpActive) {
2235
+ return;
2236
+ }
1431
2237
  unskippableFlag = true;
1432
2238
  },
1433
2239
  record(meta) {
2240
+ if (warpActive) {
2241
+ return inertRecordId;
2242
+ }
1434
2243
  const result = executePluginCommand("scenario", {
1435
2244
  subCommand: "record",
1436
2245
  meta
@@ -1469,19 +2278,19 @@ function createStage() {
1469
2278
  cmd = transformCommand(e);
1470
2279
  } catch (err) {
1471
2280
  console.error("Failed to transform scenario command:", err);
1472
- void nextLine();
2281
+ requestNextLine();
1473
2282
  return;
1474
2283
  }
1475
2284
  const handler = commandHandlers.get(cmd.command);
1476
2285
  if (!handler) {
1477
2286
  console.warn(`[Stage] No handler registered for command "${cmd.command}". Auto-advancing.`);
1478
- void nextLine();
2287
+ requestNextLine();
1479
2288
  return;
1480
2289
  }
1481
2290
  const { control, wasHandled } = buildControl();
1482
2291
  handler(cmd, control);
1483
2292
  if (!wasHandled()) {
1484
- void nextLine();
2293
+ requestNextLine();
1485
2294
  }
1486
2295
  }
1487
2296
  function dispatchTextLine(e) {
@@ -1490,7 +2299,7 @@ function createStage() {
1490
2299
  handler(e, control);
1491
2300
  }
1492
2301
  if (!wasHandled()) {
1493
- void nextLine();
2302
+ requestNextLine();
1494
2303
  }
1495
2304
  }
1496
2305
  function invokeSkipCallbacks() {
@@ -1538,6 +2347,10 @@ function createStage() {
1538
2347
  startAuto,
1539
2348
  stopAuto,
1540
2349
  isAutoing,
2350
+ startFastForward,
2351
+ stopFastForward,
2352
+ isFastForwarding,
2353
+ resetRuntimeState,
1541
2354
  bindEvents
1542
2355
  };
1543
2356
  }
@@ -1610,23 +2423,33 @@ export {
1610
2423
  createStaticNavigation,
1611
2424
  executeNodeCommand,
1612
2425
  executePluginCommand,
2426
+ fastForwardState,
2427
+ getAppStateAdapter,
1613
2428
  getNavigator,
2429
+ getSeekingType,
1614
2430
  getStageSize,
1615
2431
  globalEventListeners,
1616
2432
  globalRequestAnimationFrameListeners,
1617
2433
  moyu_exports as moyu,
1618
2434
  nextLine,
2435
+ registerAppStateAdapter,
1619
2436
  setDefaultAutoTailMs,
1620
2437
  setWaiting,
1621
2438
  skipState,
2439
+ startDebugSession,
2440
+ startRuntimeDebugSession,
2441
+ stopDebugSession,
2442
+ stopRuntimeDebugSession,
1622
2443
  useAutoBlocker,
1623
2444
  useAutoTicket,
1624
2445
  useBeforeHandleCommandCallback,
2446
+ useDebugSession,
1625
2447
  useFadeIn,
1626
2448
  useFadeInOut,
1627
2449
  useFadeOut,
1628
2450
  useInterruptCallback,
1629
2451
  useIsAutoing,
2452
+ useIsSeeking,
1630
2453
  useIsSkipping,
1631
2454
  useNavigation,
1632
2455
  useNavigationParams,
@@ -1635,5 +2458,6 @@ export {
1635
2458
  useSkipBlocker,
1636
2459
  useSkipCallback,
1637
2460
  useSoundEffect,
1638
- useStageContext
2461
+ useStageContext,
2462
+ warpState
1639
2463
  };