-
-
Notifications
You must be signed in to change notification settings - Fork 265
refactor(parse-once): single-grammar reasoning + tool-call parsing, native-first for Gemma #510
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 21 commits
Commits
Show all changes
527 commits
Select commit
Hold shift + click to select a range
ce679cd
docs(checklist): mark T001/T022/T023 automated (✅) with their red tes…
alichherawalla b7b2edb
docs(checklist): T024 honest coverage split — budgetRedflow (arithmet…
alichherawalla 0df8306
test(harness): add driveable whisper.rn STT fake (realtime + file) to…
alichherawalla 9ee2eb3
test(harness): STT chat-mode support — whisper option, setupWhisperMo…
alichherawalla 9bf9ca0
test(T080/T075): red — chat-mode STT never transcribes to text (DEV-B…
alichherawalla 5f86b38
docs(checklist): mark T075/T080 automated (chatModeSttArchitecture red)
alichherawalla 4b47f8f
docs(checklist): reconcile count to 52 automated (incl. T075/T080 STT)
alichherawalla c8f6c9f
docs(checklist): T031 marked n/a (device-stress observation, ignore p…
alichherawalla f0aae67
docs(checklist): T062 fixed (resend replays recordedTurnKind=image); …
alichherawalla 5e4d539
test(T062): green guard — text-mode resend of 'draw a dog' re-draws (…
alichherawalla 8137ef0
test(harness): reusable voice-mode/TTS harness + faithful executorch/…
alichherawalla cde4f24
test(T062): voice + enhancement resend re-draws — the exact DEV-B33 d…
alichherawalla 4ec47c6
docs(checklist): T062 FIXED+GUARDED across text, voice, and voice+enh…
alichherawalla efb6d97
test(T071): RED — prompt enhancement sends enable_thinking=true (DEV-…
alichherawalla bd97d1b
test(T018): RED — litert CPU invoke error (Status 13) surfaces, no an…
alichherawalla 84131ad
docs(checklist): T018 automated (litertCpuInvokeError red, DEV-B23)
alichherawalla 17df5fa
test(T088): GREEN guard — voice-mode mic becomes STOP during generati…
alichherawalla 1ebcbd9
docs(checklist): T088 automated (voice-mode stop-button green guard, …
alichherawalla c9e2bbc
test(T056): GREEN guard — generation error clears the spinner + shows…
alichherawalla c0adb3c
docs(checklist): T056 local error-clears-spinner green guard (DEV-B13…
alichherawalla 98ac905
revert(T056): back out errorClearsSpinner — could not falsify it (unp…
alichherawalla 95690b2
revert(T056): remove errorClearsSpinner test file (unproven green — s…
alichherawalla fde0f74
test(T056): PROVEN green guard — generation error clears the spinner …
alichherawalla 75e00af
docs(checklist): T056 PROVEN green guard (DEV-B13 local, falsified)
alichherawalla f1c62f7
test(T056): RED — llama generation failure shows NO error + spinner s…
alichherawalla f4baabd
docs(checklist): T056 is a RED reproducing B13 on the llama path (was…
alichherawalla cf1526a
test(T062-llama): PROVEN guard — B33 resend re-draws on the LLAMA eng…
alichherawalla 6dce189
test(T049): RED — remote (LM Studio) reasoning is dropped, not shown …
alichherawalla 92a0a01
docs(checklist): T049 automated (remote reasoning dropped RED, DEV-B1…
alichherawalla d915c7f
test(T051): GREEN guard — remote Ollama reasoning RENDERS (contrast t…
alichherawalla 6ef7d77
docs(checklist): T051 Ollama green guard; T050 folded into T049 (reas…
alichherawalla 67e4171
test(T033): PROVEN green guard — <think> reasoning renders in the thi…
alichherawalla ada4620
docs(checklist): T033 automated (mid-stream thinking-block green guar…
alichherawalla c62e13f
test(N3): fix the vacuous noImageModelDrawSend guard — assert on the …
alichherawalla 0881cd9
test(residencySwap): fix false green — assert residency ACCOUNTING, n…
alichherawalla bfe13d2
test(T023b): RED — Eject All leaves ALL sidecars resident, not just w…
alichherawalla 2e4efa2
docs(checklist): add T023b — ejectAll leaves all sidecars (DEV-B1 gen…
alichherawalla b61abf4
feat(residency): model selector shows the loaded model + its RAM (rem…
alichherawalla 5ee8af7
feat(residency): image tab Currently Loaded also shows RAM consumed (…
alichherawalla 08dd527
fix(test): add formatModelRam + estimateImageModelRam to ModelSelecto…
alichherawalla 10cfeca
feat(settings): add 'Stay in the loop' card (Follow @alichherawalla o…
alichherawalla 48fa40c
feat(about): add Follow @alichherawalla on X + Join Slack links to th…
alichherawalla fc93dcc
refactor: FOLLOW_X_URL + SLACK_INVITE_URL as shared constants (not in…
alichherawalla c8f7e42
feat(residency): per-model eject in the model selector (In Memory sec…
alichherawalla 40d8b1c
test: lazy-reload after per-model eject — ejected text model reloads …
alichherawalla d47ed91
fix(residency): Eject All frees every resident incl. sidecars (DEV-B1)
alichherawalla fcdef1c
chore: bump pro — remove Pro Tools entry from Settings
alichherawalla d7e78c9
fix(residency): do not load whisper resident on download (DEV-B1 #1)
alichherawalla 57eb57a
test: behavioral UI guards for the Follow/Slack cards + Pro Tools rem…
alichherawalla d95c044
test(residency): STT reclaimed on send (tight device) + debuggable lo…
alichherawalla cfb98f8
test(residency): validate STT reclaim via the model selector In Memor…
alichherawalla d8f91e9
docs(checklist): add residency matrix T111-T120 (co-residency/auto-ev…
alichherawalla fdcdeb5
test(T116): text + whisper allowed co-residence via In Memory UI
alichherawalla 2cea43a
docs(checklist): T116 co-residence → automated (textWhisperCoresident)
alichherawalla 2c214b8
test(T117): OS memory warning reclaims idle sidecars via In Memory UI
alichherawalla ce37ad2
docs(checklist): T117 auto-eviction → automated (memoryWarningEvictsS…
alichherawalla aa7e3d2
test(T115): voice-note send reclaims idle STT on a tight device (In M…
alichherawalla f7cc8bc
docs(checklist): T115 voice reclaim → automated (voiceNoteReclaimsStt)
alichherawalla 390be1c
docs: log T119 (whisper blocked-retry) as OPEN/deferred — harness gap…
alichherawalla b69b780
test(T120): TTS co-resides during a voice turn via In Memory UI
alichherawalla 29ce692
docs(checklist): T120 automated; T118/T119 deferred with honest reasons
alichherawalla a6c42a4
fix(test): T023 arrival — load whisper via real loadModel, not the (n…
alichherawalla 0e218b4
test(T044): parallel calculator tools render two result bubbles (full…
alichherawalla 3cf3829
test(T038): thinking + tool-result + answer all render (full UI, upgr…
alichherawalla c0aa70d
docs(checklist): T038 + T044 upgraded to full-UI (partial→automated)
alichherawalla d1d05d2
docs(checklist): T071 already automated (enhancementNoThinking) — fix…
alichherawalla df6168c
test(T038): flip Thinking on via the real quick-settings toggle, not …
alichherawalla 7c4987d
test(T048): remote parallel tool_calls render as bubbles + reply (ful…
alichherawalla 722b8e6
docs(checklist): T048 upgraded to full-UI (remoteParallelTools)
alichherawalla 20ed9e7
test(T035): thinking-box header reads DONE while reasoning still stre…
alichherawalla e41d0ae
test(T046): add a remote server → it connects (full UI, upgrade from …
alichherawalla 851e86d
docs(checklist): partial campaign complete — 6/13 upgraded to full-UI…
alichherawalla 2c95bdb
test(T085): voice-mode calculator journey — STT → tool → answer (full…
alichherawalla e20c2ea
test(T084): voice-mode image journey — STT 'draw a dog' → route → ima…
alichherawalla aa7d8ec
docs(checklist): T084 + T085 voice journeys automated
alichherawalla 95ea469
test(T014): GPU/OpenCL backend → GenerationMeta shows offloaded layer…
alichherawalla e22fc66
test(T020): LiteRT select is lazy (no eager warm), loads on first sen…
alichherawalla d527163
test(T072): enhancement reasoning must not become the image prompt (D…
alichherawalla a31d0fc
test(T077): chat-mode mic leaks the realtime session on navigate-away…
alichherawalla 60137f3
test(T072): validate enhancement-reasoning red at the UI layer, not t…
alichherawalla 556c469
docs(T015): defer NPU-gibberish as native-only (no JS surface to assert)
alichherawalla db1603b
test(T047): empty LAN scan shows alert AND adds no phantom server (DE…
alichherawalla 271edba
test(T053): remote model is marked in the selector (Remote badge + se…
alichherawalla 7c8499c
test(T057): tapping a pre-send image thumbnail opens no preview (DEV-…
alichherawalla b2a043e
test(T095): server+model configured → tap Continue skips onboarding i…
alichherawalla d992221
test(T096): support-share sheet dismisses after Share on X, does not …
alichherawalla 7b52051
test(T097): Home Text count with a remote model active is truthful, n…
alichherawalla 1e17504
test(T012): ModelsScreen renders N downloaded-indicator marks for N d…
alichherawalla ee9f5d7
test(T078): chat-mode mic double-tap opens a second realtime session …
alichherawalla 79f4456
test(T073): enhancement step shows no streaming/progress (DEV-B30b, red)
alichherawalla 5aafed0
test(T058,T086,T103): vision-affordance parity + voice thinking width…
alichherawalla 0b69a3c
test(T034): max-predict cutoff is silent — no cut-off/continue indica…
alichherawalla 3336b1f
test(T087): voice mode renders a stray empty '#' bubble after a tool …
alichherawalla e3c04f9
docs: defer T021 (mmproj estimate — wrong surface + harness gap) and …
alichherawalla 39956d5
docs: mark T010/T011 deferred (need the RAG doc-attach UI harness, pe…
alichherawalla 75d3f42
test(T083): deleting the TTS model mid-playback breaks playback (V5-g…
alichherawalla 27f4c8d
test(T010,T011): mounted KB doc-attach — scanned-PDF message (red) + …
alichherawalla a6d52ca
test(T118): embedding model co-resides as a sidecar after a KB index …
alichherawalla 044469c
test(T016): GPU/OpenCL init timeout falls back to CPU gracefully (DEV…
alichherawalla d17e4fd
test(T119): voice note transcribes via whisper blocked->free->retry o…
alichherawalla 1c23eaa
docs(T021): re-scope deferral — blocked on a product decision (mmproj…
alichherawalla 81e2414
fix(whisper): deleteModel only cancels the in-flight download when it…
alichherawalla 82a7f3d
chore(pro): bump pointer for V4 tts residency-release fix
alichherawalla 592bc45
fix(speech): single source of truth for speech-ready text; chat Speak…
alichherawalla 2d9539d
chore(pro): bump pointer for Q19 speech-text DRY migration
alichherawalla 0b99b4a
fix(whisper): don't list a truncated/sub-threshold file as a download…
alichherawalla e2f797a
fix(tools): single lenient tool-arg parse; recover unquoted keys + st…
alichherawalla 019ab18
fix(tools): LiteRT surfaces tool result when the model's final turn i…
alichherawalla 93ce7bb
fix(tools): tool router honors an explicit 'none' decline over a name…
alichherawalla 741b327
fix(tools): LiteRT tool-loop routes media through the modelMedia seam…
alichherawalla 2fa5edd
fix(voice): chat-mode direct-audio voice note transcribes before disp…
alichherawalla 557a2dc
fix(stt): chat-mode mic lifecycle — stop on unmount (B11) + absorb do…
alichherawalla fae483c
fix(downloads): Download Manager 'downloading' count uses isDownloadi…
alichherawalla 2c57469
fix(projects): cascade project delete, gate KB tool on project existe…
alichherawalla 6c4828b
fix(enhance): image-prompt enhancement forces thinking OFF, streams, …
alichherawalla 27b4112
fix(enhance): stream the partial rewrite into the enhancement message…
alichherawalla 231a63f
fix(thinking): show remote dedicated-reasoning + 'Thinking...' header…
alichherawalla fa1e8cb
chore(pro): bump pointer for B27 voice thinking-block width
alichherawalla e326f98
chore(pro): bump pointer for B32 stray-bubble fix
alichherawalla b7d7635
fix(litert): re-apply sampler mid-conversation when it changes (Q18)
alichherawalla ef0a38d
fix(generation): surface a runtime engine failure durably inline + ke…
alichherawalla 4ca5aa1
fix(generation): surface a silent max-predict cutoff with a cut-off i…
alichherawalla cd6ae22
fix(rag): roll back a failed index + name the scanned-PDF cause (KB)
alichherawalla b5b0de8
fix(rag): invalidate a stale-dimension tool embedding on model swap (…
alichherawalla 2505508
fix(image): mount hydration must not orphan the active image model (i…
alichherawalla 5b295d3
fix(settings): Reset to Defaults also resets the image params (Q12)
alichherawalla 0749803
feat(memory): 3-mode model-loading selector (conservative/balanced/ag…
alichherawalla 3546a1a
test(memory): 3-mode loading spec (conservative/balanced/aggressive +…
alichherawalla 27ac4e0
fix(promo): support-open-source sheet shows at most once per session
alichherawalla 6032147
feat(memory): implement 3-mode behavior + Load-Anyway-always + 2 pre-…
alichherawalla ca259f5
feat(memory): dirty-vs-clean real-RAM gate — clean residents page, di…
alichherawalla cd5c143
fix(memory): loadPolicySync projects the 3-mode setting, not just the…
alichherawalla d1de8ff
test(memory): prove no dead-end refusal (Load Anyway always offered) …
alichherawalla 5171353
test(memory): balanced SWAP + failed-unload are dirty-vs-dirty (clean…
alichherawalla 9ac7164
test(memory): re-pin single-model switching to conservative; 2.5x ima…
alichherawalla 18f9ac5
fix(memory): revert the dirty-gate regression — restore Android recla…
alichherawalla 1ba65b3
test(memory): residency matrix — co-reside/swap across all 3 modes (s…
alichherawalla a11e6ac
fix(memory): dirty physical ceiling refuses an oversized dirty model …
alichherawalla 068b912
test(memory+cleanup): align override/loadPolicySync tests to ratified…
alichherawalla 2a17bfe
fix(device-bugs): downloads recovery+retry, KB embed-fail abort, imag…
alichherawalla 4f02171
fix(device-bugs): stuck spinner (B13), remote prompt-enhance (B30), a…
alichherawalla 9d3bbd8
fix(image): Q7 guidance-scale default 7.5 (DRY); fix Q20 STT test; dr…
alichherawalla 1788eb0
test(remote): align reasoning_content spec to B16/B17 — dedicated fie…
alichherawalla 2afad7f
refactor(hygiene): extract to bring whisperService/modelResidency/ima…
alichherawalla 298f1f9
chore(hygiene): clear all pre-push eslint errors (behavior-neutral)
alichherawalla 5d16202
chore(hygiene): clear last 6 eslint errors in test harness (unused va…
alichherawalla 5b35ac2
test(chat): dictation inserts into composer, not auto-send (B26 — ali…
alichherawalla e99ae3a
test: align 2 stale specs to ratified behavior (whisper size floor, T…
alichherawalla c84872e
chore: gitignore docs/wire-captures (raw device logs kept local, not …
alichherawalla 0391730
fix(ci): unbreak the CI pipeline (secrets-in-if startup failure) + cl…
alichherawalla 48702d0
chore(ci): local Sonar scan skips (not fails) when project uses serve…
alichherawalla a70781a
fix(settings): move 'Stay in the loop' card below Off Grid AI PRO, ab…
alichherawalla c2e874c
docs(test): comprehensive release checklist — 45 -> 185 rows (gap-aud…
alichherawalla b42b8d6
docs(test): move permission-prompt rows to first-use (mic->Voice, pho…
alichherawalla 043c8e3
test(spec): UI-behavior spec-tests for onboarding-desktop-link, scan-…
alichherawalla a377838
fix(models,onboarding): make 3 device-reported bugs pass their spec-t…
alichherawalla 59977b7
Merge pull request #541 from off-grid-ai/fix/onboarding-scan-litert-d…
alichherawalla 8f36e23
fix(downloads): queued downloads survive an app kill/restart
alichherawalla 7e65286
refactor(models): one owned device-aware litert download-warning deci…
alichherawalla bb180e1
fix(downloads): restore dispatches queued items without awaiting slot…
alichherawalla ebbc585
docs(test): checklist rows for queued-kill survival, device-aware lit…
alichherawalla b4bfeac
fix(generation): stop mid-turn no longer wedges the chat ('LLM servic…
alichherawalla 65a802b
fix(capabilities): a selected-but-not-loaded GGUF shows its real Tool…
alichherawalla 6364644
feat(models): remote selection in the Models sheet carries a cloud ma…
alichherawalla 291f2b6
fix(boot): download-state recovery no longer blocks first paint
alichherawalla 5bffd1c
fix(generation): stopped turn is never mislabeled 'No response / inco…
alichherawalla 046e5ad
docs(gaps): GPU-selected-but-CPU report, manager-sheet residency desi…
alichherawalla fdf29b5
docs(gaps): reload is a drifted second load path (drops thinking+GPU+…
alichherawalla 074751a
docs(gaps): mic loader during background STT download misreads as app…
alichherawalla 1ffdc06
test(generation): RED — a send racing a settings reload silently lose…
alichherawalla 3baa327
test(generation): RED — GPU-selected load that lands on CPU is a sile…
alichherawalla 396bea2
fix(models): loaded-state publishes atomically after capability detec…
alichherawalla e34cdcc
test: delete reloadWithSettings (dead drifted second load path) and t…
alichherawalla 0cd5755
test(models): all-branch unit layer under describeGpuFallback + captu…
alichherawalla d706636
docs: close the reload-drift gaps entry (root cause corrected: readin…
alichherawalla 2e7b238
docs(claude): integration tests are the standard; failing mockist tes…
alichherawalla dcda950
test(models): RED — manager sheet is the residency surface (RAM chip …
alichherawalla d918320
test(generation): RED rendered journey — stale text failure card must…
alichherawalla 8ab8f97
fix(generation): clear the stale text failure card at the dispatch se…
alichherawalla b0fe0e6
docs: checklist row 193 (stale failure card cleared on new attempt) +…
alichherawalla 077dc91
feat(models): manager sheet is the residency surface — per-row RAM ch…
alichherawalla 53c83b3
Merge stale-failure-card fix: card clears at generation dispatch (age…
alichherawalla e3f53bd
test(models): RED — Home picker tags a 2.89GB model 'may not fit' on …
alichherawalla 12581f0
fix(models): picker fit hint uses the ONE owned device budget (fileEx…
alichherawalla 935e9f5
test(voice): RED — background STT download must not render the mic as…
alichherawalla f9354fa
fix(models): Android GPU init timeout 8s -> 25s (Adreno 735 first-loa…
alichherawalla 5f33937
test: advance fake timers past the raised 25s Android GPU init timeout
alichherawalla 4b767c6
fix(voice): background STT download renders a determinate download ri…
alichherawalla 89d6fa7
docs: checklist row 192 (mic download ring, not loader) + close the I…
alichherawalla 4468910
Merge mic-loader fix: background STT download renders a download ring…
alichherawalla 6b0c5ac
fix(generation): a stopped turn is never labeled 'Reply cut off at th…
alichherawalla 264664c
fix(tools): never auto-inject search_knowledge_base — only user-toggl…
alichherawalla 2888929
fix(generation): tool path displays the runtime's reasoning instead o…
alichherawalla acb0f54
fix(settings): Model Loading label 'Conservative' -> 'Lean' so the 3-…
alichherawalla 45604e3
test(memory): migrate the residency suite off the removed picker In-M…
alichherawalla 95a1d08
test(projects): guard that a real project never auto-injects search_k…
alichherawalla e1ea199
fix(models): reload waits for the old context's memory to be reclaime…
alichherawalla 75439fd
fix(models): show the load spinner ON the selected model row, not a b…
alichherawalla 4f1a936
fix(models): read the model's real trained context (arch-prefixed GGU…
alichherawalla 12db7dc
fix(models): load spinner shows on the just-tapped row, not the previ…
alichherawalla 8530521
fix(memory): GPU-aware text RAM estimate so Aggressive doesn't overco…
alichherawalla a2dc2c9
fix(models): selection highlight follows the just-tapped model during…
alichherawalla f540bf7
fix(models): one owner for activeModelId (select intent) + clear it o…
alichherawalla 41128bb
fix(generation): thinking toggle applies to the current turn (no off-…
alichherawalla f6ec772
fix(models): show the load spinner on the active row during a no-tap …
alichherawalla 68d2e0c
fix(vision): pair the mmproj to its model, not the first one in the dir
alichherawalla dc67edc
fix(vision): match mmproj on the quant-stripped model stem (quant-ind…
alichherawalla f42105c
fix(vision): don't trust a persisted mismatched mmproj — self-heal th…
alichherawalla d7d059e
fix(vision): refuse a non-belonging mmproj instead of pairing the clo…
alichherawalla a8ccee5
fix(downloads): show a wrench (repair), not an eye, for a vision mode…
alichherawalla 347fc42
feat(chat): surface 'vision file missing → repair' instead of a bare …
alichherawalla ad01759
fix(models): wrench (not eye) for the repair action on the model cata…
alichherawalla dbcdf1d
fix(vision): keep the repair signal on dead-link cleanup + gate image…
alichherawalla 0d20107
test(vision): pin the image-send gate — only a working vision model a…
alichherawalla c815752
refactor(vision): one strict model<->projector matching rule (SOLID/DRY)
alichherawalla 5cb2f3e
fix(downloads): one shared projector per model across quants (no re-d…
alichherawalla cd924b0
fix(chat): one shared vision gate for BOTH send and resend
alichherawalla 91ae918
fix(vision): heal the durable vision flag from the catalog → wrench s…
alichherawalla 615f010
feat(settings): add Transcription (STT) + Text to Speech (TTS) to Mod…
alichherawalla 8ba4504
fix(models): one reactive source for 'currently loaded' (loaded/unloa…
alichherawalla 7f87e97
fix(generation): Stop keeps the shown partial (both engines) + stop t…
alichherawalla 38cee0e
chore(diag): [STOP-SM] trace the keep-partial decision (convId + show…
alichherawalla 3c31df3
fix(generation): Stop keeps a reasoning-only partial (stopped mid-THI…
alichherawalla 1e5e2b7
fix(downloads): repair-in-progress uses the theme color, not warning …
alichherawalla e04d12b
fix(generation): never discard shown output on ERROR either (llama/li…
alichherawalla d57c986
fix(voice): chat-mode STT is dictation-to-the-input-box on every engi…
alichherawalla 2d3ad38
fix(reasoning): strip bare <|channel>thought opener (empty thought → …
alichherawalla b3d9406
fix(image): read steps/cfg/guidance fresh from store, not a stale cal…
alichherawalla a6c0777
fix(thinking): derive Thinking capability from chat_template, not Jin…
alichherawalla 5674d2f
chore(lint): clear pre-push blockers across the branch
alichherawalla d57a3fe
chore(lint): alias inner useAppStore require in HomeScreen.test to cl…
alichherawalla c221b08
test: right-size harness model + drop mockists of superseded behavior
alichherawalla ec97a55
chore(lint): drop symbols orphaned by the deleted useModelLoading sel…
alichherawalla 6933e0a
test(memory): eviction-premise tests declare the heavy 4GB model expl…
alichherawalla f500ec1
test(gpu): widen reload waits — reload now includes the device-critic…
alichherawalla cc1be68
refactor(models): break the ModelsManagerSheet<->useResidentRows impo…
alichherawalla eab99f6
fix(memory): changing the load mode ejects every resident (Lean now t…
alichherawalla 6ca8c9f
fix(generation): remote tool turn resets generating state on error (s…
alichherawalla 7201365
fix(chat): stack message attachments vertically (voice note above ima…
alichherawalla 314e3f7
chore(lint): trim desync-fix comment to keep generationServiceHelpers…
alichherawalla e57b7aa
chore(lint): condense two historical comments to keep generationServi…
alichherawalla 3a6d745
chore(lint): condense catch comment — generationServiceHelpers now at…
alichherawalla 930140c
ci+chore: fix CI gates — Node 22, pro submodule in typecheck/architec…
alichherawalla b18e65f
fix(memory)+ci: remove unwired survival-floor cluster; run CI jest se…
alichherawalla f3c5ca1
test(audio): assert voice thinking-block width on the constraining wr…
alichherawalla 118eef1
fix(tools): cap tool result fed to the model (~24k chars) so a bad to…
alichherawalla a4c10e1
chore(pro): bump submodule to voice-mode fixes (PR #22: image render,…
alichherawalla c0c1570
ci: run tests on Node 24 (node:sqlite) + drop mockist textDownloadPro…
alichherawalla e0b0a26
ci: run tests on Node 26, not 24 (node:sqlite teardown segfault)
alichherawalla b3f6808
test(hardening): make project-chat ordering test deterministic (contr…
alichherawalla File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| /** | ||
| * INTEGRATION — the reasoning pipeline through its REAL seams, end to end. | ||
| * | ||
| * This is the test that proves the parse-once refactor actually works for a user, not that a | ||
| * mock returned what it was told. It drives the REAL chatStore (streaming → finalize) and the | ||
| * REAL ChatMessage render, with NOTHING mocked in the parse path, and asserts the observable | ||
| * outcome: reasoning is captured, the visible answer is clean, and raw model markup NEVER | ||
| * reaches the screen. It reproduces the two bugs this refactor closes: | ||
| * - DR1 — remote channel reasoning leaking into the answer (streamed-split flow) | ||
| * - OD14 / tool-call leak — raw <tool_call>/<function=…> markup shown as text | ||
| * for every format in the single-source grammar. A wall of green unit tests could not catch a | ||
| * wiring bug between stream → store → render; this can. | ||
| */ | ||
| import React from 'react'; | ||
| import { render } from '@testing-library/react-native'; | ||
| import { useChatStore } from '../../../src/stores/chatStore'; | ||
| import { ChatMessage } from '../../../src/components/ChatMessage'; | ||
| import { REASONING_DELIMITERS } from '../../../src/utils/messageContent'; | ||
| import { ThinkTagParser } from '../../../src/services/providers/openAICompatibleStream'; | ||
| import { resetStores, getChatState } from '../../utils/testHelpers'; | ||
| import type { Message } from '../../../src/types'; | ||
|
|
||
| const REASONING = 'let me weigh the options carefully'; | ||
| const ANSWER = 'Here is the clean answer.'; | ||
|
|
||
| /** Read the single assistant message the store just finalized into the active conversation. */ | ||
| function finalizedMessage(convId: string): Message { | ||
| const msg = getChatState() | ||
| .conversations.find(c => c.id === convId) | ||
| ?.messages.at(-1); | ||
| if (!msg) throw new Error('no finalized message'); | ||
| return msg; | ||
| } | ||
|
|
||
| describe('reasoning pipeline — stream → finalize → render, real seams', () => { | ||
| beforeEach(() => resetStores()); | ||
|
|
||
| describe.each(REASONING_DELIMITERS)( | ||
| 'format opened by %j', | ||
| ({ open, close }) => { | ||
| const raw = `${open}${REASONING}${close}${ANSWER}`; | ||
|
|
||
| it('LOCAL flow: inline-tagged content is split into reasoning + clean answer, and renders both', () => { | ||
| const store = useChatStore.getState(); | ||
| const convId = store.createConversation('local-model'); | ||
| store.startStreaming(convId); | ||
| // Local (llama.rn) path: raw tokens accumulate in streamingMessage; reasoning is extracted | ||
| // at finalize by the ONE shared parser. | ||
| store.appendToStreamingMessage(raw); | ||
| store.finalizeStreamingMessage(convId); | ||
|
|
||
| const msg = finalizedMessage(convId); | ||
| expect(msg.reasoningContent).toBe(REASONING); | ||
| expect(msg.content).toBe(ANSWER); | ||
| // The stored answer carries none of the opener/closer markup. | ||
| expect(msg.content).not.toContain(open.trim()); | ||
| expect(msg.content).not.toContain(close.trim()); | ||
|
|
||
| const { getByText, queryByText } = render( | ||
| <ChatMessage message={msg} />, | ||
| ); | ||
| expect(getByText(ANSWER)).toBeTruthy(); // clean answer is visible | ||
| expect(getByText(new RegExp(REASONING.slice(0, 20)))).toBeTruthy(); // reasoning survived into the block | ||
| expect( | ||
| queryByText( | ||
| new RegExp(open.trim().replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), | ||
| ), | ||
| ).toBeNull(); // no raw markup on screen | ||
| }); | ||
|
|
||
| it('REMOTE flow: ThinkTagParser split → store → finalize keeps reasoning out of the answer (DR1)', () => { | ||
| const store = useChatStore.getState(); | ||
| const convId = store.createConversation('remote-model'); | ||
| store.startStreaming(convId); | ||
| // Remote (OpenAI-compatible) path: the streaming parser routes reasoning vs answer live, | ||
| // feeding the two store channels. Same grammar, different seam. | ||
| const parser = new ThinkTagParser(); | ||
| parser.process( | ||
| raw, | ||
| t => useChatStore.getState().appendToStreamingMessage(t), | ||
| r => useChatStore.getState().appendToStreamingReasoningContent(r), | ||
| ); | ||
| store.finalizeStreamingMessage(convId); | ||
|
|
||
| const msg = finalizedMessage(convId); | ||
| expect(msg.reasoningContent).toBe(REASONING); | ||
| expect(msg.content).toBe(ANSWER); | ||
|
|
||
| const { getByText, queryByText } = render( | ||
| <ChatMessage message={msg} />, | ||
| ); | ||
| expect(getByText(ANSWER)).toBeTruthy(); | ||
| expect( | ||
| queryByText( | ||
| new RegExp(open.trim().replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), | ||
| ), | ||
| ).toBeNull(); | ||
| }); | ||
| }, | ||
| ); | ||
|
|
||
| it('tool-call markup never reaches the stored answer or the screen (OD14 / leak class)', () => { | ||
| const store = useChatStore.getState(); | ||
| const convId = store.createConversation('local-model'); | ||
| store.startStreaming(convId); | ||
| // A model that emits its answer wrapped around raw tool-call markup. After finalize the | ||
| // stored content — and everything rendered — must be free of that markup. | ||
| store.appendToStreamingMessage( | ||
| 'Sure, checking that.<tool_call><function=get_weather><parameter=city>NYC</parameter></function></tool_call>', | ||
| ); | ||
| store.finalizeStreamingMessage(convId); | ||
|
|
||
| const msg = finalizedMessage(convId); | ||
| expect(msg.content).not.toContain('<tool_call>'); | ||
| expect(msg.content).not.toContain('<function='); | ||
| expect(msg.content).not.toContain('<parameter='); | ||
|
|
||
| const { queryByText } = render(<ChatMessage message={msg} />); | ||
| expect(queryByText(/<tool_call>/)).toBeNull(); | ||
| expect(queryByText(/<function=/)).toBeNull(); | ||
| expect(queryByText(/<parameter=/)).toBeNull(); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
29 changes: 29 additions & 0 deletions
29
__tests__/rntl/components/ChatMessageToolCallLeak.test.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import React from 'react'; | ||
| import { render } from '@testing-library/react-native'; | ||
| import { ChatMessage } from '../../../src/components/ChatMessage'; | ||
| import { createMessage } from '../../utils/factories'; | ||
|
|
||
| // On-device regression: raw tool-call markup (<tool_call>/<function=NAME>/<parameter=X>) leaked | ||
| // as visible text below the thinking block. Terminal-artifact: assert the user sees NO markup, | ||
| // across BOTH the separate-reasoning-channel path and the inline-<think> path. | ||
| describe('ChatMessage — tool-call markup never renders as visible text', () => { | ||
| const rawBlock = | ||
| '<tool_call>\n<function=search_knowledge_base>\n<parameter=query>\nAchilles of Troy\n</parameter>\n</function>\n</tool_call>'; | ||
|
|
||
| it('reasoningContent path (separate channel): markup hidden, thinking + tool chip shown', () => { | ||
| const m = createMessage({ role: 'assistant', content: rawBlock, | ||
| reasoningContent: 'The user wants to know about Achilles.', | ||
| toolCalls: [{ id: 't1', name: 'search_knowledge_base', arguments: '{"query":"Achilles of Troy"}' }] } as any); | ||
| const { queryByText, getByText } = render(<ChatMessage message={m} />); | ||
| expect(queryByText(/<function=|<parameter=|<tool_call>/)).toBeNull(); | ||
| expect(getByText(/The user wants to know about Achilles\./)).toBeTruthy(); | ||
| }); | ||
|
|
||
| it('inline <think> path: markup hidden after the reasoning block', () => { | ||
| const m = createMessage({ role: 'assistant', | ||
| content: `<think>reasoning here</think>\n${rawBlock}`, | ||
| toolCalls: [{ id: 't1', name: 'search_knowledge_base', arguments: '{}' }] } as any); | ||
| const { queryByText } = render(<ChatMessage message={m} />); | ||
| expect(queryByText(/<function=|<parameter=|<tool_call>/)).toBeNull(); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
__tests__/unit/components/ChatMessage/parseModelOutput.contract.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import { parseModelOutput } from '../../../../src/components/ChatMessage/utils'; | ||
|
|
||
| // Contract: parseModelOutput().answer is GUARANTEED free of reasoning + tool-call markup for | ||
| // EVERY format the app can emit. This invariant makes the tool-call-leak class impossible — | ||
| // if a new markup format is added to the parser, add it here. | ||
| const MARKUP = /<think>|<\/think>|<\|channel|<tool_call>|<\|tool_call|<function=|<parameter=|<invoke|<function_call/; | ||
|
|
||
| describe('parseModelOutput — answer is clean by construction (the anti-leak contract)', () => { | ||
| const toolBlock = '<tool_call>\n<function=search_kb>\n<parameter=query>\nAchilles\n</parameter>\n</function>\n</tool_call>'; | ||
| const cases: Array<[string, string, string | null]> = [ | ||
| ['inline <think>', `<think>reasoning</think>\nThe answer.`, null], | ||
| ['inline <think> + tool block', `<think>r</think>\n${toolBlock}`, null], | ||
| ['separate channel + tool block in content', toolBlock, 'the reasoning'], | ||
| ['Gemma channel', `<|channel>thought\nreasoning\n<channel|>The answer.`, null], | ||
| ['Qwen channel', `<|channel|>analysis<|message|>reasoning<|channel|>final<|message|>The answer.`, null], | ||
| ['gemma tool token', `Sure. <|tool_call>{"name":"x"}<tool_call|>`, null], | ||
| ['answer only', `Just a plain answer.`, null], | ||
| ['reasoning only (no answer)', `<think>only reasoning, no answer</think>`, null], | ||
| ]; | ||
| it.each(cases)('%s: answer carries no markup', (_label, content, reasoning) => { | ||
| const { answer } = parseModelOutput(content, reasoning); | ||
| expect(answer).not.toMatch(MARKUP); | ||
| }); | ||
|
|
||
| it('reasoning-only message does NOT duplicate reasoning into answer', () => { | ||
| const { reasoning, answer } = parseModelOutput('<think>abc</think>', null); | ||
| expect(reasoning).toContain('abc'); | ||
| expect(answer).toBe(''); | ||
| }); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.