hedgequantx 2.9.23 → 2.9.26

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.23",
3
+ "version": "2.9.26",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -334,16 +334,25 @@ const drawConnectionTest = async (agents, boxWidth, clearWithBanner) => {
334
334
 
335
335
  const W = boxWidth - 2;
336
336
 
337
- // Show loading state with spinner inside closed box
337
+ // Show loading state with spinner inside closed box (centered vertically)
338
338
  clearWithBanner();
339
339
  console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
340
340
  console.log(chalk.cyan('║') + chalk.yellow.bold(centerText('AI AGENTS CONNECTION TEST', W)) + chalk.cyan('║'));
341
341
  console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
342
342
 
343
+ // Empty lines for vertical centering (top)
344
+ console.log(chalk.cyan('║') + ' '.repeat(W) + chalk.cyan('║'));
345
+ console.log(chalk.cyan('║') + ' '.repeat(W) + chalk.cyan('║'));
346
+ console.log(chalk.cyan('║') + ' '.repeat(W) + chalk.cyan('║'));
347
+
343
348
  // Centered spinner text inside box
344
349
  const spinnerText = 'Testing connections...';
345
350
  const paddedText = centerText(spinnerText, W);
346
351
  console.log(chalk.cyan('║') + chalk.yellow(paddedText) + chalk.cyan('║'));
352
+
353
+ // Empty lines for vertical centering (bottom)
354
+ console.log(chalk.cyan('║') + ' '.repeat(W) + chalk.cyan('║'));
355
+ console.log(chalk.cyan('║') + ' '.repeat(W) + chalk.cyan('║'));
347
356
  console.log(chalk.cyan('║') + ' '.repeat(W) + chalk.cyan('║'));
348
357
  console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
349
358
 
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  const cliproxy = require('../cliproxy');
13
- const https = require('https');
13
+ const { extractJSON } = require('./parser');
14
14
 
15
15
  /** Test prompt to verify agent understands directive format */
16
16
  const TEST_PROMPT = `You are being tested. Respond ONLY with this exact JSON, nothing else:
@@ -161,6 +161,7 @@ const testAgentConnection = async (agent) => {
161
161
 
162
162
  /**
163
163
  * Validate that response matches expected JSON format
164
+ * Uses extractJSON from parser.js to handle MiniMax <think> tags and other formats
164
165
  * @param {string} content - Response content from agent
165
166
  * @returns {Object} { valid, error }
166
167
  */
@@ -169,53 +170,40 @@ const validateResponseFormat = (content) => {
169
170
  return { valid: false, error: 'Empty response' };
170
171
  }
171
172
 
172
- try {
173
- // Try to extract JSON from response
174
- let json;
175
-
176
- // Direct parse
177
- try {
178
- json = JSON.parse(content.trim());
179
- } catch (e) {
180
- // Try to find JSON in response
181
- const match = content.match(/\{[\s\S]*\}/);
182
- if (match) {
183
- json = JSON.parse(match[0]);
184
- } else {
185
- return { valid: false, error: 'No JSON in response' };
186
- }
187
- }
188
-
189
- // Check required fields
190
- if (!json.decision) {
191
- return { valid: false, error: 'Missing "decision" field' };
192
- }
193
-
194
- if (json.confidence === undefined) {
195
- return { valid: false, error: 'Missing "confidence" field' };
196
- }
197
-
198
- if (!json.reason) {
199
- return { valid: false, error: 'Missing "reason" field' };
200
- }
201
-
202
- // Validate decision value
203
- const validDecisions = ['approve', 'reject', 'modify'];
204
- if (!validDecisions.includes(json.decision)) {
205
- return { valid: false, error: `Invalid decision: ${json.decision}` };
206
- }
207
-
208
- // Validate confidence is number 0-100
209
- const conf = Number(json.confidence);
210
- if (isNaN(conf) || conf < 0 || conf > 100) {
211
- return { valid: false, error: `Invalid confidence: ${json.confidence}` };
212
- }
213
-
214
- return { valid: true, error: null };
215
-
216
- } catch (error) {
217
- return { valid: false, error: `Parse error: ${error.message}` };
173
+ // Use robust JSON extraction from parser.js
174
+ // Handles: direct JSON, markdown code blocks, JSON with extra text (MiniMax <think> tags)
175
+ const json = extractJSON(content);
176
+
177
+ if (!json) {
178
+ return { valid: false, error: 'No valid JSON found in response' };
179
+ }
180
+
181
+ // Check required fields
182
+ if (!json.decision) {
183
+ return { valid: false, error: 'Missing "decision" field' };
184
+ }
185
+
186
+ if (json.confidence === undefined) {
187
+ return { valid: false, error: 'Missing "confidence" field' };
218
188
  }
189
+
190
+ if (!json.reason) {
191
+ return { valid: false, error: 'Missing "reason" field' };
192
+ }
193
+
194
+ // Validate decision value
195
+ const validDecisions = ['approve', 'reject', 'modify'];
196
+ if (!validDecisions.includes(json.decision)) {
197
+ return { valid: false, error: `Invalid decision: ${json.decision}` };
198
+ }
199
+
200
+ // Validate confidence is number 0-100
201
+ const conf = Number(json.confidence);
202
+ if (isNaN(conf) || conf < 0 || conf > 100) {
203
+ return { valid: false, error: `Invalid confidence: ${json.confidence}` };
204
+ }
205
+
206
+ return { valid: true, error: null };
219
207
  };
220
208
 
221
209
  /**
@@ -338,7 +326,13 @@ const formatPreflightResults = (results, boxWidth) => {
338
326
  lines.push(dottedLine('Format', chalk.green('✓ VALID'), 9));
339
327
  } else {
340
328
  lines.push(dottedLine('Connection', chalk.red('✗ FAILED'), 9));
341
- lines.push(chalk.red(` Error: ${agent.error}`));
329
+ // Truncate error message to fit in box
330
+ const maxErrorLen = W - 16; // Account for " Error: " prefix
331
+ let errorMsg = agent.error || 'Unknown error';
332
+ if (errorMsg.length > maxErrorLen) {
333
+ errorMsg = errorMsg.substring(0, maxErrorLen - 3) + '...';
334
+ }
335
+ lines.push(chalk.red(` Error: ${errorMsg}`));
342
336
  }
343
337
 
344
338
  lines.push('');
@@ -19,6 +19,7 @@ const DEFAULT_RESPONSE = {
19
19
 
20
20
  /**
21
21
  * Extract JSON from a string that may contain markdown or extra text
22
+ * Handles MiniMax <think> tags, markdown code blocks, etc.
22
23
  */
23
24
  const extractJSON = (text) => {
24
25
  if (!text || typeof text !== 'string') return null;
@@ -36,8 +37,36 @@ const extractJSON = (text) => {
36
37
  } catch (e) { /* continue */ }
37
38
  }
38
39
 
39
- // Try to find JSON object pattern
40
- const jsonMatch = text.match(/\{[\s\S]*"decision"[\s\S]*\}/);
40
+ // Remove <think>...</think> tags (MiniMax)
41
+ let cleaned = text.replace(/<think>[\s\S]*?<\/think>/gi, '').trim();
42
+
43
+ // Try to parse cleaned text
44
+ try {
45
+ return JSON.parse(cleaned);
46
+ } catch (e) { /* continue */ }
47
+
48
+ // Find first complete JSON object using bracket matching
49
+ const startIdx = cleaned.indexOf('{');
50
+ if (startIdx !== -1) {
51
+ let depth = 0;
52
+ let endIdx = -1;
53
+ 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;
59
+ }
60
+ }
61
+ if (endIdx !== -1) {
62
+ try {
63
+ return JSON.parse(cleaned.substring(startIdx, endIdx + 1));
64
+ } catch (e) { /* continue */ }
65
+ }
66
+ }
67
+
68
+ // Last resort: try to find JSON object pattern with "decision"
69
+ const jsonMatch = cleaned.match(/\{[^{}]*"decision"[^{}]*\}/);
41
70
  if (jsonMatch) {
42
71
  try {
43
72
  return JSON.parse(jsonMatch[0]);