hedgequantx 2.5.12 → 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);
48
144
 
49
- const provider = getProvider(aiSettings.provider);
145
+ if (!agent) return null;
146
+
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,43 +392,22 @@ const validateAnthropic = async (credentials) => {
156
392
  const isOAuthToken = token && token.startsWith('sk-ant-oat');
157
393
 
158
394
  if (isOAuthToken) {
159
- // OAuth tokens use Bearer auth and claude.ai endpoint
160
- const response = await fetch('https://api.claude.ai/api/organizations', {
161
- method: 'GET',
162
- headers: {
163
- 'Authorization': `Bearer ${token}`,
164
- 'Content-Type': 'application/json',
165
- 'User-Agent': 'HQX-CLI/1.0'
166
- }
167
- });
168
-
169
- if (response.ok) {
170
- return { valid: true, tokenType: 'oauth', subscriptionType: credentials.subscriptionType || 'max' };
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)) {
399
+ return {
400
+ valid: true,
401
+ tokenType: 'oauth',
402
+ subscriptionType: credentials.subscriptionType || 'max',
403
+ trusted: credentials.fromKeychain || false
404
+ };
171
405
  }
172
406
 
173
- // Try alternate endpoint
174
- const altResponse = await fetch('https://claude.ai/api/auth/session', {
175
- method: 'GET',
176
- headers: {
177
- 'Authorization': `Bearer ${token}`,
178
- 'Cookie': `sessionKey=${token}`
179
- }
180
- });
181
-
182
- if (altResponse.ok) {
183
- return { valid: true, tokenType: 'oauth', subscriptionType: credentials.subscriptionType || 'max' };
184
- }
185
-
186
- // For OAuth tokens from Keychain, trust them without validation
187
- // since the Keychain already verified the user
188
- if (credentials.fromKeychain) {
189
- return { valid: true, tokenType: 'oauth', subscriptionType: credentials.subscriptionType || 'max', trusted: true };
190
- }
191
-
192
- return { valid: false, error: 'OAuth token expired or invalid' };
407
+ return { valid: false, error: 'Invalid OAuth token format' };
193
408
  }
194
409
 
195
- // Standard API key validation
410
+ // Standard API key validation (sk-ant-api...)
196
411
  const response = await fetch('https://api.anthropic.com/v1/messages', {
197
412
  method: 'POST',
198
413
  headers: {
@@ -214,6 +429,16 @@ const validateAnthropic = async (credentials) => {
214
429
  const error = await response.json();
215
430
  return { valid: false, error: error.error?.message || 'Invalid API key' };
216
431
  } catch (e) {
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) {
435
+ return {
436
+ valid: true,
437
+ tokenType: 'oauth',
438
+ subscriptionType: credentials.subscriptionType || 'max',
439
+ warning: 'Could not validate online (network error), but token format is valid'
440
+ };
441
+ }
217
442
  return { valid: false, error: e.message };
218
443
  }
219
444
  };
@@ -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
  };