@pome-sh/cli 0.21.5 → 0.21.6

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.
@@ -0,0 +1,683 @@
1
+ import { z, ZodError } from 'zod';
2
+
3
+ // ../packages/twin-gmail/dist/src/errors.js
4
+ var GmailError = class extends Error {
5
+ status;
6
+ reason;
7
+ constructor(status, reason, message) {
8
+ super(message);
9
+ this.status = status;
10
+ this.reason = reason;
11
+ this.name = "GmailError";
12
+ }
13
+ };
14
+ function gmailErrorEnvelope(error) {
15
+ if (error instanceof GmailError) {
16
+ return {
17
+ status: error.status,
18
+ body: {
19
+ error: {
20
+ code: error.status,
21
+ message: error.message,
22
+ errors: [{ message: error.message, domain: "global", reason: error.reason }],
23
+ status: googleStatus(error.status)
24
+ }
25
+ }
26
+ };
27
+ }
28
+ if (error instanceof ZodError || error instanceof Error && error.name === "ZodError") {
29
+ return {
30
+ status: 400,
31
+ body: {
32
+ error: {
33
+ code: 400,
34
+ message: "Invalid request",
35
+ errors: [{ message: "Invalid request", domain: "global", reason: "invalidArgument" }],
36
+ status: "INVALID_ARGUMENT"
37
+ }
38
+ }
39
+ };
40
+ }
41
+ if (error instanceof SyntaxError) {
42
+ return {
43
+ status: 400,
44
+ body: {
45
+ error: {
46
+ code: 400,
47
+ message: "Invalid JSON",
48
+ errors: [{ message: "Invalid JSON", domain: "global", reason: "invalidArgument" }],
49
+ status: "INVALID_ARGUMENT"
50
+ }
51
+ }
52
+ };
53
+ }
54
+ if (error instanceof Error && (error.name === "TwinError" || error.name === "UnknownToolError")) {
55
+ const unknownTool = error.name === "UnknownToolError";
56
+ const status = unknownTool ? 404 : typeof error.status === "number" ? error.status : 400;
57
+ return {
58
+ status,
59
+ body: {
60
+ error: {
61
+ code: status,
62
+ message: error.message,
63
+ errors: [
64
+ {
65
+ message: error.message,
66
+ domain: "global",
67
+ reason: unknownTool ? "notFound" : "invalidArgument"
68
+ }
69
+ ],
70
+ status: googleStatus(status)
71
+ }
72
+ }
73
+ };
74
+ }
75
+ return {
76
+ status: 500,
77
+ body: {
78
+ error: {
79
+ code: 500,
80
+ message: "Internal error",
81
+ errors: [{ message: "Internal error", domain: "global", reason: "backendError" }],
82
+ status: "INTERNAL"
83
+ }
84
+ }
85
+ };
86
+ }
87
+ function notFound(resource = "Requested entity") {
88
+ throw new GmailError(404, "notFound", `${resource} was not found.`);
89
+ }
90
+ function invalidArgument(message) {
91
+ throw new GmailError(400, "invalidArgument", message);
92
+ }
93
+ function unsupported(message) {
94
+ throw new GmailError(501, "notImplemented", message);
95
+ }
96
+ function googleStatus(status) {
97
+ if (status === 400)
98
+ return "INVALID_ARGUMENT";
99
+ if (status === 401)
100
+ return "UNAUTHENTICATED";
101
+ if (status === 403)
102
+ return "PERMISSION_DENIED";
103
+ if (status === 404)
104
+ return "NOT_FOUND";
105
+ if (status === 429)
106
+ return "RESOURCE_EXHAUSTED";
107
+ if (status === 409)
108
+ return "ALREADY_EXISTS";
109
+ if (status === 501)
110
+ return "UNIMPLEMENTED";
111
+ return "INTERNAL";
112
+ }
113
+
114
+ // ../packages/twin-gmail/dist/src/search-parse.js
115
+ var MAX_QUERY_BYTES = 4096;
116
+ var MAX_TOKENS = 256;
117
+ var MAX_DEPTH = 20;
118
+ var MAX_BRANCHES = 256;
119
+ var SEARCH_MAILBOX_MESSAGE_BUDGET = 1e4;
120
+ var KNOWN_FIELDS = /* @__PURE__ */ new Set([
121
+ "from",
122
+ "to",
123
+ "cc",
124
+ "bcc",
125
+ "deliveredto",
126
+ "list",
127
+ "subject",
128
+ "rfc822msgid",
129
+ "filename",
130
+ "after",
131
+ "newer",
132
+ "before",
133
+ "older",
134
+ "newer_than",
135
+ "older_than",
136
+ "size",
137
+ "larger",
138
+ "smaller",
139
+ "label",
140
+ "category",
141
+ "in",
142
+ "is",
143
+ "has"
144
+ ]);
145
+ var CATEGORY_LABELS = {
146
+ primary: "CATEGORY_PERSONAL",
147
+ personal: "CATEGORY_PERSONAL",
148
+ social: "CATEGORY_SOCIAL",
149
+ promotions: "CATEGORY_PROMOTIONS",
150
+ updates: "CATEGORY_UPDATES",
151
+ forums: "CATEGORY_FORUMS"
152
+ };
153
+ function validateSearchQuery(query) {
154
+ const node = parseSearchQuery(query);
155
+ const walk = (current) => {
156
+ switch (current.type) {
157
+ case "and":
158
+ case "or":
159
+ for (const child of current.children)
160
+ walk(child);
161
+ return;
162
+ case "not":
163
+ walk(current.child);
164
+ return;
165
+ case "around":
166
+ return;
167
+ case "term":
168
+ assertSearchTerm(current.field, current.value);
169
+ return;
170
+ }
171
+ };
172
+ walk(node);
173
+ return node;
174
+ }
175
+ function assertSearchTerm(field, value) {
176
+ if (!field)
177
+ return;
178
+ if (!KNOWN_FIELDS.has(field)) {
179
+ invalidArgument(`Unsupported search operator: ${field}`);
180
+ }
181
+ if (field === "category") {
182
+ if (!CATEGORY_LABELS[value.toLocaleLowerCase("en-US")]) {
183
+ invalidArgument(`Unsupported search category: ${value}`);
184
+ }
185
+ return;
186
+ }
187
+ if (field === "after" || field === "newer" || field === "before" || field === "older") {
188
+ parseDate(value);
189
+ return;
190
+ }
191
+ if (field === "newer_than" || field === "older_than") {
192
+ parseDuration(value);
193
+ return;
194
+ }
195
+ if (field === "size" || field === "larger" || field === "smaller") {
196
+ parseSize(value);
197
+ return;
198
+ }
199
+ if (field === "has") {
200
+ assertHasOperator(value);
201
+ }
202
+ }
203
+ function parseSearchQuery(query) {
204
+ if (Buffer.byteLength(query) > MAX_QUERY_BYTES)
205
+ invalidArgument("Search query exceeds limit");
206
+ const tokens = tokenize(query);
207
+ if (tokens.length > MAX_TOKENS)
208
+ invalidArgument("Search query has too many tokens");
209
+ if (tokens.length === 0)
210
+ return { type: "and", children: [] };
211
+ let position = 0;
212
+ const parseExpression = (depth, stop) => {
213
+ if (depth > MAX_DEPTH)
214
+ invalidArgument("Search query nesting exceeds limit");
215
+ const alternatives = [];
216
+ let conjunction = [];
217
+ const flush = () => {
218
+ alternatives.push(conjunction.length === 1 ? conjunction[0] : { type: "and", children: conjunction });
219
+ conjunction = [];
220
+ };
221
+ while (position < tokens.length) {
222
+ const token = tokens[position];
223
+ if (stop && token.value === stop)
224
+ break;
225
+ if (token.value.toUpperCase() === "OR") {
226
+ position++;
227
+ if (!conjunction.length)
228
+ invalidArgument("OR requires a left expression");
229
+ flush();
230
+ continue;
231
+ }
232
+ if (token.value.toUpperCase() === "AND") {
233
+ position++;
234
+ continue;
235
+ }
236
+ conjunction.push(parsePrimary(depth + 1));
237
+ }
238
+ if (conjunction.length)
239
+ flush();
240
+ if (!alternatives.length)
241
+ return { type: "and", children: [] };
242
+ return alternatives.length === 1 ? alternatives[0] : { type: "or", children: alternatives };
243
+ };
244
+ const parsePrimary = (depth) => {
245
+ const token = tokens[position++];
246
+ if (token.value === "(" || token.value === "{") {
247
+ const end = token.value === "(" ? ")" : "}";
248
+ const child = parseExpression(depth, end);
249
+ if (tokens[position]?.value !== end)
250
+ invalidArgument(`Unclosed ${token.value}`);
251
+ position++;
252
+ return token.value === "{" ? makeImplicitOr(child) : child;
253
+ }
254
+ if (token.value === ")" || token.value === "}")
255
+ invalidArgument(`Unexpected ${token.value}`);
256
+ let value = token.value;
257
+ let negate = false;
258
+ if (value === "-") {
259
+ negate = true;
260
+ const next = tokens[position++];
261
+ if (!next)
262
+ invalidArgument("Negation requires an expression");
263
+ position--;
264
+ const child = parsePrimary(depth + 1);
265
+ return { type: "not", child };
266
+ }
267
+ if (value.startsWith("-") && value.length > 1) {
268
+ negate = true;
269
+ value = value.slice(1);
270
+ }
271
+ const around = tokens[position]?.value.toUpperCase() === "AROUND";
272
+ if (around) {
273
+ position++;
274
+ const distance = Number(tokens[position++]?.value);
275
+ const right = tokens[position++]?.value;
276
+ if (!Number.isInteger(distance) || distance < 1 || distance > 100 || !right) {
277
+ invalidArgument("AROUND requires a distance and right term");
278
+ }
279
+ const node2 = { type: "around", left: value, right, distance };
280
+ return negate ? { type: "not", child: node2 } : node2;
281
+ }
282
+ const colon = value.indexOf(":");
283
+ let node;
284
+ if (colon > 0) {
285
+ const field = value.slice(0, colon).toLowerCase();
286
+ let fieldValue = value.slice(colon + 1);
287
+ if (!fieldValue && (tokens[position]?.value === "(" || tokens[position]?.value === "{")) {
288
+ node = parseFieldGroup(field, depth + 1);
289
+ return negate ? { type: "not", child: node } : node;
290
+ }
291
+ if (!fieldValue && tokens[position] && !["AND", "OR", ")", "}"].includes(tokens[position].value.toUpperCase())) {
292
+ fieldValue = tokens[position++].value;
293
+ }
294
+ node = { type: "term", field, value: fieldValue, exact: token.quoted };
295
+ } else {
296
+ node = { type: "term", value: value.startsWith("+") ? value.slice(1) : value, exact: token.quoted || value.startsWith("+") };
297
+ }
298
+ return negate ? { type: "not", child: node } : node;
299
+ };
300
+ const parseFieldGroup = (field, depth) => {
301
+ if (depth > MAX_DEPTH)
302
+ invalidArgument("Search query nesting exceeds limit");
303
+ const opening = tokens[position++].value;
304
+ const end = opening === "(" ? ")" : "}";
305
+ const groups = [[]];
306
+ while (position < tokens.length && tokens[position].value !== end) {
307
+ const token = tokens[position++];
308
+ if (token.value.toUpperCase() === "OR") {
309
+ groups.push([]);
310
+ continue;
311
+ }
312
+ if (token.value.toUpperCase() === "AND")
313
+ continue;
314
+ if (["(", "{", ")", "}"].includes(token.value))
315
+ invalidArgument("Nested field groups are unsupported");
316
+ let value = token.value;
317
+ let negate = false;
318
+ if (value.startsWith("-")) {
319
+ negate = true;
320
+ value = value.slice(1);
321
+ }
322
+ const term = { type: "term", field, value, exact: token.quoted };
323
+ groups.at(-1).push(negate ? { type: "not", child: term } : term);
324
+ }
325
+ if (tokens[position]?.value !== end)
326
+ invalidArgument(`Unclosed ${opening}`);
327
+ position++;
328
+ const nodes = groups.map((children) => children.length === 1 ? children[0] : { type: "and", children });
329
+ const useOr = opening === "{" || nodes.length > 1;
330
+ return useOr ? { type: "or", children: nodes } : nodes[0] ?? { type: "and", children: [] };
331
+ };
332
+ const root = parseExpression(0);
333
+ if (position !== tokens.length)
334
+ invalidArgument("Unexpected search token");
335
+ if (countBranches(root) > MAX_BRANCHES)
336
+ invalidArgument("Search query has too many branches");
337
+ return root;
338
+ }
339
+ function assertHasOperator(value) {
340
+ const normalized = value.toLocaleLowerCase("en-US");
341
+ if (normalized.endsWith("-star")) {
342
+ invalidArgument(`Unsupported colored-star operator: has:${value}; twin maps only STARRED via is:starred`);
343
+ }
344
+ }
345
+ function parseDate(value) {
346
+ const normalized = /^\d{4}\/\d{1,2}\/\d{1,2}$/.test(value) ? value.replaceAll("/", "-") : value;
347
+ const date = Date.parse(`${normalized}${/^\d{4}-\d/.test(normalized) ? "T00:00:00Z" : ""}`);
348
+ if (Number.isNaN(date))
349
+ invalidArgument(`Invalid search date: ${value}`);
350
+ return date;
351
+ }
352
+ function parseDuration(value) {
353
+ const match = value.match(/^(\d+)([dmy])$/i);
354
+ if (!match)
355
+ invalidArgument(`Invalid search duration: ${value}`);
356
+ const units = { d: 864e5, m: 30 * 864e5, y: 365 * 864e5 };
357
+ return Number(match[1]) * units[match[2].toLowerCase()];
358
+ }
359
+ function parseSize(value) {
360
+ const match = value.match(/^(\d+(?:\.\d+)?)([kmg])?$/i);
361
+ if (!match)
362
+ invalidArgument(`Invalid search size: ${value}`);
363
+ const scale = { k: 1024, m: 1024 ** 2, g: 1024 ** 3 };
364
+ return Math.floor(Number(match[1]) * (match[2] ? scale[match[2].toLowerCase()] : 1));
365
+ }
366
+ function countBranches(node) {
367
+ switch (node.type) {
368
+ case "and":
369
+ case "or":
370
+ return node.children.reduce((sum, child) => sum + countBranches(child), 0);
371
+ case "not":
372
+ return countBranches(node.child);
373
+ case "around":
374
+ case "term":
375
+ return 1;
376
+ }
377
+ }
378
+ function tokenize(query) {
379
+ const out = [];
380
+ let index = 0;
381
+ while (index < query.length) {
382
+ if (/\s/.test(query[index])) {
383
+ index++;
384
+ continue;
385
+ }
386
+ const char = query[index];
387
+ if ("(){}".includes(char)) {
388
+ out.push({ value: char });
389
+ index++;
390
+ continue;
391
+ }
392
+ let value = "";
393
+ let quoted = false;
394
+ while (index < query.length && !/\s/.test(query[index]) && !"(){}".includes(query[index])) {
395
+ if (query[index] === '"') {
396
+ quoted = true;
397
+ index++;
398
+ while (index < query.length && query[index] !== '"') {
399
+ if (query[index] === "\\" && index + 1 < query.length)
400
+ index++;
401
+ value += query[index++];
402
+ }
403
+ if (query[index] !== '"')
404
+ invalidArgument("Unclosed search quote");
405
+ index++;
406
+ } else {
407
+ value += query[index++];
408
+ }
409
+ }
410
+ if (value)
411
+ out.push({ value, quoted });
412
+ }
413
+ return out;
414
+ }
415
+ function makeImplicitOr(node) {
416
+ return node.type === "and" ? { type: "or", children: node.children } : node;
417
+ }
418
+ var KNOWN_FAULT_NAMES = ["rate-limited"];
419
+ var gmailFaultSchema = z.object({
420
+ name: z.enum(KNOWN_FAULT_NAMES),
421
+ target: z.string().min(1).max(128).default("messages.send"),
422
+ succeedFirst: z.number().int().nonnegative().max(1e3).default(2),
423
+ throttleFor: z.number().int().positive().max(1e3).default(3),
424
+ retryAfterSeconds: z.number().int().positive().max(3600).default(1)
425
+ }).strict();
426
+ function checkFault(db, operation) {
427
+ const fault = readFaults(db).find((f) => f.target === operation);
428
+ if (!fault)
429
+ return;
430
+ const calls = bumpFaultCounter(db, operation);
431
+ if (calls > fault.succeedFirst && calls <= fault.succeedFirst + fault.throttleFor) {
432
+ throw new GmailError(429, "rateLimitExceeded", `Rate limit exceeded for ${operation}. Retry after ${fault.retryAfterSeconds}s.`);
433
+ }
434
+ }
435
+ function readFaults(db) {
436
+ const row = db.prepare("SELECT value FROM gmail_config WHERE key = 'faults'").get();
437
+ if (!row)
438
+ return [];
439
+ try {
440
+ return JSON.parse(row.value);
441
+ } catch {
442
+ return [];
443
+ }
444
+ }
445
+ function bumpFaultCounter(db, operation) {
446
+ db.prepare("INSERT INTO fault_counters(operation, calls) VALUES (?, 1) ON CONFLICT(operation) DO UPDATE SET calls = calls + 1").run(operation);
447
+ const row = db.prepare("SELECT calls FROM fault_counters WHERE operation = ?").get(operation);
448
+ return row.calls;
449
+ }
450
+
451
+ // ../packages/twin-gmail/dist/src/seed.js
452
+ var email = z.string().trim().email().transform((value) => value.toLowerCase());
453
+ var id = z.string().min(1).max(128).regex(/^[A-Za-z0-9_-]+$/);
454
+ var attachmentSchema = z.object({
455
+ filename: z.string().max(512),
456
+ mimeType: z.string().min(1).max(255).default("application/octet-stream"),
457
+ disposition: z.enum(["attachment", "inline"]).default("attachment"),
458
+ contentId: z.string().max(998).optional(),
459
+ data: z.string().max(5e7)
460
+ }).strict();
461
+ var messageFields = {
462
+ id: id.optional(),
463
+ threadId: id.optional(),
464
+ raw: z.string().max(5e7).optional(),
465
+ from: email.optional(),
466
+ to: z.array(email).max(500).default([]),
467
+ cc: z.array(email).max(500).default([]),
468
+ bcc: z.array(email).max(500).default([]),
469
+ subject: z.string().max(998).default(""),
470
+ text: z.string().max(25e6).default(""),
471
+ html: z.string().max(25e6).default(""),
472
+ date: z.string().datetime({ offset: true }).optional(),
473
+ messageId: z.string().min(3).max(998).optional(),
474
+ inReplyTo: z.string().max(998).optional(),
475
+ references: z.array(z.string().max(998)).max(100).default([]),
476
+ attachments: z.array(attachmentSchema).max(100).default([])
477
+ };
478
+ var messageSchema = z.object({
479
+ ...messageFields,
480
+ labels: z.array(z.string().min(1).max(255)).max(100).default([])
481
+ }).strict();
482
+ var draftSchema = z.object(messageFields).strict();
483
+ var labelSchema = z.object({
484
+ id: id.optional(),
485
+ name: z.string().trim().min(1).max(225),
486
+ color: z.object({
487
+ textColor: z.string().max(32).optional(),
488
+ backgroundColor: z.string().max(32).optional()
489
+ }).strict().optional()
490
+ }).strict();
491
+ var filterSchema = z.object({
492
+ id: id.optional(),
493
+ criteria: z.object({
494
+ from: z.string().max(998).optional(),
495
+ to: z.string().max(998).optional(),
496
+ subject: z.string().max(998).optional(),
497
+ query: z.string().max(4096).optional(),
498
+ negatedQuery: z.string().max(4096).optional(),
499
+ hasAttachment: z.boolean().optional(),
500
+ excludeChats: z.boolean().optional(),
501
+ size: z.number().int().nonnegative().optional(),
502
+ sizeComparison: z.enum(["larger", "smaller"]).optional()
503
+ }).strict().default({}),
504
+ action: z.object({
505
+ addLabelIds: z.array(z.string().min(1)).max(100).default([]),
506
+ removeLabelIds: z.array(z.string().min(1)).max(100).default([]),
507
+ forward: email.optional()
508
+ }).strict().default({ addLabelIds: [], removeLabelIds: [] })
509
+ }).strict();
510
+ var sendAsSchema = z.object({
511
+ sendAsEmail: email,
512
+ displayName: z.string().max(256).default(""),
513
+ replyToAddress: email.optional(),
514
+ isPrimary: z.boolean().default(false),
515
+ isDefault: z.boolean().default(false),
516
+ verificationStatus: z.enum(["accepted", "pending"]).default("accepted")
517
+ }).strict();
518
+ var mailboxSchema = z.object({
519
+ email,
520
+ displayName: z.string().max(256).default(""),
521
+ labels: z.array(labelSchema).max(5e3).default([]),
522
+ messages: z.array(messageSchema).max(1e4).default([]),
523
+ drafts: z.array(draftSchema).max(5e3).default([]),
524
+ filters: z.array(filterSchema).max(1e3).default([]),
525
+ forwardingAddresses: z.array(z.object({
526
+ forwardingEmail: email,
527
+ verificationStatus: z.enum(["accepted", "pending"]).default("pending")
528
+ }).strict()).max(1e3).default([]),
529
+ sendAs: z.array(sendAsSchema).max(1e3).default([])
530
+ }).strict();
531
+ var gmailSeedSchema = z.object({
532
+ primaryMailbox: mailboxSchema,
533
+ mailboxes: z.array(mailboxSchema).max(100).default([]),
534
+ deliveryMode: z.enum(["sender-only", "seeded-mailboxes"]).default("sender-only"),
535
+ clock: z.string().datetime({ offset: true }).default("2025-01-01T00:00:00.000Z"),
536
+ faults: z.array(gmailFaultSchema).max(50).default([])
537
+ }).strict().superRefine((seed, ctx) => {
538
+ const emails = [seed.primaryMailbox.email, ...seed.mailboxes.map((mailbox) => mailbox.email)];
539
+ const seen = /* @__PURE__ */ new Set();
540
+ for (const mailboxEmail of emails) {
541
+ if (seen.has(mailboxEmail)) {
542
+ ctx.addIssue({ code: "custom", message: `Duplicate mailbox: ${mailboxEmail}` });
543
+ }
544
+ seen.add(mailboxEmail);
545
+ }
546
+ for (const mailbox of [seed.primaryMailbox, ...seed.mailboxes]) {
547
+ const labelNames = /* @__PURE__ */ new Set();
548
+ for (const label of mailbox.labels) {
549
+ const key = label.name.toLowerCase();
550
+ if (labelNames.has(key)) {
551
+ ctx.addIssue({ code: "custom", message: `Duplicate label in ${mailbox.email}: ${label.name}` });
552
+ }
553
+ labelNames.add(key);
554
+ }
555
+ for (const filter of mailbox.filters) {
556
+ if (filter.action.forward) {
557
+ ctx.addIssue({
558
+ code: "custom",
559
+ message: `Filter forwarding is unsupported: ${mailbox.email}`
560
+ });
561
+ }
562
+ for (const key of ["query", "negatedQuery"]) {
563
+ const value = filter.criteria[key];
564
+ if (!value)
565
+ continue;
566
+ try {
567
+ validateSearchQuery(value);
568
+ } catch (error) {
569
+ ctx.addIssue({
570
+ code: "custom",
571
+ message: `Invalid filter ${key} in ${mailbox.email}: ${error.message}`
572
+ });
573
+ }
574
+ }
575
+ }
576
+ }
577
+ });
578
+ function parseSeed(input) {
579
+ return gmailSeedSchema.parse(input);
580
+ }
581
+ function loadSeedFromEnv(env = process.env) {
582
+ const raw = env.POME_SEED_JSON;
583
+ if (!raw)
584
+ return parseSeed(defaultSeedState());
585
+ let parsed;
586
+ try {
587
+ parsed = JSON.parse(raw);
588
+ } catch (error) {
589
+ throw new Error(`POME_SEED_JSON is not valid JSON: ${error.message}`);
590
+ }
591
+ return parseSeed(parsed);
592
+ }
593
+ var DEFAULT_GMAIL_AGENT_EMAIL = "pome-agent@pome-twin.test";
594
+ function agentPathInboxMailbox(email2 = DEFAULT_GMAIL_AGENT_EMAIL) {
595
+ return {
596
+ email: email2,
597
+ displayName: "Pome Agent",
598
+ labels: [
599
+ { id: "Label_follow_up", name: "Follow Up" },
600
+ { id: "Label_build", name: "Build" }
601
+ ],
602
+ messages: [
603
+ {
604
+ id: "msg_welcome",
605
+ threadId: "thread_welcome",
606
+ from: "welcome@pome-twin.test",
607
+ to: [email2],
608
+ subject: "Welcome to your Pome Gmail twin",
609
+ text: "Your deterministic inbox is ready for agent testing.",
610
+ html: "<p>Your deterministic inbox is ready for agent testing.</p>",
611
+ date: "2026-07-18T09:00:00.000Z",
612
+ messageId: "welcome@pome-twin.test",
613
+ labels: ["INBOX"]
614
+ },
615
+ {
616
+ id: "msg_build",
617
+ threadId: "thread_build",
618
+ from: "ci@example.com",
619
+ to: [email2],
620
+ subject: "Build failed on main",
621
+ text: "The nightly build failed. See the attached log.",
622
+ date: "2026-07-19T10:00:00.000Z",
623
+ messageId: "build-001@example.com",
624
+ labels: ["INBOX", "UNREAD", "Build"],
625
+ attachments: [
626
+ {
627
+ filename: "build.log",
628
+ mimeType: "text/plain",
629
+ data: Buffer.from("BUILD FAILED step=test\n", "utf8").toString("base64")
630
+ }
631
+ ]
632
+ },
633
+ {
634
+ id: "msg_build_reply",
635
+ threadId: "thread_build",
636
+ from: email2,
637
+ to: ["ci@example.com"],
638
+ subject: "Re: Build failed on main",
639
+ text: "Looking into the failure now.",
640
+ date: "2026-07-19T11:00:00.000Z",
641
+ messageId: "build-reply@pome-twin.test",
642
+ inReplyTo: "build-001@example.com",
643
+ references: ["build-001@example.com"],
644
+ labels: ["SENT"]
645
+ },
646
+ {
647
+ id: "msg_support",
648
+ threadId: "thread_support",
649
+ from: "alice@example.com",
650
+ to: [email2],
651
+ subject: "Production export is stuck",
652
+ text: "Our production export has been stuck for an hour. Can you investigate?",
653
+ date: "2026-07-19T12:00:00.000Z",
654
+ messageId: "support-001@example.com",
655
+ labels: ["INBOX", "UNREAD"]
656
+ }
657
+ ],
658
+ drafts: [
659
+ {
660
+ id: "draft_ack",
661
+ threadId: "thread_draft_ack",
662
+ to: ["bob@example.com"],
663
+ subject: "Draft acknowledgment",
664
+ text: "Thanks \u2014 I'll follow up shortly.",
665
+ date: "2026-07-19T13:00:00.000Z",
666
+ messageId: "draft-ack@pome-twin.test"
667
+ }
668
+ ],
669
+ filters: [],
670
+ forwardingAddresses: [],
671
+ sendAs: []
672
+ };
673
+ }
674
+ function defaultSeedState() {
675
+ return {
676
+ primaryMailbox: agentPathInboxMailbox(),
677
+ mailboxes: [],
678
+ deliveryMode: "sender-only",
679
+ clock: "2026-07-20T00:00:00.000Z"
680
+ };
681
+ }
682
+
683
+ export { CATEGORY_LABELS, DEFAULT_GMAIL_AGENT_EMAIL, GmailError, KNOWN_FIELDS, SEARCH_MAILBOX_MESSAGE_BUDGET, agentPathInboxMailbox, assertHasOperator, checkFault, defaultSeedState, gmailErrorEnvelope, gmailSeedSchema, invalidArgument, loadSeedFromEnv, notFound, parseDate, parseDuration, parseSearchQuery, parseSeed, parseSize, unsupported, validateSearchQuery };