File size: 4,098 Bytes
34367da
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import axios from 'axios';
import { NeuralEvent } from '../NeuralStream.js';

const NOTION_API_KEY = process.env.NOTION_API_TOKEN;
// Use the ID from the user's link if env is missing, but prioritize env
const NOTION_DB_ID = process.env.NOTION_BLACKBOARD_DB || '2cb12238-fd44-812e-84fc-e2eccb857517';

export class NotionService {
  private static instance: NotionService;
  private isEnabled: boolean = false;
  
  private constructor() {
    this.isEnabled = !!NOTION_API_KEY;
    if (this.isEnabled) {
        console.log('📝 [NOTION] Service Enabled');
    } else {
        console.warn('⚠️ [NOTION] Service Disabled (Missing NOTION_API_TOKEN)');
    }
  }

  public static getInstance(): NotionService {
    if (!NotionService.instance) {
      NotionService.instance = new NotionService();
    }
    return NotionService.instance;
  }

  /**
   * Pushes a high-priority event to the Notion Command Center
   */
  public async logEvent(event: NeuralEvent): Promise<void> {
    if (!this.isEnabled) return;

    // Rate limiting / Spam protection
    // Only log HIGH or CRITICAL events, or specific important types
    const importantTypes = ['SECURITY_THREAT', 'KNOWLEDGE_GAP', 'SYSTEM_ERROR', 'DELEGATION'];
    if (event.severity !== 'CRITICAL' && event.severity !== 'HIGH' && !importantTypes.includes(event.type)) {
        return;
    }

    try {
      const title = `[${event.type}] ${event.source || 'System'}`;
      const payloadString = typeof event.payload === 'string' 
        ? event.payload 
        : JSON.stringify(event.payload, null, 2);

      await axios.post(
        'https://api.notion.com/v1/pages',
        {
          parent: { database_id: NOTION_DB_ID },
          properties: {
            // "Name" or "title" is the standard title property in Notion databases.
            // We try "Name" first as it's the default.
            Name: {
              title: [
                {
                  text: {
                    content: title.substring(0, 100) // Title limit
                  }
                }
              ]
            },
            // We attempt to set tags/status if they exist, but wrap in try/catch logic implicitly by API ignoring unknown props (usually throws 400)
            // To be safe, we'll just send the title and content blocks for now to avoid schema validation errors
            // unless we are sure about the schema. 
            // Strategy: Send minimal valid payload.
          },
          children: [
            {
              object: 'block',
              type: 'callout',
              callout: {
                rich_text: [{
                    type: 'text',
                    text: { content: `${event.severity || 'INFO'} | ${new Date().toLocaleTimeString()}` }
                }],
                icon: { emoji: this.getEmojiForEvent(event) }
              }
            },
            {
              object: 'block',
              type: 'code',
              code: {
                language: 'json',
                rich_text: [{
                    type: 'text',
                    text: { content: payloadString.substring(0, 2000) }
                }]
              }
            }
          ]
        },
        {
          headers: {
            'Authorization': `Bearer ${NOTION_API_KEY}`,
            'Notion-Version': '2022-06-28',
            'Content-Type': 'application/json'
          }
        }
      );
      // console.log(`📝 [NOTION] Logged: ${title}`);
    } catch (error: any) {
        if (error.response) {
            console.warn(`⚠️ [NOTION] API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`);
        }
    }
  }

  private getEmojiForEvent(event: NeuralEvent): string {
      if (event.type === 'SECURITY_THREAT') return '🚨';
      if (event.type === 'SYSTEM_ERROR') return '🔥';
      if (event.type === 'KNOWLEDGE_GAP') return '🧩';
      if (event.type === 'THOUGHT') return '🧠';
      if (event.severity === 'CRITICAL') return '⚡';
      return '📝';
  }
}

export const notionService = NotionService.getInstance();