vigthoria-cli 1.11.4 → 1.11.5

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.
@@ -47,6 +47,7 @@ export declare class ChatCommand {
47
47
  private retryPromptSignature;
48
48
  private retryPromptStreak;
49
49
  private v3SuppressThinkingStream;
50
+ private v3StreamedAnswerBuffer;
50
51
  private lastAgentRunOutcome;
51
52
  private isJwtExpirationError;
52
53
  private isNetworkError;
@@ -13,7 +13,7 @@ import { TaskDisplay } from '../utils/task-display.js';
13
13
  import { ProjectMemoryService } from '../utils/project-memory.js';
14
14
  import { buildPersonaOverlay, normalizePersonaMode } from '../utils/persona.js';
15
15
  import { runAgentSessionMenu, shouldShowAgentSessionMenu } from './agent-session-menu.js';
16
- import { createLiveOutcome, evaluateExecutorSuccess, handleTaskEvent, noteAnalysisToolUse, } from '../utils/agentRunOutcome.js';
16
+ import { createLiveOutcome, evaluateExecutorSuccess, handleTaskEvent, normalizeAgentAnswerContent, noteAnalysisToolUse, } from '../utils/agentRunOutcome.js';
17
17
  const DEFAULT_V3_AGENT_TIMEOUT_MS = (() => {
18
18
  const rawValue = process.env.VIGTHORIA_AGENT_TIMEOUT_MS || process.env.V3_AGENT_TIMEOUT_MS;
19
19
  if (!rawValue) {
@@ -66,6 +66,7 @@ export class ChatCommand {
66
66
  retryPromptSignature = null;
67
67
  retryPromptStreak = 0;
68
68
  v3SuppressThinkingStream = false;
69
+ v3StreamedAnswerBuffer = '';
69
70
  // Last completed Agent run — used by /retry, /continue, and the final summary block.
70
71
  lastAgentRunOutcome = null;
71
72
  isJwtExpirationError(error) {
@@ -733,6 +734,7 @@ export class ChatCommand {
733
734
  spinner.stop();
734
735
  }
735
736
  process.stdout.write(safeText);
737
+ this.v3StreamedAnswerBuffer += safeText;
736
738
  }
737
739
  sanitizeV3VisibleStreamText(text) {
738
740
  let output = String(text || '');
@@ -1965,6 +1967,7 @@ export class ChatCommand {
1965
1967
  qualityMissing: [],
1966
1968
  qualityBlockers: evaluation.executorSucceeded ? [] : [evaluation.statusHeadline],
1967
1969
  hasOutput: liveOutcome.workspaceHasOutput,
1970
+ answerContent: liveOutcome.answerContent || null,
1968
1971
  selfHealStatus: 'skipped',
1969
1972
  selfHealTool: null,
1970
1973
  plannerError: null,
@@ -2453,6 +2456,7 @@ export class ChatCommand {
2453
2456
  this.v3ToolCallCount = 0;
2454
2457
  this.v3LastActivity = Date.now();
2455
2458
  this.v3StreamingStarted = false;
2459
+ this.v3StreamedAnswerBuffer = '';
2456
2460
  const taskDisplay = new TaskDisplay(['Analyse workspace', 'Execute tasks', 'Validate output', 'Self-heal'], !this.jsonOutput);
2457
2461
  taskDisplay.start(0);
2458
2462
  const spinner = this.jsonOutput ? null : createSpinner({
@@ -2637,9 +2641,11 @@ export class ChatCommand {
2637
2641
  const workspaceHasOutput = this.api.hasAgentWorkspaceOutput(workspaceContext);
2638
2642
  const changedFileCount = response.changedFiles ? Object.keys(response.changedFiles).length : 0;
2639
2643
  const requiresWorkspaceChanges = this.taskRequiresWorkspaceChanges(prompt);
2644
+ const answerContent = normalizeAgentAnswerContent(response.content, this.v3StreamedAnswerBuffer);
2640
2645
  liveOutcome.changedFileCount = changedFileCount;
2641
2646
  liveOutcome.requiresWorkspaceChanges = requiresWorkspaceChanges;
2642
- liveOutcome.workspaceHasOutput = workspaceHasOutput;
2647
+ liveOutcome.workspaceHasOutput = requiresWorkspaceChanges ? workspaceHasOutput : false;
2648
+ liveOutcome.answerContent = answerContent;
2643
2649
  const success = previewGate?.required === true
2644
2650
  ? (previewGate?.passed === true || workspaceHasOutput)
2645
2651
  : true;
@@ -2683,12 +2689,27 @@ export class ChatCommand {
2683
2689
  console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
2684
2690
  }
2685
2691
  }
2692
+ else if (answerContent) {
2693
+ if (!this.directPromptMode) {
2694
+ console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
2695
+ }
2696
+ console.log('');
2697
+ console.log(chalk.bold('Answer:'));
2698
+ console.log(answerContent);
2699
+ }
2686
2700
  else if (response.content) {
2687
2701
  if (!this.directPromptMode) {
2688
2702
  console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
2689
2703
  }
2690
2704
  console.log(response.content);
2691
2705
  }
2706
+ else if (!requiresWorkspaceChanges) {
2707
+ if (!this.directPromptMode) {
2708
+ console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
2709
+ }
2710
+ console.log(chalk.yellow('The agent finished without producing an answer to your question.'));
2711
+ console.log(chalk.gray('Try /retry or ask again with more detail (e.g. file name or feature).'));
2712
+ }
2692
2713
  else {
2693
2714
  if (!this.directPromptMode) {
2694
2715
  console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
@@ -2810,6 +2831,7 @@ export class ChatCommand {
2810
2831
  qualityMissing: liveOutcome.qualityMissing,
2811
2832
  qualityBlockers: liveOutcome.qualityBlockers,
2812
2833
  hasOutput: workspaceHasOutput,
2834
+ answerContent: answerContent || null,
2813
2835
  selfHealStatus,
2814
2836
  selfHealTool,
2815
2837
  plannerError: liveOutcome.plannerError ? sanitizeUserFacingErrorText(liveOutcome.plannerError) : null,
@@ -2837,7 +2859,7 @@ export class ChatCommand {
2837
2859
  taskId: response.taskId || null,
2838
2860
  contextId: response.contextId || null,
2839
2861
  partial: response.partial === true || (!executorSucceeded && liveOutcome.tasksSucceeded > 0),
2840
- content: response.content || runEvaluation.statusHeadline,
2862
+ content: answerContent || response.content || runEvaluation.statusHeadline,
2841
2863
  statusHeadline: runEvaluation.statusHeadline,
2842
2864
  tasksSucceeded: liveOutcome.tasksSucceeded,
2843
2865
  tasksTotal: liveOutcome.tasksTotal,
@@ -2853,7 +2875,7 @@ export class ChatCommand {
2853
2875
  },
2854
2876
  }, null, 2));
2855
2877
  }
2856
- this.messages.push({ role: 'assistant', content: response.content || runEvaluation.statusHeadline });
2878
+ this.messages.push({ role: 'assistant', content: answerContent || response.content || runEvaluation.statusHeadline });
2857
2879
  watcher?.stop();
2858
2880
  return true;
2859
2881
  }
@@ -2907,6 +2929,7 @@ export class ChatCommand {
2907
2929
  qualityMissing: liveOutcome.qualityMissing,
2908
2930
  qualityBlockers: liveOutcome.qualityBlockers,
2909
2931
  hasOutput: this.api.hasAgentWorkspaceOutput(workspaceContext),
2932
+ answerContent: null,
2910
2933
  selfHealStatus: 'skipped',
2911
2934
  selfHealTool: null,
2912
2935
  plannerError: liveOutcome.plannerError ? sanitizeUserFacingErrorText(liveOutcome.plannerError) : null,
@@ -3329,14 +3352,20 @@ export class ChatCommand {
3329
3352
  : `${outcome.tasksSucceeded} task update${outcome.tasksSucceeded === 1 ? '' : 's'} reported`;
3330
3353
  console.log(' ' + chalk.white(taskLine + '.'));
3331
3354
  }
3355
+ else if (outcome.answerContent) {
3356
+ const preview = outcome.answerContent.length > 160
3357
+ ? `${outcome.answerContent.slice(0, 160)}…`
3358
+ : outcome.answerContent;
3359
+ console.log(' ' + chalk.white(`Answer: ${preview}`));
3360
+ }
3332
3361
  else if (changedFileCount > 0) {
3333
- console.log(' ' + chalk.white(`Workspace changes were produced and are ready for review.`));
3362
+ console.log(' ' + chalk.white('Workspace changes were produced and are ready for review.'));
3334
3363
  }
3335
- else if (outcome.hasOutput) {
3364
+ else if (outcome.hasOutput && executorSucceeded && changedFileCount === 0) {
3336
3365
  console.log(' ' + chalk.white('The assistant returned a final answer for your request.'));
3337
3366
  }
3338
3367
  else {
3339
- console.log(' ' + chalk.white('No final workspace output was confirmed.'));
3368
+ console.log(' ' + chalk.white('No final answer or workspace output was confirmed.'));
3340
3369
  }
3341
3370
  if (outcome.selfHealStatus && outcome.selfHealStatus !== 'skipped') {
3342
3371
  console.log(' ' + chalk.white(`Self-check status: ${outcome.selfHealStatus}.`));
package/dist/index.js CHANGED
@@ -75,8 +75,41 @@ function getPackageMetadataPaths() {
75
75
  path.join(process.env.APPDATA || '', 'npm', 'node_modules', 'vigthoria-cli', 'package.json'),
76
76
  ];
77
77
  }
78
+ function resolveRunningPackageJsonPath() {
79
+ const candidates = [];
80
+ const scriptArg = process.argv[1];
81
+ if (scriptArg) {
82
+ try {
83
+ const resolvedScript = fs.realpathSync(scriptArg);
84
+ candidates.push(path.join(path.dirname(resolvedScript), '..', 'package.json'));
85
+ }
86
+ catch {
87
+ candidates.push(path.join(path.dirname(scriptArg), '..', 'package.json'));
88
+ }
89
+ }
90
+ candidates.push(path.join(__dirname, '..', 'package.json'));
91
+ for (const candidate of candidates) {
92
+ try {
93
+ if (!fs.existsSync(candidate))
94
+ continue;
95
+ const pkg = JSON.parse(fs.readFileSync(candidate, 'utf8'));
96
+ if (pkg?.name === 'vigthoria-cli')
97
+ return candidate;
98
+ }
99
+ catch {
100
+ // try next candidate
101
+ }
102
+ }
103
+ return null;
104
+ }
78
105
  function getVersion() {
79
106
  try {
107
+ const runningPackageJson = resolveRunningPackageJsonPath();
108
+ if (runningPackageJson) {
109
+ const pkg = JSON.parse(fs.readFileSync(runningPackageJson, 'utf8'));
110
+ if (pkg.version)
111
+ return pkg.version;
112
+ }
80
113
  for (const p of getPackageMetadataPaths()) {
81
114
  if (fs.existsSync(p)) {
82
115
  const pkg = JSON.parse(fs.readFileSync(p, 'utf8'));
@@ -149,6 +182,30 @@ function compareVersions(v1, v2) {
149
182
  }
150
183
  return 0;
151
184
  }
185
+ function maxVersion(...versions) {
186
+ let best = null;
187
+ for (const version of versions) {
188
+ if (!version)
189
+ continue;
190
+ if (!best || compareVersions(version, best) > 0)
191
+ best = version;
192
+ }
193
+ return best;
194
+ }
195
+ async function fetchNpmLatestVersion() {
196
+ const { execSync } = await import('child_process');
197
+ try {
198
+ const latest = execSync('npm view vigthoria-cli version', {
199
+ encoding: 'utf8',
200
+ stdio: ['pipe', 'pipe', 'pipe'],
201
+ windowsHide: true,
202
+ }).trim();
203
+ return latest || null;
204
+ }
205
+ catch {
206
+ return null;
207
+ }
208
+ }
152
209
  const VERSION = getVersion();
153
210
  const VIGTHORIA_DEFAULT_MANIFEST_URL = process.env.VIGTHORIA_UPDATE_MANIFEST_URL || "https://coder.vigthoria.io/releases/manifest.json";
154
211
  function resolveManifestEntry(manifest, channel) {
@@ -1452,7 +1509,6 @@ Examples:
1452
1509
  .option('--channel <name>', 'Release channel to use from manifest (default: stable)', 'stable')
1453
1510
  .option('--allow-downgrade', 'Allow installing an older version from custom update source')
1454
1511
  .action(async (options) => {
1455
- const { execSync } = await import('child_process');
1456
1512
  const updateTarget = typeof options.from === 'string' ? options.from.trim() : '';
1457
1513
  const manifestUrl = typeof options.manifest === 'string' ? options.manifest.trim() : VIGTHORIA_DEFAULT_MANIFEST_URL.trim();
1458
1514
  const channel = typeof options.channel === 'string' ? options.channel.trim() : 'stable';
@@ -1484,11 +1540,13 @@ Examples:
1484
1540
  return;
1485
1541
  }
1486
1542
  }
1543
+ if (isOfflineMode()) {
1544
+ console.log(chalk.yellow('Offline mode (VIGTHORIA_OFFLINE=1): skipping update check.'));
1545
+ return;
1546
+ }
1547
+ const currentVersion = VERSION;
1548
+ let manifestEntry = null;
1487
1549
  if (manifestUrl) {
1488
- if (isOfflineMode()) {
1489
- console.log(chalk.yellow('Offline mode (VIGTHORIA_OFFLINE=1): skipping manifest update.'));
1490
- return;
1491
- }
1492
1550
  try {
1493
1551
  console.log(chalk.cyan(`Checking manifest channel ${channel}...`));
1494
1552
  const response = await axios.get(manifestUrl, {
@@ -1496,103 +1554,96 @@ Examples:
1496
1554
  maxRedirects: 5,
1497
1555
  validateStatus: (status) => status >= 200 && status < 300,
1498
1556
  });
1499
- const entry = resolveManifestEntry(response.data || {}, channel);
1500
- if (!entry || !entry.version || !entry.url) {
1501
- console.error(chalk.red(`Manifest missing valid release entry for channel: ${channel}`));
1502
- console.log(chalk.gray('Expected: channels.<channel>.version and channels.<channel>.url'));
1503
- process.exitCode = 1;
1504
- return;
1505
- }
1506
- const currentVersion = VERSION;
1507
- const comparison = compareVersions(entry.version, currentVersion);
1508
- if (!allowDowngrade && comparison <= 0) {
1509
- console.log(chalk.green(`You are running the latest version for channel ${channel} (${currentVersion})`));
1510
- return;
1511
- }
1512
- console.log(chalk.yellow(`Update available from manifest: ${currentVersion} -> ${entry.version} (${channel})`));
1513
- if (entry.notes) {
1514
- console.log(chalk.gray(` ${entry.notes}`));
1515
- }
1516
- if (options.check) {
1517
- console.log(chalk.gray(`Install with: vigthoria update --manifest ${manifestUrl} --channel ${channel}`));
1518
- return;
1519
- }
1520
- const tmpFile = path.join(os.tmpdir(), `vigthoria-update-${Date.now()}.tgz`);
1521
- try {
1522
- console.log(chalk.cyan('Downloading release package...'));
1523
- await downloadFile(entry.url, tmpFile);
1524
- if (entry.sha256) {
1525
- const expected = entry.sha256.toLowerCase();
1526
- const actual = sha256File(tmpFile).toLowerCase();
1527
- if (actual !== expected) {
1528
- console.error(chalk.red('Release checksum verification failed'));
1529
- console.error(chalk.red(`Expected: ${expected}`));
1530
- console.error(chalk.red(`Actual: ${actual}`));
1531
- process.exitCode = 1;
1532
- return;
1533
- }
1534
- console.log(chalk.green('Checksum verification passed'));
1535
- }
1536
- else {
1537
- console.log(chalk.yellow.bold('WARNING: manifest entry has no sha256.'));
1538
- console.log(chalk.yellow(' The release artifact will be installed without integrity verification.'));
1539
- console.log(chalk.gray(' Set --manifest to a trusted source, or supply sha256 in the manifest entry.'));
1540
- }
1541
- console.log(chalk.cyan('Installing update...'));
1542
- await installGlobalPackageWithNpm(tmpFile);
1543
- console.log(chalk.green(`Updated to version ${entry.version}`));
1544
- console.log(chalk.gray('Please restart the CLI to use the new version'));
1545
- return;
1546
- }
1547
- finally {
1548
- if (fs.existsSync(tmpFile)) {
1549
- try {
1550
- fs.unlinkSync(tmpFile);
1551
- }
1552
- catch { }
1553
- }
1557
+ manifestEntry = resolveManifestEntry(response.data || {}, channel);
1558
+ if (!manifestEntry?.version || !manifestEntry?.url) {
1559
+ console.log(chalk.yellow(`Manifest has no installable entry for channel ${channel}; will check npm.`));
1560
+ manifestEntry = null;
1554
1561
  }
1555
1562
  }
1556
1563
  catch (error) {
1557
- console.error(chalk.red('Failed to process manifest update:'), error.message);
1558
- console.log(chalk.gray('Falling back to npm/git update channels...'));
1564
+ console.log(chalk.yellow(`Manifest check failed: ${error.message}`));
1565
+ console.log(chalk.gray('Continuing with npm registry check...'));
1559
1566
  }
1560
1567
  }
1561
- if (isOfflineMode()) {
1562
- console.log(chalk.yellow('Offline mode (VIGTHORIA_OFFLINE=1): skipping update check.'));
1568
+ console.log(chalk.cyan('Checking npm registry...'));
1569
+ const npmVersion = await fetchNpmLatestVersion();
1570
+ if (!npmVersion) {
1571
+ console.log(chalk.yellow('Could not read latest version from npm registry.'));
1572
+ }
1573
+ const effectiveLatest = maxVersion(manifestEntry?.version, npmVersion);
1574
+ if (!effectiveLatest) {
1575
+ console.error(chalk.red('Unable to determine the latest CLI version from manifest or npm.'));
1576
+ process.exitCode = 1;
1563
1577
  return;
1564
1578
  }
1565
- console.log(chalk.cyan('Checking for updates...'));
1566
- try {
1567
- // Get latest version from npm - cross-platform
1568
- const latestVersion = execSync('npm view vigthoria-cli version', {
1569
- encoding: 'utf8',
1570
- stdio: ['pipe', 'pipe', 'pipe'],
1571
- windowsHide: true,
1572
- }).trim();
1573
- const currentVersion = VERSION;
1574
- // Use semantic version comparison (1.6.0 > 1.5.9)
1575
- const comparison = compareVersions(latestVersion, currentVersion);
1576
- if (comparison <= 0) {
1577
- console.log(chalk.green(`You are running the latest version (${currentVersion})`));
1579
+ if (manifestEntry?.version
1580
+ && npmVersion
1581
+ && compareVersions(npmVersion, manifestEntry.version) > 0) {
1582
+ console.log(chalk.yellow(`Release manifest (${manifestEntry.version}) is behind npm (${npmVersion}); npm will be used when needed.`));
1583
+ }
1584
+ if (!allowDowngrade && compareVersions(effectiveLatest, currentVersion) <= 0) {
1585
+ console.log(chalk.green(`You are running the latest version (${currentVersion})`));
1586
+ return;
1587
+ }
1588
+ console.log(chalk.yellow(`Update available: ${currentVersion} -> ${effectiveLatest}`));
1589
+ if (manifestEntry?.notes && compareVersions(manifestEntry.version, effectiveLatest) >= 0) {
1590
+ console.log(chalk.gray(` ${manifestEntry.notes}`));
1591
+ }
1592
+ if (options.check) {
1593
+ console.log(chalk.gray('Run `vigthoria update` to install the update'));
1594
+ return;
1595
+ }
1596
+ const manifestIsAuthoritative = Boolean(manifestEntry
1597
+ && manifestEntry.url
1598
+ && compareVersions(manifestEntry.version, effectiveLatest) >= 0
1599
+ && compareVersions(manifestEntry.version, currentVersion) > 0);
1600
+ if (manifestIsAuthoritative && manifestEntry) {
1601
+ const tmpFile = path.join(os.tmpdir(), `vigthoria-update-${Date.now()}.tgz`);
1602
+ try {
1603
+ console.log(chalk.cyan(`Downloading release package (${manifestEntry.version})...`));
1604
+ await downloadFile(manifestEntry.url, tmpFile);
1605
+ if (manifestEntry.sha256) {
1606
+ const expected = manifestEntry.sha256.toLowerCase();
1607
+ const actual = sha256File(tmpFile).toLowerCase();
1608
+ if (actual !== expected) {
1609
+ console.error(chalk.red('Release checksum verification failed'));
1610
+ console.error(chalk.red(`Expected: ${expected}`));
1611
+ console.error(chalk.red(`Actual: ${actual}`));
1612
+ process.exitCode = 1;
1613
+ return;
1614
+ }
1615
+ console.log(chalk.green('Checksum verification passed'));
1616
+ }
1617
+ else {
1618
+ console.log(chalk.yellow.bold('WARNING: manifest entry has no sha256.'));
1619
+ console.log(chalk.yellow(' The release artifact will be installed without integrity verification.'));
1620
+ }
1621
+ console.log(chalk.cyan('Installing update...'));
1622
+ await installGlobalPackageWithNpm(tmpFile);
1623
+ console.log(chalk.green(`Updated to version ${manifestEntry.version}`));
1624
+ console.log(chalk.gray('Please restart the CLI to use the new version'));
1578
1625
  return;
1579
1626
  }
1580
- console.log(chalk.yellow(`Update available: ${currentVersion} -> ${latestVersion}`));
1581
- if (options.check) {
1582
- console.log(chalk.gray('Run `vigthoria update` to install the update'));
1583
- return;
1627
+ finally {
1628
+ if (fs.existsSync(tmpFile)) {
1629
+ try {
1630
+ fs.unlinkSync(tmpFile);
1631
+ }
1632
+ catch { }
1633
+ }
1584
1634
  }
1585
- console.log(chalk.cyan('Installing update from npm registry...'));
1586
- await installGlobalPackageWithNpm('vigthoria-cli@latest');
1587
- console.log(chalk.green(`Updated to version ${latestVersion}`));
1635
+ }
1636
+ const npmSpec = npmVersion && compareVersions(npmVersion, currentVersion) > 0
1637
+ ? `vigthoria-cli@${npmVersion}`
1638
+ : 'vigthoria-cli@latest';
1639
+ try {
1640
+ console.log(chalk.cyan(`Installing update from npm registry (${npmSpec})...`));
1641
+ await installGlobalPackageWithNpm(npmSpec);
1642
+ console.log(chalk.green(`Updated to version ${effectiveLatest}`));
1588
1643
  console.log(chalk.gray('Please restart the CLI to use the new version'));
1589
1644
  }
1590
1645
  catch (error) {
1591
- console.error(chalk.red('Failed to check/install via npm registry:'), error.message);
1592
- if (options.check) {
1593
- console.log(chalk.gray(`npm registry check failed; fallback install target is ${gitPackageSpec}`));
1594
- return;
1595
- }
1646
+ console.error(chalk.red('Failed to install via npm registry:'), error.message);
1596
1647
  try {
1597
1648
  console.log(chalk.cyan('Attempting git package fallback...'));
1598
1649
  await installGlobalPackageWithNpm(gitPackageSpec);
@@ -1601,7 +1652,7 @@ Examples:
1601
1652
  }
1602
1653
  catch (fallbackError) {
1603
1654
  console.error(chalk.red('Fallback update also failed:'), fallbackError.message);
1604
- console.log(chalk.gray('Try manually: npm install -g vigthoria-cli@latest'));
1655
+ console.log(chalk.gray(`Try manually: npm install -g ${npmSpec}`));
1605
1656
  console.log(chalk.gray(`Or: npm install -g ${gitPackageSpec}`));
1606
1657
  process.exitCode = 1;
1607
1658
  }
@@ -14,6 +14,10 @@ export interface LiveOutcome {
14
14
  failedTaskIds: Set<string>;
15
15
  completedTaskIds: Set<string>;
16
16
  analysisToolsUsed: number;
17
+ /** read_file / grep — not list_directory-only browsing */
18
+ analysisReadToolsUsed: number;
19
+ /** Final user-facing answer text (stream + response body) */
20
+ answerContent?: string;
17
21
  streamAborted?: boolean;
18
22
  }
19
23
  export interface TaskSummaryEvent {
@@ -27,6 +31,10 @@ export interface RunEvaluation {
27
31
  uiTheme: 'success' | 'warning' | 'error';
28
32
  }
29
33
  export declare function createLiveOutcome(): LiveOutcome;
34
+ /** True when text looks like a real answer, not an executor/system placeholder. */
35
+ export declare function isSubstantiveAgentAnswer(text: string): boolean;
36
+ /** Pick the first substantive answer from response body and/or streamed text. */
37
+ export declare function normalizeAgentAnswerContent(...sources: Array<string | undefined | null>): string;
30
38
  /** Process executor_complete / task summary events from the V3 SSE stream. */
31
39
  export declare function handleTaskEvent(summary: TaskSummaryEvent, liveOutcome: LiveOutcome): void;
32
40
  /** Track read/discovery tool usage for analysis-turn accountability. */
@@ -15,9 +15,38 @@ export function createLiveOutcome() {
15
15
  failedTaskIds: new Set(),
16
16
  completedTaskIds: new Set(),
17
17
  analysisToolsUsed: 0,
18
+ analysisReadToolsUsed: 0,
19
+ answerContent: '',
18
20
  streamAborted: false,
19
21
  };
20
22
  }
23
+ const EXECUTOR_PLACEHOLDER_RE = /read-only analysis turn|run at least one discovery tool|reviewing attached files and workspace findings/i;
24
+ const LIST_ONLY_DISCOVERY_TOOLS = new Set([
25
+ 'list_directory',
26
+ 'list_dir',
27
+ 'glob',
28
+ 'dir',
29
+ ]);
30
+ /** True when text looks like a real answer, not an executor/system placeholder. */
31
+ export function isSubstantiveAgentAnswer(text) {
32
+ const trimmed = String(text || '').trim();
33
+ if (trimmed.length < 30)
34
+ return false;
35
+ if (EXECUTOR_PLACEHOLDER_RE.test(trimmed))
36
+ return false;
37
+ if (/^v3 agent workflow completed\.?$/i.test(trimmed))
38
+ return false;
39
+ return true;
40
+ }
41
+ /** Pick the first substantive answer from response body and/or streamed text. */
42
+ export function normalizeAgentAnswerContent(...sources) {
43
+ for (const source of sources) {
44
+ const text = String(source || '').trim();
45
+ if (isSubstantiveAgentAnswer(text))
46
+ return text;
47
+ }
48
+ return '';
49
+ }
21
50
  /** Process executor_complete / task summary events from the V3 SSE stream. */
22
51
  export function handleTaskEvent(summary, liveOutcome) {
23
52
  const tid = summary.task_id || summary.id;
@@ -60,6 +89,9 @@ export function noteAnalysisToolUse(toolName, liveOutcome) {
60
89
  const normalized = String(toolName || '').trim().toLowerCase();
61
90
  if (READ_DISCOVERY_TOOLS.has(normalized)) {
62
91
  liveOutcome.analysisToolsUsed += 1;
92
+ if (!LIST_ONLY_DISCOVERY_TOOLS.has(normalized)) {
93
+ liveOutcome.analysisReadToolsUsed += 1;
94
+ }
63
95
  }
64
96
  }
65
97
  /**
@@ -114,21 +146,37 @@ export function evaluateExecutorSuccess(liveOutcome) {
114
146
  }
115
147
  // Analysis / verification turns with no planned task graph
116
148
  if (!liveOutcome.requiresWorkspaceChanges) {
117
- const analysisOk = liveOutcome.analysisToolsUsed > 0 || liveOutcome.workspaceHasOutput;
118
- if (!analysisOk && hasFatalError) {
149
+ const answerText = normalizeAgentAnswerContent(liveOutcome.answerContent);
150
+ const hasSubstantiveAnswer = isSubstantiveAgentAnswer(answerText);
151
+ const hasDeepRead = liveOutcome.analysisReadToolsUsed > 0;
152
+ if (hasFatalError && !hasSubstantiveAnswer) {
119
153
  return {
120
154
  executorSucceeded: false,
121
- statusHeadline: '✕ Analysis failed — no workspace evidence gathered',
155
+ statusHeadline: '✕ Analysis failed — no answer produced',
122
156
  uiTheme: 'error',
123
157
  };
124
158
  }
125
- if (!analysisOk) {
159
+ if (!hasSubstantiveAnswer) {
160
+ if (liveOutcome.analysisToolsUsed > 0) {
161
+ return {
162
+ executorSucceeded: false,
163
+ statusHeadline: '⚠ Analysis browsed the workspace but did not answer your question — try /retry',
164
+ uiTheme: 'warning',
165
+ };
166
+ }
126
167
  return {
127
168
  executorSucceeded: false,
128
169
  statusHeadline: '⚠ Warning: No read tools confirmed — answer may be incomplete',
129
170
  uiTheme: 'warning',
130
171
  };
131
172
  }
173
+ if (!hasDeepRead) {
174
+ return {
175
+ executorSucceeded: false,
176
+ statusHeadline: '⚠ Analysis answer was not verified against source files — try /retry',
177
+ uiTheme: 'warning',
178
+ };
179
+ }
132
180
  return {
133
181
  executorSucceeded: true,
134
182
  statusHeadline: '✓ Analysis completed',
package/dist/utils/api.js CHANGED
@@ -11,6 +11,7 @@ import path from 'path';
11
11
  import WebSocket from 'ws';
12
12
  import { buildSemanticContext } from './context-ranker.js';
13
13
  import { getChangedFiles } from './workspace-cache.js';
14
+ import { isSubstantiveAgentAnswer } from './agentRunOutcome.js';
14
15
  export const VIGTHORIA_HUB_CREDITS_URL = 'https://hub.vigthoria.io/credits';
15
16
  export const VIGTHORIA_SERVER_TEMPORARILY_UNAVAILABLE_MESSAGE = 'Vigthoria Server is temporarily not available. Please try again later. If the issue persists, please contact support.';
16
17
  export class CLIError extends Error {
@@ -3308,13 +3309,19 @@ document.addEventListener('DOMContentLoaded', () => {
3308
3309
  formatV3AgentResponse(data) {
3309
3310
  const result = data?.result || {};
3310
3311
  if (typeof result === 'string') {
3311
- return this.sanitizeV3AgentResponseText(result);
3312
+ const text = this.sanitizeV3AgentResponseText(result);
3313
+ if (isSubstantiveAgentAnswer(text))
3314
+ return text;
3312
3315
  }
3313
3316
  if (typeof result?.summary === 'string' && result.summary.trim()) {
3314
- return this.sanitizeV3AgentResponseText(result.summary);
3317
+ const text = this.sanitizeV3AgentResponseText(result.summary);
3318
+ if (isSubstantiveAgentAnswer(text))
3319
+ return text;
3315
3320
  }
3316
3321
  if (typeof result?.message === 'string' && result.message.trim()) {
3317
- return this.sanitizeV3AgentResponseText(result.message);
3322
+ const text = this.sanitizeV3AgentResponseText(result.message);
3323
+ if (isSubstantiveAgentAnswer(text))
3324
+ return text;
3318
3325
  }
3319
3326
  if (Array.isArray(data?.events)) {
3320
3327
  const completionEvent = [...data.events].reverse().find((event) => event && event.type === 'complete' && typeof event.summary === 'string' && event.summary.trim());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigthoria-cli",
3
- "version": "1.11.4",
3
+ "version": "1.11.5",
4
4
  "description": "Vigthoria Coder CLI - AI-powered terminal coding assistant",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",