diff --git a/event-binding.js b/event-binding.js index e0f5fca..397912a 100644 --- a/event-binding.js +++ b/event-binding.js @@ -211,7 +211,11 @@ export function onGenerationStartedController( params = {}, dryRun = false, ) { - if (dryRun) return null; + if (dryRun) { + runtime.markDryRunPromptPreview?.(); + return null; + } + runtime.clearDryRunPromptPreview?.(); if (params?.automatic_trigger || params?.quiet_prompt) return null; const generationType = String(type || "normal").trim() || "normal"; @@ -382,6 +386,13 @@ export async function onBeforeCombinePromptsController( runtime, promptData = null, ) { + if (runtime.consumeDryRunPromptPreview?.()) { + return { + skipped: true, + reason: "dry-run-preview", + }; + } + const frozenInputSnapshot = runtime.consumeHostGenerationInputSnapshot?.() || runtime.getPendingHostGenerationInputSnapshot?.() || diff --git a/extraction-controller.js b/extraction-controller.js index 9324128..4cf8989 100644 --- a/extraction-controller.js +++ b/extraction-controller.js @@ -125,7 +125,10 @@ export async function executeExtractionBatchController( } export async function runExtractionController(runtime) { - if (runtime.getIsExtracting()) return; + if (runtime.getIsExtracting()) { + runtime.deferAutoExtraction?.("extracting"); + return; + } const settings = runtime.getSettings(); if (!settings.enabled) return; diff --git a/index.js b/index.js index 54e5d07..dec3119 100644 --- a/index.js +++ b/index.js @@ -3760,6 +3760,33 @@ function maybeResumePendingAutoExtraction(source = "auto-extraction-resume") { }; } +function markDryRunPromptPreview(ttlMs = GENERATION_RECALL_HOOK_BRIDGE_MS) { + const resolvedTtlMs = Math.max( + 100, + Math.floor(Number(ttlMs) || GENERATION_RECALL_HOOK_BRIDGE_MS), + ); + skipBeforeCombineRecallUntil = Date.now() + resolvedTtlMs; + return skipBeforeCombineRecallUntil; +} + +function clearDryRunPromptPreview() { + const hadPendingSkip = skipBeforeCombineRecallUntil > Date.now(); + skipBeforeCombineRecallUntil = 0; + return hadPendingSkip; +} + +function consumeDryRunPromptPreview(now = Date.now()) { + if (skipBeforeCombineRecallUntil <= now) { + if (skipBeforeCombineRecallUntil !== 0) { + skipBeforeCombineRecallUntil = 0; + } + return false; + } + + skipBeforeCombineRecallUntil = 0; + return true; +} + function isGraphEffectivelyEmpty(graph) { if (!graph || typeof graph !== "object") { return true; @@ -7902,10 +7929,12 @@ function onMessageSwiped(messageId, meta = null) { function onGenerationStarted(type, params = {}, dryRun = false) { return onGenerationStartedController( { + clearDryRunPromptPreview, freezeHostGenerationInputSnapshot, getPendingRecallSendIntent: () => pendingRecallSendIntent, getSendTextareaValue, isFreshRecallInputRecord, + markDryRunPromptPreview, normalizeRecallInputText, }, type, @@ -7949,6 +7978,7 @@ async function onBeforeCombinePrompts(promptData = null) { buildHistoryGenerationRecallInput, buildNormalGenerationRecallInput, clearLiveRecallInjectionPromptForRewrite, + consumeDryRunPromptPreview, consumeHostGenerationInputSnapshot, createGenerationRecallContext, getContext, diff --git a/tests/p0-regressions.mjs b/tests/p0-regressions.mjs index c265a89..8df323a 100644 --- a/tests/p0-regressions.mjs +++ b/tests/p0-regressions.mjs @@ -2697,6 +2697,16 @@ async function testGenerationRecallBeforeCombineRunsStandalone() { ); } +async function testGenerationRecallDryRunPreviewDoesNotTriggerBeforeCombineRecall() { + const harness = await createGenerationRecallHarness(); + harness.chat = [{ is_user: true, mes: "Prompt Viewer 预览" }]; + + harness.result.onGenerationStarted("normal", {}, true); + await harness.result.onBeforeCombinePrompts(); + + assert.equal(harness.runRecallCalls.length, 0); +} + async function testGenerationRecallDifferentKeyCanRunAgain() { const harness = await createGenerationRecallHarness(); harness.chat = [{ is_user: true, mes: "第一条" }]; @@ -2874,6 +2884,19 @@ async function testAutoExtractionDefersWhenGraphNotReady() { assert.equal(statuses[0]?.[0], "等待图谱加载"); } +async function testAutoExtractionDefersWhenAlreadyExtracting() { + const deferredReasons = []; + + await runExtractionController({ + getIsExtracting: () => true, + deferAutoExtraction(reason) { + deferredReasons.push(reason); + }, + }); + + assert.deepEqual(deferredReasons, ["extracting"]); +} + async function testAutoExtractionDefersWhenHistoryRecoveryBusy() { const deferredReasons = []; @@ -3986,12 +4009,14 @@ await testGenerationRecallSameKeyCanRunAgainImmediatelyAsNewGeneration(); await testGenerationRecallSameKeyCanRunAgainAfterBridgeWindow(); await testBeforeCombineRecallNotSkippedWhenGraphLoadingButRuntimeGraphReadable(); await testGenerationRecallBeforeCombineRunsStandalone(); +await testGenerationRecallDryRunPreviewDoesNotTriggerBeforeCombineRecall(); await testGenerationRecallDifferentKeyCanRunAgain(); await testGenerationRecallSkippedStateDoesNotLoopToBeforeCombine(); await testGenerationRecallSentMessageClearsStaleTransactionForSameKey(); await testRegisterCoreEventHooksIsIdempotent(); await testChatChangedDoesNotClearCoreEventBindings(); await testAutoExtractionDefersWhenGraphNotReady(); +await testAutoExtractionDefersWhenAlreadyExtracting(); await testAutoExtractionDefersWhenHistoryRecoveryBusy(); await testRemoveNodeHandlesCyclicChildGraph(); await testGenerationRecallAppliesFinalInjectionOncePerTransaction();