@agenticmail/enterprise 0.5.133 → 0.5.134

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,49 @@
1
+ import {
2
+ AgentRuntime,
3
+ EmailChannel,
4
+ FollowUpScheduler,
5
+ SessionManager,
6
+ SubAgentManager,
7
+ ToolRegistry,
8
+ callLLM,
9
+ createAgentRuntime,
10
+ createNoopHooks,
11
+ createRuntimeHooks,
12
+ estimateMessageTokens,
13
+ estimateTokens,
14
+ executeTool,
15
+ runAgentLoop,
16
+ toolsToDefinitions
17
+ } from "./chunk-Y7ZDAH4R.js";
18
+ import "./chunk-TYW5XTOW.js";
19
+ import "./chunk-AQH4DFYV.js";
20
+ import {
21
+ PROVIDER_REGISTRY,
22
+ listAllProviders,
23
+ resolveApiKeyForProvider,
24
+ resolveProvider
25
+ } from "./chunk-67KZYSLU.js";
26
+ import "./chunk-JLSQOQ5L.js";
27
+ import "./chunk-NRF3YRF7.js";
28
+ import "./chunk-KFQGP6VL.js";
29
+ export {
30
+ AgentRuntime,
31
+ EmailChannel,
32
+ FollowUpScheduler,
33
+ PROVIDER_REGISTRY,
34
+ SessionManager,
35
+ SubAgentManager,
36
+ ToolRegistry,
37
+ callLLM,
38
+ createAgentRuntime,
39
+ createNoopHooks,
40
+ createRuntimeHooks,
41
+ estimateMessageTokens,
42
+ estimateTokens,
43
+ executeTool,
44
+ listAllProviders,
45
+ resolveApiKeyForProvider,
46
+ resolveProvider,
47
+ runAgentLoop,
48
+ toolsToDefinitions
49
+ };
@@ -0,0 +1,12 @@
1
+ import {
2
+ createServer
3
+ } from "./chunk-4B5L7JVW.js";
4
+ import "./chunk-3SMTCIR4.js";
5
+ import "./chunk-RO537U6H.js";
6
+ import "./chunk-DRXMYYKN.js";
7
+ import "./chunk-67KZYSLU.js";
8
+ import "./chunk-JLSQOQ5L.js";
9
+ import "./chunk-KFQGP6VL.js";
10
+ export {
11
+ createServer
12
+ };
@@ -0,0 +1,20 @@
1
+ import {
2
+ promptCompanyInfo,
3
+ promptDatabase,
4
+ promptDeployment,
5
+ promptDomain,
6
+ promptRegistration,
7
+ provision,
8
+ runSetupWizard
9
+ } from "./chunk-42YEZIIA.js";
10
+ import "./chunk-MHIFVS5L.js";
11
+ import "./chunk-KFQGP6VL.js";
12
+ export {
13
+ promptCompanyInfo,
14
+ promptDatabase,
15
+ promptDeployment,
16
+ promptDomain,
17
+ promptRegistration,
18
+ provision,
19
+ runSetupWizard
20
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenticmail/enterprise",
3
- "version": "0.5.133",
3
+ "version": "0.5.134",
4
4
  "description": "AgenticMail Enterprise — cloud-hosted AI agent identity, email, auth & compliance for organizations",
5
5
  "type": "module",
6
6
  "bin": {
@@ -177,6 +177,33 @@ function buildRawEmail(opts: {
177
177
  return lines.join('\r\n');
178
178
  }
179
179
 
180
+ // Cache for Gmail signature (fetched once per tool creation)
181
+ let cachedSignature: string | null = null;
182
+ let signatureFetched = false;
183
+
184
+ async function getSignature(token: string): Promise<string> {
185
+ if (signatureFetched) return cachedSignature || '';
186
+ signatureFetched = true;
187
+ try {
188
+ const res = await gmail(token, '/settings/sendAs');
189
+ const primary = res.sendAs?.find((s: any) => s.isPrimary) || res.sendAs?.[0];
190
+ cachedSignature = primary?.signature || null;
191
+ } catch { cachedSignature = null; }
192
+ return cachedSignature || '';
193
+ }
194
+
195
+ function appendSignature(body: string, signatureHtml: string): string {
196
+ if (!signatureHtml) return body;
197
+ // For plain text, strip HTML tags from signature and append
198
+ const plainSig = signatureHtml.replace(/<br\s*\/?>/gi, '\n').replace(/<[^>]+>/g, '').trim();
199
+ return body + '\n\n' + plainSig;
200
+ }
201
+
202
+ function appendSignatureHtml(bodyHtml: string, signatureHtml: string): string {
203
+ if (!signatureHtml) return bodyHtml;
204
+ return bodyHtml + '<br><br>' + signatureHtml;
205
+ }
206
+
180
207
  export function createGmailTools(config: GoogleToolsConfig, _options?: ToolCreationOptions): AnyAgentTool[] {
181
208
  const tp = config.tokenProvider;
182
209
  return [
@@ -317,9 +344,12 @@ export function createGmailTools(config: GoogleToolsConfig, _options?: ToolCreat
317
344
  try {
318
345
  const token = await tp.getAccessToken();
319
346
  const email = tp.getEmail();
347
+ const sig = await getSignature(token);
348
+ const bodyWithSig = appendSignature(params.body, sig);
349
+ const htmlWithSig = params.html ? appendSignatureHtml(params.html, sig) : (sig ? `<div>${params.body.replace(/\n/g, '<br>')}</div><br><br>${sig}` : undefined);
320
350
  const raw = buildRawEmail({
321
351
  from: email, to: params.to, cc: params.cc, bcc: params.bcc,
322
- subject: params.subject, body: params.body, html: params.html,
352
+ subject: params.subject, body: bodyWithSig, html: htmlWithSig,
323
353
  replyTo: params.replyTo, inReplyTo: params.inReplyTo, references: params.references,
324
354
  });
325
355
  const sendBody: any = { raw: encodeBase64Url(raw) };
@@ -371,9 +401,12 @@ export function createGmailTools(config: GoogleToolsConfig, _options?: ToolCreat
371
401
  const subject = oh.Subject?.startsWith('Re:') ? oh.Subject : `Re: ${oh.Subject || ''}`;
372
402
  const references = [oh.References, oh['Message-ID']].filter(Boolean).join(' ');
373
403
 
404
+ const sig = await getSignature(token);
405
+ const bodyWithSig = appendSignature(params.body, sig);
406
+ const htmlWithSig = params.html ? appendSignatureHtml(params.html, sig) : (sig ? `<div>${params.body.replace(/\n/g, '<br>')}</div><br><br>${sig}` : undefined);
374
407
  const raw = buildRawEmail({
375
408
  from: email, to, cc: params.cc,
376
- subject, body: params.body, html: params.html,
409
+ subject, body: bodyWithSig, html: htmlWithSig,
377
410
  inReplyTo: oh['Message-ID'], references,
378
411
  });
379
412
 
@@ -12,6 +12,7 @@ export { createGoogleSheetsTools } from './sheets.js';
12
12
  export { createGoogleDocsTools } from './docs.js';
13
13
  export { createGoogleContactsTools } from './contacts.js';
14
14
  export { createMeetingTools } from './meetings.js';
15
+ export { createGoogleTasksTools } from './tasks.js';
15
16
 
16
17
  import type { AnyAgentTool, ToolCreationOptions } from '../../types.js';
17
18
  import type { TokenProvider } from '../oauth-token-provider.js';
@@ -22,6 +23,7 @@ import { createGoogleSheetsTools } from './sheets.js';
22
23
  import { createGoogleDocsTools } from './docs.js';
23
24
  import { createGoogleContactsTools } from './contacts.js';
24
25
  import { createMeetingTools } from './meetings.js';
26
+ import { createGoogleTasksTools } from './tasks.js';
25
27
 
26
28
  export interface GoogleToolsConfig {
27
29
  tokenProvider: TokenProvider;
@@ -44,5 +46,6 @@ export function createAllGoogleTools(config: GoogleToolsConfig, options?: ToolCr
44
46
  ...createGoogleDocsTools(config, options),
45
47
  ...createGoogleContactsTools(config, options),
46
48
  ...createMeetingTools(config, options),
49
+ ...createGoogleTasksTools(config.tokenProvider),
47
50
  ];
48
51
  }
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Google Tasks API Tools
3
+ *
4
+ * Lets agents create, list, complete, and manage tasks.
5
+ * Uses Google Tasks API v1.
6
+ */
7
+
8
+ import type { AnyAgentTool } from '../../types.js';
9
+ import type { TokenProvider } from './index.js';
10
+
11
+ // ─── Helper ─────────────────────────────────────────────
12
+
13
+ async function tasks(token: string, path: string, opts?: { method?: string; body?: any; query?: Record<string, string> }): Promise<any> {
14
+ const url = new URL(`https://tasks.googleapis.com/tasks/v1${path}`);
15
+ if (opts?.query) for (const [k, v] of Object.entries(opts.query)) url.searchParams.set(k, v);
16
+ const res = await fetch(url.toString(), {
17
+ method: opts?.method || 'GET',
18
+ headers: {
19
+ Authorization: `Bearer ${token}`,
20
+ ...(opts?.body ? { 'Content-Type': 'application/json' } : {}),
21
+ },
22
+ ...(opts?.body ? { body: JSON.stringify(opts.body) } : {}),
23
+ });
24
+ if (!res.ok) {
25
+ const errText = await res.text();
26
+ throw new Error(`Google Tasks API ${res.status}: ${errText}`);
27
+ }
28
+ if (res.status === 204) return {};
29
+ return res.json();
30
+ }
31
+
32
+ function jsonResult(data: any) { return { success: true, output: JSON.stringify(data, null, 2) }; }
33
+ function errorResult(msg: string) { return { success: false, output: `Error: ${msg}` }; }
34
+
35
+ // ─── Tool Definitions ───────────────────────────────────
36
+
37
+ export function createGoogleTasksTools(tp: TokenProvider): AnyAgentTool[] {
38
+ return [
39
+ {
40
+ name: 'google_tasks_list_tasklists',
41
+ description: 'List all task lists (like "My Tasks", custom lists). Returns task list IDs needed for other operations.',
42
+ category: 'utility' as const,
43
+ parameters: { type: 'object' as const, properties: {}, required: [] },
44
+ async execute() {
45
+ try {
46
+ const token = await tp.getAccessToken();
47
+ const result = await tasks(token, '/users/@me/lists');
48
+ const lists = (result.items || []).map((l: any) => ({ id: l.id, title: l.title, updated: l.updated }));
49
+ return jsonResult({ taskLists: lists });
50
+ } catch (e: any) { return errorResult(e.message); }
51
+ },
52
+ },
53
+ {
54
+ name: 'google_tasks_list',
55
+ description: 'List tasks in a task list. Shows pending tasks by default. Use showCompleted=true to include completed tasks.',
56
+ category: 'utility' as const,
57
+ parameters: {
58
+ type: 'object' as const,
59
+ properties: {
60
+ taskListId: { type: 'string', description: 'Task list ID (default: "@default" for primary list)' },
61
+ showCompleted: { type: 'string', description: '"true" to include completed tasks' },
62
+ maxResults: { type: 'string', description: 'Max results (default: 20)' },
63
+ },
64
+ required: [],
65
+ },
66
+ async execute(_id: string, params: any) {
67
+ try {
68
+ const token = await tp.getAccessToken();
69
+ const listId = params.taskListId || '@default';
70
+ const query: Record<string, string> = { maxResults: params.maxResults || '20' };
71
+ if (params.showCompleted === 'true') query.showCompleted = 'true';
72
+ else query.showCompleted = 'false';
73
+ const result = await tasks(token, `/lists/${encodeURIComponent(listId)}/tasks`, { query });
74
+ const items = (result.items || []).map((t: any) => ({
75
+ id: t.id, title: t.title, notes: t.notes, status: t.status,
76
+ due: t.due, completed: t.completed, updated: t.updated, parent: t.parent,
77
+ }));
78
+ return jsonResult({ tasks: items, count: items.length });
79
+ } catch (e: any) { return errorResult(e.message); }
80
+ },
81
+ },
82
+ {
83
+ name: 'google_tasks_create',
84
+ description: 'Create a new task. Use this to track work items, reminders, and follow-ups.',
85
+ category: 'utility' as const,
86
+ parameters: {
87
+ type: 'object' as const,
88
+ properties: {
89
+ title: { type: 'string', description: 'Task title (required)' },
90
+ notes: { type: 'string', description: 'Task notes/details' },
91
+ due: { type: 'string', description: 'Due date in RFC 3339 format (e.g. "2026-02-24T00:00:00.000Z")' },
92
+ taskListId: { type: 'string', description: 'Task list ID (default: "@default")' },
93
+ },
94
+ required: ['title'],
95
+ },
96
+ async execute(_id: string, params: any) {
97
+ try {
98
+ const token = await tp.getAccessToken();
99
+ const listId = params.taskListId || '@default';
100
+ const body: any = { title: params.title };
101
+ if (params.notes) body.notes = params.notes;
102
+ if (params.due) body.due = params.due;
103
+ const result = await tasks(token, `/lists/${encodeURIComponent(listId)}/tasks`, { method: 'POST', body });
104
+ return jsonResult({ created: true, id: result.id, title: result.title, due: result.due });
105
+ } catch (e: any) { return errorResult(e.message); }
106
+ },
107
+ },
108
+ {
109
+ name: 'google_tasks_complete',
110
+ description: 'Mark a task as completed.',
111
+ category: 'utility' as const,
112
+ parameters: {
113
+ type: 'object' as const,
114
+ properties: {
115
+ taskId: { type: 'string', description: 'Task ID (required)' },
116
+ taskListId: { type: 'string', description: 'Task list ID (default: "@default")' },
117
+ },
118
+ required: ['taskId'],
119
+ },
120
+ async execute(_id: string, params: any) {
121
+ try {
122
+ const token = await tp.getAccessToken();
123
+ const listId = params.taskListId || '@default';
124
+ const result = await tasks(token, `/lists/${encodeURIComponent(listId)}/tasks/${encodeURIComponent(params.taskId)}`, {
125
+ method: 'PATCH', body: { status: 'completed' },
126
+ });
127
+ return jsonResult({ completed: true, id: result.id, title: result.title });
128
+ } catch (e: any) { return errorResult(e.message); }
129
+ },
130
+ },
131
+ {
132
+ name: 'google_tasks_update',
133
+ description: 'Update a task (title, notes, due date).',
134
+ category: 'utility' as const,
135
+ parameters: {
136
+ type: 'object' as const,
137
+ properties: {
138
+ taskId: { type: 'string', description: 'Task ID (required)' },
139
+ title: { type: 'string', description: 'New title' },
140
+ notes: { type: 'string', description: 'New notes' },
141
+ due: { type: 'string', description: 'New due date (RFC 3339)' },
142
+ taskListId: { type: 'string', description: 'Task list ID (default: "@default")' },
143
+ },
144
+ required: ['taskId'],
145
+ },
146
+ async execute(_id: string, params: any) {
147
+ try {
148
+ const token = await tp.getAccessToken();
149
+ const listId = params.taskListId || '@default';
150
+ const body: any = {};
151
+ if (params.title) body.title = params.title;
152
+ if (params.notes) body.notes = params.notes;
153
+ if (params.due) body.due = params.due;
154
+ const result = await tasks(token, `/lists/${encodeURIComponent(listId)}/tasks/${encodeURIComponent(params.taskId)}`, {
155
+ method: 'PATCH', body,
156
+ });
157
+ return jsonResult({ updated: true, id: result.id, title: result.title, due: result.due });
158
+ } catch (e: any) { return errorResult(e.message); }
159
+ },
160
+ },
161
+ {
162
+ name: 'google_tasks_delete',
163
+ description: 'Delete a task.',
164
+ category: 'utility' as const,
165
+ parameters: {
166
+ type: 'object' as const,
167
+ properties: {
168
+ taskId: { type: 'string', description: 'Task ID (required)' },
169
+ taskListId: { type: 'string', description: 'Task list ID (default: "@default")' },
170
+ },
171
+ required: ['taskId'],
172
+ },
173
+ async execute(_id: string, params: any) {
174
+ try {
175
+ const token = await tp.getAccessToken();
176
+ const listId = params.taskListId || '@default';
177
+ await tasks(token, `/lists/${encodeURIComponent(listId)}/tasks/${encodeURIComponent(params.taskId)}`, { method: 'DELETE' });
178
+ return jsonResult({ deleted: true, taskId: params.taskId });
179
+ } catch (e: any) { return errorResult(e.message); }
180
+ },
181
+ },
182
+ {
183
+ name: 'google_tasks_create_list',
184
+ description: 'Create a new task list (e.g. "Follow-ups", "Customer Issues").',
185
+ category: 'utility' as const,
186
+ parameters: {
187
+ type: 'object' as const,
188
+ properties: {
189
+ title: { type: 'string', description: 'Task list title (required)' },
190
+ },
191
+ required: ['title'],
192
+ },
193
+ async execute(_id: string, params: any) {
194
+ try {
195
+ const token = await tp.getAccessToken();
196
+ const result = await tasks(token, '/users/@me/lists', { method: 'POST', body: { title: params.title } });
197
+ return jsonResult({ created: true, id: result.id, title: result.title });
198
+ } catch (e: any) { return errorResult(e.message); }
199
+ },
200
+ },
201
+ ];
202
+ }
package/src/cli-agent.ts CHANGED
@@ -160,13 +160,17 @@ export async function runAgent(_args: string[]) {
160
160
  await runtime.start();
161
161
  const runtimeApp = runtime.getApp();
162
162
 
163
- // 7b. Initialize permission engine so tool calls aren't blocked
163
+ // 7b. Initialize shared singletons from routes.js so hooks work in standalone mode
164
164
  try {
165
- const { permissionEngine } = await import('./engine/routes.js');
166
- await permissionEngine.setDb(engineDb);
165
+ const routes = await import('./engine/routes.js');
166
+ await routes.permissionEngine.setDb(engineDb);
167
167
  console.log(' Permissions: loaded from DB');
168
+ // Initialize lifecycle singleton so recordLLMUsage and other hooks work
169
+ await routes.lifecycle.setDb(engineDb);
170
+ await routes.lifecycle.loadFromDb();
171
+ console.log(' Hooks lifecycle: initialized');
168
172
  } catch (permErr: any) {
169
- console.warn(` Permissions: failed to load (${permErr.message}) — tools may be blocked`);
173
+ console.warn(` Routes init: failed (${permErr.message}) — some features may not work`);
170
174
  }
171
175
 
172
176
  // 8. Start health check HTTP server
@@ -284,7 +288,73 @@ export async function runAgent(_args: string[]) {
284
288
  console.log(`[onboarding] ✅ Onboarding complete — ${policyNames.length} policies acknowledged`);
285
289
  }
286
290
 
287
- // 11. Send welcome email to manager if configured
291
+ // 11. Auto-setup Gmail signature from org template (BEFORE welcome email so it's included)
292
+ try {
293
+ const orgSettings = await db.getSettings();
294
+ const sigTemplate = (orgSettings as any)?.signatureTemplate;
295
+ const sigEmailConfig = config.emailConfig || {};
296
+ let sigToken = sigEmailConfig.oauthAccessToken;
297
+ if (sigEmailConfig.oauthRefreshToken && sigEmailConfig.oauthClientId) {
298
+ try {
299
+ const tokenUrl = (sigEmailConfig.provider || sigEmailConfig.oauthProvider) === 'google'
300
+ ? 'https://oauth2.googleapis.com/token'
301
+ : 'https://login.microsoftonline.com/common/oauth2/v2.0/token';
302
+ const tokenRes = await fetch(tokenUrl, {
303
+ method: 'POST',
304
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
305
+ body: new URLSearchParams({
306
+ client_id: sigEmailConfig.oauthClientId,
307
+ client_secret: sigEmailConfig.oauthClientSecret,
308
+ refresh_token: sigEmailConfig.oauthRefreshToken,
309
+ grant_type: 'refresh_token',
310
+ }),
311
+ });
312
+ const tokenData = await tokenRes.json() as any;
313
+ if (tokenData.access_token) sigToken = tokenData.access_token;
314
+ } catch {}
315
+ }
316
+ if (sigTemplate && sigToken) {
317
+ const agName = config.displayName || config.name;
318
+ const agRole = config.identity?.role || 'AI Agent';
319
+ const agEmail = config.email?.address || sigEmailConfig?.email || '';
320
+ const companyName = orgSettings?.name || '';
321
+ const logoUrl = orgSettings?.logoUrl || '';
322
+
323
+ const signature = sigTemplate
324
+ .replace(/\{\{name\}\}/g, agName)
325
+ .replace(/\{\{role\}\}/g, agRole)
326
+ .replace(/\{\{email\}\}/g, agEmail)
327
+ .replace(/\{\{company\}\}/g, companyName)
328
+ .replace(/\{\{logo\}\}/g, logoUrl)
329
+ .replace(/\{\{phone\}\}/g, '');
330
+
331
+ const sendAsRes = await fetch('https://gmail.googleapis.com/gmail/v1/users/me/settings/sendAs', {
332
+ headers: { Authorization: `Bearer ${sigToken}` },
333
+ });
334
+ const sendAs = await sendAsRes.json() as any;
335
+ const primary = sendAs.sendAs?.find((s: any) => s.isPrimary) || sendAs.sendAs?.[0];
336
+ if (primary) {
337
+ const patchRes = await fetch(`https://gmail.googleapis.com/gmail/v1/users/me/settings/sendAs/${encodeURIComponent(primary.sendAsEmail)}`, {
338
+ method: 'PATCH',
339
+ headers: { Authorization: `Bearer ${sigToken}`, 'Content-Type': 'application/json' },
340
+ body: JSON.stringify({ signature }),
341
+ });
342
+ if (patchRes.ok) {
343
+ console.log(`[signature] ✅ Gmail signature set for ${primary.sendAsEmail}`);
344
+ } else {
345
+ const errBody = await patchRes.text();
346
+ console.log(`[signature] Failed (${patchRes.status}): ${errBody.slice(0, 200)}`);
347
+ }
348
+ }
349
+ } else {
350
+ if (!sigTemplate) console.log('[signature] No signature template configured');
351
+ if (!sigToken) console.log('[signature] No OAuth token for signature setup');
352
+ }
353
+ } catch (sigErr: any) {
354
+ console.log(`[signature] Skipped: ${sigErr.message}`);
355
+ }
356
+
357
+ // 12. Send welcome email to manager if configured
288
358
  // Manager email can come from config.managerEmail or config.manager.email
289
359
  const managerEmail = (config as any).managerEmail || ((config as any).manager?.type === 'external' ? (config as any).manager.email : null);
290
360
  const emailConfig = (config as any).emailConfig;
@@ -416,66 +486,6 @@ Available tools: gmail_send (to, subject, body) or agenticmail_send (to, subject
416
486
  console.error(`[onboarding] Error: ${err.message}`);
417
487
  }
418
488
 
419
- // 12. Auto-setup Gmail signature from org template
420
- try {
421
- const orgSettings = await db.getSettings();
422
- const sigTemplate = (orgSettings as any)?.signatureTemplate;
423
- const sigEmailConfig = config.emailConfig || {};
424
- // Get a fresh access token for signature setup
425
- let sigToken = sigEmailConfig.oauthAccessToken;
426
- if (sigEmailConfig.oauthRefreshToken && sigEmailConfig.oauthClientId) {
427
- try {
428
- const tokenUrl = (sigEmailConfig.provider || sigEmailConfig.oauthProvider) === 'google'
429
- ? 'https://oauth2.googleapis.com/token'
430
- : 'https://login.microsoftonline.com/common/oauth2/v2.0/token';
431
- const tokenRes = await fetch(tokenUrl, {
432
- method: 'POST',
433
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
434
- body: new URLSearchParams({
435
- client_id: sigEmailConfig.oauthClientId,
436
- client_secret: sigEmailConfig.oauthClientSecret,
437
- refresh_token: sigEmailConfig.oauthRefreshToken,
438
- grant_type: 'refresh_token',
439
- }),
440
- });
441
- const tokenData = await tokenRes.json() as any;
442
- if (tokenData.access_token) sigToken = tokenData.access_token;
443
- } catch {}
444
- }
445
- if (sigTemplate && sigToken) {
446
- const agentName = config.displayName || config.name;
447
- const role = config.identity?.role || 'AI Agent';
448
- const agentEmailAddr = config.email?.address || sigEmailConfig?.email || '';
449
- const companyName = orgSettings?.name || '';
450
- const logoUrl = orgSettings?.logoUrl || '';
451
-
452
- const signature = sigTemplate
453
- .replace(/\{\{name\}\}/g, agentName)
454
- .replace(/\{\{role\}\}/g, role)
455
- .replace(/\{\{email\}\}/g, agentEmailAddr)
456
- .replace(/\{\{company\}\}/g, companyName)
457
- .replace(/\{\{logo\}\}/g, logoUrl)
458
- .replace(/\{\{phone\}\}/g, '');
459
-
460
- // Set Gmail signature via API
461
- const sendAsRes = await fetch('https://gmail.googleapis.com/gmail/v1/users/me/settings/sendAs', {
462
- headers: { Authorization: `Bearer ${sigToken}` },
463
- });
464
- const sendAs = await sendAsRes.json() as any;
465
- const primary = sendAs.sendAs?.find((s: any) => s.isPrimary) || sendAs.sendAs?.[0];
466
- if (primary) {
467
- await fetch(`https://gmail.googleapis.com/gmail/v1/users/me/settings/sendAs/${encodeURIComponent(primary.sendAsEmail)}`, {
468
- method: 'PATCH',
469
- headers: { Authorization: `Bearer ${sigToken}`, 'Content-Type': 'application/json' },
470
- body: JSON.stringify({ signature }),
471
- });
472
- console.log(`[signature] ✅ Gmail signature set for ${primary.sendAsEmail}`);
473
- }
474
- }
475
- } catch (sigErr: any) {
476
- console.log(`[signature] Skipped: ${sigErr.message}`);
477
- }
478
-
479
489
  // 13. Start email inbox polling loop
480
490
  startEmailPolling(AGENT_ID, config, lifecycle, runtime, engineDb, memoryManager);
481
491
  }, 3000);
@@ -689,7 +699,14 @@ FORMATTING RULES (STRICTLY ENFORCED):
689
699
  - Use line breaks between paragraphs, nothing else for formatting
690
700
  - Keep it warm and conversational, not robotic or formatted
691
701
 
692
- CRITICAL: You MUST call gmail_reply EXACTLY ONCE to send your reply. Do NOT call it multiple times. Do NOT just generate text without calling the tool.`;
702
+ CRITICAL: You MUST call gmail_reply EXACTLY ONCE to send your reply. Do NOT call it multiple times. Do NOT just generate text without calling the tool.
703
+
704
+ == TASK MANAGEMENT ==
705
+ When you receive work requests, action items, or follow-ups:
706
+ - Use google_tasks_create to track them (title, notes, due date)
707
+ - Check google_tasks_list at the start of interactions to see pending work
708
+ - Use google_tasks_complete when done with items
709
+ This helps you stay organized and ensures nothing falls through the cracks.`;
693
710
 
694
711
  const session = await runtime.spawnSession({
695
712
  agentId,
@@ -593,6 +593,7 @@ export function createAgentRoutes(opts: {
593
593
  'https://www.googleapis.com/auth/spreadsheets',
594
594
  'https://www.googleapis.com/auth/documents',
595
595
  'https://www.googleapis.com/auth/contacts',
596
+ 'https://www.googleapis.com/auth/tasks',
596
597
  ];
597
598
 
598
599
  if (!oauthClientId) return c.json({ error: 'oauthClientId is required for Google OAuth' }, 400);