@momoyu-ink/kit 0.5.5 → 0.6.1

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
@@ -384,8 +395,35 @@ function createRoot(options) {
384
395
 
385
396
  // src/sandbox.ts
386
397
  var JS_GLOBAL_KEYS = new Set(Object.getOwnPropertyNames(globalThis));
387
- var JS_GLOBAL_KEYS_EXCLUDED = /* @__PURE__ */ new Set(["window", "globalThis", "ARCHIVE", "GLOBAL"]);
388
- JS_GLOBAL_KEYS_EXCLUDED.forEach((key) => JS_GLOBAL_KEYS.delete(key));
398
+ var JS_GLOBAL_KEYS_EXCLUDED = /* @__PURE__ */ new Set(["window", "globalThis", "LOCAL", "ARCHIVE", "GLOBAL"]);
399
+ JS_GLOBAL_KEYS_EXCLUDED.forEach((key) => {
400
+ JS_GLOBAL_KEYS.delete(key);
401
+ });
402
+ var localVariables = new Proxy(
403
+ {},
404
+ {
405
+ get(_, key) {
406
+ if (typeof key === "string") {
407
+ return executePluginCommand("scenario", {
408
+ subCommand: "getLocalVariable",
409
+ name: key
410
+ });
411
+ }
412
+ return void 0;
413
+ },
414
+ set(_, key, value) {
415
+ if (typeof key === "string") {
416
+ executePluginCommand("scenario", {
417
+ subCommand: "setLocalVariable",
418
+ name: key,
419
+ value
420
+ });
421
+ return true;
422
+ }
423
+ return false;
424
+ }
425
+ }
426
+ );
389
427
  var archiveVariables = new Proxy(
390
428
  {},
391
429
  {
@@ -437,15 +475,16 @@ var globalVariables = new Proxy(
437
475
  }
438
476
  );
439
477
  function safeEval(code, sandbox) {
440
- const proxy3 = new Proxy(sandbox, {
478
+ const proxy4 = new Proxy(sandbox, {
441
479
  has(target, key) {
442
480
  if (typeof key === "string" && JS_GLOBAL_KEYS.has(key)) {
443
481
  return false;
444
482
  }
445
- return key in target || key === "ARCHIVE" || key === "GLOBAL" || key === "window" || key === "globalThis";
483
+ return key in target || key === "LOCAL" || key === "ARCHIVE" || key === "GLOBAL" || key === "window" || key === "globalThis";
446
484
  },
447
485
  get(target, key) {
448
- if (key === "window" || key === "globalThis") return proxy3;
486
+ if (key === "window" || key === "globalThis") return proxy4;
487
+ if (key === "LOCAL") return localVariables;
449
488
  if (key === "ARCHIVE") return archiveVariables;
450
489
  if (key === "GLOBAL") return globalVariables;
451
490
  if (key === Symbol.unscopables) return void 0;
@@ -456,7 +495,7 @@ function safeEval(code, sandbox) {
456
495
  return true;
457
496
  }
458
497
  });
459
- return new Function("sandbox", `with(sandbox) { return (${code}) }`).bind(proxy3)(proxy3);
498
+ return new Function("sandbox", `with(sandbox) { return (${code}) }`).bind(proxy4)(proxy4);
460
499
  }
461
500
  globalThis.__moyu_eval_sandbox = (code) => {
462
501
  try {
@@ -639,9 +678,9 @@ function getStageSize() {
639
678
  }
640
679
 
641
680
  // src/components/navigation.tsx
642
- import React2 from "react";
643
681
  import { createContext, useContext } from "react";
644
682
  import { proxy, useSnapshot } from "valtio";
683
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
645
684
  var PageParamsContext = createContext(void 0);
646
685
  var OverlayParamsContext = createContext(void 0);
647
686
  var overlayIdCounter = 0;
@@ -697,6 +736,9 @@ function createStackNavigator(options) {
697
736
  delete navigationState.pageParams[page];
698
737
  }
699
738
  },
739
+ hasPage: (page) => {
740
+ return normalizedPages[page] !== void 0;
741
+ },
700
742
  goBack: () => {
701
743
  console.warn("goBack() not implemented yet");
702
744
  },
@@ -752,11 +794,14 @@ function createStaticNavigation(navigator) {
752
794
  const { pages, overlays } = navigator._descriptors;
753
795
  const pageDescriptor = pages[navState.currentPage];
754
796
  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
- }));
797
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
798
+ /* @__PURE__ */ jsx(PageParamsContext.Provider, { value: navState.pageParams[navState.currentPage], children: /* @__PURE__ */ jsx(Page, {}) }),
799
+ navState.overlayStack.map((overlay) => {
800
+ const overlayDescriptor = overlays[overlay.type];
801
+ const OverlayComponent = overlayDescriptor?.component || (() => null);
802
+ return /* @__PURE__ */ jsx(OverlayParamsContext.Provider, { value: navState.overlayParams[overlay.id], children: /* @__PURE__ */ jsx(OverlayComponent, {}) }, overlay.id);
803
+ })
804
+ ] });
760
805
  };
761
806
  }
762
807
  function getNavigator() {
@@ -775,6 +820,624 @@ function useNavigationParams() {
775
820
  return overlayParams ?? pageParams ?? {};
776
821
  }
777
822
 
823
+ // src/debug/session.ts
824
+ import { useSnapshot as useSnapshot2, proxy as proxy2 } from "valtio";
825
+ var debugState = proxy2({
826
+ active: false,
827
+ checkpoints: {},
828
+ currentCursor: null,
829
+ restoring: false,
830
+ lastError: null
831
+ });
832
+ var appStateAdapter = null;
833
+ var currentConfig = {
834
+ onMarkerEnter: () => {
835
+ },
836
+ onError: () => {
837
+ }
838
+ };
839
+ var disposeMarkerListener = null;
840
+ var disposeFinishedListener = null;
841
+ var disposeWarpFinishedListener = null;
842
+ var debugOperationQueue = Promise.resolve();
843
+ var activeFastForward = null;
844
+ var activeWarp = null;
845
+ function enqueueDebugOperation(task) {
846
+ const nextTask = debugOperationQueue.then(task, task);
847
+ debugOperationQueue = nextTask.then(
848
+ () => void 0,
849
+ () => void 0
850
+ );
851
+ return nextTask;
852
+ }
853
+ function executeScenarioCommand(payload) {
854
+ return Promise.resolve(executePluginCommand("scenario", payload));
855
+ }
856
+ function toErrorMessage(error) {
857
+ if (error instanceof Error) {
858
+ return error.message;
859
+ }
860
+ return String(error);
861
+ }
862
+ function isPromiseLike(value) {
863
+ return !!value && typeof value.then === "function";
864
+ }
865
+ function reportDebugSessionError(error) {
866
+ debugState.lastError = toErrorMessage(error);
867
+ void Promise.resolve(currentConfig.onError(error));
868
+ }
869
+ function clearLocalRuntimeState() {
870
+ debugState.checkpoints = {};
871
+ debugState.currentCursor = null;
872
+ debugState.restoring = false;
873
+ debugState.lastError = null;
874
+ }
875
+ async function doSettleActiveSeek(op, clearSlot, error) {
876
+ if (!op) {
877
+ return;
878
+ }
879
+ clearSlot();
880
+ try {
881
+ await appStateAdapter?.exitFastForwardMode?.();
882
+ } catch (exitError) {
883
+ if (error === void 0) {
884
+ error = exitError;
885
+ }
886
+ }
887
+ if (error === void 0) {
888
+ op.resolve();
889
+ return;
890
+ }
891
+ op.reject(error);
892
+ }
893
+ async function settleActiveFastForward(error) {
894
+ await doSettleActiveSeek(activeFastForward, () => {
895
+ activeFastForward = null;
896
+ }, error);
897
+ }
898
+ async function settleActiveWarp(error) {
899
+ await doSettleActiveSeek(activeWarp, () => {
900
+ activeWarp = null;
901
+ }, error);
902
+ }
903
+ async function clearRemoteCheckpoints() {
904
+ await executeScenarioCommand({ subCommand: "clearCheckpoints" });
905
+ }
906
+ async function syncLocalWarpCheckpoint(targetMarkerId) {
907
+ const cursor = await executeScenarioCommand({
908
+ subCommand: "getExecutionCursor"
909
+ });
910
+ const checkpointCursor = cursor ? {
911
+ story: cursor.story,
912
+ paragraph: cursor.paragraph,
913
+ markerId: targetMarkerId
914
+ } : debugState.currentCursor ? {
915
+ story: debugState.currentCursor.story,
916
+ paragraph: debugState.currentCursor.paragraph,
917
+ markerId: targetMarkerId
918
+ } : null;
919
+ if (!checkpointCursor) {
920
+ return;
921
+ }
922
+ const checkpoint = {
923
+ cursor: checkpointCursor,
924
+ appState: appStateAdapter ? await appStateAdapter.capture() : void 0
925
+ };
926
+ debugState.currentCursor = checkpointCursor;
927
+ debugState.checkpoints[targetMarkerId] = checkpoint;
928
+ await currentConfig.onMarkerEnter(checkpoint);
929
+ }
930
+ function captureCheckpointAppStateSync() {
931
+ if (!appStateAdapter) {
932
+ return void 0;
933
+ }
934
+ const appState = appStateAdapter.capture();
935
+ if (isPromiseLike(appState)) {
936
+ throw new Error("Async app state capture is not supported for debug checkpoints");
937
+ }
938
+ return appState;
939
+ }
940
+ function notifyMarkerEnter(checkpoint) {
941
+ try {
942
+ const result = currentConfig.onMarkerEnter(checkpoint);
943
+ if (isPromiseLike(result)) {
944
+ void result.catch(reportDebugSessionError);
945
+ }
946
+ } catch (error) {
947
+ reportDebugSessionError(error);
948
+ }
949
+ }
950
+ async function prepareJumpStart(markerId) {
951
+ const checkpoint = debugState.checkpoints[markerId];
952
+ if (checkpoint) {
953
+ const restored2 = await executeScenarioCommand({
954
+ subCommand: "restoreCheckpoint",
955
+ key: markerId
956
+ });
957
+ if (!restored2) {
958
+ throw new Error(`Failed to restore checkpoint ${markerId}`);
959
+ }
960
+ if (appStateAdapter) {
961
+ await appStateAdapter.restore(checkpoint.appState);
962
+ }
963
+ debugState.currentCursor = checkpoint.cursor;
964
+ return false;
965
+ }
966
+ const lookup = await executeScenarioCommand({
967
+ subCommand: "getFastForwardCheckpoint",
968
+ key: markerId
969
+ });
970
+ if (!lookup.markerExists) {
971
+ throw new Error(`Marker ${markerId} is not available in the current story`);
972
+ }
973
+ const startCheckpointKey = lookup.checkpointKey ?? null;
974
+ if (!startCheckpointKey) {
975
+ if (!appStateAdapter?.restartCurrentStoryFromHead) {
976
+ throw new Error("Restarting the current story from head is not supported by the app state adapter");
977
+ }
978
+ await appStateAdapter.restartCurrentStoryFromHead();
979
+ debugState.checkpoints = {};
980
+ debugState.currentCursor = null;
981
+ return true;
982
+ }
983
+ const startCheckpoint = debugState.checkpoints[startCheckpointKey];
984
+ if (!startCheckpoint) {
985
+ throw new Error(`Local checkpoint ${startCheckpointKey} not found`);
986
+ }
987
+ const restored = await executeScenarioCommand({
988
+ subCommand: "restoreCheckpoint",
989
+ key: startCheckpointKey
990
+ });
991
+ if (!restored) {
992
+ throw new Error(`Failed to restore checkpoint ${startCheckpointKey}`);
993
+ }
994
+ if (appStateAdapter) {
995
+ await appStateAdapter.restore(startCheckpoint.appState);
996
+ }
997
+ debugState.currentCursor = startCheckpoint.cursor;
998
+ return true;
999
+ }
1000
+ function handleMarkerEnter(marker) {
1001
+ const checkpoint = {
1002
+ cursor: {
1003
+ story: marker.story,
1004
+ paragraph: marker.paragraph,
1005
+ markerId: marker.markerId
1006
+ },
1007
+ appState: captureCheckpointAppStateSync()
1008
+ };
1009
+ debugState.currentCursor = checkpoint.cursor;
1010
+ debugState.checkpoints[marker.markerId] = checkpoint;
1011
+ notifyMarkerEnter(checkpoint);
1012
+ if (activeFastForward?.targetMarkerId === marker.markerId) {
1013
+ void settleActiveFastForward();
1014
+ }
1015
+ }
1016
+ async function runFastForwardToMarker(markerId) {
1017
+ if (!appStateAdapter?.enterFastForwardMode || !appStateAdapter?.exitFastForwardMode) {
1018
+ throw new Error("Fast-forward mode is not supported by the app state adapter");
1019
+ }
1020
+ if (activeFastForward !== null || activeWarp !== null) {
1021
+ throw new Error("A debug seek operation is already active");
1022
+ }
1023
+ await appStateAdapter.enterFastForwardMode({
1024
+ onAbort: (error) => {
1025
+ void settleActiveFastForward(error);
1026
+ }
1027
+ });
1028
+ const completion = new Promise((resolve, reject) => {
1029
+ activeFastForward = {
1030
+ targetMarkerId: markerId,
1031
+ resolve,
1032
+ reject
1033
+ };
1034
+ });
1035
+ try {
1036
+ await executeScenarioCommand({ subCommand: "nextLine" });
1037
+ } catch (error) {
1038
+ await settleActiveFastForward(error);
1039
+ }
1040
+ await completion;
1041
+ }
1042
+ async function runWarp(options) {
1043
+ if (!appStateAdapter?.enterFastForwardMode || !appStateAdapter?.exitFastForwardMode) {
1044
+ throw new Error("Warp mode is not supported by the app state adapter");
1045
+ }
1046
+ if (activeFastForward !== null || activeWarp !== null) {
1047
+ throw new Error("A debug seek operation is already active");
1048
+ }
1049
+ const boundary = options.boundary ?? "before";
1050
+ await appStateAdapter.enterFastForwardMode({ warp: true });
1051
+ const completion = new Promise((resolve, reject) => {
1052
+ activeWarp = {
1053
+ targetMarkerId: options.markerId,
1054
+ resolve,
1055
+ reject
1056
+ };
1057
+ });
1058
+ try {
1059
+ await executeScenarioCommand({
1060
+ subCommand: "warp",
1061
+ markerId: options.markerId,
1062
+ boundary
1063
+ });
1064
+ } catch (error) {
1065
+ await settleActiveWarp(error);
1066
+ }
1067
+ await completion;
1068
+ }
1069
+ var debugSessionController = {
1070
+ async restoreCheckpoint(markerId) {
1071
+ debugState.restoring = true;
1072
+ debugState.lastError = null;
1073
+ try {
1074
+ const needsFastForward = await enqueueDebugOperation(async () => prepareJumpStart(markerId));
1075
+ if (needsFastForward) {
1076
+ await runFastForwardToMarker(markerId);
1077
+ }
1078
+ return true;
1079
+ } catch (error) {
1080
+ debugState.lastError = toErrorMessage(error);
1081
+ throw error;
1082
+ } finally {
1083
+ debugState.restoring = false;
1084
+ }
1085
+ },
1086
+ async warp(options) {
1087
+ debugState.restoring = true;
1088
+ debugState.lastError = null;
1089
+ try {
1090
+ const needsWarp = await enqueueDebugOperation(async () => prepareJumpStart(options.markerId));
1091
+ if (needsWarp) {
1092
+ await runWarp(options);
1093
+ }
1094
+ } catch (error) {
1095
+ debugState.lastError = toErrorMessage(error);
1096
+ throw error;
1097
+ } finally {
1098
+ debugState.restoring = false;
1099
+ }
1100
+ },
1101
+ async switchPage(page, params) {
1102
+ return enqueueDebugOperation(async () => {
1103
+ debugState.lastError = null;
1104
+ try {
1105
+ if (!appStateAdapter?.switchPage) {
1106
+ throw new Error("Page switching is not supported by the app state adapter");
1107
+ }
1108
+ await appStateAdapter.switchPage(page, params);
1109
+ } catch (error) {
1110
+ debugState.lastError = toErrorMessage(error);
1111
+ throw error;
1112
+ }
1113
+ });
1114
+ }
1115
+ };
1116
+ function registerAppStateAdapter(adapter) {
1117
+ appStateAdapter = adapter;
1118
+ }
1119
+ function getAppStateAdapter() {
1120
+ return appStateAdapter;
1121
+ }
1122
+ async function startDebugSession(config = {}) {
1123
+ await stopDebugSession();
1124
+ currentConfig = {
1125
+ onMarkerEnter: config.onMarkerEnter ?? (() => {
1126
+ }),
1127
+ onError: config.onError ?? (() => {
1128
+ })
1129
+ };
1130
+ clearLocalRuntimeState();
1131
+ debugState.active = true;
1132
+ disposeMarkerListener = addEventListener("scenariomarkerenter", (event) => {
1133
+ const marker = event;
1134
+ try {
1135
+ handleMarkerEnter(marker);
1136
+ } catch (error) {
1137
+ reportDebugSessionError(error);
1138
+ }
1139
+ });
1140
+ disposeFinishedListener = addEventListener("scenariofinished", () => {
1141
+ debugState.currentCursor = null;
1142
+ if (activeFastForward) {
1143
+ void settleActiveFastForward(
1144
+ new Error(`Marker ${activeFastForward.targetMarkerId} was not reached before the scenario finished`)
1145
+ );
1146
+ }
1147
+ if (activeWarp) {
1148
+ const targetMarkerId = activeWarp.targetMarkerId;
1149
+ void settleActiveWarp(new Error(`Marker ${targetMarkerId} was not reached before the scenario finished`));
1150
+ }
1151
+ });
1152
+ disposeWarpFinishedListener = addEventListener("scenariowarpfinished", () => {
1153
+ const warp = activeWarp;
1154
+ if (!warp) {
1155
+ return;
1156
+ }
1157
+ void enqueueDebugOperation(async () => {
1158
+ try {
1159
+ await syncLocalWarpCheckpoint(warp.targetMarkerId);
1160
+ await settleActiveWarp();
1161
+ } catch (error) {
1162
+ debugState.lastError = toErrorMessage(error);
1163
+ await settleActiveWarp(error);
1164
+ await currentConfig.onError(error);
1165
+ }
1166
+ });
1167
+ });
1168
+ try {
1169
+ await clearRemoteCheckpoints();
1170
+ debugState.currentCursor = await executeScenarioCommand({
1171
+ subCommand: "getExecutionCursor"
1172
+ });
1173
+ } catch (error) {
1174
+ debugState.active = false;
1175
+ clearLocalRuntimeState();
1176
+ disposeMarkerListener?.();
1177
+ disposeFinishedListener?.();
1178
+ disposeWarpFinishedListener?.();
1179
+ disposeMarkerListener = null;
1180
+ disposeFinishedListener = null;
1181
+ disposeWarpFinishedListener = null;
1182
+ await currentConfig.onError(error);
1183
+ throw error;
1184
+ }
1185
+ return debugSessionController;
1186
+ }
1187
+ async function stopDebugSession() {
1188
+ disposeMarkerListener?.();
1189
+ disposeFinishedListener?.();
1190
+ disposeWarpFinishedListener?.();
1191
+ disposeMarkerListener = null;
1192
+ disposeFinishedListener = null;
1193
+ disposeWarpFinishedListener = null;
1194
+ await settleActiveFastForward(new Error("Debug session stopped"));
1195
+ await settleActiveWarp(new Error("Debug session stopped"));
1196
+ if (debugState.active) {
1197
+ await enqueueDebugOperation(async () => {
1198
+ try {
1199
+ await clearRemoteCheckpoints();
1200
+ } catch (error) {
1201
+ debugState.lastError = toErrorMessage(error);
1202
+ await currentConfig.onError(error);
1203
+ }
1204
+ });
1205
+ }
1206
+ debugState.active = false;
1207
+ clearLocalRuntimeState();
1208
+ }
1209
+ function clearDebugSessionRuntimeState() {
1210
+ clearLocalRuntimeState();
1211
+ void settleActiveFastForward(new Error("Debug session runtime state cleared"));
1212
+ void settleActiveWarp(new Error("Debug session runtime state cleared"));
1213
+ if (!debugState.active) {
1214
+ return;
1215
+ }
1216
+ void enqueueDebugOperation(async () => {
1217
+ try {
1218
+ await clearRemoteCheckpoints();
1219
+ } catch (error) {
1220
+ debugState.lastError = toErrorMessage(error);
1221
+ await currentConfig.onError(error);
1222
+ }
1223
+ });
1224
+ }
1225
+ function useDebugSession() {
1226
+ const snapshot = useSnapshot2(debugState);
1227
+ return {
1228
+ ...snapshot,
1229
+ startDebugSession,
1230
+ stopDebugSession,
1231
+ restoreCheckpoint: debugSessionController.restoreCheckpoint,
1232
+ warp: debugSessionController.warp,
1233
+ switchPage: debugSessionController.switchPage
1234
+ };
1235
+ }
1236
+
1237
+ // src/debug/runtime.ts
1238
+ var currentConnection = null;
1239
+ var currentController = null;
1240
+ var bufferedRequestMessages = [];
1241
+ function getRuntimeWebSocket() {
1242
+ return globalThis.WebSocket ?? null;
1243
+ }
1244
+ function isJumpRequestMessage(message) {
1245
+ 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");
1246
+ }
1247
+ function isRouteRequestMessage(message) {
1248
+ 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));
1249
+ }
1250
+ function sendRuntimeDebugMessage(socket, message) {
1251
+ const RuntimeWebSocket = getRuntimeWebSocket();
1252
+ if (!RuntimeWebSocket || socket.readyState !== RuntimeWebSocket.OPEN) {
1253
+ return;
1254
+ }
1255
+ socket.send(JSON.stringify(message));
1256
+ }
1257
+ function clearBufferedRequestMessages() {
1258
+ bufferedRequestMessages = [];
1259
+ }
1260
+ function bufferRequestMessage(message) {
1261
+ bufferedRequestMessages.push(message);
1262
+ }
1263
+ function flushBufferedRequestMessages(socket, sessionId) {
1264
+ const messages = bufferedRequestMessages;
1265
+ bufferedRequestMessages = [];
1266
+ for (const message of messages) {
1267
+ handleRuntimeDebugRequest(socket, sessionId, message);
1268
+ }
1269
+ }
1270
+ function handleRuntimeDebugRequest(socket, sessionId, message) {
1271
+ const controller = currentController;
1272
+ if (!controller) {
1273
+ bufferRequestMessage(message);
1274
+ return;
1275
+ }
1276
+ if (isJumpRequestMessage(message)) {
1277
+ const runJump = message.strategy === "warp" ? controller.warp({
1278
+ markerId: message.markerId,
1279
+ boundary: message.boundary
1280
+ }).then(() => true) : controller.restoreCheckpoint(message.markerId);
1281
+ void runJump.then((restored) => {
1282
+ if (!restored) {
1283
+ sendRuntimeDebugMessage(socket, {
1284
+ type: "jump:error",
1285
+ sessionId,
1286
+ requestId: message.requestId,
1287
+ markerId: message.markerId,
1288
+ error: "Checkpoint not found"
1289
+ });
1290
+ return;
1291
+ }
1292
+ sendRuntimeDebugMessage(socket, {
1293
+ type: "jump:done",
1294
+ sessionId,
1295
+ requestId: message.requestId,
1296
+ markerId: message.markerId
1297
+ });
1298
+ }).catch((error) => {
1299
+ sendRuntimeDebugMessage(socket, {
1300
+ type: "jump:error",
1301
+ sessionId,
1302
+ requestId: message.requestId,
1303
+ markerId: message.markerId,
1304
+ error: error instanceof Error ? error.message : String(error)
1305
+ });
1306
+ });
1307
+ return;
1308
+ }
1309
+ void controller.switchPage(message.page, message.params).then(() => {
1310
+ sendRuntimeDebugMessage(socket, {
1311
+ type: "route:done",
1312
+ sessionId,
1313
+ requestId: message.requestId,
1314
+ page: message.page
1315
+ });
1316
+ }).catch((error) => {
1317
+ sendRuntimeDebugMessage(socket, {
1318
+ type: "route:error",
1319
+ sessionId,
1320
+ requestId: message.requestId,
1321
+ page: message.page,
1322
+ error: error instanceof Error ? error.message : String(error)
1323
+ });
1324
+ });
1325
+ }
1326
+ async function readDebugParams() {
1327
+ const rawParams = await executePluginCommand("system", {
1328
+ subCommand: "getParams"
1329
+ });
1330
+ if (!rawParams) {
1331
+ return {};
1332
+ }
1333
+ try {
1334
+ const parsed = JSON.parse(rawParams);
1335
+ return {
1336
+ debug: parsed.debug === true,
1337
+ debugSessionId: typeof parsed.debugSessionId === "string" ? parsed.debugSessionId : void 0,
1338
+ debugWsUrl: typeof parsed.debugWsUrl === "string" ? parsed.debugWsUrl : void 0
1339
+ };
1340
+ } catch {
1341
+ return {};
1342
+ }
1343
+ }
1344
+ async function stopRuntimeDebugSession() {
1345
+ const connection = currentConnection;
1346
+ currentConnection = null;
1347
+ currentController = null;
1348
+ clearBufferedRequestMessages();
1349
+ const RuntimeWebSocket = getRuntimeWebSocket();
1350
+ await stopDebugSession();
1351
+ if (!connection) {
1352
+ return;
1353
+ }
1354
+ if (RuntimeWebSocket && (connection.socket.readyState === RuntimeWebSocket.OPEN || connection.socket.readyState === RuntimeWebSocket.CONNECTING)) {
1355
+ connection.socket.close();
1356
+ }
1357
+ }
1358
+ async function startRuntimeDebugSession() {
1359
+ await stopRuntimeDebugSession();
1360
+ const params = await readDebugParams();
1361
+ if (params.debug !== true || !params.debugSessionId || !params.debugWsUrl) {
1362
+ return;
1363
+ }
1364
+ const RuntimeWebSocket = getRuntimeWebSocket();
1365
+ if (!RuntimeWebSocket) {
1366
+ return;
1367
+ }
1368
+ const socket = new RuntimeWebSocket(params.debugWsUrl);
1369
+ const sessionId = params.debugSessionId;
1370
+ currentConnection = { sessionId, socket };
1371
+ socket.onopen = () => {
1372
+ if (currentConnection?.socket !== socket) {
1373
+ socket.close();
1374
+ return;
1375
+ }
1376
+ void startDebugSession({
1377
+ onMarkerEnter(checkpoint) {
1378
+ if (!checkpoint.cursor.markerId) {
1379
+ return;
1380
+ }
1381
+ sendRuntimeDebugMessage(socket, {
1382
+ type: "marker:enter",
1383
+ sessionId,
1384
+ markerId: checkpoint.cursor.markerId,
1385
+ story: checkpoint.cursor.story,
1386
+ paragraph: checkpoint.cursor.paragraph
1387
+ });
1388
+ },
1389
+ onError(error) {
1390
+ console.error("[debug] runtime debug session error:", error);
1391
+ }
1392
+ }).then((controller) => {
1393
+ currentController = controller;
1394
+ flushBufferedRequestMessages(socket, sessionId);
1395
+ }).catch((error) => {
1396
+ console.error("[debug] failed to start runtime debug session:", error);
1397
+ socket.close();
1398
+ });
1399
+ };
1400
+ socket.onclose = () => {
1401
+ if (currentConnection?.socket !== socket) {
1402
+ return;
1403
+ }
1404
+ currentConnection = null;
1405
+ currentController = null;
1406
+ clearBufferedRequestMessages();
1407
+ void stopDebugSession();
1408
+ };
1409
+ socket.onerror = (event) => {
1410
+ if (currentConnection?.socket !== socket) {
1411
+ return;
1412
+ }
1413
+ console.error("[debug] runtime websocket error:", event);
1414
+ };
1415
+ socket.onmessage = (event) => {
1416
+ if (currentConnection?.socket !== socket) {
1417
+ return;
1418
+ }
1419
+ try {
1420
+ const message = JSON.parse(String(event.data));
1421
+ if (message.sessionId && message.sessionId !== sessionId) {
1422
+ return;
1423
+ }
1424
+ if (isJumpRequestMessage(message)) {
1425
+ handleRuntimeDebugRequest(socket, sessionId, message);
1426
+ return;
1427
+ }
1428
+ if (isRouteRequestMessage(message)) {
1429
+ handleRuntimeDebugRequest(socket, sessionId, message);
1430
+ return;
1431
+ }
1432
+ if (message.type && message.type !== "session:init") {
1433
+ console.debug("[debug] runtime received message:", message);
1434
+ }
1435
+ } catch (error) {
1436
+ console.error("[debug] failed to parse runtime debug message:", error);
1437
+ }
1438
+ };
1439
+ }
1440
+
778
1441
  // src/hooks/useFadeInOut.ts
779
1442
  function useFadeInOut(inTime, _keepTime, _outTime, pause = false, onFinished) {
780
1443
  const [style, api] = (0, spring_exports.useSpring)(
@@ -877,11 +1540,13 @@ async function terminateCurrentScenario() {
877
1540
  await executePluginCommand("scenario", {
878
1541
  subCommand: "terminateStory"
879
1542
  });
1543
+ clearDebugSessionRuntimeState();
880
1544
  } catch (error) {
881
1545
  console.error("Failed to terminate scenario:", error);
882
1546
  }
883
1547
  }
884
1548
  async function startScenarioSession(session, config) {
1549
+ clearDebugSessionRuntimeState();
885
1550
  for (const story of config.stories) {
886
1551
  if (!isCurrentScenarioSession(session)) return;
887
1552
  try {
@@ -1048,18 +1713,30 @@ function useSoundEffect(src) {
1048
1713
 
1049
1714
  // src/hooks/useStage.ts
1050
1715
  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";
1716
+ import { proxy as proxy3, useSnapshot as useSnapshot3 } from "valtio";
1052
1717
  var SKIP_INTERVAL_MS = 80;
1718
+ var DEFAULT_FAST_FORWARD_STEP_LIMIT = 1e4;
1719
+ var DEFAULT_FAST_FORWARD_TIME_LIMIT_MS = 3e3;
1053
1720
  var autoRuntime = {
1054
1721
  defaultTailMs: 0
1055
1722
  };
1056
- var skipState = proxy2({ active: false });
1057
- var autoState = proxy2({ active: false });
1723
+ var skipState = proxy3({ active: false });
1724
+ var autoState = proxy3({ active: false });
1725
+ var fastForwardState = proxy3({ active: false });
1726
+ var warpState = proxy3({ active: false });
1058
1727
  function useIsSkipping() {
1059
- return useSnapshot2(skipState).active;
1728
+ return useSnapshot3(skipState).active;
1060
1729
  }
1061
1730
  function useIsAutoing() {
1062
- return useSnapshot2(autoState).active;
1731
+ return useSnapshot3(autoState).active;
1732
+ }
1733
+ function useIsSeeking() {
1734
+ return useSnapshot3(fastForwardState).active;
1735
+ }
1736
+ function getSeekingType() {
1737
+ if (warpState.active) return "warp";
1738
+ if (fastForwardState.active) return "fast-forward";
1739
+ return null;
1063
1740
  }
1064
1741
  function setDefaultAutoTailMs(ms) {
1065
1742
  if (!Number.isFinite(ms)) {
@@ -1167,6 +1844,101 @@ function createStage() {
1167
1844
  let _autoBarrierSeq = 0;
1168
1845
  let _autoTicketSeq = 0;
1169
1846
  const _pendingAutoTickets = /* @__PURE__ */ new Map();
1847
+ let _fastForwardRuntime = null;
1848
+ let _managedAdvanceInFlight = false;
1849
+ let _managedAdvancePending = false;
1850
+ function resetManagedAdvanceState() {
1851
+ _managedAdvanceInFlight = false;
1852
+ _managedAdvancePending = false;
1853
+ }
1854
+ function clearFastForwardRuntime() {
1855
+ _fastForwardRuntime = null;
1856
+ resetManagedAdvanceState();
1857
+ }
1858
+ function stopFastForwardInternal() {
1859
+ fastForwardState.active = false;
1860
+ warpState.active = false;
1861
+ clearFastForwardRuntime();
1862
+ }
1863
+ function abortFastForward(error) {
1864
+ const runtime = _fastForwardRuntime;
1865
+ stopFastForwardInternal();
1866
+ if (!runtime?.onAbort) {
1867
+ return;
1868
+ }
1869
+ try {
1870
+ runtime.onAbort(error);
1871
+ } catch (abortError) {
1872
+ console.error("Error in fast-forward abort callback:", abortError);
1873
+ }
1874
+ }
1875
+ function canAdvanceFastForward() {
1876
+ if (!fastForwardState.active || _fastForwardRuntime === null) {
1877
+ return true;
1878
+ }
1879
+ const elapsedMs = Date.now() - _fastForwardRuntime.startedAt;
1880
+ if (elapsedMs > _fastForwardRuntime.timeLimitMs) {
1881
+ abortFastForward(new Error(`Fast forward exceeded time limit (${_fastForwardRuntime.timeLimitMs}ms)`));
1882
+ return false;
1883
+ }
1884
+ _fastForwardRuntime.stepCount += 1;
1885
+ if (_fastForwardRuntime.stepCount > _fastForwardRuntime.stepLimit) {
1886
+ abortFastForward(new Error(`Fast forward exceeded step limit (${_fastForwardRuntime.stepLimit})`));
1887
+ return false;
1888
+ }
1889
+ return true;
1890
+ }
1891
+ function normalizeAdvanceError(error) {
1892
+ return error instanceof Error ? error : new Error(String(error));
1893
+ }
1894
+ function isManagedAdvanceActive() {
1895
+ return fastForwardState.active || skipState.active || autoState.active;
1896
+ }
1897
+ function canScheduleManagedAdvance() {
1898
+ if (fastForwardState.active) {
1899
+ return canAdvanceFastForward();
1900
+ }
1901
+ return true;
1902
+ }
1903
+ function handleManagedAdvanceError(error) {
1904
+ console.error("Failed to advance scenario:", error);
1905
+ if (fastForwardState.active) {
1906
+ abortFastForward(normalizeAdvanceError(error));
1907
+ }
1908
+ }
1909
+ function scheduleManagedAdvance() {
1910
+ if (!isManagedAdvanceActive()) {
1911
+ _managedAdvancePending = false;
1912
+ return;
1913
+ }
1914
+ if (_managedAdvanceInFlight) {
1915
+ _managedAdvancePending = true;
1916
+ return;
1917
+ }
1918
+ if (!canScheduleManagedAdvance()) {
1919
+ _managedAdvancePending = false;
1920
+ return;
1921
+ }
1922
+ _managedAdvanceInFlight = true;
1923
+ _managedAdvancePending = false;
1924
+ void Promise.resolve(nextLine()).catch(handleManagedAdvanceError).finally(() => {
1925
+ _managedAdvanceInFlight = false;
1926
+ if (!isManagedAdvanceActive()) {
1927
+ _managedAdvancePending = false;
1928
+ return;
1929
+ }
1930
+ if (_managedAdvancePending) {
1931
+ scheduleManagedAdvance();
1932
+ }
1933
+ });
1934
+ }
1935
+ function requestNextLine() {
1936
+ if (isManagedAdvanceActive()) {
1937
+ scheduleManagedAdvance();
1938
+ return;
1939
+ }
1940
+ void Promise.resolve(nextLine()).catch(handleManagedAdvanceError);
1941
+ }
1170
1942
  function clearAutoResumeTimer() {
1171
1943
  if (_autoResumeTimer !== null) {
1172
1944
  clearTimeout(_autoResumeTimer);
@@ -1201,7 +1973,7 @@ function createStage() {
1201
1973
  if (!autoState.active) return;
1202
1974
  if (_autoBarrier === null || _autoBarrier.id !== barrierId) return;
1203
1975
  _autoBarrier = null;
1204
- void nextLine();
1976
+ requestNextLine();
1205
1977
  }, safeDelayMs);
1206
1978
  }
1207
1979
  function trySettleAutoBarrier(barrier) {
@@ -1337,7 +2109,7 @@ function createStage() {
1337
2109
  return;
1338
2110
  }
1339
2111
  if (_autoBarrier === null) {
1340
- void nextLine();
2112
+ requestNextLine();
1341
2113
  }
1342
2114
  }, autoRuntime.defaultTailMs);
1343
2115
  }
@@ -1345,11 +2117,12 @@ function createStage() {
1345
2117
  if (_skipTimer !== null) clearTimeout(_skipTimer);
1346
2118
  _skipTimer = setTimeout(() => {
1347
2119
  _skipTimer = null;
1348
- if (skipState.active) nextLine();
2120
+ if (skipState.active) requestNextLine();
1349
2121
  }, SKIP_INTERVAL_MS);
1350
2122
  }
1351
2123
  function startSkip() {
1352
2124
  if (skipState.active) return;
2125
+ if (fastForwardState.active) return;
1353
2126
  if (isAnyBlockerActive()) return;
1354
2127
  skipState.active = true;
1355
2128
  invokeSkipCallbacks();
@@ -1361,7 +2134,7 @@ function createStage() {
1361
2134
  if (_skipTimer !== null) {
1362
2135
  clearTimeout(_skipTimer);
1363
2136
  _skipTimer = null;
1364
- nextLine();
2137
+ requestNextLine();
1365
2138
  }
1366
2139
  }
1367
2140
  function isSkipping() {
@@ -1369,6 +2142,7 @@ function createStage() {
1369
2142
  }
1370
2143
  function startAuto() {
1371
2144
  if (autoState.active) return;
2145
+ if (fastForwardState.active) return;
1372
2146
  if (isAnyAutoBlockerActive()) return;
1373
2147
  autoState.active = true;
1374
2148
  tryInterrupt();
@@ -1386,12 +2160,58 @@ function createStage() {
1386
2160
  function isAutoing() {
1387
2161
  return autoState.active;
1388
2162
  }
2163
+ function startFastForward(options) {
2164
+ if (fastForwardState.active) return;
2165
+ resetRuntimeState();
2166
+ _fastForwardRuntime = {
2167
+ stepLimit: Math.max(1, options?.stepLimit ?? DEFAULT_FAST_FORWARD_STEP_LIMIT),
2168
+ timeLimitMs: Math.max(1, options?.timeLimitMs ?? DEFAULT_FAST_FORWARD_TIME_LIMIT_MS),
2169
+ stepCount: 0,
2170
+ startedAt: Date.now(),
2171
+ onAbort: options?.onAbort,
2172
+ warp: options?.warp === true
2173
+ };
2174
+ fastForwardState.active = true;
2175
+ warpState.active = _fastForwardRuntime.warp;
2176
+ }
2177
+ function stopFastForward() {
2178
+ stopFastForwardInternal();
2179
+ }
2180
+ function isFastForwarding() {
2181
+ return fastForwardState.active;
2182
+ }
2183
+ function resetRuntimeState() {
2184
+ skipState.active = false;
2185
+ autoState.active = false;
2186
+ fastForwardState.active = false;
2187
+ warpState.active = false;
2188
+ clearFastForwardRuntime();
2189
+ if (_skipTimer !== null) {
2190
+ clearTimeout(_skipTimer);
2191
+ _skipTimer = null;
2192
+ }
2193
+ if (_autoTimer !== null) {
2194
+ clearTimeout(_autoTimer);
2195
+ _autoTimer = null;
2196
+ }
2197
+ clearAutoBarrier();
2198
+ clearPendingAutoTickets();
2199
+ }
1389
2200
  function buildControl() {
1390
- let handled = false;
2201
+ const warpActive = warpState.active;
2202
+ let handled = warpActive;
1391
2203
  let unskippableFlag = false;
2204
+ const inertRecordId = "__warp__";
1392
2205
  const control = {
1393
2206
  setWaiting(time, skippable) {
1394
2207
  handled = true;
2208
+ if (warpActive) {
2209
+ return;
2210
+ }
2211
+ if (fastForwardState.active) {
2212
+ requestNextLine();
2213
+ return;
2214
+ }
1395
2215
  if (skipState.active) {
1396
2216
  invokeSkipCallbacks();
1397
2217
  scheduleSkipNextLine();
@@ -1408,6 +2228,13 @@ function createStage() {
1408
2228
  },
1409
2229
  hold() {
1410
2230
  handled = true;
2231
+ if (warpActive) {
2232
+ return;
2233
+ }
2234
+ if (fastForwardState.active) {
2235
+ requestNextLine();
2236
+ return;
2237
+ }
1411
2238
  if (skipState.active && !unskippableFlag) {
1412
2239
  if (isAnyBlockerActive()) {
1413
2240
  stopSkip();
@@ -1426,12 +2253,21 @@ function createStage() {
1426
2253
  },
1427
2254
  nextLine() {
1428
2255
  handled = true;
1429
- void nextLine();
2256
+ if (warpActive) {
2257
+ return;
2258
+ }
2259
+ requestNextLine();
1430
2260
  },
1431
2261
  unskippable() {
2262
+ if (warpActive) {
2263
+ return;
2264
+ }
1432
2265
  unskippableFlag = true;
1433
2266
  },
1434
2267
  record(meta) {
2268
+ if (warpActive) {
2269
+ return inertRecordId;
2270
+ }
1435
2271
  const result = executePluginCommand("scenario", {
1436
2272
  subCommand: "record",
1437
2273
  meta
@@ -1470,19 +2306,19 @@ function createStage() {
1470
2306
  cmd = transformCommand(e);
1471
2307
  } catch (err) {
1472
2308
  console.error("Failed to transform scenario command:", err);
1473
- void nextLine();
2309
+ requestNextLine();
1474
2310
  return;
1475
2311
  }
1476
2312
  const handler = commandHandlers.get(cmd.command);
1477
2313
  if (!handler) {
1478
2314
  console.warn(`[Stage] No handler registered for command "${cmd.command}". Auto-advancing.`);
1479
- void nextLine();
2315
+ requestNextLine();
1480
2316
  return;
1481
2317
  }
1482
2318
  const { control, wasHandled } = buildControl();
1483
2319
  handler(cmd, control);
1484
2320
  if (!wasHandled()) {
1485
- void nextLine();
2321
+ requestNextLine();
1486
2322
  }
1487
2323
  }
1488
2324
  function dispatchTextLine(e) {
@@ -1491,7 +2327,7 @@ function createStage() {
1491
2327
  handler(e, control);
1492
2328
  }
1493
2329
  if (!wasHandled()) {
1494
- void nextLine();
2330
+ requestNextLine();
1495
2331
  }
1496
2332
  }
1497
2333
  function invokeSkipCallbacks() {
@@ -1539,6 +2375,10 @@ function createStage() {
1539
2375
  startAuto,
1540
2376
  stopAuto,
1541
2377
  isAutoing,
2378
+ startFastForward,
2379
+ stopFastForward,
2380
+ isFastForwarding,
2381
+ resetRuntimeState,
1542
2382
  bindEvents
1543
2383
  };
1544
2384
  }
@@ -1611,23 +2451,33 @@ export {
1611
2451
  createStaticNavigation,
1612
2452
  executeNodeCommand,
1613
2453
  executePluginCommand,
2454
+ fastForwardState,
2455
+ getAppStateAdapter,
1614
2456
  getNavigator,
2457
+ getSeekingType,
1615
2458
  getStageSize,
1616
2459
  globalEventListeners,
1617
2460
  globalRequestAnimationFrameListeners,
1618
2461
  moyu_exports as moyu,
1619
2462
  nextLine,
2463
+ registerAppStateAdapter,
1620
2464
  setDefaultAutoTailMs,
1621
2465
  setWaiting,
1622
2466
  skipState,
2467
+ startDebugSession,
2468
+ startRuntimeDebugSession,
2469
+ stopDebugSession,
2470
+ stopRuntimeDebugSession,
1623
2471
  useAutoBlocker,
1624
2472
  useAutoTicket,
1625
2473
  useBeforeHandleCommandCallback,
2474
+ useDebugSession,
1626
2475
  useFadeIn,
1627
2476
  useFadeInOut,
1628
2477
  useFadeOut,
1629
2478
  useInterruptCallback,
1630
2479
  useIsAutoing,
2480
+ useIsSeeking,
1631
2481
  useIsSkipping,
1632
2482
  useNavigation,
1633
2483
  useNavigationParams,
@@ -1636,5 +2486,6 @@ export {
1636
2486
  useSkipBlocker,
1637
2487
  useSkipCallback,
1638
2488
  useSoundEffect,
1639
- useStageContext
2489
+ useStageContext,
2490
+ warpState
1640
2491
  };