@conversionpros/aiva 1.0.0 → 2.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 (150) hide show
  1. package/bin/aiva.js +26 -14
  2. package/lib/bluebubbles.js +145 -0
  3. package/lib/config-gen.js +253 -0
  4. package/lib/config.js +1 -1
  5. package/lib/constants.js +72 -0
  6. package/lib/launch-agent.js +112 -0
  7. package/lib/prerequisites.js +236 -0
  8. package/lib/process.js +59 -145
  9. package/lib/setup.js +224 -194
  10. package/lib/validate.js +194 -0
  11. package/package.json +7 -32
  12. package/auto-deploy.js +0 -190
  13. package/cli-sync.js +0 -126
  14. package/d2a-prompt-template.txt +0 -106
  15. package/diagnostics-api.js +0 -304
  16. package/docs/ara-dedup-fix-scope.md +0 -112
  17. package/docs/ara-fix-round2-scope.md +0 -61
  18. package/docs/ara-greeting-fix-scope.md +0 -70
  19. package/docs/calendar-date-fix-scope.md +0 -28
  20. package/docs/getting-started.md +0 -115
  21. package/docs/network-architecture-rollout-scope.md +0 -43
  22. package/docs/scope-google-oauth-integration.md +0 -351
  23. package/docs/settings-page-scope.md +0 -50
  24. package/docs/xai-imagine-scope.md +0 -116
  25. package/docs/xai-voice-integration-scope.md +0 -115
  26. package/docs/xai-voice-tools-scope.md +0 -165
  27. package/email-router.js +0 -512
  28. package/follow-up-handler.js +0 -606
  29. package/gateway-monitor.js +0 -158
  30. package/google-email.js +0 -379
  31. package/google-oauth.js +0 -310
  32. package/grok-imagine.js +0 -97
  33. package/health-reporter.js +0 -287
  34. package/invisible-prefix-base.txt +0 -206
  35. package/invisible-prefix-owner.txt +0 -26
  36. package/invisible-prefix-slim.txt +0 -10
  37. package/invisible-prefix.txt +0 -43
  38. package/knowledge-base.js +0 -472
  39. package/lib/cli.js +0 -19
  40. package/lib/server.js +0 -42
  41. package/meta-capi.js +0 -206
  42. package/meta-leads.js +0 -411
  43. package/notion-oauth.js +0 -323
  44. package/public/agent-config.html +0 -241
  45. package/public/aiva-avatar-anime.png +0 -0
  46. package/public/css/docs.css.bak +0 -688
  47. package/public/css/onboarding.css +0 -543
  48. package/public/diagrams/claude-subscription-pool.html +0 -329
  49. package/public/diagrams/claude-subscription-pool.png +0 -0
  50. package/public/docs-icon.png +0 -0
  51. package/public/escalation.html +0 -237
  52. package/public/group-config.html +0 -300
  53. package/public/icon-192.png +0 -0
  54. package/public/icon-512.png +0 -0
  55. package/public/icons/agents.svg +0 -1
  56. package/public/icons/attach.svg +0 -1
  57. package/public/icons/characters.svg +0 -1
  58. package/public/icons/chat.svg +0 -1
  59. package/public/icons/docs.svg +0 -1
  60. package/public/icons/heartbeat.svg +0 -1
  61. package/public/icons/messages.svg +0 -1
  62. package/public/icons/mic.svg +0 -1
  63. package/public/icons/notes.svg +0 -1
  64. package/public/icons/settings.svg +0 -1
  65. package/public/icons/tasks.svg +0 -1
  66. package/public/images/onboarding/p0-communication-layer.png +0 -0
  67. package/public/images/onboarding/p0-infinite-surface.png +0 -0
  68. package/public/images/onboarding/p0-learning-model.png +0 -0
  69. package/public/images/onboarding/p0-meet-aiva.png +0 -0
  70. package/public/images/onboarding/p4-contact-intelligence.png +0 -0
  71. package/public/images/onboarding/p4-context-compounds.png +0 -0
  72. package/public/images/onboarding/p4-message-router.png +0 -0
  73. package/public/images/onboarding/p4-per-contact-rules.png +0 -0
  74. package/public/images/onboarding/p4-send-messages.png +0 -0
  75. package/public/images/onboarding/p6-be-precise.png +0 -0
  76. package/public/images/onboarding/p6-review-escalations.png +0 -0
  77. package/public/images/onboarding/p6-voice-input.png +0 -0
  78. package/public/images/onboarding/p7-completion.png +0 -0
  79. package/public/index.html +0 -11594
  80. package/public/js/onboarding.js +0 -699
  81. package/public/manifest.json +0 -24
  82. package/public/messages-v2.html +0 -2824
  83. package/public/permission-approve.html.bak +0 -107
  84. package/public/permissions.html +0 -150
  85. package/public/styles/design-system.css +0 -68
  86. package/router-db.js +0 -604
  87. package/router-utils.js +0 -28
  88. package/router-v2/adapters/imessage.js +0 -191
  89. package/router-v2/adapters/quo.js +0 -82
  90. package/router-v2/adapters/whatsapp.js +0 -192
  91. package/router-v2/contact-manager.js +0 -234
  92. package/router-v2/conversation-engine.js +0 -498
  93. package/router-v2/data/knowledge-base.json +0 -176
  94. package/router-v2/data/router-v2.db +0 -0
  95. package/router-v2/data/router-v2.db-shm +0 -0
  96. package/router-v2/data/router-v2.db-wal +0 -0
  97. package/router-v2/data/router.db +0 -0
  98. package/router-v2/db.js +0 -457
  99. package/router-v2/escalation-bridge.js +0 -540
  100. package/router-v2/follow-up-engine.js +0 -347
  101. package/router-v2/index.js +0 -441
  102. package/router-v2/ingestion.js +0 -213
  103. package/router-v2/knowledge-base.js +0 -231
  104. package/router-v2/lead-qualifier.js +0 -152
  105. package/router-v2/learning-loop.js +0 -202
  106. package/router-v2/outbound-sender.js +0 -160
  107. package/router-v2/package.json +0 -13
  108. package/router-v2/permission-gate.js +0 -86
  109. package/router-v2/playbook.js +0 -177
  110. package/router-v2/prompts/base.js +0 -52
  111. package/router-v2/prompts/first-contact.js +0 -38
  112. package/router-v2/prompts/lead-qualification.js +0 -37
  113. package/router-v2/prompts/scheduling.js +0 -72
  114. package/router-v2/prompts/style-overrides.js +0 -22
  115. package/router-v2/scheduler.js +0 -301
  116. package/router-v2/scripts/migrate-v1-to-v2.js +0 -215
  117. package/router-v2/scripts/seed-faq.js +0 -67
  118. package/router-v2/seed-knowledge-base.js +0 -39
  119. package/router-v2/utils/ai.js +0 -129
  120. package/router-v2/utils/phone.js +0 -52
  121. package/router-v2/utils/response-validator.js +0 -98
  122. package/router-v2/utils/sanitize.js +0 -222
  123. package/router.js +0 -5005
  124. package/routes/google-calendar.js +0 -186
  125. package/scripts/deploy.sh +0 -62
  126. package/scripts/macos-calendar.sh +0 -232
  127. package/scripts/onboard-device.sh +0 -466
  128. package/server.js +0 -5131
  129. package/start.sh +0 -24
  130. package/templates/AGENTS.md +0 -548
  131. package/templates/IDENTITY.md +0 -15
  132. package/templates/docs-agents.html +0 -132
  133. package/templates/docs-app.html +0 -130
  134. package/templates/docs-home.html +0 -83
  135. package/templates/docs-imessage.html +0 -121
  136. package/templates/docs-tasks.html +0 -123
  137. package/templates/docs-tips.html +0 -175
  138. package/templates/getting-started.html +0 -809
  139. package/templates/invisible-prefix-base.txt +0 -171
  140. package/templates/invisible-prefix-owner.txt +0 -282
  141. package/templates/invisible-prefix.txt +0 -338
  142. package/templates/manifest.json +0 -61
  143. package/templates/memory-org/clients.md +0 -7
  144. package/templates/memory-org/credentials.md +0 -9
  145. package/templates/memory-org/devices.md +0 -7
  146. package/templates/updates.html +0 -464
  147. package/tts-proxy.js +0 -96
  148. package/voice-call-local.js +0 -731
  149. package/voice-call.js +0 -732
  150. package/wa-listener.js +0 -354
Binary file
Binary file
File without changes
package/router-v2/db.js DELETED
@@ -1,457 +0,0 @@
1
- // ── Database Setup, Migrations, Prepared Statements ──────
2
- 'use strict';
3
-
4
- const path = require('path');
5
- const fs = require('fs');
6
-
7
- const DB_DIR = path.join(__dirname, 'data');
8
- const DB_PATH = path.join(DB_DIR, 'router-v2.db');
9
-
10
- let db = null;
11
- let stmts = {};
12
-
13
- /**
14
- * Initialize the database. Creates tables if they don't exist.
15
- * Must be called once at startup before any other db operations.
16
- * @returns {{ db: import('better-sqlite3').Database, stmts: Object }}
17
- */
18
- function initDatabase() {
19
- const Database = require('better-sqlite3');
20
- if (!fs.existsSync(DB_DIR)) fs.mkdirSync(DB_DIR, { recursive: true });
21
-
22
- db = new Database(DB_PATH);
23
- db.pragma('journal_mode = WAL');
24
- db.pragma('foreign_keys = ON');
25
-
26
- createTables();
27
- prepareStatements();
28
-
29
- console.log(`[db] Initialized at ${DB_PATH}`);
30
- return { db, stmts };
31
- }
32
-
33
- function createTables() {
34
- // ── Contacts ──
35
- db.exec(`
36
- CREATE TABLE IF NOT EXISTS contacts (
37
- phone TEXT PRIMARY KEY,
38
- name TEXT NOT NULL DEFAULT 'Unknown',
39
- category TEXT NOT NULL DEFAULT 'unknown',
40
- response_mode TEXT NOT NULL DEFAULT 'auto',
41
- style TEXT NOT NULL DEFAULT 'casual',
42
- instructions TEXT DEFAULT '',
43
- source TEXT DEFAULT 'unknown',
44
- introduced INTEGER DEFAULT 0,
45
- qualification_score INTEGER DEFAULT 0,
46
- pipeline_stage TEXT DEFAULT 'none',
47
- created_at DATETIME DEFAULT (datetime('now')),
48
- updated_at DATETIME DEFAULT (datetime('now'))
49
- )
50
- `);
51
-
52
- // ── Contact Context ──
53
- db.exec(`
54
- CREATE TABLE IF NOT EXISTS contact_context (
55
- phone TEXT PRIMARY KEY,
56
- relationship TEXT DEFAULT '',
57
- last_topic TEXT DEFAULT '',
58
- pending_items TEXT DEFAULT '[]',
59
- conversation_summary TEXT DEFAULT '',
60
- preferences TEXT DEFAULT '{}',
61
- last_interaction DATETIME,
62
- FOREIGN KEY (phone) REFERENCES contacts(phone) ON DELETE CASCADE
63
- )
64
- `);
65
-
66
- // ── Contact Scopes ──
67
- db.exec(`
68
- CREATE TABLE IF NOT EXISTS contact_scopes (
69
- phone TEXT NOT NULL,
70
- scope TEXT NOT NULL,
71
- granted INTEGER DEFAULT 0,
72
- granted_by TEXT DEFAULT 'manual',
73
- granted_at DATETIME,
74
- PRIMARY KEY (phone, scope)
75
- )
76
- `);
77
-
78
- // ── Conversation State ──
79
- db.exec(`
80
- CREATE TABLE IF NOT EXISTS conversation_state (
81
- phone TEXT PRIMARY KEY,
82
- state TEXT NOT NULL DEFAULT 'idle',
83
- state_data TEXT DEFAULT '{}',
84
- entered_at DATETIME DEFAULT (datetime('now')),
85
- expires_at DATETIME
86
- )
87
- `);
88
-
89
- // ── Message Log ──
90
- db.exec(`
91
- CREATE TABLE IF NOT EXISTS message_log (
92
- id INTEGER PRIMARY KEY AUTOINCREMENT,
93
- phone TEXT NOT NULL,
94
- channel TEXT DEFAULT 'imessage',
95
- direction TEXT NOT NULL DEFAULT 'inbound',
96
- text TEXT DEFAULT '',
97
- attachments TEXT DEFAULT '[]',
98
- sent_by TEXT DEFAULT 'contact',
99
- state_at_time TEXT DEFAULT '',
100
- created_at DATETIME DEFAULT (datetime('now'))
101
- )
102
- `);
103
-
104
- // ── FAQ Entries ──
105
- db.exec(`
106
- CREATE TABLE IF NOT EXISTS faq_entries (
107
- id INTEGER PRIMARY KEY AUTOINCREMENT,
108
- device_id TEXT,
109
- question TEXT NOT NULL,
110
- answer TEXT NOT NULL,
111
- category TEXT DEFAULT 'general',
112
- keywords TEXT DEFAULT '',
113
- enabled INTEGER DEFAULT 1,
114
- created_at DATETIME DEFAULT (datetime('now'))
115
- )
116
- `);
117
-
118
- // ── Playbook Sections ──
119
- db.exec(`
120
- CREATE TABLE IF NOT EXISTS playbook_sections (
121
- id INTEGER PRIMARY KEY AUTOINCREMENT,
122
- device_id TEXT,
123
- section_type TEXT NOT NULL,
124
- title TEXT DEFAULT '',
125
- content TEXT NOT NULL,
126
- priority INTEGER DEFAULT 10,
127
- enabled INTEGER DEFAULT 1,
128
- version INTEGER DEFAULT 1,
129
- created_at DATETIME DEFAULT (datetime('now')),
130
- updated_at DATETIME DEFAULT (datetime('now'))
131
- )
132
- `);
133
-
134
- // ── Preferences (Learning Loop) ──
135
- db.exec(`
136
- CREATE TABLE IF NOT EXISTS preferences (
137
- id INTEGER PRIMARY KEY AUTOINCREMENT,
138
- scope TEXT DEFAULT 'global',
139
- question_pattern TEXT NOT NULL,
140
- answer TEXT NOT NULL,
141
- source TEXT DEFAULT 'router',
142
- confidence REAL DEFAULT 0.7,
143
- hit_count INTEGER DEFAULT 0,
144
- last_used_at DATETIME,
145
- created_at DATETIME DEFAULT (datetime('now')),
146
- updated_at DATETIME DEFAULT (datetime('now'))
147
- )
148
- `);
149
-
150
- // ── Preference Log (audit trail) ──
151
- db.exec(`
152
- CREATE TABLE IF NOT EXISTS preference_log (
153
- id INTEGER PRIMARY KEY AUTOINCREMENT,
154
- preference_id INTEGER,
155
- escalation_id TEXT,
156
- phone TEXT,
157
- original_question TEXT,
158
- tier_resolved TEXT,
159
- created_at DATETIME DEFAULT (datetime('now')),
160
- FOREIGN KEY (preference_id) REFERENCES preferences(id)
161
- )
162
- `);
163
-
164
- // ── Escalations ──
165
- db.exec(`
166
- CREATE TABLE IF NOT EXISTS escalations (
167
- id INTEGER PRIMARY KEY AUTOINCREMENT,
168
- escalation_id TEXT UNIQUE NOT NULL,
169
- phone TEXT NOT NULL,
170
- trigger_message TEXT DEFAULT '',
171
- context_sent TEXT DEFAULT '{}',
172
- response_received TEXT DEFAULT '',
173
- status TEXT DEFAULT 'pending',
174
- strike_count INTEGER DEFAULT 0,
175
- is_client_support INTEGER DEFAULT 0,
176
- created_at DATETIME DEFAULT (datetime('now')),
177
- responded_at DATETIME,
178
- timeout_at DATETIME
179
- )
180
- `);
181
-
182
- // ── Follow-Up Tracker ──
183
- db.exec(`
184
- CREATE TABLE IF NOT EXISTS follow_up_tracker (
185
- id INTEGER PRIMARY KEY AUTOINCREMENT,
186
- phone TEXT NOT NULL UNIQUE,
187
- channel TEXT DEFAULT 'imessage',
188
- contact_name TEXT DEFAULT 'Unknown',
189
- last_our_message TEXT DEFAULT '',
190
- last_our_message_at DATETIME,
191
- context_summary TEXT DEFAULT '',
192
- follow_up_count INTEGER DEFAULT 0,
193
- max_follow_ups INTEGER DEFAULT 3,
194
- next_follow_up_at DATETIME,
195
- status TEXT DEFAULT 'active',
196
- last_follow_up_at DATETIME,
197
- last_follow_up_topic TEXT DEFAULT '',
198
- opted_out INTEGER DEFAULT 0,
199
- created_at DATETIME DEFAULT (datetime('now')),
200
- updated_at DATETIME DEFAULT (datetime('now'))
201
- )
202
- `);
203
-
204
- // ── Scheduling Rules ──
205
- db.exec(`
206
- CREATE TABLE IF NOT EXISTS scheduling_rules (
207
- category TEXT PRIMARY KEY,
208
- rule_preset TEXT DEFAULT 'flexible',
209
- custom_instructions TEXT DEFAULT '',
210
- structured_overrides TEXT DEFAULT '{}',
211
- updated_at DATETIME DEFAULT (datetime('now'))
212
- )
213
- `);
214
-
215
- // ── Settings ──
216
- db.exec(`
217
- CREATE TABLE IF NOT EXISTS settings (
218
- key TEXT PRIMARY KEY,
219
- value TEXT NOT NULL
220
- )
221
- `);
222
-
223
- // ── Indexes ──
224
- const indexes = [
225
- 'CREATE INDEX IF NOT EXISTS idx_msg_phone ON message_log(phone)',
226
- 'CREATE INDEX IF NOT EXISTS idx_msg_created ON message_log(created_at)',
227
- 'CREATE INDEX IF NOT EXISTS idx_msg_direction ON message_log(phone, direction)',
228
- 'CREATE INDEX IF NOT EXISTS idx_scopes_phone ON contact_scopes(phone)',
229
- 'CREATE INDEX IF NOT EXISTS idx_escalation_id ON escalations(escalation_id)',
230
- 'CREATE INDEX IF NOT EXISTS idx_escalation_status ON escalations(status)',
231
- 'CREATE INDEX IF NOT EXISTS idx_escalation_phone ON escalations(phone)',
232
- 'CREATE INDEX IF NOT EXISTS idx_followup_status ON follow_up_tracker(status)',
233
- 'CREATE INDEX IF NOT EXISTS idx_followup_next ON follow_up_tracker(next_follow_up_at)',
234
- 'CREATE INDEX IF NOT EXISTS idx_faq_device ON faq_entries(device_id)',
235
- 'CREATE INDEX IF NOT EXISTS idx_playbook_device ON playbook_sections(device_id)',
236
- 'CREATE INDEX IF NOT EXISTS idx_playbook_type ON playbook_sections(section_type)',
237
- 'CREATE INDEX IF NOT EXISTS idx_prefs_scope ON preferences(scope)',
238
- 'CREATE INDEX IF NOT EXISTS idx_prefs_pattern ON preferences(question_pattern)',
239
- 'CREATE INDEX IF NOT EXISTS idx_conv_state ON conversation_state(state)',
240
- ];
241
- for (const idx of indexes) {
242
- try { db.exec(idx); } catch (e) { /* index may already exist */ }
243
- }
244
-
245
- // ── Default Settings ──
246
- const defaults = {
247
- followUpEnabled: 'true',
248
- followUpStartHour: '8',
249
- followUpEndHour: '18',
250
- maxDailyAutoResponses: '50',
251
- maxDailyFollowUps: '10',
252
- debounceMs: '3000',
253
- escalationNotifyPhone: '+15099794110',
254
- sonnetModel: 'claude-sonnet-4-20250514',
255
- awayMessage: '',
256
- masterPhone: '+15099794110',
257
- defaultResponseMode: 'block',
258
- imessageDefaultMode: '',
259
- whatsappDefaultMode: '',
260
- v2DryRun: 'false',
261
- calendarAccountId: '',
262
- };
263
- const setSetting = db.prepare('INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)');
264
- for (const [key, value] of Object.entries(defaults)) {
265
- setSetting.run(key, value);
266
- }
267
-
268
- // ── Default Scheduling Rules ──
269
- const setRule = db.prepare('INSERT OR IGNORE INTO scheduling_rules (category, rule_preset) VALUES (?, ?)');
270
- setRule.run('family', 'flexible');
271
- setRule.run('friend', 'flexible');
272
- setRule.run('team', 'work-hours');
273
- setRule.run('client', 'professional');
274
- setRule.run('lead', 'professional');
275
- setRule.run('unknown', 'gatekeeper');
276
- }
277
-
278
- function prepareStatements() {
279
- stmts = {
280
- // ── Contacts ──
281
- getContact: db.prepare('SELECT * FROM contacts WHERE phone = ?'),
282
- getAllContacts: db.prepare('SELECT * FROM contacts ORDER BY name'),
283
- searchContacts: db.prepare("SELECT * FROM contacts WHERE name LIKE ? OR phone LIKE ? ORDER BY name"),
284
- filterByCategory: db.prepare('SELECT * FROM contacts WHERE category = ? ORDER BY name'),
285
- upsertContact: db.prepare(`
286
- INSERT INTO contacts (phone, name, category, response_mode, style, instructions, source, introduced, qualification_score, pipeline_stage, updated_at)
287
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
288
- ON CONFLICT(phone) DO UPDATE SET
289
- name=excluded.name, category=excluded.category, response_mode=excluded.response_mode,
290
- style=excluded.style, instructions=excluded.instructions, source=excluded.source,
291
- introduced=excluded.introduced, qualification_score=excluded.qualification_score,
292
- pipeline_stage=excluded.pipeline_stage, updated_at=datetime('now')
293
- `),
294
- updateContactField: db.prepare("UPDATE contacts SET updated_at = datetime('now') WHERE phone = ?"),
295
- deleteContact: db.prepare('DELETE FROM contacts WHERE phone = ?'),
296
- markIntroduced: db.prepare("UPDATE contacts SET introduced = 1, updated_at = datetime('now') WHERE phone = ?"),
297
- updateQualification: db.prepare("UPDATE contacts SET qualification_score = ?, pipeline_stage = ?, updated_at = datetime('now') WHERE phone = ?"),
298
- updateCategory: db.prepare("UPDATE contacts SET category = ?, updated_at = datetime('now') WHERE phone = ?"),
299
-
300
- // ── Contact Context ──
301
- getContext: db.prepare('SELECT * FROM contact_context WHERE phone = ?'),
302
- upsertContext: db.prepare(`
303
- INSERT INTO contact_context (phone, relationship, last_topic, pending_items, conversation_summary, preferences, last_interaction)
304
- VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
305
- ON CONFLICT(phone) DO UPDATE SET
306
- relationship=excluded.relationship, last_topic=excluded.last_topic,
307
- pending_items=excluded.pending_items, conversation_summary=excluded.conversation_summary,
308
- preferences=excluded.preferences, last_interaction=datetime('now')
309
- `),
310
- deleteContext: db.prepare('DELETE FROM contact_context WHERE phone = ?'),
311
-
312
- // ── Contact Scopes ──
313
- getScope: db.prepare('SELECT * FROM contact_scopes WHERE phone = ? AND scope = ?'),
314
- getAllScopes: db.prepare('SELECT * FROM contact_scopes WHERE phone = ?'),
315
- upsertScope: db.prepare(`
316
- INSERT INTO contact_scopes (phone, scope, granted, granted_by, granted_at)
317
- VALUES (?, ?, ?, ?, datetime('now'))
318
- ON CONFLICT(phone, scope) DO UPDATE SET granted=excluded.granted, granted_by=excluded.granted_by, granted_at=datetime('now')
319
- `),
320
- deleteScope: db.prepare('DELETE FROM contact_scopes WHERE phone = ? AND scope = ?'),
321
- deleteScopesByPhone: db.prepare('DELETE FROM contact_scopes WHERE phone = ?'),
322
-
323
- // ── Conversation State ──
324
- getState: db.prepare('SELECT * FROM conversation_state WHERE phone = ?'),
325
- upsertState: db.prepare(`
326
- INSERT INTO conversation_state (phone, state, state_data, entered_at, expires_at)
327
- VALUES (?, ?, ?, datetime('now'), ?)
328
- ON CONFLICT(phone) DO UPDATE SET state=excluded.state, state_data=excluded.state_data,
329
- entered_at=datetime('now'), expires_at=excluded.expires_at
330
- `),
331
- clearExpiredStates: db.prepare("UPDATE conversation_state SET state = 'idle', state_data = '{}' WHERE expires_at IS NOT NULL AND expires_at < datetime('now')"),
332
-
333
- // ── Message Log ──
334
- insertMessage: db.prepare(`
335
- INSERT INTO message_log (phone, channel, direction, text, attachments, sent_by, state_at_time)
336
- VALUES (?, ?, ?, ?, ?, ?, ?)
337
- `),
338
- getRecentMessages: db.prepare('SELECT * FROM message_log WHERE phone = ? ORDER BY created_at DESC LIMIT ?'),
339
- getRecentMessagesAll: db.prepare(`
340
- SELECT m.*, COALESCE(c.name, m.phone) as contact_name
341
- FROM message_log m LEFT JOIN contacts c ON c.phone = m.phone
342
- ORDER BY m.created_at DESC LIMIT ?
343
- `),
344
- getLastOutbound: db.prepare("SELECT * FROM message_log WHERE phone = ? AND direction = 'outbound' ORDER BY created_at DESC LIMIT 1"),
345
- getLastInbound: db.prepare("SELECT * FROM message_log WHERE phone = ? AND direction = 'inbound' ORDER BY created_at DESC LIMIT 1"),
346
- getLastSentBy: db.prepare("SELECT sent_by FROM message_log WHERE phone = ? AND direction = 'outbound' AND sent_by != '' ORDER BY created_at DESC LIMIT 1"),
347
- countRecentOutbound: db.prepare("SELECT COUNT(*) as count FROM message_log WHERE phone = ? AND direction = 'outbound' AND sent_by = 'aiva' AND created_at >= datetime('now', '-' || ? || ' hours')"),
348
- countRecentFollowUps: db.prepare("SELECT COUNT(*) as count FROM message_log WHERE phone = ? AND direction = 'outbound' AND state_at_time = 'follow-up' AND created_at >= datetime('now', '-24 hours')"),
349
-
350
- // ── FAQ ──
351
- getAllFaq: db.prepare('SELECT * FROM faq_entries WHERE enabled = 1 ORDER BY category, id'),
352
- getFaqByDevice: db.prepare('SELECT * FROM faq_entries WHERE (device_id = ? OR device_id IS NULL) AND enabled = 1 ORDER BY category, id'),
353
- insertFaq: db.prepare('INSERT INTO faq_entries (device_id, question, answer, category, keywords) VALUES (?, ?, ?, ?, ?)'),
354
- updateFaq: db.prepare('UPDATE faq_entries SET question=?, answer=?, category=?, keywords=?, enabled=? WHERE id=?'),
355
- deleteFaq: db.prepare('DELETE FROM faq_entries WHERE id = ?'),
356
- getFaqById: db.prepare('SELECT * FROM faq_entries WHERE id = ?'),
357
-
358
- // ── Playbook ──
359
- getPlaybookSections: db.prepare('SELECT * FROM playbook_sections WHERE (device_id = ? OR device_id IS NULL) AND enabled = 1 ORDER BY priority, id'),
360
- getPlaybookByType: db.prepare('SELECT * FROM playbook_sections WHERE section_type = ? AND (device_id = ? OR device_id IS NULL) AND enabled = 1 ORDER BY priority'),
361
- insertPlaybook: db.prepare("INSERT INTO playbook_sections (device_id, section_type, title, content, priority) VALUES (?, ?, ?, ?, ?)"),
362
- updatePlaybook: db.prepare("UPDATE playbook_sections SET section_type=?, title=?, content=?, priority=?, enabled=?, version=version+1, updated_at=datetime('now') WHERE id=?"),
363
- deletePlaybook: db.prepare('DELETE FROM playbook_sections WHERE id = ?'),
364
- getPlaybookById: db.prepare('SELECT * FROM playbook_sections WHERE id = ?'),
365
-
366
- // ── Preferences ──
367
- getAllPreferences: db.prepare('SELECT * FROM preferences ORDER BY hit_count DESC, confidence DESC'),
368
- getPreferencesByScope: db.prepare('SELECT * FROM preferences WHERE (scope = ? OR scope = ?) AND confidence >= 0.5 ORDER BY confidence DESC, hit_count DESC LIMIT ?'),
369
- insertPreference: db.prepare(`
370
- INSERT INTO preferences (scope, question_pattern, answer, source, confidence)
371
- VALUES (?, ?, ?, ?, ?)
372
- `),
373
- updatePreference: db.prepare("UPDATE preferences SET answer=?, source=?, confidence=?, hit_count=hit_count+1, last_used_at=datetime('now'), updated_at=datetime('now') WHERE id=?"),
374
- bumpPreferenceHit: db.prepare("UPDATE preferences SET hit_count=hit_count+1, last_used_at=datetime('now') WHERE id=?"),
375
- deletePreference: db.prepare('DELETE FROM preferences WHERE id = ?'),
376
- decayPreferences: db.prepare("UPDATE preferences SET confidence = MAX(0.0, confidence - ?) WHERE source = ? AND updated_at < datetime('now', '-30 days')"),
377
-
378
- // ── Preference Log ──
379
- insertPreferenceLog: db.prepare('INSERT INTO preference_log (preference_id, escalation_id, phone, original_question, tier_resolved) VALUES (?, ?, ?, ?, ?)'),
380
-
381
- // ── Escalations ──
382
- insertEscalation: db.prepare(`
383
- INSERT INTO escalations (escalation_id, phone, trigger_message, context_sent, status, is_client_support, timeout_at)
384
- VALUES (?, ?, ?, ?, 'pending', ?, ?)
385
- `),
386
- getEscalation: db.prepare('SELECT * FROM escalations WHERE escalation_id = ?'),
387
- getActiveEscalation: db.prepare("SELECT * FROM escalations WHERE phone = ? AND status = 'pending' ORDER BY created_at DESC LIMIT 1"),
388
- resolveEscalation: db.prepare("UPDATE escalations SET status = 'responded', response_received = ?, responded_at = datetime('now') WHERE escalation_id = ?"),
389
- timeoutEscalation: db.prepare("UPDATE escalations SET status = 'timeout' WHERE escalation_id = ?"),
390
- failEscalation: db.prepare("UPDATE escalations SET status = 'failed' WHERE escalation_id = ?"),
391
- incrementStrike: db.prepare('UPDATE escalations SET strike_count = strike_count + 1 WHERE escalation_id = ?'),
392
- getTimedOutEscalations: db.prepare("SELECT * FROM escalations WHERE status = 'pending' AND timeout_at < datetime('now')"),
393
-
394
- // ── Follow-Up Tracker ──
395
- getActiveFollowUps: db.prepare("SELECT * FROM follow_up_tracker WHERE status = 'active' AND next_follow_up_at <= datetime('now') AND opted_out = 0"),
396
- getAllFollowUps: db.prepare("SELECT * FROM follow_up_tracker ORDER BY CASE status WHEN 'active' THEN 0 WHEN 'cold' THEN 1 WHEN 'paused' THEN 2 ELSE 3 END, next_follow_up_at ASC"),
397
- getFollowUpByPhone: db.prepare('SELECT * FROM follow_up_tracker WHERE phone = ?'),
398
- upsertFollowUp: db.prepare(`
399
- INSERT INTO follow_up_tracker (phone, channel, contact_name, last_our_message, last_our_message_at, status, follow_up_count, next_follow_up_at, updated_at)
400
- VALUES (?, ?, ?, ?, ?, ?, 0, ?, datetime('now'))
401
- ON CONFLICT(phone) DO UPDATE SET
402
- channel=excluded.channel, contact_name=excluded.contact_name,
403
- last_our_message=excluded.last_our_message, last_our_message_at=excluded.last_our_message_at,
404
- status=excluded.status, follow_up_count=0, next_follow_up_at=excluded.next_follow_up_at,
405
- updated_at=datetime('now')
406
- `),
407
- updateFollowUpStatus: db.prepare("UPDATE follow_up_tracker SET status = ?, updated_at = datetime('now') WHERE phone = ?"),
408
- incrementFollowUp: db.prepare("UPDATE follow_up_tracker SET follow_up_count = follow_up_count + 1, last_follow_up_at = datetime('now'), next_follow_up_at = ?, updated_at = datetime('now') WHERE phone = ?"),
409
- markFollowUpCold: db.prepare("UPDATE follow_up_tracker SET status = 'cold', updated_at = datetime('now') WHERE phone = ?"),
410
- updateFollowUpOptOut: db.prepare("UPDATE follow_up_tracker SET opted_out = ?, updated_at = datetime('now') WHERE phone = ?"),
411
- updateFollowUpTopic: db.prepare("UPDATE follow_up_tracker SET last_follow_up_topic = ?, updated_at = datetime('now') WHERE phone = ?"),
412
- deleteFollowUp: db.prepare('DELETE FROM follow_up_tracker WHERE phone = ?'),
413
-
414
- // ── Scheduling Rules ──
415
- getSchedulingRule: db.prepare('SELECT * FROM scheduling_rules WHERE category = ?'),
416
- upsertSchedulingRule: db.prepare("INSERT OR REPLACE INTO scheduling_rules (category, rule_preset, custom_instructions, structured_overrides, updated_at) VALUES (?, ?, ?, ?, datetime('now'))"),
417
- getAllSchedulingRules: db.prepare('SELECT * FROM scheduling_rules'),
418
-
419
- // ── Settings ──
420
- getSetting: db.prepare('SELECT value FROM settings WHERE key = ?'),
421
- setSetting: db.prepare('INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)'),
422
- getAllSettings: db.prepare('SELECT * FROM settings'),
423
- };
424
- }
425
-
426
- /**
427
- * Get a setting value by key.
428
- * @param {string} key
429
- * @returns {string}
430
- */
431
- function getSetting(key) {
432
- const row = stmts.getSetting.get(key);
433
- return row ? row.value : '';
434
- }
435
-
436
- /**
437
- * Set a setting value.
438
- * @param {string} key
439
- * @param {string} value
440
- */
441
- function setSetting(key, value) {
442
- stmts.setSetting.run(key, String(value));
443
- }
444
-
445
- /**
446
- * Get the raw database instance (for advanced queries).
447
- * @returns {import('better-sqlite3').Database}
448
- */
449
- function getDb() { return db; }
450
-
451
- /**
452
- * Get all prepared statements.
453
- * @returns {Object}
454
- */
455
- function getStmts() { return stmts; }
456
-
457
- module.exports = { initDatabase, getSetting, setSetting, getDb, getStmts };