@momoyu-ink/kit 0.5.5 → 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
@@ -437,7 +448,7 @@ var globalVariables = new Proxy(
437
448
  }
438
449
  );
439
450
  function safeEval(code, sandbox) {
440
- const proxy3 = new Proxy(sandbox, {
451
+ const proxy4 = new Proxy(sandbox, {
441
452
  has(target, key) {
442
453
  if (typeof key === "string" && JS_GLOBAL_KEYS.has(key)) {
443
454
  return false;
@@ -445,7 +456,7 @@ function safeEval(code, sandbox) {
445
456
  return key in target || key === "ARCHIVE" || key === "GLOBAL" || key === "window" || key === "globalThis";
446
457
  },
447
458
  get(target, key) {
448
- if (key === "window" || key === "globalThis") return proxy3;
459
+ if (key === "window" || key === "globalThis") return proxy4;
449
460
  if (key === "ARCHIVE") return archiveVariables;
450
461
  if (key === "GLOBAL") return globalVariables;
451
462
  if (key === Symbol.unscopables) return void 0;
@@ -456,7 +467,7 @@ function safeEval(code, sandbox) {
456
467
  return true;
457
468
  }
458
469
  });
459
- return new Function("sandbox", `with(sandbox) { return (${code}) }`).bind(proxy3)(proxy3);
470
+ return new Function("sandbox", `with(sandbox) { return (${code}) }`).bind(proxy4)(proxy4);
460
471
  }
461
472
  globalThis.__moyu_eval_sandbox = (code) => {
462
473
  try {
@@ -639,9 +650,9 @@ function getStageSize() {
639
650
  }
640
651
 
641
652
  // src/components/navigation.tsx
642
- import React2 from "react";
643
653
  import { createContext, useContext } from "react";
644
654
  import { proxy, useSnapshot } from "valtio";
655
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
645
656
  var PageParamsContext = createContext(void 0);
646
657
  var OverlayParamsContext = createContext(void 0);
647
658
  var overlayIdCounter = 0;
@@ -697,6 +708,9 @@ function createStackNavigator(options) {
697
708
  delete navigationState.pageParams[page];
698
709
  }
699
710
  },
711
+ hasPage: (page) => {
712
+ return normalizedPages[page] !== void 0;
713
+ },
700
714
  goBack: () => {
701
715
  console.warn("goBack() not implemented yet");
702
716
  },
@@ -752,11 +766,14 @@ function createStaticNavigation(navigator) {
752
766
  const { pages, overlays } = navigator._descriptors;
753
767
  const pageDescriptor = pages[navState.currentPage];
754
768
  const Page = pageDescriptor?.component || (() => null);
755
- 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) => {
756
- const overlayDescriptor = overlays[overlay.type];
757
- const OverlayComponent = overlayDescriptor?.component || (() => null);
758
- return /* @__PURE__ */ React2.createElement(OverlayParamsContext.Provider, { key: overlay.id, value: navState.overlayParams[overlay.id] }, /* @__PURE__ */ React2.createElement(OverlayComponent, null));
759
- }));
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
+ ] });
760
777
  };
761
778
  }
762
779
  function getNavigator() {
@@ -775,6 +792,624 @@ function useNavigationParams() {
775
792
  return overlayParams ?? pageParams ?? {};
776
793
  }
777
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
+
778
1413
  // src/hooks/useFadeInOut.ts
779
1414
  function useFadeInOut(inTime, _keepTime, _outTime, pause = false, onFinished) {
780
1415
  const [style, api] = (0, spring_exports.useSpring)(
@@ -877,11 +1512,13 @@ async function terminateCurrentScenario() {
877
1512
  await executePluginCommand("scenario", {
878
1513
  subCommand: "terminateStory"
879
1514
  });
1515
+ clearDebugSessionRuntimeState();
880
1516
  } catch (error) {
881
1517
  console.error("Failed to terminate scenario:", error);
882
1518
  }
883
1519
  }
884
1520
  async function startScenarioSession(session, config) {
1521
+ clearDebugSessionRuntimeState();
885
1522
  for (const story of config.stories) {
886
1523
  if (!isCurrentScenarioSession(session)) return;
887
1524
  try {
@@ -1048,18 +1685,30 @@ function useSoundEffect(src) {
1048
1685
 
1049
1686
  // src/hooks/useStage.ts
1050
1687
  import { createContext as createContext2, useContext as useContext2, useEffect as useEffect3, createElement, useCallback as useCallback2 } from "react";
1051
- import { proxy as proxy2, useSnapshot as useSnapshot2 } from "valtio";
1688
+ import { proxy as proxy3, useSnapshot as useSnapshot3 } from "valtio";
1052
1689
  var SKIP_INTERVAL_MS = 80;
1690
+ var DEFAULT_FAST_FORWARD_STEP_LIMIT = 1e4;
1691
+ var DEFAULT_FAST_FORWARD_TIME_LIMIT_MS = 3e3;
1053
1692
  var autoRuntime = {
1054
1693
  defaultTailMs: 0
1055
1694
  };
1056
- var skipState = proxy2({ active: false });
1057
- 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 });
1058
1699
  function useIsSkipping() {
1059
- return useSnapshot2(skipState).active;
1700
+ return useSnapshot3(skipState).active;
1060
1701
  }
1061
1702
  function useIsAutoing() {
1062
- 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;
1063
1712
  }
1064
1713
  function setDefaultAutoTailMs(ms) {
1065
1714
  if (!Number.isFinite(ms)) {
@@ -1167,6 +1816,101 @@ function createStage() {
1167
1816
  let _autoBarrierSeq = 0;
1168
1817
  let _autoTicketSeq = 0;
1169
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
+ }
1170
1914
  function clearAutoResumeTimer() {
1171
1915
  if (_autoResumeTimer !== null) {
1172
1916
  clearTimeout(_autoResumeTimer);
@@ -1201,7 +1945,7 @@ function createStage() {
1201
1945
  if (!autoState.active) return;
1202
1946
  if (_autoBarrier === null || _autoBarrier.id !== barrierId) return;
1203
1947
  _autoBarrier = null;
1204
- void nextLine();
1948
+ requestNextLine();
1205
1949
  }, safeDelayMs);
1206
1950
  }
1207
1951
  function trySettleAutoBarrier(barrier) {
@@ -1337,7 +2081,7 @@ function createStage() {
1337
2081
  return;
1338
2082
  }
1339
2083
  if (_autoBarrier === null) {
1340
- void nextLine();
2084
+ requestNextLine();
1341
2085
  }
1342
2086
  }, autoRuntime.defaultTailMs);
1343
2087
  }
@@ -1345,11 +2089,12 @@ function createStage() {
1345
2089
  if (_skipTimer !== null) clearTimeout(_skipTimer);
1346
2090
  _skipTimer = setTimeout(() => {
1347
2091
  _skipTimer = null;
1348
- if (skipState.active) nextLine();
2092
+ if (skipState.active) requestNextLine();
1349
2093
  }, SKIP_INTERVAL_MS);
1350
2094
  }
1351
2095
  function startSkip() {
1352
2096
  if (skipState.active) return;
2097
+ if (fastForwardState.active) return;
1353
2098
  if (isAnyBlockerActive()) return;
1354
2099
  skipState.active = true;
1355
2100
  invokeSkipCallbacks();
@@ -1361,7 +2106,7 @@ function createStage() {
1361
2106
  if (_skipTimer !== null) {
1362
2107
  clearTimeout(_skipTimer);
1363
2108
  _skipTimer = null;
1364
- nextLine();
2109
+ requestNextLine();
1365
2110
  }
1366
2111
  }
1367
2112
  function isSkipping() {
@@ -1369,6 +2114,7 @@ function createStage() {
1369
2114
  }
1370
2115
  function startAuto() {
1371
2116
  if (autoState.active) return;
2117
+ if (fastForwardState.active) return;
1372
2118
  if (isAnyAutoBlockerActive()) return;
1373
2119
  autoState.active = true;
1374
2120
  tryInterrupt();
@@ -1386,12 +2132,58 @@ function createStage() {
1386
2132
  function isAutoing() {
1387
2133
  return autoState.active;
1388
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
+ }
1389
2172
  function buildControl() {
1390
- let handled = false;
2173
+ const warpActive = warpState.active;
2174
+ let handled = warpActive;
1391
2175
  let unskippableFlag = false;
2176
+ const inertRecordId = "__warp__";
1392
2177
  const control = {
1393
2178
  setWaiting(time, skippable) {
1394
2179
  handled = true;
2180
+ if (warpActive) {
2181
+ return;
2182
+ }
2183
+ if (fastForwardState.active) {
2184
+ requestNextLine();
2185
+ return;
2186
+ }
1395
2187
  if (skipState.active) {
1396
2188
  invokeSkipCallbacks();
1397
2189
  scheduleSkipNextLine();
@@ -1408,6 +2200,13 @@ function createStage() {
1408
2200
  },
1409
2201
  hold() {
1410
2202
  handled = true;
2203
+ if (warpActive) {
2204
+ return;
2205
+ }
2206
+ if (fastForwardState.active) {
2207
+ requestNextLine();
2208
+ return;
2209
+ }
1411
2210
  if (skipState.active && !unskippableFlag) {
1412
2211
  if (isAnyBlockerActive()) {
1413
2212
  stopSkip();
@@ -1426,12 +2225,21 @@ function createStage() {
1426
2225
  },
1427
2226
  nextLine() {
1428
2227
  handled = true;
1429
- void nextLine();
2228
+ if (warpActive) {
2229
+ return;
2230
+ }
2231
+ requestNextLine();
1430
2232
  },
1431
2233
  unskippable() {
2234
+ if (warpActive) {
2235
+ return;
2236
+ }
1432
2237
  unskippableFlag = true;
1433
2238
  },
1434
2239
  record(meta) {
2240
+ if (warpActive) {
2241
+ return inertRecordId;
2242
+ }
1435
2243
  const result = executePluginCommand("scenario", {
1436
2244
  subCommand: "record",
1437
2245
  meta
@@ -1470,19 +2278,19 @@ function createStage() {
1470
2278
  cmd = transformCommand(e);
1471
2279
  } catch (err) {
1472
2280
  console.error("Failed to transform scenario command:", err);
1473
- void nextLine();
2281
+ requestNextLine();
1474
2282
  return;
1475
2283
  }
1476
2284
  const handler = commandHandlers.get(cmd.command);
1477
2285
  if (!handler) {
1478
2286
  console.warn(`[Stage] No handler registered for command "${cmd.command}". Auto-advancing.`);
1479
- void nextLine();
2287
+ requestNextLine();
1480
2288
  return;
1481
2289
  }
1482
2290
  const { control, wasHandled } = buildControl();
1483
2291
  handler(cmd, control);
1484
2292
  if (!wasHandled()) {
1485
- void nextLine();
2293
+ requestNextLine();
1486
2294
  }
1487
2295
  }
1488
2296
  function dispatchTextLine(e) {
@@ -1491,7 +2299,7 @@ function createStage() {
1491
2299
  handler(e, control);
1492
2300
  }
1493
2301
  if (!wasHandled()) {
1494
- void nextLine();
2302
+ requestNextLine();
1495
2303
  }
1496
2304
  }
1497
2305
  function invokeSkipCallbacks() {
@@ -1539,6 +2347,10 @@ function createStage() {
1539
2347
  startAuto,
1540
2348
  stopAuto,
1541
2349
  isAutoing,
2350
+ startFastForward,
2351
+ stopFastForward,
2352
+ isFastForwarding,
2353
+ resetRuntimeState,
1542
2354
  bindEvents
1543
2355
  };
1544
2356
  }
@@ -1611,23 +2423,33 @@ export {
1611
2423
  createStaticNavigation,
1612
2424
  executeNodeCommand,
1613
2425
  executePluginCommand,
2426
+ fastForwardState,
2427
+ getAppStateAdapter,
1614
2428
  getNavigator,
2429
+ getSeekingType,
1615
2430
  getStageSize,
1616
2431
  globalEventListeners,
1617
2432
  globalRequestAnimationFrameListeners,
1618
2433
  moyu_exports as moyu,
1619
2434
  nextLine,
2435
+ registerAppStateAdapter,
1620
2436
  setDefaultAutoTailMs,
1621
2437
  setWaiting,
1622
2438
  skipState,
2439
+ startDebugSession,
2440
+ startRuntimeDebugSession,
2441
+ stopDebugSession,
2442
+ stopRuntimeDebugSession,
1623
2443
  useAutoBlocker,
1624
2444
  useAutoTicket,
1625
2445
  useBeforeHandleCommandCallback,
2446
+ useDebugSession,
1626
2447
  useFadeIn,
1627
2448
  useFadeInOut,
1628
2449
  useFadeOut,
1629
2450
  useInterruptCallback,
1630
2451
  useIsAutoing,
2452
+ useIsSeeking,
1631
2453
  useIsSkipping,
1632
2454
  useNavigation,
1633
2455
  useNavigationParams,
@@ -1636,5 +2458,6 @@ export {
1636
2458
  useSkipBlocker,
1637
2459
  useSkipCallback,
1638
2460
  useSoundEffect,
1639
- useStageContext
2461
+ useStageContext,
2462
+ warpState
1640
2463
  };