@agenticmail/enterprise 0.5.168 → 0.5.169

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.
@@ -6410,6 +6410,227 @@ function ToolSecuritySection(props) {
6410
6410
  // AGENT DETAIL PAGE (Main Orchestrator)
6411
6411
  // ════════════════════════════════════════════════════════════
6412
6412
 
6413
+ // ─── Autonomy Settings Section ──────────────────────────
6414
+
6415
+ function AutonomySection(props) {
6416
+ var agentId = props.agentId;
6417
+ var engineAgent = props.engineAgent;
6418
+ var reload = props.reload;
6419
+ var app = useApp();
6420
+ var toast = app.toast;
6421
+
6422
+ var defaults = {
6423
+ enabled: true,
6424
+ clockEnabled: true,
6425
+ dailyCatchupEnabled: true, dailyCatchupHour: 9, dailyCatchupMinute: 0,
6426
+ weeklyCatchupEnabled: true, weeklyCatchupDay: 1, weeklyCatchupHour: 9, weeklyCatchupMinute: 0,
6427
+ goalCheckEnabled: true, goalCheckHours: [14, 17],
6428
+ knowledgeContribEnabled: true, knowledgeContribDay: 5, knowledgeContribHour: 15,
6429
+ escalationEnabled: true, guardrailEnforcementEnabled: true, driveAccessRequestEnabled: true,
6430
+ };
6431
+
6432
+ var existing = (engineAgent?.config?.autonomy) || {};
6433
+ var _form = useState(Object.assign({}, defaults, existing));
6434
+ var form = _form[0]; var setForm = _form[1];
6435
+ var _saving = useState(false);
6436
+ var saving = _saving[0]; var setSaving = _saving[1];
6437
+ var _dirty = useState(false);
6438
+ var dirty = _dirty[0]; var setDirty = _dirty[1];
6439
+
6440
+ var set = function(key, val) {
6441
+ var u = Object.assign({}, form);
6442
+ u[key] = val;
6443
+ setForm(u);
6444
+ setDirty(true);
6445
+ };
6446
+
6447
+ var save = function() {
6448
+ setSaving(true);
6449
+ var ea = engineAgent || {};
6450
+ var isRunning = ea.state === 'running' || ea.state === 'active' || ea.state === 'degraded';
6451
+ var endpoint = isRunning ? '/agents/' + agentId + '/hot-update' : '/agents/' + agentId + '/config';
6452
+ var method = isRunning ? 'POST' : 'PATCH';
6453
+ engineCall(endpoint, {
6454
+ method: method,
6455
+ body: JSON.stringify({ updates: { autonomy: form }, updatedBy: 'dashboard' })
6456
+ }).then(function() {
6457
+ toast('Autonomy settings saved' + (isRunning ? ' (agent will reload within 10 min)' : ''), 'success');
6458
+ setDirty(false);
6459
+ setSaving(false);
6460
+ if (reload) reload();
6461
+ }).catch(function(err) {
6462
+ toast(err.message, 'error');
6463
+ setSaving(false);
6464
+ });
6465
+ };
6466
+
6467
+ var DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
6468
+
6469
+ var toggleStyle = function(on) {
6470
+ return {
6471
+ width: 40, height: 22, borderRadius: 11,
6472
+ background: on ? 'var(--success)' : 'var(--border)',
6473
+ cursor: 'pointer', position: 'relative', flexShrink: 0,
6474
+ transition: 'background 0.2s', display: 'inline-block',
6475
+ };
6476
+ };
6477
+ var knobStyle = function(on) {
6478
+ return {
6479
+ width: 18, height: 18, borderRadius: '50%', background: '#fff',
6480
+ position: 'absolute', top: 2, left: on ? 20 : 2,
6481
+ transition: 'left 0.2s', boxShadow: '0 1px 3px rgba(0,0,0,0.2)',
6482
+ };
6483
+ };
6484
+
6485
+ var Toggle = function(p) {
6486
+ return h('div', { style: { display: 'flex', alignItems: 'center', gap: 10 } },
6487
+ h('div', { style: toggleStyle(p.value), onClick: function() { set(p.field, !p.value); } },
6488
+ h('div', { style: knobStyle(p.value) })
6489
+ ),
6490
+ h('span', { style: { fontSize: 13, fontWeight: 500 } }, p.label),
6491
+ p.desc && h('span', { style: { fontSize: 11, color: 'var(--text-muted)' } }, p.desc)
6492
+ );
6493
+ };
6494
+
6495
+ var TimeSelect = function(p) {
6496
+ return h('div', { style: { display: 'flex', gap: 8, alignItems: 'center' } },
6497
+ h('select', { className: 'input', style: { width: 80 }, value: p.hour, onChange: function(e) { set(p.hourField, parseInt(e.target.value)); } },
6498
+ Array.from({length: 24}, function(_, i) {
6499
+ var label = i === 0 ? '12 AM' : i < 12 ? i + ' AM' : i === 12 ? '12 PM' : (i - 12) + ' PM';
6500
+ return h('option', { key: i, value: i }, label);
6501
+ })
6502
+ ),
6503
+ h('span', null, ':'),
6504
+ h('select', { className: 'input', style: { width: 65 }, value: p.minute, onChange: function(e) { set(p.minuteField, parseInt(e.target.value)); } },
6505
+ [0, 15, 30, 45].map(function(m) { return h('option', { key: m, value: m }, String(m).padStart(2, '0')); })
6506
+ )
6507
+ );
6508
+ };
6509
+
6510
+ var DaySelect = function(p) {
6511
+ return h('select', { className: 'input', style: { width: 130 }, value: p.value, onChange: function(e) { set(p.field, parseInt(e.target.value)); } },
6512
+ DAYS.map(function(d, i) { return h('option', { key: i, value: i }, d); })
6513
+ );
6514
+ };
6515
+
6516
+ var cardStyle = { marginBottom: 12 };
6517
+ var rowStyle = { display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '10px 16px', borderBottom: '1px solid var(--border)' };
6518
+ var configRow = { display: 'flex', gap: 12, alignItems: 'center', padding: '8px 16px 8px 48px', borderBottom: '1px solid var(--border)', fontSize: 13 };
6519
+
6520
+ return h(Fragment, null,
6521
+ // Header
6522
+ h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 } },
6523
+ h('div', null,
6524
+ h('div', { style: { fontSize: 15, fontWeight: 600 } }, 'Agent Autonomy Settings'),
6525
+ h('div', { style: { fontSize: 12, color: 'var(--text-muted)' } }, 'Configure automated behaviors — all times use agent timezone')
6526
+ ),
6527
+ h('div', { style: { display: 'flex', gap: 8 } },
6528
+ dirty && h('span', { style: { fontSize: 11, color: 'var(--warning)', alignSelf: 'center' } }, 'Unsaved changes'),
6529
+ h('button', { className: 'btn btn-primary btn-sm', disabled: !dirty || saving, onClick: save }, saving ? 'Saving...' : 'Save')
6530
+ )
6531
+ ),
6532
+
6533
+ // Master switch
6534
+ h('div', { className: 'card', style: cardStyle },
6535
+ h('div', { style: rowStyle },
6536
+ h(Toggle, { field: 'enabled', value: form.enabled, label: 'Enable Autonomy System', desc: 'Master switch for all automated agent behaviors' })
6537
+ )
6538
+ ),
6539
+
6540
+ // Clock In/Out
6541
+ h('div', { className: 'card', style: Object.assign({}, cardStyle, { opacity: form.enabled ? 1 : 0.5 }) },
6542
+ h('div', { style: rowStyle },
6543
+ h(Toggle, { field: 'clockEnabled', value: form.clockEnabled, label: 'Auto Clock-In/Out', desc: 'Clock in/out based on work schedule' })
6544
+ ),
6545
+ form.clockEnabled && h('div', { style: configRow },
6546
+ h('span', { style: { color: 'var(--text-muted)' } }, 'Uses times from the Workforce tab schedule')
6547
+ )
6548
+ ),
6549
+
6550
+ // Daily Catchup
6551
+ h('div', { className: 'card', style: Object.assign({}, cardStyle, { opacity: form.enabled ? 1 : 0.5 }) },
6552
+ h('div', { style: rowStyle },
6553
+ h(Toggle, { field: 'dailyCatchupEnabled', value: form.dailyCatchupEnabled, label: 'Daily Manager Catchup', desc: 'Email summary to manager each workday' })
6554
+ ),
6555
+ form.dailyCatchupEnabled && h('div', { style: configRow },
6556
+ h('span', null, 'Send at'),
6557
+ h(TimeSelect, { hour: form.dailyCatchupHour, minute: form.dailyCatchupMinute, hourField: 'dailyCatchupHour', minuteField: 'dailyCatchupMinute' })
6558
+ )
6559
+ ),
6560
+
6561
+ // Weekly Catchup
6562
+ h('div', { className: 'card', style: Object.assign({}, cardStyle, { opacity: form.enabled ? 1 : 0.5 }) },
6563
+ h('div', { style: rowStyle },
6564
+ h(Toggle, { field: 'weeklyCatchupEnabled', value: form.weeklyCatchupEnabled, label: 'Weekly Manager Catchup', desc: 'Broader summary + goals, replaces daily on chosen day' })
6565
+ ),
6566
+ form.weeklyCatchupEnabled && h('div', { style: configRow },
6567
+ h('span', null, 'Send on'),
6568
+ h(DaySelect, { value: form.weeklyCatchupDay, field: 'weeklyCatchupDay' }),
6569
+ h('span', null, 'at'),
6570
+ h(TimeSelect, { hour: form.weeklyCatchupHour, minute: form.weeklyCatchupMinute, hourField: 'weeklyCatchupHour', minuteField: 'weeklyCatchupMinute' })
6571
+ )
6572
+ ),
6573
+
6574
+ // Goal Check
6575
+ h('div', { className: 'card', style: Object.assign({}, cardStyle, { opacity: form.enabled ? 1 : 0.5 }) },
6576
+ h('div', { style: rowStyle },
6577
+ h(Toggle, { field: 'goalCheckEnabled', value: form.goalCheckEnabled, label: 'Goal Progress Checks', desc: 'Reviews Google Tasks at set hours (last = end-of-day review)' })
6578
+ ),
6579
+ form.goalCheckEnabled && h('div', { style: configRow },
6580
+ h('span', null, 'Check at hours:'),
6581
+ h('input', { className: 'input', style: { width: 150 },
6582
+ value: (form.goalCheckHours || [14, 17]).join(', '),
6583
+ placeholder: '14, 17',
6584
+ onChange: function(e) {
6585
+ var hrs = e.target.value.split(',').map(function(s) { return parseInt(s.trim()); }).filter(function(n) { return !isNaN(n) && n >= 0 && n <= 23; });
6586
+ set('goalCheckHours', hrs.length > 0 ? hrs : [14, 17]);
6587
+ }
6588
+ }),
6589
+ h('span', { style: { fontSize: 11, color: 'var(--text-muted)' } }, '(24h format, comma-separated)')
6590
+ )
6591
+ ),
6592
+
6593
+ // Knowledge Contribution
6594
+ h('div', { className: 'card', style: Object.assign({}, cardStyle, { opacity: form.enabled ? 1 : 0.5 }) },
6595
+ h('div', { style: rowStyle },
6596
+ h(Toggle, { field: 'knowledgeContribEnabled', value: form.knowledgeContribEnabled, label: 'Weekly Knowledge Contribution', desc: 'Agent reviews learnings and contributes to role-based knowledge base' })
6597
+ ),
6598
+ form.knowledgeContribEnabled && h('div', { style: configRow },
6599
+ h('span', null, 'Contribute on'),
6600
+ h(DaySelect, { value: form.knowledgeContribDay, field: 'knowledgeContribDay' }),
6601
+ h('span', null, 'at'),
6602
+ h('select', { className: 'input', style: { width: 80 }, value: form.knowledgeContribHour, onChange: function(e) { set('knowledgeContribHour', parseInt(e.target.value)); } },
6603
+ Array.from({length: 24}, function(_, i) {
6604
+ var label = i === 0 ? '12 AM' : i < 12 ? i + ' AM' : i === 12 ? '12 PM' : (i - 12) + ' PM';
6605
+ return h('option', { key: i, value: i }, label);
6606
+ })
6607
+ )
6608
+ )
6609
+ ),
6610
+
6611
+ // Smart Escalation
6612
+ h('div', { className: 'card', style: Object.assign({}, cardStyle, { opacity: form.enabled ? 1 : 0.5 }) },
6613
+ h('div', { style: rowStyle },
6614
+ h(Toggle, { field: 'escalationEnabled', value: form.escalationEnabled, label: 'Smart Answer Escalation', desc: 'Search memory → Drive → escalate to manager when unsure' })
6615
+ )
6616
+ ),
6617
+
6618
+ // Guardrail Enforcement
6619
+ h('div', { className: 'card', style: Object.assign({}, cardStyle, { opacity: form.enabled ? 1 : 0.5 }) },
6620
+ h('div', { style: rowStyle },
6621
+ h(Toggle, { field: 'guardrailEnforcementEnabled', value: form.guardrailEnforcementEnabled, label: 'Runtime Guardrail Enforcement', desc: 'Evaluate guardrail rules on inbound emails and tool calls' })
6622
+ )
6623
+ ),
6624
+
6625
+ // Drive Access Requests
6626
+ h('div', { className: 'card', style: Object.assign({}, cardStyle, { opacity: form.enabled ? 1 : 0.5 }) },
6627
+ h('div', { style: rowStyle },
6628
+ h(Toggle, { field: 'driveAccessRequestEnabled', value: form.driveAccessRequestEnabled, label: 'Drive Access Requests', desc: 'When agent cannot access a file, it requests access from manager instead of failing silently' })
6629
+ )
6630
+ )
6631
+ );
6632
+ }
6633
+
6413
6634
  function AgentDetailPage(props) {
6414
6635
  var agentId = props.agentId;
6415
6636
  var onBack = props.onBack;
@@ -6430,8 +6651,8 @@ function AgentDetailPage(props) {
6430
6651
  var _agents = useState([]);
6431
6652
  var agents = _agents[0]; var setAgents = _agents[1];
6432
6653
 
6433
- var TABS = ['overview', 'personal', 'email', 'configuration', 'manager', 'tools', 'skills', 'permissions', 'activity', 'communication', 'workforce', 'memory', 'guardrails', 'budget', 'tool-security', 'deployment'];
6434
- var TAB_LABELS = { 'tool-security': 'Tool Security', 'manager': 'Manager & Catch-Up', 'email': 'Email', 'tools': 'Tools' };
6654
+ var TABS = ['overview', 'personal', 'email', 'configuration', 'manager', 'tools', 'skills', 'permissions', 'activity', 'communication', 'workforce', 'memory', 'guardrails', 'autonomy', 'budget', 'tool-security', 'deployment'];
6655
+ var TAB_LABELS = { 'tool-security': 'Tool Security', 'manager': 'Manager & Catch-Up', 'email': 'Email', 'tools': 'Tools', 'autonomy': 'Autonomy' };
6435
6656
 
6436
6657
  var load = function() {
6437
6658
  setLoading(true);
@@ -6563,6 +6784,7 @@ function AgentDetailPage(props) {
6563
6784
  tab === 'workforce' && h(WorkforceSection, { agentId: agentId }),
6564
6785
  tab === 'memory' && h(MemorySection, { agentId: agentId }),
6565
6786
  tab === 'guardrails' && h(GuardrailsSection, { agentId: agentId, agents: agents }),
6787
+ tab === 'autonomy' && h(AutonomySection, { agentId: agentId, engineAgent: engineAgent, reload: load }),
6566
6788
  tab === 'budget' && h(BudgetSection, { agentId: agentId }),
6567
6789
  tab === 'tool-security' && h(ToolSecuritySection, { agentId: agentId }),
6568
6790
  tab === 'deployment' && h(DeploymentSection, { agentId: agentId, engineAgent: engineAgent, agent: agent, reload: load, onBack: onBack })
@@ -14,6 +14,67 @@ import type { EngineDatabase } from './db-adapter.js';
14
14
 
15
15
  // ─── Types ──────────────────────────────────────────────
16
16
 
17
+ /**
18
+ * Autonomy settings — all configurable via dashboard.
19
+ * Stored in managed_agents.config.autonomy JSON field.
20
+ */
21
+ export interface AutonomySettings {
22
+ /** Master switch — disables all autonomy features */
23
+ enabled: boolean;
24
+
25
+ /** Auto clock-in/out based on work schedule */
26
+ clockEnabled: boolean;
27
+
28
+ /** Daily catchup email to manager */
29
+ dailyCatchupEnabled: boolean;
30
+ dailyCatchupHour: number; // 0-23, default 9
31
+ dailyCatchupMinute: number; // 0-59, default 0
32
+
33
+ /** Weekly catchup email (broader summary + goals) */
34
+ weeklyCatchupEnabled: boolean;
35
+ weeklyCatchupDay: number; // 0=Sun..6=Sat, default 1 (Monday)
36
+ weeklyCatchupHour: number; // default 9
37
+ weeklyCatchupMinute: number; // default 0
38
+
39
+ /** Goal progress check */
40
+ goalCheckEnabled: boolean;
41
+ goalCheckHours: number[]; // hours of day to check, default [14, 17]
42
+
43
+ /** Knowledge contribution */
44
+ knowledgeContribEnabled: boolean;
45
+ knowledgeContribDay: number; // 0=Sun..6=Sat, default 5 (Friday)
46
+ knowledgeContribHour: number; // default 15
47
+
48
+ /** Smart escalation */
49
+ escalationEnabled: boolean;
50
+
51
+ /** Guardrail enforcement at runtime */
52
+ guardrailEnforcementEnabled: boolean;
53
+
54
+ /** Drive access request on 403 */
55
+ driveAccessRequestEnabled: boolean;
56
+ }
57
+
58
+ export const DEFAULT_AUTONOMY_SETTINGS: AutonomySettings = {
59
+ enabled: true,
60
+ clockEnabled: true,
61
+ dailyCatchupEnabled: true,
62
+ dailyCatchupHour: 9,
63
+ dailyCatchupMinute: 0,
64
+ weeklyCatchupEnabled: true,
65
+ weeklyCatchupDay: 1,
66
+ weeklyCatchupHour: 9,
67
+ weeklyCatchupMinute: 0,
68
+ goalCheckEnabled: true,
69
+ goalCheckHours: [14, 17],
70
+ knowledgeContribEnabled: true,
71
+ knowledgeContribDay: 5,
72
+ knowledgeContribHour: 15,
73
+ escalationEnabled: true,
74
+ guardrailEnforcementEnabled: true,
75
+ driveAccessRequestEnabled: true,
76
+ };
77
+
17
78
  export interface AutonomyConfig {
18
79
  agentId: string;
19
80
  orgId: string;
@@ -27,6 +88,7 @@ export interface AutonomyConfig {
27
88
  engineDb: EngineDatabase;
28
89
  memoryManager?: any;
29
90
  lifecycle?: any;
91
+ settings?: Partial<AutonomySettings>;
30
92
  }
31
93
 
32
94
  export interface ClockState {
@@ -49,6 +111,7 @@ interface CatchupData {
49
111
 
50
112
  export class AgentAutonomyManager {
51
113
  private config: AutonomyConfig;
114
+ private settings: AutonomySettings;
52
115
  private clockState: ClockState = { clockedIn: false };
53
116
  private schedulerInterval?: NodeJS.Timeout;
54
117
  private catchupInterval?: NodeJS.Timeout;
@@ -57,29 +120,68 @@ export class AgentAutonomyManager {
57
120
 
58
121
  constructor(config: AutonomyConfig) {
59
122
  this.config = config;
123
+ this.settings = { ...DEFAULT_AUTONOMY_SETTINGS, ...(config.settings || {}) };
60
124
  }
61
125
 
126
+ /** Reload settings from DB (called when config changes via dashboard) */
127
+ async reloadSettings(): Promise<void> {
128
+ try {
129
+ const rows = await this.config.engineDb.query<any>(
130
+ `SELECT config FROM managed_agents WHERE id = $1`, [this.config.agentId]
131
+ );
132
+ if (rows?.[0]?.config) {
133
+ const cfg = typeof rows[0].config === 'string' ? JSON.parse(rows[0].config) : rows[0].config;
134
+ if (cfg.autonomy) {
135
+ this.settings = { ...DEFAULT_AUTONOMY_SETTINGS, ...cfg.autonomy };
136
+ console.log('[autonomy] Settings reloaded from DB');
137
+ }
138
+ }
139
+ } catch (err: any) {
140
+ console.warn(`[autonomy] Failed to reload settings: ${err.message}`);
141
+ }
142
+ }
143
+
144
+ getSettings(): AutonomySettings { return { ...this.settings }; }
145
+
62
146
  async start(): Promise<void> {
147
+ if (!this.settings.enabled) {
148
+ console.log('[autonomy] Disabled via settings, skipping');
149
+ return;
150
+ }
151
+
63
152
  console.log('[autonomy] Starting agent autonomy system...');
64
153
 
154
+ // Load latest settings from DB
155
+ await this.reloadSettings();
156
+
65
157
  // Check clock state on boot
66
- await this.checkClockState();
158
+ if (this.settings.clockEnabled) await this.checkClockState();
67
159
 
68
160
  // Schedule checker runs every minute
69
- this.schedulerInterval = setInterval(() => this.checkClockState(), 60_000);
161
+ this.schedulerInterval = setInterval(() => {
162
+ if (this.settings.clockEnabled) this.checkClockState();
163
+ }, 60_000);
70
164
 
71
- // Catchup email checker runs every 30 minutes (checks if it's time for daily/weekly)
72
- this.catchupInterval = setInterval(() => this.checkCatchupSchedule(), 30 * 60_000);
73
- // Check immediately on start (after 30s delay for systems to settle)
165
+ // Catchup email checker runs every 15 minutes
166
+ this.catchupInterval = setInterval(() => this.checkCatchupSchedule(), 15 * 60_000);
74
167
  setTimeout(() => this.checkCatchupSchedule(), 30_000);
75
168
 
76
169
  // Knowledge contribution checker runs every hour
77
170
  this.knowledgeInterval = setInterval(() => this.checkKnowledgeContribution(), 60 * 60_000);
78
171
 
79
- // Goal progress checker runs every 2 hours
80
- this.goalCheckInterval = setInterval(() => this.checkGoalProgress(), 2 * 60 * 60_000);
172
+ // Goal progress checker runs every 30 minutes (more granular than 2h)
173
+ this.goalCheckInterval = setInterval(() => this.checkGoalProgress(), 30 * 60_000);
174
+
175
+ // Reload settings from DB every 10 minutes (picks up dashboard changes)
176
+ setInterval(() => this.reloadSettings(), 10 * 60_000);
81
177
 
82
- console.log('[autonomy] System started — clock, catchup, knowledge, goals active');
178
+ const features = [];
179
+ if (this.settings.clockEnabled) features.push('clock');
180
+ if (this.settings.dailyCatchupEnabled) features.push('daily-catchup@' + this.settings.dailyCatchupHour + ':' + String(this.settings.dailyCatchupMinute).padStart(2, '0'));
181
+ if (this.settings.weeklyCatchupEnabled) features.push('weekly-catchup@' + ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'][this.settings.weeklyCatchupDay]);
182
+ if (this.settings.goalCheckEnabled) features.push('goals@' + this.settings.goalCheckHours.join(','));
183
+ if (this.settings.knowledgeContribEnabled) features.push('knowledge@' + ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'][this.settings.knowledgeContribDay]);
184
+ console.log('[autonomy] Active features: ' + features.join(', '));
83
185
  }
84
186
 
85
187
  stop(): void {
@@ -187,15 +289,21 @@ export class AgentAutonomyManager {
187
289
  const localTime = new Date(now.toLocaleString('en-US', { timeZone: tz }));
188
290
  const hour = localTime.getHours();
189
291
  const minute = localTime.getMinutes();
190
- const dayOfWeek = localTime.getDay(); // 0=Sun, 1=Mon
292
+ const dayOfWeek = localTime.getDay();
191
293
  const dateStr = localTime.toISOString().split('T')[0];
192
294
 
193
- // Daily catchup: 9:00 AM on workdays (within first 30 min window)
194
- const isDailyCatchupTime = hour === 9 && minute < 30;
195
- // Weekly catchup: Monday 9:00 AM (within first 30 min window)
196
- const isWeeklyCatchupTime = dayOfWeek === 1 && hour === 9 && minute < 30;
295
+ // Weekly catchup: configurable day + time
296
+ const isWeeklyCatchupTime = this.settings.weeklyCatchupEnabled
297
+ && dayOfWeek === this.settings.weeklyCatchupDay
298
+ && hour === this.settings.weeklyCatchupHour
299
+ && minute >= this.settings.weeklyCatchupMinute && minute < this.settings.weeklyCatchupMinute + 15;
300
+
301
+ // Daily catchup: configurable time (skip if weekly already covers it)
302
+ const isDailyCatchupTime = this.settings.dailyCatchupEnabled
303
+ && hour === this.settings.dailyCatchupHour
304
+ && minute >= this.settings.dailyCatchupMinute && minute < this.settings.dailyCatchupMinute + 15;
197
305
 
198
- if (!isDailyCatchupTime) return;
306
+ if (!isDailyCatchupTime && !isWeeklyCatchupTime) return;
199
307
 
200
308
  // Check if we already sent today's catchup
201
309
  const catchupKey = isWeeklyCatchupTime ? `weekly_catchup_${dateStr}` : `daily_catchup_${dateStr}`;
@@ -330,15 +438,16 @@ Available tools: gmail_send (to, subject, body), google_tasks_create (listId, ti
330
438
  // ─── 3. Goal Setting & Auto-Reminders ──────────────
331
439
 
332
440
  private async checkGoalProgress(): Promise<void> {
333
- if (!this.config.runtime || !this.clockState.clockedIn) return;
441
+ if (!this.settings.goalCheckEnabled || !this.config.runtime || !this.clockState.clockedIn) return;
334
442
 
335
443
  const now = new Date();
336
444
  const tz = this.config.timezone || 'UTC';
337
445
  const localTime = new Date(now.toLocaleString('en-US', { timeZone: tz }));
338
446
  const hour = localTime.getHours();
339
447
 
340
- // Check goals at 2 PM and 5 PM (end-of-day review)
341
- if (hour !== 14 && hour !== 17) return;
448
+ // Check goals at configured hours
449
+ const goalHours = this.settings.goalCheckHours || [14, 17];
450
+ if (!goalHours.includes(hour)) return;
342
451
 
343
452
  const dateStr = localTime.toISOString().split('T')[0];
344
453
  const checkKey = `goal_check_${dateStr}_${hour}`;
@@ -348,7 +457,8 @@ Available tools: gmail_send (to, subject, body), google_tasks_create (listId, ti
348
457
  console.log(`[autonomy] Goal progress check at ${hour}:00`);
349
458
 
350
459
  try {
351
- const prompt = hour === 17
460
+ const isEndOfDay = hour === Math.max(...goalHours);
461
+ const prompt = isEndOfDay
352
462
  ? `It's end of day. Review your goals and tasks:
353
463
  1. Call google_tasks_list to see your current tasks
354
464
  2. Review what you completed today
@@ -365,7 +475,7 @@ Available tools: gmail_send (to, subject, body), google_tasks_create (listId, ti
365
475
  const session = await this.config.runtime.spawnSession({
366
476
  agentId: this.config.agentId,
367
477
  message: prompt,
368
- systemPrompt: `You are ${this.config.agentName}, a ${this.config.role}. You are doing a ${hour === 17 ? 'end-of-day' : 'mid-day'} goal review. Be thorough but efficient.`,
478
+ systemPrompt: `You are ${this.config.agentName}, a ${this.config.role}. You are doing a ${isEndOfDay ? 'end-of-day' : 'mid-day'} goal review. Be thorough but efficient.`,
369
479
  });
370
480
  console.log(`[autonomy] ✅ Goal check session ${session.id} created`);
371
481
  await this.setMemoryFlag(checkKey);
@@ -377,17 +487,17 @@ Available tools: gmail_send (to, subject, body), google_tasks_create (listId, ti
377
487
  // ─── 4. Knowledge Contribution (Friday) ────────────
378
488
 
379
489
  private async checkKnowledgeContribution(): Promise<void> {
380
- if (!this.config.runtime || !this.clockState.clockedIn) return;
490
+ if (!this.settings.knowledgeContribEnabled || !this.config.runtime || !this.clockState.clockedIn) return;
381
491
 
382
492
  const now = new Date();
383
493
  const tz = this.config.timezone || 'UTC';
384
494
  const localTime = new Date(now.toLocaleString('en-US', { timeZone: tz }));
385
- const dayOfWeek = localTime.getDay(); // 5=Friday
495
+ const dayOfWeek = localTime.getDay();
386
496
  const hour = localTime.getHours();
387
497
  const dateStr = localTime.toISOString().split('T')[0];
388
498
 
389
- // Friday at 3 PM
390
- if (dayOfWeek !== 5 || hour !== 15) return;
499
+ // Configurable day and hour
500
+ if (dayOfWeek !== this.settings.knowledgeContribDay || hour !== this.settings.knowledgeContribHour) return;
391
501
 
392
502
  const contribKey = `knowledge_contribution_${dateStr}`;
393
503
  const alreadyDone = await this.checkMemoryFlag(contribKey);