total-recall-brain 3.4.0 → 3.6.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.
@@ -312,6 +312,458 @@ export const SubQuerySchema = z.object({
312
312
  reasoning: z.string().describe("Explanation for why these sub-queries are necessary")
313
313
  });
314
314
 
315
+ // ─── Document Primitives (§5.1 of the SSSS spec) ───────────────────────────
316
+
317
+ export const ContactSchema = z.object({
318
+ type: z.literal('contact'),
319
+ name: z.string(),
320
+ first_name: z.string().optional().nullable(),
321
+ last_name: z.string().optional().nullable(),
322
+ email: z.string().optional().nullable(),
323
+ phone: z.string().optional().nullable(),
324
+ company: z.string().optional().nullable(),
325
+ job_title: z.string().optional().nullable(),
326
+ tags: z.array(z.unknown()).optional().nullable(),
327
+ notes: z.string().optional().nullable(),
328
+ source: z.string().optional().nullable(),
329
+ contact_type: z.string().optional().nullable(),
330
+ priority: z.string().optional().nullable(),
331
+ relationship_strength: z.number().optional().nullable(),
332
+ custom_fields: z.record(z.unknown()).optional().nullable(),
333
+ db_id: z.string().optional().nullable(),
334
+ owner_user_id: z.string().optional().nullable(),
335
+ workspace_id: z.string().optional().nullable(),
336
+ });
337
+
338
+ export const CompanySchema = z.object({
339
+ type: z.literal('company'),
340
+ name: z.string(),
341
+ domain: z.string().optional().nullable(),
342
+ industry: z.string().optional().nullable(),
343
+ website: z.string().optional().nullable(),
344
+ db_id: z.string().optional().nullable(),
345
+ workspace_id: z.string().optional().nullable(),
346
+ });
347
+
348
+ export const DealSchema = z.object({
349
+ type: z.literal('deal'),
350
+ name: z.string(),
351
+ value: z.number(),
352
+ stage: z.string(),
353
+ contact_id: z.string().optional().nullable(),
354
+ company_id: z.string().optional().nullable(),
355
+ currency: z.string().optional().nullable(),
356
+ probability: z.number().optional().nullable(),
357
+ expected_close_date: z.string().optional().nullable(),
358
+ is_archived: z.boolean().optional().nullable(),
359
+ description: z.string().optional().nullable(),
360
+ source: z.string().optional().nullable(),
361
+ tags: z.array(z.unknown()).optional().nullable(),
362
+ custom_fields: z.record(z.unknown()).optional().nullable(),
363
+ db_id: z.string().optional().nullable(),
364
+ workspace_id: z.string().optional().nullable(),
365
+ });
366
+
367
+ export const BrandSchema = z.object({
368
+ type: z.literal('brand'),
369
+ name: z.string(),
370
+ version: z.string().optional().nullable(),
371
+ description: z.string().optional().nullable(),
372
+ colors: z.record(z.unknown()).optional().nullable(),
373
+ typography: z.record(z.unknown()).optional().nullable(),
374
+ rounded: z.record(z.unknown()).optional().nullable(),
375
+ spacing: z.record(z.unknown()).optional().nullable(),
376
+ components: z.record(z.unknown()).optional().nullable(),
377
+ vibe: z.string().optional().nullable(),
378
+ locale: z.string().optional().nullable(),
379
+ assets: z.record(z.unknown()).optional().nullable(),
380
+ brand_voice: z.array(z.unknown()).optional().nullable(),
381
+ accessibility_palettes: z.record(z.unknown()).optional().nullable(),
382
+ tagline: z.string().optional().nullable(),
383
+ });
384
+
385
+ export const DeploymentSchema = z.object({
386
+ type: z.literal('deployment'),
387
+ name: z.string(),
388
+ slug: z.string(),
389
+ status: z.string().optional().nullable(),
390
+ runtime_url: z.string().optional().nullable(),
391
+ mcp_config: z.record(z.unknown()).optional().nullable(),
392
+ container_settings: z.record(z.unknown()).optional().nullable(),
393
+ workspace_id: z.string().optional().nullable(),
394
+ db_id: z.string().optional().nullable(),
395
+ });
396
+
397
+ export const ListingSchema = z.object({
398
+ type: z.literal('listing'),
399
+ name: z.string(),
400
+ pricing_model: z.string(),
401
+ credits_charge_rate: z.number().optional().nullable(),
402
+ connected_stripe_account: z.string().optional().nullable(),
403
+ is_published: z.boolean().optional().nullable(),
404
+ workspace_id: z.string().optional().nullable(),
405
+ db_id: z.string().optional().nullable(),
406
+ });
407
+
408
+ export const KnowledgeSchema = z.object({
409
+ type: z.literal('knowledge'),
410
+ name: z.string(),
411
+ slug: z.string(),
412
+ description: z.string().optional().nullable(),
413
+ category: z.string().optional().nullable(),
414
+ is_public: z.boolean().optional().nullable(),
415
+ is_active: z.boolean().optional().nullable(),
416
+ is_system_managed: z.boolean().optional().nullable(),
417
+ shared_across_workspaces: z.boolean().optional().nullable(),
418
+ settings: z.record(z.unknown()).optional().nullable(),
419
+ workspace_id: z.string().optional().nullable(),
420
+ db_id: z.string().optional().nullable(),
421
+ user_id: z.string().optional().nullable(),
422
+ });
423
+
424
+ export const KnowledgeSourceSchema = z.object({
425
+ type: z.literal('knowledge_source'),
426
+ title: z.string(),
427
+ knowledge_base_slug: z.string(),
428
+ content: z.string().optional().nullable(),
429
+ content_type: z.string().optional().nullable(),
430
+ source_url: z.string().optional().nullable(),
431
+ metadata: z.record(z.unknown()).optional().nullable(),
432
+ embedding_vector: z.array(z.unknown()).optional().nullable(),
433
+ workspace_id: z.string().optional().nullable(),
434
+ db_id: z.string().optional().nullable(),
435
+ });
436
+
437
+ export const CalendarEventSchema = z.object({
438
+ type: z.literal('calendar_event'),
439
+ title: z.string().optional().nullable(),
440
+ description: z.string().optional().nullable(),
441
+ start_time: z.string(),
442
+ end_time: z.string().optional().nullable(),
443
+ timezone: z.string().optional().nullable(),
444
+ all_day: z.boolean().optional().nullable(),
445
+ event_type: z.string(),
446
+ calendar_id: z.string().optional().nullable(),
447
+ location: z.string().optional().nullable(),
448
+ meeting_url: z.string().optional().nullable(),
449
+ attendees: z.array(z.unknown()).optional().nullable(),
450
+ location_context: z.record(z.unknown()).optional().nullable(),
451
+ db_id: z.string().optional().nullable(),
452
+ workspace_id: z.string().optional().nullable(),
453
+ user_id: z.string().optional().nullable(),
454
+ created_at: z.string().optional().nullable(),
455
+ updated_at: z.string().optional().nullable(),
456
+ });
457
+
458
+ export const EventSchema = z.object({
459
+ type: z.literal('event'),
460
+ event_type: z.string(),
461
+ thread_id: z.string().optional().nullable(),
462
+ turn_index: z.number().optional().nullable(),
463
+ rating: z.number().optional().nullable(),
464
+ comment: z.string().optional().nullable(),
465
+ comment_lang: z.string().optional().nullable(),
466
+ timestamp: z.string().optional().nullable(),
467
+ });
468
+
469
+ export const LanguageConventionSchema = z.object({
470
+ type: z.literal('language_convention'),
471
+ locale: z.string(),
472
+ language_name: z.string(),
473
+ keys: z.record(z.unknown()),
474
+ });
475
+
476
+ export const ProfileSchema = z.object({
477
+ type: z.literal('profile'),
478
+ user_id: z.string(),
479
+ locale: z.string(),
480
+ display_name: z.string().optional().nullable(),
481
+ timezone: z.string().optional().nullable(),
482
+ workspaces: z.array(z.unknown()).optional().nullable(),
483
+ notification_preferences: z.record(z.unknown()).optional().nullable(),
484
+ });
485
+
486
+ export const SharingGroupSchema = z.object({
487
+ type: z.literal('sharing_group'),
488
+ name: z.string(),
489
+ color: z.string(),
490
+ description: z.string().optional().nullable(),
491
+ owner_user_id: z.string(),
492
+ members: z.array(z.unknown()).optional().nullable(),
493
+ share_credits: z.boolean().optional().nullable(),
494
+ share_skills: z.boolean().optional().nullable(),
495
+ share_assistants: z.boolean().optional().nullable(),
496
+ share_email_accounts: z.boolean().optional().nullable(),
497
+ share_email_domains: z.boolean().optional().nullable(),
498
+ share_phone_numbers: z.boolean().optional().nullable(),
499
+ share_api_keys: z.boolean().optional().nullable(),
500
+ share_knowledge_base: z.boolean().optional().nullable(),
501
+ share_credentials: z.boolean().optional().nullable(),
502
+ share_team_members: z.boolean().optional().nullable(),
503
+ share_workflows: z.boolean().optional().nullable(),
504
+ share_templates: z.boolean().optional().nullable(),
505
+ share_branding: z.boolean().optional().nullable(),
506
+ share_crm: z.boolean().optional().nullable(),
507
+ share_integrations: z.boolean().optional().nullable(),
508
+ share_notifications: z.boolean().optional().nullable(),
509
+ share_deployments: z.boolean().optional().nullable(),
510
+ });
511
+
512
+ export const PageSchema = z.object({
513
+ type: z.literal('page'),
514
+ slug: z.string(),
515
+ name: z.string(),
516
+ icon: z.string().optional().nullable(),
517
+ layout: z.string().optional().nullable(),
518
+ sandbox_entry: z.string(),
519
+ is_active: z.boolean().optional().nullable(),
520
+ workspace_id: z.string().optional().nullable(),
521
+ db_id: z.string().optional().nullable(),
522
+ });
523
+
524
+ export const VoicemailSchema = z.object({
525
+ type: z.literal('voicemail'),
526
+ mailbox_id: z.string(),
527
+ caller_id: z.string().optional().nullable(),
528
+ caller_name: z.string().optional().nullable(),
529
+ duration_seconds: z.number(),
530
+ recording_url: z.string(),
531
+ recording_path: z.string().optional().nullable(),
532
+ recording_size_bytes: z.number().optional().nullable(),
533
+ transcript: z.string().optional().nullable(),
534
+ transcript_confidence: z.number().optional().nullable(),
535
+ transcription_status: z.string().optional().nullable(),
536
+ is_urgent: z.boolean().optional().nullable(),
537
+ is_read: z.boolean().optional().nullable(),
538
+ is_deleted: z.boolean().optional().nullable(),
539
+ folder: z.string().optional().nullable(),
540
+ callback_number: z.string().optional().nullable(),
541
+ inbox_item_id: z.string().optional().nullable(),
542
+ workspace_id: z.string().optional().nullable(),
543
+ user_id: z.string().optional().nullable(),
544
+ db_id: z.string().optional().nullable(),
545
+ created_at: z.string().optional().nullable(),
546
+ });
547
+
548
+ export const PersonalizationProfileSchema = z.object({
549
+ type: z.literal('personalization_profile'),
550
+ name: z.string(),
551
+ slug: z.string(),
552
+ user_id: z.string(),
553
+ profile_type: z.string(),
554
+ description: z.string().optional().nullable(),
555
+ personalization_text: z.string().optional().nullable(),
556
+ is_default: z.boolean().optional().nullable(),
557
+ is_active: z.boolean().optional().nullable(),
558
+ scope: z.string().optional().nullable(),
559
+ workspace_id: z.string().optional().nullable(),
560
+ attached_files: z.array(z.unknown()).optional().nullable(),
561
+ active_times: z.array(z.unknown()).optional().nullable(),
562
+ });
563
+
564
+ export const AccountAssistantSchema = z.object({
565
+ type: z.literal('account_assistant'),
566
+ name: z.string(),
567
+ user_id: z.string(),
568
+ scope: z.string(),
569
+ enabled: z.boolean(),
570
+ description: z.string().optional().nullable(),
571
+ system_message: z.string().optional().nullable(),
572
+ avatar_url: z.string().optional().nullable(),
573
+ color_theme: z.string().optional().nullable(),
574
+ voice_id: z.string().optional().nullable(),
575
+ is_receptionist_enabled: z.boolean().optional().nullable(),
576
+ auto_answer_calls: z.boolean().optional().nullable(),
577
+ verification_pin: z.string().optional().nullable(),
578
+ greeting_known: z.string().optional().nullable(),
579
+ greeting_unknown: z.string().optional().nullable(),
580
+ routing_rules: z.array(z.unknown()).optional().nullable(),
581
+ enabled_tabs: z.array(z.unknown()).optional().nullable(),
582
+ });
583
+
584
+ export const AccountMemorySchema = z.object({
585
+ type: z.literal('account_memory'),
586
+ title: z.string(),
587
+ user_id: z.string(),
588
+ category: z.string(),
589
+ confidence: z.number().optional().nullable(),
590
+ source: z.string().optional().nullable(),
591
+ });
592
+
593
+ export const AccountWorkflowSchema = z.object({
594
+ type: z.literal('account_workflow'),
595
+ name: z.string(),
596
+ slug: z.string(),
597
+ user_id: z.string(),
598
+ scope: z.string(),
599
+ description: z.string().optional().nullable(),
600
+ enabled: z.boolean().optional().nullable(),
601
+ trigger_type: z.string().optional().nullable(),
602
+ trigger_config: z.record(z.unknown()).optional().nullable(),
603
+ steps: z.array(z.unknown()).optional().nullable(),
604
+ actions: z.array(z.unknown()).optional().nullable(),
605
+ target_workspaces: z.array(z.unknown()).optional().nullable(),
606
+ last_run: z.string().optional().nullable(),
607
+ run_count: z.number().optional().nullable(),
608
+ });
609
+
610
+ export const WorkspaceTransferSchema = z.object({
611
+ type: z.literal('workspace_transfer'),
612
+ transfer_id: z.string(),
613
+ source_user_id: z.string(),
614
+ target_email: z.string(),
615
+ workspace_id: z.string(),
616
+ status: z.string(),
617
+ initiated_at: z.string(),
618
+ target_user_id: z.string().optional().nullable(),
619
+ workspace_name: z.string().optional().nullable(),
620
+ resolved_at: z.string().optional().nullable(),
621
+ expires_at: z.string().optional().nullable(),
622
+ });
623
+
624
+ export const ExtensionSchema = z.object({
625
+ type: z.literal('extension'),
626
+ extension: z.string(),
627
+ display_name: z.string(),
628
+ owner_user_id: z.string().optional().nullable(),
629
+ workspace_id: z.string().optional().nullable(),
630
+ assistant_id: z.string().optional().nullable(),
631
+ status: z.string(),
632
+ voicemail_enabled: z.boolean().optional().nullable(),
633
+ metadata: z.record(z.unknown()).optional().nullable(),
634
+ });
635
+
636
+ export const PhoneNumberSchema = z.object({
637
+ type: z.literal('phone_number'),
638
+ phone_number: z.string(),
639
+ provider: z.string(),
640
+ provider_number_id: z.string().optional().nullable(),
641
+ receptionist_id: z.string().optional().nullable(),
642
+ status: z.string(),
643
+ workspace_id: z.string().optional().nullable(),
644
+ user_id: z.string().optional().nullable(),
645
+ });
646
+
647
+ export const WorkspaceSchema = z.object({
648
+ type: z.literal('workspace'),
649
+ name: z.string(),
650
+ slug: z.string().optional().nullable(),
651
+ description: z.string().optional().nullable(),
652
+ business_type: z.string().optional().nullable(),
653
+ domain: z.string().optional().nullable(),
654
+ provisioning_mode: z.string().optional().nullable(),
655
+ canonical_version: z.number().optional().nullable(),
656
+ content_hash: z.string().optional().nullable(),
657
+ brand_ref: z.string().optional().nullable(),
658
+ page_refs: z.array(z.unknown()).optional().nullable(),
659
+ assistant_refs: z.array(z.unknown()).optional().nullable(),
660
+ workflow_refs: z.array(z.unknown()).optional().nullable(),
661
+ knowledge_refs: z.array(z.unknown()).optional().nullable(),
662
+ commerce_ref: z.string().optional().nullable(),
663
+ cms_refs: z.array(z.unknown()).optional().nullable(),
664
+ social_refs: z.array(z.unknown()).optional().nullable(),
665
+ ticketing_refs: z.array(z.unknown()).optional().nullable(),
666
+ comms_refs: z.array(z.unknown()).optional().nullable(),
667
+ economy: z.record(z.unknown()).optional().nullable(),
668
+ demo_fixtures_ref: z.string().optional().nullable(),
669
+ capabilities: z.array(z.unknown()).optional().nullable(),
670
+ workspace_id: z.string().optional().nullable(),
671
+ db_id: z.string().optional().nullable(),
672
+ created_at: z.string().optional().nullable(),
673
+ updated_at: z.string().optional().nullable(),
674
+ });
675
+
676
+ export const CommerceCatalogSchema = z.object({
677
+ type: z.literal('commerce_catalog'),
678
+ name: z.string(),
679
+ currency: z.string().optional().nullable(),
680
+ tax_behavior: z.string().optional().nullable(),
681
+ shipping_zones: z.array(z.unknown()).optional().nullable(),
682
+ collections: z.array(z.unknown()).optional().nullable(),
683
+ fulfillment: z.record(z.unknown()).optional().nullable(),
684
+ stripe_account_ref: z.string().optional().nullable(),
685
+ workspace_id: z.string().optional().nullable(),
686
+ db_id: z.string().optional().nullable(),
687
+ });
688
+
689
+ export const ProductSchema = z.object({
690
+ type: z.literal('product'),
691
+ name: z.string(),
692
+ slug: z.string(),
693
+ description: z.string().optional().nullable(),
694
+ product_type: z.string().optional().nullable(),
695
+ status: z.string().optional().nullable(),
696
+ variants: z.array(z.unknown()).optional().nullable(),
697
+ prices: z.array(z.unknown()).optional().nullable(),
698
+ subscription: z.record(z.unknown()).optional().nullable(),
699
+ collection: z.string().optional().nullable(),
700
+ media_refs: z.array(z.unknown()).optional().nullable(),
701
+ inventory: z.record(z.unknown()).optional().nullable(),
702
+ metadata: z.record(z.unknown()).optional().nullable(),
703
+ workspace_id: z.string().optional().nullable(),
704
+ db_id: z.string().optional().nullable(),
705
+ });
706
+
707
+ export const CmsCollectionSchema = z.object({
708
+ type: z.literal('cms_collection'),
709
+ name: z.string(),
710
+ slug: z.string(),
711
+ description: z.string().optional().nullable(),
712
+ fields: z.array(z.unknown()).optional().nullable(),
713
+ ordering: z.string().optional().nullable(),
714
+ is_published: z.boolean().optional().nullable(),
715
+ workspace_id: z.string().optional().nullable(),
716
+ db_id: z.string().optional().nullable(),
717
+ });
718
+
719
+ export const CmsEntrySchema = z.object({
720
+ type: z.literal('cms_entry'),
721
+ title: z.string(),
722
+ slug: z.string(),
723
+ collection_slug: z.string(),
724
+ status: z.string().optional().nullable(),
725
+ publish_at: z.string().optional().nullable(),
726
+ order: z.number().optional().nullable(),
727
+ fields: z.record(z.unknown()).optional().nullable(),
728
+ media_refs: z.array(z.unknown()).optional().nullable(),
729
+ excerpt: z.string().optional().nullable(),
730
+ seo: z.record(z.unknown()).optional().nullable(),
731
+ workspace_id: z.string().optional().nullable(),
732
+ db_id: z.string().optional().nullable(),
733
+ });
734
+
735
+ export const SocialSchema = z.object({
736
+ type: z.literal('social'),
737
+ name: z.string(),
738
+ platform: z.string().optional().nullable(),
739
+ handle: z.string().optional().nullable(),
740
+ connection_ref: z.string().optional().nullable(),
741
+ posting_calendar: z.array(z.unknown()).optional().nullable(),
742
+ creative_refs: z.array(z.unknown()).optional().nullable(),
743
+ status: z.string().optional().nullable(),
744
+ workspace_id: z.string().optional().nullable(),
745
+ db_id: z.string().optional().nullable(),
746
+ });
747
+
748
+ export const TicketedEventSchema = z.object({
749
+ type: z.literal('ticketed_event'),
750
+ name: z.string(),
751
+ slug: z.string(),
752
+ start_time: z.string(),
753
+ description: z.string().optional().nullable(),
754
+ status: z.string().optional().nullable(),
755
+ end_time: z.string().optional().nullable(),
756
+ timezone: z.string().optional().nullable(),
757
+ venue: z.record(z.unknown()).optional().nullable(),
758
+ sessions: z.array(z.unknown()).optional().nullable(),
759
+ ticket_tiers: z.array(z.unknown()).optional().nullable(),
760
+ capacity: z.number().optional().nullable(),
761
+ stripe_account_ref: z.string().optional().nullable(),
762
+ media_refs: z.array(z.unknown()).optional().nullable(),
763
+ workspace_id: z.string().optional().nullable(),
764
+ db_id: z.string().optional().nullable(),
765
+ });
766
+
315
767
  // ─── Schema Registry (§5 of the SSSS spec) ─────────────────────────────────
316
768
 
317
769
  /** Map from SSSS `type` value to its Zod schema. Used by the operation validator. */
@@ -330,4 +782,33 @@ export const SSSS_SCHEMAS = {
330
782
  'schema-proposal': SchemaProposalSchema,
331
783
  migration: MigrationSchema,
332
784
  release: ReleaseSchema,
785
+ contact: ContactSchema,
786
+ company: CompanySchema,
787
+ deal: DealSchema,
788
+ brand: BrandSchema,
789
+ deployment: DeploymentSchema,
790
+ listing: ListingSchema,
791
+ knowledge: KnowledgeSchema,
792
+ knowledge_source: KnowledgeSourceSchema,
793
+ calendar_event: CalendarEventSchema,
794
+ event: EventSchema,
795
+ language_convention: LanguageConventionSchema,
796
+ profile: ProfileSchema,
797
+ sharing_group: SharingGroupSchema,
798
+ page: PageSchema,
799
+ voicemail: VoicemailSchema,
800
+ personalization_profile: PersonalizationProfileSchema,
801
+ account_assistant: AccountAssistantSchema,
802
+ account_memory: AccountMemorySchema,
803
+ account_workflow: AccountWorkflowSchema,
804
+ workspace_transfer: WorkspaceTransferSchema,
805
+ extension: ExtensionSchema,
806
+ phone_number: PhoneNumberSchema,
807
+ workspace: WorkspaceSchema,
808
+ commerce_catalog: CommerceCatalogSchema,
809
+ product: ProductSchema,
810
+ cms_collection: CmsCollectionSchema,
811
+ cms_entry: CmsEntrySchema,
812
+ social: SocialSchema,
813
+ ticketed_event: TicketedEventSchema,
333
814
  };
@@ -1,7 +1,15 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import yaml from 'yaml';
4
- import { brainDir } from './config.mjs';
4
+ import {
5
+ brainDir,
6
+ braveApiKey as defaultBraveKey,
7
+ braveSearchApiKey as defaultBraveSearchKey,
8
+ exaApiKey as defaultExaKey,
9
+ githubToken as defaultGithubToken,
10
+ serperApiKey as defaultSerperKey,
11
+ tavilyApiKey as defaultTavilyKey
12
+ } from './config.mjs';
5
13
 
6
14
  /**
7
15
  * Research Source Adapters — Real Service Integrations
@@ -42,12 +50,12 @@ export function loadResearchConfig(configPath = DEFAULT_CONFIG_PATH) {
42
50
 
43
51
  return {
44
52
  // ── Paid search providers (fallback chain: Brave → Tavily → Exa → Serper) ──
45
- braveApiKey: process.env.BRAVE_SEARCH_API_KEY || process.env.BRAVE_API_KEY || secrets.brave_api_key || fileConfig.brave_api_key || null,
46
- tavilyApiKey: process.env.TAVILY_API_KEY || secrets.tavily_api_key || fileConfig.tavily_api_key || null,
47
- exaApiKey: process.env.EXA_API_KEY || secrets.exa_api_key || fileConfig.exa_api_key || null,
48
- serperApiKey: process.env.SERPER_API_KEY || secrets.serper_api_key || fileConfig.serper_api_key || null,
53
+ braveApiKey: process.env.BRAVE_SEARCH_API_KEY || process.env.BRAVE_API_KEY || secrets.brave_api_key || fileConfig.brave_api_key || defaultBraveKey || defaultBraveSearchKey || null,
54
+ tavilyApiKey: process.env.TAVILY_API_KEY || secrets.tavily_api_key || fileConfig.tavily_api_key || defaultTavilyKey || null,
55
+ exaApiKey: process.env.EXA_API_KEY || secrets.exa_api_key || fileConfig.exa_api_key || defaultExaKey || null,
56
+ serperApiKey: process.env.SERPER_API_KEY || secrets.serper_api_key || fileConfig.serper_api_key || defaultSerperKey || null,
49
57
  // GitHub: personal access token for higher rate limits (60→5000 req/hr)
50
- githubToken: process.env.GITHUB_TOKEN || secrets.github_token || fileConfig.github_token || null,
58
+ githubToken: process.env.GITHUB_TOKEN || secrets.github_token || fileConfig.github_token || defaultGithubToken || null,
51
59
  fetchTimeoutMs: fileConfig.fetch_timeout_ms || 10000,
52
60
  maxResultsPerSource: fileConfig.max_results_per_source || 5,
53
61
  userAgent: fileConfig.user_agent || 'TotalRecall/1.0 (knowledge-acquisition; +https://github.com/gregiteen/total-recall)',
@@ -198,8 +198,12 @@ try {
198
198
  app.use('/api', apiRateLimiter());
199
199
  app.use(restRouter);
200
200
  logger.info('server', 'REST API mounted (/api/*, /v1/models, /.well-known/total-recall.json)');
201
+
202
+ const { collabRouter } = await import('./routes/collab.mjs');
203
+ app.use(collabRouter);
204
+ logger.info('server', 'Collaboration API routes mounted (/api/collab/*)');
201
205
  } catch (err) {
202
- logger.error('server', `REST API failed to load: ${err.message}`);
206
+ logger.error('server', `REST API or Collab routes failed to load: ${err.message}`);
203
207
  }
204
208
 
205
209
  // ─── API Routes (/v1/chat/completions) ──────────────────────────────────────────
@@ -573,7 +577,23 @@ const HOST = nodeEnv === 'production' && publicBindRequested && serverSecurityCo
573
577
  ? '127.0.0.1'
574
578
  : configuredHost;
575
579
 
580
+ import { WebSocketServer } from 'ws';
581
+ const collabWss = new WebSocketServer({ noServer: true });
582
+
576
583
  const server = app.listen(PORT, HOST, () => {
584
+ server.on('upgrade', async (request, socket, head) => {
585
+ const url = new URL(request.url, `http://${request.headers.host || 'localhost'}`);
586
+ if (url.pathname === '/collab-ws') {
587
+ try {
588
+ const { handleCollabUpgrade } = await import('./routes/collab.mjs');
589
+ handleCollabUpgrade(request, socket, head, collabWss);
590
+ } catch (err) {
591
+ socket.write('HTTP/1.1 500 Internal Server Error\r\n\r\n');
592
+ socket.destroy();
593
+ }
594
+ }
595
+ });
596
+
577
597
  if (HOST !== configuredHost) {
578
598
  logger.error("server", `Refusing public bind '${configuredHost}' in production. Bound to ${HOST}.`);
579
599
  }