@conversionpros/aiva 1.0.0

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 (152) hide show
  1. package/README.md +148 -0
  2. package/auto-deploy.js +190 -0
  3. package/bin/aiva.js +81 -0
  4. package/cli-sync.js +126 -0
  5. package/d2a-prompt-template.txt +106 -0
  6. package/diagnostics-api.js +304 -0
  7. package/docs/ara-dedup-fix-scope.md +112 -0
  8. package/docs/ara-fix-round2-scope.md +61 -0
  9. package/docs/ara-greeting-fix-scope.md +70 -0
  10. package/docs/calendar-date-fix-scope.md +28 -0
  11. package/docs/getting-started.md +115 -0
  12. package/docs/network-architecture-rollout-scope.md +43 -0
  13. package/docs/scope-google-oauth-integration.md +351 -0
  14. package/docs/settings-page-scope.md +50 -0
  15. package/docs/xai-imagine-scope.md +116 -0
  16. package/docs/xai-voice-integration-scope.md +115 -0
  17. package/docs/xai-voice-tools-scope.md +165 -0
  18. package/email-router.js +512 -0
  19. package/follow-up-handler.js +606 -0
  20. package/gateway-monitor.js +158 -0
  21. package/google-email.js +379 -0
  22. package/google-oauth.js +310 -0
  23. package/grok-imagine.js +97 -0
  24. package/health-reporter.js +287 -0
  25. package/invisible-prefix-base.txt +206 -0
  26. package/invisible-prefix-owner.txt +26 -0
  27. package/invisible-prefix-slim.txt +10 -0
  28. package/invisible-prefix.txt +43 -0
  29. package/knowledge-base.js +472 -0
  30. package/lib/cli.js +19 -0
  31. package/lib/config.js +124 -0
  32. package/lib/health.js +57 -0
  33. package/lib/process.js +207 -0
  34. package/lib/server.js +42 -0
  35. package/lib/setup.js +472 -0
  36. package/meta-capi.js +206 -0
  37. package/meta-leads.js +411 -0
  38. package/notion-oauth.js +323 -0
  39. package/package.json +61 -0
  40. package/public/agent-config.html +241 -0
  41. package/public/aiva-avatar-anime.png +0 -0
  42. package/public/css/docs.css.bak +688 -0
  43. package/public/css/onboarding.css +543 -0
  44. package/public/diagrams/claude-subscription-pool.html +329 -0
  45. package/public/diagrams/claude-subscription-pool.png +0 -0
  46. package/public/docs-icon.png +0 -0
  47. package/public/escalation.html +237 -0
  48. package/public/group-config.html +300 -0
  49. package/public/icon-192.png +0 -0
  50. package/public/icon-512.png +0 -0
  51. package/public/icons/agents.svg +1 -0
  52. package/public/icons/attach.svg +1 -0
  53. package/public/icons/characters.svg +1 -0
  54. package/public/icons/chat.svg +1 -0
  55. package/public/icons/docs.svg +1 -0
  56. package/public/icons/heartbeat.svg +1 -0
  57. package/public/icons/messages.svg +1 -0
  58. package/public/icons/mic.svg +1 -0
  59. package/public/icons/notes.svg +1 -0
  60. package/public/icons/settings.svg +1 -0
  61. package/public/icons/tasks.svg +1 -0
  62. package/public/images/onboarding/p0-communication-layer.png +0 -0
  63. package/public/images/onboarding/p0-infinite-surface.png +0 -0
  64. package/public/images/onboarding/p0-learning-model.png +0 -0
  65. package/public/images/onboarding/p0-meet-aiva.png +0 -0
  66. package/public/images/onboarding/p4-contact-intelligence.png +0 -0
  67. package/public/images/onboarding/p4-context-compounds.png +0 -0
  68. package/public/images/onboarding/p4-message-router.png +0 -0
  69. package/public/images/onboarding/p4-per-contact-rules.png +0 -0
  70. package/public/images/onboarding/p4-send-messages.png +0 -0
  71. package/public/images/onboarding/p6-be-precise.png +0 -0
  72. package/public/images/onboarding/p6-review-escalations.png +0 -0
  73. package/public/images/onboarding/p6-voice-input.png +0 -0
  74. package/public/images/onboarding/p7-completion.png +0 -0
  75. package/public/index.html +11594 -0
  76. package/public/js/onboarding.js +699 -0
  77. package/public/manifest.json +24 -0
  78. package/public/messages-v2.html +2824 -0
  79. package/public/permission-approve.html.bak +107 -0
  80. package/public/permissions.html +150 -0
  81. package/public/styles/design-system.css +68 -0
  82. package/router-db.js +604 -0
  83. package/router-utils.js +28 -0
  84. package/router-v2/adapters/imessage.js +191 -0
  85. package/router-v2/adapters/quo.js +82 -0
  86. package/router-v2/adapters/whatsapp.js +192 -0
  87. package/router-v2/contact-manager.js +234 -0
  88. package/router-v2/conversation-engine.js +498 -0
  89. package/router-v2/data/knowledge-base.json +176 -0
  90. package/router-v2/data/router-v2.db +0 -0
  91. package/router-v2/data/router-v2.db-shm +0 -0
  92. package/router-v2/data/router-v2.db-wal +0 -0
  93. package/router-v2/data/router.db +0 -0
  94. package/router-v2/db.js +457 -0
  95. package/router-v2/escalation-bridge.js +540 -0
  96. package/router-v2/follow-up-engine.js +347 -0
  97. package/router-v2/index.js +441 -0
  98. package/router-v2/ingestion.js +213 -0
  99. package/router-v2/knowledge-base.js +231 -0
  100. package/router-v2/lead-qualifier.js +152 -0
  101. package/router-v2/learning-loop.js +202 -0
  102. package/router-v2/outbound-sender.js +160 -0
  103. package/router-v2/package.json +13 -0
  104. package/router-v2/permission-gate.js +86 -0
  105. package/router-v2/playbook.js +177 -0
  106. package/router-v2/prompts/base.js +52 -0
  107. package/router-v2/prompts/first-contact.js +38 -0
  108. package/router-v2/prompts/lead-qualification.js +37 -0
  109. package/router-v2/prompts/scheduling.js +72 -0
  110. package/router-v2/prompts/style-overrides.js +22 -0
  111. package/router-v2/scheduler.js +301 -0
  112. package/router-v2/scripts/migrate-v1-to-v2.js +215 -0
  113. package/router-v2/scripts/seed-faq.js +67 -0
  114. package/router-v2/seed-knowledge-base.js +39 -0
  115. package/router-v2/utils/ai.js +129 -0
  116. package/router-v2/utils/phone.js +52 -0
  117. package/router-v2/utils/response-validator.js +98 -0
  118. package/router-v2/utils/sanitize.js +222 -0
  119. package/router.js +5005 -0
  120. package/routes/google-calendar.js +186 -0
  121. package/scripts/deploy.sh +62 -0
  122. package/scripts/macos-calendar.sh +232 -0
  123. package/scripts/onboard-device.sh +466 -0
  124. package/server.js +5131 -0
  125. package/start.sh +24 -0
  126. package/templates/AGENTS.md +548 -0
  127. package/templates/IDENTITY.md +15 -0
  128. package/templates/docs-agents.html +132 -0
  129. package/templates/docs-app.html +130 -0
  130. package/templates/docs-home.html +83 -0
  131. package/templates/docs-imessage.html +121 -0
  132. package/templates/docs-tasks.html +123 -0
  133. package/templates/docs-tips.html +175 -0
  134. package/templates/getting-started.html +809 -0
  135. package/templates/invisible-prefix-base.txt +171 -0
  136. package/templates/invisible-prefix-owner.txt +282 -0
  137. package/templates/invisible-prefix.txt +338 -0
  138. package/templates/manifest.json +61 -0
  139. package/templates/memory-org/clients.md +7 -0
  140. package/templates/memory-org/credentials.md +9 -0
  141. package/templates/memory-org/devices.md +7 -0
  142. package/templates/updates.html +464 -0
  143. package/templates/workspace/AGENTS.md.tmpl +161 -0
  144. package/templates/workspace/HEARTBEAT.md.tmpl +17 -0
  145. package/templates/workspace/IDENTITY.md.tmpl +15 -0
  146. package/templates/workspace/MEMORY.md.tmpl +16 -0
  147. package/templates/workspace/SOUL.md.tmpl +51 -0
  148. package/templates/workspace/USER.md.tmpl +25 -0
  149. package/tts-proxy.js +96 -0
  150. package/voice-call-local.js +731 -0
  151. package/voice-call.js +732 -0
  152. package/wa-listener.js +354 -0
@@ -0,0 +1,304 @@
1
+ /**
2
+ * AIVA Remote Diagnostics API
3
+ * Port 3850 — exposes device health, logs, errors, system info
4
+ * Authenticated via DIAG_TOKEN (Bearer token), except /health
5
+ */
6
+
7
+ const http = require('http');
8
+ const fs = require('fs');
9
+ const os = require('os');
10
+ const path = require('path');
11
+ const { execSync } = require('child_process');
12
+ const url = require('url');
13
+
14
+ const PORT = 3850;
15
+ const DIAG_TOKEN = process.env.DIAG_TOKEN || '5f734d1d83211f8fff8f21b53e1c42d9bc27ad7bd47ae1ebc9b72fd9ef3c9281';
16
+ const HOME = os.homedir();
17
+ const AIVA_DIR = path.join(HOME, '.openclaw', 'workspace', 'aiva-tasks');
18
+ const startTime = Date.now();
19
+
20
+ // --- Helpers ---
21
+
22
+ function readLastLines(filePath, n = 100) {
23
+ try {
24
+ const content = fs.readFileSync(filePath, 'utf8');
25
+ const lines = content.split('\n');
26
+ return lines.slice(-n).join('\n');
27
+ } catch (e) {
28
+ return null;
29
+ }
30
+ }
31
+
32
+ function sendJSON(res, statusCode, data) {
33
+ res.writeHead(statusCode, {
34
+ 'Content-Type': 'application/json',
35
+ 'Access-Control-Allow-Origin': '*',
36
+ 'Access-Control-Allow-Headers': 'Authorization, Content-Type',
37
+ });
38
+ res.end(JSON.stringify(data));
39
+ }
40
+
41
+ function authenticate(req) {
42
+ const auth = req.headers['authorization'];
43
+ if (!auth || !auth.startsWith('Bearer ')) return false;
44
+ return auth.slice(7) === DIAG_TOKEN;
45
+ }
46
+
47
+ function safeExec(cmd) {
48
+ try {
49
+ return execSync(cmd, { encoding: 'utf8', timeout: 10000 }).trim();
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
54
+
55
+ // --- Route Handlers ---
56
+
57
+ function handleHealth(req, res) {
58
+ sendJSON(res, 200, {
59
+ status: 'ok',
60
+ uptime: Math.floor((Date.now() - startTime) / 1000),
61
+ hostname: os.hostname(),
62
+ timestamp: new Date().toISOString(),
63
+ });
64
+ }
65
+
66
+ function handleLogs(req, res, query) {
67
+ const lines = parseInt(query.lines) || 100;
68
+ const type = query.type || 'out'; // out | err
69
+
70
+ // Try PM2 logs first
71
+ const pm2LogDir = path.join(HOME, '.pm2', 'logs');
72
+ const suffix = type === 'err' ? 'error' : 'out';
73
+ const pm2File = path.join(pm2LogDir, `aiva-app-${suffix}.log`);
74
+
75
+ let content = readLastLines(pm2File, lines);
76
+ let source = 'pm2';
77
+
78
+ if (!content) {
79
+ // Try LaunchAgent logs
80
+ const laFile = type === 'err'
81
+ ? path.join(HOME, 'Library', 'Logs', 'aiva-app-stderr.log')
82
+ : path.join(HOME, 'Library', 'Logs', 'aiva-app-stdout.log');
83
+ content = readLastLines(laFile, lines);
84
+ source = 'launchagent';
85
+ }
86
+
87
+ sendJSON(res, 200, {
88
+ source,
89
+ type,
90
+ lines: lines,
91
+ content: content || 'No log file found',
92
+ });
93
+ }
94
+
95
+ function handleErrors(req, res) {
96
+ // Parse PM2 error log and out log for error patterns
97
+ const logFiles = [
98
+ path.join(HOME, '.pm2', 'logs', 'aiva-app-error.log'),
99
+ path.join(HOME, '.pm2', 'logs', 'aiva-app-out.log'),
100
+ ];
101
+
102
+ const errorPattern = /error|Error|ERROR|TypeError|ReferenceError|SyntaxError|ENOENT|ECONNREFUSED|UnhandledPromise|stack trace/i;
103
+ const now = Date.now();
104
+ const buckets = { '1h': 0, '6h': 0, '24h': 0 };
105
+ const recentErrors = [];
106
+
107
+ for (const file of logFiles) {
108
+ let content;
109
+ try {
110
+ content = fs.readFileSync(file, 'utf8');
111
+ } catch { continue; }
112
+
113
+ const lines = content.split('\n');
114
+ for (const line of lines) {
115
+ if (!errorPattern.test(line)) continue;
116
+
117
+ // Try to extract timestamp - PM2 logs often have ISO timestamps
118
+ const tsMatch = line.match(/(\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2})/);
119
+ if (tsMatch) {
120
+ const ts = new Date(tsMatch[1]).getTime();
121
+ if (isNaN(ts)) continue;
122
+ const age = now - ts;
123
+ if (age < 3600000) buckets['1h']++;
124
+ if (age < 21600000) buckets['6h']++;
125
+ if (age < 86400000) buckets['24h']++;
126
+ if (age < 3600000 && recentErrors.length < 20) {
127
+ recentErrors.push(line.trim().slice(0, 200));
128
+ }
129
+ } else {
130
+ // No timestamp - count in 24h bucket
131
+ buckets['24h']++;
132
+ }
133
+ }
134
+ }
135
+
136
+ sendJSON(res, 200, { buckets, recentErrors });
137
+ }
138
+
139
+ function handleSystem(req, res) {
140
+ const cpus = os.cpus();
141
+ const loadAvg = os.loadavg();
142
+ const totalMem = os.totalmem();
143
+ const freeMem = os.freemem();
144
+
145
+ // macOS version
146
+ const macVersion = safeExec('sw_vers -productVersion') || 'unknown';
147
+ const nodeVersion = process.version;
148
+
149
+ // Disk usage
150
+ let disk = null;
151
+ const dfOut = safeExec("df -h / | tail -1");
152
+ if (dfOut) {
153
+ const parts = dfOut.split(/\s+/);
154
+ disk = { total: parts[1], used: parts[2], available: parts[3], pctUsed: parts[4] };
155
+ }
156
+
157
+ // Tailscale IP
158
+ const tailscaleIP = safeExec('tailscale ip -4') || null;
159
+
160
+ // Network interfaces (simplified)
161
+ const nets = os.networkInterfaces();
162
+ const interfaces = {};
163
+ for (const [name, addrs] of Object.entries(nets)) {
164
+ interfaces[name] = addrs
165
+ .filter(a => !a.internal)
166
+ .map(a => ({ address: a.address, family: a.family }));
167
+ }
168
+
169
+ sendJSON(res, 200, {
170
+ hostname: os.hostname(),
171
+ platform: os.platform(),
172
+ arch: os.arch(),
173
+ macVersion,
174
+ nodeVersion,
175
+ uptime: os.uptime(),
176
+ cpuCount: cpus.length,
177
+ cpuModel: cpus[0]?.model,
178
+ loadAvg: { '1m': loadAvg[0], '5m': loadAvg[1], '15m': loadAvg[2] },
179
+ memory: {
180
+ total: totalMem,
181
+ free: freeMem,
182
+ used: totalMem - freeMem,
183
+ pctUsed: ((1 - freeMem / totalMem) * 100).toFixed(1) + '%',
184
+ },
185
+ disk,
186
+ tailscaleIP,
187
+ interfaces,
188
+ });
189
+ }
190
+
191
+ function handleProcesses(req, res) {
192
+ const processNames = ['aiva-app', 'auto-deploy', 'health-reporter', 'diagnostics-api'];
193
+ const results = [];
194
+
195
+ // Try PM2 first
196
+ const pm2Json = safeExec('pm2 jlist 2>/dev/null');
197
+ let pm2Processes = [];
198
+ if (pm2Json) {
199
+ try { pm2Processes = JSON.parse(pm2Json); } catch {}
200
+ }
201
+
202
+ for (const name of processNames) {
203
+ const pm2Proc = pm2Processes.find(p => p.name === name);
204
+ if (pm2Proc) {
205
+ results.push({
206
+ name,
207
+ manager: 'pm2',
208
+ status: pm2Proc.pm2_env?.status || 'unknown',
209
+ pid: pm2Proc.pid,
210
+ uptime: pm2Proc.pm2_env?.pm_uptime ? Date.now() - pm2Proc.pm2_env.pm_uptime : null,
211
+ restarts: pm2Proc.pm2_env?.restart_time || 0,
212
+ memory: pm2Proc.monit?.memory || null,
213
+ cpu: pm2Proc.monit?.cpu || null,
214
+ });
215
+ } else {
216
+ // Check if running as regular process
217
+ const pid = safeExec(`pgrep -f "${name}" 2>/dev/null`);
218
+ results.push({
219
+ name,
220
+ manager: pid ? 'system' : 'none',
221
+ status: pid ? 'online' : 'stopped',
222
+ pid: pid ? parseInt(pid.split('\n')[0]) : null,
223
+ uptime: null,
224
+ restarts: null,
225
+ });
226
+ }
227
+ }
228
+
229
+ sendJSON(res, 200, { processes: results });
230
+ }
231
+
232
+ function handleConfig(req, res) {
233
+ const envFile = path.join(AIVA_DIR, '.env');
234
+ try {
235
+ const content = fs.readFileSync(envFile, 'utf8');
236
+ const masked = content.split('\n').map(line => {
237
+ if (!line.includes('=') || line.startsWith('#')) return line;
238
+ const eqIdx = line.indexOf('=');
239
+ const key = line.slice(0, eqIdx);
240
+ return `${key}=***`;
241
+ }).join('\n');
242
+ sendJSON(res, 200, { config: masked });
243
+ } catch {
244
+ sendJSON(res, 200, { config: 'No .env file found' });
245
+ }
246
+ }
247
+
248
+ function handleDeployLog(req, res) {
249
+ const logFile = path.join(AIVA_DIR, 'data', 'auto-deploy.log');
250
+ const content = readLastLines(logFile, 50);
251
+ sendJSON(res, 200, { log: content || 'No deploy log found' });
252
+ }
253
+
254
+ // --- Server ---
255
+
256
+ const server = http.createServer((req, res) => {
257
+ // CORS preflight
258
+ if (req.method === 'OPTIONS') {
259
+ res.writeHead(204, {
260
+ 'Access-Control-Allow-Origin': '*',
261
+ 'Access-Control-Allow-Methods': 'GET, OPTIONS',
262
+ 'Access-Control-Allow-Headers': 'Authorization, Content-Type',
263
+ });
264
+ return res.end();
265
+ }
266
+
267
+ if (req.method !== 'GET') {
268
+ return sendJSON(res, 405, { error: 'Method not allowed' });
269
+ }
270
+
271
+ const parsed = url.parse(req.url, true);
272
+ const pathname = parsed.pathname;
273
+
274
+ // /health is unauthenticated
275
+ if (pathname === '/health') return handleHealth(req, res);
276
+
277
+ // All other routes require auth
278
+ if (!authenticate(req)) {
279
+ return sendJSON(res, 401, { error: 'Unauthorized — provide valid Bearer token' });
280
+ }
281
+
282
+ try {
283
+ switch (pathname) {
284
+ case '/logs': return handleLogs(req, res, parsed.query);
285
+ case '/errors': return handleErrors(req, res);
286
+ case '/system': return handleSystem(req, res);
287
+ case '/processes': return handleProcesses(req, res);
288
+ case '/config': return handleConfig(req, res);
289
+ case '/deploy-log': return handleDeployLog(req, res);
290
+ default: return sendJSON(res, 404, { error: 'Not found' });
291
+ }
292
+ } catch (err) {
293
+ console.error('Handler error:', err);
294
+ sendJSON(res, 500, { error: 'Internal server error', detail: err.message });
295
+ }
296
+ });
297
+
298
+ process.on('uncaughtException', (err) => {
299
+ console.error('Uncaught exception:', err);
300
+ });
301
+
302
+ server.listen(PORT, '0.0.0.0', () => {
303
+ console.log(`Diagnostics API running on port ${PORT}`);
304
+ });
@@ -0,0 +1,112 @@
1
+ # Ara Voice Fix: Duplicate Response Audio Suppression
2
+
3
+ ## File: `voice-call.js`
4
+
5
+ ## Root Cause
6
+ xAI's Realtime API sometimes fires TWO complete response cycles for a single user turn. The current dedup only catches it at `response.done` — but by then the audio has already been streamed to the client via `response.output_audio.delta`. The user hears the same answer twice.
7
+
8
+ ## Fix: Track response IDs and suppress duplicate audio
9
+
10
+ ### Step 1: Add response tracking per call session
11
+
12
+ In the `callSession` object (wherever it's initialized, look for `callSession = {`), add:
13
+
14
+ ```javascript
15
+ completedResponseTexts: [], // track completed response texts
16
+ currentResponseId: null, // track current response ID
17
+ ```
18
+
19
+ ### Step 2: Capture response ID on `response.created`
20
+
21
+ In the `response.created` case, capture the response ID:
22
+
23
+ ```javascript
24
+ case 'response.created':
25
+ callSession.currentResponseText = '';
26
+ callSession.currentResponseId = msg.response?.id || null;
27
+ socket.emit('voice-call-status', { status: 'speaking' });
28
+ break;
29
+ ```
30
+
31
+ ### Step 3: Suppress audio at the delta level
32
+
33
+ Replace the `response.output_audio.delta` handler:
34
+
35
+ ```javascript
36
+ case 'response.output_audio.delta':
37
+ if (msg.delta) {
38
+ // Check if we're in a duplicate response by comparing accumulated text so far
39
+ // We'll use a flag set during transcript accumulation
40
+ if (!callSession.suppressCurrentResponse) {
41
+ socket.emit('voice-call-audio-delta', { audio: msg.delta });
42
+ }
43
+ }
44
+ break;
45
+ ```
46
+
47
+ ### Step 4: Detect duplicates during transcript accumulation
48
+
49
+ Replace the `response.output_audio_transcript.delta` handler:
50
+
51
+ ```javascript
52
+ case 'response.output_audio_transcript.delta':
53
+ if (msg.delta) {
54
+ callSession.currentResponseText += msg.delta;
55
+ // Once we have 50+ chars, check if this matches a previous response
56
+ if (!callSession.suppressCurrentResponse && callSession.currentResponseText.length >= 50) {
57
+ const prefix = callSession.currentResponseText.slice(0, 50);
58
+ if (callSession.completedResponseTexts.some(t => t.slice(0, 50) === prefix)) {
59
+ callSession.suppressCurrentResponse = true;
60
+ console.log('[voice-call] Suppressing duplicate response audio');
61
+ }
62
+ }
63
+ if (!callSession.suppressCurrentResponse) {
64
+ socket.emit('voice-call-transcript-delta', { text: msg.delta });
65
+ }
66
+ }
67
+ break;
68
+ ```
69
+
70
+ ### Step 5: Record completed responses and reset
71
+
72
+ Replace the `response.output_audio_transcript.done` handler:
73
+
74
+ ```javascript
75
+ case 'response.output_audio_transcript.done':
76
+ if (msg.transcript && !callSession.suppressCurrentResponse) {
77
+ callSession.transcript.push({ role: 'assistant', text: msg.transcript });
78
+ }
79
+ break;
80
+ ```
81
+
82
+ Update the `response.done` handler — remove the old dedup logic and replace with:
83
+
84
+ ```javascript
85
+ case 'response.done': {
86
+ socket.emit('voice-call-status', { status: 'listening' });
87
+ const respText = callSession.currentResponseText;
88
+ if (respText && !callSession.suppressCurrentResponse) {
89
+ callSession.completedResponseTexts.push(respText);
90
+ // Keep only last 5 to prevent memory growth
91
+ if (callSession.completedResponseTexts.length > 5) {
92
+ callSession.completedResponseTexts.shift();
93
+ }
94
+ socket.emit('voice-call-response-done', { text: respText });
95
+ }
96
+ // Reset for next response
97
+ callSession.suppressCurrentResponse = false;
98
+ break;
99
+ }
100
+ ```
101
+
102
+ ### Step 6: Clean up old dedup code
103
+
104
+ Remove the `lastResponseText` Map (line ~24) and its cleanup on disconnect (line ~711) since we're using per-session tracking now.
105
+
106
+ ## After Changes
107
+ - `pm2 restart aiva-app`
108
+ - Verify started
109
+
110
+ ## IMPORTANT
111
+ - Only modify `voice-call.js`
112
+ - Do NOT touch `public/index.html`
@@ -0,0 +1,61 @@
1
+ # Ara Voice Fix Round 2: Calendar Tool + Duplicate Responses
2
+
3
+ ## File: `voice-call.js`
4
+
5
+ ## Fix 1: Filter past events in `check_calendar` tool (around line 345-353)
6
+
7
+ The `check_calendar` tool handler returns ALL events including past ones. Add past-event filtering when `timeframe === 'today'`.
8
+
9
+ **REPLACE** the calResults return block (around line 352-353):
10
+
11
+ ```javascript
12
+ // Filter past events for today view
13
+ if (tf === 'today') {
14
+ calResults = calResults.filter(line => {
15
+ const timeMatch = line.match(/(\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2})/);
16
+ if (timeMatch) {
17
+ const eventTime = new Date(timeMatch[1].replace(' ', 'T'));
18
+ return eventTime > now;
19
+ }
20
+ return true; // keep lines without parseable time
21
+ });
22
+ }
23
+ return { success: true, calendar: calResults.join('\n') || 'No upcoming events today.' };
24
+ ```
25
+
26
+ ## Fix 2: Filter past events in `weekCalendar` pre-fetch (around line 87-102)
27
+
28
+ The week calendar pre-fetch does NOT filter past events. Add the same filter:
29
+
30
+ After `weekLines.push(...)` and before `result.weekCalendar = weekLines.join('\n')`, add:
31
+
32
+ ```javascript
33
+ // Filter out past events from this week too
34
+ weekLines = weekLines.filter(line => {
35
+ const timeMatch = line.match(/(\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2})/);
36
+ if (timeMatch) {
37
+ const eventTime = new Date(timeMatch[1].replace(' ', 'T'));
38
+ return eventTime > now;
39
+ }
40
+ return true;
41
+ });
42
+ ```
43
+
44
+ ## Fix 3: Prevent duplicate responses
45
+
46
+ Look for where `response.audio.done` or `response.done` events trigger sending audio to the client. If there's a risk of duplicate tool-call responses being sent, add a dedup guard. Specifically check if there's a pattern where both a tool result AND a follow-up response trigger separate audio sends for the same content.
47
+
48
+ If you see a `response.audio.done` handler that doesn't dedup, add a simple guard:
49
+ ```javascript
50
+ // Track last response to prevent duplicates
51
+ const lastResponseText = new Map(); // callId -> lastText
52
+ ```
53
+ Before sending audio response text, check if it's substantially the same as the last one sent (first 50 chars match). If so, skip it.
54
+
55
+ ## After Changes
56
+ - `pm2 restart aiva-app`
57
+ - Verify started successfully
58
+
59
+ ## IMPORTANT
60
+ - Only modify `voice-call.js`
61
+ - Do NOT touch `public/index.html` or microphone feature
@@ -0,0 +1,70 @@
1
+ # Ara Voice Call Fix: Greeting Behavior + Calendar Filtering
2
+
3
+ ## File: `voice-call.js`
4
+
5
+ ## Problem
6
+ 1. Ara dumps calendar events immediately on connect before understanding user intent
7
+ 2. Calendar context includes past events (e.g., 7:30 AM and 11 AM events mentioned at 2:30 PM)
8
+
9
+ ## Fix 1: System Prompt — Don't Volunteer Info
10
+
11
+ Change the greeting instruction (around line 147):
12
+
13
+ **FROM:**
14
+ ```
15
+ - Start the conversation with a casual greeting like "Hey! What's up?"
16
+ ```
17
+
18
+ **TO:**
19
+ ```
20
+ - Start the conversation with a brief, casual greeting like "Hey Brandon, what's up?" and WAIT for the user to speak. Do NOT volunteer any information (calendar, tasks, etc.) until the user tells you what they need. Your job is to listen first, then assist. Only bring up calendar/tasks/context when the user asks or when it's directly relevant to what they're discussing.
21
+ ```
22
+
23
+ ## Fix 2: Filter Past Calendar Events
24
+
25
+ In `buildSystemPrompt()` where calendar events are appended (around line 163-165), filter out events that have already passed:
26
+
27
+ **FROM:**
28
+ ```javascript
29
+ if (ctx.calendar?.length) {
30
+ const events = ctx.calendar.slice(0, 5).map(e => `- ${e.title || e.summary} (${e.time || e.start || ''})`).join('\n');
31
+ prompt += `\n\nToday's calendar:\n${events}`;
32
+ }
33
+ ```
34
+
35
+ **TO:**
36
+ ```javascript
37
+ if (ctx.calendar?.length) {
38
+ const now = new Date();
39
+ const futureEvents = ctx.calendar.filter(e => {
40
+ const eventTime = e.time || e.start || '';
41
+ if (!eventTime) return true; // keep events with no time (all-day)
42
+ try {
43
+ const eventDate = new Date(eventTime);
44
+ return eventDate > now;
45
+ } catch { return true; }
46
+ });
47
+ if (futureEvents.length) {
48
+ const events = futureEvents.slice(0, 5).map(e => `- ${e.title || e.summary} (${e.time || e.start || ''})`).join('\n');
49
+ prompt += `\n\nUpcoming calendar events (DO NOT mention these unless asked):\n${events}`;
50
+ }
51
+ }
52
+ ```
53
+
54
+ Also apply the same past-event filtering to the pre-fetch calendar sections (lines ~55-104) where `todayCalendar` and `weekCalendar` are built.
55
+
56
+ ## Fix 3: Label Context as Reference-Only
57
+
58
+ Change the calendar/tasks/chat context labels to make it clear they're reference material, not conversation starters:
59
+
60
+ - `Today's calendar:` → `Upcoming calendar events (reference only — do NOT mention unless asked):`
61
+ - `Active tasks:` → `Active tasks (reference only — do NOT mention unless asked):`
62
+ - `Recent chat:` → `Recent chat context (reference only):`
63
+
64
+ ## After Changes
65
+ - Restart: `pm2 restart aiva-app`
66
+ - Test by calling and confirming Ara greets briefly and waits
67
+
68
+ ## IMPORTANT
69
+ - Do NOT touch the microphone icon (voice-to-text) — only the phone icon voice call feature
70
+ - Do NOT touch `public/index.html` — changes are server-side only in `voice-call.js`
@@ -0,0 +1,28 @@
1
+ # Calendar Date Parsing Fix — Scope Doc
2
+
3
+ ## Problem
4
+ The `check_calendar` tool in `voice-call.js` uses relative date strings like `+7d` and `+2d` with the `gog calendar list` command, but `gog` doesn't support that syntax. It needs absolute ISO dates (e.g., `2026-02-11`) or keywords like `today`, `tomorrow`, `monday`.
5
+
6
+ ## File
7
+ `/Users/brandonburgan/.openclaw/workspace/aiva-tasks/voice-call.js`
8
+
9
+ ## Fix
10
+ In the `check_calendar` tool handler, replace any relative date logic (`+Xd`) with computed absolute dates using JavaScript's `Date` object.
11
+
12
+ Example: If `timeframe === 'week'`, compute:
13
+ ```js
14
+ const from = new Date().toISOString().split('T')[0]; // today
15
+ const to = new Date(Date.now() + 7 * 86400000).toISOString().split('T')[0]; // 7 days out
16
+ ```
17
+
18
+ Then pass `--from ${from} --to ${to}` to the `gog calendar list` command.
19
+
20
+ ## Specific Changes
21
+ 1. Find the `check_calendar` tool handler function
22
+ 2. Replace any `+Xd` style date strings with computed absolute dates
23
+ 3. Handle all timeframe values: `today`, `tomorrow`, `week`, `month`
24
+ 4. Make sure the `--from` flag uses today's date (not a relative string)
25
+
26
+ ## Testing
27
+ After changes, restart PM2: `pm2 restart aiva-app`
28
+ Test by making a voice call and asking "What's on my calendar this week?"
@@ -0,0 +1,115 @@
1
+ # Getting to Know Your AIVA Assistant
2
+
3
+ Welcome! AIVA (AI Virtual Assistant) is your personal AI-powered assistant designed to help you stay organized, communicate effectively, and get things done. Here's everything you need to know to get the most out of AIVA.
4
+
5
+ ---
6
+
7
+ ## What is AIVA?
8
+
9
+ AIVA is more than a chatbot — she's a team of specialized AI agents working together behind the scenes. Think of it like having a personal office staff: each agent has a specific role, and they coordinate to handle your requests efficiently.
10
+
11
+ You don't need to worry about which agent does what — just communicate naturally, and AIVA routes everything to the right place.
12
+
13
+ ---
14
+
15
+ ## How AIVA is Set Up
16
+
17
+ AIVA uses a **multi-agent system**, meaning different agents handle different types of work:
18
+
19
+ ### Main Agent (Your Central Brain)
20
+ This is the agent you talk to in the **AIVA App**. It's the coordinator — the one that:
21
+ - Receives your requests and figures out who should handle them
22
+ - Manages your task board
23
+ - Distributes knowledge across all other agents
24
+ - Handles calendar, email monitoring, and general questions
25
+ - Sends you morning and evening briefings
26
+
27
+ **Think of it as your executive assistant** who delegates to specialists.
28
+
29
+ ### Outreach Agent (Your Message Manager)
30
+ This agent handles all **iMessage and text communication**. When someone texts your AIVA-managed number:
31
+ - The outreach agent reads and responds (based on your preferences)
32
+ - It manages conversations with contacts on your behalf
33
+ - It follows up on active conversations automatically
34
+ - It surfaces important messages to the main agent when needed
35
+
36
+ **Think of it as your receptionist** — fielding calls and messages so you don't have to.
37
+
38
+ ### Email Agent (Your Inbox Manager)
39
+ Monitors your email, triages messages, and alerts you to anything important. Routine stuff gets handled automatically so you only see what matters.
40
+
41
+ ### Research Agent (Your Analyst)
42
+ When you need information — market research, competitor analysis, or deep dives on a topic — the research agent handles it and reports back with findings.
43
+
44
+ ### Content Agent (Your Writer)
45
+ Handles content creation tasks — drafts, copy, documentation, social posts, and more.
46
+
47
+ ---
48
+
49
+ ## How to Communicate with AIVA
50
+
51
+ ### The AIVA App (Your Main Channel)
52
+ The AIVA app is your **central hub**. This is where you should:
53
+ - **Brain dump** — tell AIVA everything on your mind. She'll organize it, create tasks, and distribute knowledge to the right agents.
54
+ - **Make requests** — "Schedule a meeting with John next week" or "Research the best CRM tools for small businesses"
55
+ - **Check your task board** — see what's in progress, what's done, and what needs attention
56
+ - **Review agent activity** — see what your agents are working on
57
+
58
+ **Pro tip:** Voice messages work great in the app. Just talk naturally — AIVA will transcribe and act on it.
59
+
60
+ ### iMessage (Your Outreach Channel)
61
+ Messages sent to your AIVA phone number go directly to the **outreach agent**. This is specifically for:
62
+ - Managing conversations with contacts
63
+ - Scheduling and follow-ups
64
+ - Having your outreach agent handle communication on your behalf
65
+
66
+ **Important:** The outreach agent is a separate specialist from your main AIVA. Training it happens through iMessage conversations. The more context you give it about how you want to handle specific contacts, the better it gets.
67
+
68
+ ### What Happens When You Submit a Request
69
+
70
+ 1. **You send a message** (via app, voice, or text)
71
+ 2. **AIVA receives it** and creates a task on your board
72
+ 3. **The right agent picks it up** based on what needs to be done
73
+ 4. **Work happens** — you can check progress on the task board anytime
74
+ 5. **You get notified** when it's done, with a summary of what was accomplished
75
+
76
+ ---
77
+
78
+ ## Best Practices
79
+
80
+ ### Do This
81
+ - **Be specific** — "Schedule a 30-min call with John on Thursday at 2 PM" works better than "set something up with John"
82
+ - **Use the app for big-picture stuff** — strategy, planning, brain dumps, complex requests
83
+ - **Use iMessage for message management** — training your outreach agent on how to handle contacts
84
+ - **Check your task board** — it's the single source of truth for all work in progress
85
+ - **Give feedback** — if AIVA does something you don't like, say so. She learns from your preferences.
86
+
87
+ ### Avoid This
88
+ - **Don't repeat yourself** — if you submitted a request, it's on the board. No need to follow up unless it's been too long.
89
+ - **Don't mix channels unnecessarily** — use the app for requests, iMessage for message management
90
+ - **Don't assume AIVA knows everything** — she wakes up fresh each session. Important context should be stated clearly.
91
+
92
+ ---
93
+
94
+ ## Frequently Asked Questions
95
+
96
+ **Q: Does AIVA remember our conversations?**
97
+ A: AIVA maintains memory through daily notes and long-term memory files. Important decisions, preferences, and context are saved between sessions. However, she starts each session fresh and reads her notes — so the more important something is, the more clearly you should state it.
98
+
99
+ **Q: Can AIVA text people on my behalf?**
100
+ A: Yes! The outreach agent can handle iMessage conversations. You set preferences for each contact (respond, ignore, take message only), and AIVA follows them. She'll always check with you before contacting someone new.
101
+
102
+ **Q: How do I know what AIVA is working on?**
103
+ A: Check the task board in the app. Every request becomes a task with status tracking (To Do, In Progress, Done).
104
+
105
+ **Q: What if AIVA makes a mistake?**
106
+ A: Tell her! Feedback helps AIVA improve. She logs lessons learned and adjusts her approach going forward.
107
+
108
+ **Q: Is my information private?**
109
+ A: Yes. AIVA treats your data with strict privacy. She won't share personal information in group settings or with other contacts without your explicit permission.
110
+
111
+ ---
112
+
113
+ ## Updates
114
+
115
+ *Check the Updates page for the latest changes and improvements to your AIVA assistant.*