diff --git a/event-binding.js b/event-binding.js index 50e4edd..8bb3ed9 100644 --- a/event-binding.js +++ b/event-binding.js @@ -506,6 +506,12 @@ export function onMessageReceivedController( messageId = null, _type = "", ) { + const enqueueMicrotask = + typeof runtime.queueMicrotask === "function" + ? runtime.queueMicrotask.bind(runtime) + : typeof globalThis.queueMicrotask === "function" + ? globalThis.queueMicrotask.bind(globalThis) + : (task) => Promise.resolve().then(task); const persistenceState = runtime.getGraphPersistenceState?.() || {}; const loadState = persistenceState.loadState || ""; const dbReady = @@ -552,7 +558,7 @@ export function onMessageReceivedController( : lastMessage; if (runtime.isAssistantChatMessage(targetMessage)) { - runtime.queueMicrotask(() => { + enqueueMicrotask(() => { void runtime.runExtraction().catch((error) => { runtime.console.error("[ST-BME] 异步自动提取失败:", error); runtime.notifyExtractionIssue( diff --git a/tests/p0-regressions.mjs b/tests/p0-regressions.mjs index b713efb..9e1e49a 100644 --- a/tests/p0-regressions.mjs +++ b/tests/p0-regressions.mjs @@ -10,6 +10,7 @@ import { onChatChangedController, onGenerationAfterCommandsController, onGenerationStartedController, + onMessageReceivedController, onMessageSwipedController, registerCoreEventHooksController, } from "../event-binding.js"; @@ -2946,6 +2947,48 @@ async function testSwipeRoutesToRerollWithoutHistoryRecoveryFallback() { assert.equal(result.recoveryPath, "reverse-journal"); } +async function testMessageReceivedQueuesExtractionWithoutRuntimeQueueMicrotask() { + let runExtractionCalls = 0; + let refreshCalls = 0; + + onMessageReceivedController( + { + getGraphPersistenceState: () => ({ loadState: "loaded", dbReady: true }), + getCurrentGraph: () => null, + getPendingRecallSendIntent: () => ({ text: "", at: 0 }), + isFreshRecallInputRecord: () => true, + createRecallInputRecord: () => ({ text: "", at: 0 }), + setPendingRecallSendIntent() {}, + getContext: () => ({ + chat: [ + { is_user: true, mes: "u1" }, + { is_user: false, mes: "a1" }, + ], + }), + isAssistantChatMessage(message) { + return Boolean(message) && !message.is_user && !message.is_system; + }, + runExtraction: async () => { + runExtractionCalls += 1; + }, + console: { + error() {}, + }, + notifyExtractionIssue() {}, + refreshPersistedRecallMessageUi() { + refreshCalls += 1; + }, + }, + 1, + "assistant", + ); + + await waitForTick(); + + assert.equal(runExtractionCalls, 1); + assert.equal(refreshCalls, 1); +} + async function testAutoExtractionDefersWhenGraphNotReady() { const deferredReasons = []; const statuses = []; @@ -4218,6 +4261,7 @@ await testGenerationRecallSentMessageClearsStaleTransactionForSameKey(); await testRegisterCoreEventHooksIsIdempotent(); await testChatChangedDoesNotClearCoreEventBindings(); await testSwipeRoutesToRerollWithoutHistoryRecoveryFallback(); +await testMessageReceivedQueuesExtractionWithoutRuntimeQueueMicrotask(); await testAutoExtractionDefersWhenGraphNotReady(); await testAutoExtractionDefersWhenAlreadyExtracting(); await testAutoExtractionDefersWhenHistoryRecoveryBusy();