@agenticmail/enterprise 0.5.115 → 0.5.117

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.
@@ -133,15 +133,22 @@ export class GoogleEmailProvider implements IEmailProvider {
133
133
 
134
134
  async send(options: SendEmailOptions): Promise<{ messageId: string }> {
135
135
  const raw = this.buildRawEmail(options);
136
+ const sendBody: any = { raw };
137
+ // Include threadId to keep replies in the same Gmail thread
138
+ if (options.threadId) sendBody.threadId = options.threadId;
136
139
  const data = await this.gmailFetch('/messages/send', {
137
140
  method: 'POST',
138
- body: JSON.stringify({ raw }),
141
+ body: JSON.stringify(sendBody),
139
142
  });
140
143
  return { messageId: data.id };
141
144
  }
142
145
 
143
146
  async reply(uid: string, body: string, replyAll = false): Promise<{ messageId: string }> {
144
- const original = await this.readMessage(uid);
147
+ // Fetch original with threadId
148
+ const originalData = await this.gmailFetch(`/messages/${uid}?format=full`);
149
+ const original = this.fullToMessage(originalData);
150
+ const threadId = originalData.threadId;
151
+
145
152
  const to = replyAll
146
153
  ? [original.from.email, ...(original.to || []).map(t => t.email), ...(original.cc || []).map(c => c.email)].filter(e => e !== this.identity?.email).join(', ')
147
154
  : original.from.email;
@@ -152,6 +159,7 @@ export class GoogleEmailProvider implements IEmailProvider {
152
159
  body,
153
160
  inReplyTo: original.messageId,
154
161
  references: original.references ? [...original.references, original.messageId!] : [original.messageId!],
162
+ threadId,
155
163
  });
156
164
  }
157
165
 
@@ -236,6 +244,47 @@ export class GoogleEmailProvider implements IEmailProvider {
236
244
  await Promise.all(uids.map(uid => this.deleteMessage(uid)));
237
245
  }
238
246
 
247
+ // ─── Gmail Push Notifications ────────────────────────
248
+
249
+ /**
250
+ * Set up Gmail push notifications via Google Cloud Pub/Sub.
251
+ * Gmail will POST to your Pub/Sub topic when new emails arrive.
252
+ * Requires: Pub/Sub topic created, Gmail API granted publish permission.
253
+ * Returns: historyId and expiration (watch lasts ~7 days, must renew).
254
+ */
255
+ async watchInbox(topicName: string): Promise<{ historyId: string; expiration: string }> {
256
+ const data = await this.gmailFetch('/watch', {
257
+ method: 'POST',
258
+ body: JSON.stringify({
259
+ topicName,
260
+ labelIds: ['INBOX'],
261
+ labelFilterBehavior: 'INCLUDE',
262
+ }),
263
+ });
264
+ return { historyId: data.historyId, expiration: data.expiration };
265
+ }
266
+
267
+ /**
268
+ * Stop Gmail push notifications.
269
+ */
270
+ async stopWatch(): Promise<void> {
271
+ await this.gmailFetch('/stop', { method: 'POST' });
272
+ }
273
+
274
+ /**
275
+ * Get message history since a historyId (for processing push notification deltas).
276
+ */
277
+ async getHistory(startHistoryId: string): Promise<{ messages: Array<{ id: string; threadId: string }>; historyId: string }> {
278
+ const data = await this.gmailFetch(`/history?startHistoryId=${startHistoryId}&historyTypes=messageAdded&labelId=INBOX`);
279
+ const messages: Array<{ id: string; threadId: string }> = [];
280
+ for (const h of (data.history || [])) {
281
+ for (const added of (h.messagesAdded || [])) {
282
+ if (added.message?.id) messages.push({ id: added.message.id, threadId: added.message.threadId });
283
+ }
284
+ }
285
+ return { messages, historyId: data.historyId || startHistoryId };
286
+ }
287
+
239
288
  // ─── Helpers ────────────────────────────────────────
240
289
 
241
290
  private resolveLabelId(folder: string): string {
@@ -252,12 +301,15 @@ export class GoogleEmailProvider implements IEmailProvider {
252
301
  }
253
302
 
254
303
  private buildRawEmail(options: SendEmailOptions): string {
304
+ const fromAddr = this.identity?.email;
305
+ const fromName = this.identity?.name;
255
306
  const lines = [
307
+ fromAddr ? `From: ${fromName ? `${fromName} <${fromAddr}>` : fromAddr}` : '',
256
308
  `To: ${options.to}`,
257
309
  `Subject: ${options.subject}`,
258
310
  `Content-Type: text/plain; charset=utf-8`,
259
- ];
260
- if (options.cc) lines.splice(1, 0, `Cc: ${options.cc}`);
311
+ ].filter(Boolean);
312
+ if (options.cc) lines.splice(2, 0, `Cc: ${options.cc}`);
261
313
  if (options.inReplyTo) lines.push(`In-Reply-To: ${options.inReplyTo}`);
262
314
  if (options.references?.length) lines.push(`References: ${options.references.join(' ')}`);
263
315
  lines.push('', options.body);
@@ -81,6 +81,8 @@ export interface SendEmailOptions {
81
81
  inReplyTo?: string;
82
82
  references?: string[];
83
83
  attachments?: { filename: string; content: string; contentType?: string; encoding?: string }[];
84
+ /** Gmail thread ID — keeps reply in the same thread */
85
+ threadId?: string;
84
86
  }
85
87
 
86
88
  export interface SearchCriteria {
@@ -185,6 +185,11 @@ export class EngineDatabase {
185
185
  return this.db.run(sql, params);
186
186
  }
187
187
 
188
+ /** Alias for execute() — used by SessionManager and other components */
189
+ async run(sql: string, params?: any[]): Promise<void> {
190
+ return this.db.run(sql, params);
191
+ }
192
+
188
193
  /**
189
194
  * List all dynamic (ext_*) tables currently in the database.
190
195
  */