Skip to content

feat(calendar): add provider-neutral availability tool - #1821

Merged
Chadpiha merged 9 commits into
OpenWhispr:mainfrom
boseq:codex/calendar-availability
Aug 25, 2026
Merged

feat(calendar): add provider-neutral availability tool#1821
Chadpiha merged 9 commits into
OpenWhispr:mainfrom
boseq:codex/calendar-availability

Conversation

@boseq

@boseq boseq commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • add a read-only get_calendar_availability tool over the shared local calendar cache
  • combine selected Google, Microsoft, and Apple calendars into privacy-safe busy intervals and available slots
  • normalize provider free/busy and self-declined semantics, including all-day events, overlap boundaries, buffers, and minimum slot duration
  • add IPC/preload wiring, prompt guidance, UI status/icon support, types, translations, and focused coverage

Scope

  • no new OAuth scopes, token flows, or provider connections
  • no raw event titles, attendees, meeting links, account identifiers, or provider names are returned to the model
  • results are explicitly labeled as local-calendar-cache coverage for the next seven local calendar days

Validation

  • 45 focused calendar availability/provider/database/tool tests
  • TypeScript typecheck
  • full ESLint
  • i18n consistency check
  • Swift typecheck
  • renderer production build
  • Prettier and git diff checks

Manual testing

Validated locally with Google, Microsoft, and Apple events in a 9 AM–6 PM range, including minimum-duration filtering and cross-provider busy interval union.

boseq added 2 commits August 25, 2026 16:43
Keep the provider-neutral cached availability tool and the minimal Google, Microsoft, and Apple availability mappings. Remove on-demand provider refresh, OAuth lifecycle, concurrency, calendar pruning, and unrelated event lifecycle expansion.
Comment thread test/helpers/calendarAvailabilityService.test.js Outdated
Comment thread test/helpers/calendarAvailabilityService.test.js Fixed
boseq added 3 commits August 25, 2026 19:25
…s [BOSEQ]

- Google sync tokens pin the timeMin/timeMax window of the full sync that
  created them, so cached coverage decayed below the availability tool's
  7-day horizon as tokens aged. Add sync_token_expires_at, a 1-day TTL,
  and a 9-day lookahead, mirroring the Microsoft delta-token pattern.
- Relay the time/connection-dependent validation errors (past start,
  horizon, end-before-now, no calendar connected) through the tool so the
  model can correct the request; provider/database errors stay generic.
- Simplify the tool's response sanitizer into a field projection.
- Reuse parseEventTime in the database range filter.
- Replace URL-substring privacy asserts with a regex (CodeQL alerts).
@boseq
boseq marked this pull request as ready for review August 25, 2026 14:43
boseq and others added 4 commits August 25, 2026 20:32
…[BOSEQ]

Review follow-ups on OpenWhispr#1821: the renderer tool now imports the validation
bounds, defaults, relayable error strings, and RFC3339 check from
calendarAvailability.js instead of duplicating them, so the advertised
schema and relayed errors can never drift from what the main process
enforces. Busy intervals no longer cross IPC: they carry buffer padding
and nothing in the renderer consumed them, so the service strips them
and the projection/type drop the field.
- module.exports in a renderer-imported module hangs the Vite SSR test
  harness (the useChatStreaming tests never finish, which is also what
  stalls the tests jobs in CI). Write the helper in ESM like
  meetingJoinUrl.js: Vite consumes it natively and main-process CJS
  callers load it through Node's require(esm) with syntax detection.
- Split computeCalendarAvailability out of calculateCalendarAvailability
  so the availability service, which validates up front to build its
  cache query window, no longer validates every request twice.
Models often send explicit null for optional tool parameters; the
main-process validator already treats null as absent via its ??
defaults, so drop nulls in the renderer instead of failing the request.
Also derive the seven-day horizon wording in the tool schema from
MAX_AVAILABILITY_HORIZON_DAYS so it cannot drift from validation.
@Chadpiha
Chadpiha self-requested a review August 25, 2026 17:02

@Chadpiha Chadpiha left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — solid work. I did a full pass on this: read every changed file, traced the flow end-to-end (prompt context → tool call → renderer validation → IPC → service → cache query → interval math → localized facts back to the model), and ran the suite, typecheck, i18n check, and a renderer production build locally.

Things I specifically verified:

  • Privacy boundary holds. Only times, durations, and flags ever reach the model; the projection fails closed on malformed payloads, and unknown IPC errors collapse to a generic message with an allowlist for the correctable ones.
  • Cache coverage vs. the 7-day horizon checks out for all three providers (Google 9d lookahead + 1d token TTL, Microsoft 14d/7d TTL + lookback, Apple 8d snapshot + 2h lookback), including ongoing multi-day events since all three provider queries are overlap/end-time based.
  • Microsoft all-day date-only storage — I confirmed against Graph's documented behavior that all-day events stay date-pure at midnight in the preferred zone, and the format change can't regress reminders or the upcoming UI because those queries exclude is_all_day rows.
  • Migration safety — table ordering is right, fresh DBs no-op, and the forced REST resync populates the new columns; until then unknown conservatively blocks, which is the right failure direction.

I pushed two small commits onto the branch:

  1. fix(calendar): load shared availability helper as ESM — the constants-sharing commit imported a CJS (module.exports) helper into the renderer graph, which hangs the Vite-SSR chat-streaming tests (that's why the CI tests jobs were stuck). Converted the helper to ESM per the meetingJoinUrl.js convention, and split computeCalendarAvailability out so the service validates each request once.
  2. fix(calendar): accept null optional args in availability tool — models often send explicit null for optional params; the renderer now treats null as absent like the service's ?? defaults already did (with a regression test), and the seven-day wording in the schema is now derived from MAX_AVAILABILITY_HORIZON_DAYS.

Two behavioral notes for the record, both fine by me: unanswered invitations block availability on all providers (only an explicit decline or free/transparent unblocks — matches provider free/busy semantics), and broad multi-day questions intentionally get a clarifying question about daily hours before per-day calls.

@Chadpiha
Chadpiha merged commit b96f4a2 into OpenWhispr:main Aug 25, 2026
9 checks passed
github-actions Bot pushed a commit to PlaybookMediaLLC/openwhispr that referenced this pull request Aug 26, 2026
# [1.11.0](v1.10.0...v1.11.0) (2026-08-26)

### Bug Fixes

* **ai:** retry HTTP 408 request timeouts in the API retry strategy ([OpenWhispr#1734](https://github.com/PlaybookMediaLLC/openwhispr/issues/1734)) ([d3661cb](d3661cb))
* **auth:** Unify onboarding and reauthentication display ([OpenWhispr#1763](https://github.com/PlaybookMediaLLC/openwhispr/issues/1763)) ([3cc197f](3cc197f))
* **calendar:** fail OAuth loopback immediately on state mismatch ([OpenWhispr#1753](https://github.com/PlaybookMediaLLC/openwhispr/issues/1753)) ([8b77a59](8b77a59))
* **calendar:** group date-only upcoming events on the local calendar day ([2ef397b](2ef397b))
* **calendar:** resolve Microsoft series masters through their calendar ([OpenWhispr#1835](https://github.com/PlaybookMediaLLC/openwhispr/issues/1835)) ([a0bbf4f](a0bbf4f))
* **chat:** persist conversation migration links ([OpenWhispr#1772](https://github.com/PlaybookMediaLLC/openwhispr/issues/1772)) ([15b5b3f](15b5b3f))
* click should be the delibrate action to select model for STT models ([90cfe03](90cfe03))
* **cli:** enforce request body byte limit ([OpenWhispr#1777](https://github.com/PlaybookMediaLLC/openwhispr/issues/1777)) ([996fe79](996fe79))
* **cli:** preserve UTF-8 request bodies ([OpenWhispr#1386](https://github.com/PlaybookMediaLLC/openwhispr/issues/1386)) ([ef627c9](ef627c9))
* **diarization:** diarize the mic track for in-person meetings — fixes [OpenWhispr#1627](https://github.com/PlaybookMediaLLC/openwhispr/issues/1627) ([0a10184](0a10184))
* **diarization:** skip orphan speaker embeddings in softened mic mode ([2ffbe35](2ffbe35))
* **dictation:** initialize onboarding event ref safely ([OpenWhispr#1756](https://github.com/PlaybookMediaLLC/openwhispr/issues/1756)) ([c5d515b](c5d515b))
* dismiss dictation preview when no audio detected ([OpenWhispr#1667](https://github.com/PlaybookMediaLLC/openwhispr/issues/1667)) ([7208bdf](7208bdf))
* **history:** keep the wide two-column layout regardless of calendar connection ([d45d1d2](d45d1d2))
* **i18n:** guard localStorage deref against partial window stubs ([OpenWhispr#1842](https://github.com/PlaybookMediaLLC/openwhispr/issues/1842)) ([0686a26](0686a26))
* **linux:** map KDE punctuation hotkeys to Qt key codes ([OpenWhispr#1752](https://github.com/PlaybookMediaLLC/openwhispr/issues/1752)) ([523acab](523acab))
* **linux:** Preserve focus for Sway overlays ([OpenWhispr#1718](https://github.com/PlaybookMediaLLC/openwhispr/issues/1718)) ([063b5b6](063b5b6))
* **linux:** push-to-talk on Wayland (Hyprland, KDE, GNOME 48+) for dictation hotkey ([OpenWhispr#1738](https://github.com/PlaybookMediaLLC/openwhispr/issues/1738)) ([b5c5fb4](b5c5fb4))
* **local-llm:** Phase 0 correctness + hygiene fixes for local model params and canaries ([OpenWhispr#1714](https://github.com/PlaybookMediaLLC/openwhispr/issues/1714)) ([41cf1d4](41cf1d4)), closes [OpenWhispr#1176](https://github.com/PlaybookMediaLLC/openwhispr/issues/1176) [OpenWhispr#1021](https://github.com/PlaybookMediaLLC/openwhispr/issues/1021) [OpenWhispr#1611](https://github.com/PlaybookMediaLLC/openwhispr/issues/1611)
* **logging:** Suppress packaged Windows console output ([OpenWhispr#1719](https://github.com/PlaybookMediaLLC/openwhispr/issues/1719)) ([30dedcf](30dedcf))
* make model selection an explicit click and only bootstrap if no model is selected/downloaed ([b9b5335](b9b5335))
* **meeting:** handle nullish segments and non-object patches in speakerAssignmentPolicy ([OpenWhispr#1798](https://github.com/PlaybookMediaLLC/openwhispr/issues/1798)) ([c519e1f](c519e1f)), closes [OpenWhispr#1797](https://github.com/PlaybookMediaLLC/openwhispr/issues/1797)
* Mode-switch validity guard ([58d9af0](58d9af0))
* **models:** Honor redirected cache roots ([OpenWhispr#1721](https://github.com/PlaybookMediaLLC/openwhispr/issues/1721)) ([da39195](da39195)), closes [OpenWhispr#1279](https://github.com/PlaybookMediaLLC/openwhispr/issues/1279)
* **network:** treat the full IPv6 link-local range as private HTTP ([OpenWhispr#1733](https://github.com/PlaybookMediaLLC/openwhispr/issues/1733)) ([1fe91f2](1fe91f2))
* **notes:** abort local transcription and diarization when an upload is cancelled — fixes [OpenWhispr#1401](https://github.com/PlaybookMediaLLC/openwhispr/issues/1401) ([7bbc04c](7bbc04c))
* **notes:** format future timestamps as dates ([OpenWhispr#1768](https://github.com/PlaybookMediaLLC/openwhispr/issues/1768)) ([5d5f08c](5d5f08c))
* **notes:** keep markdown mirror paths inside base ([OpenWhispr#1773](https://github.com/PlaybookMediaLLC/openwhispr/issues/1773)) ([2893be4](2893be4))
* **notes:** keep note content visible above the floating chat panel ([OpenWhispr#1769](https://github.com/PlaybookMediaLLC/openwhispr/issues/1769)) ([fa11ddc](fa11ddc))
* **notes:** reject cancelled uploads before ffmpeg conversion and server boot ([53ec075](53ec075))
* **parakeet:** Clarify unsupported-macOS guidance and dedupe version compare ([cd4dedc](cd4dedc)), closes [OpenWhispr#862](https://github.com/PlaybookMediaLLC/openwhispr/issues/862) [OpenWhispr#764](https://github.com/PlaybookMediaLLC/openwhispr/issues/764) [OpenWhispr#862](https://github.com/PlaybookMediaLLC/openwhispr/issues/862)
* **parakeet:** Gate incompatible macOS runtime ([0c271fe](0c271fe)), closes [OpenWhispr#862](https://github.com/PlaybookMediaLLC/openwhispr/issues/862)
* **parakeet:** Localize disabled provider tab labels ([514866c](514866c)), closes [OpenWhispr#862](https://github.com/PlaybookMediaLLC/openwhispr/issues/862)
* **paste:** skip trailing space after CJK text in append smart spacing ([OpenWhispr#1823](https://github.com/PlaybookMediaLLC/openwhispr/issues/1823)) ([f26b059](f26b059))
* retire dead cloud models and make the canary suites trustworthy ([OpenWhispr#1722](https://github.com/PlaybookMediaLLC/openwhispr/issues/1722)) ([9b3f57a](9b3f57a))
* **settings:** Clamp the LLM request timeout input on blur ([7032088](7032088))
* **settings:** close stale-state gaps in explicit model selection ([82fd37a](82fd37a))
* **settings:** enforce auto-end and request timeout defaults ([OpenWhispr#1755](https://github.com/PlaybookMediaLLC/openwhispr/issues/1755)) ([77c6661](77c6661))
* **settings:** harden browse-only provider tabs and drop dead code ([983bf58](983bf58))
* **settings:** keep the browsed whisper-list validation off foreign local models ([204e83b](204e83b))
* **snippets:** do not crash snippet expansion when the list is nullish ([773fed0](773fed0))
* **stores:** Survive imports under partial browser window stubs ([OpenWhispr#1841](https://github.com/PlaybookMediaLLC/openwhispr/issues/1841)) ([b934b88](b934b88))
* **uploads:** anchor upload transcript timestamps to the meeting path's epoch-ms base ([df826f0](df826f0))
* **uploads:** normalize provider names and clamp verbose segment timestamps ([OpenWhispr#1800](https://github.com/PlaybookMediaLLC/openwhispr/issues/1800)) ([d9499f5](d9499f5)), closes [OpenWhispr#1799](https://github.com/PlaybookMediaLLC/openwhispr/issues/1799)
* **utils:** handle nullish and invalid inputs safely in date formatting ([OpenWhispr#1783](https://github.com/PlaybookMediaLLC/openwhispr/issues/1783)) ([OpenWhispr#1784](https://github.com/PlaybookMediaLLC/openwhispr/issues/1784)) ([858e824](858e824))
* validate transcription hosts exactly ([8aced79](8aced79))
* **whisper:** accept any casing for the GPU opt-out flag ([28bf6f4](28bf6f4))
* **whisper:** engage downloaded GPU packs without the env flag ([d4c207a](d4c207a)), closes [OpenWhispr#1340](https://github.com/PlaybookMediaLLC/openwhispr/issues/1340)
* **whisper:** raise decoder anti-hallucination thresholds on local transcription — fixes [OpenWhispr#1458](https://github.com/PlaybookMediaLLC/openwhispr/issues/1458) ([b7fdf80](b7fdf80))
* **windows:** block untrusted file navigation ([OpenWhispr#1776](https://github.com/PlaybookMediaLLC/openwhispr/issues/1776)) ([45c20b8](45c20b8))
* **windows:** restore the captured target window before pasting — fixes [OpenWhispr#859](https://github.com/PlaybookMediaLLC/openwhispr/issues/859) ([de9e335](de9e335)), closes [OpenWhispr#668](https://github.com/PlaybookMediaLLC/openwhispr/issues/668)

### Features

* **billing:** self-serve Enterprise upgrade + admin console entry ([OpenWhispr#1623](https://github.com/PlaybookMediaLLC/openwhispr/issues/1623)) ([e039861](e039861))
* **calendar:** add provider-neutral availability tool ([OpenWhispr#1821](https://github.com/PlaybookMediaLLC/openwhispr/issues/1821)) ([b96f4a2](b96f4a2))
* **calendar:** redesign Coming up sidebar with join-and-take-notes and empty states ([a5e59ee](a5e59ee))
* complete Oppulence Voice cloud integration ([fed389c](fed389c))
* **hyprland:** support both Lua and legacy configs ([OpenWhispr#1664](https://github.com/PlaybookMediaLLC/openwhispr/issues/1664)) ([47da21d](47da21d))
* **llm:** Apply the configurable request timeout to the Tinfoil provider ([f240775](f240775)), closes [OpenWhispr#1479](https://github.com/PlaybookMediaLLC/openwhispr/issues/1479)
* **meetings:** Auto-end forgotten meeting recordings ([OpenWhispr#1494](https://github.com/PlaybookMediaLLC/openwhispr/issues/1494)) ([bc306d2](bc306d2))
* **models:** add Gemini 3.5 and 3.1 Flash Lite models ([OpenWhispr#1702](https://github.com/PlaybookMediaLLC/openwhispr/issues/1702)) ([90f79b6](90f79b6))
* **notes:** real speaker identity in note generation and [@mention](https://github.com/mention) owner tagging ([672cc90](672cc90))
* **onboarding:** rebuild guided setup experience ([OpenWhispr#1670](https://github.com/PlaybookMediaLLC/openwhispr/issues/1670)) ([9382171](9382171)), closes [#141414](https://github.com/PlaybookMediaLLC/openwhispr/issues/141414)
* **policy:** Enforce org-required local model downloads ([OpenWhispr#1836](https://github.com/PlaybookMediaLLC/openwhispr/issues/1836)) ([409bc28](409bc28))
* **policy:** Mirror the server-only memoryEnabled org-policy field ([OpenWhispr#1717](https://github.com/PlaybookMediaLLC/openwhispr/issues/1717)) ([91c1195](91c1195))
* **sidebar:** redesign Upgrade to Pro banner to new card style ([973cbf5](973cbf5))
* **uploads:** persist segment timestamps from BYOK cloud transcriptions for SRT export — fixes [OpenWhispr#1095](https://github.com/PlaybookMediaLLC/openwhispr/issues/1095) ([aada021](aada021))

### Performance Improvements

* **windows:** skip the paste settle when the target is already foreground ([0266639](0266639))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants