Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions docs/LOCAL_DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,9 +247,10 @@ One passing full run per PR is sufficient; iterate freely with

In addition to the body marker, `pre-pr` upserts a single PR comment
identified by `<!-- AGENTIC-VERIFY-COMMENT -->`. The comment carries
the phase table at the top and the full agentic-verify markdown
report inside a `<details>` block, so reviewers can see the verdict
without expanding and read the whole transcript when they want to.
the phase table at the top and local artifact paths for the
agentic-verify markdown, JSON, and trace files. It does not publish
the report contents or trace to GitHub because agentic verification
can inspect mailbox and calendar surfaces during local runs.
Repeated runs PATCH the same comment in place — the thread stays
clean. Use `--no-comment` to skip it (the body marker / CI gate is
unaffected).
Expand All @@ -267,6 +268,8 @@ in CI.
just re-run `setup-dev-data.mjs` to rebuild from the refresh token.
- **`.pre-pr-report.md` is gitignored.** It's a local artifact; the
PR body holds the authoritative version once injected.
- **Agentic traces stay local.** The PR comment links to local artifact
Comment thread
mickn marked this conversation as resolved.
Outdated
paths only; transcripts and tool outputs are not copied into GitHub.

## Plan reference

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
118 changes: 118 additions & 0 deletions docs/superpowers/specs/2026-05-28-calendar-invite-editor-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Calendar Invite Editor Design
Comment thread
mickn marked this conversation as resolved.
Outdated

## Goal

Add a Superhuman-style calendar invite creation flow for selected email threads. The user can trigger the flow from the keyboard or command palette, review AI-extracted event details in the Calendar tab, see the proposed event against their day view, edit fields, and create/send the Google Calendar invite.

## User Flow

When an email thread is selected, the user can press `i` or choose `Create calendar invite` from the `Cmd+K` command palette. Exo switches the right sidebar to the Calendar tab and enters a `new invite` mode.

The Calendar tab first shows an extraction/loading state while AI reads the selected thread. When extraction completes, Exo shows an editable event form above the day view. The proposed event appears as a temporary block in the day view before it exists in Google Calendar. Editing the date, start time, end time, or duration updates the proposed block live so the user can see whether they are free.

The primary action is `Create and send`. It creates the Google Calendar event and sends invitations to guests immediately after the user has reviewed the form. `Cancel` or `Esc` exits invite mode without creating anything.

## UI Design

The invite editor lives inside the existing Calendar sidebar tab rather than as a separate temporary panel. This keeps event creation next to the availability view.

The top form uses compact icon-led rows:

- Title
- Guests
- Date and time
- Video conferencing or meeting link
- Location
- Agenda or notes
- Calendar selector

Below the form, the existing day view remains visible. Existing events render normally. The proposed invite renders as a distinct temporary event block using the extracted title and time.

The editor should fit the right rail without feeling cramped. If needed, the form and day view can share a scrollable column, with the final action bar pinned at the bottom.

## Extraction Behavior

AI extraction is required for all event fields. The extractor receives the selected thread subject, participants, and message bodies, then returns a strict structured draft:

- `title`
- `start`
- `end`
- `timezone`
- `guests`
- `conference`
- `location`
- `description`
- `calendarId`
- `confidence`
- `warnings`

The agent should infer guests from thread context. It should not blindly include every sender, To recipient, and CC recipient.

Google Meet is the default conference option unless the thread clearly contains another meeting link, video provider, or physical location. If the thread includes a Zoom link, Meet link, phone bridge, or address, the extracted draft should preserve that instead of overwriting it with Google Meet.

Timezone should come from the user’s selected Google calendar when available, especially the primary/default calendar. If the calendar timezone is not available, fallback to `Intl.DateTimeFormat().resolvedOptions().timeZone`. The AI may identify an explicitly mentioned timezone, but the editor should normalize and display the event in the user calendar timezone.

Missing or uncertain values should not prevent the panel from opening. The UI should show blanks and inline warnings so the user can complete the event manually.

## Calendar Creation

The MVP supports Google Calendar only.

Creating an invite uses Google Calendar event creation with guest notifications enabled. The event creation request should include:

- selected calendar ID
- summary/title
- start/end with timezone
- attendees
- description
- location
- Google Meet conference creation when selected
- `sendUpdates: "all"` so guests receive invitations immediately

After creation succeeds, Exo should refresh calendar state and exit edit mode so the created event appears in the day view.

## Permissions

The existing calendar integration is read-only. This feature needs Google Calendar write permissions for event creation. Existing users may need to re-authenticate Google once after the scope change.

The UI should handle missing write permission clearly: show an inline error with a re-authenticate action rather than failing silently.

## Error Handling

Warnings should be inline and reviewable:

- missing title
- missing start/end time
- ambiguous duration
- no guests
- no write-capable Google Calendar account
- failed AI extraction
- failed Google Meet creation
- failed event creation

Only final creation should be blocked by missing required fields or missing write permission. Extraction failures should leave the user in an editable blank invite form when possible.

## Testing

Implementation should include focused tests for:

- AI extractor schema parsing and fallback warnings
- timezone selection from Google calendar metadata with local fallback
- event creation payload generation
- `sendUpdates: "all"` behavior
- Google Meet conference payload behavior
- missing-field validation before final creation
- `i` shortcut and `Cmd+K` command availability
- Calendar tab editor rendering proposed event alongside existing events
- error states for extraction failure and calendar creation failure

## Non-Goals

This feature does not include:

- non-Google calendar providers
- editing or deleting existing calendar events
- sending invites without a review screen
- automatic scheduling optimization across multiple proposed times
- RSVP management
- background invite creation from the agent without the Calendar tab editor
24 changes: 8 additions & 16 deletions scripts/agentic-verify.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -579,11 +579,6 @@ async function main() {
const toolCalls = [];
const textChunks = [];
let resultMeta = null;
// Per-tool-call cap so a single huge `take_snapshot` (which dumps
// the entire DOM) can't bloat the log file. The PR comment trims
// again at body-size scale; this cap keeps individual entries
// human-readable while preserving the bulk of the trace.
const TOOL_RESULT_LOG_CAP = 4000;
let toolIndex = 0;
const toolIdToIndex = new Map();

Expand All @@ -607,6 +602,12 @@ async function main() {
return JSON.stringify(c ?? "");
}

function summarizeToolResult(block) {
const text = extractToolResultText(block);
const kind = block.is_error ? "error" : "result";
return `[${kind} content omitted; ${text.length} chars]`;
}

for await (const msg of result) {
if (msg.type === "system" && msg.subtype === "init") {
const cdpTools = (msg.tools ?? []).filter((t) =>
Expand All @@ -624,7 +625,7 @@ async function main() {
log(` input: ${formatToolInput(block.input)}`);
} else if (block.type === "text" && block.text) {
textChunks.push(block.text);
log(`text: ${block.text}`);
log(`text: [omitted; agent narration may include mailbox/calendar content]`);
}
}
}
Expand All @@ -633,16 +634,7 @@ async function main() {
if (block.type === "tool_result") {
const idx = toolIdToIndex.get(block.tool_use_id) ?? "?";
const tag = block.is_error ? "error" : "result";
const text = extractToolResultText(block);
Comment thread
mickn marked this conversation as resolved.
const capped =
text.length > TOOL_RESULT_LOG_CAP
? text.slice(0, TOOL_RESULT_LOG_CAP) +
` …[truncated, ${text.length - TOOL_RESULT_LOG_CAP} more chars]`
: text;
// Indent multi-line output so it's visually grouped with
// the tool call in the log.
const indented = capped.split("\n").map((l) => ` ${l}`).join("\n");
log(`${tag}#${idx}:\n${indented}`);
log(`${tag}#${idx}: ${summarizeToolResult(block)}`);
}
}
}
Expand Down
Loading