hedgequantx 2.5.13 → 2.5.14

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.
@@ -1,22 +1,42 @@
1
1
  /**
2
2
  * AI Service Manager
3
- * Manages AI provider connections and settings
3
+ * Manages multiple AI provider connections
4
4
  */
5
5
 
6
6
  const { getProviders, getProvider } = require('./providers');
7
- const { settings } = require('../../config');
7
+ const { storage } = require('../session');
8
+ const AISupervisor = require('./supervisor');
8
9
 
9
- // In-memory cache of current connection
10
- let currentConnection = null;
10
+ // In-memory cache of connections
11
+ let connectionsCache = null;
11
12
 
12
13
  /**
13
14
  * Get AI settings from storage
14
15
  */
15
16
  const getAISettings = () => {
16
17
  try {
17
- return settings.get('ai') || {};
18
+ const sessions = storage.load();
19
+ const aiSettings = sessions.find(s => s.type === 'ai') || { type: 'ai', agents: [] };
20
+
21
+ // Migrate old single-agent format to multi-agent
22
+ if (aiSettings.provider && !aiSettings.agents) {
23
+ return {
24
+ type: 'ai',
25
+ agents: [{
26
+ id: generateAgentId(),
27
+ provider: aiSettings.provider,
28
+ option: aiSettings.option,
29
+ credentials: aiSettings.credentials,
30
+ model: aiSettings.model,
31
+ name: getProvider(aiSettings.provider)?.name || 'AI Agent',
32
+ createdAt: Date.now()
33
+ }],
34
+ activeAgentId: null
35
+ };
36
+ }
37
+ return aiSettings;
18
38
  } catch {
19
- return {};
39
+ return { type: 'ai', agents: [] };
20
40
  }
21
41
  };
22
42
 
@@ -25,42 +45,139 @@ const getAISettings = () => {
25
45
  */
26
46
  const saveAISettings = (aiSettings) => {
27
47
  try {
28
- settings.set('ai', aiSettings);
48
+ const sessions = storage.load();
49
+ const otherSessions = sessions.filter(s => s.type !== 'ai');
50
+
51
+ aiSettings.type = 'ai';
52
+ otherSessions.push(aiSettings);
53
+
54
+ storage.save(otherSessions);
55
+ connectionsCache = null; // Invalidate cache
29
56
  } catch (e) {
30
57
  // Silent fail
31
58
  }
32
59
  };
33
60
 
34
61
  /**
35
- * Check if AI is connected
62
+ * Generate unique agent ID
63
+ */
64
+ const generateAgentId = () => {
65
+ return 'agent_' + Date.now().toString(36) + Math.random().toString(36).substr(2, 5);
66
+ };
67
+
68
+ /**
69
+ * Get all connected agents
70
+ */
71
+ const getAgents = () => {
72
+ const aiSettings = getAISettings();
73
+ const agents = aiSettings.agents || [];
74
+
75
+ return agents.map(agent => {
76
+ const provider = getProvider(agent.provider);
77
+ return {
78
+ id: agent.id,
79
+ name: agent.name || provider?.name || 'Unknown',
80
+ provider: provider,
81
+ providerId: agent.provider,
82
+ option: agent.option,
83
+ model: agent.model || provider?.defaultModel,
84
+ createdAt: agent.createdAt,
85
+ isActive: agent.id === aiSettings.activeAgentId
86
+ };
87
+ }).filter(a => a.provider); // Filter out invalid providers
88
+ };
89
+
90
+ /**
91
+ * Get agent count
92
+ */
93
+ const getAgentCount = () => {
94
+ const aiSettings = getAISettings();
95
+ return (aiSettings.agents || []).length;
96
+ };
97
+
98
+ /**
99
+ * Check if any AI is connected
36
100
  */
37
101
  const isConnected = () => {
102
+ return getAgentCount() > 0;
103
+ };
104
+
105
+ /**
106
+ * Get active agent (or first agent if none active)
107
+ */
108
+ const getActiveAgent = () => {
38
109
  const aiSettings = getAISettings();
39
- return !!(aiSettings.provider && aiSettings.credentials);
110
+ const agents = aiSettings.agents || [];
111
+
112
+ if (agents.length === 0) return null;
113
+
114
+ // Find active agent or use first one
115
+ const activeId = aiSettings.activeAgentId;
116
+ const agent = activeId
117
+ ? agents.find(a => a.id === activeId)
118
+ : agents[0];
119
+
120
+ if (!agent) return null;
121
+
122
+ const provider = getProvider(agent.provider);
123
+ if (!provider) return null;
124
+
125
+ return {
126
+ id: agent.id,
127
+ name: agent.name || provider.name,
128
+ provider: provider,
129
+ providerId: agent.provider,
130
+ option: agent.option,
131
+ model: agent.model || provider.defaultModel,
132
+ credentials: agent.credentials,
133
+ connected: true
134
+ };
40
135
  };
41
136
 
42
137
  /**
43
- * Get current connection info
138
+ * Get agent by ID
44
139
  */
45
- const getConnection = () => {
140
+ const getAgent = (agentId) => {
46
141
  const aiSettings = getAISettings();
47
- if (!aiSettings.provider) return null;
142
+ const agents = aiSettings.agents || [];
143
+ const agent = agents.find(a => a.id === agentId);
144
+
145
+ if (!agent) return null;
48
146
 
49
- const provider = getProvider(aiSettings.provider);
147
+ const provider = getProvider(agent.provider);
50
148
  if (!provider) return null;
51
149
 
52
150
  return {
151
+ id: agent.id,
152
+ name: agent.name || provider.name,
53
153
  provider: provider,
54
- option: aiSettings.option,
55
- model: aiSettings.model || provider.defaultModel,
154
+ providerId: agent.provider,
155
+ option: agent.option,
156
+ model: agent.model || provider.defaultModel,
157
+ credentials: agent.credentials,
56
158
  connected: true
57
159
  };
58
160
  };
59
161
 
60
162
  /**
61
- * Connect to a provider
163
+ * Set active agent
62
164
  */
63
- const connect = async (providerId, optionId, credentials, model = null) => {
165
+ const setActiveAgent = (agentId) => {
166
+ const aiSettings = getAISettings();
167
+ const agents = aiSettings.agents || [];
168
+
169
+ if (!agents.find(a => a.id === agentId)) {
170
+ throw new Error('Agent not found');
171
+ }
172
+
173
+ aiSettings.activeAgentId = agentId;
174
+ saveAISettings(aiSettings);
175
+ };
176
+
177
+ /**
178
+ * Add a new agent (connect to provider)
179
+ */
180
+ const addAgent = async (providerId, optionId, credentials, model = null, customName = null) => {
64
181
  const provider = getProvider(providerId);
65
182
  if (!provider) {
66
183
  throw new Error('Invalid provider');
@@ -71,34 +188,153 @@ const connect = async (providerId, optionId, credentials, model = null) => {
71
188
  throw new Error('Invalid option');
72
189
  }
73
190
 
74
- // Save to settings
75
- const aiSettings = {
191
+ const aiSettings = getAISettings();
192
+ if (!aiSettings.agents) {
193
+ aiSettings.agents = [];
194
+ }
195
+
196
+ // Create new agent
197
+ const agentId = generateAgentId();
198
+ const newAgent = {
199
+ id: agentId,
76
200
  provider: providerId,
77
201
  option: optionId,
78
202
  credentials: credentials,
79
- model: model || provider.defaultModel
203
+ model: model || provider.defaultModel,
204
+ name: customName || provider.name,
205
+ createdAt: Date.now()
80
206
  };
81
207
 
208
+ aiSettings.agents.push(newAgent);
209
+
210
+ // Set as active if first agent
211
+ if (aiSettings.agents.length === 1) {
212
+ aiSettings.activeAgentId = agentId;
213
+ }
214
+
215
+ saveAISettings(aiSettings);
216
+
217
+ // Start AI supervision automatically
218
+ const agent = getAgent(agentId);
219
+ if (agent) {
220
+ // Mock algo target - in real implementation, this would be HQX Ultra Scalping
221
+ const mockAlgo = {
222
+ name: 'HQX Ultra Scalping',
223
+ status: 'ready',
224
+ active: false
225
+ };
226
+
227
+ // Check if other agents are already supervising
228
+ const allAgents = getAgents();
229
+ const activeSupervisionCount = allAgents.filter(a => a.id !== agentId && a.isActive).length;
230
+
231
+ if (activeSupervisionCount === 0) {
232
+ // First agent - start single supervision
233
+ AISupervisor.start(agentId, mockAlgo);
234
+ console.log(`\n🤖 AI supervision started for ${agent.name}`);
235
+ console.log(` Monitoring: HQX Ultra Scalping`);
236
+ console.log(` Mode: Single agent supervision`);
237
+ } else {
238
+ // Additional agent - switch to consensus mode
239
+ console.log(`\n🤖 ${agent.name} added to supervision`);
240
+ console.log(` Switching to multi-agent consensus mode`);
241
+ console.log(` Total agents: ${allAgents.length}`);
242
+
243
+ // Start consensus mode would be handled by supervisor
244
+ AISupervisor.start(agentId, mockAlgo);
245
+ }
246
+ }
247
+
248
+ return agent;
249
+ };
250
+
251
+ /**
252
+ * Remove an agent
253
+ */
254
+ const removeAgent = (agentId) => {
255
+ const aiSettings = getAISettings();
256
+ const agents = aiSettings.agents || [];
257
+
258
+ const index = agents.findIndex(a => a.id === agentId);
259
+ if (index === -1) {
260
+ throw new Error('Agent not found');
261
+ }
262
+
263
+ agents.splice(index, 1);
264
+ aiSettings.agents = agents;
265
+
266
+ // Stop AI supervision for this agent
267
+ AISupervisor.stop(agentId);
268
+
269
+ // If removed agent was active, set new active
270
+ if (aiSettings.activeAgentId === agentId) {
271
+ aiSettings.activeAgentId = agents.length > 0 ? agents[0].id : null;
272
+ }
273
+
274
+ saveAISettings(aiSettings);
275
+ };
276
+
277
+ /**
278
+ * Update agent settings
279
+ */
280
+ const updateAgent = (agentId, updates) => {
281
+ const aiSettings = getAISettings();
282
+ const agents = aiSettings.agents || [];
283
+
284
+ const agent = agents.find(a => a.id === agentId);
285
+ if (!agent) {
286
+ throw new Error('Agent not found');
287
+ }
288
+
289
+ // Apply updates
290
+ if (updates.name) agent.name = updates.name;
291
+ if (updates.model) agent.model = updates.model;
292
+ if (updates.credentials) agent.credentials = updates.credentials;
293
+
82
294
  saveAISettings(aiSettings);
83
- currentConnection = getConnection();
295
+ return getAgent(agentId);
296
+ };
297
+
298
+ /**
299
+ * Disconnect all agents
300
+ */
301
+ const disconnectAll = () => {
302
+ // Stop all AI supervision sessions
303
+ AISupervisor.stopAll();
84
304
 
85
- return currentConnection;
305
+ saveAISettings({ agents: [] });
306
+ };
307
+
308
+ /**
309
+ * Legacy: Get current connection (returns active agent)
310
+ * @deprecated Use getActiveAgent() instead
311
+ */
312
+ const getConnection = () => {
313
+ return getActiveAgent();
86
314
  };
87
315
 
88
316
  /**
89
- * Disconnect from AI
317
+ * Legacy: Connect to a provider (adds new agent)
318
+ * @deprecated Use addAgent() instead
319
+ */
320
+ const connect = async (providerId, optionId, credentials, model = null) => {
321
+ return addAgent(providerId, optionId, credentials, model);
322
+ };
323
+
324
+ /**
325
+ * Legacy: Disconnect (removes all agents)
326
+ * @deprecated Use removeAgent() or disconnectAll() instead
90
327
  */
91
328
  const disconnect = () => {
92
- saveAISettings({});
93
- currentConnection = null;
329
+ disconnectAll();
94
330
  };
95
331
 
96
332
  /**
97
- * Get credentials (for API calls)
333
+ * Get credentials for active agent
98
334
  */
99
335
  const getCredentials = () => {
100
- const aiSettings = getAISettings();
101
- return aiSettings.credentials || null;
336
+ const agent = getActiveAgent();
337
+ return agent?.credentials || null;
102
338
  };
103
339
 
104
340
  /**
@@ -156,33 +392,22 @@ const validateAnthropic = async (credentials) => {
156
392
  const isOAuthToken = token && token.startsWith('sk-ant-oat');
157
393
 
158
394
  if (isOAuthToken) {
159
- // For OAuth tokens from Keychain/SecureStorage, trust them directly
160
- // No need to validate - they come from a trusted source (OS secure storage)
161
- if (credentials.fromKeychain) {
395
+ // OAuth tokens (from Claude Max/Pro subscription) cannot be validated via public API
396
+ // They use a different authentication flow through claude.ai
397
+ // Trust them if they have the correct format (sk-ant-oatXX-...)
398
+ if (token.length > 50 && /^sk-ant-oat\d{2}-[a-zA-Z0-9_-]+$/.test(token)) {
162
399
  return {
163
400
  valid: true,
164
401
  tokenType: 'oauth',
165
- subscriptionType: credentials.subscriptionType || 'max',
166
- trusted: true
167
- };
168
- }
169
-
170
- // For manually entered OAuth tokens, we can't validate them easily
171
- // Claude doesn't have a public API for OAuth token validation
172
- // Just check the format and accept
173
- if (token.length > 50) {
174
- return {
175
- valid: true,
176
- tokenType: 'oauth',
177
- subscriptionType: 'max',
178
- warning: 'Token format looks valid, but could not verify online'
402
+ subscriptionType: credentials.subscriptionType || 'max',
403
+ trusted: credentials.fromKeychain || false
179
404
  };
180
405
  }
181
406
 
182
407
  return { valid: false, error: 'Invalid OAuth token format' };
183
408
  }
184
409
 
185
- // Standard API key validation
410
+ // Standard API key validation (sk-ant-api...)
186
411
  const response = await fetch('https://api.anthropic.com/v1/messages', {
187
412
  method: 'POST',
188
413
  headers: {
@@ -204,14 +429,14 @@ const validateAnthropic = async (credentials) => {
204
429
  const error = await response.json();
205
430
  return { valid: false, error: error.error?.message || 'Invalid API key' };
206
431
  } catch (e) {
207
- // If validation fails but token is from Keychain, still trust it
208
- if (credentials.fromKeychain) {
432
+ // Network error - if it's an OAuth token, still accept it (can't validate anyway)
433
+ const token = credentials.apiKey || credentials.sessionKey || credentials.accessToken;
434
+ if (token && token.startsWith('sk-ant-oat') && token.length > 50) {
209
435
  return {
210
436
  valid: true,
211
437
  tokenType: 'oauth',
212
- subscriptionType: credentials.subscriptionType || 'max',
213
- trusted: true,
214
- warning: 'Could not validate online, but trusted from Keychain'
438
+ subscriptionType: credentials.subscriptionType || 'max',
439
+ warning: 'Could not validate online (network error), but token format is valid'
215
440
  };
216
441
  }
217
442
  return { valid: false, error: e.message };
@@ -401,14 +626,32 @@ const validateOpenAICompatible = async (provider, credentials) => {
401
626
  };
402
627
 
403
628
  module.exports = {
629
+ // Provider info
404
630
  getProviders,
405
631
  getProvider,
632
+
633
+ // Multi-agent API
634
+ getAgents,
635
+ getAgentCount,
636
+ getAgent,
637
+ getActiveAgent,
638
+ setActiveAgent,
639
+ addAgent,
640
+ removeAgent,
641
+ updateAgent,
642
+ disconnectAll,
643
+
644
+ // Legacy API (for backwards compatibility)
406
645
  isConnected,
407
646
  getConnection,
408
647
  connect,
409
648
  disconnect,
410
649
  getCredentials,
650
+
651
+ // Validation
411
652
  validateConnection,
653
+
654
+ // Settings
412
655
  getAISettings,
413
656
  saveAISettings
414
657
  };