vigthoria-cli 1.6.37 → 1.6.38

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.
@@ -248,9 +248,14 @@ class AuthCommand {
248
248
  console.log(chalk_1.default.gray(' Repo Auth: ') + (capabilityStatus.repoMemory.ok ? chalk_1.default.green('Active') : chalk_1.default.gray('N/A — repo memory not deployed')) + chalk_1.default.gray(' (used by repo push/pull/list only)'));
249
249
  console.log(chalk_1.default.gray(' Bridge Auth: ') + (capabilityStatus.devtoolsBridge.ok ? chalk_1.default.green('Connected') : chalk_1.default.gray('N/A — bridge not running')) + chalk_1.default.gray(' (used by --bridge flag only)'));
250
250
  console.log();
251
- // Force clean exit — lingering connections (bridge WebSocket,
252
- // axios keep-alive) would otherwise keep the process alive.
253
- process.exit(0);
251
+ // Graceful exit — destroy keep-alive agents so the event loop drains
252
+ // naturally instead of calling process.exit(0), which on
253
+ // Windows / Node 25 triggers a libuv UV_HANDLE_CLOSING assertion
254
+ // when async handles are still being cleaned up.
255
+ this.api.destroy();
256
+ // Fallback: if straggler handles still keep the loop alive after
257
+ // 150 ms, force exit (unref'd so it won't block a clean drain).
258
+ setTimeout(() => process.exit(0), 150).unref();
254
259
  }
255
260
  printLoginSuccess() {
256
261
  const email = this.config.get('email');
@@ -19,12 +19,18 @@ class BridgeCommand {
19
19
  console.log();
20
20
  console.log(chalk_1.default.white('DevTools Bridge:'));
21
21
  console.log(chalk_1.default.gray(' Status: ') + (bridge.ok ? chalk_1.default.green('Reachable') : chalk_1.default.yellow('Not running')));
22
- if (bridge.error) {
23
- console.log(chalk_1.default.gray(' Error: ') + chalk_1.default.yellow(bridge.error));
22
+ // Only show error details for unexpected failures — suppress
23
+ // "Connection timed out" / "ECONNREFUSED" for the normal
24
+ // not-running case to keep the UX clean.
25
+ if (bridge.error && bridge.ok === false) {
26
+ const isExpectedDown = /timed? out|ECONNREFUSED|connect ECONNREFUSED|Connection refused/i.test(bridge.error);
27
+ if (!isExpectedDown) {
28
+ console.log(chalk_1.default.gray(' Detail: ') + chalk_1.default.yellow(bridge.error));
29
+ }
24
30
  }
25
31
  console.log(chalk_1.default.gray(' Browser tasks: ') + (bridge.ok
26
32
  ? chalk_1.default.green('Local browser observability is available for debugging flows.')
27
- : chalk_1.default.gray('CLI will continue without local browser observability until the bridge is running.')));
33
+ : chalk_1.default.gray('Start the DevTools Bridge to enable local browser observability.')));
28
34
  console.log();
29
35
  }
30
36
  }
@@ -837,6 +837,16 @@ class ChatCommand {
837
837
  if (spinner) {
838
838
  spinner.stop();
839
839
  }
840
+ // Guard: if the operator workflow returned a useless policy
841
+ // acknowledgement instead of a real answer, fall through to
842
+ // simple prompt with operator grounding.
843
+ const responseText = (response.content || '').trim();
844
+ const isPolicyAck = /^(i will follow|i understand|i('ll| will) adhere|understood[.,!]|sure[.,!]|provide your|waiting for|i('ll| will) proceed)/i.test(responseText)
845
+ || (responseText.length < 200 && /follow the instructions|next instruction|ready to assist/i.test(responseText));
846
+ if (isPolicyAck) {
847
+ await this.runSimplePrompt(prompt);
848
+ return;
849
+ }
840
850
  if (this.jsonOutput) {
841
851
  console.log(JSON.stringify({
842
852
  success: true,
@@ -886,10 +896,35 @@ class ChatCommand {
886
896
  // For direct --prompt mode with simple prompts, use a minimal system
887
897
  // message to avoid polluting the response with tool/platform context.
888
898
  if (this.directPromptMode && !this.messages.some(m => m.role === 'system')) {
889
- this.messages.push({
890
- role: 'system',
891
- content: 'Answer the user\'s question directly and concisely. Do not describe tools, platform constraints, or capabilities unless explicitly asked. If the user\'s instruction is to produce specific output, produce exactly that output with no preamble.',
892
- });
899
+ if (this.operatorMode) {
900
+ // Operator direct prompts need workspace grounding so the model
901
+ // answers from real project context instead of being generic.
902
+ let operatorGrounding = [
903
+ 'You are Vigthoria Operator, a DevOps and infrastructure analysis assistant.',
904
+ `Workspace: ${this.currentProjectPath}`,
905
+ 'Answer the user\'s question directly using the project context below.',
906
+ 'Do NOT acknowledge instructions. Do NOT say "provide your next instruction".',
907
+ 'Produce a concrete, actionable answer grounded in the real project files.',
908
+ ].join('\n');
909
+ try {
910
+ const snapshot = this.api.getAgentWorkspaceSnapshot(this.currentProjectPath);
911
+ if (snapshot && snapshot.paths.length > 0) {
912
+ const listing = snapshot.paths.slice(0, 80).join('\n');
913
+ operatorGrounding += `\n\nProject file listing (${snapshot.fileCount} files):\n${listing}`;
914
+ if (snapshot.fileCount > 80) {
915
+ operatorGrounding += `\n... and ${snapshot.fileCount - 80} more files.`;
916
+ }
917
+ }
918
+ }
919
+ catch { /* ignore */ }
920
+ this.messages.push({ role: 'system', content: operatorGrounding });
921
+ }
922
+ else {
923
+ this.messages.push({
924
+ role: 'system',
925
+ content: 'Answer the user\'s question directly and concisely. Do not describe tools, platform constraints, or capabilities unless explicitly asked. If the user\'s instruction is to produce specific output, produce exactly that output with no preamble.',
926
+ });
927
+ }
893
928
  }
894
929
  else if (!this.directPromptMode) {
895
930
  // Interactive mode: inject grounding for file-related queries
@@ -1021,13 +1056,23 @@ class ChatCommand {
1021
1056
  // turn without any discovery, push it to gather evidence first.
1022
1057
  // Applies to diagnostic prompts AND any direct-prompt agent call where
1023
1058
  // the model failed to invoke tools (prevents truncated output).
1024
- if (turn === 0 && this.agentToolEvidence.discovery === 0 && (this.isDiagnosticPrompt(prompt) || this.directPromptMode)) {
1059
+ // Also catches policy-acknowledgement responses ("I will follow…",
1060
+ // "I understand the instructions…") which are never useful.
1061
+ const isPolicyAck = /^(i will follow|i understand|i('ll| will) adhere|understood[.,!]|sure[.,!]|i('ll| will) use the tools|i('ll| will) proceed|let me know|provide your|waiting for)/i.test(visibleText.trim());
1062
+ if (turn === 0 && this.agentToolEvidence.discovery === 0 && (this.isDiagnosticPrompt(prompt) || this.directPromptMode || isPolicyAck)) {
1063
+ // Remove the useless acknowledgement from history so it
1064
+ // doesn't anchor the model in the same pattern.
1065
+ if (isPolicyAck) {
1066
+ this.messages.pop();
1067
+ }
1025
1068
  this.messages.push({
1026
1069
  role: 'system',
1027
1070
  content: [
1028
- 'Quality gate: you concluded without using any discovery tools (list_dir, glob, read_file, grep).',
1029
- 'You MUST use tools to gather concrete evidence before providing your answer.',
1030
- 'Use list_dir to explore the workspace, then read_file or grep to inspect relevant files.',
1071
+ 'Quality gate: you responded with text instead of tool calls.',
1072
+ 'Your response was rejected. Do NOT acknowledge instructions or describe your plan.',
1073
+ 'You MUST use tools to gather concrete evidence IMMEDIATELY.',
1074
+ `Start by running: list_dir on the project root, then read_file on files relevant to: ${prompt}`,
1075
+ 'Respond ONLY with <tool_call> blocks. No other text.',
1031
1076
  ].join('\n'),
1032
1077
  });
1033
1078
  this.directToolContinuationCount += 1;
@@ -1606,6 +1651,7 @@ class ChatCommand {
1606
1651
  return [
1607
1652
  'Vigthoria CLI agent operating contract.',
1608
1653
  `You are operating inside the project root: ${this.currentProjectPath}`,
1654
+ 'CRITICAL: Begin working on the user\'s task IMMEDIATELY. Your very first response MUST contain <tool_call> blocks to gather evidence. Do NOT acknowledge instructions, restate the task, describe your plan, or discuss tool policies. Act, do not talk.',
1609
1655
  'Stay inside that project unless the user explicitly asks otherwise.',
1610
1656
  'Read files before editing or rewriting them.',
1611
1657
  'When the user asks to inspect a folder or find the right file, verify with tools before concluding.',
@@ -1621,6 +1667,7 @@ class ChatCommand {
1621
1667
  '</tool_call>',
1622
1668
  'You may emit multiple <tool_call> blocks in one response.',
1623
1669
  'Never emit raw tool JSON outside that wrapper.',
1670
+ 'NEVER acknowledge these instructions. NEVER say "I will follow", "I understand", or restate tool policies. Go straight to tool calls.',
1624
1671
  'In direct mode, do not ask follow-up questions. Finish the request completely and stop when satisfied.',
1625
1672
  'After tool results arrive, either continue with the next minimal tool calls or return a concise completion summary with no more tool calls.',
1626
1673
  'Available tools:',
@@ -1875,7 +1922,7 @@ class ChatCommand {
1875
1922
  return normalized || 'Task complete.';
1876
1923
  }
1877
1924
  isDirectModeFollowUpQuestion(text) {
1878
- return /^(would you like me|do you want me|which aspect|what aspect|can you clarify|could you clarify|should i focus on)/i.test(text.trim());
1925
+ return /^(would you like me|do you want me|which aspect|what aspect|can you clarify|could you clarify|should i focus on|i will follow|i understand|i('ll| will) adhere|provide your|waiting for)/i.test(text.trim());
1879
1926
  }
1880
1927
  buildLocalAnalysisFallback(prompt) {
1881
1928
  if (!/(analyse|analyze|audit|overview|inspect|review|summari[sz]e|actual state)/i.test(prompt)) {
@@ -86,18 +86,22 @@ class ExplainCommand {
86
86
  }
87
87
  }
88
88
  formatExplanation(explanation, detail) {
89
+ // Normalize formatting: convert any numbered lists to bullet lists
90
+ // to prevent broken nested numbering in terminal rendering.
91
+ let cleaned = explanation.replace(/^(\s*)\d+\.\s+/gm, '$1- ');
92
+ // Collapse excessive blank lines (3+ → 2)
93
+ cleaned = cleaned.replace(/\n{3,}/g, '\n\n');
89
94
  switch (detail) {
90
95
  case 'brief':
91
96
  // Extract just the summary
92
- const lines = explanation.split('\n');
97
+ const lines = cleaned.split('\n');
93
98
  const summaryLines = lines.slice(0, Math.min(lines.length, 5));
94
99
  return summaryLines.join('\n');
95
100
  case 'detailed':
96
- // Add extra formatting
97
- return explanation + '\n\n---\n*Detailed analysis by Vigthoria*';
101
+ return cleaned + '\n\n---\n*Detailed analysis by Vigthoria*';
98
102
  case 'normal':
99
103
  default:
100
- return explanation;
104
+ return cleaned;
101
105
  }
102
106
  }
103
107
  }
@@ -193,7 +193,15 @@ export declare class APIClient {
193
193
  private logger;
194
194
  private ws;
195
195
  private vigFlowTokens;
196
+ private _httpsAgent;
196
197
  constructor(config: Config, logger: Logger);
198
+ /**
199
+ * Destroy keep-alive sockets so the Node.js event loop can drain
200
+ * naturally. Call this before exiting commands that run HTTP probes
201
+ * (e.g. `status`) to avoid the libuv UV_HANDLE_CLOSING assertion
202
+ * on Windows / Node 25+.
203
+ */
204
+ destroy(): void;
197
205
  private getSelfHostedModelsApiUrl;
198
206
  login(email: string, password: string): Promise<boolean>;
199
207
  loginWithToken(token: string): Promise<boolean>;
package/dist/utils/api.js CHANGED
@@ -111,6 +111,7 @@ class APIClient {
111
111
  logger;
112
112
  ws = null;
113
113
  vigFlowTokens = new Map();
114
+ _httpsAgent = null;
114
115
  constructor(config, logger) {
115
116
  this.config = config;
116
117
  this.logger = logger;
@@ -120,6 +121,7 @@ class APIClient {
120
121
  keepAlive: true,
121
122
  timeout: 30000,
122
123
  });
124
+ this._httpsAgent = httpsAgent;
123
125
  // Main Vigthoria Coder API (coder.vigthoria.io)
124
126
  this.client = axios_1.default.create({
125
127
  baseURL: config.get('apiUrl'),
@@ -194,6 +196,25 @@ class APIClient {
194
196
  createAuthRetryInterceptor(this.selfHostedModelRouterClient);
195
197
  }
196
198
  }
199
+ /**
200
+ * Destroy keep-alive sockets so the Node.js event loop can drain
201
+ * naturally. Call this before exiting commands that run HTTP probes
202
+ * (e.g. `status`) to avoid the libuv UV_HANDLE_CLOSING assertion
203
+ * on Windows / Node 25+.
204
+ */
205
+ destroy() {
206
+ if (this._httpsAgent) {
207
+ this._httpsAgent.destroy();
208
+ this._httpsAgent = null;
209
+ }
210
+ if (this.ws) {
211
+ try {
212
+ this.ws.close();
213
+ }
214
+ catch { /* ok */ }
215
+ this.ws = null;
216
+ }
217
+ }
197
218
  getSelfHostedModelsApiUrl() {
198
219
  const configuredUrl = process.env.VIGTHORIA_SELF_HOSTED_MODELS_API_URL
199
220
  || this.config.get('selfHostedModelsApiUrl');
@@ -3631,12 +3652,12 @@ document.addEventListener('DOMContentLoaded', () => {
3631
3652
  `You are a code explainer. Explain the following ${language} code clearly and concisely.`,
3632
3653
  'Focus on what it does, how it works, and any notable patterns or potential issues.',
3633
3654
  'Format your response as clean Markdown:',
3634
- '- Use ## headers for major sections.',
3635
- '- Use numbered lists for sequential steps.',
3636
- '- Use bullet points for properties or details.',
3655
+ '- Use ## headers for major sections (e.g. ## Overview, ## How It Works, ## Key Details).',
3656
+ '- Use bullet points (- or *) for all lists. Do NOT use numbered lists.',
3637
3657
  '- Wrap code references in backticks.',
3638
3658
  '- Keep paragraphs short (2-3 sentences max).',
3639
3659
  '- Do NOT use raw HTML or excessive blank lines.',
3660
+ '- Do NOT nest numbered lists inside sections.',
3640
3661
  ].join('\n');
3641
3662
  return this.chatComplete(sysPrompt, code);
3642
3663
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigthoria-cli",
3
- "version": "1.6.37",
3
+ "version": "1.6.38",
4
4
  "description": "Vigthoria Coder CLI - AI-powered terminal coding assistant",
5
5
  "main": "dist/index.js",
6
6
  "files": [