hedgequantx 2.9.32 → 2.9.33

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.9.32",
3
+ "version": "2.9.33",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
package/src/app.js CHANGED
@@ -77,7 +77,10 @@ const refreshStats = async () => {
77
77
 
78
78
  try {
79
79
  const allAccounts = await connections.getAllAccounts();
80
- const activeAccounts = allAccounts.filter(acc => acc.status === 0);
80
+ // Filter active accounts: status === 0 (ProjectX) OR status === 'active' (Rithmic) OR no status
81
+ const activeAccounts = allAccounts.filter(acc =>
82
+ acc.status === 0 || acc.status === 'active' || acc.status === undefined || acc.status === null
83
+ );
81
84
 
82
85
  let totalBalance = null;
83
86
  let totalPnl = null;
@@ -113,50 +113,75 @@ const testApiKeyConnection = async (agent) => {
113
113
 
114
114
  /**
115
115
  * Test a single agent connection and response format
116
+ * Includes retry logic for non-deterministic agents (like MiniMax)
116
117
  * @param {Object} agent - Agent config { id, provider, modelId, connectionType, apiKey, ... }
118
+ * @param {number} maxRetries - Maximum retry attempts (default 2)
117
119
  * @returns {Promise<Object>} { success, latency, formatValid, error }
118
120
  */
119
- const testAgentConnection = async (agent) => {
120
- const startTime = Date.now();
121
+ const testAgentConnection = async (agent, maxRetries = 2) => {
122
+ let lastResult = null;
121
123
 
122
- try {
123
- // Route based on connection type
124
- if (agent.connectionType === 'apikey') {
125
- return await testApiKeyConnection(agent);
126
- }
127
-
128
- // CLIProxy connection
129
- const result = await cliproxy.chat(agent.provider, agent.modelId, TEST_PROMPT, AGENT_TIMEOUT);
130
- const latency = Date.now() - startTime;
124
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
125
+ const startTime = Date.now();
131
126
 
132
- if (!result.success) {
133
- return {
127
+ try {
128
+ // Route based on connection type
129
+ if (agent.connectionType === 'apikey') {
130
+ lastResult = await testApiKeyConnection(agent);
131
+ } else {
132
+ // CLIProxy connection
133
+ const result = await cliproxy.chat(agent.provider, agent.modelId, TEST_PROMPT, AGENT_TIMEOUT);
134
+ const latency = Date.now() - startTime;
135
+
136
+ if (!result.success) {
137
+ lastResult = {
138
+ success: false,
139
+ latency,
140
+ formatValid: false,
141
+ error: result.error || 'No response from agent'
142
+ };
143
+ } else {
144
+ // Validate response format
145
+ const formatResult = validateResponseFormat(result.content);
146
+
147
+ lastResult = {
148
+ success: formatResult.valid,
149
+ latency,
150
+ formatValid: formatResult.valid,
151
+ error: formatResult.valid ? null : formatResult.error,
152
+ response: result.content
153
+ };
154
+ }
155
+ }
156
+
157
+ // If successful, return immediately
158
+ if (lastResult.success) {
159
+ return lastResult;
160
+ }
161
+
162
+ // Only retry on format errors (not connection errors)
163
+ if (lastResult.error && !lastResult.error.includes('valid JSON') &&
164
+ !lastResult.error.includes('Missing') && !lastResult.error.includes('Invalid')) {
165
+ // Connection error - don't retry
166
+ return lastResult;
167
+ }
168
+
169
+ // Wait a bit before retry
170
+ if (attempt < maxRetries) {
171
+ await new Promise(r => setTimeout(r, 500));
172
+ }
173
+
174
+ } catch (error) {
175
+ lastResult = {
134
176
  success: false,
135
- latency,
177
+ latency: Date.now() - startTime,
136
178
  formatValid: false,
137
- error: result.error || 'No response from agent'
179
+ error: error.message || 'Connection failed'
138
180
  };
139
181
  }
140
-
141
- // Validate response format
142
- const formatResult = validateResponseFormat(result.content);
143
-
144
- return {
145
- success: formatResult.valid,
146
- latency,
147
- formatValid: formatResult.valid,
148
- error: formatResult.valid ? null : formatResult.error,
149
- response: result.content
150
- };
151
-
152
- } catch (error) {
153
- return {
154
- success: false,
155
- latency: Date.now() - startTime,
156
- formatValid: false,
157
- error: error.message || 'Connection failed'
158
- };
159
182
  }
183
+
184
+ return lastResult;
160
185
  };
161
186
 
162
187
  /**
@@ -37,8 +37,13 @@ const extractJSON = (text) => {
37
37
  } catch (e) { /* continue */ }
38
38
  }
39
39
 
40
- // Remove <think>...</think> tags (MiniMax)
41
- let cleaned = text.replace(/<think>[\s\S]*?<\/think>/gi, '').trim();
40
+ // Remove <think>...</think> tags (MiniMax) - multiple patterns
41
+ let cleaned = text
42
+ .replace(/<think>[\s\S]*?<\/think>/gi, '')
43
+ .replace(/<thinking>[\s\S]*?<\/thinking>/gi, '')
44
+ .replace(/<reasoning>[\s\S]*?<\/reasoning>/gi, '')
45
+ .replace(/<analysis>[\s\S]*?<\/analysis>/gi, '')
46
+ .trim();
42
47
 
43
48
  // Try to parse cleaned text
44
49
  try {
@@ -50,14 +55,37 @@ const extractJSON = (text) => {
50
55
  if (startIdx !== -1) {
51
56
  let depth = 0;
52
57
  let endIdx = -1;
58
+ let inString = false;
59
+ let escapeNext = false;
60
+
53
61
  for (let i = startIdx; i < cleaned.length; i++) {
54
- if (cleaned[i] === '{') depth++;
55
- if (cleaned[i] === '}') depth--;
56
- if (depth === 0) {
57
- endIdx = i;
58
- break;
62
+ const char = cleaned[i];
63
+
64
+ if (escapeNext) {
65
+ escapeNext = false;
66
+ continue;
67
+ }
68
+
69
+ if (char === '\\') {
70
+ escapeNext = true;
71
+ continue;
72
+ }
73
+
74
+ if (char === '"') {
75
+ inString = !inString;
76
+ continue;
77
+ }
78
+
79
+ if (!inString) {
80
+ if (char === '{') depth++;
81
+ if (char === '}') depth--;
82
+ if (depth === 0) {
83
+ endIdx = i;
84
+ break;
85
+ }
59
86
  }
60
87
  }
88
+
61
89
  if (endIdx !== -1) {
62
90
  try {
63
91
  return JSON.parse(cleaned.substring(startIdx, endIdx + 1));
@@ -65,6 +93,19 @@ const extractJSON = (text) => {
65
93
  }
66
94
  }
67
95
 
96
+ // Try to extract just decision/confidence/reason pattern
97
+ const decisionMatch = cleaned.match(/"decision"\s*:\s*"(approve|reject|modify)"/i);
98
+ const confidenceMatch = cleaned.match(/"confidence"\s*:\s*(\d+)/);
99
+ const reasonMatch = cleaned.match(/"reason"\s*:\s*"([^"]+)"/);
100
+
101
+ if (decisionMatch && confidenceMatch) {
102
+ return {
103
+ decision: decisionMatch[1].toLowerCase(),
104
+ confidence: parseInt(confidenceMatch[1]),
105
+ reason: reasonMatch ? reasonMatch[1] : 'Extracted from partial response'
106
+ };
107
+ }
108
+
68
109
  // Last resort: try to find JSON object pattern with "decision"
69
110
  const jsonMatch = cleaned.match(/\{[^{}]*"decision"[^{}]*\}/);
70
111
  if (jsonMatch) {