vigthoria-cli 1.11.10 → 1.11.12

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.
@@ -48,6 +48,7 @@ export declare class ChatCommand {
48
48
  private retryPromptStreak;
49
49
  private v3SuppressThinkingStream;
50
50
  private v3StreamedAnswerBuffer;
51
+ private v3StreamedAnswerDisplayed;
51
52
  private v3SeenToolCalls;
52
53
  private v3SeenToolResults;
53
54
  private lastAgentRunOutcome;
@@ -123,6 +124,9 @@ export declare class ChatCommand {
123
124
  private isRawV3StreamPayload;
124
125
  private consumeV3StreamPayload;
125
126
  private writeV3StreamText;
127
+ private displayV3StreamedAnswer;
128
+ private renderAgentAnswerForTerminal;
129
+ private getTerminalContentWidth;
126
130
  private sanitizeV3VisibleStreamText;
127
131
  private writeAgentActivityLine;
128
132
  private updateV3AgentSpinner;
@@ -14,6 +14,8 @@ 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
16
  import { createLiveOutcome, evaluateExecutorSuccess, handleTaskEvent, isSubstantiveAgentAnswer, isToolEvidenceStubAnswer, normalizeAgentAnswerContent, noteAnalysisToolUse, } from '../utils/agentRunOutcome.js';
17
+ import { looksLikeMarkdownReport, renderMarkdownToTerminal, summarizeMarkdownReport, } from '../utils/terminalMarkdown.js';
18
+ import { emitDeckEvent, isDeckModeEnabled } from '../utils/deckEvents.js';
17
19
  const DEFAULT_V3_AGENT_TIMEOUT_MS = (() => {
18
20
  const rawValue = process.env.VIGTHORIA_AGENT_TIMEOUT_MS || process.env.V3_AGENT_TIMEOUT_MS;
19
21
  if (!rawValue) {
@@ -67,6 +69,7 @@ export class ChatCommand {
67
69
  retryPromptStreak = 0;
68
70
  v3SuppressThinkingStream = false;
69
71
  v3StreamedAnswerBuffer = '';
72
+ v3StreamedAnswerDisplayed = false;
70
73
  v3SeenToolCalls = new Set();
71
74
  v3SeenToolResults = new Set();
72
75
  // Last completed Agent run — used by /retry, /continue, and the final summary block.
@@ -788,8 +791,37 @@ export class ChatCommand {
788
791
  else {
789
792
  spinner.stop();
790
793
  }
791
- process.stdout.write(safeText);
792
794
  this.v3StreamedAnswerBuffer += safeText;
795
+ spinner.text = looksLikeMarkdownReport(this.v3StreamedAnswerBuffer)
796
+ ? 'Writing analysis report...'
797
+ : 'Writing response...';
798
+ }
799
+ displayV3StreamedAnswer() {
800
+ if (this.jsonOutput || this.v3StreamedAnswerDisplayed) {
801
+ return;
802
+ }
803
+ const content = this.v3StreamedAnswerBuffer.trim();
804
+ if (!isSubstantiveAgentAnswer(content)) {
805
+ return;
806
+ }
807
+ emitDeckEvent({ type: 'report_ready', markdown: content });
808
+ if (isDeckModeEnabled()) {
809
+ this.v3StreamedAnswerDisplayed = true;
810
+ return;
811
+ }
812
+ console.log('');
813
+ console.log(renderMarkdownToTerminal(content, this.getTerminalContentWidth()));
814
+ this.v3StreamedAnswerDisplayed = true;
815
+ }
816
+ renderAgentAnswerForTerminal(content) {
817
+ const normalized = String(content || '').trim();
818
+ if (!normalized)
819
+ return '';
820
+ return renderMarkdownToTerminal(normalized, this.getTerminalContentWidth());
821
+ }
822
+ getTerminalContentWidth() {
823
+ const cols = process.stdout.columns || 80;
824
+ return Math.max(60, Math.min(cols - 2, 100));
793
825
  }
794
826
  sanitizeV3VisibleStreamText(text) {
795
827
  let output = String(text || '');
@@ -849,6 +881,13 @@ export class ChatCommand {
849
881
  }
850
882
  this.v3SeenToolCalls.add(eventKey);
851
883
  this.v3ToolCallCount += 1;
884
+ const toolName = event.name || event.tool || event.tool_name || '';
885
+ emitDeckEvent({
886
+ type: 'tool_start',
887
+ tool: toolName,
888
+ index: this.v3ToolCallCount,
889
+ target: event.arguments?.path || event.arguments?.file_path || event.arguments?.pattern || '',
890
+ });
852
891
  const toolDesc = this.describeV3AgentTool(event.tool || event.name || event.tool_name);
853
892
  const toolTarget = event.arguments?.path || event.arguments?.file_path || event.arguments?.pattern || '';
854
893
  const sanitizedTarget = this.sanitizeServerPath(String(toolTarget));
@@ -859,16 +898,16 @@ export class ChatCommand {
859
898
  this.writeAgentActivityLine(this.fitTerminalText(stepLabel) + '\n');
860
899
  // Show extra detail for key tools
861
900
  const args = event.arguments || {};
862
- const toolName = event.name || event.tool || '';
863
- if ((toolName === 'write_file' || toolName === 'edit_file') && typeof args.content === 'string') {
901
+ const toolNameForDetail = event.name || event.tool || '';
902
+ if ((toolNameForDetail === 'write_file' || toolNameForDetail === 'edit_file') && typeof args.content === 'string') {
864
903
  const len = args.content.length;
865
904
  this.writeAgentActivityLine(chalk.gray(` ${len > 1000 ? Math.round(len / 1024) + ' KB' : len + ' bytes'} content\n`));
866
905
  }
867
- else if (toolName === 'bash' && typeof args.command === 'string') {
906
+ else if (toolNameForDetail === 'bash' && typeof args.command === 'string') {
868
907
  const command = this.sanitizeServerPath(args.command);
869
908
  this.writeAgentActivityLine(chalk.gray(` ${this.fitTerminalText(`$ ${command}`, 6)}\n`));
870
909
  }
871
- else if (toolName === 'read_file') {
910
+ else if (toolNameForDetail === 'read_file') {
872
911
  this.writeAgentActivityLine(chalk.gray(` ${this.fitTerminalText(`reading ${sanitizedTarget || 'file'}`, 6)}\n`));
873
912
  }
874
913
  spinner.text = `Running ${toolDesc}...`;
@@ -883,6 +922,12 @@ export class ChatCommand {
883
922
  this.v3SeenToolResults.add(eventKey);
884
923
  const success = event.success !== false;
885
924
  const toolName = event.name || event.tool || '';
925
+ emitDeckEvent({
926
+ type: 'tool_end',
927
+ tool: toolName,
928
+ success,
929
+ index: this.v3ToolCallCount,
930
+ });
886
931
  const indicator = success ? chalk.green(' ✓') : chalk.red(' ✗');
887
932
  if (spinner.isSpinning)
888
933
  spinner.stop();
@@ -923,12 +968,15 @@ export class ChatCommand {
923
968
  if (event.type === 'thinking') {
924
969
  this.v3IterationCount += 1;
925
970
  const iterText = this.sanitizeServerPath(event.content || '');
926
- if (spinner.isSpinning)
927
- spinner.stop();
928
- const display = iterText && !/read-only analysis turn|run at least one discovery tool/i.test(iterText)
929
- ? iterText
930
- : `Step ${this.v3IterationCount}: inspecting workspace…`;
931
- this.writeAgentActivityLine(chalk.cyan(`\n── ${this.fitTerminalText(display, 6)} ──\n`));
971
+ const isGenericStep = !iterText
972
+ || /read-only analysis turn|run at least one discovery tool/i.test(iterText)
973
+ || /^Step \d+:/i.test(iterText)
974
+ || /reviewing attached files|inspecting workspace/i.test(iterText);
975
+ if (!isGenericStep) {
976
+ if (spinner.isSpinning)
977
+ spinner.stop();
978
+ this.writeAgentActivityLine(chalk.cyan(`\n── ${this.fitTerminalText(iterText, 6)} ──\n`));
979
+ }
932
980
  spinner.text = 'Analyzing...';
933
981
  return;
934
982
  }
@@ -951,6 +999,14 @@ export class ChatCommand {
951
999
  const tools = event.tool_calls || this.v3ToolCallCount;
952
1000
  if (spinner.isSpinning)
953
1001
  spinner.stop();
1002
+ this.displayV3StreamedAnswer();
1003
+ emitDeckEvent({
1004
+ type: 'run_complete',
1005
+ success: true,
1006
+ tools: event.tool_calls || this.v3ToolCallCount,
1007
+ iterations: event.iterations || this.v3IterationCount,
1008
+ elapsed: event.elapsed || '',
1009
+ });
954
1010
  let statLine = `${iters} iterations, ${tools} tool calls`;
955
1011
  if (elapsed)
956
1012
  statLine += `, ${elapsed}`;
@@ -2545,8 +2601,10 @@ export class ChatCommand {
2545
2601
  this.v3LastActivity = Date.now();
2546
2602
  this.v3StreamingStarted = false;
2547
2603
  this.v3StreamedAnswerBuffer = '';
2604
+ this.v3StreamedAnswerDisplayed = false;
2548
2605
  this.v3SeenToolCalls.clear();
2549
2606
  this.v3SeenToolResults.clear();
2607
+ emitDeckEvent({ type: 'agent_start' });
2550
2608
  const taskDisplay = new TaskDisplay(['Analyse workspace', 'Execute tasks', 'Validate output', 'Self-heal'], false);
2551
2609
  taskDisplay.start(0);
2552
2610
  const spinner = this.jsonOutput ? null : createSpinner({
@@ -2731,9 +2789,8 @@ export class ChatCommand {
2731
2789
  if (spinner) {
2732
2790
  spinner.stop();
2733
2791
  }
2734
- // End streaming line if we streamed text inline
2735
- if (this.v3StreamingStarted) {
2736
- process.stdout.write('\n');
2792
+ if (this.v3StreamingStarted && !this.v3StreamedAnswerDisplayed) {
2793
+ this.displayV3StreamedAnswer();
2737
2794
  }
2738
2795
  const previewGate = (response.metadata?.previewGate || null);
2739
2796
  const workspaceHasOutput = this.api.hasAgentWorkspaceOutput(workspaceContext);
@@ -2828,15 +2885,13 @@ export class ChatCommand {
2828
2885
  executorSucceeded = false;
2829
2886
  }
2830
2887
  if (!this.jsonOutput && !this.directPromptMode) {
2831
- if (this.v3StreamingStarted && isSubstantiveAgentAnswer(this.v3StreamedAnswerBuffer)) {
2832
- console.log('');
2833
- console.log(chalk.bold('Answer:'));
2834
- console.log(this.v3StreamedAnswerBuffer.trim());
2888
+ if (this.v3StreamedAnswerDisplayed) {
2889
+ // Highlighted report already printed once above.
2835
2890
  }
2836
2891
  else if (isSubstantiveAgentAnswer(finalAnswer)) {
2837
2892
  console.log('');
2838
2893
  console.log(chalk.bold('Answer:'));
2839
- console.log(finalAnswer);
2894
+ console.log(this.renderAgentAnswerForTerminal(finalAnswer));
2840
2895
  }
2841
2896
  else if (!requiresWorkspaceChanges) {
2842
2897
  console.log('');
@@ -2845,7 +2900,7 @@ export class ChatCommand {
2845
2900
  }
2846
2901
  else if (finalAnswer) {
2847
2902
  console.log('');
2848
- console.log(finalAnswer);
2903
+ console.log(this.renderAgentAnswerForTerminal(finalAnswer));
2849
2904
  }
2850
2905
  }
2851
2906
  if (!executorSucceeded && requiresWorkspaceChanges && changedFileCount === 0 && !liveOutcome.executorError) {
@@ -3369,6 +3424,15 @@ export class ChatCommand {
3369
3424
  */
3370
3425
  printAgentRunSummary(outcome, evaluation, changedFileCount) {
3371
3426
  const executorSucceeded = evaluation.executorSucceeded;
3427
+ if (outcome.answerContent && isSubstantiveAgentAnswer(outcome.answerContent)) {
3428
+ const reportSummary = summarizeMarkdownReport(outcome.answerContent);
3429
+ emitDeckEvent({
3430
+ type: 'run_summary',
3431
+ success: executorSucceeded,
3432
+ title: reportSummary?.title || 'Agent run complete',
3433
+ sections: reportSummary?.sections || [],
3434
+ });
3435
+ }
3372
3436
  const bar = chalk.gray('─'.repeat(63));
3373
3437
  const ok = chalk.green('✓');
3374
3438
  const warn = chalk.yellow('⚠');
@@ -3456,10 +3520,20 @@ export class ChatCommand {
3456
3520
  console.log(' ' + chalk.white(taskLine + '.'));
3457
3521
  }
3458
3522
  else if (outcome.answerContent && isSubstantiveAgentAnswer(outcome.answerContent)) {
3459
- const preview = outcome.answerContent.length > 160
3460
- ? `${outcome.answerContent.slice(0, 160)}…`
3461
- : outcome.answerContent;
3462
- console.log(' ' + chalk.white(`Answer: ${preview}`));
3523
+ const reportSummary = summarizeMarkdownReport(outcome.answerContent);
3524
+ if (reportSummary) {
3525
+ const sectionPreview = reportSummary.sections.length > 0
3526
+ ? reportSummary.sections.slice(0, 4).join(', ')
3527
+ : `${Math.round(reportSummary.charCount / 1000)}k chars`;
3528
+ console.log(' ' + chalk.white(reportSummary.title));
3529
+ console.log(' ' + chalk.gray(`Sections: ${sectionPreview}`));
3530
+ }
3531
+ else {
3532
+ const preview = outcome.answerContent.length > 160
3533
+ ? `${outcome.answerContent.slice(0, 160)}…`
3534
+ : outcome.answerContent;
3535
+ console.log(' ' + chalk.white(preview));
3536
+ }
3463
3537
  }
3464
3538
  else if (changedFileCount > 0) {
3465
3539
  console.log(' ' + chalk.white('Workspace changes were produced and are ready for review.'));
package/dist/utils/api.js CHANGED
@@ -1524,7 +1524,7 @@ export class APIClient {
1524
1524
  if (json.length <= LIMIT)
1525
1525
  return json;
1526
1526
  if (process.env.DEBUG || process.env.VIGTHORIA_DEBUG) {
1527
- console.log(`[context] Payload ${json.length} chars exceeds ${LIMIT} limit, compacting...`);
1527
+ process.stderr.write(`[context] Payload ${json.length} chars exceeds ${LIMIT} limit, compacting...\n`);
1528
1528
  }
1529
1529
  // Phase 1 — shrink workspaceFiles to fit
1530
1530
  const summary = payload.localWorkspaceSummary;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Structured events for Vigthoria Workbench (VIGTHORIA_DECK_MODE=1).
3
+ * Emitted as single lines: @vigthoria-deck:{json}
4
+ * Workbench strips these from terminal display and updates UI panels.
5
+ */
6
+ export type DeckEventType = 'agent_start' | 'tool_start' | 'tool_end' | 'report_ready' | 'run_complete' | 'run_summary';
7
+ export interface DeckEvent {
8
+ type: DeckEventType;
9
+ ts: number;
10
+ [key: string]: unknown;
11
+ }
12
+ export declare function isDeckModeEnabled(): boolean;
13
+ export declare function emitDeckEvent(event: Omit<DeckEvent, 'ts'> & {
14
+ ts?: number;
15
+ }): void;
16
+ export declare function parseDeckLine(line: string): DeckEvent | null;
17
+ export declare function stripDeckLines(text: string): string;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Structured events for Vigthoria Workbench (VIGTHORIA_DECK_MODE=1).
3
+ * Emitted as single lines: @vigthoria-deck:{json}
4
+ * Workbench strips these from terminal display and updates UI panels.
5
+ */
6
+ const DECK_PREFIX = '@vigthoria-deck:';
7
+ export function isDeckModeEnabled() {
8
+ return /^(1|true|yes)$/i.test(String(process.env.VIGTHORIA_DECK_MODE || ''));
9
+ }
10
+ export function emitDeckEvent(event) {
11
+ if (!isDeckModeEnabled())
12
+ return;
13
+ const payload = {
14
+ ...event,
15
+ ts: event.ts ?? Date.now(),
16
+ };
17
+ process.stdout.write(`${DECK_PREFIX}${JSON.stringify(payload)}\n`);
18
+ }
19
+ export function parseDeckLine(line) {
20
+ const trimmed = line.trim();
21
+ if (!trimmed.startsWith(DECK_PREFIX))
22
+ return null;
23
+ try {
24
+ return JSON.parse(trimmed.slice(DECK_PREFIX.length));
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ }
30
+ export function stripDeckLines(text) {
31
+ return text
32
+ .split('\n')
33
+ .filter((line) => !line.trim().startsWith(DECK_PREFIX))
34
+ .join('\n');
35
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Shared terminal markdown rendering for CLI commands.
3
+ */
4
+ export interface MarkdownReportSummary {
5
+ title: string;
6
+ sections: string[];
7
+ charCount: number;
8
+ }
9
+ /**
10
+ * Clean marked-terminal output for release-grade CLI presentation.
11
+ */
12
+ export declare function cleanTerminalMarkdownOutput(raw: string): string;
13
+ export declare function looksLikeMarkdownReport(text: string): boolean;
14
+ export declare function renderMarkdownToTerminal(markdown: string, width?: number): string;
15
+ export declare function summarizeMarkdownReport(markdown: string): MarkdownReportSummary | null;
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Shared terminal markdown rendering for CLI commands.
3
+ */
4
+ import { Marked } from 'marked';
5
+ import { markedTerminal } from 'marked-terminal';
6
+ let cachedRenderer = null;
7
+ function getTerminalWidth() {
8
+ const cols = process.stdout.columns || 80;
9
+ return Math.max(60, Math.min(cols - 2, 100));
10
+ }
11
+ function getRenderer(width) {
12
+ const resolvedWidth = width ?? getTerminalWidth();
13
+ if (cachedRenderer && cachedRenderer.width === resolvedWidth) {
14
+ return cachedRenderer.marked;
15
+ }
16
+ const marked = new Marked();
17
+ marked.use(markedTerminal({
18
+ showSectionPrefix: false,
19
+ tab: 2,
20
+ width: resolvedWidth,
21
+ reflowText: true,
22
+ }));
23
+ cachedRenderer = { width: resolvedWidth, marked };
24
+ return marked;
25
+ }
26
+ /**
27
+ * Clean marked-terminal output for release-grade CLI presentation.
28
+ */
29
+ export function cleanTerminalMarkdownOutput(raw) {
30
+ let out = raw;
31
+ out = out.replace(/^ \* /gm, '- ');
32
+ out = out.replace(/^ \* /gm, ' - ');
33
+ const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, '');
34
+ out = out.split('\n').filter((line) => {
35
+ if (line === '')
36
+ return true;
37
+ return stripAnsi(line).trim().length > 0;
38
+ }).join('\n');
39
+ out = out.replace(/\n{3,}/g, '\n\n');
40
+ return out;
41
+ }
42
+ export function looksLikeMarkdownReport(text) {
43
+ const trimmed = String(text || '').trim();
44
+ if (!trimmed)
45
+ return false;
46
+ return /^#{1,3}\s+\S/m.test(trimmed)
47
+ || /^\|.+\|/m.test(trimmed)
48
+ || /^[-*]\s+\*\*/m.test(trimmed);
49
+ }
50
+ export function renderMarkdownToTerminal(markdown, width) {
51
+ const input = String(markdown || '').trim();
52
+ if (!input)
53
+ return '';
54
+ if (!looksLikeMarkdownReport(input)) {
55
+ return input;
56
+ }
57
+ const rendered = getRenderer(width).parse(input);
58
+ return cleanTerminalMarkdownOutput(rendered);
59
+ }
60
+ export function summarizeMarkdownReport(markdown) {
61
+ const text = String(markdown || '').trim();
62
+ if (!text || !looksLikeMarkdownReport(text)) {
63
+ return null;
64
+ }
65
+ const lines = text.split('\n');
66
+ let title = '';
67
+ const sections = [];
68
+ for (const line of lines) {
69
+ const h1 = line.match(/^#\s+(.+)$/);
70
+ if (h1 && !title) {
71
+ title = h1[1].replace(/[*_`]/g, '').trim();
72
+ continue;
73
+ }
74
+ const h2 = line.match(/^##\s+(.+)$/);
75
+ if (h2) {
76
+ const section = h2[1].replace(/[*_`]/g, '').trim();
77
+ if (section && sections.length < 8) {
78
+ sections.push(section);
79
+ }
80
+ }
81
+ }
82
+ if (!title) {
83
+ const firstLine = lines.find((line) => line.trim().length > 0)?.trim() || 'Analysis report';
84
+ title = firstLine.replace(/^#+\s*/, '').replace(/[*_`]/g, '').slice(0, 120);
85
+ }
86
+ return {
87
+ title,
88
+ sections,
89
+ charCount: text.length,
90
+ };
91
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigthoria-cli",
3
- "version": "1.11.10",
3
+ "version": "1.11.12",
4
4
  "description": "Vigthoria Coder CLI - AI-powered terminal coding assistant",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",