@extrovert.dev/mcp 0.1.0-pre.3

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 (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +313 -0
  3. package/dist/bin.d.ts +13 -0
  4. package/dist/bin.d.ts.map +1 -0
  5. package/dist/bin.js +89 -0
  6. package/dist/bin.js.map +1 -0
  7. package/dist/client.d.ts +956 -0
  8. package/dist/client.d.ts.map +1 -0
  9. package/dist/client.js +1354 -0
  10. package/dist/client.js.map +1 -0
  11. package/dist/config.d.ts +51 -0
  12. package/dist/config.d.ts.map +1 -0
  13. package/dist/config.js +53 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/contract.d.ts +62 -0
  16. package/dist/contract.d.ts.map +1 -0
  17. package/dist/contract.js +78 -0
  18. package/dist/contract.js.map +1 -0
  19. package/dist/extract.d.ts +25 -0
  20. package/dist/extract.d.ts.map +1 -0
  21. package/dist/extract.js +131 -0
  22. package/dist/extract.js.map +1 -0
  23. package/dist/fixtures.d.ts +676 -0
  24. package/dist/fixtures.d.ts.map +1 -0
  25. package/dist/fixtures.js +2685 -0
  26. package/dist/fixtures.js.map +1 -0
  27. package/dist/http.d.ts +18 -0
  28. package/dist/http.d.ts.map +1 -0
  29. package/dist/http.js +124 -0
  30. package/dist/http.js.map +1 -0
  31. package/dist/index.d.ts +17 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +18 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/server.d.ts +20 -0
  36. package/dist/server.d.ts.map +1 -0
  37. package/dist/server.js +36 -0
  38. package/dist/server.js.map +1 -0
  39. package/dist/stdio.d.ts +8 -0
  40. package/dist/stdio.d.ts.map +1 -0
  41. package/dist/stdio.js +22 -0
  42. package/dist/stdio.js.map +1 -0
  43. package/dist/tools.d.ts +27 -0
  44. package/dist/tools.d.ts.map +1 -0
  45. package/dist/tools.js +2752 -0
  46. package/dist/tools.js.map +1 -0
  47. package/dist/types.d.ts +1037 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +83 -0
  50. package/dist/types.js.map +1 -0
  51. package/package.json +72 -0
@@ -0,0 +1,676 @@
1
+ /**
2
+ * Offline fixture store.
3
+ *
4
+ * Every method here maps 1:1 onto a REST call in `client.ts`. Live MCP sessions
5
+ * use the Go REST API by default; this store is used for tests and offline demos
6
+ * when `EXTROVERT_MOCK=1`.
7
+ *
8
+ * The store is intentionally stateful within a process: creating an inbox,
9
+ * sending mail, and waiting for a reply all mutate the same in-memory data, so
10
+ * the example agent flow (redeem -> create_inbox -> send -> wait_for_email)
11
+ * produces coherent results offline.
12
+ */
13
+ import type { Attachment, AttachmentDownload, AttachmentInput, BatchUpdateResult, Category, ContactListDirection, ContactListEntry, ContactListKind, DeleteResult, Domain, DomainOffboard, EnrollmentResult, GraduationStatus, Inbox, Job, KeyTier, MailboxCredentials, Message, Page, Review, ReviewDecisionContext, ReviewerDecisionResult, ReviewPolicy, RiskDial, CategoryPacingState, ReviewEventsResult, ReviewFeedback, ReviewTurn, Rule, RuleAuditEntry, ProblemField, ScanBacklogStatus, SendEmailResult, SendResult, SubmitForReviewResult, SuppressionEntry, SuppressionPrecheck, ReputationRollup, ReputationFinding, ListDeliverabilityFindingsInput, Thread, ThreadDetail, SignUpResult, VerifyResult, WaitForEmailResult, Webhook, WebhookEvent, WhoAmI } from "./types.js";
14
+ import type { AckReviewEventInput, GetRuleAuditInput, GetRulesInput, InboxMetadataPatch, ListReviewEventsInput, ListReviewsInput, PostReviewChatInput, ProposeCategoryInput, RestampReviewInput, ReviewerDecideInput, SaveRuleInput, SubmitForReviewInput, SubmitForwardForReviewInput, SubmitRevisionInput, SubmitReplyForReviewInput, UpdateCategoryInput, WaitForReviewEventInput } from "./client.js";
15
+ /**
16
+ * The recipient the mock seeds an active org-scope suppression for, so the
17
+ * suppression reads (check/list/revoke) and the `recipient_suppressed`
18
+ * send-rejection path have deterministic data offline.
19
+ */
20
+ export declare const SEEDED_SUPPRESSED_RECIPIENT = "unsubscribed@example.com";
21
+ /**
22
+ * The recipient whose delivery ALWAYS fails at the mock's outbound-provider boundary, so the
23
+ * `approved -> failed` edge and its terminal `send_failed` nudge are drivable
24
+ * offline. Without it the mock could only ever demonstrate the happy path, and a
25
+ * drain loop that never sees `send_failed` is a drain loop nobody proved
26
+ * terminates on failure — which is exactly how the missing terminal nudges
27
+ * survived unnoticed.
28
+ */
29
+ export declare const SEEDED_SEND_FAILURE_RECIPIENT = "bounce@example.com";
30
+ export declare class FixtureStore {
31
+ private inboxes;
32
+ private messages;
33
+ /** message id -> stored attachments (mock mirror of the real MIME parts). */
34
+ private attachments;
35
+ /** webhook id -> registered webhook (mock mirror of extrovert_webhooks). */
36
+ private webhooks;
37
+ /** entry id -> contact-list entry (mock mirror of extrovert_contact_lists). */
38
+ private contactLists;
39
+ /** domain name -> onboarded domain (mock mirror of extrovert_domains). */
40
+ private domains;
41
+ /** job id -> async job status (mock mirror of extrovert_jobs; currently only
42
+ * the domain-offboard teardown enqueues one). */
43
+ private jobs;
44
+ /** suppression id -> recipient opt-out row (mock mirror of extrovert_suppressions). */
45
+ private suppressions;
46
+ /** review id -> review request (mock mirror of extrovert_review_requests). */
47
+ private reviews;
48
+ /** review id -> append-only thread turns (mock mirror of the turn log). */
49
+ private reviewTurns;
50
+ /**
51
+ * review id -> reviewer hand-back count (M8 Slice B circuit breaker (a)). The wire
52
+ * Review shape doesn't carry hop_count, so the mock tracks it here to surface the
53
+ * max_hops breaker on the decision context + reviewer_decide.
54
+ */
55
+ private reviewHopCounts;
56
+ /**
57
+ * review id -> durable nudges for that review (mock mirror of
58
+ * extrovert_review_nudges), oldest-first with a per-review monotonic seq. The
59
+ * authoritative liveness queue (spec §4.5) the agent drains/acks.
60
+ */
61
+ private reviewEvents;
62
+ /** review id -> the agent's last-acked seq (the per-(agent, review) cursor). */
63
+ private reviewEventCursors;
64
+ /** review id -> the thread a queued reply/forward delivers into (materialized at submit). */
65
+ private reviewThreads;
66
+ /** review id -> the opaque parent message id a queued reply threads to. */
67
+ private reviewParents;
68
+ /** category id -> category (mock mirror of extrovert_categories, D9/D10). */
69
+ private categories;
70
+ /** rule id -> writing rule (mock mirror of extrovert_writing_rules, D2/D11). */
71
+ private rules;
72
+ /** udo id -> change/undo audit row (mock mirror of extrovert_rule_undo_log). */
73
+ private ruleAudit;
74
+ /** human_email -> mock self-signup state (in-memory OTP). */
75
+ private signups;
76
+ /** "<scope>:<client_id>" -> the created resource id, mirroring the server's
77
+ * idempotency replay (a repeat with the same key returns the first result). */
78
+ private idempotency;
79
+ private readonly agentId;
80
+ /**
81
+ * The ceiling tier of the session's key (redesign §3.1). Default `project`
82
+ * (legacy bare `pk_agent_` behavior). Drives the bare-vs-wildcard list ceiling:
83
+ * an `org` key must pick a breadth (`breadth_required`); a non-org key cannot use
84
+ * the org wildcard (`forbidden_scope`). Mirrors the live choke-point so the
85
+ * isolation contract is exercised offline.
86
+ */
87
+ private readonly keyTier;
88
+ /**
89
+ * The org's review policy, mirrored offline so the mock enforces the SAME tree
90
+ * the server does.
91
+ *
92
+ * The default is `require_review` — deliberately, and matching the column
93
+ * default every real account gets. A mock that defaulted to `allow_direct` would
94
+ * teach every offline agent that a bare send just sends, which is precisely the
95
+ * lie that let the wire bug live for months: the mock passed while the real API
96
+ * refused. Override it with `EXTROVERT_MOCK_REVIEW_POLICY=allow_direct` when a
97
+ * test needs a delivered message rather than a queued review.
98
+ */
99
+ private readonly reviewPolicy;
100
+ /**
101
+ * The `front_run_next` nudge keys already enqueued, so N identical retries
102
+ * against a terminal review collapse to ONE row (the server dedupes on a
103
+ * deterministic key over reason + review + terminal state + parent revision).
104
+ */
105
+ private frontRunKeys;
106
+ constructor(opts?: {
107
+ keyTier?: KeyTier;
108
+ reviewPolicy?: ReviewPolicy;
109
+ });
110
+ /** The resolved review policy for this account (mock mirror of the inbox read). */
111
+ effectiveReviewPolicy(): ReviewPolicy;
112
+ /**
113
+ * Persist a review and recompute the derived `closed` flag from its state. Every
114
+ * mutation goes through here so `closed` can never drift from `state` — an agent
115
+ * polling `closed` after a crash is trusting exactly this.
116
+ */
117
+ private commitReview;
118
+ /**
119
+ * The mock mirror of the submit-time D3 gate. A resolved-review send REQUIRES an
120
+ * intent; a bare send/reply/forward has none by construction, so under anything
121
+ * but an explicitly-asserted direct mode on an `allow_direct` account it is
122
+ * refused — nothing sent, nothing queued.
123
+ */
124
+ private resolvedModeIsReview;
125
+ /**
126
+ * The SUBMIT-TIME pre-flight: contact lists and list-unsubscribe suppression,
127
+ * run against the resolved recipient set BEFORE the intent gate.
128
+ *
129
+ * The order matters and mirrors the server exactly. A blocked or suppressed
130
+ * recipient is a fact about the message that no amount of intent will fix, so
131
+ * answering `intent_required` first would send the agent off to add an intent
132
+ * and retry straight into the same wall. Running it before the review is created
133
+ * also means no human is ever handed a draft that could never have been sent.
134
+ */
135
+ private preflight;
136
+ /**
137
+ * Reject a BARE send/reply/forward under a policy that requires review. The
138
+ * error mirrors the server's 422 `intent_required` remediation, including the
139
+ * `retry_with` example, so an offline agent recovers exactly the way it would
140
+ * against the live API.
141
+ */
142
+ private requireDirectSendAllowed;
143
+ signUp(input: {
144
+ human_email: string;
145
+ username?: string;
146
+ }): SignUpResult;
147
+ verify(otp: string): VerifyResult;
148
+ whoami(): WhoAmI;
149
+ redeemEnrollment(token: string, agentHandle?: string): EnrollmentResult;
150
+ createInbox(opts: {
151
+ username?: string;
152
+ domain?: string;
153
+ displayName?: string;
154
+ inboundWebhookUrl?: string;
155
+ metadata?: InboxMetadataPatch;
156
+ projectId?: string;
157
+ clientId?: string;
158
+ }): Inbox;
159
+ listInboxes(opts?: {
160
+ limit?: number;
161
+ project?: string;
162
+ wildcard?: boolean;
163
+ } | number): Page<Inbox>;
164
+ getInbox(idOrAddress: string): Inbox | undefined;
165
+ /** Update an inbox's settings in place (mirrors PATCH /v1/inboxes/{inbox_id}). */
166
+ updateInbox(idOrAddress: string, opts: {
167
+ displayName?: string;
168
+ inboundWebhookUrl?: string;
169
+ dailySendLimit?: number;
170
+ metadata?: InboxMetadataPatch | null;
171
+ projectId?: string;
172
+ }): Inbox | undefined;
173
+ getCredentials(idOrAddress: string): MailboxCredentials;
174
+ deleteInbox(idOrAddress: string): {
175
+ id: string;
176
+ deleted: true;
177
+ } | undefined;
178
+ /**
179
+ * A BARE send — no mode/intent/category_id. Policy-gated exactly like the live
180
+ * endpoint: only an `allow_direct` account delivers here; under `require_review`
181
+ * (the default) this is 422 `intent_required` with the remediation attached, and
182
+ * nothing is sent or queued.
183
+ */
184
+ sendEmail(opts: {
185
+ inbox: string;
186
+ to: string[];
187
+ subject: string;
188
+ text: string;
189
+ html?: string;
190
+ cc?: string[];
191
+ bcc?: string[];
192
+ reply_to?: string;
193
+ headers?: Record<string, string>;
194
+ attachments?: AttachmentInput[];
195
+ }): SendEmailResult;
196
+ /**
197
+ * The actual mock delivery, shared by the bare direct path and the review
198
+ * loop's approve/auto-send dispatch. Policy is enforced by the CALLERS, never
199
+ * here: an approved review has already passed the human and must deliver.
200
+ */
201
+ private deliverSend;
202
+ /**
203
+ * Thread-aware reply (mock). Resolves the parent by message_id or the latest
204
+ * message in thread_id, derives recipients/subject server-side, and returns the
205
+ * canonical `{message_id, thread_id}` — matching the real API contract.
206
+ */
207
+ replyEmail(opts: {
208
+ inbox: string;
209
+ threadId?: string;
210
+ messageId?: string;
211
+ text?: string;
212
+ html?: string;
213
+ cc?: string[];
214
+ bcc?: string[];
215
+ replyTo?: string;
216
+ replyAll?: boolean;
217
+ attachments?: AttachmentInput[];
218
+ }): SendResult;
219
+ /** The mock reply delivery, shared by the direct path and approval dispatch. */
220
+ private deliverReply;
221
+ /**
222
+ * Forward an existing message to new recipients (mock). Policy-gated like send
223
+ * and reply: a forward quotes an entire received thread to arbitrary NEW
224
+ * recipients, so leaving it ungated would make it the documented bypass.
225
+ */
226
+ forwardEmail(opts: {
227
+ inbox: string;
228
+ messageId: string;
229
+ to: string[];
230
+ cc?: string[];
231
+ bcc?: string[];
232
+ text?: string;
233
+ html?: string;
234
+ }): SendResult;
235
+ /** The mock forward delivery, shared by the direct path and approval dispatch. */
236
+ private deliverForward;
237
+ /**
238
+ * Submit a new message for review (mock). Mirrors the server's deterministic
239
+ * routing: a `direct` mode is sent immediately (`kind:"sent"`); otherwise the
240
+ * message is parked in `needs_review` (`kind:"queued_for_review"`). Intent is
241
+ * required when the resolved mode is review (D3).
242
+ */
243
+ submitForReview(input: SubmitForReviewInput): SubmitForReviewResult;
244
+ /** Submit an in-thread reply for review (mock). Same routing as submitForReview. */
245
+ submitReplyForReview(input: SubmitReplyForReviewInput): SubmitForReviewResult;
246
+ /**
247
+ * Submit a forward for review (mock). Same routing as submitForReview, with the
248
+ * forward's subject + quoted body MATERIALIZED here so the human reviews the
249
+ * exact bytes that go out rather than a body re-derived from the live parent at
250
+ * approval time (which would silently discard the reviewer's edit).
251
+ */
252
+ submitForwardForReview(input: SubmitForwardForReviewInput): SubmitForReviewResult;
253
+ /** Resolve a reply's parent by message id, else the latest message in a thread. */
254
+ private resolveReplyParent;
255
+ /**
256
+ * Record the review row that governed a DIRECT (policy-permitted) send, park it
257
+ * in the terminal `auto_sent` state with `send_path: agent_direct`, and emit its
258
+ * one terminal `sent` nudge.
259
+ *
260
+ * The direct path used to hand back no handle at all, which meant an agent that
261
+ * crashed between the request and the response could never ask what became of
262
+ * the message. Leaving the dominant path handle-less would have entrenched
263
+ * exactly the crash-recovery hole the review loop exists to close.
264
+ */
265
+ private recordDirectReview;
266
+ /**
267
+ * Enqueue the ONE terminal nudge a finished review is allowed to produce.
268
+ *
269
+ * The invariant a drain loop is written against: every review that reaches
270
+ * `sent`, `auto_sent`, `failed` or `cancelled` emits exactly one terminal nudge,
271
+ * and it is the last and highest-`seq` nudge that review will ever produce. The
272
+ * payload carries everything the agent needs to stop — including WHY a send
273
+ * failed, which was previously stored and exposed on no surface at all.
274
+ */
275
+ private enqueueTerminalNudge;
276
+ /**
277
+ * Best-effort `front_run_next` nudge for an agent that tried to mutate a review
278
+ * somebody else already finished. It is enqueued OUTSIDE any transition (there
279
+ * is none) and deduped on a deterministic key, so a retry loop hitting the same
280
+ * 409 with the same parent_revision collapses N identical nudges to ONE row.
281
+ */
282
+ private enqueueFrontRunNudge;
283
+ /**
284
+ * The mock mirror of the 409 taxonomy for a review-mutating verb. A terminal row
285
+ * and a wrong-phase row are DIFFERENT errors on purpose: one must never be
286
+ * retried, the other needs a different verb. Collapsing them to a single
287
+ * "conflict" is what made a skill's one 409 handler retry a sent message forever.
288
+ */
289
+ private assertMutable;
290
+ /** List review requests (mock), newest-first, with optional state/category/inbox filters. */
291
+ listReviews(input?: ListReviewsInput): Page<Review>;
292
+ /** Get one review request (mock). */
293
+ getReview(id: string): Review;
294
+ /** Get a review's append-only thread turns (mock). */
295
+ getReviewTurns(id: string): Page<ReviewTurn>;
296
+ /**
297
+ * Get the human's assembled feedback for a review (mock; M5): the diff + the
298
+ * human comments/rejection turns + the decision (derived from state) + the rules
299
+ * born from this review. Mirrors the server's $0-LLM assembly.
300
+ */
301
+ getReviewFeedback(id: string): ReviewFeedback;
302
+ /**
303
+ * Post a chat turn on a review's thread (mock; M5): append an agent_question turn,
304
+ * flip in_review -> chatting on the first turn, enqueue a feedback_added event.
305
+ * Idempotent-key dedup is the transport's concern (replayed there); the mock just
306
+ * appends one turn per call.
307
+ */
308
+ postReviewChat(input: PostReviewChatInput): Review;
309
+ /**
310
+ * Post a new agent draft under a parent_revision CAS (mock; M5). A mismatch is a
311
+ * 409 STALE with NO mutation (D17); a clean CAS re-renders the draft in place
312
+ * (revision++), returns to needs_review, and enqueues a redraft_requested event.
313
+ */
314
+ submitRevision(input: SubmitRevisionInput): Review;
315
+ /** Withdraw a pending review (mock; M5) to the terminal cancelled state. */
316
+ cancelReview(id: string): Review;
317
+ /**
318
+ * Re-stamp a draft's rules-version WITHOUT redrafting (mock; D19/§8 $0 escape valve).
319
+ * Advances the version the draft is current against; no revision bump, no body change.
320
+ * A terminal/approved draft 409s; against_version < 0 is invalid.
321
+ */
322
+ restampReview(input: RestampReviewInput): Review;
323
+ /** Get the reviewer's decision context for a review (mock; §9). */
324
+ getReviewDecisionContext(id: string): ReviewDecisionContext;
325
+ /**
326
+ * Submit a reviewer decision (mock; §9). approve/edit → the platform "sends" with the
327
+ * composer's creds (kind=sent, send_path=reviewer_approved); reject → back to the
328
+ * composer (needs_review, hop_count++) UNLESS a breaker forces the human; escalate →
329
+ * the human queue. revision/version are the CAS (409 STALE on mismatch, NO mutation).
330
+ */
331
+ reviewerDecide(input: ReviewerDecideInput): ReviewerDecisionResult;
332
+ /**
333
+ * Browse the registry (mock), newest-first, excluding merged/soft-deleted
334
+ * (merged_into set). `match` is a pure lexical filter (every token must appear in
335
+ * name+description) — NO LLM, mirroring the server.
336
+ */
337
+ listCategories(match?: string): Page<Category>;
338
+ /** Get one category (mock); throws NotFound on an unknown id. */
339
+ getCategory(id: string): Category;
340
+ /** Propose a category (mock): stands immediately, author_kind=agent. */
341
+ proposeCategory(input: ProposeCategoryInput): Category;
342
+ /** Rename / re-describe a category (mock) — metadata only (D10). */
343
+ updateCategory(input: UpdateCategoryInput): Category;
344
+ /** The mock account-default risk dial (mirrors the server defaults). */
345
+ private accountDial;
346
+ /**
347
+ * Read the effective risk dial (mock): the account default + every category with
348
+ * an inherited (null override) effective dial. The mock category carries no risk-
349
+ * dial overrides, so every category inherits — effective == account.
350
+ */
351
+ getRiskDial(): RiskDial;
352
+ /** The next rung up the graduation ladder ("" if none). */
353
+ private nextGraduationState;
354
+ /**
355
+ * Read a category's graduation gate status (mock). The mock category has no
356
+ * counters, so it reports zero clean approvals / zero drift against the account
357
+ * defaults; can_graduate is true for supervised→auto_notify (no maturity gate).
358
+ */
359
+ getGraduationStatus(categoryId: string): GraduationStatus;
360
+ /**
361
+ * Propose graduating a category (mock): records nothing mutating and returns the
362
+ * current gate status. It does NOT flip the bit (D16) — the category state is
363
+ * unchanged.
364
+ */
365
+ proposeGraduation(categoryId: string, _evidence?: Record<string, unknown>): GraduationStatus;
366
+ /**
367
+ * Read the D19/§8 backlog-reconciliation status (mock): counts the QUEUED drafts in a
368
+ * category that are stale vs current-enough against the current rules-version. The
369
+ * mock has no per-draft composed_* stamps, so every queued draft reads current-enough;
370
+ * the contract shape is exercised (the integer-compare is covered by the Go tests).
371
+ */
372
+ getBacklogStatus(categoryId: string): ScanBacklogStatus;
373
+ /**
374
+ * Read the demand-driven pacing state (mock — M7 Slice B/§8): the cursor + effective
375
+ * window/ceiling/interval + each queued draft's classification. The mock has no cursor
376
+ * (nothing reviewed) and no composed_* stamps, so every queued draft reads in-window-
377
+ * fresh until the window fills, then ahead; the contract shape is exercised (the
378
+ * cursor/staleness mechanics are covered by the Go tests).
379
+ */
380
+ getPacingState(categoryId: string): CategoryPacingState;
381
+ /** rank a rule for the §7 ladder (mock mirror of the server's deterministic order). */
382
+ private ruleRank;
383
+ /**
384
+ * Get the ORDERED active rule set (mock). Applies the §7 precedence ladder and the
385
+ * category-before-general concatenation, mirroring the server (NO LLM).
386
+ */
387
+ getRules(input?: GetRulesInput): Page<Rule>;
388
+ /** Save / edit a rule (mock) — append-only by supersession (D11). */
389
+ saveRule(input: SaveRuleInput): Rule;
390
+ /** Promote a rule between the category and general layers (mock, via supersession). */
391
+ promoteRule(id: string, toScope: "general" | "category"): Rule;
392
+ /** Retire a rule (mock) — soft delete; history survives. */
393
+ retireRule(id: string): Rule;
394
+ /** Read the rule/category change audit log (mock). */
395
+ getRuleAudit(input?: GetRuleAuditInput): Page<RuleAuditEntry>;
396
+ /** Undo a rule change (mock) — restore the prior version; idempotent (re-undo 409). */
397
+ undoRuleChange(udoId: string): Rule;
398
+ /** recordRuleAudit appends one change/undo audit row (mock). */
399
+ private recordRuleAudit;
400
+ /**
401
+ * enqueueReviewEvent appends a durable nudge for a review with the next
402
+ * per-review monotonic seq (mock mirror of the server's enqueue-on-transition).
403
+ * Used by the seed + (in a fuller mock) by transition handlers.
404
+ */
405
+ private enqueueReviewEvent;
406
+ /** Drain the next un-acked review events (mock), FIFO per review + cursors. */
407
+ listReviewEvents(input?: ListReviewEventsInput): ReviewEventsResult;
408
+ /**
409
+ * Long-poll for a review event (mock). Offline there is nothing to wait FOR, so
410
+ * it returns the immediate drain (empty when caught up) — matching the server's
411
+ * "empty on timeout" contract.
412
+ */
413
+ waitForReviewEvent(input?: WaitForReviewEventInput): ReviewEventsResult;
414
+ /** Ack review events (mock): advance per-review cursors monotonically. */
415
+ ackReviewEvent(input: AckReviewEventInput): {
416
+ cursors: ReviewEventsResult["cursors"];
417
+ };
418
+ /**
419
+ * createReviewRecord mints a needs_review row + the intent (agent_note) and
420
+ * initial-draft (agent_draft) turns, mirroring the server's submit-time writes.
421
+ */
422
+ private createReviewRecord;
423
+ listMessages(opts: {
424
+ inbox: string;
425
+ limit?: number;
426
+ offset?: number;
427
+ unreadOnly?: boolean;
428
+ from?: string;
429
+ to?: string;
430
+ subject?: string;
431
+ }): Page<Message>;
432
+ /** Fetch a single message by id across all inboxes (mirrors GET /v1/messages/{id}). */
433
+ getMessage(id: string): Message;
434
+ /** Toggle the \Seen flag for a message by id (mirrors PATCH .../messages/{id}). */
435
+ markRead(id: string, read: boolean): Message;
436
+ listThreads(opts: {
437
+ inbox: string;
438
+ limit?: number;
439
+ }): Page<Thread>;
440
+ /** Fetch one thread (with its messages, oldest-first) by id under an inbox. */
441
+ getThread(idOrAddress: string, threadId: string): ThreadDetail;
442
+ /**
443
+ * Delete a message by id (mirrors DELETE .../messages/{id}). The mock moves the
444
+ * message to a Trash folder (soft delete) or removes it outright when expunge is
445
+ * set or it already lives in Trash. Throws NotFoundError when absent.
446
+ */
447
+ deleteMessage(idOrAddress: string, id: string, expunge: boolean): DeleteResult;
448
+ /**
449
+ * Delete every message in a thread by id (mirrors DELETE .../threads/{id}).
450
+ * Moves them to Trash (soft) or removes them (expunge / already in Trash).
451
+ */
452
+ deleteThread(idOrAddress: string, threadId: string, expunge: boolean): DeleteResult;
453
+ /**
454
+ * Batch mark read/unread and/or move folder for a list of message ids under one
455
+ * inbox (mirrors PATCH .../messages/batch). Ids not present in the inbox are
456
+ * reported in `failed`; the rest in `updated`.
457
+ */
458
+ batchUpdateMessages(idOrAddress: string, ids: string[], read: boolean | undefined, folder: string | undefined): BatchUpdateResult;
459
+ /** Build the canonical Thread wire shape (snippet, participant strings). */
460
+ private toThread;
461
+ search(opts: {
462
+ query: string;
463
+ inbox?: string;
464
+ limit?: number;
465
+ }): Page<Message>;
466
+ /**
467
+ * Offline `wait_for_email`. Resolves as soon as a matching inbound message is
468
+ * present. Because `sendEmail` queues an auto-reply ~1.2s out, the demo flow
469
+ * resolves quickly; otherwise it resolves against an already-seeded OTP mail.
470
+ */
471
+ waitForEmail(opts: {
472
+ inbox: string;
473
+ from?: string;
474
+ subject?: string;
475
+ regex?: string;
476
+ linkHint?: string;
477
+ timeoutMs: number;
478
+ pollMs?: number;
479
+ }): Promise<WaitForEmailResult>;
480
+ private resolveInbox;
481
+ private requireInbox;
482
+ private appendMessage;
483
+ /** List a message's attachment metadata (mirrors the list endpoint). */
484
+ listAttachments(messageId: string): Page<Attachment>;
485
+ /** Fetch one attachment's bytes + metadata (mirrors the download endpoint). */
486
+ getAttachment(messageId: string, attachmentId: string): AttachmentDownload;
487
+ /** Register a webhook; returns the row WITH the one-time signing secret. */
488
+ registerWebhook(input: {
489
+ url: string;
490
+ events?: WebhookEvent[];
491
+ inbox?: string;
492
+ clientId?: string;
493
+ }): Webhook;
494
+ /** List webhooks (secret redacted). */
495
+ listWebhooks(): Page<Webhook>;
496
+ /** Get one webhook (secret redacted). Throws NotFoundError when absent. */
497
+ getWebhook(id: string): Webhook;
498
+ /**
499
+ * Update a webhook in place (secret redacted in the response). Every field is
500
+ * optional; an unset field leaves the stored value untouched (PATCH semantics).
501
+ * Throws NotFoundError when absent.
502
+ */
503
+ updateWebhook(id: string, input: {
504
+ url?: string;
505
+ events?: WebhookEvent[];
506
+ inbox?: string;
507
+ active?: boolean;
508
+ }): Webhook;
509
+ /** Delete a webhook. Throws NotFoundError when absent. */
510
+ deleteWebhook(id: string): {
511
+ id: string;
512
+ deleted: true;
513
+ };
514
+ /** Add one allow/block entry scoped to an inbox. */
515
+ addContactListEntry(inbox: string, input: {
516
+ kind: ContactListKind;
517
+ direction?: ContactListDirection;
518
+ pattern?: string;
519
+ }): ContactListEntry;
520
+ /** List the entries governing an inbox (inbox-specific + account-wide). */
521
+ listContactListEntries(inbox: string): Page<ContactListEntry>;
522
+ /** Delete a contact-list entry by id. Throws NotFoundError when absent. */
523
+ deleteContactListEntry(_inbox: string, id: string): {
524
+ id: string;
525
+ deleted: true;
526
+ };
527
+ /** Onboard a domain. Mirrors the server's per-mode record set + status. */
528
+ onboardDomain(input: {
529
+ domain: string;
530
+ mode?: "shared" | "ns_delegated" | "manual" | "purchased";
531
+ mail_host_ip?: string;
532
+ scope?: "org" | "project";
533
+ project_id?: string;
534
+ }): Domain;
535
+ /** List onboarded domains (records omitted on the summary, mirroring the server). */
536
+ listDomains(): Page<Domain>;
537
+ /** Get one domain's detail + the records to set, inline. Throws NotFoundError when absent. */
538
+ getDomain(domain: string): Domain;
539
+ /** Trigger/refresh verification; returns the (re-read) detail. Throws when absent. */
540
+ verifyDomain(domain: string): Domain;
541
+ /**
542
+ * Offboard (remove) a domain. Throws NotFoundError when absent. Mirrors the live
543
+ * API's async contract: it returns an accepted teardown job (there is no job
544
+ * runner in-fixture, so the row is removed synchronously and a synthetic
545
+ * succeeded job is reported).
546
+ */
547
+ offboardDomain(domain: string): DomainOffboard;
548
+ /** Get one async job's poll status (mirrors `GET /v1/jobs/{job_id}`). Throws NotFoundError when absent. */
549
+ getJob(jobId: string): Job;
550
+ /**
551
+ * Pre-check whether the caller's org suppresses a recipient (mirrors
552
+ * `GET /v1/suppressions?recipient=…`): `{recipient, suppressed, rows}` over the
553
+ * active (non-revoked) org rows for that canonicalized recipient.
554
+ */
555
+ precheckSuppression(recipient: string): SuppressionPrecheck;
556
+ /** Offline deliverability rollup (mirrors `GET /v1/reputation`): healthy, no data. */
557
+ getReputation(): ReputationRollup;
558
+ /** Offline findings list (mirrors `GET /v1/reputation/findings`): empty. */
559
+ listDeliverabilityFindings(_input?: ListDeliverabilityFindingsInput): Page<ReputationFinding>;
560
+ /** List the caller's own org suppression rows (mirrors the paged `GET /v1/suppressions`). */
561
+ listSuppressions(input: {
562
+ scope?: "org" | "shared_domain" | "global";
563
+ include_revoked?: boolean;
564
+ limit?: number;
565
+ cursor?: string;
566
+ }): Page<SuppressionEntry>;
567
+ /**
568
+ * Revoke one org-scope suppression row (mirrors `POST /v1/suppressions/{id}/revoke`);
569
+ * a reason is required. Throws NotFoundError when the id is unknown or not the
570
+ * caller's own org row (global/shared rows are platform-operator only → 404).
571
+ */
572
+ revokeSuppression(id: string, reason: string): SuppressionEntry;
573
+ /**
574
+ * Reject the WHOLE send if ANY recipient has an active org-scope suppression,
575
+ * naming exactly the suppressed addresses (never the scope/origin) so the caller
576
+ * can drop them and retry — mirroring the live `recipient_suppressed` (422) path.
577
+ */
578
+ private enforceSuppression;
579
+ /**
580
+ * Enforce the send-direction contact lists for an inbox: reject a block-listed
581
+ * recipient, or any recipient outside the allowlist when allowlist mode is on.
582
+ */
583
+ private enforceSendPolicy;
584
+ /** Drop a believable inbound OTP reply into the thread shortly after a send. */
585
+ private queueAutoReply;
586
+ private seed;
587
+ }
588
+ /** Thrown when an inbox/thread cannot be resolved (maps to API 404). */
589
+ export declare class NotFoundError extends Error {
590
+ constructor(message: string);
591
+ }
592
+ /** Thrown when a send is rejected by an inbox's contact lists (maps to API 403). */
593
+ export declare class BlockedError extends Error {
594
+ constructor(message: string);
595
+ }
596
+ /**
597
+ * Thrown when a send is rejected because one or more recipients have opted out
598
+ * (list-unsubscribe / suppression; maps to API 422 `recipient_suppressed`). The
599
+ * message names exactly the suppressed addresses so the agent can drop them and
600
+ * retry; `recipients` carries the same list machine-readably.
601
+ */
602
+ export declare class SuppressedError extends Error {
603
+ readonly recipients: string[];
604
+ constructor(recipients: string[]);
605
+ }
606
+ /** Thrown when a project_id assertion does not match the key's bound project (maps to API 403). */
607
+ export declare class ForbiddenError extends Error {
608
+ constructor(message: string);
609
+ }
610
+ /**
611
+ * Thrown when an org-tier key issues a bare list that needs an explicit breadth
612
+ * pick (maps to API 400 `breadth_required`; redesign §4.1). The message names the
613
+ * next call (a project id or the org wildcard).
614
+ */
615
+ export declare class BreadthRequiredError extends Error {
616
+ constructor(message: string);
617
+ }
618
+ /**
619
+ * Thrown when a send/reply/forward is refused because the account's review policy
620
+ * requires an intent (maps to API 422 `intent_required`; D3).
621
+ *
622
+ * `problemErrors` carries the SAME `{field, code, detail}` hints the live problem
623
+ * body does — including the `retry_with` example — because the offline error is
624
+ * useless as practice if it is less actionable than the real one.
625
+ */
626
+ export declare class IntentRequiredError extends Error {
627
+ readonly problemErrors?: ProblemField[];
628
+ constructor(message: string, problemErrors?: ProblemField[]);
629
+ }
630
+ /** Thrown on a conflicting/idempotent-replay mutation (maps to API 409). */
631
+ export declare class ConflictError extends Error {
632
+ constructor(message: string);
633
+ }
634
+ /**
635
+ * Thrown when `text` and its deprecated `body` alias disagree (maps to API 400
636
+ * `conflicting_alias`). There is no safe guess: picking a winner would relay the
637
+ * wrong bytes from the customer's own domain to a real recipient.
638
+ */
639
+ export declare class ConflictingAliasError extends Error {
640
+ readonly problemErrors: ProblemField[];
641
+ constructor(message: string);
642
+ }
643
+ /**
644
+ * Base for the review-loop 409s that carry the RECOVERY FACTS as problem fields:
645
+ * the current state / revision / version, plus one `allowed_action` per legal verb.
646
+ * A stale-CAS retry therefore needs no extra `get_review`, and a wrong-state agent
647
+ * is told what IS legal instead of retrying the same verb forever.
648
+ */
649
+ export declare class ReviewConflictError extends ConflictError {
650
+ readonly problemErrors: ProblemField[];
651
+ constructor(message: string, review: Review);
652
+ }
653
+ /**
654
+ * 409 `stale` — the `(revision[,version])` you named is no longer current and
655
+ * NOTHING was mutated. The ONE 409 worth retrying: re-read, re-apply your edit on
656
+ * top of the other party's, resubmit with the new parent_revision. Bounded (<=3).
657
+ */
658
+ export declare class StaleError extends ReviewConflictError {
659
+ constructor(message: string, review: Review);
660
+ }
661
+ /**
662
+ * 409 `wrong_state` — this VERB is illegal from the current state, but the draft
663
+ * is still live. NEVER retry the same verb; read the `allowed_action` hints and
664
+ * pick a legal one.
665
+ */
666
+ export declare class WrongStateError extends ReviewConflictError {
667
+ constructor(message: string, review: Review);
668
+ }
669
+ /**
670
+ * 409 `terminal` — sent / auto_sent / cancelled. Nothing will ever succeed on this
671
+ * review. STOP; a `front_run_next` nudge is waiting in the drain.
672
+ */
673
+ export declare class TerminalError extends ReviewConflictError {
674
+ constructor(message: string, review: Review);
675
+ }
676
+ //# sourceMappingURL=fixtures.d.ts.map