@momoyu-ink/kit 0.7.0 → 0.8.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.
@@ -134,6 +134,10 @@ type JsonValue = number | string | boolean | Array<JsonValue> | {
134
134
  type ScenarioCommand = {
135
135
  subCommand: 'addStory';
136
136
  name: string;
137
+ } | {
138
+ subCommand: 'replaceStoryData';
139
+ name: string;
140
+ content: string;
137
141
  } | {
138
142
  subCommand: 'removeStory';
139
143
  name: string;
@@ -134,6 +134,10 @@ type JsonValue = number | string | boolean | Array<JsonValue> | {
134
134
  type ScenarioCommand = {
135
135
  subCommand: 'addStory';
136
136
  name: string;
137
+ } | {
138
+ subCommand: 'replaceStoryData';
139
+ name: string;
140
+ content: string;
137
141
  } | {
138
142
  subCommand: 'removeStory';
139
143
  name: string;
@@ -1,4 +1,4 @@
1
1
  export { Fragment, jsxDEV } from 'react/jsx-dev-runtime';
2
- export { J as JSX } from './jsx-dev-runtime-CKclO27C.mjs';
2
+ export { J as JSX } from './jsx-dev-runtime-BCxm528Z.mjs';
3
3
  import 'react/jsx-runtime';
4
4
  import 'react';
@@ -1,4 +1,4 @@
1
1
  export { Fragment, jsxDEV } from 'react/jsx-dev-runtime';
2
- export { J as JSX } from './jsx-dev-runtime-CKclO27C.js';
2
+ export { J as JSX } from './jsx-dev-runtime-BCxm528Z.js';
3
3
  import 'react/jsx-runtime';
4
4
  import 'react';
@@ -1,4 +1,4 @@
1
1
  export { Fragment, jsx, jsxs } from 'react/jsx-runtime';
2
- export { J as JSX } from './jsx-dev-runtime-CKclO27C.mjs';
2
+ export { J as JSX } from './jsx-dev-runtime-BCxm528Z.mjs';
3
3
  import 'react/jsx-dev-runtime';
4
4
  import 'react';
@@ -1,4 +1,4 @@
1
1
  export { Fragment, jsx, jsxs } from 'react/jsx-runtime';
2
- export { J as JSX } from './jsx-dev-runtime-CKclO27C.js';
2
+ export { J as JSX } from './jsx-dev-runtime-BCxm528Z.js';
3
3
  import 'react/jsx-dev-runtime';
4
4
  import 'react';
package/dist/lib.cjs CHANGED
@@ -1214,9 +1214,44 @@ async function settleActiveWarp(error) {
1214
1214
  activeWarp = null;
1215
1215
  }, error);
1216
1216
  }
1217
+ async function clearLocalDebugSessionRuntimeState() {
1218
+ await settleActiveFastForward(new Error("Debug session runtime state cleared"));
1219
+ await settleActiveWarp(new Error("Debug session runtime state cleared"));
1220
+ clearLocalRuntimeState();
1221
+ }
1217
1222
  async function clearRemoteCheckpoints() {
1218
1223
  await executeScenarioCommand({ subCommand: "clearCheckpoints" });
1219
1224
  }
1225
+ function pruneLocalStoryCheckpoints(story, preserved) {
1226
+ debugState.checkpoints = Object.fromEntries(
1227
+ Object.entries(debugState.checkpoints).filter(([key, checkpoint]) => {
1228
+ if (checkpoint.cursor.story !== story) {
1229
+ return true;
1230
+ }
1231
+ return preserved.has(key);
1232
+ })
1233
+ );
1234
+ }
1235
+ async function syncCurrentExecutionCursor(fallbackStory = null) {
1236
+ debugState.currentCursor = await executeScenarioCommand({
1237
+ subCommand: "getExecutionCursor"
1238
+ });
1239
+ debugState.currentStory = debugState.currentCursor?.story ?? fallbackStory;
1240
+ }
1241
+ async function executeRestartBoundary(boundary, story) {
1242
+ if (boundary.kind === "checkpoint") {
1243
+ await debugSessionController.restoreCheckpoint(boundary.checkpointKey, { story });
1244
+ return;
1245
+ }
1246
+ if (!appStateAdapter?.restartStoryFromHead) {
1247
+ throw new Error("Restarting a story from an explicit entry is not supported by the app state adapter");
1248
+ }
1249
+ await appStateAdapter.restartStoryFromHead({
1250
+ story: boundary.story,
1251
+ entry: boundary.kind === "restart-story" ? boundary.entry : boundary.paragraph
1252
+ });
1253
+ await syncCurrentExecutionCursor(boundary.story);
1254
+ }
1220
1255
  async function syncLocalWarpCheckpoint(targetMarkerId) {
1221
1256
  const cursor = await executeScenarioCommand({
1222
1257
  subCommand: "getExecutionCursor"
@@ -1276,15 +1311,17 @@ async function prepareJumpStart(markerId, options) {
1276
1311
  subCommand: "restoreCheckpoint",
1277
1312
  key: markerId
1278
1313
  });
1279
- if (!restored2) {
1280
- throw new Error(`Failed to restore checkpoint ${markerId}`);
1281
- }
1282
- if (appStateAdapter) {
1283
- await appStateAdapter.restore(checkpoint.appState);
1314
+ if (restored2) {
1315
+ if (appStateAdapter) {
1316
+ await appStateAdapter.restore(checkpoint.appState);
1317
+ }
1318
+ debugState.currentCursor = checkpoint.cursor;
1319
+ debugState.currentStory = checkpoint.cursor.story;
1320
+ return false;
1284
1321
  }
1285
- debugState.currentCursor = checkpoint.cursor;
1286
- debugState.currentStory = checkpoint.cursor.story;
1287
- return false;
1322
+ debugState.checkpoints = Object.fromEntries(
1323
+ Object.entries(debugState.checkpoints).filter(([key]) => key !== markerId)
1324
+ );
1288
1325
  }
1289
1326
  const lookup = await executeScenarioCommand({
1290
1327
  subCommand: "getFastForwardCheckpoint",
@@ -1304,7 +1341,8 @@ async function prepareJumpStart(markerId, options) {
1304
1341
  }
1305
1342
  const startCheckpoint = debugState.checkpoints[startCheckpointKey];
1306
1343
  if (!startCheckpoint) {
1307
- throw new Error(`Local checkpoint ${startCheckpointKey} not found`);
1344
+ await restartJumpStoryFromHead(targetStory, currentStory);
1345
+ return true;
1308
1346
  }
1309
1347
  const restored = await executeScenarioCommand({
1310
1348
  subCommand: "restoreCheckpoint",
@@ -1447,6 +1485,46 @@ var debugSessionController = {
1447
1485
  throw error;
1448
1486
  }
1449
1487
  });
1488
+ },
1489
+ async replaceStory(options) {
1490
+ debugState.restoring = true;
1491
+ debugState.lastError = null;
1492
+ try {
1493
+ const outcome = await enqueueDebugOperation(
1494
+ async () => executeScenarioCommand({
1495
+ subCommand: "replaceStoryData",
1496
+ name: options.story,
1497
+ content: options.content
1498
+ })
1499
+ );
1500
+ if (outcome.plan.mode === "invalidate-only") {
1501
+ pruneLocalStoryCheckpoints(options.story, /* @__PURE__ */ new Set());
1502
+ }
1503
+ if (outcome.plan.mode === "reposition") {
1504
+ if (!outcome.plan.boundary) {
1505
+ throw new Error("Story replace outcome is missing an executable boundary");
1506
+ }
1507
+ const seekMode = options.seekMode ?? "warp";
1508
+ const replaceTargetMarkerId = options.targetMarkerId ?? outcome.plan.targetMarkerId;
1509
+ await executeRestartBoundary(outcome.plan.boundary, options.story);
1510
+ if (replaceTargetMarkerId && !(outcome.plan.boundary.kind === "checkpoint" && outcome.plan.boundary.checkpointKey === replaceTargetMarkerId)) {
1511
+ if (seekMode === "warp") {
1512
+ await runWarp({
1513
+ markerId: replaceTargetMarkerId,
1514
+ boundary: "after"
1515
+ });
1516
+ } else if (seekMode === "fast-forward") {
1517
+ await runFastForwardToMarker(replaceTargetMarkerId);
1518
+ }
1519
+ }
1520
+ }
1521
+ return outcome;
1522
+ } catch (error) {
1523
+ debugState.lastError = toErrorMessage(error);
1524
+ throw error;
1525
+ } finally {
1526
+ debugState.restoring = false;
1527
+ }
1450
1528
  }
1451
1529
  };
1452
1530
  function registerAppStateAdapter(adapter) {
@@ -1543,22 +1621,6 @@ async function stopDebugSession() {
1543
1621
  debugState.active = false;
1544
1622
  clearLocalRuntimeState();
1545
1623
  }
1546
- function clearDebugSessionRuntimeState() {
1547
- clearLocalRuntimeState();
1548
- void settleActiveFastForward(new Error("Debug session runtime state cleared"));
1549
- void settleActiveWarp(new Error("Debug session runtime state cleared"));
1550
- if (!debugState.active) {
1551
- return;
1552
- }
1553
- void enqueueDebugOperation(async () => {
1554
- try {
1555
- await clearRemoteCheckpoints();
1556
- } catch (error) {
1557
- debugState.lastError = toErrorMessage(error);
1558
- await currentConfig.onError(error);
1559
- }
1560
- });
1561
- }
1562
1624
  function useDebugSession() {
1563
1625
  const snapshot = (0, import_valtio2.useSnapshot)(debugState);
1564
1626
  return {
@@ -1567,7 +1629,8 @@ function useDebugSession() {
1567
1629
  stopDebugSession,
1568
1630
  restoreCheckpoint: debugSessionController.restoreCheckpoint,
1569
1631
  warp: debugSessionController.warp,
1570
- switchRoute: debugSessionController.switchRoute
1632
+ switchRoute: debugSessionController.switchRoute,
1633
+ replaceStory: debugSessionController.replaceStory
1571
1634
  };
1572
1635
  }
1573
1636
 
@@ -1657,6 +1720,9 @@ function isJumpRequestMessage(message) {
1657
1720
  function isRouteRequestMessage(message) {
1658
1721
  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));
1659
1722
  }
1723
+ function isStoryReplaceMessage(message) {
1724
+ return message.type === "story:replace" && typeof message.sessionId === "string" && typeof message.requestId === "number" && typeof message.story === "string" && typeof message.content === "string" && (message.seekMode === void 0 || message.seekMode === "off" || message.seekMode === "fast-forward" || message.seekMode === "warp") && (message.targetMarkerId === void 0 || typeof message.targetMarkerId === "string");
1725
+ }
1660
1726
  function isUiReplaceMessage(message) {
1661
1727
  return message.type === "ui:replace" && typeof message.sessionId === "string";
1662
1728
  }
@@ -1743,6 +1809,35 @@ function handleRuntimeDebugRequest(socket, sessionId, message) {
1743
1809
  });
1744
1810
  return;
1745
1811
  }
1812
+ if (isStoryReplaceMessage(message)) {
1813
+ setRuntimeDebugVariableEmissionSuspended(true);
1814
+ void controller.replaceStory({
1815
+ story: message.story,
1816
+ content: message.content,
1817
+ seekMode: message.seekMode,
1818
+ targetMarkerId: message.targetMarkerId
1819
+ }).then(async () => {
1820
+ sendRuntimeDebugMessage(socket, {
1821
+ type: "story:replace:done",
1822
+ sessionId,
1823
+ requestId: message.requestId,
1824
+ story: message.story
1825
+ });
1826
+ await syncRuntimeDebugVariablesSnapshotSafe("story-replace-done");
1827
+ }).catch(async (error) => {
1828
+ sendRuntimeDebugMessage(socket, {
1829
+ type: "story:replace:error",
1830
+ sessionId,
1831
+ requestId: message.requestId,
1832
+ story: message.story,
1833
+ error: error instanceof Error ? error.message : String(error)
1834
+ });
1835
+ await syncRuntimeDebugVariablesSnapshotSafe("story-replace-error");
1836
+ }).finally(() => {
1837
+ setRuntimeDebugVariableEmissionSuspended(false);
1838
+ });
1839
+ return;
1840
+ }
1746
1841
  setRuntimeDebugVariableEmissionSuspended(true);
1747
1842
  void controller.switchRoute(message.page, message.params).then(async () => {
1748
1843
  sendRuntimeDebugMessage(socket, {
@@ -1880,6 +1975,10 @@ async function startRuntimeDebugSession() {
1880
1975
  handleRuntimeDebugRequest(socket, sessionId, message);
1881
1976
  return;
1882
1977
  }
1978
+ if (isStoryReplaceMessage(message)) {
1979
+ handleRuntimeDebugRequest(socket, sessionId, message);
1980
+ return;
1981
+ }
1883
1982
  if (isUiReplaceMessage(message)) {
1884
1983
  handleRuntimeDebugRequest(socket, sessionId, message);
1885
1984
  return;
@@ -1995,13 +2094,13 @@ async function terminateCurrentScenario() {
1995
2094
  await executePluginCommand("scenario", {
1996
2095
  subCommand: "terminateStory"
1997
2096
  });
1998
- clearDebugSessionRuntimeState();
2097
+ await clearLocalDebugSessionRuntimeState();
1999
2098
  } catch (error) {
2000
2099
  console.error("Failed to terminate scenario:", error);
2001
2100
  }
2002
2101
  }
2003
2102
  async function startScenarioSession(session, config) {
2004
- clearDebugSessionRuntimeState();
2103
+ await clearLocalDebugSessionRuntimeState();
2005
2104
  for (const story of config.stories) {
2006
2105
  if (!isCurrentScenarioSession(session)) return;
2007
2106
  try {
package/dist/lib.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { ReactElement, Component, ForwardRefExoticComponent, CSSProperties, FC, ComponentType } from 'react';
3
- import { M as MouseEvent, a as MoyuEvent, C as Command, b as MaybePromise, J as JSX, c as JsonValue } from './jsx-dev-runtime-CKclO27C.mjs';
4
- export { A as AudioCommand, d as AudioSettings, B as BubbleEvent, D as DetailedMoyuProps, E as EffectParams, G as GamepadCommand, K as KeyboardEvent, e as KeyboardEventKind, f as MakeNullOptional, g as MouseEventKind, h as MoyuAnimationAttributes, i as MoyuBackdropAttributes, j as MoyuClipAttributes, k as MoyuContainerAttributes, l as MoyuEventHandler, m as MoyuFilterAttributes, n as MoyuListenerAttributes, o as MoyuNodeAttributes, p as MoyuSpriteAttributes, q as MoyuTextAttributes, r as MoyuVideoAttributes, N as Node, S as ScenarioCommand, s as SystemCommand, T as TextCommand, t as TouchEvent, u as TouchEventKind, v as Tuple2, w as Tuple3, x as Tuple4, W as WindowState, y as createBubbleEvent, z as getStageSize } from './jsx-dev-runtime-CKclO27C.mjs';
3
+ import { M as MouseEvent, a as MoyuEvent, C as Command, b as MaybePromise, J as JSX, c as JsonValue } from './jsx-dev-runtime-BCxm528Z.mjs';
4
+ export { A as AudioCommand, d as AudioSettings, B as BubbleEvent, D as DetailedMoyuProps, E as EffectParams, G as GamepadCommand, K as KeyboardEvent, e as KeyboardEventKind, f as MakeNullOptional, g as MouseEventKind, h as MoyuAnimationAttributes, i as MoyuBackdropAttributes, j as MoyuClipAttributes, k as MoyuContainerAttributes, l as MoyuEventHandler, m as MoyuFilterAttributes, n as MoyuListenerAttributes, o as MoyuNodeAttributes, p as MoyuSpriteAttributes, q as MoyuTextAttributes, r as MoyuVideoAttributes, N as Node, S as ScenarioCommand, s as SystemCommand, T as TextCommand, t as TouchEvent, u as TouchEventKind, v as Tuple2, w as Tuple3, x as Tuple4, W as WindowState, y as createBubbleEvent, z as getStageSize } from './jsx-dev-runtime-BCxm528Z.mjs';
5
5
  import { FluidValue } from '@react-spring/shared';
6
6
  import { ElementType, AssignableKeys, ComponentPropsWithRef } from '@react-spring/types';
7
7
  import { Snapshot } from 'valtio';
@@ -522,6 +522,34 @@ type ExecutionCursor = {
522
522
  markerId?: string;
523
523
  };
524
524
 
525
+ type ExecutableRestartBoundary = {
526
+ kind: 'checkpoint';
527
+ checkpointKey: string;
528
+ } | {
529
+ kind: 'restart-story';
530
+ story: string;
531
+ entry: string;
532
+ } | {
533
+ kind: 'restart-paragraph';
534
+ story: string;
535
+ paragraph: string;
536
+ };
537
+
538
+ type StoryReplaceMode = 'noop' | 'reposition' | 'invalidate-only';
539
+
540
+ type StoryReplaceExecutionPlan = {
541
+ mode: StoryReplaceMode;
542
+ boundary?: ExecutableRestartBoundary;
543
+ targetMarkerId?: string;
544
+ changedControlFlow: boolean;
545
+ };
546
+
547
+ type StoryReplaceOutcome = {
548
+ story: string;
549
+ currentStoryAffected: boolean;
550
+ plan: StoryReplaceExecutionPlan;
551
+ };
552
+
525
553
  /** Flow control interface passed to every handler. */
526
554
  interface GameControl {
527
555
  /** Timed wait — blocks auto-advance. Engine fires scenariowaitingcancelled on timeout or skip. */
@@ -695,6 +723,12 @@ interface DebugSessionController {
695
723
  restoreCheckpoint(markerId: string, options?: DebugJumpTargetOptions): Promise<boolean>;
696
724
  warp(options: DebugWarpOptions): Promise<void>;
697
725
  switchRoute(routeName: string, params?: Record<string, unknown>): Promise<void>;
726
+ replaceStory(options: {
727
+ story: string;
728
+ content: string;
729
+ seekMode?: 'off' | 'fast-forward' | 'warp';
730
+ targetMarkerId?: string;
731
+ }): Promise<StoryReplaceOutcome>;
698
732
  }
699
733
  interface DebugStoryStartOptions {
700
734
  story: string;
@@ -718,6 +752,12 @@ declare function useDebugSession(): {
718
752
  restoreCheckpoint: (markerId: string, options?: DebugJumpTargetOptions) => Promise<boolean>;
719
753
  warp: (options: DebugWarpOptions) => Promise<void>;
720
754
  switchRoute: (routeName: string, params?: Record<string, unknown>) => Promise<void>;
755
+ replaceStory: (options: {
756
+ story: string;
757
+ content: string;
758
+ seekMode?: "off" | "fast-forward" | "warp";
759
+ targetMarkerId?: string;
760
+ }) => Promise<StoryReplaceOutcome>;
721
761
  active: boolean;
722
762
  checkpoints: {
723
763
  readonly [x: string]: {
package/dist/lib.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { ReactElement, Component, ForwardRefExoticComponent, CSSProperties, FC, ComponentType } from 'react';
3
- import { M as MouseEvent, a as MoyuEvent, C as Command, b as MaybePromise, J as JSX, c as JsonValue } from './jsx-dev-runtime-CKclO27C.js';
4
- export { A as AudioCommand, d as AudioSettings, B as BubbleEvent, D as DetailedMoyuProps, E as EffectParams, G as GamepadCommand, K as KeyboardEvent, e as KeyboardEventKind, f as MakeNullOptional, g as MouseEventKind, h as MoyuAnimationAttributes, i as MoyuBackdropAttributes, j as MoyuClipAttributes, k as MoyuContainerAttributes, l as MoyuEventHandler, m as MoyuFilterAttributes, n as MoyuListenerAttributes, o as MoyuNodeAttributes, p as MoyuSpriteAttributes, q as MoyuTextAttributes, r as MoyuVideoAttributes, N as Node, S as ScenarioCommand, s as SystemCommand, T as TextCommand, t as TouchEvent, u as TouchEventKind, v as Tuple2, w as Tuple3, x as Tuple4, W as WindowState, y as createBubbleEvent, z as getStageSize } from './jsx-dev-runtime-CKclO27C.js';
3
+ import { M as MouseEvent, a as MoyuEvent, C as Command, b as MaybePromise, J as JSX, c as JsonValue } from './jsx-dev-runtime-BCxm528Z.js';
4
+ export { A as AudioCommand, d as AudioSettings, B as BubbleEvent, D as DetailedMoyuProps, E as EffectParams, G as GamepadCommand, K as KeyboardEvent, e as KeyboardEventKind, f as MakeNullOptional, g as MouseEventKind, h as MoyuAnimationAttributes, i as MoyuBackdropAttributes, j as MoyuClipAttributes, k as MoyuContainerAttributes, l as MoyuEventHandler, m as MoyuFilterAttributes, n as MoyuListenerAttributes, o as MoyuNodeAttributes, p as MoyuSpriteAttributes, q as MoyuTextAttributes, r as MoyuVideoAttributes, N as Node, S as ScenarioCommand, s as SystemCommand, T as TextCommand, t as TouchEvent, u as TouchEventKind, v as Tuple2, w as Tuple3, x as Tuple4, W as WindowState, y as createBubbleEvent, z as getStageSize } from './jsx-dev-runtime-BCxm528Z.js';
5
5
  import { FluidValue } from '@react-spring/shared';
6
6
  import { ElementType, AssignableKeys, ComponentPropsWithRef } from '@react-spring/types';
7
7
  import { Snapshot } from 'valtio';
@@ -522,6 +522,34 @@ type ExecutionCursor = {
522
522
  markerId?: string;
523
523
  };
524
524
 
525
+ type ExecutableRestartBoundary = {
526
+ kind: 'checkpoint';
527
+ checkpointKey: string;
528
+ } | {
529
+ kind: 'restart-story';
530
+ story: string;
531
+ entry: string;
532
+ } | {
533
+ kind: 'restart-paragraph';
534
+ story: string;
535
+ paragraph: string;
536
+ };
537
+
538
+ type StoryReplaceMode = 'noop' | 'reposition' | 'invalidate-only';
539
+
540
+ type StoryReplaceExecutionPlan = {
541
+ mode: StoryReplaceMode;
542
+ boundary?: ExecutableRestartBoundary;
543
+ targetMarkerId?: string;
544
+ changedControlFlow: boolean;
545
+ };
546
+
547
+ type StoryReplaceOutcome = {
548
+ story: string;
549
+ currentStoryAffected: boolean;
550
+ plan: StoryReplaceExecutionPlan;
551
+ };
552
+
525
553
  /** Flow control interface passed to every handler. */
526
554
  interface GameControl {
527
555
  /** Timed wait — blocks auto-advance. Engine fires scenariowaitingcancelled on timeout or skip. */
@@ -695,6 +723,12 @@ interface DebugSessionController {
695
723
  restoreCheckpoint(markerId: string, options?: DebugJumpTargetOptions): Promise<boolean>;
696
724
  warp(options: DebugWarpOptions): Promise<void>;
697
725
  switchRoute(routeName: string, params?: Record<string, unknown>): Promise<void>;
726
+ replaceStory(options: {
727
+ story: string;
728
+ content: string;
729
+ seekMode?: 'off' | 'fast-forward' | 'warp';
730
+ targetMarkerId?: string;
731
+ }): Promise<StoryReplaceOutcome>;
698
732
  }
699
733
  interface DebugStoryStartOptions {
700
734
  story: string;
@@ -718,6 +752,12 @@ declare function useDebugSession(): {
718
752
  restoreCheckpoint: (markerId: string, options?: DebugJumpTargetOptions) => Promise<boolean>;
719
753
  warp: (options: DebugWarpOptions) => Promise<void>;
720
754
  switchRoute: (routeName: string, params?: Record<string, unknown>) => Promise<void>;
755
+ replaceStory: (options: {
756
+ story: string;
757
+ content: string;
758
+ seekMode?: "off" | "fast-forward" | "warp";
759
+ targetMarkerId?: string;
760
+ }) => Promise<StoryReplaceOutcome>;
721
761
  active: boolean;
722
762
  checkpoints: {
723
763
  readonly [x: string]: {
package/dist/lib.mjs CHANGED
@@ -1202,9 +1202,44 @@ async function settleActiveWarp(error) {
1202
1202
  activeWarp = null;
1203
1203
  }, error);
1204
1204
  }
1205
+ async function clearLocalDebugSessionRuntimeState() {
1206
+ await settleActiveFastForward(new Error("Debug session runtime state cleared"));
1207
+ await settleActiveWarp(new Error("Debug session runtime state cleared"));
1208
+ clearLocalRuntimeState();
1209
+ }
1205
1210
  async function clearRemoteCheckpoints() {
1206
1211
  await executeScenarioCommand({ subCommand: "clearCheckpoints" });
1207
1212
  }
1213
+ function pruneLocalStoryCheckpoints(story, preserved) {
1214
+ debugState.checkpoints = Object.fromEntries(
1215
+ Object.entries(debugState.checkpoints).filter(([key, checkpoint]) => {
1216
+ if (checkpoint.cursor.story !== story) {
1217
+ return true;
1218
+ }
1219
+ return preserved.has(key);
1220
+ })
1221
+ );
1222
+ }
1223
+ async function syncCurrentExecutionCursor(fallbackStory = null) {
1224
+ debugState.currentCursor = await executeScenarioCommand({
1225
+ subCommand: "getExecutionCursor"
1226
+ });
1227
+ debugState.currentStory = debugState.currentCursor?.story ?? fallbackStory;
1228
+ }
1229
+ async function executeRestartBoundary(boundary, story) {
1230
+ if (boundary.kind === "checkpoint") {
1231
+ await debugSessionController.restoreCheckpoint(boundary.checkpointKey, { story });
1232
+ return;
1233
+ }
1234
+ if (!appStateAdapter?.restartStoryFromHead) {
1235
+ throw new Error("Restarting a story from an explicit entry is not supported by the app state adapter");
1236
+ }
1237
+ await appStateAdapter.restartStoryFromHead({
1238
+ story: boundary.story,
1239
+ entry: boundary.kind === "restart-story" ? boundary.entry : boundary.paragraph
1240
+ });
1241
+ await syncCurrentExecutionCursor(boundary.story);
1242
+ }
1208
1243
  async function syncLocalWarpCheckpoint(targetMarkerId) {
1209
1244
  const cursor = await executeScenarioCommand({
1210
1245
  subCommand: "getExecutionCursor"
@@ -1264,15 +1299,17 @@ async function prepareJumpStart(markerId, options) {
1264
1299
  subCommand: "restoreCheckpoint",
1265
1300
  key: markerId
1266
1301
  });
1267
- if (!restored2) {
1268
- throw new Error(`Failed to restore checkpoint ${markerId}`);
1269
- }
1270
- if (appStateAdapter) {
1271
- await appStateAdapter.restore(checkpoint.appState);
1302
+ if (restored2) {
1303
+ if (appStateAdapter) {
1304
+ await appStateAdapter.restore(checkpoint.appState);
1305
+ }
1306
+ debugState.currentCursor = checkpoint.cursor;
1307
+ debugState.currentStory = checkpoint.cursor.story;
1308
+ return false;
1272
1309
  }
1273
- debugState.currentCursor = checkpoint.cursor;
1274
- debugState.currentStory = checkpoint.cursor.story;
1275
- return false;
1310
+ debugState.checkpoints = Object.fromEntries(
1311
+ Object.entries(debugState.checkpoints).filter(([key]) => key !== markerId)
1312
+ );
1276
1313
  }
1277
1314
  const lookup = await executeScenarioCommand({
1278
1315
  subCommand: "getFastForwardCheckpoint",
@@ -1292,7 +1329,8 @@ async function prepareJumpStart(markerId, options) {
1292
1329
  }
1293
1330
  const startCheckpoint = debugState.checkpoints[startCheckpointKey];
1294
1331
  if (!startCheckpoint) {
1295
- throw new Error(`Local checkpoint ${startCheckpointKey} not found`);
1332
+ await restartJumpStoryFromHead(targetStory, currentStory);
1333
+ return true;
1296
1334
  }
1297
1335
  const restored = await executeScenarioCommand({
1298
1336
  subCommand: "restoreCheckpoint",
@@ -1435,6 +1473,46 @@ var debugSessionController = {
1435
1473
  throw error;
1436
1474
  }
1437
1475
  });
1476
+ },
1477
+ async replaceStory(options) {
1478
+ debugState.restoring = true;
1479
+ debugState.lastError = null;
1480
+ try {
1481
+ const outcome = await enqueueDebugOperation(
1482
+ async () => executeScenarioCommand({
1483
+ subCommand: "replaceStoryData",
1484
+ name: options.story,
1485
+ content: options.content
1486
+ })
1487
+ );
1488
+ if (outcome.plan.mode === "invalidate-only") {
1489
+ pruneLocalStoryCheckpoints(options.story, /* @__PURE__ */ new Set());
1490
+ }
1491
+ if (outcome.plan.mode === "reposition") {
1492
+ if (!outcome.plan.boundary) {
1493
+ throw new Error("Story replace outcome is missing an executable boundary");
1494
+ }
1495
+ const seekMode = options.seekMode ?? "warp";
1496
+ const replaceTargetMarkerId = options.targetMarkerId ?? outcome.plan.targetMarkerId;
1497
+ await executeRestartBoundary(outcome.plan.boundary, options.story);
1498
+ if (replaceTargetMarkerId && !(outcome.plan.boundary.kind === "checkpoint" && outcome.plan.boundary.checkpointKey === replaceTargetMarkerId)) {
1499
+ if (seekMode === "warp") {
1500
+ await runWarp({
1501
+ markerId: replaceTargetMarkerId,
1502
+ boundary: "after"
1503
+ });
1504
+ } else if (seekMode === "fast-forward") {
1505
+ await runFastForwardToMarker(replaceTargetMarkerId);
1506
+ }
1507
+ }
1508
+ }
1509
+ return outcome;
1510
+ } catch (error) {
1511
+ debugState.lastError = toErrorMessage(error);
1512
+ throw error;
1513
+ } finally {
1514
+ debugState.restoring = false;
1515
+ }
1438
1516
  }
1439
1517
  };
1440
1518
  function registerAppStateAdapter(adapter) {
@@ -1531,22 +1609,6 @@ async function stopDebugSession() {
1531
1609
  debugState.active = false;
1532
1610
  clearLocalRuntimeState();
1533
1611
  }
1534
- function clearDebugSessionRuntimeState() {
1535
- clearLocalRuntimeState();
1536
- void settleActiveFastForward(new Error("Debug session runtime state cleared"));
1537
- void settleActiveWarp(new Error("Debug session runtime state cleared"));
1538
- if (!debugState.active) {
1539
- return;
1540
- }
1541
- void enqueueDebugOperation(async () => {
1542
- try {
1543
- await clearRemoteCheckpoints();
1544
- } catch (error) {
1545
- debugState.lastError = toErrorMessage(error);
1546
- await currentConfig.onError(error);
1547
- }
1548
- });
1549
- }
1550
1612
  function useDebugSession() {
1551
1613
  const snapshot = useSnapshot2(debugState);
1552
1614
  return {
@@ -1555,7 +1617,8 @@ function useDebugSession() {
1555
1617
  stopDebugSession,
1556
1618
  restoreCheckpoint: debugSessionController.restoreCheckpoint,
1557
1619
  warp: debugSessionController.warp,
1558
- switchRoute: debugSessionController.switchRoute
1620
+ switchRoute: debugSessionController.switchRoute,
1621
+ replaceStory: debugSessionController.replaceStory
1559
1622
  };
1560
1623
  }
1561
1624
 
@@ -1645,6 +1708,9 @@ function isJumpRequestMessage(message) {
1645
1708
  function isRouteRequestMessage(message) {
1646
1709
  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));
1647
1710
  }
1711
+ function isStoryReplaceMessage(message) {
1712
+ return message.type === "story:replace" && typeof message.sessionId === "string" && typeof message.requestId === "number" && typeof message.story === "string" && typeof message.content === "string" && (message.seekMode === void 0 || message.seekMode === "off" || message.seekMode === "fast-forward" || message.seekMode === "warp") && (message.targetMarkerId === void 0 || typeof message.targetMarkerId === "string");
1713
+ }
1648
1714
  function isUiReplaceMessage(message) {
1649
1715
  return message.type === "ui:replace" && typeof message.sessionId === "string";
1650
1716
  }
@@ -1731,6 +1797,35 @@ function handleRuntimeDebugRequest(socket, sessionId, message) {
1731
1797
  });
1732
1798
  return;
1733
1799
  }
1800
+ if (isStoryReplaceMessage(message)) {
1801
+ setRuntimeDebugVariableEmissionSuspended(true);
1802
+ void controller.replaceStory({
1803
+ story: message.story,
1804
+ content: message.content,
1805
+ seekMode: message.seekMode,
1806
+ targetMarkerId: message.targetMarkerId
1807
+ }).then(async () => {
1808
+ sendRuntimeDebugMessage(socket, {
1809
+ type: "story:replace:done",
1810
+ sessionId,
1811
+ requestId: message.requestId,
1812
+ story: message.story
1813
+ });
1814
+ await syncRuntimeDebugVariablesSnapshotSafe("story-replace-done");
1815
+ }).catch(async (error) => {
1816
+ sendRuntimeDebugMessage(socket, {
1817
+ type: "story:replace:error",
1818
+ sessionId,
1819
+ requestId: message.requestId,
1820
+ story: message.story,
1821
+ error: error instanceof Error ? error.message : String(error)
1822
+ });
1823
+ await syncRuntimeDebugVariablesSnapshotSafe("story-replace-error");
1824
+ }).finally(() => {
1825
+ setRuntimeDebugVariableEmissionSuspended(false);
1826
+ });
1827
+ return;
1828
+ }
1734
1829
  setRuntimeDebugVariableEmissionSuspended(true);
1735
1830
  void controller.switchRoute(message.page, message.params).then(async () => {
1736
1831
  sendRuntimeDebugMessage(socket, {
@@ -1868,6 +1963,10 @@ async function startRuntimeDebugSession() {
1868
1963
  handleRuntimeDebugRequest(socket, sessionId, message);
1869
1964
  return;
1870
1965
  }
1966
+ if (isStoryReplaceMessage(message)) {
1967
+ handleRuntimeDebugRequest(socket, sessionId, message);
1968
+ return;
1969
+ }
1871
1970
  if (isUiReplaceMessage(message)) {
1872
1971
  handleRuntimeDebugRequest(socket, sessionId, message);
1873
1972
  return;
@@ -1983,13 +2082,13 @@ async function terminateCurrentScenario() {
1983
2082
  await executePluginCommand("scenario", {
1984
2083
  subCommand: "terminateStory"
1985
2084
  });
1986
- clearDebugSessionRuntimeState();
2085
+ await clearLocalDebugSessionRuntimeState();
1987
2086
  } catch (error) {
1988
2087
  console.error("Failed to terminate scenario:", error);
1989
2088
  }
1990
2089
  }
1991
2090
  async function startScenarioSession(session, config) {
1992
- clearDebugSessionRuntimeState();
2091
+ await clearLocalDebugSessionRuntimeState();
1993
2092
  for (const story of config.stories) {
1994
2093
  if (!isCurrentScenarioSession(session)) return;
1995
2094
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momoyu-ink/kit",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Software Development Kit of Moyu Visual Novel Engine",
5
5
  "main": "dist/lib.cjs",
6
6
  "module": "dist/lib.mjs",