@conversionpros/aiva 1.0.1 → 2.0.1

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 +9 -34
  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
@@ -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
- };