Skip to content

Carlos/gmail confirm ux#11

Merged
cjus merged 3 commits into
mainfrom
carlos/gmail-confirm-ux
May 10, 2026
Merged

Carlos/gmail confirm ux#11
cjus merged 3 commits into
mainfrom
carlos/gmail-confirm-ux

Conversation

@cjus
Copy link
Copy Markdown
Owner

@cjus cjus commented May 10, 2026

Tool confirm UX: per-tool formatters + outcome footer + Gmail integration polish

cjus added 3 commits May 9, 2026 18:55
Two operator-UX fixes for the inline-keyboard confirmation prompt:

1. Per-tool confirm-prompt formatters. New `meta.confirmFormatters`
   field on the integration module contract; loader aggregates into a
   `Map<toolName, ConfirmFormatter>` plumbed into both the Telegram
   and web brokers. Lookup strips the `mcp__solrac__` prefix; absent
   entries fall back to a fenced-code-block JSON dump. Failing
   formatters degrade to JSON instead of blocking the prompt.

2. Post-tool-execution outcome footer. `broker.request` now returns
   `{ decision, finalize }` instead of a bare verdict; the broker
   owns the full message lifecycle so the verdict edit + outcome
   edit stay consistent. Ollama path calls `finalize` after the
   handler runs (success or thrown). Claude SDK path stashes the
   handle in a per-turn map keyed by tool_name + JSON.stringify(input);
   a new `createPostToolUseHook` correlates by `tool_use_id` on
   PostToolUse / PostToolUseFailure. `main.ts::handleCallbackQuery`
   simplified — broker owns text edits, the toast is the only thing
   tied to the callback id.

Confirm prompts also switched to markdown source: web UI's marked.js
path renders proper `<ul><li>` lists; Telegram path goes through
`mdToTelegramHtml`. Without the markdown source, the web UI's
innerHTML render collapses newlines into a single paragraph.
Per-tool `confirmFormatters` for the six confirm-tier Gmail tools so
the operator sees email subjects + senders instead of opaque message
ids. All formatters return markdown:

  - apply_label / remove_label / archive / trash / delete: batched
    `messages.get(format:metadata, headers:[Subject,From])` resolves
    each id; markdown bullet list capped at 12 rows with an "and N
    more" overflow line.
  - send_message: from / to / cc / bcc / subject / body preview as
    a fenced code block. No API call needed.

Subjects pass through `stripEmojis` so promotional 🚀✨🚗 noise gets
dropped before render, and `mdEsc` escapes markdown-special chars
in user-controlled fields (subjects, account names) to prevent
injection. Capped per-render fan-out keeps the confirm prompt under
Telegram's practical 4KB ceiling on bulk actions.
Google's OAuth token endpoint returns `invalid_grant` (400) when a
refresh token has been revoked or expired — distinct from the
Gmail API's 401. The existing `errorResult` only covered 401, so
the operator-facing message was the raw `invalid_grant` string.
This is the most common operator-side failure mode (especially
when an OAuth client is in "Testing" status, where refresh tokens
expire after 7 days), so it's worth a friendly path.

`isInvalidGrant` checks both `e.message` and `e.response.data.error`
since googleapis surfaces it inconsistently across call sites. The
401-or-invalid-grant branch now includes the alias in the hint:
"Run: bun scripts/gmail-auth.ts <alias>". Each tool handler threads
`args.account` into `errorResult` (gmail_list_accounts is the only
exception — it has no account arg, falls through to the generic
"<alias>" placeholder).

Discovered while testing the new confirm-prompt formatters: two of
five configured accounts surfaced raw `invalid_grant` because they
were authed under "Testing" status and the 7-day TTL had elapsed.
@cjus cjus merged commit 3b46176 into main May 10, 2026
1 check passed
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.

1 participant