@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
@@ -1,129 +0,0 @@
1
- // ── AI Call Wrapper (Sonnet via OpenClaw proxy) ───────────
2
- 'use strict';
3
-
4
- const fs = require('fs');
5
- const path = require('path');
6
-
7
- const PROXY_URL = 'http://127.0.0.1:18789/v1/chat/completions';
8
- const CONFIG_PATH = path.join(process.env.HOME || '/Users/brandonburgan', '.openclaw', 'openclaw.json');
9
- const DEFAULT_MODEL = 'claude-sonnet-4-20250514';
10
-
11
- let _cachedPassword = null;
12
-
13
- function getOpenClawPassword() {
14
- if (_cachedPassword) return _cachedPassword;
15
- try {
16
- const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
17
- _cachedPassword = config?.gateway?.auth?.password || '';
18
- return _cachedPassword;
19
- } catch {
20
- return '';
21
- }
22
- }
23
-
24
- function log(msg, data) {
25
- const ts = new Date().toISOString();
26
- if (data) console.log(`[${ts}] [AI] ${msg}`, JSON.stringify(data));
27
- else console.log(`[${ts}] [AI] ${msg}`);
28
- }
29
-
30
- function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
31
-
32
- /**
33
- * Call Sonnet via the OpenClaw proxy with retry and backoff.
34
- * @param {Object} options
35
- * @param {Array<{role: string, content: string}>} options.messages - Chat messages
36
- * @param {Array<Object>} [options.tools] - Tool definitions for function calling
37
- * @param {string} [options.model] - Model override (default: latest Sonnet)
38
- * @param {number} [options.maxTokens=1000] - Max tokens
39
- * @param {number} [options.temperature=0.7] - Temperature
40
- * @param {number} [options.timeoutMs=60000] - Request timeout
41
- * @returns {Promise<{content: string|null, toolCalls: Array|null, usage: Object|null}>}
42
- */
43
- async function callSonnet({ messages, tools, model, maxTokens = 1000, temperature = 0.7, timeoutMs = 60000 }) {
44
- const password = getOpenClawPassword();
45
- if (!password) {
46
- log('No OpenClaw password found');
47
- return { content: null, toolCalls: null, usage: null };
48
- }
49
-
50
- const body = {
51
- model: model || DEFAULT_MODEL,
52
- max_tokens: maxTokens,
53
- temperature,
54
- messages,
55
- };
56
- if (tools && tools.length > 0) {
57
- body.tools = tools;
58
- }
59
-
60
- // Retry with exponential backoff
61
- const delays = [0, 5000, 15000, 30000, 60000];
62
- for (let attempt = 0; attempt < delays.length; attempt++) {
63
- if (attempt > 0) {
64
- log(`Retry ${attempt}/${delays.length - 1}, waiting ${delays[attempt] / 1000}s`);
65
- await sleep(delays[attempt]);
66
- }
67
-
68
- try {
69
- const resp = await fetch(PROXY_URL, {
70
- method: 'POST',
71
- headers: {
72
- 'Content-Type': 'application/json',
73
- 'Authorization': `Bearer ${password}`,
74
- },
75
- body: JSON.stringify(body),
76
- signal: AbortSignal.timeout(timeoutMs),
77
- });
78
-
79
- if (!resp.ok) {
80
- const errText = await resp.text().catch(() => 'unknown');
81
- log('Proxy HTTP error', { status: resp.status, body: errText.substring(0, 200), attempt });
82
- continue;
83
- }
84
-
85
- const data = await resp.json();
86
- const choice = data.choices?.[0];
87
- if (!choice) {
88
- log('No choices in response', { attempt });
89
- continue;
90
- }
91
-
92
- const message = choice.message;
93
- const content = message?.content?.trim() || null;
94
- const toolCalls = message?.tool_calls || null;
95
-
96
- return {
97
- content,
98
- toolCalls,
99
- usage: data.usage || null,
100
- };
101
- } catch (e) {
102
- log('Proxy request error', { error: e.message, attempt });
103
- continue;
104
- }
105
- }
106
-
107
- log('All retries exhausted');
108
- return { content: null, toolCalls: null, usage: null };
109
- }
110
-
111
- /**
112
- * Simple text-in/text-out AI call.
113
- * @param {string} systemPrompt - System prompt
114
- * @param {string} userMessage - User message
115
- * @param {Object} [opts] - Additional options (model, maxTokens, temperature)
116
- * @returns {Promise<string|null>} Response text or null on failure
117
- */
118
- async function callAI(systemPrompt, userMessage, opts = {}) {
119
- const result = await callSonnet({
120
- messages: [
121
- { role: 'system', content: systemPrompt },
122
- { role: 'user', content: userMessage },
123
- ],
124
- ...opts,
125
- });
126
- return result.content;
127
- }
128
-
129
- module.exports = { callSonnet, callAI, DEFAULT_MODEL };
@@ -1,52 +0,0 @@
1
- // ── Phone Number Normalization Utilities ──────────────────
2
- 'use strict';
3
-
4
- /**
5
- * Normalize a phone number to E.164 format.
6
- * Strips all non-digit characters (except leading +), ensures +1 prefix for US numbers.
7
- * @param {string} raw - Raw phone number input
8
- * @returns {string} E.164 normalized phone number (e.g. +15551234567)
9
- */
10
- function normalizePhone(raw) {
11
- if (!raw) return '';
12
- let cleaned = String(raw).replace(/[^\d+]/g, '');
13
- // Remove leading + to work with digits only
14
- const hasPlus = cleaned.startsWith('+');
15
- cleaned = cleaned.replace(/^\+/, '');
16
- // Remove leading 1 if 11 digits (US format)
17
- if (cleaned.length === 11 && cleaned.startsWith('1')) {
18
- return '+' + cleaned;
19
- }
20
- // 10 digits - assume US, add +1
21
- if (cleaned.length === 10) {
22
- return '+1' + cleaned;
23
- }
24
- // Already has country code or international
25
- if (hasPlus || cleaned.length > 10) {
26
- return '+' + cleaned;
27
- }
28
- // Fallback - return what we have with +
29
- return '+' + cleaned;
30
- }
31
-
32
- /**
33
- * Check if a phone number is valid (basic check - has enough digits).
34
- * @param {string} phone - Phone number (should be E.164)
35
- * @returns {boolean}
36
- */
37
- function isValidPhone(phone) {
38
- if (!phone) return false;
39
- const digits = phone.replace(/\D/g, '');
40
- return digits.length >= 10 && digits.length <= 15;
41
- }
42
-
43
- /**
44
- * Strip +1 prefix for display purposes.
45
- * @param {string} phone - E.164 phone number
46
- * @returns {string} Phone without country code
47
- */
48
- function stripCountryCode(phone) {
49
- return (phone || '').replace(/^\+1/, '');
50
- }
51
-
52
- module.exports = { normalizePhone, isValidPhone, stripCountryCode };
@@ -1,98 +0,0 @@
1
- // ── Response Validator - Haiku post-validation layer ──
2
- // Checks if Sonnet's response overpromises capabilities the contact doesn't have.
3
- 'use strict';
4
-
5
- const fs = require('fs');
6
- const path = require('path');
7
-
8
- const PROXY_URL = 'http://127.0.0.1:18789/v1/chat/completions';
9
- const MODEL = 'claude-haiku-4-20250514';
10
- const CONFIG_PATH = path.join(process.env.HOME || '/Users/brandonburgan', '.openclaw', 'openclaw.json');
11
-
12
- let _cachedPassword = null;
13
- function getPassword() {
14
- if (_cachedPassword) return _cachedPassword;
15
- try {
16
- const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
17
- _cachedPassword = config?.gateway?.auth?.password || '';
18
- return _cachedPassword;
19
- } catch { return ''; }
20
- }
21
-
22
- /**
23
- * Validate a Sonnet response against actual contact permissions.
24
- * Uses Haiku via the OpenClaw proxy to check if the response overpromises.
25
- * Fails open (returns valid) on any error.
26
- *
27
- * @param {string} responseText - Sonnet's generated response
28
- * @param {Object} permissions - What the contact CAN do
29
- * @param {boolean} permissions.canSchedule
30
- * @param {boolean} permissions.canCreateTask
31
- * @param {boolean} permissions.canCreateReminder
32
- * @param {boolean} permissions.canSendFile
33
- * @param {boolean} permissions.canSendMessage
34
- * @returns {Promise<{valid: boolean, reason: string|null}>}
35
- */
36
- async function validateResponse(responseText, permissions) {
37
- try {
38
- // Build list of denied capabilities
39
- const denied = [];
40
- if (!permissions.canSchedule) denied.push('scheduling/booking appointments/checking calendar availability');
41
- if (!permissions.canCreateTask) denied.push('creating tasks');
42
- if (!permissions.canCreateReminder) denied.push('setting reminders');
43
- if (!permissions.canSendFile) denied.push('sending files');
44
- if (!permissions.canSendMessage) denied.push('sending messages to others');
45
-
46
- // If everything is allowed, no need to validate
47
- if (denied.length === 0) return { valid: true, reason: null };
48
-
49
- const prompt = `You are a strict compliance validator. The assistant has these PROHIBITED capabilities: ${denied.join(', ')}.
50
-
51
- The assistant MUST NOT:
52
- - Promise, imply, or suggest it will do any prohibited action
53
- - Say it will "pass along", "let someone know", "check on that", "get back to you", or relay any request related to a prohibited capability
54
- - Mention Brandon or any human by name in relation to a prohibited capability
55
- - Acknowledge the request in any way that implies it will be handled
56
-
57
- If the response does ANY of the above, it is overpromising.
58
-
59
- Response to validate: "${responseText}"
60
-
61
- JSON only: {"overpromises": true/false, "capability": "which one or null"}`;
62
-
63
- const password = getPassword();
64
- const resp = await fetch(PROXY_URL, {
65
- method: 'POST',
66
- headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${password}` },
67
- body: JSON.stringify({
68
- model: MODEL,
69
- messages: [{ role: 'user', content: prompt }],
70
- max_tokens: 100,
71
- temperature: 0,
72
- }),
73
- signal: AbortSignal.timeout(10000),
74
- });
75
-
76
- if (!resp.ok) {
77
- console.log(`[${new Date().toISOString()}] [VALIDATOR] Proxy error ${resp.status} (fail-open)`);
78
- return { valid: true, reason: null };
79
- }
80
-
81
- const data = await resp.json();
82
- const text = data.choices?.[0]?.message?.content || '';
83
- const match = text.match(/\{[^}]+\}/);
84
- if (!match) return { valid: true, reason: null };
85
-
86
- const parsed = JSON.parse(match[0]);
87
- if (parsed.overpromises) {
88
- return { valid: false, reason: parsed.capability || 'unknown' };
89
- }
90
- return { valid: true, reason: null };
91
- } catch (err) {
92
- // Fail open
93
- console.log(`[${new Date().toISOString()}] [VALIDATOR] Error (fail-open): ${err.message}`);
94
- return { valid: true, reason: null };
95
- }
96
- }
97
-
98
- module.exports = { validateResponse };
@@ -1,222 +0,0 @@
1
- // ── Outbound Message Sanitization ─────────────────────────
2
- // Ported from v1 router.js - every regex, every block pattern.
3
- // This is the last line of defense before a message reaches a contact.
4
- 'use strict';
5
-
6
- // ── Block patterns - messages matching 2+ of these (or 1 strong) are blocked ──
7
- const OUTBOUND_BLOCK_PATTERNS = [
8
- // Command/tool output
9
- /gog\s+calendar/i,
10
- /GOG_KEYRING_PASSWORD/i,
11
- /openclaw\s+(cron|gateway|message|session)/i,
12
- /imsg\s+send/i,
13
- /execSync|exec\(|spawn\(/i,
14
- /curl\s+/i,
15
- /POST\s+\/api\//i,
16
- /GET\s+\/api\//i,
17
- /localhost:\d+/i,
18
- /127\.0\.0\.1/i,
19
- /\$ \w+/, // shell prompts
20
- // Error output
21
- /Error:|ERR!|ECONNREFUSED|ETIMEDOUT|ENOENT|stack trace/i,
22
- /at\s+\w+\s+\(.*:\d+:\d+\)/, // stack trace lines
23
- /Command failed|exit code|stderr/i,
24
- // Internal reasoning / chain-of-thought
25
- /<thinking>[\s\S]*?<\/thinking>/i,
26
- /\[ROUTER[\-:]|ROUTER-ALERT/i,
27
- /\[Actionable:|CALENDAR_REQUEST|REMINDER_REQUEST|TASK_REQUEST/,
28
- /escalat(?:ion|e|ing)\s+(?:to|code|request)/i,
29
- /pending.action|pending_items|pending_request/i,
30
- /system\s*prompt|chain.of.thought/i,
31
- /HEARTBEAT_OK|HEARTBEAT\.md/i,
32
- /AGENTS\.md|IDENTITY\.md|MEMORY\.md|TOOLS\.md/i,
33
- /sub-?agent|subagent|spawned.*agent/i,
34
- /webhook.*openclaw|openclaw.*webhook/i,
35
- /callback.*url|callbackUrl/i,
36
- // Schedule/calendar internals (not human-facing)
37
- /available\s+slots?\s*:/i,
38
- /Brandon'?s\s+(?:full\s+)?schedule/i,
39
- // API keys and tokens
40
- /sk-ant-|Bearer\s+\w{20,}|eyJ[A-Za-z0-9_-]{20,}/i,
41
- /x-aiva-internal/i,
42
- // JSON blobs (likely internal data)
43
- /\{"(?:status|error|requestId|phone|pending|action)":/,
44
- ];
45
-
46
- // Strong patterns - a single match is enough to block
47
- const STRONG_BLOCK_PATTERNS = [
48
- /GOG_KEYRING_PASSWORD/i,
49
- /sk-ant-|Bearer\s+\w{20,}/i,
50
- /<thinking>[\s\S]*?<\/thinking>/i,
51
- /x-aiva-internal/i,
52
- /HEARTBEAT_OK|HEARTBEAT\.md/i,
53
- /AGENTS\.md|IDENTITY\.md|TOOLS\.md/i,
54
- /at\s+\w+\s+\(.*:\d+:\d+\)/,
55
- /\{"(?:status|error|requestId|phone|pending|action)":/,
56
- /openclaw/i,
57
- ];
58
-
59
- // Error message patterns
60
- const ERROR_PATTERNS = [
61
- /no response from openclaw/i,
62
- /openclaw.*error/i,
63
- /internal server error/i,
64
- /ECONNREFUSED/i,
65
- /agent.*timeout/i,
66
- /session.*failed/i,
67
- /tool.*error/i,
68
- ];
69
-
70
- // Chain-of-thought patterns to strip from responses
71
- const COT_PATTERNS = [
72
- /(?:Wait,?\s+(?:I need to|let me|I should|actually))[^\n]*/gi,
73
- /(?:Let me (?:re-?read|reconsider|re-?think|re-?evaluate|look at))[^\n]*/gi,
74
- /(?:Actually,?\s+(?:I think|let me|I should|on second thought))[^\n]*/gi,
75
- /(?:Hmm,?\s+(?:I think|let me|I should|looking at))[^\n]*/gi,
76
- /(?:On second thought)[^\n]*/gi,
77
- /(?:I need to (?:reconsider|re-?think|re-?evaluate|check|be careful))[^\n]*/gi,
78
- /(?:I'm responding as (?:Brandon's|an?) (?:assistant|AI))[^\n]*/gi,
79
- ];
80
-
81
- // Internal directive patterns to strip
82
- const INTERNAL_PATTERNS = [
83
- /(?:I should\s)[^\n]*/gi,
84
- /(?:I need to\s)[^\n]*/gi,
85
- /(?:Looking at the conversation)[^\n]*/gi,
86
- /(?:Analyzing (?:the |this ))[^\n]*/gi,
87
- /(?:Based on (?:the |my )(?:instructions|rules|system prompt|context))[^\n]*/gi,
88
- /(?:The (?:user|contact|sender) (?:is asking|wants|seems))[^\n]*/gi,
89
- /(?:My (?:task|job|role|goal) (?:is|here is))[^\n]*/gi,
90
- ];
91
-
92
- // Sensitive line patterns for stripping from multi-part responses
93
- const SENSITIVE_LINE_PATTERNS = [
94
- /api[_-]?key/i, /password/i, /token/i, /secret/i, /openclaw/i,
95
- /localhost/i, /127\.0\.0\.1/i, /\.openclaw\//i, /curl\s/i,
96
- /sk-ant-/i, /sk-or-/i, /ghp_/i, /xai-/i, /r8_/i,
97
- /supabase/i, /cloudflare/i, /twilio/i, /replicate/i,
98
- /SID:/i, /Base64:/i, /\.json/i, /PM2/i, /ngrok/i,
99
- /sessions_send/i, /sessions_spawn/i, /heartbeat/i,
100
- /AGENTS\.md/i, /HEARTBEAT\.md/i, /MEMORY\.md/i, /TOOLS\.md/i,
101
- /task.*board/i, /cron.*job/i, /gateway/i,
102
- ];
103
-
104
- /**
105
- * Check if a message contains internal content that should NEVER reach a contact.
106
- * @param {string} text - Message text to check
107
- * @returns {boolean} True if the message contains internal content
108
- */
109
- function isInternalContent(text) {
110
- if (!text) return false;
111
- const matchCount = OUTBOUND_BLOCK_PATTERNS.reduce((count, p) => count + (p.test(text) ? 1 : 0), 0);
112
- const strongMatch = STRONG_BLOCK_PATTERNS.some(p => p.test(text));
113
- return strongMatch || matchCount >= 2;
114
- }
115
-
116
- /**
117
- * Check if a message is an error message from internal systems.
118
- * @param {string} text - Message text to check
119
- * @returns {boolean}
120
- */
121
- function isErrorMessage(text) {
122
- if (!text) return false;
123
- return ERROR_PATTERNS.some(p => p.test(text));
124
- }
125
-
126
- /**
127
- * Sanitize a response before sending to a contact.
128
- * Strips chain-of-thought, internal directives, em dashes, and other artifacts.
129
- * Returns null if the message should be suppressed entirely.
130
- * @param {string} text - Raw response text
131
- * @returns {string|null} Sanitized text, or null if message should be suppressed
132
- */
133
- function sanitizeResponse(text) {
134
- if (!text) return null;
135
- let result = text;
136
-
137
- // 1. Strip thinking blocks
138
- result = result.replace(/<thinking>[\s\S]*?<\/thinking>/gi, '');
139
-
140
- // 2. NO_REPLY anywhere in text - suppress entire message
141
- if (/NO_REPLY/i.test(result)) return null;
142
-
143
- // 3. Strip chain-of-thought patterns
144
- for (const pat of COT_PATTERNS) {
145
- result = result.replace(pat, '');
146
- }
147
-
148
- // 4. Strip internal directive lines
149
- for (const pat of INTERNAL_PATTERNS) {
150
- result = result.replace(pat, '');
151
- }
152
-
153
- // 5. Handle multiple draft concatenation - take last clean block
154
- const draftSplitters = /(?:Wait|Actually|Let me re|Hmm|On second thought)[^\n]*\n/i;
155
- if (draftSplitters.test(result)) {
156
- const parts = result.split(draftSplitters).map(p => p.trim()).filter(Boolean);
157
- if (parts.length > 1) {
158
- result = parts[parts.length - 1];
159
- }
160
- }
161
-
162
- // 6. Replace em dashes, en dashes, and horizontal bars with commas (AI artifact, looks unnatural in texts)
163
- result = result.replace(/[\u2013\u2014\u2015]/g, ',').replace(/ , /g, ', ');
164
-
165
- // 7. Final cleanup
166
- result = result.replace(/\n{3,}/g, '\n\n').trim();
167
- if (!result || result.length === 0) return null;
168
-
169
- return result;
170
- }
171
-
172
- /**
173
- * Strip sensitive lines from multi-part text (e.g. conversation history assembly).
174
- * @param {string} text - Text that may contain sensitive lines
175
- * @returns {string} Text with sensitive lines removed
176
- */
177
- function stripSensitiveLines(text) {
178
- if (!text) return '';
179
- const lines = text.split('\n');
180
- return lines.filter(line => !SENSITIVE_LINE_PATTERNS.some(p => p.test(line))).join('\n');
181
- }
182
-
183
- /**
184
- * Full sanitization pipeline for outbound messages.
185
- * Combines sanitizeResponse + isInternalContent + isErrorMessage checks.
186
- * @param {string} text - Raw outbound message
187
- * @param {boolean} isMaster - Whether sending to the master phone (less restrictive)
188
- * @returns {{ text: string|null, blocked: boolean, reason: string }}
189
- */
190
- function sanitizeOutbound(text, isMaster = false) {
191
- if (!text) return { text: null, blocked: true, reason: 'empty' };
192
-
193
- // Sanitize first
194
- let sanitized = sanitizeResponse(text);
195
- if (!sanitized) return { text: null, blocked: true, reason: 'sanitizer_null' };
196
-
197
- // For non-master phones, check for internal content and errors
198
- if (!isMaster) {
199
- if (isInternalContent(sanitized)) {
200
- return { text: null, blocked: true, reason: 'internal_content' };
201
- }
202
- if (isErrorMessage(sanitized)) {
203
- return { text: null, blocked: true, reason: 'error_message' };
204
- }
205
- }
206
-
207
- // Clean up escape sequences and extra whitespace for text messages
208
- sanitized = sanitized.replace(/\\n/g, '\n').replace(/\\t/g, ' ');
209
- sanitized = sanitized.replace(/\n{3,}/g, '\n\n').trim();
210
-
211
- return { text: sanitized, blocked: false, reason: null };
212
- }
213
-
214
- module.exports = {
215
- isInternalContent,
216
- isErrorMessage,
217
- sanitizeResponse,
218
- stripSensitiveLines,
219
- sanitizeOutbound,
220
- OUTBOUND_BLOCK_PATTERNS,
221
- STRONG_BLOCK_PATTERNS,
222
- };