Repository navigation
Record Teams calls locally and transcribe them with Whisper on the CPU - #15
Merged
Merged
Conversation
The owner decided on 2026-10-28 (decision D1 of the stage 2 meetings spec) that open_mess should transcribe calls that have no Teams transcript. This spec settles how: the daemon notices that Teams holds the microphone (the Windows consent store), records the Teams process's loopback audio and the microphone in shared mode, and after the call turns them into text with Whisper large-v3-turbo on candle, inside the daemon, on the CPU. Audio lives in %LOCALAPPDATA% only until it is transcribed; the text stays until the owner deletes it. Agents read transcripts through three core tools under a reserved `local` read permission; only the window can enable recording, change settings or delete. Reviewed by Codex and GLM for 10 rounds; GLM approved, and the last Codex P2 (no visible indicator with a hidden tray icon, muted notifications and no microphone track) is a rebuttal the owner accepted.
The review of f42c13f found that a release dropping a model revision from the allowed list would delete the only model on disk at start and stop recording until the owner downloads again. The list now only grows, and the daemon deletes a model folder only after a newer revision downloaded or on the owner's click.
Step 1 of the local transcription spec (.specs/2026-10-28-local-transcription.md, 4.4): each recording is a folder under %LOCALAPPDATA%\open_mess\recordings with meta.json, the tracks' WAV files until transcription, and transcript.json. The data folder may roam to a server, so nothing of a recording goes there. Crash safety comes from the order of writes and a recovery pass at start: a recording the daemon left gets valid WAV headers and waits marked interrupted, a transcription that wrote its transcript is done, leftover audio of a done recording goes. Audio not transcribed within 7 days is deleted, and the size and disk limits refuse a new recording. Ids are checked before they touch a path, and the WAV reader takes only the header open_mess writes, with every size checked against the file. Nothing calls this yet; detection, capture and transcription come in the next steps.
Review of 82a012c (Codex and GLM P2): a track shorter than its header (the daemon stopped as the track was created) made the recovery fail and left the recording in state recording forever, outside retention and inside the audio limit. Such a track holds no audio and is now deleted; a recording with no track left is failed, and the writer puts the header on disk at once. The end of a left recording is also read from the tracks' last write before the header fix, so the 7-day retention no longer restarts at the daemon's restart.
Review of 167d79f (Codex and GLM P2): recovery deleted a track whenever fixing its header failed, so a recording another program held open at start could lose hours of audio. Only a file shorter than the WAV header (no samples) is deleted now; any other error leaves the recording as it is for the next start, with a regression test.
Review of 704af6a (Codex P2): a recording whose track another program held at start stayed in state recording until the next daemon start, outside retention. recover now takes the recording and transcription running now and leaves them alone, so the daily pass with expire can run it too and pick the recording up once the file is free.
Step 2 of the local transcription spec (.specs/2026-10-28-local-transcription.md, 4.3 and 4.7), the owner's choice of engine: Whisper large-v3-turbo in pure Rust, in the daemon, on the CPU. model.rs pins revision 41f01f3f of openai/whisper-large-v3-turbo (MIT) and reads each file whole against its SHA-256 and size; the weights go to candle from that buffer, since candle maps files only as unsafe. stt.rs cuts each track into 30 s windows, skips silent ones (Whisper invents text over silence), detects the language per track unless one is set, and decodes with Whisper's timestamp rules and temperature fallback, including the compression-ratio check against looping text. The model's text is untrusted: cues and transcripts are capped, and a cancel flag is checked before the encoder and after each decoder step. isolated() runs a transcription on its own thread with half the cores, so a panic in candle ends as an error, not in the daemon. Tests run every step on a tiny zero-weight model and decode real tokens of the model's tokenizer, so CI downloads nothing. cargo deny: candle's gemm uses the unmaintained paste, and candle holds zip at 8, whose advisories are about extract, which neither candle nor open_mess calls; both are ignored with reasons. Nothing calls this yet; the queue and the daemon come next.
Review of 826ea0c (Codex P2): the decoder stopped at half of Whisper's 448 decoder positions, so the end of fast speech in a 30 s window was lost without a mark. It now decodes until the positions run out, and a window cut at the limit marks its last piece, as the spec's caps require. The pieces of a window come from one function now, with a test.
Step 3 of the local transcription spec (.specs/2026-10-28-local-transcription.md, 4.5): list_recordings and get_recording_transcript, the same over MCP and the CLI. They answer under the read permission of the reserved account `local`, which no real account (`<module>:<account>`) can be; the permission is read before the files and again before the answer, so taking it back mid-call lets nothing out. The transcript comes in pages by size, its text under `untrusted` with the notice; ids are checked before any path, and a recording not done says so. The window shows a "Nagrania lokalne" row with one read box, even with no account; its write permission is stored as never whatever is sent, and it has no conversations. The daemon reads recordings from %LOCALAPPDATA% where updates install (Windows); elsewhere the tools say they are not supported. stop_recording and the status object come with the capture, which they control.
Step 4 of the local transcription spec (.specs/2026-10-28-local-transcription.md, 4.6 and 4.7). The new Nagrywanie rozmów section shows the model and offers "Pobierz model" and "Usuń model"; only the window starts a download, nothing else of the recording reaches the network. A revision downloads file by file into its folder under %LOCALAPPDATA%, each streamed through the update downloader's checks: HTTPS, Hugging Face and its CDN hosts only (a `*.hf.co` entry, since the weights redirect to regional hosts), every redirect checked, the exact size and SHA-256 compiled in, three hours for the 1.6 GB weights. A failed download leaves no folder; a whole one replaces the other listed revisions. Files are now pinned by exact size, also when they are read for a transcription. The update downloader's per-file loop became the shared download_file it uses too. The recording settings and the list of recordings join the section with the recording itself.
Review of 2771cef (Codex P2): a daemon stopped during the model download left an incomplete revision folder and its .part file, up to 1.6 GB, for good. At start, a listed revision's folder without all its files or with a .part file is deleted; unlisted folders are left alone. GLM P3: tests for the *.hf.co host rule and for the window's 'newer model' flag.
The live check of step 0 showed that Windows no longer writes the ConsentStore times on the owner's computer and that process loopback of Teams gives silence during a call, while the loopback of the headset carries it. The spec now detects a call by the audio sessions of ms-teams.exe, records the other side from the device Teams plays the call on for the whole call, and records the microphone only while it is on in Teams. The CPU measurement of step 0e is in too: the spec's path needs about 72 minutes for an hour of one track. On the owner's decision the transcription gets its own copy of the Whisper network, with a decoder that keeps its keys and values and a cancel check between encoder layers, and packs only speech into the model's windows. Rounds R11-R15 of the spec review are in its changelog; the owner chose to implement without further rounds. docs/protocols/teams.md gets section 10 with the redacted step 0 results: audio sessions, loopback, model download hosts and the CPU numbers.
list_recordings returned at most the 500 newest recordings, and since only filters newer ones, so an older recording could not be reached. It now takes before, the last recording of the previous page, as read_messages does. The language of a transcript was written only to meta.json, after transcript.json, so a crash between the two left a done recording with no language. transcript.json now carries it too, and the repair at start copies it into meta.json.
…yers candle's Whisper runs the decoder's whole token sequence again at every step and has no way to stop inside the encoder. On the owner's computer that made a 30 s window take about 33 s, and a call that started during a transcription waited for a whole encoder pass, about 9.5 s, while the spec allows 5. recording/whisper.rs is a copy of candle-transformers 0.11's Whisper network (MIT OR Apache-2.0) with two changes: the decoder keeps the keys and values of the tokens it has seen, and the encoder checks the cancel flag between its layers. Weight names and shapes are unchanged, so the same model file loads. Measured on the pinned model: a 30 s window in 22.8 s instead of 32.8 s with the same text, and a cancel during the encoder returns after 0.11 s. A test checks that the kept keys give the same output as running the whole sequence.
A track was cut into 30 s windows and a window was skipped only when it was silent as a whole. In a call each track is silent while the other side speaks, but the turns are shorter than a window, so in a ten-minute two-track dialogue all 40 windows went through the encoder although less than half of the audio was speech. The speech is now found in 30 ms frames, kept with 300 ms around it and pauses under a second joined, and packed into windows of at most 30 s with 200 ms of silence between pieces; the same dialogue needs 16 windows. Times in a window are mapped back to the track through the piece they fall in, and a time from the model that lies in a pause or past the pieces moves to the nearest piece, so a cue stays on speech. The model's times are untrusted, so the mapping saturates instead of overflowing on a huge one.
Recordings in the waiting state had nothing to transcribe them. A queue thread in the daemon now takes them one at a time, the oldest first, and only while nothing records: a recording that starts cancels the running transcription, which goes back to waiting and starts over after the call. Reading the model checks the cancel flag every 64 MiB, so a call waits a fraction of a second even then. The model is loaded for each recording and dropped after it, so the daemon's memory comes back between recordings. A transcription starts only with the model on disk and 6.6 GB of memory free (the measured peak and 2 GB), because a failed allocation ends the process. A panic or an error fails only its recording; a model that fails its checksum leaves the recording waiting and holds the queue until the model is downloaded again. Deleting a recording stops its transcription first, so no file of the folder is open when it goes. The same thread runs the retention pass every hour. list_recordings shows the progress of the running transcription. The measured result of the whole new path goes into docs/protocols/teams.md: a ten-minute two-track call in 418 s, about 42 minutes per hour.
Finding the speech reads the whole track before the first window, and packing reads through the silence between pieces; neither looked at the cancel flag. With a six-hour track that kept the transcription busy for seconds after a call started, where the spec allows a fraction of one. Both now check it every 30 s of audio. Codex's review of 49e06a3.
Step 0 showed that the ConsentStore times are not written on the owner's computer, while the audio sessions of ms-teams.exe follow a call closely: active during it, inactive while muted, removed about 180 s after the hang-up. recording/detect.rs reads those sessions on every active device (wasapi, safe functions; process names from sysinfo) and turns the reads into a call: it starts on an active session, lasts while a session is listed, and ends 15 s after none is. A read that fails is skipped; reads failing for a minute make detection unavailable and end the call. The 30-minute cap on a quiet call waits for step 0b, as the spec says. The logic takes the reads and the time, so it is tested without Windows; an ignored test reads the live sessions of the computer it runs on.
The ignored test that reads this computer's audio sessions printed its counts, which the workspace lints refuse; 88a1690 went in without a green cargo xtask ci because of it. It now asserts what it read instead.
recording/capture.rs records the device Teams plays the call on, by loopback, and the microphone Teams uses, each into its own 16 kHz mono track (spec 4.2). A track is written by the clock rather than by the packets: what did not arrive is silence and what came past the clock is cut, so both tracks keep the recording's length and their times agree, also when a device is quiet for minutes. The microphone track gets silence while Teams has the microphone muted, and what the microphone heard then is dropped in memory. The capture follows the devices the detection reports when Teams moves to another one, opens a device again after an error, and leaves a track silent after three failed tries 20 s apart. Devices are reached through a trait, so the logic is tested with fakes; the WASAPI side opens shared streams only. An ignored test reads this computer's loopback briefly.
… a failed read Reviews of 3c5f2d8 and 88a1690: - Any InvalidData from a transcription was taken for a damaged model, so a recording with a bad WAV header left itself waiting and held the whole queue (GLM P1). Only the model's own size or checksum check now reports a damaged model (SttError::Damaged); a bad track fails its recording, as the spec says, and the queue goes on. - Nothing cleared the damaged state after the model was downloaded again (Codex P2). The gate now holds only while the weights are the ones that failed, so a new download lets transcriptions start. - A transcription that came to nothing (stopped, a meta.json that cannot be written) was started again at once, in a tight loop (GLM P2). The queue now waits for a change, as it does when empty, and shows the reason meanwhile. - When the process list could not be read, the Teams sessions came back as none, which ended a call that went on (Codex P2). A lookup that reads none of the processes asked for is now a failed read, which the detection skips and reports only after a minute. The capture uses the same lookup.
recording/recorder.rs takes the detection's read every second and runs a recording's life (spec 4.1, 4.2). A recording starts while a call is on, a Teams session is active, the automatic mode is on, the call was not stopped, the tray is up, the model is there and the disk and the limits allow it; otherwise the owner is told once per call why it is not recorded. It ends with the call, a stop, six hours, less than 2 GB free, the detection lost or the capture failing, each with its reason, and the recording then waits for the transcription queue. A stopped call is not recorded again until it ends, also across a restart of the daemon (skip.json, kept while Teams lists sessions at start). Turning the automatic mode off ends a recording without that. The owner sees it in toasts under their own AppUserModelID, open_mess.recording, whose buttons Zatrzymaj and Zatrzymaj i usuń act on the recording they name only, and in the tray: "open_mess - nagrywanie" and a Zatrzymaj nagrywanie item while a call is recorded. Agents get stop_recording, under the read permission of local, and status gets the recording object, with the reason the queue waits (Codex on 3c5f2d8). Outside, the recorder reaches the settings, the disk, the capture and the notices through hooks, so its logic is tested without Windows.
The Nagrywanie rozmów section gets what spec 4.6 gives it beside the model: the automatic mode (off by default, confirmed when turned on), the microphone and the language, saved through changes_allowed; the recording going on with its own Zatrzymaj; why calls are not recorded or transcriptions wait; and the recordings with Pokaż, Usuń, Ponów and Usuń wszystkie. Pokaż is the owner's read of a transcript, whatever agents may read, so a refusal for agents never locks the owner out; the text goes into the page through textContent only. Deleting stops the recording going on or its transcription first, and a folder another program holds stays with an error while the rest go. Ponów puts a failed recording whose audio is left back in the queue. Usuń model is refused while a call is recorded or transcribed.
Reviews of b9b8687 and 922ccde: - A call stopped by hand started recording again when the detection failed for a minute and came back (Codex P1): only a working read now tells that a call is over. - A capture that failed or panicked ended the recording, and the next read started a new one in the same call, every few seconds with its toast (GLM P1). Six hours, a failed capture and a failed write now end a recording as a stop does: the rest of that call is not recorded (Codex P2, GLM P2). - A skip.json that could not be written was forgotten; it is written again on the next read (Codex P2). - A track lost after three failed tries stayed silent when Teams moved to a working device (Codex P2, GLM P2): another device starts afresh. - Samples that came after a gap were written right after the silence, about 200 ms early; they now end at the clock (Codex P2). - A device that opens but fails every read never counted as lost; only a read that works now resets the count (Codex P2).
Spec 4.8 (and Codex's review of 922ccde): an update stopped the daemon whatever was being recorded, so the rest of the call was lost. - Zainstaluj and the toast refuse to start while a call is recorded ("aktualizacja po końcu nagrania"); installing on its own waits for the call's end like it waits for other work. - A call recorded during phase A or B stops the installation as a cancel does, everything resumed, and the window reads "przerwano: nagrywanie rozmowy". Phase A therefore never gives up on its 30 minutes during a call. Installing on its own may start the same release again after the call: its once-only mark is cleared then. - Once the daemon hands over to the helper, no recording starts; the rest of the call is recorded after the restart. The recorder and the update share an UpdateLink: whether a call is recorded, and whether the daemon is handing over.
What is recorded and when, where it stays and for how long, who reads the text, and that telling the participants and following the organisation's rules is up to the owner (spec 2026-10-28-local-transcription, section 7).
An ignored test that runs the real detection, capture and toasts in a temporary folder while the owner is in a Teams call, so the daemon the owner runs is not touched. It prints only the Teams sessions' states and each track's level per second, and deletes the recording at the end: what step 0b needs to settle (the other side heard while muted, a long mute, joining muted, a device change) without keeping any audio.
…topped Codex's reviews of db70d3c, fa142e9 and 5d6b1fa: - A recording could start between the update's last look and its handover to the helper, and the daemon then ended in the middle of it (P1). UpdateLink is now one atomic state: a recording and a handover each claim it, and only one of them can. - Why phase A or B stopped was read again after resume(), so a call that ended meanwhile lost the reason, and a cancel during a call was taken for the call's and let installing on its own start the release again (P2 x2). The phases now say why when they stop. - A capture with no track left open ended the recording and skipped the rest of the call, which spec 4.2 says it must not (P1). It no longer skips; no new recording starts while Teams stays on the device that did not open, and one does once Teams moves. - Deleting the recording going on reported success when its folder could not go (P2); the error now reaches the window. - Usuń model checked for a recording and then deleted, so one could start in between (P2). The check and the deletion now run while no recording can start; a transcription that finds the model gone waits. - The window also refreshes while a recording waits (P3).
Codex on 990f2f2: a recording lasts until Teams lets go of its audio, about three minutes after the hang-up, not the hang-up itself; and an agent allowed to read the transcripts passes them to its model provider, so "never sent anywhere" was wrong.
A recording whose tracks could not be opened remembered only the speaker, and took it from the detection read rather than from the capture. If Teams moved to other speakers just before that read, the new, working device was blocked for the rest of the call; and moving only to another microphone never let the recording start again. The capture now publishes the devices its dead flags belong to, so a failure counts only for the devices it happened on, and the block holds the pair of speaker and recorded microphone. The live check removes the folders a killed run left behind and says that it must not run while the daemon records, since both share the toasts' app id.
The live check removed every folder in %TEMP% whose name started with its prefix, so a folder of the same family kept on purpose (a backup, say) would have gone with it. Its temporary folder now sits inside one folder with an exact name, and only that folder is removed before a run.
The owner decided on 2026-11-03 that a meeting's context is its transcript and its chat. A recording does not know its meeting, so the recording tools' descriptions tell the agent how to join them by time: teams_list_meetings for the meeting that overlaps the recording, then read_messages of its conversation, or list_conversations and search where the meetings tool is not offered. No new field and no second way to the chat: it still comes from read_messages, under untrusted and the Teams account's permissions. The spec records the decision, the rounds R16-R19 with their fixes (the measured 42 minutes per hour as a known limitation, the retention promise in line with a transcription that runs at the deadline, the reason set) and the promised comment against panic = "abort" in the workspace Cargo.toml.
Brings in stages 2 and 3 (meetings, transcripts, live captions), the module tools, the two-language window and the release pipeline. The recording side follows them: its window section, toasts and tray menu now have English beside Polish (spec 2026-10-30-ui-languages), while the reasons in status and list_recordings stay English for agents and are shown in the window's language; an update stopped by a call is recorded as an English outcome like the others. The recording tools' names are reserved against module tools as the shared ones are. The Windows audio facts move to section 11 of docs/protocols/teams.md, since section 10 now holds live captions. The lockfile keeps candle 0.11.0, tokenizers 0.22.2 and zip 8.6.0, the versions the transcription was measured with and deny.toml covers.
Relabelling the tray read whether a call was recorded and then set the menu, so a recording that started in between was shown, then overwritten with the state read before it: no stop item and no recording tooltip for the rest of the call. The tray's state now sits behind one lock that both the recorder's indicator and the relabelling hold while they change the tray. The last window messages of the recordings without an English version (unknown recording, transcript not ready or damaged, no model to download) now follow the window's language too.
The tray's lock was held while the menu changed, and Tauri changes a menu on the main thread: the recorder's indicator, already under the recorder's lock, held the tray's lock and waited for the main thread, while the main thread, relabelling after a change of Windows's language, waited for the tray's lock. The window, the tray and the detection would have stopped for good. The indicator now only stores whether a call is recorded and posts a redraw to the main thread; relabelling posts one too. Each redraw reads the language and the state when it runs, so nobody waits and the last redraw shows the latest state.
The test helpers from both sides are kept: the sign-in page forwarding from main, the recordings folder from this branch. The new admin tests build the window state with the recording fields too.
RUSTSEC-2026-0429 is another `ZipArchive::extract` advisory, like the three already ignored: candle reads zip only for .npz and .pth weights, never extracts, and open_mess loads safetensors. The fix is in zip 9, which candle 0.11.0 does not allow; the transcription was measured on 0.11.0.
4 tasks done
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Status: the live test with a second person in a Teams call (spec steps 0b and 7) has not been done yet. Everything here is tested with fakes and synthetic audio; no real call has gone through detection, capture and transcription end to end. Until step 0b confirms it,
CONFIRMEDinrecording/detect.rsstays false, so the recording's end is not trimmed: it runs until Teams lets go of its audio sessions, about 3 minutes after hang-up. Please do not merge before the live test.Change
open_mess can now record the owner's Teams calls on their own Windows computer and turn them into text there, with Whisper (large-v3-turbo) in pure Rust (candle) on the CPU, inside the daemon. The owner decided this on 2026-10-28 (automatic recording of every call, a reminder toast, audio kept until transcribed and at most 7 days, text kept until deleted) and on 2026-11-02 (implement, with an own copy of the Whisper network).
recording/detect.rs): the audio sessions Windows lists forms-teams.exe. A call starts on an active session and ends 15 s after Teams removes its sessions. ConsentStore and process loopback failed live (step 0,docs/protocols/teams.mdsection 11).recording/capture.rs): endpoint loopback of the device Teams plays the call on (others, the whole call, so it also holds notifications and other apps), and the microphone only while Teams has it on (me). Both tracks are written by the clock, follow device changes, and a device that cannot be opened ends the recording without skipping the call.recording/recorder.rs): start, stop,skip.jsonacross restarts, limits (6 h, disk, audio and text totals), toasts under their own AUMID with Stop and Stop-and-delete buttons, tray tooltip and menu item,status.recording.recording/stt.rs,recording/whisper.rs,recording/queue.rs): own copy of candle-transformers' Whisper with a decoder KV cache and a cancel check between encoder layers; only the speech is packed into the 30 s windows; one recording at a time, never while a call is recorded, behind a 6.6 GB memory gate. Measured on the owner's i9-13900K: about 42 minutes per hour of two-sided call (target 30, gate 60), peak 4.54 GB.recording/model.rs): downloaded only from the configuration window, pinned revision, exact sizes and SHA-256, hostshuggingface.coand*.hf.co.list_recordings,get_recording_transcript(cues underuntrusted) andstop_recording, under the read permission of the reserved accountlocal. Agents cannot start a recording, change a setting or delete anything. The tools' descriptions point agents to the meeting's chat (teams_list_meetings, thenread_messages), per the owner's decision of 2026-11-03; the recording tools' names are reserved against module tools.UpdateLink).Spec
.specs/2026-10-28-local-transcription.md. Rounds R1-R15 before the implementation (R10: GLM APPROVE, Codex's P2 rebutted with the owner's agreement; R15: Codex APPROVE, GLM's P2s fixed). The owner then decided on 2026-11-02 to implement without further rounds. The meeting-chat change of 2026-11-03 went through R16-R19: GLM APPROVE in every round; Codex's P2s were fixed or added to the rebuttal register (no upper time bound inread_messages: a stage-1 tool, out of scope). Rounds stopped after R19, where neither reviewer had anything left on the chat change. Merging main added only the two-language texts (spec 2026-10-30-ui-languages) and moved the Windows audio facts to section 11 ofdocs/protocols/teams.md, with no further round.Tests
The new behaviour is covered by tests that fail without it:
tokenizer.json.tests/recording_live.rs: an ignored live check for steps 0b and 7.cargo xtask cion Windows at ecdaac8 (after merging main with release 0.1.0 and the Paperclip server): green, 771 passed, 0 failed. Earlier runs on a heavily loaded machine twice timed out one test from main,open-mess-hosttoo_many_meetings_ask_for_a_narrower_window("the service did not answer in time"). It passes alone in 0.8 s and passed in the last full run.Commit reviews
Every commit was reviewed by Codex and GLM and reconciled in
.reviews/<sha>.processed. P1 and P2 findings by area:skip.jsonwrites. All fixed in fa142e9.Rejected with reasons:
systemgatesProcess.%TEMP%holds none.Coexistence (platform modules only)
Not a platform module, and nothing talks to Teams' services. Detection only reads the audio session list. Capture is shared-mode and passive: no volume, mute or ducking changes, and no access to Teams' files or settings (checked live in step 0, where the call went on unchanged). The full check beside the official client during a real call is the pending live test.
Checklist
unwrap/expect/panic!/ unchecked indexing in non-test codelist_recordings,get_recording_transcript,stop_recording)candle-core,candle-nnandcandle-transformers0.11.0 (MIT OR Apache-2.0): Whisper in pure Rust on the CPU, so no C++ toolchain or GPU SDK is needed.rayon1.12 (MIT OR Apache-2.0): the transcription's own thread pool. candle already pulls it in.miniz_oxide0.8 (MIT OR Zlib OR Apache-2.0): the compression-ratio check against looping text. flate2 already pulls it in.sysinfo0.37 (MIT): free memory and disk, and Teams' process name.wasapi0.26 (MIT): Windows audio sessions and capture through safe functions, Windows only.deny.tomlignorespaste, which candle's gemm uses (unmaintained only). It also ignores threezipextract advisories: candle reads zip only for.npzand.pthweights, never extracts, and open_mess loads safetensors.