unsloth/tests/studio/test_multi_chat_prompt_queue_contract.py
Daniel Han 135147814b
Fix CI on main: stale test doubles, a stale router stub, and two source defects (#8956)
* Fix CI on main: stale test doubles, a stale router stub, and two source defects

main has been red since Aug 14 and every open PR inherits it. Five clusters, none
of them caused by the PRs that were showing them.

context_length (13 tests, plus 4 more in disguise). #8700 added an unguarded
llama_backend.context_length read to the chat-completions path and updated five
test files, missing three. The real LlamaCppBackend has had the property for a
long time, so no user was ever affected; the doubles were simply
under-specified. The four gguf_stream_slot_release failures are the SAME bug:
those doubles reach the same line, but the AttributeError is swallowed into the
response task and surfaces as a 20 second timeout, which reads as a flake.

Nine hand-written doubles across five files each re-declared the same attribute
block with no shared base, so one new read broke whichever files happened not to
be updated. They now share FakeLlamaCppBackend, and a canary drives the real
route with a bare double so the next such read fails in one place, named, at the
point of the change. The stream waits no longer discard the driving task's
exception, so that class of failure cannot present as a bare timeout again.

youtube_router (2 tests). routes/__init__.py exports it and main.py imports it;
the app is fine. test_desktop_auth stubs sys.modules[routes] with a hardcoded
list of 17 routers, deliberately, to avoid importing the ML stack. #8648 added a
router and did not update it, the second time this has happened after
openai_codex_auth_router in #8511. The stub is now derived from main.py's own
import block, so it cannot go stale.

Repo tests (CPU), 6 failures, of which two are real source defects:
llama-extra-args.ts put the Studio brand into a user-visible validation message,
which the desktop branding contract forbids in runtime surfaces, and
test_playwright_server_lifecycle.py read checked-in files without an encoding,
which is a real Windows cp1252 crash the lint exists to catch. Both fixed in the
source. The other four are stale assertions chasing text that #8702 legitimately
moved or reflowed; they now assert the behaviour instead, via the real
override_lookup_candidates() and the element-scan pattern their own siblings
already use.

Not addressed here: pip scan-packages :: hf-stack reports 173 findings in
third-party deps under SCAN_ENFORCE=1 and is red on main too. Baselining a
supply-chain scanner to get green is the wrong reflex, so it wants its own look.

* Studio: break the settings/chat import cycle that stopped the UI rendering

#8932 added SIDEBAR_ORGANIZATION_STORAGE_KEY to the @/features/chat barrel and
had general-tab.tsx read it back out of that barrel. The key is used at MODULE
scope, in the storage-key list, and the barrel is part of an import cycle that
reaches this file, so the binding is still in its temporal dead zone when the
list is built:

  Cannot access 'SIDEBAR_ORGANIZATION_STORAGE_KEY' before initialization

That kills the whole module graph, so the page renders nothing. It is why
Frontend CI has been failing on main with a Playwright locator that finds no
elements, which reads as a flaky browser test rather than a module-init error.

Importing the key straight from its module breaks the cycle. Verified by
bisection with the real browser smoke: it passes at cfee13795 (before #8932),
fails on main with the TDZ error above, and passes again with this one-line
change. Typecheck clean, 2756 frontend tests pass.

#8932's own branch was already red with this exact failure before it merged.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Security: fix the filesystem-enumeration false positives in scan-packages

The hf-stack shard of pip scan-packages has been red on main. One CRITICAL
was blocking it, and tracking it down turned up a pattern bug behind nine of
the entries already in the baseline.

The blocking finding, unsloth-zoo/llama_cpp.py under "Harvests environment
variables/secrets AND makes network calls", is first-party: _github_auth_headers()
reads GH_TOKEN so the llama.cpp releases API call is not rate limited. That
file and check were already reviewed and baselined. It reopened because
unsloth-zoo 2026.8.12 replaced

    keynames = "\n" + "\n".join(os.environ.keys())

with a targeted _is_colab_environment() helper. Every other matched line is
byte identical to what was reviewed, so the current code is a strict subset of
the approved evidence. The entry is refreshed, with the hash generated by
--write-baseline rather than by hand. No PR caused this; it is the baseline's
reopen-on-change working as designed against an upstream release.

The pattern bug is in RE_FS_ENUM:

    r"|\bhistory\b.*\bread\b"  # reading shell history

Under re.DOTALL that .* spans the whole file, so any module containing the
word "history" anywhere before the word "read" anywhere is filesystem
enumeration, and with a network call in the same file that is a CRITICAL.
That is httpx's Response.history, retries.history in urllib3, IPython's
history module, torch's CUDA memory history. Nine of the eleven baselined
CRITICALs under this check were that one alternative, each suppressing a whole
file for the check.

Meanwhile the precise half was dead. \b\.bash_history\b puts \b between "/"
and "." in "~/.bash_history", where neither side is a word character, so it
could never match; same for \b\.zsh_history\b. This is the unsatisfiable-\b
bug already fixed once for /proc/self/status. Checked against the old pattern:
it matched none of five real history-file reads and all four benign cases.

Naming the files instead inverts that. The nine dead baseline entries are
removed, which narrows the allowlist rather than widening it, and three tests
pin both directions.

Verified by running all three shards locally against the same requirements
transform CI uses: hf-stack now exits 0 (was 1), studio and extras stay at 0,
and no removed entry resurfaced.

* Fix two more stale sidebar contract tests left by #8932

Both fail on pristine origin/main, so they are not from this branch. #8932
moved the sidebar's user-visible copy into the locale file and rewrote the
delete-switch predicate to cover its new bulk targets. Neither change breaks a
contract; both broke a grep of app-sidebar.tsx.

test_the_delete_switch_does_not_promise_project_files greps for the sentence
"This chat's own sandbox folder is removed from disk." It is still there,
verbatim, in studio/frontend/src/i18n/locales/en.ts. The promise is the
contract, not its address, so the test now searches the frontend sources and
survives the next move while still failing on a reworded promise.

test_the_delete_switch_reaches_a_chat_moved_into_a_project pinned one spelling
of deleteTargetHasFiles:

    -return target.kind === "project" || target.kind === "chat";
    +return target.kind !== "run";

Same answer for a chat and for a project, plus the new "chats" / "projects"
bulk kinds. What must hold is that a run is excluded and that project
membership is never consulted, so the test reads the brace-matched function
body and asserts that instead of the old one-liner.

Both were checked by mutation: reinstating the misleading copy, and gating
deleteTargetHasFiles on target.item.projectId, each fail the rewritten test.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Address the review: scope a contract test, keep a parity check, close a scanner gap

Four items, all confirmed against the code at head before changing anything.

1. tests/studio/test_model_picker_contracts.py: the rollback ordering assertion
searched the whole of chat-page.tsx, which takes the same snapshot in the hub
auto-load path at line 2558. The only applyModelLoadConfigToRuntime call is at
3262, so the index comparison was satisfied by the unrelated occurrence: deleting
the snapshot inside selectWithConfig outright still passed both assertions.
Verified by mutation. Now scoped to the selectWithConfig body, which fails on
that deletion.

2. studio/backend/tests/test_research_internal_call_tool_gate.py: dropping
perf_callback from the kwargs comparison hid presence as well as identity, so the
opt-out losing its callback on one path would have gone unnoticed and cost that
path its tok/s readout. Assert both are callable (or both absent) first, then
exclude. Verified by mutation at routes/inference.py:14512.

3. tests/studio/test_model_picker_contracts.py was source-only and ran without the
backend environment; calling the real ladder for a standalone .gguf pulled in
hub.utils.gguf, then loggers, then structlog, so a bare pytest run failed after
183 passes. CI installs studio.txt and is unaffected, so coverage there is
unchanged. The helper now skips on a missing third-party package only; a missing
first-party module still fails.

4. scripts/scan_packages.py: fish stores history at $XDG_DATA_HOME/fish/fish_history
with no leading dot (fishshell.com/docs/current/cmds/history.html), so the dotted
alternative could never match the real path and narrowing the pattern left an
exfiltration blind spot. Added a non-dotted form. The fish read plus a network call
is a CRITICAL again, the dotted shells still match, and the false positives stay
suppressed.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Tighten comments added by the CI repair

* Fix two more stale studio contract constants left by #8932

Both fail on pristine origin/main, so neither comes from this branch, and in
both the source is right and the test-side constant is stale.

INLINE_ROW_IDS in tests/studio/playwright_mac_tab_capabilities.py still listed
four rows. #7863 had put Video under "More" as layout v5, so the script dropped
it: an unpinned row renders no data-testid and every assertion on it silently
observes nothing. #8932 pins Video under Images again, and records it in the
migration history as v7, so it is deliberate and versioned rather than an
accidental revert. Video is observable again, so the script samples it again.
test_inline_row_ids_match_the_frontends_default_pinned_set exists precisely to
catch this tuple drifting from the store in either direction, and it did its job.

test_multi_chat_prompt_queue_contract.py pinned the zero-argument spelling
"return await clearStoredChats();". #8932 gave the call an options argument,
which changes nothing about the ordering the assertion is there to hold, so it
now matches on the call prefix, the same way the sibling assertion three lines
up already does.

Both mutation-checked: unpinning Video in the store fails the first, and moving
requestPromptQueueStop after clearStoredChats fails the second.

* Narrow a contract search I made too wide, and read it once

Self-audit of an earlier fix in this branch, under the same standard applied to
everything else here.

test_the_delete_switch_does_not_promise_project_files greps for a promise the
delete dialog must make. #8932 moved that copy into the locale file, so the
original grep of app-sidebar.tsx broke, and my fix widened the search to the
whole frontend tree. That is the same defect the review caught in the rollback
ordering assertion: a search wide enough to be satisfied by an unrelated
occurrence proves nothing. The sentence appearing in any of ~1200 files, a
comment or a dead module included, would have passed it.

Now scoped by intent. The promise the dialog MUST make is looked for where the
sidebar's user-visible copy lives, the locales and the component. The promise it
must NOT make is still looked for across all of src, since breadth only makes a
negative stricter.

Mutation-checked: rewording the real string and planting the original in an
unrelated module fails the test, where the whole-tree form passed.

The two scopes are also read through an lru_cache. The wide one concatenates
about 1200 files, and it was re-reading every one of them on each call.

* Address the second review: real scoping, not the appearance of it

Five items, each reproduced against the code at head before changing anything.
Three are cases where my own earlier fix looked scoped but was not.

deleteTargetHasFiles: the negative assertions did not establish the contract.
`return target.kind === "run";` -- the exact inversion, which hides the delete
switch for every chat and project -- mentions "run", mentions no projectId, and
contains neither prohibited expression, so it passed all four checks. Confirmed
by construction. Now the direction is pinned: run is the kind excluded, never
the one included.

The selectWithConfig slice ran to end of file, not to the callback's closing
brace: 13,566 characters rather than 579. Moving applyModelLoadConfigToRuntime
out of the callback while leaving the snapshot behind still passed. Brace-matched
now, and that mutation fails.

asgi_stream_helpers returned on the frame without inspecting the task, so a
send() that sets the event and then raises left both futures done, the frame
branch won, and the caller's gather(return_exceptions = True) discarded the
exception. That is the silence the helper exists to break. Reproduced directly.
The task is checked first now, and the message says whether the failure came
before or after the frame.

Two scanner gaps, both from narrowing RE_FS_ENUM. Constructed fish paths put a
quote rather than a separator before the basename, so Path.home() / "fish" /
"fish_history" and os.path.join(h, "fish", "fish_history") did not match. And the
dotted list omitted PowerShell's ConsoleHost_history.txt, Ruby's .irb_history and
SQLite's .sqlite_history. A quote now counts as a boundary and those names are
covered, case-insensitively for the Windows one. Ten read forms match, and the
httpx, urllib3, IPython and torch false positives stay suppressed.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-08-16 02:02:47 -07:00

820 lines
37 KiB
Python

# SPDX-License-Identifier: AGPL-3.0-only
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
"""Static contracts for independent per-chat prompt queues."""
import re
from pathlib import Path
REPO = Path(__file__).resolve().parents[2]
FRONTEND = REPO / "studio/frontend/src"
THREAD = (FRONTEND / "components/assistant-ui/thread.tsx").read_text(encoding = "utf-8")
APP_SIDEBAR = (FRONTEND / "components/app-sidebar.tsx").read_text(encoding = "utf-8")
CHAT_ADAPTER = (FRONTEND / "features/chat/api/chat-adapter.ts").read_text(encoding = "utf-8")
MODEL_RUNTIME = (FRONTEND / "features/chat/hooks/use-chat-model-runtime.ts").read_text(
encoding = "utf-8"
)
CONFIRM_MODEL_SWAP = (FRONTEND / "features/chat/utils/confirm-stop-running-chats.ts").read_text(
encoding = "utf-8"
)
RUNTIME_PROVIDER = (FRONTEND / "features/chat/runtime-provider.tsx").read_text(encoding = "utf-8")
CHAT_RUNTIME_STORE = (FRONTEND / "features/chat/stores/chat-runtime-store.ts").read_text(
encoding = "utf-8"
)
CHAT_PAGE = (FRONTEND / "features/chat/chat-page.tsx").read_text(encoding = "utf-8")
SHARED_COMPOSER = (FRONTEND / "features/chat/shared-composer.tsx").read_text(encoding = "utf-8")
QUEUE_BOUNDARY = (FRONTEND / "features/chat/utils/prompt-queue-boundary.ts").read_text(
encoding = "utf-8"
)
QUEUED_MODEL_CAPABILITIES = (
FRONTEND / "features/chat/utils/queued-model-capabilities.ts"
).read_text(encoding = "utf-8")
PRE_STREAM_RESERVATION = (FRONTEND / "features/chat/utils/pre-stream-run-reservation.ts").read_text(
encoding = "utf-8"
)
CHAT_CLEAR_BOUNDARY = (FRONTEND / "features/chat/utils/chat-history-clear-boundary.ts").read_text(
encoding = "utf-8"
)
CHAT_HISTORY_STORAGE = (FRONTEND / "features/chat/utils/chat-history-storage.ts").read_text(
encoding = "utf-8"
)
QUEUED_SETTINGS = (FRONTEND / "features/chat/utils/queued-chat-run-settings.ts").read_text(
encoding = "utf-8"
)
SIDEBAR_ITEMS = (FRONTEND / "features/chat/hooks/use-chat-sidebar-items.ts").read_text(
encoding = "utf-8"
)
CLEAR_ALL_CHATS = (FRONTEND / "features/chat/utils/clear-all-chats.ts").read_text(encoding = "utf-8")
STOP_CHAT_THREAD = (FRONTEND / "features/chat/utils/stop-chat-thread.ts").read_text(
encoding = "utf-8"
)
def _between(source: str, start: str, end: str) -> str:
assert start in source, f"missing start marker: {start}"
tail = source.split(start, 1)[1]
assert end in tail, f"missing end marker after {start}: {end}"
return tail.split(end, 1)[0]
def test_scheduler_dispatches_each_ready_chat_without_a_frontend_global_cap():
pump = _between(
THREAD,
"function pumpPromptQueues()",
"async function dispatchQueuedPrompt(",
)
assert "while (true)" in pump
assert pump.index("promptQueueDispatchingRunIds.add(run.id)") < pump.index(
"dispatchQueuedPrompt(run, item, run.generation)"
)
assert "PROMPT_QUEUE_GLOBAL_CONCURRENCY" not in THREAD
assert "promptQueueHasCapacity" not in THREAD
assert "const reservations = new Map<symbol" in PRE_STREAM_RESERVATION
assert "reservationByThreadId" in PRE_STREAM_RESERVATION
assert "let preStreamRunReservations = 0" not in PRE_STREAM_RESERVATION
def test_each_chat_queue_stays_sequential_and_targets_its_background_runtime():
target = _between(
THREAD,
"const createPromptQueueTarget = useCallback(",
"const dismissWaitToast",
)
state_handler = _between(
THREAD,
"function handlePromptQueueRunState(",
"function ensurePromptQueueSubscription(",
)
assert "runtime.threads.getById(id)" in target
assert "setActiveThreadId" not in target
assert "return runningIds.length > 0" not in THREAD
assert "isPromptQueueRunTargetRunning(run" in state_handler
assert "advancePromptQueue(run)" in state_handler
assert "promptQueueActiveRunIds.has(run.id)" in THREAD
assert "Boolean(getActivePromptQueueItem(run)?.dispatched)" in THREAD
append = _between(
THREAD,
"function appendQueuedPrompt(",
"async function targetHasIndexingDocuments(",
)
assert "schedulePromptQueueTargetStatePoll(run)" in append
indexing_probe = _between(
THREAD,
"async function targetHasIndexingDocuments(",
"function getActivePromptQueueItem(",
)
assert "const documents = await listThreadDocuments(threadId)" in indexing_probe
assert "catch {\n // A failed status probe" in indexing_probe
assert "return true;" in indexing_probe
def test_saved_queues_survive_navigation_but_abandoned_temporary_queues_stop():
saved_switch = _between(
RUNTIME_PROVIDER,
"function ThreadAutoSwitch(",
"function ThreadNewChatSwitch(",
)
temporary_switch = _between(
RUNTIME_PROVIDER,
"function ThreadNewChatSwitch(",
"function ActiveThreadSync(",
)
assert "requestPromptQueueStop" not in saved_switch
assert "requestTemporaryPromptQueueStop()" in saved_switch
assert "switchToThread(threadId)" in saved_switch
assert "requestTemporaryPromptQueueStop()" in temporary_switch
assert "switchToNewThread()" in temporary_switch
temporary_toggle = _between(
CHAT_PAGE,
"const toggleIncognito = useCallback(",
"const hydratePersistedSettings",
)
assert "if (wasIncognito)" in temporary_toggle
assert "requestTemporaryPromptQueueStop()" in temporary_toggle
assert temporary_toggle.index("requestTemporaryPromptQueueStop()") < temporary_toggle.index(
"if (onEmptyScratchChat) return"
)
cancel_registrar = _between(
RUNTIME_PROVIDER,
"function CancelRegistrar()",
"function ThreadBackendAutosave(",
)
assert "thread?.getState().isRunning" in cancel_registrar
assert "unsubscribe = thread.subscribe(" in cancel_registrar
assert "threadListItem.remoteId" in cancel_registrar
assert "registerThreadCancel(threadId, cancel)" in cancel_registrar
assert "clearThreadCancel(threadId, cancel)" in cancel_registrar
assert cancel_registrar.index("thread.subscribe(") < cancel_registrar.rindex(
"clearThreadCancel(threadId, cancel)"
)
assert "state.cancelByThreadId[threadId] !== cancel" in CHAT_RUNTIME_STORE
def test_composer_only_queues_behind_the_current_chat():
submit = _between(
THREAD,
"const handleSubmit = useCallback(",
"const stopQueue = useCallback(",
)
assert "aui.thread().getState().isRunning" in submit
assert "usePromptQueueUI.getState()" in submit
assert "livePreStreamRunActive" in submit
assert "liveThreadIsRunning || livePreStreamRunActive" in submit
assert "startHydratedPromptQueue(" in submit
assert "aui.composer().getState().text.trim() !== queuedPrompt" in submit
assert "promptQueueStartPendingRef.current" in THREAD
assert "promptQueueStartPendingRef.current.has(reservationKey)" in THREAD
assert "promptQueueStartPendingRef.current.delete(reservationKey)" in THREAD
assert "promptQueueStartPendingRef.current.set(reservationKey, reservation)" in THREAD
assert "temporary: useChatRuntimeStore.getState().incognito" in THREAD
assert "localPromptQueueModelBoundary.capture()" in THREAD
assert "shouldAbortPendingQueueForModelBoundary" in THREAD
assert "queuedSettingsEpoch:" in THREAD
assert "shouldAbortPendingQueueForSettingsChange" in THREAD
assert "capturedEpoch: reservation.queuedSettingsEpoch" in THREAD
assert "currentEpoch: currentQueueSettings.queuedSettingsEpoch" in THREAD
assert "capturedTemporary: reservation.temporary" in THREAD
assert "currentTemporary: currentQueueSettings.incognito" in THREAD
assert "!settingsInvalidated" in THREAD
assert "reservation.cancelled = true" in THREAD
assert "temporaryOnly && !reservation.temporary" in THREAD
assert "onAborted?.()" in THREAD
assert 'toast.info("Saved list was not queued"' in THREAD
assert ".finally(() =>" in THREAD
assert "anyPromptQueueRunning" not in submit
assert "promptQueueAtCapacity" not in submit
assert "sendReservedComposer();" in submit
assert "reservePreStreamRun(preStreamThreadIds, {" in THREAD
assert "usesLocalModel:" in THREAD
assert "aui.threads().__internal_getAssistantRuntime?.()" in THREAD
assert "threads.getById(reservedThreadId).cancelRun()" in THREAD
assert "adoptPreStreamRunReservation(token, preStreamThreadIds)" in THREAD
assert "hasPreStreamRunReservation(getQueueThreadIds())" in THREAD
append_failure = _between(
THREAD,
"function handleQueuedPromptAppendFailure(",
"function consumePromptQueueDeepResearch(",
)
terminal_failure = append_failure.split(
"if (item.dispatchRetries > PROMPT_QUEUE_MAX_DISPATCH_RETRIES)", 1
)[1]
assert terminal_failure.index("item.target.cancel();") < terminal_failure.index(
"deletePromptQueueRun(run);"
)
assert terminal_failure.index("deletePromptQueueRun(run);") < terminal_failure.index(
"item.target.complete();"
)
assert "releaseCurrentPreStreamRun();" in CHAT_ADAPTER
assert "releasePreStreamRunReservation(reservationToken)" in CHAT_ADAPTER
assert "class PreStreamAwareAttachmentAdapter" in RUNTIME_PROVIDER
assert "preStreamRunThreadIdsForRuntime(" in RUNTIME_PROVIDER
attachment_adapter = _between(
RUNTIME_PROVIDER,
"const attachments = useMemo(",
"const adapters = useMemo(",
)
assert "[state.remoteId, state.id]" in attachment_adapter
assert "useChatRuntimeStore.getState().activeThreadId" in attachment_adapter
assert "preStreamRunThreadIdsForAdapter(" in CHAT_ADAPTER
adapter_wrapper = CHAT_ADAPTER.rsplit("async *run(args)", 1)[1]
assert "args.unstable_threadId," in adapter_wrapper
assert "useChatRuntimeStore.getState().activeThreadId" in adapter_wrapper
persisted_wrapper = _between(
RUNTIME_PROVIDER,
"function createPersistedRunAdapter(",
"function useStudioRuntimeAdapters(",
)
assert persisted_wrapper.index(
"const trackedRunStartThreadIds = runStartThreadIdsForMessages("
) < persisted_wrapper.index("findPreStreamRunReservation(reservationThreadIds)")
assert "[options.unstable_threadId, ...trackedRunStartThreadIds]" in persisted_wrapper
assert "findPreStreamRunReservation(reservationThreadIds)" in persisted_wrapper
assert "await waitForRunStartHistoryAppend(options.messages)" in persisted_wrapper
assert "releasePreStreamRunReservation(reservationToken)" in persisted_wrapper
assert "notifyPromptQueueRunFailed(" in persisted_wrapper
persisted_failure = _between(
persisted_wrapper,
"} catch (error) {",
"throw error;",
)
assert persisted_failure.index("releasePreStreamRunReservation(reservationToken)") < (
persisted_failure.index("notifyPromptQueueRunFailed(")
)
assert re.search(
r"releasePreStreamRunReservation\(reservationToken\);\s*}\s*"
r"//.*?notifyPromptQueueRunFailed\(",
persisted_failure,
re.S,
), "queue failure notification must not depend on a direct-send reservation"
def test_queued_settings_are_thread_scoped_without_cross_chat_fallback():
target = _between(
THREAD,
"const createPromptQueueTarget = useCallback(",
"const dismissWaitToast",
)
assert "await useChatRuntimeStore.getState().hydratePersistedSettings()" in target
assert target.index(
"await useChatRuntimeStore.getState().hydratePersistedSettings()"
) < target.index("snapshotQueuedChatRunSettings(chatStateAtQueueStart)")
assert "!promptQueueTargetMountedRef.current" in target
assert "const currentState = aui.threadListItem().getState()" in target
assert "initialRunningThreadIds.includes(id)" in target
assert "snapshotQueuedChatRunSettings(chatStateAtQueueStart)" in target
assert "registerQueuedChatRunSettings(" in target
assert "params: { ...runSettingsAtQueueStart.params }" in target
assert "runSettingsAtQueueStart.deepResearchEnabled = false" in target
assert target.index("const appendResult = thread.append(") < target.index(
"runSettingsAtQueueStart.deepResearchEnabled = false"
)
assert "void (appendResult as Promise<void>).catch(() => undefined)" in target
assert "function consumePromptQueueDeepResearch(" in THREAD
assert "!item.target.usesDeepResearch" in THREAD
assert ".then(() => consumePromptQueueDeepResearch(run, item))" in THREAD
assert "usesDeepResearch: runSettingsAtQueueStart.deepResearchEnabled" in target
assert "if (existingRun.deepResearchConsumed)" in THREAD
assert "addQueuedChatRunSettingsThreadIds(settingsId" in target
assert ".getItemById(state.id)\n .initialize()" in target
assert "await updateStoredChatThread(remoteId" in target
assert "let shouldCorrectPersistedModel: boolean | null = null" in target
assert "shouldCorrectPersistedModel ??= !state.remoteId" in target
assert "if (shouldCorrectPersistedModel)" in target
assert (
target.index("await updateStoredChatThread(remoteId")
< target.index("shouldCorrectPersistedModel = false")
< target.index("const appendResult = thread.append(")
)
assert 'modelId: runSettingsAtQueueStart.params.checkpoint ?? ""' in target
assert target.index("await updateStoredChatThread(remoteId") < target.index(
"const appendResult = thread.append("
)
assert (
target.index("addQueuedChatRunSettingsThreadIds(settingsId")
< target.index("syncPromptQueueUI()")
< target.index("const appendResult = thread.append(")
)
assert "let cancelled = false" in target
assert target.count("cancelled ||") >= 2
assert target.count("!pendingSettingsIds.has(settingsId)") >= 2
assert target.index("!pendingSettingsIds.has(settingsId)") < target.index(
"const appendResult = thread.append("
)
assert "cancelled = true" in target
assert "isTargetCurrentThread() &&" in target
assert "consumeQueuedChatRunSettings(resolvedThreadId)" in CHAT_ADAPTER
assert '"deepResearchEnabled"' in QUEUED_SETTINGS
assert '"supportsReasoning"' in QUEUED_SETTINGS
assert '"reasoningAlwaysOn"' in QUEUED_SETTINGS
assert '"reasoningStyle"' in QUEUED_SETTINGS
assert '"supportsReasoningOff"' in QUEUED_SETTINGS
assert '"reasoningEffortLevels"' in QUEUED_SETTINGS
assert '"supportsPreserveThinking"' in QUEUED_SETTINGS
assert '"researchWebsitePolicy"' in QUEUED_SETTINGS
assert CHAT_ADAPTER.index(
"consumeQueuedChatRunSettings(resolvedThreadId)"
) < CHAT_ADAPTER.index("if (runtime.deepResearchEnabled && threadAlreadyResearched)")
research = _between(
CHAT_ADAPTER,
"if (\n runtime.deepResearchEnabled",
"const sandboxSessionId",
)
assert "const liveRuntime = useChatRuntimeStore.getState()" in research
assert "...queuedRunSettings" in research
auto_load_merge = _between(
CHAT_ADAPTER,
"// Re-read store after auto-load / model-ready wait.",
"const { params } = runtime",
)
assert "...queuedRunSettings.params" in auto_load_merge
assert "queuedEmptyModelRuntime?.checkpoint" in auto_load_merge
assert "liveRuntime.params.checkpoint" in auto_load_merge
assert "liveRuntime.supportsTools" in auto_load_merge
assert "liveRuntime.supportsReasoning" in auto_load_merge
assert "liveRuntime.ggufContextLength" in auto_load_merge
assert "isExternalModelId(visibleState.params.checkpoint)" in CHAT_ADAPTER
assert "resolveInferenceCheckpointId(status)" in CHAT_ADAPTER
assert "skipAdoptServerModel: true" in CHAT_ADAPTER
assert "snapshotVisibleModelState(" in CHAT_ADAPTER
assert "restoreVisibleModelState(visibleExternalState)" in CHAT_ADAPTER
assert '"ggufContextLength"' in CHAT_ADAPTER
assert '"ggufMaxContextLength"' in CHAT_ADAPTER
assert '"ggufNativeContextLength"' in CHAT_ADAPTER
assert '"loadedIsMultimodal"' in CHAT_ADAPTER
assert '"loadedIsDiffusion"' in CHAT_ADAPTER
assert (
'"contextUsage"'
not in CHAT_ADAPTER[
CHAT_ADAPTER.index("const VISIBLE_MODEL_RUNTIME_KEYS") : CHAT_ADAPTER.index(
"] as const satisfies", CHAT_ADAPTER.index("const VISIBLE_MODEL_RUNTIME_KEYS")
)
]
)
assert "contextUsage: liveUsage.contextUsage" in CHAT_ADAPTER
assert "contextUsageByThreadId: liveUsage.contextUsageByThreadId" in CHAT_ADAPTER
assert "visibleState.activeThreadEpoch" in CHAT_ADAPTER
assert "activeThreadEpoch ===" in CHAT_ADAPTER
assert "visibleState.queuedSettingsEpoch" in CHAT_ADAPTER
assert "queuedSettingsEpoch ===" in CHAT_ADAPTER
assert "preserveVisibleSettings: true" in CHAT_ADAPTER
assert "captureResolvedRuntime: (runtime) =>" in CHAT_ADAPTER
assert "applyAutoLoadRuntimeState(options" in CHAT_ADAPTER
assert CHAT_ADAPTER.count("trackQueuedSettings: !options?.preserveVisibleSettings") >= 4
assert "const visibleRoute = window.location.href" in CHAT_ADAPTER
assert "window.location.href === visibleRoute" in CHAT_ADAPTER
assert "trackQueuedSettings: false" in CHAT_ADAPTER
assert CHAT_ADAPTER.count("await resolveQueuedEmptyLocalModel(abortSignal)") >= 2
assert "persist: !options?.preserveVisibleSettings" in CHAT_ADAPTER
assert "beginModelLoading()" in CHAT_ADAPTER
assert "endModelLoading(lifecycleLease)" in CHAT_ADAPTER
lifecycle = _between(
CHAT_ADAPTER,
"async function resolveQueuedEmptyLocalModel(",
"export function createOpenAIStreamAdapter",
)
assert lifecycle.index("beginModelLoading()") < lifecycle.index("await getInferenceStatus()")
assert lifecycle.index("await getInferenceStatus()") < lifecycle.index(
"await autoLoadSmallestModel("
)
assert "getInferenceStatus().catch(() => null)" not in lifecycle
assert "const status = await getInferenceStatus();" in lifecycle
assert "options?.abortSignal?.throwIfAborted()" in CHAT_ADAPTER
assert CHAT_ADAPTER.count("await persistResolvedQueuedModel(params.checkpoint)") >= 2
assert "notifyQueuedRunFailed" not in CHAT_ADAPTER
assert "pendingSettings.length === 1" not in QUEUED_SETTINGS
assert "entry.threadIds.has(threadId)" in QUEUED_SETTINGS
assert "return pendingSettings[index].settings" in QUEUED_SETTINGS
assert "pendingSettings.splice(index, 1)[0].settings" not in QUEUED_SETTINGS
assert "complete: discardOldestPendingSettings" in target
assert "getActivePromptQueueItem(run)?.target.complete()" in THREAD
assert "adapterRunStartedSignals" not in CHAT_ADAPTER
assert "pendingSettings.some((entry) => entry.threadIds.has(threadId))" in QUEUED_SETTINGS
queued_run_failure = _between(
CHAT_ADAPTER,
"try {\n yield* adapter.run(args);",
"} finally {",
)
assert "if (!args.abortSignal.aborted)" in queued_run_failure
assert queued_run_failure.index("notifyPromptQueueRunFailed(") < queued_run_failure.index(
"throw error;"
)
queue_failure_handler = _between(
THREAD,
"function handlePromptQueueRunFailed(",
'if (typeof window !== "undefined")',
)
assert "if (failedRun)" in queue_failure_handler
assert "retainPendingPromptQueueItemsAfterFailure(failedRun)" in queue_failure_handler
assert "deletePromptQueueRun(failedRun);" in queue_failure_handler
retained_failure = _between(
THREAD,
"function retainPendingPromptQueueItemsAfterFailure(run: PromptQueueRun)",
"function cancelPendingPromptQueueFactoriesForStop<",
)
assert retained_failure.index("activeItem.target.complete();") < retained_failure.index(
"run.items.splice(activeIndex, 1);"
)
assert "waitForPromptQueueTargetIdle(run);" in retained_failure
local_queue_stop = _between(
THREAD,
"function stopLocalPromptQueueRun(run: PromptQueueRun)",
"function stopLocalPromptQueueRunsForThreadIds(threadIds: string[])",
)
assert "if (plan.refreshTargetIdleWait)" in local_queue_stop
assert "refreshPromptQueueTargetIdleWait(run);" in local_queue_stop
assert "claimPreStreamRunReservation(reservationToken);" in RUNTIME_PROVIDER
assert "if (!reservation.claimed)" in PRE_STREAM_RESERVATION
assert "loadedIsMultimodal: isMultimodalResponse(status)" in lifecycle
assert "isAudio: status.is_audio ?? false" in lifecycle
assert "hasAudioInput: status.has_audio_input ?? false" in lifecycle
assert CHAT_ADAPTER.count("models: mergeQueuedModelCapabilities(") == 2
assert "modelIndex === index ? { ...model, ...capabilities } : model" in (
QUEUED_MODEL_CAPABILITIES
)
assert "loadedIsMultimodal: state.loadedIsMultimodal" in CHAT_ADAPTER
assert "queuedEmptyModelRuntime?.loadedIsMultimodal" in auto_load_merge
assert "usesLocalModel:" in target
assert "usePromptQueueUI.getState().byThreadId" in CONFIRM_MODEL_SWAP
assert "getLocalPromptQueueThreadIds" in CONFIRM_MODEL_SWAP
assert "promptQueueThreadIds" in MODEL_RUNTIME
assert MODEL_RUNTIME.count("requestLocalPromptQueueStop(") >= 4
assert MODEL_RUNTIME.index("requestLocalPromptQueueStop();") < MODEL_RUNTIME.index(
"const loadResponse = await loadModel("
)
eject = _between(
MODEL_RUNTIME,
"const ejectModel = useCallback(",
"return {",
)
select_model = _between(
MODEL_RUNTIME,
"const selectModel = useCallback(",
"const ejectModel = useCallback(",
)
assert (
select_model.index("beginModelLoading()")
< select_model.index("await confirmStopRunningChatsIfNeeded(")
< select_model.index("cancelPreStreamRunReservations(stopDecision.preStreamRunTokens)")
< select_model.index("requestLocalPromptQueueStop(stopDecision.promptQueueThreadIds)")
)
assert "beginModelLoading()" in eject
assert "endModelLoading(lifecycleLease)" in eject
assert "beginModelLoading()" in SHARED_COMPOSER
assert "endModelLoading(compareLifecycleLease)" in SHARED_COMPOSER
assert SHARED_COMPOSER.count("releaseCompareModelLifecycle();") >= 3
compare_upgrade = _between(
SHARED_COMPOSER,
"const upgraded = await confirmTransformersUpgradeIfNeeded({",
"});",
)
assert "forceCancelActive:" in compare_upgrade
assert "compareStopDecision?.forceCancelActive ?? false" in compare_upgrade
compare_handle = _between(
SHARED_COMPOSER,
"export function RegisterCompareHandle(",
"type PendingImage =",
)
assert "aui.threads().__internal_getAssistantRuntime?.()" in compare_handle
assert "runtime?.threads.getById(threadId)" in compare_handle
assert "thread.subscribe(" in compare_handle
assert "useChatRuntimeStore.subscribe(" not in compare_handle
gpu_discovery = _between(
SHARED_COMPOSER,
"// Warm the device cache before the snapshot below",
"// The GPU/offload knobs both compare loads must use",
)
assert "await ensureGpuDeviceCache();" in gpu_discovery
assert "catch (error) {\n releaseCompareModelLifecycle();" in gpu_discovery
side_one = _between(
SHARED_COMPOSER,
"// Side 1: load → generate → wait",
"// Side 2: load → generate → wait",
)
assert (
side_one.index("const status1 = await ensureModelLoaded(model1)")
< side_one.index("releaseCompareModelLifecycle();")
< side_one.index("handle1.startRun()")
)
side_two = _between(
SHARED_COMPOSER,
"// Side 2: load → generate → wait",
"compareStepSucceededRef.current = true",
)
assert (
side_two.index("acquireCompareModelLifecycle();")
< side_two.index("await confirmStopRunningChatsIfNeeded(")
< side_two.index("compareStopDecision = currentStopDecision")
< side_two.index("const status2 = await ensureModelLoaded(model2)")
< side_two.index("releaseCompareModelLifecycle();")
< side_two.index("handle2.startRun()")
)
assert "requestLocalPromptQueueStop" in eject
assert (
eject.index("beginModelLoading()")
< eject.index("await confirmStopRunningChatsIfNeeded(")
< eject.index("cancelPreStreamRunReservations(stopDecision.preStreamRunTokens)")
< eject.index("requestLocalPromptQueueStop(stopDecision.promptQueueThreadIds)")
)
assert "function promptQueueRunUsesLocalModel(run: PromptQueueRun)" in THREAD
assert ".slice(Math.max(run.index, 0))" in THREAD
assert ".some((item) => item.target.usesLocalModel)" in THREAD
assert "local: promptQueueRunUsesLocalModel(run)" in THREAD
assert "detail: { threadIds, localOnly: true }" in QUEUE_BOUNDARY
assert "stopLocalPromptQueueRunsForThreadIds(threadIds ?? [])" in THREAD
local_queue_stop = _between(
THREAD,
"function stopLocalPromptQueueRun(run: PromptQueueRun)",
"function stopLocalPromptQueueRunsForThreadIds(threadIds: string[])",
)
assert "planLocalPromptQueueStop(" in local_queue_stop
assert "activeItem?.target.cancel();" in local_queue_stop
assert "waitForPromptQueueTargetIdle(run);" in local_queue_stop
pending_factory_stop = _between(
THREAD,
"function cancelPendingPromptQueueFactoriesForStop<",
"function stopAllPromptQueueRuns()",
)
assert pending_factory_stop.index("if (localOnly)") < pending_factory_stop.index(
"for (const [key, reservation]"
)
assert "cancelPendingPromptQueueFactoriesForStop(" in THREAD
assert "temporary: incognitoAtQueueStart" in THREAD
assert "temporary: promptQueueRunIsTemporary(run)" in THREAD
assert "dispatched: Boolean(getActivePromptQueueItem(run)?.dispatched)" in THREAD
assert "queueEntry?.dispatched" in THREAD
assert 'aria-label="Stop queued message"' in THREAD
assert "entry.temporary" in QUEUE_BOUNDARY
assert "localPromptQueueModelBoundary.advance()" in QUEUE_BOUNDARY
assert "entry.local" in QUEUE_BOUNDARY
assert "queuedRunSettings.params.checkpoint" in CHAT_ADAPTER
persisted_adapter = _between(
RUNTIME_PROVIDER,
"function createPersistedRunAdapter(",
"function useStudioRuntimeAdapters(",
)
assert "const trackedRunStartThreadIds = runStartThreadIdsForMessages(" in persisted_adapter
assert "isPreStreamRunReservationCancelled(reservationToken)" in persisted_adapter
assert persisted_adapter.count("throwIfReservationCancelled();") == 2
assert persisted_adapter.index(
"requestPromptQueueStop(persistedRunThreadIds)"
) < persisted_adapter.index("notifyPromptQueueRunFailed(")
assert "pendingRunStartThreadIdsByMessageId" in RUNTIME_PROVIDER
assert "localThreadId," in RUNTIME_PROVIDER
successful_persisted_preflight = _between(
RUNTIME_PROVIDER,
"async function waitForRunStartHistoryAppend(",
"function createPersistedRunAdapter(",
)
assert successful_persisted_preflight.index(
"pendingRunStartReadyByMessageId.delete(userMessage.id)"
) < successful_persisted_preflight.index(
"pendingRunStartThreadIdsByMessageId.delete(userMessage.id)"
)
assert "!runningByThreadId[threadId] && !cancel" in STOP_CHAT_THREAD
assert "serverCancels.length === 0" in STOP_CHAT_THREAD
assert "await confirmStopRunningChatsIfNeeded(" in SHARED_COMPOSER
send_flow = _between(
SHARED_COMPOSER,
"async function send()",
"sendRef.current = send;",
)
assert "const submittedText = text;" in send_flow
assert "const submittedImages = pendingImages;" in send_flow
assert "const submittedAudio = pendingAudio;" in send_flow
assert "textRef.current === submittedText" in send_flow
assert "pendingImagesRef.current === submittedImages" in send_flow
assert "pendingAudioRef.current === submittedAudio" in send_flow
confirm_index = send_flow.index("await confirmStopRunningChatsIfNeeded(")
first_draft_check = send_flow.index("if (!submittedDraftIsCurrent())")
gpu_discovery_index = send_flow.index("await ensureGpuDeviceCache();")
second_draft_check = send_flow.index(
"if (!submittedDraftIsCurrent())",
gpu_discovery_index,
)
assert (
send_flow.index("beginModelLoading()")
< confirm_index
< first_draft_check
< gpu_discovery_index
< second_draft_check
< send_flow.index("clearSubmittedDraft();")
)
assert "requestLocalPromptQueueStop(" in SHARED_COMPOSER
assert "compareStopDecision?.preStreamRunTokens ?? []" in SHARED_COMPOSER
assert SHARED_COMPOSER.index("requestLocalPromptQueueStop(") < SHARED_COMPOSER.index(
"const resp = await loadModel("
)
apply_compare_stop = _between(
send_flow,
"const applyCompareStopDecision = () => {",
"// Helper: load a model and update store checkpoint",
)
assert "cancelPreStreamRunReservations(" in apply_compare_stop
assert "compareStopDecision?.preStreamRunTokens ?? []" in apply_compare_stop
assert "requestLocalPromptQueueStop(" in apply_compare_stop
assert "compareStopDecision?.promptQueueThreadIds" in apply_compare_stop
ensure_compare_model = _between(
send_flow,
"async function ensureModelLoaded(",
"// Side 1: load",
)
already_active = _between(
ensure_compare_model,
"if (isAlreadyActive && !config && !loadedFromConfig) {",
"}",
)
assert already_active.index("applyCompareStopDecision();") < already_active.index(
'return "ready";'
)
assert ensure_compare_model.count("applyCompareStopDecision();") == 2
validated_load_stop = ensure_compare_model.rindex("applyCompareStopDecision();")
assert (
ensure_compare_model.index("const validation = await validateModel(")
< validated_load_stop
< ensure_compare_model.index("const resp = await loadModel(")
)
assert "force_cancel_active:" in SHARED_COMPOSER
assert (
"resolvedThreadId ===\n useChatRuntimeStore.getState().activeThreadId"
in CHAT_ADAPTER
)
assert (
"findLatestUserAudioBase64(\n survivingMessages,\n !queuedRunSettings"
in CHAT_ADAPTER
)
assert "if (audioBase64 && !queuedRunSettings)" in CHAT_ADAPTER
assert ".setThreadContextUsage(usageThreadKey, usage)" in CHAT_ADAPTER
assert (
"usageThreadIsVisible &&\n"
" useChatRuntimeStore.getState().params.checkpoint === params.checkpoint"
in CHAT_ADAPTER
)
def test_compare_prompt_list_resets_when_preflight_never_starts_a_run():
reset = _between(
SHARED_COMPOSER,
"function resetPromptQueue()",
"function advanceQueue()",
)
assert "isQueueRunningRef.current = false;" in reset
assert "setIsQueueRunning(false);" in reset
assert "queueRef.current = [];" in reset
assert "queueIndexRef.current = 0;" in reset
assert "setQueueProgress({ current: 0, total: 0 });" in reset
send_flow = _between(
SHARED_COMPOSER,
"async function send()",
"sendRef.current = send;",
)
unavailable_lifecycle = _between(
send_flow,
"if (compareLifecycleLease === null)",
"const releaseCompareModelLifecycle = () =>",
)
assert "resetPromptQueue();" in unavailable_lifecycle
failed_preflight = _between(
send_flow,
"compareStopDecision = await confirmStopRunningChatsIfNeeded(",
"if (!compareStopDecision.proceed)",
)
assert "resetPromptQueue();" in failed_preflight
declined_preflight = _between(
send_flow,
"if (!compareStopDecision.proceed)",
"if (!submittedDraftIsCurrent())",
)
assert (
declined_preflight.index("releaseCompareModelLifecycle();")
< declined_preflight.index("resetPromptQueue();")
< declined_preflight.index("return;")
)
changed_draft = _between(
send_flow,
"const keepChangedDraft = () =>",
"const clearSubmittedDraft = () =>",
)
assert "releaseCompareModelLifecycle();" in changed_draft
assert "resetPromptQueue();" in changed_draft
failed_gpu_discovery = _between(
send_flow,
"// Warm the device cache before the snapshot below",
"// The GPU/offload knobs both compare loads must use",
)
assert "resetPromptQueue();" in failed_gpu_discovery
compare_run = _between(
send_flow,
"setComparing(true);",
"} else {",
)
failed_compare = _between(compare_run, "} catch (err) {", "} finally {")
assert "compareStepSucceededRef.current = false;" in failed_compare
assert "resetPromptQueue();" in failed_compare
def test_clear_all_invalidates_and_removes_late_fresh_thread_initialization():
target = _between(
THREAD,
"const createPromptQueueTarget = useCallback(",
"const dismissWaitToast",
)
assert "const historyClearGeneration = chatHistoryClearBoundary.capture()" in target
assert "chatHistoryClearBoundary.capture() !== historyClearGeneration" in target
assert "if (initializingFreshThread)" in target
assert "initializedFreshThreadId = remoteId" in target
assert "freshThreadAppendAccepted = true" in target
assert "removeFreshThreadPersistedAfterAbort()" in target
assert "removeFreshThreadPersistedAfterAbort(true)" not in target
assert "markChatThreadDeleted(initializedFreshThreadId)" in target
assert "deleteStoredChatThreads([initializedFreshThreadId])" in target
assert "aui.threads().switchToNewThread()" in target
assert "chatHistoryClearBoundary.advance();" in CLEAR_ALL_CHATS
assert CLEAR_ALL_CHATS.index("chatHistoryClearBoundary.advance();") < CLEAR_ALL_CHATS.index(
"requestPromptQueueStop();"
)
# Matched on the call prefix, not the whole call: #8932 gave clearStoredChats an options
# argument, which changes nothing about the ordering this pins.
assert CLEAR_ALL_CHATS.index("requestPromptQueueStop();") < CLEAR_ALL_CHATS.index(
"return await clearStoredChats("
)
assert "const historyClearGeneration = chatHistoryClearBoundary.capture();" in RUNTIME_PROVIDER
assert "await throwIfHistoryWasCleared(initialized.remoteId);" in RUNTIME_PROVIDER
assert "await throwIfHistoryWasCleared(remoteId);" in RUNTIME_PROVIDER
assert "trackStoredChatThreadRecord(" in RUNTIME_PROVIDER
assert "class ChatHistoryClearBoundary" in CHAT_CLEAR_BOUNDARY
assert "capture(): number" in CHAT_CLEAR_BOUNDARY
assert "advance(): number" in CHAT_CLEAR_BOUNDARY
assert "const reopenAdmission = threadRecordWrites.closeAdmission();" in CHAT_HISTORY_STORAGE
assert (
"const pendingThreadIds = threadRecordWrites.idsRequiringFence();" in CHAT_HISTORY_STORAGE
)
assert "tombstoneThreadIds: idsToFence" in CHAT_HISTORY_STORAGE
assert "threadRecordWrites.confirmFinalState(idsToFence);" in CHAT_HISTORY_STORAGE
def test_a_failed_thread_row_write_surfaces_to_the_patch_caller():
"""A retry that reports undefined reads as "no row to update", so the queued run's
model correction is dropped and never retried: thread.tsx clears
shouldCorrectPersistedModel right after the awaited updateStoredChatThread."""
retry = _between(
CHAT_HISTORY_STORAGE,
"async function retryFailedThreadRecord(",
"export async function listStoredChatMessages(",
)
# awaiting the tracked write, not the settle-all helper, is what propagates the failure
assert "await trackStoredChatThreadRecord(threadId, createRecord);" in retry
assert "await awaitStoredChatThreadWrites(threadId);\n return" not in retry
def test_noop_setting_refreshes_do_not_invalidate_pending_queues():
assert "shouldAdvanceQueuedSettingsEpoch(" in CHAT_RUNTIME_STORE
set_params = _between(CHAT_RUNTIME_STORE, "setParams: (params, options)", "setCustomPresets:")
assert "state.params," in set_params
assert "params," in set_params
assert "queuedSettingsChanged" in set_params
set_checkpoint = _between(
CHAT_RUNTIME_STORE,
"setCheckpoint: (modelId, ggufVariant, options)",
"setActiveThreadId:",
)
assert "nextGgufVariant" in set_checkpoint
assert "nextDeepResearchEnabled" in set_checkpoint
assert "queuedSettingsChanged" in set_checkpoint
def test_stop_delete_archive_and_clear_are_thread_scoped():
stop_listener = _between(
THREAD,
"window.addEventListener(PROMPT_QUEUE_STOP_EVENT",
"window.addEventListener(PROMPT_QUEUE_RUN_FAILED_EVENT",
)
assert "stopPromptQueueRunForThreadIds(threadIds)" in stop_listener
assert "requestPromptQueueStop(toArchive.map((thread) => thread.id));" in SIDEBAR_ITEMS
assert "requestPromptQueueStop(threadIds);" in SIDEBAR_ITEMS
assert "requestPromptQueueStop();" in CLEAR_ALL_CHATS
assert "serverCancelByThreadId" in CLEAR_ALL_CHATS
assert "stopChatThread(threadId)" in CLEAR_ALL_CHATS
assert "detail: { threadIds, temporaryOnly: true }" in QUEUE_BOUNDARY
assert "if (temporaryOnly)" in THREAD
assert "threadIds !== undefined && threadIds.length === 0" in QUEUE_BOUNDARY
assert "detail: threadIds ? { threadIds } : undefined" in QUEUE_BOUNDARY
assert "const aliasesByQueuedRun = new Map<string, string[]>()" in CONFIRM_MODEL_SWAP
assert "aliases.some((threadId) => runningIds.has(threadId))" in CONFIRM_MODEL_SWAP
def test_sidebar_exposes_queue_activity_for_each_thread():
assert "const queueByThreadId = usePromptQueueUI((s) => s.byThreadId);" in APP_SIDEBAR
assert "hasQueuedActivity" in APP_SIDEBAR
assert "showWorkSpinner" in APP_SIDEBAR
assert "{showWorkSpinner && (" in APP_SIDEBAR
assert "hasUnreadActivity" in APP_SIDEBAR
assert "clearChatNotifications(item)" in APP_SIDEBAR