Skip to content

feat(agent-cal): connection-based unified calendar API, SDK, and CLI with connect command#28297

Closed
sahitya-chandra wants to merge 18 commits intomainfrom
feat/agent-cal-connection-based-api
Closed

feat(agent-cal): connection-based unified calendar API, SDK, and CLI with connect command#28297
sahitya-chandra wants to merge 18 commits intomainfrom
feat/agent-cal-connection-based-api

Conversation

@sahitya-chandra
Copy link
Copy Markdown
Member

@sahitya-chandra sahitya-chandra commented Mar 5, 2026

What does this PR do?

Implements Agent Cal — a product enabling AI agents to connect to user calendars via Cal.com's verified OAuth infrastructure, eliminating the need for agent developers to set up their own Google/Outlook OAuth credentials.

4 Workstreams

1. Backend API — Unified Calendar API Extensions (apps/api/v2/)

  • New GET /v2/calendars/connections endpoint returning all calendar connections with connectionId
  • Connection-scoped CRUD: GET/POST/PATCH/DELETE /v2/calendars/connections/{connectionId}/events/*
  • Connection-scoped free/busy: GET /v2/calendars/connections/{connectionId}/freebusy
  • Legacy calendar-type endpoints: GET/POST/DELETE /v2/calendars/{calendar}/events, GET /{calendar}/freebusy
  • Backward compat: dual @Get/@Patch decorators for singular /event/ (deprecated) and plural /events/
  • ConnectedCalendarEntry interface to eliminate inline type annotations
  • DRY service layer with shared private helpers (listEventsWithClient, createEventWithClient, etc.)
  • Input validation: @IsDefined() on start/end, @IsTimeZone() on timezone fields, cross-field to >= from validation

2. @calcom/agent-cal SDK (packages/agent-cal/)

  • TypeScript SDK with AgentCal client class (API key or OAuth token auth)
  • Methods: getConnections(), listEvents(), createEvent(), getEvent(), updateEvent(), deleteEvent(), getFreeBusy()
  • HTTP client with exponential backoff retries on 429/5xx
  • PKCE auth utilities, credential storage (~/.agentcal/credentials.json)
  • Unit tests for client and HTTP utilities

3. CLI Auth Flow (in packages/agent-cal/src/cli.ts)

  • npx @calcom/agent-cal auth — PKCE OAuth with localhost callback server (port 9876)
  • npx @calcom/agent-cal connect — Opens Cal.com calendar settings in the browser to connect Google, Outlook, or Apple Calendar
  • Subcommands: status, events list, token, disconnect, mcp
  • Server-side token exchange (tokens never travel via browser redirect)
  • OAuth scope: READ_BOOKING READ_PROFILE

4. OAuth Landing Page (apps/web/app/agent/oauth/page.tsx)

  • Minimal branded entry point that redirects to Cal.com OAuth authorize with forwarded query params
  • Preserves array query params via append() (no silent collapsing)
  • MVP stub — full calendar connection UI with @calcom/atoms planned for Phase 2

Bonus: MCP Server (packages/agent-cal/src/mcp/server.ts)

  • JSON-RPC 2.0 over stdio exposing all calendar tools for MCP-compatible agent hosts
  • Handles notifications/initialized as no-op per MCP protocol
  • Parse errors send null ID per JSON-RPC spec

Updates since last revision

New: All-day events now included in listEvents response

  • Previously, listEventsWithClient filtered events with e.start?.dateTime != null, which silently dropped all-day events (Google Calendar uses start.date instead of start.dateTime for all-day events)
  • GoogleCalendarEventResponse interface updated: dateTime/timeZone now optional, added date field on start/end
  • Output pipe transformDateTimeWithZone now handles both timed events and all-day events, converting date-only (e.g. "2026-03-20") to midnight UTC ("2026-03-20T00:00:00" with timezone "UTC")
  • Filter now accepts events with either dateTime or date: e.start?.dateTime != null || e.start?.date != null

Previous: connect CLI command

  • Added npx @calcom/agent-cal connect — opens the user's Cal.com calendar settings page in the browser so they can add Google, Outlook, or Apple Calendar without manually navigating the URL
  • Derives web app URL from NEXT_PUBLIC_WEBAPP_URL or CAL_OAUTH_AUTHORIZE_URL env vars, falling back to https://app.cal.com
  • status command now suggests running connect when no calendars are connected (instead of a generic URL)
  • README updated with connect command documentation

Previous: Security & validation fixes:

  • Security: HTML-escape user-controlled values in CLI OAuth callback responses to prevent reflected XSS
  • Security: Restrict credential file permissions to 0o600 (owner read/write only) on ~/.agentcal/credentials.json
  • Security: CONFIDENTIAL OAuth client enforcement — token endpoint requires client_secret for CONFIDENTIAL clients; PKCE alone is insufficient
  • Security: JWT revocation validation — removed bypass that allowed revoked JWTs to pass auth checks
  • Added @IsDefined() validators on start/end event fields (prevents partial event creation)
  • Added @IsTimeZone() validation on timezone parameters (rejects invalid IANA timezones)
  • Added cross-field to >= from validation on date ranges via custom @Validate(IsAfterFrom) decorator

Previous: Bug fixes & improvements:

  • Fixed OAuth scope parsing to use whitespace (RFC 6749) instead of commas in authorize-view.tsx
  • CLI token URL derivation — when using local Cal.com (e.g. localhost:3000), token endpoint now uses same origin instead of hardcoded api.cal.com
  • Token route falls back to form-urlencoded when Content-Type header missing (RFC 6749 §4.1.3 compliance)
  • deleteEvent now checks for status: "error" in response (was silently ignoring errors)
  • getDefaultGoogleCalendarId continues searching when first connection has no usable calendar (was returning null too early)
  • base64UrlEncode throws error instead of returning empty string when no encoder available
  • MCP parse errors now send null ID per JSON-RPC 2.0 spec (was sending undefined)
  • OAuth page preserves array query params via URLSearchParams.append() (was collapsing to first value)
  • cmdToken() writes token directly to stdout via process.stdout.write (no log prefix for piping)
  • README error example corrected to use connId instead of "google" string

Previous: API shape changes:

  • CalendarConnectionItem.email is now string | null (was string! with "unknown" fallback) — BREAKING for consumers expecting always-present email field
  • Removed @IsEmail() validator from email field (allows null values)
  • Fixed OAuth scope from READ_CALENDARS WRITE_CALENDARS to valid Cal.com scopes READ_BOOKING READ_PROFILE
  • Token route error responses now include error_description field per RFC 6749
  • Token route accepts both JSON and form-urlencoded request bodies (via parseRequestData)

Previous: Code quality:

  • Extracted ConnectedCalendarEntry interface in controller (replaced inline type annotations)
  • Extracted shared private helpers in GoogleCalendarService to DRY up user-scoped vs connection-scoped methods
  • Fixed misleading JSDoc on needsRefresh() function
  • Build script now uses tsup.config.ts (includes MCP server entry point)

Important items for human review

  • BREAKING CHANGE: email field now nullableCalendarConnectionItem.email changed from string! (always present) to string | null. Consumers expecting .email to always be a string will need updates.
  • All-day event representation — All-day events from Google Calendar (which use start.date instead of start.dateTime) are now converted to midnight UTC. For example, a March 20 all-day event becomes "2026-03-20T00:00:00" with timezone "UTC". Google's end.date is exclusive (next day), so it appears as "2026-03-21T00:00:00 UTC". Consumers should be aware that there's no explicit allDay boolean flag — they must infer this from the 00:00:00 time and UTC timezone pattern.
  • ConnectedCalendarEntry type assertion: Controller casts connectedCalendars as ConnectedCalendarEntry[]. If CalendarsService.getCalendars() returns a different shape, runtime errors may occur. Consider adding runtime validation.
  • Token route fallback logic — when Content-Type header is missing, now falls back to form-urlencoded (was defaulting to JSON). Could this break existing token consumers that were relying on JSON being the implicit default?
  • agent.cal.com stub: Current implementation is a redirect page. Full branded landing + calendar connection UI (with @calcom/atoms) is Phase 2.
  • No integration tests: Only unit tests for SDK. API endpoints and CLI auth flow are untested in CI.
  • connect command URL derivation: Constructs web app URL from env vars with multiple fallbacks. If env vars are partially set or misconfigured, could redirect to wrong URL.
  • Large PR size: 3300+ lines across 26 files. Consider reviewing in phases: (1) Backend API, (2) SDK, (3) CLI + MCP.

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. If N/A, write N/A here and check the checkbox.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

Prerequisites

  • Cal.com OAuth client with redirect URI http://localhost:9876/callback
  • Google Calendar connected to your Cal.com account (or use the connect command below)

Test flow (CLI)

# 1. Auth with Cal.com
npx @calcom/agent-cal auth --client-id YOUR_CLIENT_ID

# 2. Connect a calendar (opens browser to Cal.com calendar settings)
npx @calcom/agent-cal connect

# 3. List connections (should show Google with connectionId)
npx @calcom/agent-cal status

# 4. List events (should include both timed events and all-day events)
npx @calcom/agent-cal events list --from 2026-03-01 --to 2026-03-31

# 5. Test SDK programmatically
node -e "
import { AgentCal } from '@calcom/agent-cal';
const cal = new AgentCal(); // auto-load from ~/.agentcal/
const conns = await cal.getConnections();
console.log(conns);
"

Test flow (API)

# Authenticate to get access token
ACCESS_TOKEN="..." # from auth flow above or API key

# List connections (check that email is null for connections without email)
curl -H "Authorization: Bearer $ACCESS_TOKEN" \
  https://api.cal.com/v2/calendars/connections

# List events for a connection (should include all-day events)
curl -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://api.cal.com/v2/calendars/connections/123/events?from=2026-03-01&to=2026-03-31"

# Create event
curl -X POST -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title":"Test","start":{"time":"2026-03-10T14:00:00","timeZone":"UTC"},"end":{"time":"2026-03-10T15:00:00","timeZone":"UTC"}}' \
  https://api.cal.com/v2/calendars/connections/123/events


# Test token endpoint with missing Content-Type (should fall back to form-urlencoded)
curl -X POST \
  -d "grant_type=authorization_code&code=...&client_id=...&redirect_uri=..." \
  https://api.cal.com/v2/auth/oauth2/token

Test validation edge cases

# Test invalid timezone (should fail with @IsTimeZone validation error)
curl -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://api.cal.com/v2/calendars/connections/123/events?from=2026-03-01&to=2026-03-31&timeZone=Invalid/Zone"

# Test to-before-from (should fail with cross-field validation error)
curl -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://api.cal.com/v2/calendars/connections/123/freebusy?from=2026-03-31&to=2026-03-01"

# Test event creation without start/end (should fail with @IsDefined validation)
curl -X POST -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title":"Test"}' \
  https://api.cal.com/v2/calendars/connections/123/events

Environment variables


Link to Devin Session: https://app.devin.ai/sessions/eea91e3180b7429d9b39a3bb36028315
Requested by: @sahitya-chandra

- Backend: GET /v2/calendars/connections and connection-scoped events/freebusy
  - GET/POST /connections/{connectionId}/events, GET/PATCH/DELETE .../events/{eventId}, GET .../freebusy
  - CredentialsRepository.findCredentialByIdAndUserId, GoogleCalendarService methods by credentialId
- SDK: getConnections(), all event/freebusy methods take connectionId (not calendar type)
- CLI: status shows connectionIds; events list uses --connection-id or first connection
- MCP: list_connections tool; all tools use connection_id (optional, defaults to first)
- Deprecation note for GET /:calendar/event/ (prefer /events/ and connection-scoped)
@devin-ai-integration
Copy link
Copy Markdown
Contributor

PR Review — Agent Cal Implementation vs Plan

I've reviewed the full diff against the implementation plan we iterated on. Here's my assessment:


Plan Coverage

Plan Item Status Notes
WS1: GET /v2/calendars/connections ✅ Done Lists connections with connectionId, type, email
WS1: GET /connections/{connectionId}/events ✅ Done Date range filtering with from/to
WS1: POST /connections/{connectionId}/events ✅ Done
WS1: GET /connections/{connectionId}/events/{eventId} ✅ Done
WS1: PATCH /connections/{connectionId}/events/{eventId} ✅ Done
WS1: DELETE /connections/{connectionId}/events/{eventId} ✅ Done
WS1: GET /connections/{connectionId}/freebusy ✅ Done
WS1: Legacy /{calendar}/events endpoints ✅ Done list, create, delete, freebusy all added
WS1: Rewrite middleware (singular→plural) ⚠️ Handled differently The existing dual @Get decorators (/:calendar/event/:eventUid + /:calendar/events/:eventUid) already handle backward compat for GET. No explicit middleware was added. This works, but see issue #1 below.
WS2: @calcom/agent-cal SDK ✅ Done Full client with connectionId-based CRUD, types, HTTP with retries
WS2: Unit tests ✅ Done client.test.ts + http.test.ts with mocked fetch
WS3: CLI auth (PKCE) ✅ Done Secure flow — code+state via redirect, CLI exchanges server-side
WS3: CLI subcommands ✅ Done auth, status, events list, token, disconnect, mcp
WS4: agent.cal.com landing ⚠️ Minimal See issue #5 below
MCP server ✅ Done (bonus) stdio JSON-RPC with all calendar tools

Issues Found

1. PATCH backward compatibility gap

The original controller already has dual @Get decorators for singular/plural:

@Get("/:calendar/event/:eventUid")
@Get("/:calendar/events/:eventUid")

But updateCalendarEvent only has @Patch("/:calendar/events/:eventUid"). If any existing consumer was hitting PATCH /:calendar/event/:eventUid (singular), that route doesn't exist.

Recommendation: Verify if the legacy PATCH was ever singular (/event/). If it was, add a dual @Patch("/:calendar/event/:eventUid") decorator on updateCalendarEvent to match the GET pattern — or add the rewrite middleware the plan called for.

2. CalendarConnectionItem.email validation conflict

In list-connections.output.ts, the email field has both @IsString() and @IsEmail():

@IsString()
@IsEmail()
email!: string;

But in the controller, you fall back to "unknown" when no email is found:

const email = c.primary?.externalId ?? c.primary?.email ?? "";
return { ...  email: email || "unknown" };

"unknown" won't pass @IsEmail() validation. If NestJS response validation pipes are enabled, this will throw. Either remove @IsEmail() or ensure the email is always valid (skip connections without an email).

3. OAuth scope seems too narrow

The CLI requests scope: "READ_PROFILE":

authUrl.searchParams.set("scope", "READ_PROFILE");

This likely won't grant the token permission to do calendar CRUD operations (create, update, delete events). You probably need calendar-scoped permissions. Check what scopes Cal.com's OAuth supports for unified calendar access.

4. Service layer code duplication

GoogleCalendarService now has parallel methods that are nearly identical:

  • listEventsForUser / listEventsForUserByConnectionId
  • createEventForUser / createEventForUserByConnectionId
  • deleteEventForUser / deleteEventForUserByConnectionId

The only difference is how they obtain the calendar_v3.Calendar instance. Consider extracting the shared logic:

private async listEventsWithClient(
  calendar: calendar_v3.Calendar, calendarId: string, timeMin: string, timeMax: string
): Promise<GoogleCalendarEventResponse[]> { ... }

async listEventsForUser(userId, calendarId, timeMin, timeMax) {
  const calendar = await this.getCalendarClientForUser(userId);
  return this.listEventsWithClient(calendar, calendarId, timeMin, timeMax);
}

async listEventsForUserByConnectionId(userId, credentialId, calendarId, timeMin, timeMax) {
  const calendar = await this.getCalendarClientByCredentialId(userId, credentialId);
  return this.listEventsWithClient(calendar, calendarId, timeMin, timeMax);
}

5. agent.cal.com page is a redirect stub

The plan's Workstream 4 called for:

  1. OAuth authorize page with branding ("Connect your calendar for [Agent Name]")
  2. Calendar connection page (post-auth) using @calcom/atoms (<Connect.GoogleCalendar />, etc.)
  3. Callback handler

What was implemented is a minimal server-side redirect at apps/web/app/agent/oauth/page.tsx that forwards all query params to /auth/oauth2/authorize. This means:

  • No branded landing page for agents
  • No calendar connection UI (Phase 2 of auth — connecting Google/Outlook — happens entirely within Cal.com's existing settings flow, not embedded in the agent.cal.com flow)
  • No callback handler on the web side

This is fine as an MVP/Phase 1, but should be called out as incomplete for the full vision. The two-phase auth flow (Cal.com login → calendar provider connection) currently requires the user to manually connect calendars in their Cal.com settings after OAuth.

6. Controller has heavy inline type annotations

The controller uses many inline type casts instead of typed interfaces:

.filter((c: { integration: { type: string } }) => c.integration.type === GOOGLE_CALENDAR_TYPE)
.map((c: { credentialId: number; integration: { type: string }; primary?: { externalId?: string; email?: string } }) => { ... })

This suggests CalendarsService.getCalendars() returns a loosely typed result. Consider creating a proper interface for the connected calendar response shape, or importing one if it exists.

7. MCP server doesn't handle notifications/initialized

MCP protocol requires clients to send notifications/initialized after the initialize response. Your server returns a -32601 Method not found error for this. It should be handled as a no-op:

if (req.method === "notifications/initialized" || req.method?.startsWith("notifications/")) {
  return; // notifications don't require a response
}

8. Build script vs tsup.config.ts mismatch

package.json build script:

"build": "tsup src/index.ts src/cli.ts --format cjs,esm --dts --clean"

tsup.config.ts:

entry: ["src/index.ts", "src/cli.ts", "src/mcp/server.ts"]

The build script passes args directly and ignores tsup.config.ts, so the MCP server entry point won't be bundled when running npm run build. Either update the script to just "build": "tsup" (which uses the config file) or add src/mcp/server.ts to the script args.


What looks good

  • Security of auth flow is correct — PKCE with server-side token exchange, state validation, no tokens in redirects. This matches the plan exactly.
  • SDK API design is cleanconnectionId-first API, proper TypeScript types, encodeURIComponent on path params, error handling.
  • Tests cover the right things — HTTP retries, auth validation, all CRUD methods, custom baseUrl.
  • Connection-based routing is properly implemented — Both connection-scoped and legacy calendar-type-scoped endpoints coexist.
  • The CredentialsRepository.findCredentialByIdAndUserId properly scopes by both credentialId AND userId, preventing users from accessing other users' connections.

Summary

The core architecture is solid and closely follows the plan. The main gaps are:

  1. Scope issue (READ_PROFILE may not be enough for calendar CRUD) — this could be a blocker
  2. agent.cal.com is a stub — acceptable for Phase 1 but needs buildout
  3. Service duplication — polish item
  4. MCP notifications handling — small fix needed

Let me know if you want me to fix any of these issues directly.

cubic-dev-ai[bot]

This comment was marked as resolved.

- Add dual @patch decorator for legacy singular /event/ backward compat
- Remove @isemail on CalendarConnectionItem.email (conflicts with 'unknown' fallback)
- Broaden OAuth scope to READ_PROFILE READ_CALENDARS WRITE_CALENDARS
- Extract shared private helpers in GoogleCalendarService (DRY)
- Extract ConnectedCalendarEntry interface in controller (remove inline types)
- Handle MCP notifications/initialized as no-op instead of error
- Fix build script to use tsup.config.ts (includes MCP server entry)
cubic-dev-ai[bot]

This comment was marked as resolved.

…edential file permissions

- Add escapeHtml() helper to sanitize user-controlled values in HTML responses
- Apply escapeHtml() to error query param and token exchange error messages
- Set mode 0o600 on credentials.json to prevent other local users from reading tokens
READ_CALENDARS and WRITE_CALENDARS don't exist in Cal.com's AccessScope enum.
The only valid scopes are READ_BOOKING and READ_PROFILE. Calendar CRUD works
via the unified calendar API endpoints which check token validity, not
specific calendar scopes.
- Parse OAuth scope by whitespace (RFC 6749) in authorize view
- Allow CONFIDENTIAL clients to use PKCE without client_secret
- Derive token URL from authorize URL when set; pass CAL_API_BASE_URL in CLI
- Web token route: accept JSON, return error_description
- API v2: validate web-issued JWTs (CALENDSO_ENCRYPTION_KEY) when not in DB
cubic-dev-ai[bot]

This comment was marked as resolved.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 6, 2026

Devin AI is addressing Cubic AI's review feedback

A Devin session has been created to address the issues identified by Cubic AI.

View Devin Session

… remove JWT revocation bypass

- OAuthService.ts: Revert validateClient to always require client_secret for
  CONFIDENTIAL clients. PKCE supplements but never replaces client authentication
  per RFC 6749 §2.3. Previously, passing any code_verifier allowed skipping
  client_secret verification entirely.

- tokens.repository.ts: Remove decodeWebOAuthToken JWT fallback from
  getAccessTokenExpiryDate, getAccessTokenOwnerId, and getAccessTokenClient.
  When a token is revoked (deleted from DB), the JWT fallback would still accept
  it as valid until natural expiry, bypassing revocation. Web-issued tokens must
  be stored in the accessToken table so deletion equals revocation.

Co-Authored-By: bot_apk <apk@cognition.ai>
devin-ai-integration bot and others added 2 commits March 6, 2026 11:18
…mData

The route was changed to import parseRequestData but the test mock still
referenced parseUrlFormData, causing all 13 token endpoint tests to fail
with 500 errors.

Co-Authored-By: bot_apk <apk@cognition.ai>
- P0: cmdToken() writes token to stdout via process.stdout.write (no log prefix)
- P1: MCP parse-error sends null ID per JSON-RPC spec (was undefined)
- P1: Add @isdefined() on start/end fields in CreateUnifiedCalendarEventInput
- P1: Token route falls back to form-urlencoded when Content-Type missing (RFC 6749)
- P2: email field on CalendarConnectionItem is now nullable (was 'unknown' sentinel)
- P2: deleteEvent checks for status:'error' responses
- P2: getDefaultGoogleCalendarId continues searching when connection has no calendar
- P2: base64UrlEncode throws instead of returning empty string when no encoder
- P2: README error example uses connId instead of 'google'
- P2: timeZone validated with @IsTimeZone(); to-before-from cross-field validation
- P2: OAuth page preserves array query params via append()
- P3: Fixed misleading JSDoc on needsRefresh()
cubic-dev-ai[bot]

This comment was marked as resolved.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 6, 2026

Devin AI is addressing Cubic AI's review feedback

New feedback has been sent to the existing Devin session.

View Devin Session

devin-ai-integration bot and others added 3 commits March 6, 2026 11:31
…rseRequestData/parseUrlFormData

Co-Authored-By: bot_apk <apk@cognition.ai>
- Remove raw token output from CLI (security: avoid logging sensitive info)
  Replaced 'token' command with 'token-info' that shows masked token + expiry
- Freebusy validator throws BadRequestException for invalid date ranges
  instead of returning false, keeping API error response structure consistent
@devin-ai-integration
Copy link
Copy Markdown
Contributor

Cubic AI Feedback — Addressed

Both high-confidence issues identified by Cubic AI have been fixed in commit 8582731:

  1. P1 (confidence 10/10): Avoid Logging Sensitive InformationcmdToken() no longer outputs raw access tokens to stdout. Replaced token command with token-info that shows a masked token (abc123...wxyz) plus expiry status.

  2. P2 (confidence 9/10): Freebusy validator error handlingIsAfterFrom custom validator now throws BadRequestException instead of returning false, keeping API error responses in the standard structure.


✅ Pushed commit 8582731

@devin-ai-integration
Copy link
Copy Markdown
Contributor

Cubic AI review feedback — addressed issues:

  1. OAuthService.ts:374 (confidence 9/10) — Fixed. Removed the code_verifier-only bypass for CONFIDENTIAL clients. Per RFC 6749 §2.3, PKCE supplements but never replaces client authentication. CONFIDENTIAL clients must always provide client_secret.

  2. tokens.repository.ts:181 (confidence 9/10) — Fixed. Removed the JWT fallback (decodeWebOAuthToken) that bypassed token revocation checks. When a token is revoked/deleted from the DB, it is now properly rejected instead of being accepted via JWT signature verification alone.

Skipped issue:

  1. token/route.ts:12 (confidence 7/10) — Skipped. Per instructions, only issues with confidence ≥ 9/10 are addressed. This issue has since been addressed separately in commit 51f78e3da1 by the PR author.

Test fix:

  • Updated the unit test mock for route.test.ts to add headers support to MockNextRequest and export both parseRequestData and parseUrlFormData to match the updated route implementation. All 13 tests now pass.

@sahitya-chandra sahitya-chandra marked this pull request as ready for review March 10, 2026 14:22
@sahitya-chandra sahitya-chandra requested a review from a team as a code owner March 10, 2026 14:22
@graphite-app graphite-app bot added the community Created by Linear-GitHub Sync label Mar 10, 2026
…ettings

Opens the user's Cal.com instance calendar settings page in the browser
so they can connect Google, Outlook, or Apple Calendar. Derives the URL
from NEXT_PUBLIC_WEBAPP_URL or CAL_OAUTH_AUTHORIZE_URL env vars, falling
back to https://app.cal.com.

Also updates 'status' no-connections message to suggest 'connect' command.
@devin-ai-integration devin-ai-integration bot changed the title feat(agent-cal): connection-based unified calendar API and SDK (getConnections, connectionId-scoped endpoints) feat(agent-cal): connection-based unified calendar API, SDK, and CLI with connect command Mar 11, 2026
All-day Google Calendar events use start.date instead of start.dateTime.
The previous filter (e.start?.dateTime != null) silently dropped them.

Changes:
- GoogleCalendarEventResponse interface: dateTime/timeZone now optional, added date field
- listEventsWithClient: filter accepts events with either dateTime or date
- Output pipe transformDateTimeWithZone: converts date-only to midnight UTC
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 2 potential issues.

View 6 additional findings in Devin Review.

Open in Devin Review

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/api/v2/src/modules/cal-unified-calendars/pipes/get-calendar-event-details-output-pipe.ts">

<violation number="1" location="apps/api/v2/src/modules/cal-unified-calendars/pipes/get-calendar-event-details-output-pipe.ts:129">
P2: Don’t default the timezone to "UTC" when Google omits `timeZone`; if the `dateTime` already carries an offset, forcing UTC makes the output inconsistent and can shift client-side interpretation. Derive the offset from `dateTime` (or leave it unset) instead of hardcoding UTC.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 11, 2026

Devin AI is addressing Cubic AI's review feedback

New feedback has been sent to the existing Devin session.

View Devin Session


✅ No changes pushed

@devin-ai-integration
Copy link
Copy Markdown
Contributor

Cubic AI flagged 1 issue on the all-day events commit:

Violation 1 (confidence 7/10): UTC timezone default in transformDateTimeWithZone — Skipped per policy (confidence < 9/10). The suggestion to derive timezone from the dateTime offset string is reasonable but low-confidence. The current ?? "UTC" fallback is consistent with the all-day event handling and matches how Google Calendar typically includes timeZone alongside dateTime.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/api/v2/src/modules/cal-unified-calendars/inputs/freebusy-unified.input.ts">

<violation number="1" location="apps/api/v2/src/modules/cal-unified-calendars/inputs/freebusy-unified.input.ts:11">
P2: Throw a BadRequestException here instead of returning false so the API uses the standard error response format.

(Based on your team's feedback about validators throwing BadRequestException on invalid input.) [FEEDBACK_USED]</violation>
</file>

<file name="apps/web/modules/auth/oauth2/authorize-view.tsx">

<violation number="1" location="apps/web/modules/auth/oauth2/authorize-view.tsx:41">
P1: Scope parsing now accepts comma-delimited scopes, which can incorrectly grant permissions from malformed OAuth scope input. Keep scope splitting space-only.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 11, 2026

Devin AI is addressing Cubic AI's review feedback

New feedback has been sent to the existing Devin session.

View Devin Session


✅ Pushed commit 7225a2d

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community Created by Linear-GitHub Sync size/XXL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant