@vruum/mcp 0.15.0 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/tools.json +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vruum/mcp",
3
- "version": "0.15.0",
3
+ "version": "0.15.1",
4
4
  "mcpName": "ai.vruum/mcp",
5
5
  "description": "Stdio MCP bridge for the hosted Vruum revenue-platform MCP (https://api.vruum.ai/mcp): serves the advertised tool surface for credential-free introspection and proxies tool calls with your Vruum token.",
6
6
  "license": "MIT",
package/tools.json CHANGED
@@ -1681,7 +1681,7 @@
1681
1681
  "readOnlyHint": false,
1682
1682
  "title": "Manage Messages"
1683
1683
  },
1684
- "description": "Manage Messages\n\nTriage and edit outreach messages — the consolidated message writer.\n\nActions (id = message UUID unless noted):\n- prepare_initial: save initial-email authoring work before enrollment. Scalar\n id is the OBJECTIVE id. Payload = {planning_version, person_ids}, with\n 1–100 distinct current members without an execution plan per call. Requires objective-owner or\n workspace-admin authority. Reuse the same members on an uncertain retry:\n existing prose and decisions are never overwritten. Write each returned\n needs_draft row through edit, then review at review_url (All conversations).\n Does not activate, enroll, generate prose, buy research or approve/send.\n The first sequence step must be email only; no later drafts are created.\n- reply_plan_prepare: propose a next step for an existing ordinary email or LinkedIn\n reply draft. First fetch type=conversation_reply_plan id=<person>\n filters={message_id}. Payload: person_id and context_fingerprint. Returns\n a cited answer, human meeting/material handoff, future check or hold.\n Preparation persists nothing and grants no sending authority.\n- reply_plan_save: only after the person reviews the proposal, save its\n fields, person_id, context_fingerprint and stable request_id. Atomically\n records the canonical commitment and owned manual follow-up. A reply\n updates the unsent draft; a hold/handoff rejects that reviewed unsent\n draft. Return to full native draft review for separate exact approval.\n No meeting, material delivery or customer outcome is established.\n- link_commitment_meeting: explicitly link or unlink a captured meeting to a\n reply Action. Read fetch type=conversation_meetings id=<person> with\n message_id first. Pass the exact snapshot, expected_link_id, commitment ID,\n decision (link/unlink) and rationale. This records operator attribution,\n never provider confirmation, calendar changes or completion.\n- commitment_followup: assign, stop or close manual follow-up work. Scalar\n id is the reply message. Read conversation_commitments first. Payload pins\n person_id, action_id, commitment_evidence_id, expected_review_id and the\n latest expected_followup_id/version (null if absent), plus request_id,\n decision and rationale. assign requires an active workspace owner_user_id\n and future due_at; stop/close omit both. close requires a current completed\n review for the same commitment. Exact retries reuse request_id. Assignment\n authorizes manual tracking only, never contact or calendar execution.\n- review_commitment: record a sourced completion review or reopen it.\n Fetch conversation_commitments first. Pass person_id, action_id,\n commitment_evidence_id, expected_review_id (null for the first review),\n request_id (retain on exact retry), decision (completed|reopened), and\n rationale. Completion source is {kind: provider_send|meeting_booked|\n meeting_held, id: receipt/booking/attendance evidence UUID, snapshot: the\n exact delivery or meeting object returned by that read}. Meeting links\n must be exact. Reopening requires a prior completion and source=null.\n This records operator judgment, not a customer outcome or metric.\n Source corrections invalidate completion on read. Scalar message id only.\n- record_commitment: record, revise or withdraw a reviewed team next step\n (including existing commitments after the reply Action ends). For revisions\n and withdrawals pin expected_commitment_id and expected_review_id from the\n current read, and supply revision_reason. Initial records use null references.\n Completion reviews and follow-up assignments do not carry over to revisions.\n Approved/executing reply work remains frozen; terminal reply state, version\n and delivery never change. Read conversation_commitments again after saving.\n First fetch type=conversation_commitments,\n id=person_id, filters={message_id}. Pass that source_message_id,\n source_hash, action_id and action_version with person_id, request_id\n (a UUID retained on exact retries), owner_user_id, due_at (timezone-bearing),\n next_step, completion_condition and disposition (active|withdrawn).\n An active record needs a current workspace owner and future due date.\n This does not approve/send a message or confirm a customer promise.\n A delivery or calendar fact does not complete the free-text condition.\n Scalar message id only. A stale refusal requires a fresh read and review.\n- approve: mark a draft READY TO SEND. Sending happens asynchronously and\n respects the objective's designed schedule plus a per-person cooldown —\n approval may SCHEDULE rather than fire immediately. The response's\n `processing_state` says which ('queued_for_send', 'scheduled',\n 'held_for_channel', or 'channel_state_unknown'). A hold/unknown state\n includes machine-readable `channel_hold`\n quota/reconnect feedback and `send_not_before` when it auto-resumes;\n a scheduled/held message is NOT lost and must NOT be re-approved or\n re-drafted (get_outreach_review shows `channel_holds`, the plan's\n `plan_next_action_at`, and `approved_pending_send_count`).\n Every approval pins the exact content reviewed: read the draft with\n get_outreach_review content_length='full' (after any edit) and pass\n its `review_fingerprint`. Scalar id: payload.review_fingerprint =\n \"<fingerprint>\". Id ARRAY (native bulk, per-message results):\n payload.review_fingerprints = {\"<message_id>\": \"<fingerprint>\", ...}\n with exactly one entry per id; a single review_fingerprint is refused\n for an array. Each draft approves only if it still matches its own\n fingerprint; a changed one fails `draft_review_changed` per item —\n re-read it, show the current text, and approve it again.\n A saved email has retained_email metadata. Review its exact sender,\n recipient, subject and body, then use a SCALAR id and\n payload.retained_email_decision={action_id, action_version,\n proposal_revision, fingerprint} from that fresh review. This pins the\n immutable proposal. A stale or missing token refuses the decision.\n- reject: reject a draft (array → native bulk, same as approve).\n Saved emails require the same scalar retained_email_decision token.\n Saved emails cannot be edited, regenerated, or decided in a batch;\n reject and start new work when their content must change.\n- edit: replace subject/content (`payload` = {subject?, content?}).\n Scalar id ONLY — one call per message (an id array is rejected;\n sending identical content to many people is not a supported flow).\n Optional `attachments` sets the media sent with this draft\n (linkedin_inmail / linkedin_message / email only): a list where each item\n is a new upload {content_base64 (raw base64, no data: prefix; PNG/JPEG/GIF/\n WebP/PDF, 10MB max), filename} OR {storage_path} to keep one the draft\n already has (from get_outreach_review). Omit = leave unchanged, [] = remove\n all, max 5. Attachments on any other channel are rejected.\n Optional `asset_id` binds an outreach asset's newest version (null\n unbinds): the content must then carry {asset_link} exactly once, where\n the recipient's own link goes at send. A version whose source check\n failed is refused (`asset_check_failed`); a connection request cannot\n carry one. The reviewer sees the asset beside the draft, and approving\n the draft approves that version.\n- asset_create: make up-front work for a prospect (no id; `payload` =\n {request_id, kind: pipeline_sample|account_teardown|one_pager, title,\n body (Markdown), sources: [{url, title, accessed_at?}], person_id?,\n objective_id?, company_id?, made_by: agent|member}). Every source is\n checked; a pipeline sample or teardown needs at least one. Name a\n person at another company only with a public source showing that role\n now. Reuse request_id on a retry. Creating approves and sends nothing;\n bind it with edit. Returns the asset and its version (check_state).\n- asset_update: a new version (id = asset id; `payload` =\n {expected_version: the latest_version you read, title, body, sources}).\n Recipients' links keep showing the last approved version until a\n person approves this one, with its next draft or via asset_approve.\n- asset_approve: a person approves a version they reviewed without a\n draft (id = asset id; `payload` = {version}). Recorded as an Action;\n links show it from then on. Never call this on your own judgment.\n- asset_revoke: every link to the asset stops working (id = asset id;\n `payload` = {reason?}).\n- regenerate: return the message to the authoring queue (status becomes\n needs_draft, content cleared) — author the replacement at triage via\n edit (no payload); id array loops with per-item status. Works on\n draft, rejected, AND failed rows — regenerate is the recovery path\n for a send the provider bounced (attachments survive; re-author,\n then re-approve). The row gets a FRESH 14-day authoring window; the\n response's expires_at reports the new sweep deadline (VRU-745).\n- draft_reply: queue a reply for AUTHORING (creates a needs_draft\n inbound_reply row — write it via edit) — id is the COMPANY-PERSON id\n (the person you're talking to), not a message id.\n- draft_followup: queue a follow-up for AUTHORING (creates a\n needs_draft followup row) — id is the COMPANY-PERSON id. Returns 409\n `touch_already_exists` (with the existing row's id and status) when the\n person's plan already has a live or delivered row for the resolved\n touch — review THAT row instead of re-drafting; never loop on this call.\n Returns 422 `content_gate_blocked` when the resolved step is a breakup\n and the person never received a content-bearing message (VRU-746) —\n author an opener instead or stop the plan; never retry as-is.\n- send: send a one-off email NOW to a person (id = the PERSON id, not a\n message id; scalar only). `payload` = {client_request_id,\n sender_config_id, to_email,\n subject, body (PLAIN TEXT — blank lines become paragraphs, newlines become\n line breaks; HTML is escaped, do NOT pass tags), to_name?, thread_id?,\n in_reply_to_provider_message_id?}.\n Recorded as a manual touch (the engine won't follow up) and lands on the\n timeline attributed to you. The backend enforces the safety floor:\n to_email must match the person's known email, suppressed/archived\n recipients are rejected, the mailbox must be an active one in your\n workspace, an exact retry reuses the same client_request_id without\n double-sending (a changed payload with the same UUID is rejected), and a\n per-person cooldown blocks a second outbound within ~20h of the last\n one (error_code='cooldown_active' with `blocked_until` and the safe\n next action — if it fires, the person was contacted recently and their\n sequence is handling them; do not re-send or re-word). A deliberate\n repeat send inside the window is possible via payload\n {override_cooldown: true, override_reason} — owner/operator role only,\n audit-logged. Use ONLY when the human operator explicitly asks for a\n repeat send; NEVER decide to override on your own or in reaction to a\n cooldown block.\n- send_linkedin: send a one-off LinkedIn touch NOW to a person (id = the\n PERSON id, scalar only) — no objective, no enrollment. `payload` =\n {client_request_id, send_type, body, subject?, override_cooldown?,\n override_reason?}.\n send_type is 'dm' (message an existing connection), 'inmail' (message a\n non-connection, uses an InMail credit, subject REQUIRED), or 'connection'\n (send a connection request; body is the note, truncated to the sender's\n tier cap). Send_type is validated against the person's LIVE connection\n status: a mismatch (DM to a non-connection, invite to someone already\n connected/pending) returns error_code='channel_unavailable' with the safe\n next action — pick a send_type that matches, do not retry blindly. Same\n manual safety floor as `send`: suppressed/archived recipients rejected,\n idempotent on retry, and the per-person ~20h cooldown applies to ALL\n three types including connection requests (error_code='cooldown_active').\n override_cooldown/override_reason work as in `send` (owner/operator only).\n- reconcile_delivery: inspect or apply provider evidence for one unresolved\n delivery (id = PERSON id). Dry-run is the default. Applying requires\n provider_message_id, provider_sent_at, and reason; message_id disambiguates\n multiple unresolved intents and a missing/resolved explicit message_id is\n rejected (it never falls through to create). Vruum fetches the exact\n provider object through the tenant-owned Unipile account and verifies the\n outbound sender, recipient, channel payload, and provider timestamp before\n mutation; caller strings alone are not evidence. For a first-ever external\n send, omit message_id and also supply provider_account_id, channel, content,\n and subject for email/InMail (a connection request may use body/content=\"\").\n Evidence-free operator attestation is rejected.\n- reconcile_external_email: preview or apply provider-read-only Gmail\n reconciliation for one objective (id = objective UUID, scalar only).\n `payload` includes {account_id, action, preview_id?, after?, before?}.\n Resolve account_id first with fetch type=settings,\n subtype=channel_status: choose channels.email.accounts[].id for the\n intended sender mailbox. Legacy Unipile account ids remain accepted.\n action='preview' is mutation-free and returns a preview_id plus exact\n matched/ambiguous/unmatched counts. action='apply' requires that exact\n preview_id and creates reservations/finalizations without sending.\n action='exceptions' returns the exception-first rescue queue. Guarded\n action='hold' or 'release'/'mark_cancelled' requires reservation_id and\n reason; release restores dispatch permission and must only follow\n affirmative cancellation evidence or an explicit human instruction.\n- backfill_external_sends: import HISTORICAL external sends (emails typed\n in Gmail/Outlook, DMs typed in LinkedIn) for known people — NO id\n (workspace-scoped). `payload` = {dry_run (required — start true and\n review result_data.items before re-running false), channel?\n ('email'|'linkedin'|'all', default all), lookback_days? (1-90, default\n 30), email_account_id?, linkedin_account_id?}. Enqueues one background\n job and returns job_id — poll get_job_status for per-account summaries.\n Idempotent (provider-identity + content dedup), owner/operator only.\n\nBulk approve/reject hits the same endpoint the dashboard's bulk triage\nuses (max 50 per call — the target's own ceiling); `payload` may carry\nits extra fields (approve: `review_fingerprints`, see above) but not\nmessage_ids — ids always travel via `id`.\nregenerate arrays loop with per-item status (max 100); edit takes a\nscalar id only. Partial failure is reported per item, never silent.\nPayloads are validated by the underlying route's own schema.\n\nProse gate: approve and edit re-lint the content server-side and can\nreturn error_code='prose_gate_blocked' with structured `failures[].fix`.\n`payload` accepts `reason` (corpus note), `override_reason` (taste\noverride — proceeds despite block failures, logged to the corpus;\nhonored only for owner/operator roles on the tenant, otherwise ignored\nand the gate blocks normally), and `client_rules_version` (responses\nflag `rules_changed` when the server's rules differ). Pre-check drafts\ncheaply with check_prose.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
1684
+ "description": "Manage Messages\n\nTriage and edit outreach messages — the consolidated message writer.\n\nActions (id = message UUID unless noted):\n- prepare_initial: save initial-email authoring work before enrollment. Scalar\n id is the OBJECTIVE id. Payload = {planning_version, person_ids}, with\n 1–100 distinct current members without an execution plan per call. Requires objective-owner or\n workspace-admin authority. Reuse the same members on an uncertain retry:\n existing prose and decisions are never overwritten. Write each returned\n needs_draft row through edit, then review at review_url (All conversations).\n Does not activate, enroll, generate prose, buy research or approve/send.\n The first sequence step must be email only; no later drafts are created.\n- reply_plan_prepare: propose a next step for an existing ordinary email or LinkedIn\n reply draft. First fetch type=conversation_reply_plan id=<person>\n filters={message_id}. Payload: person_id and context_fingerprint. Returns\n a cited answer, human meeting/material handoff, future check or hold.\n Preparation persists nothing and grants no sending authority.\n- reply_plan_save: only after the person reviews the proposal, save its\n fields, person_id, context_fingerprint and stable request_id. Atomically\n records the canonical commitment and owned manual follow-up. A reply\n updates the unsent draft; a hold/handoff rejects that reviewed unsent\n draft. Return to full native draft review for separate exact approval.\n No meeting, material delivery or customer outcome is established.\n- link_commitment_meeting: explicitly link or unlink a captured meeting to a\n reply Action. Read fetch type=conversation_meetings id=<person> with\n message_id first. Pass the exact snapshot, expected_link_id, commitment ID,\n decision (link/unlink) and rationale. This records operator attribution,\n never provider confirmation, calendar changes or completion.\n- commitment_followup: assign, stop or close manual follow-up work. Scalar\n id is the reply message. Read conversation_commitments first. Payload pins\n person_id, action_id, commitment_evidence_id, expected_review_id and the\n latest expected_followup_id/version (null if absent), plus request_id,\n decision and rationale. assign requires an active workspace owner_user_id\n and future due_at; stop/close omit both. close requires a current completed\n review for the same commitment. Exact retries reuse request_id. Assignment\n authorizes manual tracking only, never contact or calendar execution.\n- review_commitment: record a sourced completion review or reopen it.\n Fetch conversation_commitments first. Pass person_id, action_id,\n commitment_evidence_id, expected_review_id (null for the first review),\n request_id (retain on exact retry), decision (completed|reopened), and\n rationale. Completion source is {kind: provider_send|meeting_booked|\n meeting_held, id: receipt/booking/attendance evidence UUID, snapshot: the\n exact delivery or meeting object returned by that read}. Meeting links\n must be exact. Reopening requires a prior completion and source=null.\n This records operator judgment, not a customer outcome or metric.\n Source corrections invalidate completion on read. Scalar message id only.\n- record_commitment: record, revise or withdraw a reviewed team next step\n (including existing commitments after the reply Action ends). For revisions\n and withdrawals pin expected_commitment_id and expected_review_id from the\n current read, and supply revision_reason. Initial records use null references.\n Completion reviews and follow-up assignments do not carry over to revisions.\n Approved/executing reply work remains frozen; terminal reply state, version\n and delivery never change. Read conversation_commitments again after saving.\n First fetch type=conversation_commitments,\n id=person_id, filters={message_id}. Pass that source_message_id,\n source_hash, action_id and action_version with person_id, request_id\n (a UUID retained on exact retries), owner_user_id, due_at (timezone-bearing),\n next_step, completion_condition and disposition (active|withdrawn).\n An active record needs a current workspace owner and future due date.\n This does not approve/send a message or confirm a customer promise.\n A delivery or calendar fact does not complete the free-text condition.\n Scalar message id only. A stale refusal requires a fresh read and review.\n- approve: mark a draft READY TO SEND. Sending happens asynchronously and\n respects the objective's designed schedule plus a per-person cooldown —\n approval may SCHEDULE rather than fire immediately. The response's\n `processing_state` says which ('queued_for_send', 'scheduled',\n 'held_for_channel', or 'channel_state_unknown'). A hold/unknown state\n includes machine-readable `channel_hold`\n quota/reconnect feedback and `send_not_before` when it auto-resumes;\n a scheduled/held message is NOT lost and must NOT be re-approved or\n re-drafted (get_outreach_review shows `channel_holds`, the plan's\n `plan_next_action_at`, and `approved_pending_send_count`).\n Every approval pins the exact content reviewed: read the draft with\n get_outreach_review content_length='full' (after any edit) and pass\n its `review_fingerprint`. Scalar id: payload.review_fingerprint =\n \"<fingerprint>\". Id ARRAY (native bulk, per-message results):\n payload.review_fingerprints = {\"<message_id>\": \"<fingerprint>\", ...}\n with exactly one entry per id; a single review_fingerprint is refused\n for an array. Each draft approves only if it still matches its own\n fingerprint; a changed one fails `draft_review_changed` per item —\n re-read it, show the current text, and approve it again.\n A saved email has retained_email metadata. Review its exact sender,\n recipient, subject and body, then use a SCALAR id and\n payload.retained_email_decision={action_id, action_version,\n proposal_revision, fingerprint} from that fresh review. This pins the\n immutable proposal. A stale or missing token refuses the decision.\n- reject: reject a draft (array → native bulk, same as approve).\n Saved emails require the same scalar retained_email_decision token.\n Saved emails cannot be edited, regenerated, or decided in a batch;\n reject and start new work when their content must change.\n- unapprove: take an APPROVED message that has not been sent back to\n draft (no payload) — the way to change a touch that is approved but\n still held (business hours, cooldown, paused plan) before it sends.\n Then edit/reject/regenerate it as a draft; it sends again only after a\n fresh approve with the review_fingerprint of the current draft (re-read\n it with get_outreach_review content_length='full'; a draft on a\n paused plan stays out of that queue, and cannot be approved, until the\n plan resumes). Refused with 409 `send_in_flight` once the send has\n started, `not_approved` for any other status, `effect_owned` while a\n coordination job owns the send.\n Same approver rule as approve. Id array loops with per-item status.\n- edit: replace subject/content (`payload` = {subject?, content?}).\n Scalar id ONLY — one call per message (an id array is rejected;\n sending identical content to many people is not a supported flow).\n Optional `attachments` sets the media sent with this draft\n (linkedin_inmail / linkedin_message / email only): a list where each item\n is a new upload {content_base64 (raw base64, no data: prefix; PNG/JPEG/GIF/\n WebP/PDF, 10MB max), filename} OR {storage_path} to keep one the draft\n already has (from get_outreach_review). Omit = leave unchanged, [] = remove\n all, max 5. Attachments on any other channel are rejected.\n Optional `asset_id` binds an outreach asset's newest version (null\n unbinds): the content must then carry {asset_link} exactly once, where\n the recipient's own link goes at send. A version whose source check\n failed is refused (`asset_check_failed`); a connection request cannot\n carry one. The reviewer sees the asset beside the draft, and approving\n the draft approves that version.\n- asset_create: make up-front work for a prospect (no id; `payload` =\n {request_id, kind: pipeline_sample|account_teardown|one_pager, title,\n body (Markdown), sources: [{url, title, accessed_at?}], person_id?,\n objective_id?, company_id?, made_by: agent|member}). Every source is\n checked; a pipeline sample or teardown needs at least one. Name a\n person at another company only with a public source showing that role\n now. Reuse request_id on a retry. Creating approves and sends nothing;\n bind it with edit. Returns the asset and its version (check_state).\n- asset_update: a new version (id = asset id; `payload` =\n {expected_version: the latest_version you read, title, body, sources}).\n Recipients' links keep showing the last approved version until a\n person approves this one, with its next draft or via asset_approve.\n- asset_approve: a person approves a version they reviewed without a\n draft (id = asset id; `payload` = {version}). Recorded as an Action;\n links show it from then on. Never call this on your own judgment.\n- asset_revoke: every link to the asset stops working (id = asset id;\n `payload` = {reason?}).\n- regenerate: return the message to the authoring queue (status becomes\n needs_draft, content cleared) — author the replacement at triage via\n edit (no payload); id array loops with per-item status. Works on\n draft, rejected, AND failed rows — regenerate is the recovery path\n for a send the provider bounced (attachments survive; re-author,\n then re-approve). The row gets a FRESH 14-day authoring window; the\n response's expires_at reports the new sweep deadline (VRU-745).\n- draft_reply: queue a reply for AUTHORING (creates a needs_draft\n inbound_reply row — write it via edit) — id is the COMPANY-PERSON id\n (the person you're talking to), not a message id.\n- draft_followup: queue a follow-up for AUTHORING (creates a\n needs_draft followup row) — id is the COMPANY-PERSON id. Returns 409\n `touch_already_exists` (with the existing row's id and status) when the\n person's plan already has a live or delivered row for the resolved\n touch — review THAT row instead of re-drafting; never loop on this call.\n Returns 422 `content_gate_blocked` when the resolved step is a breakup\n and the person never received a content-bearing message (VRU-746) —\n author an opener instead or stop the plan; never retry as-is.\n- send: send a one-off email NOW to a person (id = the PERSON id, not a\n message id; scalar only). `payload` = {client_request_id,\n sender_config_id, to_email,\n subject, body (PLAIN TEXT — blank lines become paragraphs, newlines become\n line breaks; HTML is escaped, do NOT pass tags), to_name?, thread_id?,\n in_reply_to_provider_message_id?}.\n Recorded as a manual touch (the engine won't follow up) and lands on the\n timeline attributed to you. The backend enforces the safety floor:\n to_email must match the person's known email, suppressed/archived\n recipients are rejected, the mailbox must be an active one in your\n workspace, an exact retry reuses the same client_request_id without\n double-sending (a changed payload with the same UUID is rejected), and a\n per-person cooldown blocks a second outbound within ~20h of the last\n one (error_code='cooldown_active' with `blocked_until` and the safe\n next action — if it fires, the person was contacted recently and their\n sequence is handling them; do not re-send or re-word). A deliberate\n repeat send inside the window is possible via payload\n {override_cooldown: true, override_reason} — owner/operator role only,\n audit-logged. Use ONLY when the human operator explicitly asks for a\n repeat send; NEVER decide to override on your own or in reaction to a\n cooldown block.\n- send_linkedin: send a one-off LinkedIn touch NOW to a person (id = the\n PERSON id, scalar only) — no objective, no enrollment. `payload` =\n {client_request_id, send_type, body, subject?, override_cooldown?,\n override_reason?}.\n send_type is 'dm' (message an existing connection), 'inmail' (message a\n non-connection, uses an InMail credit, subject REQUIRED), or 'connection'\n (send a connection request; body is the note, truncated to the sender's\n tier cap). Send_type is validated against the person's LIVE connection\n status: a mismatch (DM to a non-connection, invite to someone already\n connected/pending) returns error_code='channel_unavailable' with the safe\n next action — pick a send_type that matches, do not retry blindly. Same\n manual safety floor as `send`: suppressed/archived recipients rejected,\n idempotent on retry, and the per-person ~20h cooldown applies to ALL\n three types including connection requests (error_code='cooldown_active').\n override_cooldown/override_reason work as in `send` (owner/operator only).\n- reconcile_delivery: inspect or apply provider evidence for one unresolved\n delivery (id = PERSON id). Dry-run is the default. Applying requires\n provider_message_id, provider_sent_at, and reason; message_id disambiguates\n multiple unresolved intents and a missing/resolved explicit message_id is\n rejected (it never falls through to create). Vruum fetches the exact\n provider object through the tenant-owned Unipile account and verifies the\n outbound sender, recipient, channel payload, and provider timestamp before\n mutation; caller strings alone are not evidence. For a first-ever external\n send, omit message_id and also supply provider_account_id, channel, content,\n and subject for email/InMail (a connection request may use body/content=\"\").\n Evidence-free operator attestation is rejected.\n- reconcile_external_email: preview or apply provider-read-only Gmail\n reconciliation for one objective (id = objective UUID, scalar only).\n `payload` includes {account_id, action, preview_id?, after?, before?}.\n Resolve account_id first with fetch type=settings,\n subtype=channel_status: choose channels.email.accounts[].id for the\n intended sender mailbox. Legacy Unipile account ids remain accepted.\n action='preview' is mutation-free and returns a preview_id plus exact\n matched/ambiguous/unmatched counts. action='apply' requires that exact\n preview_id and creates reservations/finalizations without sending.\n action='exceptions' returns the exception-first rescue queue. Guarded\n action='hold' or 'release'/'mark_cancelled' requires reservation_id and\n reason; release restores dispatch permission and must only follow\n affirmative cancellation evidence or an explicit human instruction.\n- backfill_external_sends: import HISTORICAL external sends (emails typed\n in Gmail/Outlook, DMs typed in LinkedIn) for known people — NO id\n (workspace-scoped). `payload` = {dry_run (required — start true and\n review result_data.items before re-running false), channel?\n ('email'|'linkedin'|'all', default all), lookback_days? (1-90, default\n 30), email_account_id?, linkedin_account_id?}. Enqueues one background\n job and returns job_id — poll get_job_status for per-account summaries.\n Idempotent (provider-identity + content dedup), owner/operator only.\n\nBulk approve/reject hits the same endpoint the dashboard's bulk triage\nuses (max 50 per call — the target's own ceiling); `payload` may carry\nits extra fields (approve: `review_fingerprints`, see above) but not\nmessage_ids — ids always travel via `id`.\nregenerate and unapprove arrays loop with per-item status (max 100); edit takes a\nscalar id only. Partial failure is reported per item, never silent.\nPayloads are validated by the underlying route's own schema.\n\nProse gate: approve and edit re-lint the content server-side and can\nreturn error_code='prose_gate_blocked' with structured `failures[].fix`.\n`payload` accepts `reason` (corpus note), `override_reason` (taste\noverride — proceeds despite block failures, logged to the corpus;\nhonored only for owner/operator roles on the tenant, otherwise ignored\nand the gate blocks normally), and `client_rules_version` (responses\nflag `rules_changed` when the server's rules differ). Pre-check drafts\ncheaply with check_prose.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
1685
1685
  "inputSchema": {
1686
1686
  "properties": {
1687
1687
  "action": {