Commit Graph
187 Commits
Author SHA1 Message Date
iamdoubz 1cf0be1259 test(commands): cover graph_calendar_enabled in the privacy egress allowlist test (M4.4) 2026-07-07 18:00:20 -05:00
iamdoubz a48550da1e style(sync): cargo fmt 2026-07-07 17:57:13 -05:00
iamdoubz ce4c54c93b style(commands): cargo fmt 2026-07-07 17:57:12 -05:00
iamdoubz aa5ae127b3 style(calendar): cargo fmt 2026-07-07 17:57:10 -05:00
iamdoubz 3d90265e8f chore(lib): register MS Graph calendar commands (T8.9, M4.4) 2026-07-07 17:53:31 -05:00
iamdoubz d81b4726f9 feat(commands): MS Graph calendar link/import/disconnect + egress allowlist (T8.9, M4.4, FR-CAL-6) 2026-07-07 17:53:13 -05:00
iamdoubz ef6bb28f3e feat(models): add graph_calendar Settings fields (T8.9, M4.4) 2026-07-07 17:51:48 -05:00
iamdoubz d407be8bb0 feat(calendar): GraphSource for MS Graph calendar import (T8.9, M4.4, FR-CAL-6) 2026-07-07 17:51:40 -05:00
iamdoubz 8a80580851 chore(sync): expose resolve_access_token to crate for calendar reuse (M4.4) 2026-07-07 17:49:32 -05:00
iamdoubz 83cb30ef0e feat(calendar): add graph-calendar OAuth provider (T8.9, M4.4) 2026-07-07 17:49:25 -05:00
iamdoubz 6991c1d1bc feat(sync): Dropbox and Box upload targets (T9.10, M4.3)
DropboxTarget: path-addressed like OneDrive/WebDAV; create_folder_v2 creates
the whole intermediate path in one call; upload-session chunking above
LARGE_FILE_THRESHOLD (start/append_v2/finish).

BoxTarget: Box addresses items by numeric ID, not path, so ensure_dir/exists/
put all walk (and lazily create) the folder chain from root ("0") by listing
each level's children. Always uploads via Box's session API regardless of
file size rather than its simple multipart endpoint, since the session API
takes plain PUT bodies (consistent with every other target here) and needs
no new reqwest feature; Box computes and returns each part's digest, so no
local hashing is needed either.

Both follow the same documented ceiling as OneDriveTarget's put_chunked
(M4.1): the upload-session id lives only for one put() call, not persisted
across a durable-queue retry after a restart.

Tests: in-process std::net mock servers for both providers (same pattern as
the existing MockNextcloud/MockGraph), each exercising a real multi-chunk
upload end-to-end.
2026-07-07 14:46:43 -05:00
iamdoubz 40709b257d fix(transcription): resolve_language normalizes case-insensitive "en" match (T8.7, M4.2)
resolve_language(Some("EN"), false) was passed through unchanged instead of
being normalized to lowercase "en" - the English-only-model guard only fired
when the requested language *differed* from English, not when it matched with
different casing. Also fixes cargo fmt drift in mod.rs/npu.rs left over from
the M4.2 worktree (stopped mid-verification per session instruction before
running fmt).
2026-07-07 13:59:55 -05:00
iamdoubz 4e33e7d84c Merge branch 'm4-2-work' into feature_chore_bug_005 2026-07-07 13:33:14 -05:00
iamdoubz 2cc48c9d4d feat(sync): chunked/resumable upload for large recordings (T9.2, M4.1)
Recordings are now native-quality (~50-100 MB) and put() used to buffer
the whole file into memory with a single PUT/upload call; OneDrive/Graph
also caps a single PUT at 250 MB. This replaces that with disk-streamed,
chunked uploads on both sync backends (FR-SYNC-2/5):

- Both WebDavTarget::put and OneDriveTarget::put now stream the file off
  disk in fixed-size buffers (tokio::fs::File + BufReader) instead of
  tokio::fs::read()'ing it whole — memory use is O(chunk), not O(file
  size), for every upload, large or small.
- Files at or below LARGE_FILE_THRESHOLD (8 MiB) still take a single
  streamed PUT; only large artifacts (in practice, .wav recordings) take
  the chunked path, so small transcript/notes/summary uploads are
  unaffected.
- WebDAV (Nextcloud/ownCloud): implements the chunking-v2 protocol —
  MKCOL an upload collection keyed deterministically off the remote
  path's sha256 (stable across retries), PUT lexically-sortable parts,
  MOVE the virtual `.file` to assemble server-side. A retry PROPFINDs
  the collection first and skips any part already landed, so a
  partially-uploaded large file resumes instead of restarting at byte 0
  — genuine mid-file resume, not just chunk-level retry. Generic WebDAV
  targets (Seafile, Synology, unbranded servers) have no equivalent
  server-side assembly endpoint, so they fall back to one streamed PUT
  above the threshold too — memory-safe, just not chunk-resumable
  (documented ponytail simplification).
- OneDrive/Graph: implements the createUploadSession + Content-Range
  byte-range PUT flow (chunks capped at 10 MiB, a multiple of Graph's
  required 320 KiB granularity). A failed chunk is retried a few times
  within the same put() call. ponytail-documented ceiling: the session
  URL isn't persisted on the sync_jobs row, so a retry from a *later*
  pump cycle (e.g. after an app restart) starts a fresh session and
  re-uploads from byte 0 rather than resuming — true cross-attempt
  resume would need a sync_jobs column, left as a follow-up.
- Progress is reported via the existing ProgressSink after each chunk,
  not just once at the end, so the UI sees steady movement on a large
  upload (FR-SYNC-11).
- OneDriveTarget's Graph URLs are now built from a `graph_base()` helper
  (overridable via WA_GRAPH_BASE_URL, unset in production) so the
  chunked session flow can be exercised against a local mock in tests.

Tests (all against small, in-process, dependency-free mock servers —
std::net only, no new crates, same pattern as oauth::LoopbackRedirect):
- nextcloud_chunked_upload_assembles_and_resumes: multi-chunk upload
  assembles byte-for-byte, then a resumed upload with one chunk
  pre-seeded skips exactly that chunk.
- small_file_skips_chunking_even_on_a_chunking_capable_target: a small
  file never takes the chunked path.
- onedrive_chunked_upload_via_graph_session_mock: multi-chunk Graph
  upload session assembles byte-for-byte with per-chunk progress.
- parse_chunk_sizes_extracts_href_and_length_ignoring_namespace_prefix
  and chunk_name_is_zero_padded_and_lexically_sortable: pure unit tests
  for the PROPFIND-response scraper and chunk-naming scheme.

Skip-if-unchanged (SHA-256, in storage::upsert_sync_job) and the
exponential-backoff retry queue (storage::claim_due_sync_jobs) are
untouched — this only changes how SyncTarget::put moves bytes.

Verified: cargo fmt clean; cargo clippy --features sync and --features
mcp both clean (-D warnings); cargo test --lib (default features:
audio, cpu-transcription, diarization, pst, sync, npu) 165 passed, 0
failed, 7 ignored (unrelated hardware-gated tests); cargo test --features
sync --lib: 18 passed, 0 failed, 1 ignored (webdav_round_trip, opt-in
live-server test per existing convention).
2026-07-07 13:32:09 -05:00
iamdoubz 7490ff5db4 feat(transcription): wire language selection through recording/reprocess/recovery (T8.7, M4.2) 2026-07-07 13:06:00 -05:00
iamdoubz 34fff3f7de feat(core): register list_whisper_languages Tauri command (T8.7, M4.2) 2026-07-07 13:05:55 -05:00
iamdoubz 7675e0467e fix(diarization): set multilingual:false for the shared ModelInfo shape (T8.7) 2026-07-07 13:05:36 -05:00
iamdoubz 75ca3df0a0 feat(core): track resolved per-meeting language on RecordingSession (T8.7, M4.2) 2026-07-07 13:02:24 -05:00
iamdoubz 66d9eff2f9 feat(storage): persist requested language at meeting creation (T8.7, M4.2) 2026-07-07 13:02:14 -05:00
iamdoubz 1fb1da0946 feat(transcription): thread language param through OnnxTranscriber (ignored on NPU/DirectML, T8.7) 2026-07-07 13:01:17 -05:00
iamdoubz 496a8aa29b feat(transcription): wire whisper language param through Transcriber trait (T8.7, M4.2) 2026-07-07 13:00:52 -05:00
iamdoubz 7f88101a79 feat(transcription): whisper language catalog for Settings dropdown (T8.7, M4.2) 2026-07-07 13:00:48 -05:00
iamdoubz 37a6dd172d feat(transcription): multilingual model catalog entries (T8.7, M4.2) 2026-07-07 12:58:52 -05:00
iamdoubz 6bc0949e7e feat(transcription): add multilingual/language fields to ModelInfo+Settings (T8.7, M4.2) 2026-07-07 12:58:12 -05:00
iamdoubz e966181ab8 Merge branch 'worktree-agent-af5a53986b9441608' into feature_chore_bug_005
# Conflicts:
#	src-tauri/src/llm/mod.rs
#	src/lib/views/Settings.svelte
#	src/lib/views/SummaryPanel.svelte
2026-07-07 08:15:04 -05:00
iamdoubz b986c570f7 fix(llm): reset stale hosted endpoint when quick-switching away from Anthropic
apply_llm_provider_args now resets llm_endpoint to Ollama's local
default when switching to a non-Anthropic provider without an explicit
endpoint and the stored endpoint is still Anthropic's fixed hosted
URL. Prevents a future per-use provider switch (SummaryPanel) that
only sends `provider` from silently leaving llm_endpoint pointed at a
third party for a provider that has no business talking to it.
2026-07-07 07:56:09 -05:00
iamdoubz d73af49742 feat(llm): set_llm_provider stores Anthropic key in credential store, extends egress allowlist (T10.2/M3.2)
set_llm_provider now accepts provider "anthropic": the apiKey argument
is written straight to crate::llm::credentials (OS credential store)
and never assigned into Settings, so it structurally cannot reach
settings.json/wa.db (FR-SEC-1). apply_llm_provider_args pulls the
settings-mutation logic into a small pure function specifically so
that guarantee is unit-testable without touching a real keyring.

llm_provider_from_settings gets an "anthropic" arm; api.anthropic.com
joins the settings-derived egress allowlist through the exact same
generic is_local()-based path privacy_self_check_json already uses for
every other provider — no anthropic-specific allowlist code, and the
host only appears once the provider is actually selected (never
unconditionally).

Also fixes pre-existing cargo-fmt drift on one unrelated line this
file's formatter pass touched (CONTENT_RANGE header call).
2026-07-07 07:50:15 -05:00
iamdoubz e7925fc3ad feat(llm): add hosted_ai_acknowledged settings field (T10.3/M3.3)
One-time "data leaves your device" acknowledgment flag for hosted
(non-local) AI providers, persisted like consent_acknowledged so the
banner doesn't nag on every use. serde(default) so it's false for any
settings.json written before this field existed.
2026-07-07 07:47:57 -05:00
iamdoubz c3e8bae27f feat(llm): AnthropicProvider real implementation (T10.1/M3.1)
Implements AnthropicProvider::status/summarize/suggest_tags against the
real Anthropic Messages API (POST /v1/messages, x-api-key + anthropic-
version headers, SSE streaming for summarize, non-streaming for the
short tag reply), replacing the stub that returned "isn't built yet".

- Adds llm::credentials (mirrors sync::credentials) so hosted API keys
  live in the OS credential store, never settings/DB/logs (ADR-0011).
- Fixes OpenAiCompatProvider::api_key() to actually read credential_ref
  from the store instead of the hardcoded None left by the T10a.2 stub.
- Splits build_messages/build_tag_messages into system/user halves
  (build_system_and_user/tag_system_and_user) since Anthropic's system
  prompt is a top-level field, not a messages[0] entry like OpenAI's shape.
- is_local() is unconditionally false for AnthropicProvider (no
  unauthenticated mode, unlike OpenAiCompatProvider).
- Adds a loopback raw-socket mock HTTP server (mirrors sync::oauth's
  LoopbackRedirect pattern — no HTTP-mock crate in the dependency tree)
  and tests proving request/response shape for both AnthropicProvider
  and OpenAiCompatProvider against mocked endpoints.
2026-07-07 07:47:03 -05:00
iamdoubz 2582d8947f Merge branch 'worktree-agent-af937df502b61b006' into feature_chore_bug_005
# Conflicts:
#	src-tauri/src/storage/mod.rs
2026-07-07 01:15:08 -05:00
iamdoubz 57e757f01f revert(mcp): bind_loopback/serve back to pub(crate) (T10.4)
No longer need to be pub now that the e2e test that required it is
reverted -- restores the tighter, originally-intended encapsulation.
2026-07-07 00:56:20 -05:00
iamdoubz 71824d44cc fix(mcp): make bind_loopback/serve/HttpServerHandle pub for the e2e test (T10.4)
Widens visibility from pub(crate) to pub so tests/mcp_server_test.rs
(an external integration-test crate, per CLAUDE.md's "cross-service
tests in /tests") can drive the real HTTP transport end-to-end. This
does not weaken the loopback guarantee: bind_loopback still refuses
non-loopback regardless of caller -- only its visibility changed.
2026-07-07 00:47:55 -05:00
iamdoubz ec8b12f636 fix(mcp): collapse nested if-let per clippy (T10.4) 2026-07-07 00:31:32 -05:00
iamdoubz 63bfc293cb feat(briefs): create/list/get_feature_brief + set_brief_exposed command bodies (T10.6, M1.4/M1.6) 2026-07-07 00:30:04 -05:00
iamdoubz 4fc3df504a refactor(briefs): bundle distill() transcript args to satisfy clippy (M1.3) 2026-07-07 00:30:03 -05:00
iamdoubz 55b520d373 style(storage): rustfmt fixup for list_feature_briefs (M1.1) 2026-07-07 00:30:01 -05:00
iamdoubz bd97032550 style: rustfmt (incl. one pre-existing long line in serve_recording) 2026-07-07 00:30:00 -05:00
iamdoubz 59742e4fdf style(mcp): rustfmt Store trait additions 2026-07-07 00:29:58 -05:00
iamdoubz 8caeb818a7 style(mcp): rustfmt run_mcp_stdio 2026-07-07 00:29:56 -05:00
iamdoubz 71c4c6ecf0 style(mcp): rustfmt 2026-07-07 00:29:54 -05:00
iamdoubz 386be75658 style(mcp): rustfmt 2026-07-07 00:29:52 -05:00
iamdoubz 36a28dc096 style(mcp): rustfmt 2026-07-07 00:29:50 -05:00
iamdoubz 07072551c0 style(mcp): rustfmt 2026-07-07 00:29:49 -05:00
iamdoubz e0112ec8c4 style(mcp): rustfmt 2026-07-07 00:29:48 -05:00
iamdoubz 45e34284e9 fix(mcp): use HttpServerHandle.local_addr instead of a second lookup (T10.4)
Fixes a dead_code warning (clippy -D warnings would fail on it) --
server.rs was calling listener.local_addr() itself before handing the
listener to http_transport::serve(), leaving the handle's own
local_addr field unread.
2026-07-07 00:27:56 -05:00
iamdoubz 0ecc72f18f feat(mcp): wire mcp_status/set_mcp_enabled/set_mcp_scope/mcp_access_log (T10.4)
Replaces the four not_implemented() stubs. mcp_status/set_mcp_enabled
reach the process-wide RmcpServer singleton (behind `#[cfg(feature =
"mcp")]`, with a not_implemented fallback for builds without it);
set_mcp_scope/mcp_access_log are plain settings/Store I/O and work in
every build regardless of the `mcp` cargo feature. The token is only
ever returned once, right when set_mcp_enabled mints it -- it is never
re-readable afterwards, same as any other freshly-issued secret.
2026-07-07 00:26:11 -05:00
iamdoubz f99845ef1b feat(mcp): run_mcp_stdio() serves one session over process stdio (T10.4, FR-MCP-6)
Builds its own tokio runtime (there is no Tauri app in this mode) and
tracing goes to stderr, not stdout -- stdout is the MCP JSON-RPC channel.
Connects to the same wa.db as the GUI instance via SqliteStore::connect,
builds a WaMcpHandler with no AppHandle (None), and runs it to
completion via mcp::stdio_transport::serve_once. A build without the
`mcp` feature prints an error and exits(1) instead of silently opening
the GUI.
2026-07-07 00:24:47 -05:00
iamdoubz 949bade137 feat(mcp): --mcp-stdio launches the headless stdio adapter (T10.4, FR-MCP-6)
Checked before whispassist_lib::run() builds the Tauri app -- the flag
routes straight to run_mcp_stdio() (next commit) and returns instead of
opening a window.
2026-07-07 00:23:47 -05:00
iamdoubz eb2aa2a0d6 fix(mcp): pass the GUI's AppHandle as Some(..) to the handler (T10.4)
Follows the previous commit's WaMcpHandler::new signature change.
2026-07-07 00:23:27 -05:00
iamdoubz 49cc1e6cd3 fix(mcp): WaMcpHandler's AppHandle is optional (T10.4)
The --mcp-stdio child process (main.rs, next commits) has no running
Tauri app/window to emit "mcp://access" events to -- only the GUI
instance's HTTP transport does. The mcp_access_log DB row is still
written unconditionally either way (FR-MCP-5); only the live event is
skipped when there's no AppHandle.
2026-07-07 00:23:20 -05:00