twinclaw 1.1.1 → 1.1.2

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 (35) hide show
  1. package/README.md +3 -3
  2. package/dist/api/handlers/agents.js +82 -0
  3. package/dist/api/handlers/debug.js +69 -0
  4. package/dist/api/handlers/devices.js +79 -0
  5. package/dist/api/handlers/jobs.js +99 -0
  6. package/dist/api/handlers/status.js +149 -0
  7. package/dist/api/router.js +272 -2
  8. package/dist/api/runtime-event-producer.js +20 -0
  9. package/dist/api/shared.js +34 -0
  10. package/dist/api/websocket-hub.js +18 -7
  11. package/dist/config/json-config.js +393 -1
  12. package/dist/core/heartbeat.js +304 -8
  13. package/dist/core/lane-executor.js +18 -0
  14. package/dist/core/onboarding.js +2 -2
  15. package/dist/core/self-improve-cli.js +212 -0
  16. package/dist/core/simplified-onboarding.js +158 -16
  17. package/dist/index.js +61 -33
  18. package/dist/interfaces/dispatcher.js +4 -2
  19. package/dist/interfaces/whatsapp_handler.js +243 -2
  20. package/dist/services/auto-configurer.js +591 -0
  21. package/dist/services/device-pairing.js +378 -0
  22. package/dist/services/hooks.js +130 -0
  23. package/dist/services/job-scheduler.js +133 -1
  24. package/dist/services/learning-system.js +226 -0
  25. package/dist/services/self-healing.js +267 -0
  26. package/dist/services/skill-acquisition/intent-parser.js +115 -0
  27. package/dist/services/skill-acquisition/research-engine.js +319 -0
  28. package/dist/services/skill-builder.js +235 -0
  29. package/dist/services/sub-agent-service.js +215 -0
  30. package/dist/services/web-service.js +70 -0
  31. package/dist/services/webhook-service.js +127 -0
  32. package/dist/skills/builtin.js +827 -0
  33. package/dist/tools/agent-improvement.js +208 -0
  34. package/dist/types/skill-acquisition.js +4 -0
  35. package/package.json +3 -1
@@ -1,6 +1,7 @@
1
1
  import * as readline from 'readline';
2
2
  import { randomBytes } from 'node:crypto';
3
3
  import { getConfigPath, readConfig, writeConfig, } from '../config/config-loader.js';
4
+ import { BUILTIN_WINDOWS_SKILLS } from '../config/json-config.js';
4
5
  import { initializeWorkspace, getWorkspaceDir } from '../config/workspace.js';
5
6
  import { ensureIdentityFiles } from '../config/identity-bootstrap.js';
6
7
  import { createSession } from '../services/db.js';
@@ -107,8 +108,17 @@ async function runWhatsAppQRLogin() {
107
108
  }
108
109
  export async function runSimplifiedOnboarding() {
109
110
  console.clear();
110
- console.log('═══════════════════════════════════════════════════════════');
111
- console.log(' 🎉 Welcome to TwinClaw Setup! ');
111
+ console.log(`
112
+ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
113
+ ██░▄▄▄░██░▄▄░██░▄▄▄██░▀██░██░▄▄▀██░████░▄▄▀██░███░██
114
+ ██░███░██░▀▀░██░▄▄▄██░█░█░██░█████░████░▀▀░██░█░█░██
115
+ ██░▀▀▀░██░█████░▀▀▀██░██▄░██░▀▀▄██░▀▀░█░██░██▄▀▄▀▄██
116
+ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
117
+ 🦅 TWINCLAW 🦅
118
+
119
+ TwinClaw — Your Agentic AI Gateway
120
+ Multi-Modal Hooks | Proactive Memory | Voice-First
121
+ `);
112
122
  console.log('═══════════════════════════════════════════════════════════\n');
113
123
  await initializeWorkspace();
114
124
  const configPath = getConfigPath();
@@ -161,15 +171,52 @@ export async function runSimplifiedOnboarding() {
161
171
  console.log('\n═══════════════════════════════════════════════════════════');
162
172
  console.log(' STEP 4: Telegram Setup');
163
173
  console.log('═══════════════════════════════════════════════════════════\n');
164
- console.log('To get a Telegram Bot Token:');
165
- console.log(' 1. Open Telegram @BotFather');
166
- console.log(' 2. Send /newbot');
167
- console.log(' 3. Follow instructions and copy the token\n');
168
- const telegramToken = await promptSecret('Telegram Bot Token: ');
169
- const telegramUserId = await prompt('Telegram User ID (your account): ');
174
+ console.log('┌────────────────────────────────────────────────────────────────┐');
175
+ console.log(' How to get a Telegram Bot Token: │');
176
+ console.log('│ │');
177
+ console.log(' 1. Open Telegram and chat with @BotFather │');
178
+ console.log('│ 2. Send /newbot (or /mybots) │');
179
+ console.log('│ 3. Follow instructions and copy the token │');
180
+ console.log('│ (looks like 123456:ABC-DEF...) │');
181
+ console.log('│ │');
182
+ console.log('│ Tip: You can also set TELEGRAM_BOT_TOKEN in your env. │');
183
+ console.log('│ Docs: https://docs.twinclaw.ai/telegram │');
184
+ console.log('└────────────────────────────────────────────────────────────────┘\n');
185
+ const telegramToken = await promptSecret('Enter Telegram bot token: ');
186
+ console.log('\n┌────────────────────────────────────────────────────────────────┐');
187
+ console.log('│ Telegram DM Access Policy: │');
188
+ console.log('│ │');
189
+ console.log('│ pairing - Unknown DMs get a pairing code (recommended) │');
190
+ console.log('│ allowlist - Only specific users can message │');
191
+ console.log('│ open - Anyone can message │');
192
+ console.log('│ │');
193
+ console.log('│ To approve: twinclaw pairing approve telegram <code> │');
194
+ console.log('│ Docs: https://docs.twinclaw.ai/pairing │');
195
+ console.log('└────────────────────────────────────────────────────────────────┘\n');
196
+ const dmPolicyChoice = await prompt('DM policy [allowlist]: ');
197
+ const dmPolicies = {
198
+ '1': 'pairing', 'pairing': 'pairing',
199
+ '2': 'allowlist', 'allowlist': 'allowlist',
200
+ '3': 'open', 'open': 'open'
201
+ };
202
+ const dmPolicy = dmPolicies[dmPolicyChoice] || 'allowlist';
203
+ let telegramUserId = null;
204
+ if (dmPolicy === 'allowlist') {
205
+ console.log('\n┌────────────────────────────────────────────────────────────────┐');
206
+ console.log('│ How to find your Telegram User ID: │');
207
+ console.log('│ │');
208
+ console.log('│ 1. DM your bot, then check twinclaw logs (safest) │');
209
+ console.log('│ 2. Call: https://api.telegram.org/bot<token>/getUpdates │');
210
+ console.log('│ 3. Third-party: DM @userinfobot or @getidsbot │');
211
+ console.log('│ │');
212
+ console.log('│ Docs: https://docs.twinclaw.ai/telegram │');
213
+ console.log('└────────────────────────────────────────────────────────────────┘\n');
214
+ const userIdInput = await prompt('Enter Telegram User ID (your account): ');
215
+ telegramUserId = userIdInput ? userIdInput : null;
216
+ }
170
217
  config.messaging = config.messaging || {
171
- telegram: { enabled: false, botToken: '', userId: null },
172
- whatsapp: { enabled: false, phoneNumber: '' },
218
+ telegram: { enabled: false, botToken: '', userId: null, dmPolicy: 'allowlist', groupPolicy: 'allowlist', allowFrom: [] },
219
+ whatsapp: { enabled: false, phoneNumber: '', dmPolicy: 'allowlist', groupPolicy: 'allowlist', allowFrom: [] },
173
220
  voice: { groqApiKey: '' },
174
221
  inbound: { enabled: false, debounceMs: 0 },
175
222
  streaming: {
@@ -185,19 +232,59 @@ export async function runSimplifiedOnboarding() {
185
232
  enabled: !!telegramToken,
186
233
  botToken: telegramToken,
187
234
  userId: telegramUserId ? parseInt(telegramUserId, 10) : null,
235
+ dmPolicy,
236
+ groupPolicy: 'allowlist',
237
+ allowFrom: telegramUserId ? [telegramUserId] : [],
188
238
  };
239
+ console.log('\n✅ Telegram configured!\n');
189
240
  }
190
241
  if (useWhatsApp) {
191
242
  console.log('\n═══════════════════════════════════════════════════════════');
192
243
  console.log(' STEP 5: WhatsApp Setup');
193
244
  console.log('═══════════════════════════════════════════════════════════\n');
245
+ console.log('┌────────────────────────────────────────────────────────────────┐');
246
+ console.log('│ How to link WhatsApp: │');
247
+ console.log('│ │');
248
+ console.log('│ 1. Open WhatsApp on your phone │');
249
+ console.log('│ 2. Tap Menu (⋮) → Linked Devices │');
250
+ console.log('│ 3. Tap "Link a Device" and scan the QR code │');
251
+ console.log('│ │');
252
+ console.log('│ Note: WhatsApp session persists after setup. │');
253
+ console.log('│ Docs: https://docs.twinclaw.ai/whatsapp │');
254
+ console.log('└────────────────────────────────────────────────────────────────┘\n');
194
255
  const startWhatsApp = await promptYesNo('Start WhatsApp QR code login now?', true);
195
256
  if (startWhatsApp) {
257
+ console.log('\n');
196
258
  const success = await runWhatsAppQRLogin();
197
259
  if (success) {
260
+ console.log('\n┌────────────────────────────────────────────────────────────────┐');
261
+ console.log('│ WhatsApp DM Access Policy: │');
262
+ console.log('│ │');
263
+ console.log('│ allowlist - Only specific numbers can message (recommended) │');
264
+ console.log('│ open - Anyone can message │');
265
+ console.log('│ │');
266
+ console.log('│ To approve: twinclaw pairing approve whatsapp <code> │');
267
+ console.log('│ Docs: https://docs.twinclaw.ai/pairing │');
268
+ console.log('└────────────────────────────────────────────────────────────────┘\n');
269
+ const waDmPolicyChoice = await prompt('DM policy [allowlist]: ');
270
+ const waDmPolicies = {
271
+ '1': 'allowlist', 'allowlist': 'allowlist',
272
+ '2': 'open', 'open': 'open'
273
+ };
274
+ const waDmPolicy = waDmPolicies[waDmPolicyChoice] || 'allowlist';
275
+ let waAllowFrom = [];
276
+ if (waDmPolicy === 'allowlist') {
277
+ console.log('\nEnter phone numbers that can message TwinClaw:');
278
+ console.log(' Format: +1234567890 (E.164 format)');
279
+ console.log(' Separate multiple numbers with commas\n');
280
+ const allowFromInput = await prompt('Phone numbers: ');
281
+ waAllowFrom = allowFromInput
282
+ ? allowFromInput.split(',').map(p => p.trim()).filter(p => p)
283
+ : [];
284
+ }
198
285
  config.messaging = config.messaging || {
199
- telegram: { enabled: false, botToken: '', userId: null },
200
- whatsapp: { enabled: false, phoneNumber: '' },
286
+ telegram: { enabled: false, botToken: '', userId: null, dmPolicy: 'pairing', groupPolicy: 'allowlist', allowFrom: [] },
287
+ whatsapp: { enabled: false, phoneNumber: '', dmPolicy: 'allowlist', groupPolicy: 'allowlist', allowFrom: [] },
201
288
  voice: { groqApiKey: '' },
202
289
  inbound: { enabled: false, debounceMs: 0 },
203
290
  streaming: {
@@ -212,29 +299,84 @@ export async function runSimplifiedOnboarding() {
212
299
  config.messaging.whatsapp = {
213
300
  enabled: true,
214
301
  phoneNumber: '',
302
+ dmPolicy: waDmPolicy,
303
+ groupPolicy: 'allowlist',
304
+ allowFrom: waAllowFrom,
215
305
  };
306
+ console.log('\n✅ WhatsApp configured!\n');
216
307
  }
217
308
  }
218
309
  }
219
310
  console.log('\n═══════════════════════════════════════════════════════════');
220
- console.log(' STEP 6: Voice (Optional)');
311
+ console.log(' STEP 6: Skills (Windows-Focused)');
312
+ console.log('═══════════════════════════════════════════════════════════\n');
313
+ console.log('Skills extend TwinClaw with additional capabilities.\n');
314
+ console.log('Available Windows Skills:');
315
+ console.log('─'.repeat(60));
316
+ const windowsSkills = BUILTIN_WINDOWS_SKILLS.filter(s => s.autoInstall || s.os === 'windows' || s.os === 'all');
317
+ const productivitySkills = windowsSkills.filter(s => s.category === 'productivity');
318
+ const systemSkills = windowsSkills.filter(s => s.category === 'system');
319
+ const utilSkills = windowsSkills.filter(s => s.category === 'utilities');
320
+ const devSkills = windowsSkills.filter(s => s.category === 'development');
321
+ console.log('\n📦 Productivity (auto-enabled):');
322
+ for (const skill of productivitySkills) {
323
+ console.log(` ✓ ${skill.name} - ${skill.description}`);
324
+ }
325
+ console.log('\n🖥️ System (Windows):');
326
+ for (const skill of systemSkills.slice(0, 8)) {
327
+ console.log(` ○ ${skill.name} - ${skill.description}`);
328
+ }
329
+ console.log('\n🔧 Development:');
330
+ for (const skill of devSkills) {
331
+ console.log(` ○ ${skill.name} - ${skill.description}`);
332
+ }
333
+ console.log('\n📱 Utilities:');
334
+ for (const skill of utilSkills.slice(0, 6)) {
335
+ console.log(` ○ ${skill.name} - ${skill.description}`);
336
+ }
337
+ console.log('\n' + '─'.repeat(60));
338
+ console.log(`Total: ${windowsSkills.length} skills available`);
339
+ console.log('\nSkills can be configured later with: twinclaw skills list');
340
+ await promptYesNo('\nContinue (skills can be configured later)?', true);
341
+ console.log('\n═══════════════════════════════════════════════════════════');
342
+ console.log(' STEP 7: 🤖 Self-Improving Agent');
343
+ console.log('═══════════════════════════════════════════════════════════\n');
344
+ console.log('TwinClaw can automatically improve itself!\n');
345
+ console.log('🧠 Auto-Learning: Learns from successes and failures');
346
+ console.log('💚 Self-Healing: Auto-detects and fixes issues');
347
+ console.log('🔧 Auto-Config: Connects to new services automatically');
348
+ console.log('🧱 Skill Builder: Creates new skills on demand\n');
349
+ console.log('┌────────────────────────────────────────────────────────────────┐');
350
+ console.log('│ Supported Integrations (auto-configure): │');
351
+ console.log('│ │');
352
+ console.log('│ Slack, Discord, Notion, GitHub, Jira, Linear, Spotify, │');
353
+ console.log('│ OpenWeather, Twilio, and more... │');
354
+ console.log('│ │');
355
+ console.log('│ Just ask: "Connect to Slack" or "Add GitHub integration" │');
356
+ console.log('└────────────────────────────────────────────────────────────────┘\n');
357
+ const enableSelfImprovement = await promptYesNo('Enable self-improving features?', true);
358
+ console.log('\n═══════════════════════════════════════════════════════════');
359
+ console.log(' STEP 8: Voice (Optional)');
221
360
  console.log('═══════════════════════════════════════════════════════════\n');
222
361
  const groqKey = await promptSecret('Groq API Key for voice/STT (optional, press Enter to skip): ');
223
362
  config = {
224
363
  ...config,
225
364
  runtime: {
226
365
  apiSecret: apiSecret || randomBytes(24).toString('hex'),
227
- apiPort: config.runtime?.apiPort || 3100,
366
+ apiPort: config.runtime?.apiPort || 67420,
228
367
  secretVaultRequired: config.runtime?.secretVaultRequired || [],
229
368
  },
230
369
  models: {
231
370
  openRouterApiKey: openrouterKey || '',
232
371
  modalApiKey: modalKey || '',
233
372
  geminiApiKey: geminiKey || '',
373
+ definitions: [],
374
+ primaryModel: '',
375
+ fallbackModels: [],
234
376
  },
235
377
  messaging: config.messaging || {
236
- telegram: { enabled: false, botToken: '', userId: null },
237
- whatsapp: { enabled: false, phoneNumber: '' },
378
+ telegram: { enabled: false, botToken: '', userId: null, dmPolicy: 'allowlist', groupPolicy: 'allowlist', allowFrom: [] },
379
+ whatsapp: { enabled: false, phoneNumber: '', dmPolicy: 'allowlist', groupPolicy: 'allowlist', allowFrom: [] },
238
380
  voice: { groqApiKey: '' },
239
381
  inbound: { enabled: false, debounceMs: 0 },
240
382
  streaming: {
package/dist/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
3
  const require = createRequire(import.meta.url);
4
- import { handleOnboardCli, runOnboarding, runSetupWizard, startBasicREPL } from './core/onboarding.js';
4
+ import { handleOnboardCli, runOnboarding, runSetupWizard, runSimplifiedOnboarding, startBasicREPL } from './core/onboarding.js';
5
5
  import { Gateway } from './core/gateway.js';
6
6
  import { handleDoctorCli, handleHelpCli, handleUnknownCommand } from './core/cli.js';
7
+ import { handleSelfImproveCli } from './core/self-improve-cli.js';
7
8
  import { HeartbeatService } from './core/heartbeat.js';
8
9
  import { Dispatcher } from './interfaces/dispatcher.js';
9
10
  import { TelegramHandler } from './interfaces/telegram_handler.js';
@@ -24,7 +25,7 @@ import { logThought } from './utils/logger.js';
24
25
  import { PolicyEngine } from './services/policy-engine.js';
25
26
  import { savePolicyAuditLog } from './services/db.js';
26
27
  import { getSecretVaultService } from './services/secret-vault.js';
27
- import { getConfigValue, checkAndMigrateWorkspace } from './config/config-loader.js';
28
+ import { getConfigValue, checkAndMigrateWorkspace, readConfig } from './config/config-loader.js';
28
29
  import { getIdentityDir, getWorkspaceSubdir } from './config/workspace.js';
29
30
  import { handleSecretVaultCli } from './core/secret-vault-cli.js';
30
31
  import { handlePairingCli } from './core/pairing-cli.js';
@@ -33,6 +34,10 @@ import { handleGatewayCli } from './core/gateway-cli.js';
33
34
  import { handleLogsCli } from './core/logs-cli.js';
34
35
  import { getDmPairingService } from './services/dm-pairing.js';
35
36
  import { randomUUID } from 'node:crypto';
37
+ import { initializeSelfHealer, getSelfHealingService } from './services/self-healing.js';
38
+ import { initializeSkillBuilder, getSkillBuilder } from './services/skill-builder.js';
39
+ import { initializeAutoConfigurer, getAutoConfigurer } from './services/auto-configurer.js';
40
+ import { initializeLearning, getLearningSystem } from './services/learning-system.js';
36
41
  const secretVault = getSecretVaultService();
37
42
  const pairingService = getDmPairingService();
38
43
  function assertWindowsOnlyRuntime() {
@@ -47,6 +52,9 @@ assertWindowsOnlyRuntime();
47
52
  if (handleHelpCli(process.argv.slice(2))) {
48
53
  process.exit(process.exitCode ?? 0);
49
54
  }
55
+ if (await handleSelfImproveCli(process.argv.slice(2))) {
56
+ process.exit(process.exitCode ?? 0);
57
+ }
50
58
  if (handleDoctorCli(process.argv.slice(2))) {
51
59
  process.exit(process.exitCode ?? 0);
52
60
  }
@@ -100,11 +108,8 @@ async function tryAutoSetup() {
100
108
  catch (error) {
101
109
  console.log("\n[TwinClaw] Welcome! Critical configuration missing.");
102
110
  console.log("Starting the interactive setup wizard to configure your agent.\n");
103
- const result = await runSetupWizard();
104
- if (result.status !== 'success') {
105
- const exitCode = result.status === 'cancelled' ? 130 : 1;
106
- process.exit(exitCode);
107
- }
111
+ await runSimplifiedOnboarding();
112
+ await runSetupWizard();
108
113
  console.log("\n[TwinClaw] Setup complete. Initializing Gateway...\n");
109
114
  }
110
115
  }
@@ -143,6 +148,23 @@ fileWatcher.addTarget({
143
148
  // ── MCP Skill Registry & Server Manager ──────────────────────────────────────
144
149
  const skillRegistry = new SkillRegistry();
145
150
  skillRegistry.registerMany(createBuiltinSkills());
151
+ // ── Self-Improving Services ───────────────────────────────────────────────────
152
+ const selfHealer = getSelfHealingService();
153
+ const skillBuilder = getSkillBuilder();
154
+ const autoConfigurer = getAutoConfigurer();
155
+ const learningSystem = getLearningSystem();
156
+ void (async () => {
157
+ try {
158
+ await initializeSelfHealer();
159
+ await initializeSkillBuilder();
160
+ await initializeAutoConfigurer();
161
+ await initializeLearning();
162
+ void logThought('[TwinClaw] Self-improving services initialized');
163
+ }
164
+ catch (err) {
165
+ console.warn('[TwinClaw] Self-improving services init warning:', err);
166
+ }
167
+ })();
146
168
  const mcpManager = new McpServerManager(skillRegistry);
147
169
  void (async () => {
148
170
  try {
@@ -187,32 +209,41 @@ const telegramBotToken = secretVault.readSecret('TELEGRAM_BOT_TOKEN');
187
209
  const telegramUserId = getConfigValue('TELEGRAM_USER_ID')?.trim();
188
210
  const whatsappPhoneNumber = secretVault.readSecret('WHATSAPP_PHONE_NUMBER') ?? getConfigValue('WHATSAPP_PHONE_NUMBER');
189
211
  const groqApiKey = secretVault.readSecret('GROQ_API_KEY');
212
+ const twinClawConfig = await readConfig();
190
213
  let dispatcher = null;
191
214
  if (telegramBotToken || whatsappPhoneNumber) {
192
- if (groqApiKey) {
193
- let telegramHandler;
194
- let whatsappHandler;
195
- const telegramAllowFrom = [];
196
- const whatsappAllowFrom = [];
197
- if (telegramBotToken) {
198
- telegramHandler = new TelegramHandler(telegramBotToken);
199
- if (telegramUserId) {
200
- const parsedTelegramUserId = Number(telegramUserId);
201
- if (!Number.isInteger(parsedTelegramUserId) || parsedTelegramUserId <= 0) {
202
- console.error('[TwinClaw] TELEGRAM_USER_ID must be a positive integer when provided.');
203
- }
204
- else {
205
- telegramAllowFrom.push(String(parsedTelegramUserId));
206
- }
215
+ let telegramHandler;
216
+ let whatsappHandler;
217
+ let sttService;
218
+ let ttsService;
219
+ const telegramAllowFrom = [...(twinClawConfig.messaging.telegram?.allowFrom || [])];
220
+ const whatsappAllowFrom = [...(twinClawConfig.messaging.whatsapp?.allowFrom || [])];
221
+ if (telegramBotToken) {
222
+ telegramHandler = new TelegramHandler(telegramBotToken);
223
+ if (telegramUserId) {
224
+ const parsedTelegramUserId = Number(telegramUserId);
225
+ if (!Number.isInteger(parsedTelegramUserId) || parsedTelegramUserId <= 0) {
226
+ console.error('[TwinClaw] TELEGRAM_USER_ID must be a positive integer when provided.');
227
+ }
228
+ else {
229
+ telegramAllowFrom.push(String(parsedTelegramUserId));
207
230
  }
208
231
  }
209
- if (whatsappPhoneNumber) {
210
- whatsappHandler = new WhatsAppHandler();
232
+ }
233
+ if (whatsappPhoneNumber) {
234
+ whatsappHandler = new WhatsAppHandler();
235
+ if (!whatsappAllowFrom.includes(whatsappPhoneNumber)) {
211
236
  whatsappAllowFrom.push(whatsappPhoneNumber);
212
237
  }
213
- const sttService = new SttService(groqApiKey);
214
- const ttsService = new TtsService(groqApiKey);
215
- // Initialize persistent delivery queue
238
+ }
239
+ if (groqApiKey) {
240
+ sttService = new SttService(groqApiKey);
241
+ ttsService = new TtsService(groqApiKey);
242
+ }
243
+ else {
244
+ console.warn('[TwinClaw] GROQ_API_KEY not set. Voice/STT features disabled.');
245
+ }
246
+ if (telegramHandler || whatsappHandler) {
216
247
  const queueService = new QueueService(async (platform, chatId, text) => {
217
248
  switch (platform) {
218
249
  case 'telegram':
@@ -233,19 +264,16 @@ if (telegramBotToken || whatsappPhoneNumber) {
233
264
  dispatcher = new Dispatcher(telegramHandler, whatsappHandler, sttService, ttsService, gateway, queueService, {
234
265
  pairingService,
235
266
  telegram: {
236
- dmPolicy: 'pairing',
267
+ dmPolicy: 'allowlist',
237
268
  allowFrom: telegramAllowFrom,
238
269
  },
239
270
  whatsapp: {
240
- dmPolicy: 'pairing',
271
+ dmPolicy: 'allowlist',
241
272
  allowFrom: whatsappAllowFrom,
242
273
  },
243
274
  });
244
275
  void logThought('[TwinClaw] Messaging dispatcher and persistent queue initialized.');
245
276
  }
246
- else {
247
- console.warn('[TwinClaw] Dispatcher requires GROQ_API_KEY.');
248
- }
249
277
  }
250
278
  else {
251
279
  console.log('[TwinClaw] Messaging dispatcher not initialized (missing Telegram AND WhatsApp configs).');
@@ -305,7 +333,7 @@ const runtimeEventProducer = new RuntimeEventProducer({
305
333
  dispatcher: dispatcher ?? undefined,
306
334
  modelRouter,
307
335
  });
308
- const apiPort = Number(getConfigValue('API_PORT')) || 18789;
336
+ const apiPort = Number(getConfigValue('API_PORT')) || 67420;
309
337
  startApiServer({
310
338
  heartbeat,
311
339
  skillRegistry,
@@ -5,7 +5,7 @@ import { InboundDebounceService } from '../services/inbound-debounce.js';
5
5
  import { EmbeddedBlockChunker } from '../services/block-chunker.js';
6
6
  import { getConfigValue } from '../config/config-loader.js';
7
7
  const DEFAULT_ACCESS_CONFIG = {
8
- dmPolicy: 'pairing',
8
+ dmPolicy: 'allowlist',
9
9
  allowFrom: [],
10
10
  };
11
11
  function buildPairingChallenge(channel, code) {
@@ -154,7 +154,9 @@ export class Dispatcher {
154
154
  const filePath = message.audioFilePath;
155
155
  let transcribedText;
156
156
  try {
157
- transcribedText = await this.#stt.transcribeFile(filePath);
157
+ if (this.#stt) {
158
+ transcribedText = await this.#stt.transcribeFile(filePath);
159
+ }
158
160
  }
159
161
  finally {
160
162
  fs.unlink(filePath, (unlinkErr) => {