function getTimerApi(runtime) {
  const rawSetTimeout =
    typeof runtime?.setTimeout === "function"
      ? runtime.setTimeout
      : globalThis.setTimeout;
  const rawClearTimeout =
    typeof runtime?.clearTimeout === "function"
      ? runtime.clearTimeout
      : globalThis.clearTimeout;

  return {
    setTimeout(...args) {
      return Reflect.apply(rawSetTimeout, globalThis, args);
    },
    clearTimeout(...args) {
      return Reflect.apply(rawClearTimeout, globalThis, args);
    },
  };
}

function toSafeFloor(value, fallback = null) {
  if (value == null || value === "") return fallback;
  const numeric = Number(value);
  return Number.isFinite(numeric) ? Math.floor(numeric) : fallback;
}

function isTavernHelperPromptViewerSyntheticGeneration(runtime) {
  if (!runtime.isTavernHelperPromptViewerRefreshActive?.()) {
    return false;
  }

  const pendingSendIntent = runtime.getPendingRecallSendIntent?.();
  return !runtime.isFreshRecallInputRecord?.(pendingSendIntent);
}

export function registerBeforeCombinePromptsController(runtime, listener) {
  const makeFirst = runtime.getEventMakeFirst();
  if (typeof makeFirst === "function") {
    return makeFirst(
      runtime.eventTypes.GENERATE_BEFORE_COMBINE_PROMPTS,
      listener,
    );
  }

  runtime.console.warn("[ST-BME] eventMakeFirst 不可用，回退到普通事件注册");
  runtime.eventSource.on(
    runtime.eventTypes.GENERATE_BEFORE_COMBINE_PROMPTS,
    listener,
  );
  return null;
}

export function registerGenerationAfterCommandsController(runtime, listener) {
  const makeFirst = runtime.getEventMakeFirst();
  const eventName = runtime.eventTypes.GENERATION_AFTER_COMMANDS;
  if (typeof makeFirst === "function") {
    const cleanup = makeFirst(eventName, listener);
    return cleanup;
  }

  runtime.console.warn(
    "[ST-BME] eventMakeFirst 不可用，GENERATION_AFTER_COMMANDS 回退到普通事件注册",
  );
  runtime.eventSource.on(eventName, listener);
  return null;
}

export function scheduleSendIntentHookRetryController(runtime, delayMs = 400) {
  const timers = getTimerApi(runtime);
  timers.clearTimeout(runtime.getSendIntentHookRetryTimer());
  const timer = timers.setTimeout(() => {
    runtime.setSendIntentHookRetryTimer(null);
    runtime.installSendIntentHooks();
  }, delayMs);
  runtime.setSendIntentHookRetryTimer(timer);
}

export function installSendIntentHooksController(runtime) {
  for (const cleanup of runtime.consumeSendIntentHookCleanup()) {
    try {
      cleanup();
    } catch (error) {
      runtime.console.warn("[ST-BME] 清理发送意图钩子失败:", error);
    }
  }

  const sendButton = runtime.document.getElementById("send_but");
  const sendTextarea = runtime.document.getElementById("send_textarea");

  if (sendButton) {
    const captureSendIntent = () => {
      runtime.recordRecallSendIntent(
        runtime.getSendTextareaValue(),
        "send-button",
      );
    };

    sendButton.addEventListener("click", captureSendIntent, true);
    sendButton.addEventListener("pointerup", captureSendIntent, true);
    sendButton.addEventListener("touchend", captureSendIntent, true);
    runtime.pushSendIntentHookCleanup(() => {
      sendButton.removeEventListener("click", captureSendIntent, true);
      sendButton.removeEventListener("pointerup", captureSendIntent, true);
      sendButton.removeEventListener("touchend", captureSendIntent, true);
    });
  }

  if (sendTextarea) {
    const captureEnterIntent = (event) => {
      if (
        (event.key === "Enter" || event.key === "NumpadEnter") &&
        !event.shiftKey
      ) {
        runtime.recordRecallSendIntent(
          runtime.getSendTextareaValue(),
          "textarea-enter",
        );
      }
    };

    sendTextarea.addEventListener("keydown", captureEnterIntent, true);
    runtime.pushSendIntentHookCleanup(() => {
      sendTextarea.removeEventListener("keydown", captureEnterIntent, true);
    });
  }

  if (!sendButton || !sendTextarea) {
    runtime.scheduleSendIntentHookRetry();
  }
}

export function registerCoreEventHooksController(runtime) {
  const { eventSource, eventTypes, handlers } = runtime;
  const registrationState = runtime.getCoreEventBindingState?.() || {};

  if (registrationState.registered) {
    runtime.console?.warn?.("[ST-BME] 核心事件已注册，跳过重复绑定");
    return registrationState;
  }

  const cleanups = [];
  const bind = (eventName, listener, options = undefined) => {
    if (!eventName || typeof listener !== "function") return;
    eventSource.on(eventName, listener, options);
    if (typeof eventSource.off === "function") {
      cleanups.push(() => eventSource.off(eventName, listener));
    } else if (typeof eventSource.removeListener === "function") {
      cleanups.push(() => eventSource.removeListener(eventName, listener));
    }
  };

  bind(eventTypes.CHAT_CHANGED, handlers.onChatChanged);
  if (eventTypes.CHAT_LOADED) {
    bind(eventTypes.CHAT_LOADED, handlers.onChatLoaded);
  }
  if (eventTypes.MESSAGE_SENT) {
    bind(eventTypes.MESSAGE_SENT, handlers.onMessageSent);
  }
  if (eventTypes.GENERATION_STARTED) {
    bind(eventTypes.GENERATION_STARTED, handlers.onGenerationStarted);
  }
  if (eventTypes.GENERATION_ENDED) {
    bind(eventTypes.GENERATION_ENDED, handlers.onGenerationEnded);
  }

  const beforeCombineCleanup = runtime.registerBeforeCombinePrompts(
    handlers.onBeforeCombinePrompts,
  );
  if (typeof beforeCombineCleanup === "function") {
    cleanups.push(beforeCombineCleanup);
  }

  const afterCommandsCleanup = runtime.registerGenerationAfterCommands(
    handlers.onGenerationAfterCommands,
  );
  if (typeof afterCommandsCleanup === "function") {
    cleanups.push(afterCommandsCleanup);
  }

  bind(eventTypes.MESSAGE_RECEIVED, handlers.onMessageReceived);
  bind(eventTypes.MESSAGE_DELETED, handlers.onMessageDeleted);
  bind(eventTypes.MESSAGE_EDITED, handlers.onMessageEdited);
  bind(eventTypes.MESSAGE_SWIPED, handlers.onMessageSwiped);
  if (eventTypes.MESSAGE_UPDATED) {
    bind(eventTypes.MESSAGE_UPDATED, handlers.onMessageUpdated);
  }
  if (eventTypes.MESSAGE_SWIPE_DELETED && typeof handlers.onMessageDeleted === "function") {
    bind(eventTypes.MESSAGE_SWIPE_DELETED, handlers.onMessageDeleted);
  }
  if (eventTypes.USER_MESSAGE_RENDERED) {
    bind(eventTypes.USER_MESSAGE_RENDERED, handlers.onUserMessageRendered);
  }
  if (eventTypes.CHARACTER_MESSAGE_RENDERED) {
    bind(
      eventTypes.CHARACTER_MESSAGE_RENDERED,
      handlers.onCharacterMessageRendered,
    );
  }
  bind(eventTypes.GENERATION_CONTEXT_READY, handlers.onGenerationContextReady, {
    priority: 20,
  });
  bind(
    eventTypes.GENERATION_BEFORE_WORLD_INFO_SCAN,
    handlers.onGenerationBeforeWorldInfoScan,
    { priority: 20 },
  );
  bind(
    eventTypes.GENERATION_AFTER_WORLD_INFO_SCAN,
    handlers.onGenerationAfterWorldInfoScan,
    { priority: 20 },
  );
  bind(
    eventTypes.GENERATION_WORLD_INFO_FINALIZED,
    handlers.onGenerationWorldInfoFinalized,
    { priority: 20 },
  );
  bind(
    eventTypes.GENERATION_BEFORE_API_REQUEST,
    handlers.onGenerationBeforeApiRequest,
    { priority: 20 },
  );
  bind(eventTypes.CHAT_BRANCH_CREATED, handlers.onChatBranchCreated, {
    priority: 20,
  });

  const nextState = {
    registered: true,
    cleanups,
    registeredAt: Date.now(),
  };
  runtime.setCoreEventBindingState?.(nextState);
  return nextState;
}

export function onChatChangedController(runtime) {
  const timers = getTimerApi(runtime);
  runtime.clearPendingHistoryMutationChecks();
  timers.clearTimeout(runtime.getPendingHistoryRecoveryTimer());
  runtime.setPendingHistoryRecoveryTimer(null);
  runtime.setPendingHistoryRecoveryTrigger("");
  runtime.clearPendingAutoExtraction?.();
  runtime.clearPendingBackgroundVectorSync?.();
  runtime.clearPendingGraphLoadRetry();
  runtime.setSkipBeforeCombineRecallUntil(0);
  runtime.setLastPreGenerationRecallKey("");
  runtime.setLastPreGenerationRecallAt(0);
  runtime.clearGenerationRecallTransactionsForChat("", { clearAll: true });
  runtime.abortAllRunningStages();
  runtime.dismissAllStageNotices();
  runtime.syncGraphLoadFromLiveContext({
    source: "chat-changed",
    force: true,
  });
  runtime.clearCurrentGenerationTrivialSkip?.("chat-changed");
  runtime.clearInjectionState();
  runtime.clearRecallInputTracking();
  runtime.installSendIntentHooks();
  runtime.refreshPersistedRecallMessageUi?.();
}

export function onChatLoadedController(runtime) {
  runtime.syncGraphLoadFromLiveContext({
    source: "chat-loaded",
  });
  runtime.refreshPersistedRecallMessageUi?.();
}

export function onMessageSentController(runtime, messageId) {
  const context = runtime.getContext();
  const chat = context?.chat;
  const normalizedMessageId =
    messageId === null || messageId === undefined || messageId === ""
      ? null
      : Number(messageId);
  let resolvedMessageId = Number.isFinite(normalizedMessageId)
    ? normalizedMessageId
    : null;
  let message =
    Array.isArray(chat) && Number.isFinite(resolvedMessageId)
      ? chat[resolvedMessageId]
      : null;

  if (!message?.is_user && Array.isArray(chat)) {
    for (let index = chat.length - 1; index >= 0; index--) {
      if (!chat[index]?.is_user) continue;
      resolvedMessageId = index;
      message = chat[index];
      break;
    }
  }

  if (!message?.is_user) return;
  const trivialInputResult = runtime.isTrivialUserInput?.(message.mes || "") || {
    trivial: false,
    reason: "",
    normalizedText: "",
  };
  const tokenEstimate =
    typeof runtime.estimateTokens === "function"
      ? Number(runtime.estimateTokens(message.mes || ""))
      : Number.NaN;
  const isZeroTokenInput =
    Number.isFinite(tokenEstimate) && tokenEstimate <= 0;

  if (trivialInputResult.trivial || isZeroTokenInput) {
    runtime.markCurrentGenerationTrivialSkip?.({
      reason: trivialInputResult.trivial
        ? trivialInputResult.reason
        : "zero-token",
      chatId: context?.chatId || "",
      chatLength: Array.isArray(chat) ? chat.length : 0,
    });
    runtime.clearPendingRecallSendIntent?.();
    runtime.clearPendingHostGenerationInputSnapshot?.();
    console.info?.(
      `[ST-BME] trivial-input skip: reason=${
        trivialInputResult.trivial ? trivialInputResult.reason : "zero-token"
      } len=${String(trivialInputResult.normalizedText || message.mes || "").length} hook=MESSAGE_SENT`,
    );
    runtime.refreshPersistedRecallMessageUi?.();
    return;
  }
  runtime.recordRecallSentUserMessage(
    resolvedMessageId,
    message.mes || "",
  );
  // GENERATION_AFTER_COMMANDS 在 sendMessageAsUser 之前触发，此时新用户消息
  // 尚未进入 chat，recall 记录会被写到上一条 user 上。这里用户消息刚入场，
  // transaction 仍在桥接窗口内，立即把记录重新绑定到正确的楼层。
  runtime.rebindRecallRecordToNewUserMessage?.(resolvedMessageId);
  runtime.refreshPersistedRecallMessageUi?.();
}

export function onUserMessageRenderedController(runtime, messageId = null) {
  // MESSAGE_SENT 早于实际 DOM 挂载；这里等宿主确认 user 楼层渲染完成后，
  // 再补一次 Recall Card 刷新，避免“当前楼层没卡片，下一楼才补出来”。
  runtime.refreshPersistedRecallMessageUi?.(40);
  return {
    messageId: Number.isFinite(Number(messageId)) ? Number(messageId) : null,
    refreshed: true,
    source: "user-message-rendered",
  };
}

export function onCharacterMessageRenderedController(
  runtime,
  messageId = null,
  type = "",
) {
  runtime.refreshPersistedRecallMessageUi?.(80);
  return {
    messageId: Number.isFinite(Number(messageId)) ? Number(messageId) : null,
    type: String(type || ""),
    refreshed: true,
    source: "character-message-rendered",
  };
}

export function onGenerationStartedController(
  runtime,
  type,
  params = {},
  dryRun = false,
) {
  if (dryRun) {
    runtime.markDryRunPromptPreview?.();
    return null;
  }
  runtime.clearDryRunPromptPreview?.();
  if (params?.automatic_trigger || params?.quiet_prompt) return null;

  const generationType = String(type || "normal").trim() || "normal";
  if (generationType !== "normal") return null;

  if (isTavernHelperPromptViewerSyntheticGeneration(runtime)) {
    const context = runtime.getContext?.() || {};
    runtime.markCurrentGenerationTrivialSkip?.({
      reason: "tavern-helper-prompt-viewer",
      chatId: context?.chatId || "",
      chatLength: Array.isArray(context?.chat) ? context.chat.length : 0,
    });
    runtime.clearPendingRecallSendIntent?.();
    runtime.clearPendingHostGenerationInputSnapshot?.();
    console.debug?.(
      "[ST-BME] skip: tavern-helper-prompt-viewer hook=GENERATION_STARTED",
    );
    return null;
  }

  const pendingSendIntent = runtime.getPendingRecallSendIntent?.();
  const pendingIntentText = runtime.isFreshRecallInputRecord?.(
    pendingSendIntent,
  )
    ? pendingSendIntent.text
    : "";
  const textareaText =
    typeof runtime.getSendTextareaValue === "function"
      ? runtime.getSendTextareaValue()
      : "";
  const snapshotText =
    runtime.normalizeRecallInputText?.(pendingIntentText || textareaText) || "";
  const trivialInputResult = runtime.isTrivialUserInput?.(snapshotText);
  if (trivialInputResult?.trivial) {
    const context = runtime.getContext?.() || {};
    runtime.markCurrentGenerationTrivialSkip?.({
      reason: trivialInputResult.reason,
      chatId: context?.chatId || "",
      chatLength: Array.isArray(context?.chat) ? context.chat.length : 0,
    });
    runtime.clearPendingRecallSendIntent?.();
    runtime.clearPendingHostGenerationInputSnapshot?.();
    console.info?.(
      `[ST-BME] trivial-input skip: reason=${trivialInputResult.reason} len=${trivialInputResult.normalizedText.length} hook=GENERATION_STARTED`,
    );
    return null;
  }
  runtime.clearCurrentGenerationTrivialSkip?.("new-non-trivial-generation");
  return runtime.freezeHostGenerationInputSnapshot(
    snapshotText,
    pendingIntentText
      ? "generation-started-send-intent"
      : "generation-started-textarea",
  );
}

export function onMessageDeletedController(
  runtime,
  chatLengthOrMessageId,
  meta = null,
) {
  runtime.invalidateRecallAfterHistoryMutation("消息已删除");
  runtime.scheduleHistoryMutationRecheck(
    "message-deleted",
    chatLengthOrMessageId,
    meta,
  );
  runtime.refreshPersistedRecallMessageUi?.();
}

export function onMessageEditedController(runtime, messageId, meta = null) {
  if (runtime.isMvuExtraAnalysisGuardActive?.()) {
    console.debug?.("[ST-BME] skip: mvu-extra-analysis hook=MESSAGE_EDITED");
    runtime.refreshPersistedRecallMessageUi?.();
    return;
  }
  const parsedMessageId = Number(messageId);
  if (Number.isFinite(parsedMessageId)) {
    runtime.removeMessageRecallRecord?.(Math.floor(parsedMessageId));
  }
  runtime.invalidateRecallAfterHistoryMutation("消息已编辑");
  runtime.scheduleHistoryMutationRecheck("message-edited", messageId, meta);
  runtime.refreshPersistedRecallMessageUi?.();
}

export function onMessageUpdatedController(runtime, messageId, meta = null) {
  runtime.recordIgnoredMutationEvent?.("message-updated", {
    messageId: Number.isFinite(Number(messageId)) ? Number(messageId) : null,
    meta,
    reason: "lightweight-refresh-only",
  });
  runtime.refreshPersistedRecallMessageUi?.();
  return {
    messageId: Number.isFinite(Number(messageId)) ? Number(messageId) : null,
    lightweight: true,
    refreshed: true,
  };
}

export async function onMessageSwipedController(runtime, messageId, meta = null) {
  const parsedFloor = Number(messageId);
  const fromFloor = Number.isFinite(parsedFloor) ? parsedFloor : undefined;
  const preparedRerollReuse = runtime.prepareRerollRecallReuse?.({
    fromFloor,
    meta,
  });
  if (!preparedRerollReuse) {
    runtime.invalidateRecallAfterHistoryMutation("已切换楼层 swipe");
  }
  let result = {
    success: false,
    rollbackPerformed: false,
    extractionTriggered: false,
    requestedFloor: fromFloor ?? null,
    effectiveFromFloor: null,
    recoveryPath: "reroll-handler-unavailable",
    affectedBatchCount: 0,
    error: "swipe reroll handler unavailable",
  };

  if (typeof runtime.onReroll === "function") {
    try {
      result = await runtime.onReroll({ fromFloor, meta });
    } catch (error) {
      runtime.console?.error?.("[ST-BME] swipe reroll failed:", error);
      result = {
        success: false,
        rollbackPerformed: false,
        extractionTriggered: false,
        requestedFloor: fromFloor ?? null,
        effectiveFromFloor: null,
        recoveryPath: "reroll-threw",
        affectedBatchCount: 0,
        error: error?.message || String(error) || "swipe reroll failed",
      };
    }
  } else {
    runtime.console?.warn?.(
      "[ST-BME] MESSAGE_SWIPED missing onReroll; skip generic history recovery fallback.",
      { messageId, meta },
    );
  }
  if (!result?.success) {
    runtime.clearPendingRerollRecallReuse?.("swipe-reroll-failed");
  }
  runtime.refreshPersistedRecallMessageUi?.();
  return result;
}

export async function onGenerationAfterCommandsController(
  runtime,
  type,
  params = {},
  dryRun = false,
) {
  if (dryRun) {
    return;
  }

  const generationType = String(type || "normal").trim() || "normal";

  if (runtime.isMvuExtraAnalysisGuardActive?.()) {
    console.debug?.(
      "[ST-BME] skip: mvu-extra-analysis hook=GENERATION_AFTER_COMMANDS",
    );
    return;
  }

  if (
    generationType === "normal" &&
    isTavernHelperPromptViewerSyntheticGeneration(runtime)
  ) {
    const context = runtime.getContext?.() || {};
    runtime.markCurrentGenerationTrivialSkip?.({
      reason: "tavern-helper-prompt-viewer",
      chatId: runtime.getCurrentChatId?.() || context?.chatId || "",
      chatLength: Array.isArray(context?.chat) ? context.chat.length : 0,
    });
    runtime.clearPendingRecallSendIntent?.();
    runtime.clearPendingHostGenerationInputSnapshot?.();
    console.debug?.(
      "[ST-BME] skip: tavern-helper-prompt-viewer hook=GENERATION_AFTER_COMMANDS",
    );
    return;
  }

  const frozenInputSnapshot =
    generationType === "normal"
      ? runtime.consumeHostGenerationInputSnapshot?.({ preserve: true }) ||
        runtime.consumeHostGenerationInputSnapshot?.()
      : null;

  const context = runtime.getContext();
  const chat = context?.chat;

  const recallOptions = runtime.buildGenerationAfterCommandsRecallInput(
    type,
    {
      ...params,
      frozenInputSnapshot,
    },
    chat,
  );
  if (!recallOptions) {
    return;
  }
  if (recallOptions?.__trivialSkip) {
    return;
  }

  const recallContext = runtime.createGenerationRecallContext({
    hookName: "GENERATION_AFTER_COMMANDS",
    generationType,
    recallOptions,
  });
  if (!recallContext.shouldRun && !recallContext.transaction) {
    return;
  }

  const runtimeRecallOptions =
    recallContext.recallOptions || recallOptions || {};
  if (
    params &&
    typeof params === "object" &&
    runtimeRecallOptions?.authoritativeInputUsed === true
  ) {
    const authoritativePrompt = String(
      runtimeRecallOptions?.overrideUserMessage ||
        runtimeRecallOptions?.userMessage ||
        "",
    ).trim();
    if (authoritativePrompt) {
      params.prompt = authoritativePrompt;
      if (Object.prototype.hasOwnProperty.call(params, "user_input")) {
        params.user_input = authoritativePrompt;
      }
    }
  }
  const deliveryMode =
    runtime.resolveGenerationRecallDeliveryMode?.(
      recallContext.hookName,
      recallContext.generationType,
      runtimeRecallOptions,
    ) || "immediate";
  let recallResult = runtime.getGenerationRecallTransactionResult?.(
    recallContext.transaction,
  );

  if (recallContext.shouldRun) {
    runtime.markGenerationRecallTransactionHookState(
      recallContext.transaction,
      recallContext.hookName,
      "running",
    );
    if (deliveryMode === "deferred") {
      runtime.clearLiveRecallInjectionPromptForRewrite?.();
    }
    recallResult = await runtime.runRecall({
      ...runtimeRecallOptions,
      deliveryMode,
      recallKey: recallContext.recallKey,
      hookName: recallContext.hookName,
      signal: params?.signal,
    });
    runtime.storeGenerationRecallTransactionResult?.(
      recallContext.transaction,
      recallResult,
      {
        hookName: recallContext.hookName,
        deliveryMode,
      },
    );

    runtime.markGenerationRecallTransactionHookState(
      recallContext.transaction,
      recallContext.hookName,
      runtime.getGenerationRecallHookStateFromResult(recallResult),
    );
  }

  // immediate 模式下，runRecall → applyRecallInjection 内部已通过
  // setExtensionPrompt 完成了注入，此处直接返回召回结果。
  // 后续 GENERATE_BEFORE_COMBINE_PROMPTS 阶段会通过
  // applyFinalRecallInjectionForGeneration 做 deferred rewrite 兜底。
  if (deliveryMode === "immediate") {
    runtime.ensurePersistedRecallRecordForGeneration?.({
      generationType: recallContext.generationType,
      recallResult,
      transaction: recallContext.transaction,
      recallOptions: runtimeRecallOptions,
      hookName: recallContext.hookName,
    });
    // immediate 路径通常会在 runRecall 内完成持久化；如果当时 user 楼层还没稳定，
    // 上面的兜底补写会把 fresh recall 绑定回最终 user 楼层。
    // 这里再补一次 UI 刷新，避免需要等到消息编辑/历史恢复后才看到 Recall Card。
    runtime.refreshPersistedRecallMessageUi?.();
    return recallResult;
  }

  return runtime.applyFinalRecallInjectionForGeneration({
    generationType: recallContext.generationType,
    freshRecallResult: recallResult,
    transaction: recallContext.transaction,
    hookName: recallContext.hookName,
  });
}

export async function onBeforeCombinePromptsController(
  runtime,
  promptData = null,
) {
  if (runtime.consumeDryRunPromptPreview?.()) {
    return {
      skipped: true,
      reason: "dry-run-preview",
    };
  }

  if (runtime.isMvuExtraAnalysisGuardActive?.()) {
    console.debug?.(
      "[ST-BME] skip: mvu-extra-analysis hook=GENERATE_BEFORE_COMBINE_PROMPTS",
    );
    return {
      skipped: true,
      reason: "mvu-extra-analysis",
    };
  }

  if (isTavernHelperPromptViewerSyntheticGeneration(runtime)) {
    const context = runtime.getContext?.() || {};
    runtime.markCurrentGenerationTrivialSkip?.({
      reason: "tavern-helper-prompt-viewer",
      chatId: runtime.getCurrentChatId?.() || context?.chatId || "",
      chatLength: Array.isArray(context?.chat) ? context.chat.length : 0,
    });
    runtime.clearPendingRecallSendIntent?.();
    runtime.clearPendingHostGenerationInputSnapshot?.();
    console.debug?.(
      "[ST-BME] skip: tavern-helper-prompt-viewer hook=GENERATE_BEFORE_COMBINE_PROMPTS",
    );
    return {
      skipped: true,
      reason: "tavern-helper-prompt-viewer",
    };
  }

  const frozenInputSnapshot =
    runtime.consumeHostGenerationInputSnapshot?.() ||
    runtime.getPendingHostGenerationInputSnapshot?.() ||
    runtime.createRecallInputRecord?.() ||
    {};
  const context = runtime.getContext();
  const chat = context?.chat;
  const normalInput = runtime.buildNormalGenerationRecallInput(chat, {
    frozenInputSnapshot,
  });
  if (normalInput?.__trivialSkip) {
    return {
      skipped: true,
      reason: `trivial:${normalInput.trivialReason || ""}`,
    };
  }
  const recallOptions =
    normalInput ||
    runtime.buildHistoryGenerationRecallInput(chat) ||
    {};
  const recallContext = runtime.createGenerationRecallContext({
    hookName: "GENERATE_BEFORE_COMBINE_PROMPTS",
    generationType: "normal",
    recallOptions,
  });
  if (!recallContext.shouldRun && !recallContext.transaction) {
    return;
  }

  const runtimeRecallOptions =
    recallContext.recallOptions || recallOptions || {};
  const deliveryMode =
    runtime.resolveGenerationRecallDeliveryMode?.(
      recallContext.hookName,
      recallContext.generationType,
      runtimeRecallOptions,
    ) || "deferred";
  let recallResult = runtime.getGenerationRecallTransactionResult?.(
    recallContext.transaction,
  );

  if (recallContext.shouldRun) {
    runtime.markGenerationRecallTransactionHookState(
      recallContext.transaction,
      recallContext.hookName,
      "running",
    );
    if (deliveryMode === "deferred") {
      runtime.clearLiveRecallInjectionPromptForRewrite?.();
    }
    recallResult = await runtime.runRecall({
      ...runtimeRecallOptions,
      deliveryMode,
      recallKey: recallContext.recallKey,
      hookName: recallContext.hookName,
    });
    runtime.storeGenerationRecallTransactionResult?.(
      recallContext.transaction,
      recallResult,
      {
        hookName: recallContext.hookName,
        deliveryMode,
      },
    );
    runtime.markGenerationRecallTransactionHookState(
      recallContext.transaction,
      recallContext.hookName,
      runtime.getGenerationRecallHookStateFromResult(recallResult),
    );
  }

  return runtime.applyFinalRecallInjectionForGeneration({
    generationType: recallContext.generationType,
    freshRecallResult: recallResult,
    transaction: recallContext.transaction,
    promptData,
    hookName: recallContext.hookName,
  });
}

export function onMessageReceivedController(
  runtime,
  messageId = null,
  _type = "",
) {
  const enqueueMicrotask =
    typeof globalThis.queueMicrotask === "function"
        ? globalThis.queueMicrotask.bind(globalThis)
      : typeof runtime.queueMicrotask === "function"
        ? (task) => Reflect.apply(runtime.queueMicrotask, globalThis, [task])
        : (task) => Promise.resolve().then(task);
  const persistenceState = runtime.getGraphPersistenceState?.() || {};
  const loadState = persistenceState.loadState || "";
  const dbReady =
    persistenceState.dbReady ??
    (loadState === "loaded" || loadState === "empty-confirmed");
  if (
    !dbReady ||
    loadState === "loading" ||
    loadState === "shadow-restored" ||
    loadState === "blocked"
  ) {
    runtime.syncGraphLoadFromLiveContext?.({
      source: "message-received-reconcile",
    });
  }

  if (runtime.getCurrentGraph()) {
    if (
      runtime.getGraphPersistenceState()?.pendingPersist &&
      runtime.isGraphMetadataWriteAllowed()
    ) {
      runtime.maybeFlushQueuedGraphPersist("message-received-pending-flush");
    }
  }

  const pendingRecallSendIntent = runtime.getPendingRecallSendIntent();
  if (
    pendingRecallSendIntent?.text &&
    !runtime.isFreshRecallInputRecord(pendingRecallSendIntent)
  ) {
    runtime.setPendingRecallSendIntent(runtime.createRecallInputRecord());
  }

  const context = runtime.getContext();
  const chat = context?.chat;
  const settings =
    typeof runtime.getSettings === "function" ? runtime.getSettings() : {};
  const lastProcessedAssistantFloor =
    typeof runtime.getLastProcessedAssistantFloor === "function"
      ? runtime.getLastProcessedAssistantFloor()
      : -1;
  const receivedMessage =
    Array.isArray(chat) && Number.isFinite(Number(messageId))
      ? chat[Number(messageId)]
      : null;
  const lastMessage =
    Array.isArray(chat) && chat.length > 0 ? chat[chat.length - 1] : null;
  const targetMessage = runtime.isAssistantChatMessage(receivedMessage)
    ? receivedMessage
    : lastMessage;
  const targetMessageIndex = runtime.isAssistantChatMessage(receivedMessage)
    ? Number(messageId)
    : runtime.isAssistantChatMessage(lastMessage)
      ? chat.length - 1
      : null;

  if (runtime.isAssistantChatMessage(targetMessage)) {
    if (runtime.consumeCurrentGenerationTrivialSkip?.(targetMessageIndex)) {
      runtime.console?.info?.(
        "[ST-BME] trivial-input skip: extraction bypassed",
        { messageId: targetMessageIndex },
      );
      runtime.refreshPersistedRecallMessageUi?.();
      return;
    }
    const autoExtractionPlan =
      typeof runtime.resolveAutoExtractionPlan === "function"
        ? runtime.resolveAutoExtractionPlan({
            chat,
            settings,
            lastProcessedAssistantFloor,
          })
        : null;
    if (!autoExtractionPlan?.canRun) {
      runtime.console?.debug?.(
        "[ST-BME] assistant message received, auto extraction not runnable yet",
        {
          messageId: Number.isFinite(Number(targetMessageIndex))
            ? Number(targetMessageIndex)
            : null,
          reason: String(autoExtractionPlan?.reason || "not-runnable"),
          strategy: String(autoExtractionPlan?.strategy || "normal"),
        },
      );
      runtime.refreshPersistedRecallMessageUi?.();
      return;
    }
    runtime.console?.debug?.(
      "[ST-BME] assistant message received, queueing auto extraction",
      {
        messageId: Number.isFinite(Number(targetMessageIndex))
          ? Number(targetMessageIndex)
          : null,
        chatLength: Array.isArray(chat) ? chat.length : 0,
        loadState,
        dbReady,
      },
    );
    if (
      runtime.getIsHostGenerationRunning?.() === true &&
      typeof runtime.deferAutoExtraction === "function"
    ) {
      runtime.console?.debug?.(
        "[ST-BME] assistant message received during host generation, deferring auto extraction",
        {
          messageId: Number.isFinite(Number(targetMessageIndex))
            ? Number(targetMessageIndex)
            : null,
          targetEndFloor: toSafeFloor(autoExtractionPlan.plannedBatchEndFloor, null),
        },
      );
      runtime.deferAutoExtraction("generation-running", {
        messageId: targetMessageIndex,
        targetEndFloor: autoExtractionPlan.plannedBatchEndFloor,
        strategy: autoExtractionPlan.strategy,
      });
      runtime.refreshPersistedRecallMessageUi?.();
      return;
    }
    enqueueMicrotask(() => {
      void runtime
        .runExtraction({
          lockedEndFloor: autoExtractionPlan.plannedBatchEndFloor,
          triggerSource: "message-received",
        })
        .catch((error) => {
        runtime.console.error("[ST-BME] 异步自动提取失败:", error);
        runtime.notifyExtractionIssue(
          error?.message || String(error) || "自动提取失败",
        );
        });
    });
  }
  runtime.refreshPersistedRecallMessageUi?.();
}
