CedarCopilot wants to merge 3 commits into staging from feat/slack-send-email-action
Libra ran this behavior against the change and confirmed the check detects when it breaks.
Changed code: google.ts.
Live on prod, 3 of 9 surfaces working, less than a day left
Verification cited only 1 matching post-deploy event, below the 20-event floor for calling a change verified. Nothing is failing; there is not yet enough traffic to confirm it.
Verification cited only 1 matching post-deploy event, below the 20-event floor for calling a change verified. Nothing is failing; there is not yet enough traffic to confirm it.
Verification cited only 1 matching post-deploy event, below the 20-event floor for calling a change verified. Nothing is failing; there is not yet enough traffic to confirm it.
Verification cited only 1 matching post-deploy event, below the 20-event floor for calling a change verified. Nothing is failing; there is not yet enough traffic to confirm it.
Verification cited only 1 matching post-deploy event, below the 20-event floor for calling a change verified. Nothing is failing; there is not yet enough traffic to confirm it.
Since deploy, OTEL found no /slack/interactions or send_email_draft-specific spans. CloudWatch shows 4 Slack interaction handler executions (8 handler log lines) across 2 users, with no tied handler failures. One outbound structured response explicitly contained a.
Libra measured 3 of 9 production surfaces on prod. 1 surface had under 20 requests, so Libra has not judged it; 4 surfaces had no requests at all. Libra checks hourly for 3 days after each deploy.
Adds a send_email_draft operation to the Slack button registry so a drafted email posted to Slack gets a one-click Send action alongside the existing "Review Draft" link button, gated by a native Slack confirmation dialog , sending is the first irreversible op in this registry.
See docs/design/slack-send-email-action.md for the full design writeup. Key points:
GoogleMailManager.parseDraft discarded Gmail's own message.threadId. Naively echoing it back isn't enough on its own , Gmail assigns every message a threadId, including a brand-new draft's only message (threadId === message.id there, since it's the thread's first message). A reply's threadId differs from its own message id. parseDraft now derives threadId only when it represents a genuine reply, matching the same distinction createDraft already relies on when writing.send_email_draft reuses the existing sendDraft/getDraft/updateDraftSent/completeTaskByDraftId path rather than calling Gmail directly , it inherits assertProviderSideEffectsAllowed/withSuppressedDelivery for free. It's idempotent (checks analytics_draft_actions first; falls back to treating a getDraft 404 as "already sent," since that check is best-effort and Gmail deletes a draft immediately after sending), and refuses to send a draft with no recipient (drafting deliberately leaves to blank rather than guess one).confirm dialog renders client-side before any webhook fires , a decline never reaches the server, so there's nothing to log, and any dynamic confirm text has to be baked in at message-build time. Added to the button schema (and its two duplicates in notifyUserTool.ts/notifyTool.ts), plus a hardcoded default specifically for send_email_draft so sending is never one click away regardless of what the agent's JSON contains.listTasksTool.ts) , without it there was no value to put in the button's args.draftId.ParsedDraft.threadId fix in the driver (lib/driver/google.ts, lib/driver/types.ts).send_email_draft op in operation-registry.ts.confirm schema field + Block Kit rendering + hardcoded default (headless-response-schemas.ts, notifyUserTool.ts, notifyTool.ts, slack-block-kit.ts).slack-bot-chat.ts, operation-registry.ts's agent_prompt example, listTasksTool.ts).slack-interactions.ts ("Click the button to retry" instead of a hardcoded "Click Accept," which was actively wrong for this op's "Send" label).Explicitly not extended to iMessage tapbacks , a tapback is a single instant reaction with no confirm-dialog equivalent, so a "Send" tapback would be one fat-finger tap from an irreversible send with no gate at all.
Libra flagged two things on the commit:
cancel_task/snooze_task/agent_prompt have none either , only buildApprovalApplyPrompt (a pure function) is tested; handleSlackInteraction has no test file today. New tests match existing precedent (testing the op's execute directly).getDraft/sendDraft/create all route through the shared withGmailCall → handleOperationError wrapper (google.ts:5241-5249), which already checks invalid_grant generically for every Gmail call. This PR adds no new raw Gmail API surface , it calls the same existing send.ts functions every other send surface uses , so this handl| Surface | Requests | Errors | p95 | Users | Verdict |
|---|---|---|---|---|---|
| /a8n/notify/google | 1032 → 513 | 0 → 0 (0%) | 226 ms → 157 ms | 0 | Working 513 requests since the deploy with 0 errors (0 errors in the 1,032-request baseline before it). p95 157 ms, was 226 ms. |
| POST /a8n/notify/google | 296 → 171 | 0 → 0 (0%) | 250 ms → 157 ms | 0 | Working 171 requests since the deploy with 0 errors (0 errors in the 296-request baseline before it). p95 157 ms, was 250 ms. |
| /a8n/notify/google-calendar | 131 → 36 | 0 → 0 (0%) | 135 ms → 59 ms | 0 | Working 36 requests since the deploy with 0 errors (0 errors in the 131-request baseline before it). p95 59 ms, was 135 ms. |
| gmail.users.threads.get | 329 → 474 | 10 → 6 (1%) | 612 ms → 419 ms | 73 | Pre-existing 6 of 474 requests failed (1.3%) since the deploy, matching the 3% baseline error rate before it. Not attributable to this change. |
| POST /a8n/notify/google-calendar | 39 → 12 | 0 → 0 (0%) | 188 ms → 59 ms | 0 | Insufficient traffic 12 requests, under the 20 Libra needs |
| google_subscription_subscribe | 0 → 0 | 0 → 0 (0%) | not measured | 0 | No traffic No requests recorded since this deploy. |
| execute_tool notify-user | 2 → 0 | 0 → 0 (0%) | 1030 ms → not measured | 0 | No traffic No requests recorded since this deploy. |
| /api/trpc/recall.getCallSessionByGoogleEventId | 0 → 0 | 0 → 0 (0%) | not measured | 0 | No traffic No requests recorded since this deploy. |
| /api/auth/callback/google | 0 → 0 | 0 → 0 (0%) | not measured | 0 | No traffic No requests recorded since this deploy. |