@conversionpros/aiva 1.0.1 → 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 (149) 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/constants.js +72 -0
  5. package/lib/launch-agent.js +112 -0
  6. package/lib/prerequisites.js +236 -0
  7. package/lib/process.js +59 -145
  8. package/lib/setup.js +224 -194
  9. package/lib/validate.js +194 -0
  10. package/package.json +7 -32
  11. package/auto-deploy.js +0 -190
  12. package/cli-sync.js +0 -126
  13. package/d2a-prompt-template.txt +0 -106
  14. package/diagnostics-api.js +0 -304
  15. package/docs/ara-dedup-fix-scope.md +0 -112
  16. package/docs/ara-fix-round2-scope.md +0 -61
  17. package/docs/ara-greeting-fix-scope.md +0 -70
  18. package/docs/calendar-date-fix-scope.md +0 -28
  19. package/docs/getting-started.md +0 -115
  20. package/docs/network-architecture-rollout-scope.md +0 -43
  21. package/docs/scope-google-oauth-integration.md +0 -351
  22. package/docs/settings-page-scope.md +0 -50
  23. package/docs/xai-imagine-scope.md +0 -116
  24. package/docs/xai-voice-integration-scope.md +0 -115
  25. package/docs/xai-voice-tools-scope.md +0 -165
  26. package/email-router.js +0 -512
  27. package/follow-up-handler.js +0 -606
  28. package/gateway-monitor.js +0 -158
  29. package/google-email.js +0 -379
  30. package/google-oauth.js +0 -310
  31. package/grok-imagine.js +0 -97
  32. package/health-reporter.js +0 -287
  33. package/invisible-prefix-base.txt +0 -206
  34. package/invisible-prefix-owner.txt +0 -26
  35. package/invisible-prefix-slim.txt +0 -10
  36. package/invisible-prefix.txt +0 -43
  37. package/knowledge-base.js +0 -472
  38. package/lib/cli.js +0 -19
  39. package/lib/server.js +0 -42
  40. package/meta-capi.js +0 -206
  41. package/meta-leads.js +0 -411
  42. package/notion-oauth.js +0 -323
  43. package/public/agent-config.html +0 -241
  44. package/public/aiva-avatar-anime.png +0 -0
  45. package/public/css/docs.css.bak +0 -688
  46. package/public/css/onboarding.css +0 -543
  47. package/public/diagrams/claude-subscription-pool.html +0 -329
  48. package/public/diagrams/claude-subscription-pool.png +0 -0
  49. package/public/docs-icon.png +0 -0
  50. package/public/escalation.html +0 -237
  51. package/public/group-config.html +0 -300
  52. package/public/icon-192.png +0 -0
  53. package/public/icon-512.png +0 -0
  54. package/public/icons/agents.svg +0 -1
  55. package/public/icons/attach.svg +0 -1
  56. package/public/icons/characters.svg +0 -1
  57. package/public/icons/chat.svg +0 -1
  58. package/public/icons/docs.svg +0 -1
  59. package/public/icons/heartbeat.svg +0 -1
  60. package/public/icons/messages.svg +0 -1
  61. package/public/icons/mic.svg +0 -1
  62. package/public/icons/notes.svg +0 -1
  63. package/public/icons/settings.svg +0 -1
  64. package/public/icons/tasks.svg +0 -1
  65. package/public/images/onboarding/p0-communication-layer.png +0 -0
  66. package/public/images/onboarding/p0-infinite-surface.png +0 -0
  67. package/public/images/onboarding/p0-learning-model.png +0 -0
  68. package/public/images/onboarding/p0-meet-aiva.png +0 -0
  69. package/public/images/onboarding/p4-contact-intelligence.png +0 -0
  70. package/public/images/onboarding/p4-context-compounds.png +0 -0
  71. package/public/images/onboarding/p4-message-router.png +0 -0
  72. package/public/images/onboarding/p4-per-contact-rules.png +0 -0
  73. package/public/images/onboarding/p4-send-messages.png +0 -0
  74. package/public/images/onboarding/p6-be-precise.png +0 -0
  75. package/public/images/onboarding/p6-review-escalations.png +0 -0
  76. package/public/images/onboarding/p6-voice-input.png +0 -0
  77. package/public/images/onboarding/p7-completion.png +0 -0
  78. package/public/index.html +0 -11594
  79. package/public/js/onboarding.js +0 -699
  80. package/public/manifest.json +0 -24
  81. package/public/messages-v2.html +0 -2824
  82. package/public/permission-approve.html.bak +0 -107
  83. package/public/permissions.html +0 -150
  84. package/public/styles/design-system.css +0 -68
  85. package/router-db.js +0 -604
  86. package/router-utils.js +0 -28
  87. package/router-v2/adapters/imessage.js +0 -191
  88. package/router-v2/adapters/quo.js +0 -82
  89. package/router-v2/adapters/whatsapp.js +0 -192
  90. package/router-v2/contact-manager.js +0 -234
  91. package/router-v2/conversation-engine.js +0 -498
  92. package/router-v2/data/knowledge-base.json +0 -176
  93. package/router-v2/data/router-v2.db +0 -0
  94. package/router-v2/data/router-v2.db-shm +0 -0
  95. package/router-v2/data/router-v2.db-wal +0 -0
  96. package/router-v2/data/router.db +0 -0
  97. package/router-v2/db.js +0 -457
  98. package/router-v2/escalation-bridge.js +0 -540
  99. package/router-v2/follow-up-engine.js +0 -347
  100. package/router-v2/index.js +0 -441
  101. package/router-v2/ingestion.js +0 -213
  102. package/router-v2/knowledge-base.js +0 -231
  103. package/router-v2/lead-qualifier.js +0 -152
  104. package/router-v2/learning-loop.js +0 -202
  105. package/router-v2/outbound-sender.js +0 -160
  106. package/router-v2/package.json +0 -13
  107. package/router-v2/permission-gate.js +0 -86
  108. package/router-v2/playbook.js +0 -177
  109. package/router-v2/prompts/base.js +0 -52
  110. package/router-v2/prompts/first-contact.js +0 -38
  111. package/router-v2/prompts/lead-qualification.js +0 -37
  112. package/router-v2/prompts/scheduling.js +0 -72
  113. package/router-v2/prompts/style-overrides.js +0 -22
  114. package/router-v2/scheduler.js +0 -301
  115. package/router-v2/scripts/migrate-v1-to-v2.js +0 -215
  116. package/router-v2/scripts/seed-faq.js +0 -67
  117. package/router-v2/seed-knowledge-base.js +0 -39
  118. package/router-v2/utils/ai.js +0 -129
  119. package/router-v2/utils/phone.js +0 -52
  120. package/router-v2/utils/response-validator.js +0 -98
  121. package/router-v2/utils/sanitize.js +0 -222
  122. package/router.js +0 -5005
  123. package/routes/google-calendar.js +0 -186
  124. package/scripts/deploy.sh +0 -62
  125. package/scripts/macos-calendar.sh +0 -232
  126. package/scripts/onboard-device.sh +0 -466
  127. package/server.js +0 -5131
  128. package/start.sh +0 -24
  129. package/templates/AGENTS.md +0 -548
  130. package/templates/IDENTITY.md +0 -15
  131. package/templates/docs-agents.html +0 -132
  132. package/templates/docs-app.html +0 -130
  133. package/templates/docs-home.html +0 -83
  134. package/templates/docs-imessage.html +0 -121
  135. package/templates/docs-tasks.html +0 -123
  136. package/templates/docs-tips.html +0 -175
  137. package/templates/getting-started.html +0 -809
  138. package/templates/invisible-prefix-base.txt +0 -171
  139. package/templates/invisible-prefix-owner.txt +0 -282
  140. package/templates/invisible-prefix.txt +0 -338
  141. package/templates/manifest.json +0 -61
  142. package/templates/memory-org/clients.md +0 -7
  143. package/templates/memory-org/credentials.md +0 -9
  144. package/templates/memory-org/devices.md +0 -7
  145. package/templates/updates.html +0 -464
  146. package/tts-proxy.js +0 -96
  147. package/voice-call-local.js +0 -731
  148. package/voice-call.js +0 -732
  149. 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 };