vigthoria-cli 1.9.10 → 1.9.20

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.
Files changed (53) hide show
  1. package/README.md +4 -4
  2. package/dist/commands/auth.js +48 -65
  3. package/dist/commands/bridge.js +12 -19
  4. package/dist/commands/cancel.js +15 -22
  5. package/dist/commands/chat.d.ts +35 -0
  6. package/dist/commands/chat.js +747 -256
  7. package/dist/commands/config.js +31 -71
  8. package/dist/commands/deploy.js +83 -123
  9. package/dist/commands/device.d.ts +35 -0
  10. package/dist/commands/device.js +239 -0
  11. package/dist/commands/edit.js +32 -39
  12. package/dist/commands/explain.js +18 -25
  13. package/dist/commands/fork.js +22 -27
  14. package/dist/commands/generate.js +37 -44
  15. package/dist/commands/history.js +20 -25
  16. package/dist/commands/hub.js +95 -102
  17. package/dist/commands/index.js +41 -46
  18. package/dist/commands/legion.d.ts +1 -0
  19. package/dist/commands/legion.js +162 -209
  20. package/dist/commands/preview.js +60 -98
  21. package/dist/commands/replay.js +27 -32
  22. package/dist/commands/repo.js +103 -141
  23. package/dist/commands/review.js +29 -36
  24. package/dist/commands/security.js +5 -12
  25. package/dist/commands/update.js +15 -49
  26. package/dist/commands/workflow.d.ts +8 -1
  27. package/dist/commands/workflow.js +53 -19
  28. package/dist/index.js +409 -234
  29. package/dist/utils/api.d.ts +5 -0
  30. package/dist/utils/api.js +373 -166
  31. package/dist/utils/bridge-client.js +11 -52
  32. package/dist/utils/cli-state.d.ts +54 -0
  33. package/dist/utils/cli-state.js +185 -0
  34. package/dist/utils/config.d.ts +5 -0
  35. package/dist/utils/config.js +35 -14
  36. package/dist/utils/context-ranker.js +15 -21
  37. package/dist/utils/files.js +5 -42
  38. package/dist/utils/logger.js +42 -50
  39. package/dist/utils/post-write-validator.js +22 -29
  40. package/dist/utils/project-memory.d.ts +56 -0
  41. package/dist/utils/project-memory.js +289 -0
  42. package/dist/utils/session.d.ts +29 -3
  43. package/dist/utils/session.js +137 -85
  44. package/dist/utils/task-display.js +13 -20
  45. package/dist/utils/tools.d.ts +19 -0
  46. package/dist/utils/tools.js +84 -87
  47. package/dist/utils/workspace-cache.js +18 -26
  48. package/dist/utils/workspace-stream.js +26 -64
  49. package/install.ps1 +15 -1
  50. package/install.sh +1 -1
  51. package/package.json +5 -3
  52. package/scripts/release/LOCAL_MACHINE_USER_VERIFICATION.md +1 -1
  53. package/scripts/release/validate-no-go-gates.sh +2 -2
@@ -1,54 +1,16 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
37
- };
38
- Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.ChatCommand = void 0;
40
- const chalk_1 = __importDefault(require("chalk"));
41
- const fs = __importStar(require("fs"));
42
- const os = __importStar(require("os"));
43
- const path = __importStar(require("path"));
44
- const readline = __importStar(require("readline"));
45
- const logger_js_1 = require("../utils/logger.js");
46
- const api_js_1 = require("../utils/api.js");
47
- const tools_js_1 = require("../utils/tools.js");
48
- const session_js_1 = require("../utils/session.js");
49
- const bridge_client_js_1 = require("../utils/bridge-client.js");
50
- const workspace_stream_js_1 = require("../utils/workspace-stream.js");
51
- const task_display_js_1 = require("../utils/task-display.js");
1
+ import chalk from 'chalk';
2
+ import * as fs from 'fs';
3
+ import * as os from 'os';
4
+ import * as path from 'path';
5
+ import * as readline from 'readline';
6
+ import { createSpinner } from '../utils/logger.js';
7
+ import { APIClient, CLIError, classifyError, formatCLIError, sanitizeUserFacingErrorText, sanitizeUserFacingPathText, propagateError } from '../utils/api.js';
8
+ import { AgenticTools, robustifyStreamResponse } from '../utils/tools.js';
9
+ import { SessionManager } from '../utils/session.js';
10
+ import { BridgeClient, getBridgeClient } from '../utils/bridge-client.js';
11
+ import { WorkspaceWatcher } from '../utils/workspace-stream.js';
12
+ import { TaskDisplay } from '../utils/task-display.js';
13
+ import { ProjectMemoryService } from '../utils/project-memory.js';
52
14
  const DEFAULT_V3_AGENT_TIMEOUT_MS = (() => {
53
15
  const rawValue = process.env.VIGTHORIA_AGENT_TIMEOUT_MS || process.env.V3_AGENT_TIMEOUT_MS;
54
16
  if (!rawValue) {
@@ -73,13 +35,14 @@ const DEFAULT_V3_AGENT_SOFT_TIMEOUT_MS = (() => {
73
35
  const parsed = Number.parseInt(rawValue, 10);
74
36
  return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
75
37
  })();
76
- class ChatCommand {
38
+ export class ChatCommand {
77
39
  config;
78
40
  logger;
79
41
  api;
80
42
  messages = [];
81
43
  tools = null;
82
44
  sessionManager;
45
+ projectMemory = null;
83
46
  currentSession = null;
84
47
  agentMode = false;
85
48
  currentProjectPath = process.cwd();
@@ -96,6 +59,8 @@ class ChatCommand {
96
59
  savePlanToVigFlow = false;
97
60
  jsonOutput = false;
98
61
  modelGovernanceFallback = null;
62
+ // Last completed Agent run — used by /retry, /continue, and the final summary block.
63
+ lastAgentRunOutcome = null;
99
64
  isJwtExpirationError(error) {
100
65
  const candidate = error;
101
66
  const message = `${candidate?.message || ''} ${typeof candidate?.details === 'string' ? candidate.details : ''}`.toLowerCase();
@@ -143,27 +108,27 @@ class ChatCommand {
143
108
  message.includes('aborted');
144
109
  }
145
110
  toUserFacingApiError(error, context) {
146
- const classified = (0, api_js_1.classifyError)(error);
111
+ const classified = classifyError(error);
147
112
  const status = classified.statusCode || (this.isJwtExpirationError(error) ? 401 : 500);
148
113
  if (this.isJwtExpirationError(error)) {
149
- return new api_js_1.CLIError('Your Vigthoria session has expired. Run `vigthoria login` to authenticate again.', 'auth', { statusCode: 401 });
114
+ return new CLIError('Your Vigthoria session has expired. Run `vigthoria login` to authenticate again.', 'auth', { statusCode: 401 });
150
115
  }
151
116
  if (this.isTimeoutError(error)) {
152
- return new api_js_1.CLIError(`${context} timed out. Check your connection and try again.`, 'timeout', { statusCode: status });
117
+ return new CLIError(`${context} timed out. Check your connection and try again.`, 'timeout', { statusCode: status });
153
118
  }
154
119
  if (this.isNetworkError(error)) {
155
- return new api_js_1.CLIError(`${context} could not reach the Vigthoria API. Check your network connection and try again.`, 'network', { statusCode: status });
120
+ return new CLIError(`${context} could not reach the Vigthoria API. Check your network connection and try again.`, 'network', { statusCode: status });
156
121
  }
157
- const message = (0, api_js_1.sanitizeUserFacingErrorText)(classified.message || `${context} failed`);
158
- return new api_js_1.CLIError(message, status === 401 ? 'auth' : 'model_backend', { statusCode: status });
122
+ const message = sanitizeUserFacingErrorText(classified.message || `${context} failed`);
123
+ return new CLIError(message, status === 401 ? 'auth' : 'model_backend', { statusCode: status });
159
124
  }
160
125
  handleApiError(error, context) {
161
126
  const userFacingError = this.toUserFacingApiError(error, context);
162
127
  if (!this.jsonOutput) {
163
- console.error(chalk_1.default.red(`${context} failed: ${userFacingError.message}`));
128
+ console.error(chalk.red(`${context} failed: ${userFacingError.message}`));
164
129
  }
165
130
  const original = error && typeof error === 'object' ? error : { message: String(error) };
166
- (0, api_js_1.propagateError)({
131
+ propagateError({
167
132
  ...original,
168
133
  message: userFacingError.message,
169
134
  statusCode: userFacingError.statusCode,
@@ -186,7 +151,7 @@ class ChatCommand {
186
151
  catch (error) {
187
152
  if (!this.jsonOutput) {
188
153
  const transient = this.isTimeoutError(error) ? 'timeout' : this.isNetworkError(error) ? 'network error' : 'API error';
189
- console.error(chalk_1.default.red(`${context} failed with ${transient}: ${this.toUserFacingApiError(error, context).message}`));
154
+ console.error(chalk.red(`${context} failed with ${transient}: ${this.toUserFacingApiError(error, context).message}`));
190
155
  }
191
156
  lastError = error;
192
157
  if (this.isJwtExpirationError(error)) {
@@ -329,25 +294,28 @@ class ChatCommand {
329
294
  };
330
295
  }
331
296
  getMessagesForModel() {
332
- const memoryContext = this.sessionManager.buildMemoryContext(this.currentSession);
333
297
  const messages = [...this.messages];
334
- if (!memoryContext) {
335
- return messages;
336
- }
337
- const alreadyInjected = messages.some((message) => message.role === 'system' && message.content.includes('Session memory summary from earlier conversation turns.'));
338
- if (alreadyInjected) {
339
- return messages;
340
- }
341
- const insertionIndex = messages.findIndex((message) => message.role !== 'system');
342
- const memoryMessage = {
343
- role: 'system',
344
- content: memoryContext,
345
- };
346
- if (insertionIndex === -1) {
347
- messages.push(memoryMessage);
348
- return messages;
298
+ const memoryContexts = [
299
+ this.sessionManager.buildMemoryContext(this.currentSession),
300
+ this.projectMemory?.buildContextForPrompt(this.getLastUserPrompt()) || '',
301
+ ].filter((entry) => entry && entry.trim());
302
+ for (const memoryContext of memoryContexts) {
303
+ const marker = memoryContext.includes('Vigthoria project brain memory.')
304
+ ? 'Vigthoria project brain memory.'
305
+ : 'Session memory summary from earlier conversation turns.';
306
+ const alreadyInjected = messages.some((message) => message.role === 'system' && message.content.includes(marker));
307
+ if (alreadyInjected) {
308
+ continue;
309
+ }
310
+ const insertionIndex = messages.findIndex((message) => message.role !== 'system');
311
+ const memoryMessage = { role: 'system', content: memoryContext };
312
+ if (insertionIndex === -1) {
313
+ messages.push(memoryMessage);
314
+ }
315
+ else {
316
+ messages.splice(insertionIndex, 0, memoryMessage);
317
+ }
349
318
  }
350
- messages.splice(insertionIndex, 0, memoryMessage);
351
319
  return messages;
352
320
  }
353
321
  isDiagnosticPrompt(prompt) {
@@ -432,18 +400,67 @@ class ChatCommand {
432
400
  const shaping = this.buildTaskShapingInstructions(prompt);
433
401
  return shaping ? `${prompt}\n\n${shaping}` : prompt;
434
402
  }
403
+ isProjectBrainRuntimeDisabled() {
404
+ return /^(1|true|yes)$/i.test(String(process.env.VIGTHORIA_NO_BRAIN || process.env.VIGTHORIA_BRAIN_DISABLED || ''));
405
+ }
406
+ buildProjectBrainRuntimeContext(prompt, source) {
407
+ if (this.isProjectBrainRuntimeDisabled()) {
408
+ return undefined;
409
+ }
410
+ try {
411
+ if (!this.projectMemory) {
412
+ this.projectMemory = new ProjectMemoryService(this.currentProjectPath);
413
+ }
414
+ const status = this.projectMemory.getStatus();
415
+ const context = this.projectMemory.buildContextForPrompt(prompt);
416
+ if (!context && status.itemCount === 0) {
417
+ return undefined;
418
+ }
419
+ return {
420
+ schema: 'vigthoria.project-brain.v1',
421
+ source,
422
+ memoryDir: status.memoryDir,
423
+ itemCount: status.itemCount,
424
+ updatedAt: status.updatedAt,
425
+ context,
426
+ };
427
+ }
428
+ catch {
429
+ return undefined;
430
+ }
431
+ }
432
+ rememberBrainEvent(type, text, mode) {
433
+ if (this.isProjectBrainRuntimeDisabled()) {
434
+ return;
435
+ }
436
+ try {
437
+ if (!this.projectMemory) {
438
+ this.projectMemory = new ProjectMemoryService(this.currentProjectPath);
439
+ }
440
+ this.projectMemory.remember(type, text, { source: 'vigthoria-cli', mode, model: this.currentModel });
441
+ }
442
+ catch {
443
+ // Project Brain memory must not break chat, GoA, or operator execution.
444
+ }
445
+ }
435
446
  async getPromptRuntimeContext(prompt) {
447
+ const runtimeContext = {};
448
+ const brainContext = this.buildProjectBrainRuntimeContext(prompt, this.operatorMode ? 'vigthoria-cli.operator' : this.agentMode ? 'vigthoria-cli.agent' : 'vigthoria-cli.chat');
449
+ if (brainContext) {
450
+ runtimeContext.vigthoriaBrain = brainContext;
451
+ }
436
452
  if (!this.isBrowserTaskPrompt(prompt)) {
437
- return {};
453
+ return runtimeContext;
438
454
  }
439
455
  const bridgeStatus = await this.callApi('Checking DevTools Bridge status', () => this.api.getDevtoolsBridgeStatus(), 0);
440
456
  if (!this.jsonOutput && bridgeStatus.ok) {
441
- console.log(chalk_1.default.gray(`Browser task detected. DevTools Bridge is reachable at ${bridgeStatus.endpoint}.`));
457
+ console.log(chalk.gray(`Browser task detected. DevTools Bridge is reachable at ${bridgeStatus.endpoint}.`));
442
458
  }
443
459
  else if (!this.jsonOutput) {
444
- console.log(chalk_1.default.yellow(`Browser task detected. DevTools Bridge is not running at ${bridgeStatus.endpoint}.`));
460
+ console.log(chalk.yellow(`Browser task detected. DevTools Bridge is not running at ${bridgeStatus.endpoint}.`));
445
461
  }
446
462
  return {
463
+ ...runtimeContext,
447
464
  browserTask: true,
448
465
  devtoolsBridgeAvailable: bridgeStatus.ok,
449
466
  devtoolsBridgeEndpoint: bridgeStatus.endpoint,
@@ -460,12 +477,7 @@ class ChatCommand {
460
477
  sanitizeServerPath(text) {
461
478
  if (!text)
462
479
  return text;
463
- // Strip common server path prefixes from output
464
- return text
465
- .replace(/\/var\/www\/V3-Code-Agent\//g, '')
466
- .replace(/\/var\/www\/[a-zA-Z0-9_-]+\//g, '')
467
- .replace(/\/tmp\/vig-remote-[a-zA-Z0-9_-]+\//g, '')
468
- .replace(/\/opt\/vigthoria[a-zA-Z0-9_/-]*\//g, '');
480
+ return sanitizeUserFacingPathText(text);
469
481
  }
470
482
  describeV3AgentTool(toolName) {
471
483
  const normalized = String(toolName || '').toLowerCase();
@@ -498,12 +510,12 @@ class ChatCommand {
498
510
  const source = (event && typeof event === 'object' && !Buffer.isBuffer(event) && !(event instanceof Uint8Array))
499
511
  ? (event.body ?? event.stream ?? event.response ?? event)
500
512
  : event;
501
- for await (const chunk of (0, tools_js_1.robustifyStreamResponse)(source)) {
513
+ for await (const chunk of robustifyStreamResponse(source)) {
502
514
  this.v3LastActivity = Date.now();
503
515
  if (chunk.type === 'error') {
504
516
  if (spinner.isSpinning)
505
517
  spinner.stop();
506
- process.stderr.write(chalk_1.default.red(`\nStream error: ${chunk.content}\n`));
518
+ process.stderr.write(chalk.red(`\nStream error: ${chunk.content}\n`));
507
519
  continue;
508
520
  }
509
521
  if (chunk.content) {
@@ -515,11 +527,12 @@ class ChatCommand {
515
527
  const message = error instanceof Error ? error.message : String(error);
516
528
  if (spinner.isSpinning)
517
529
  spinner.stop();
518
- process.stderr.write(chalk_1.default.red(`\nStream error: ${message}\n`));
530
+ process.stderr.write(chalk.red(`\nStream error: ${message}\n`));
519
531
  }
520
532
  }
521
533
  writeV3StreamText(spinner, text) {
522
- if (!text) {
534
+ const safeText = this.sanitizeServerPath(text);
535
+ if (!safeText) {
523
536
  return;
524
537
  }
525
538
  if (!this.v3StreamingStarted) {
@@ -532,7 +545,7 @@ class ChatCommand {
532
545
  else {
533
546
  spinner.stop();
534
547
  }
535
- process.stdout.write(text);
548
+ process.stdout.write(safeText);
536
549
  }
537
550
  updateV3AgentSpinner(spinner, event) {
538
551
  if (this.isRawV3StreamPayload(event)) {
@@ -540,7 +553,7 @@ class ChatCommand {
540
553
  const message = error instanceof Error ? error.message : String(error);
541
554
  if (spinner.isSpinning)
542
555
  spinner.stop();
543
- process.stderr.write(chalk_1.default.red(`\nStream error: ${message}\n`));
556
+ process.stderr.write(chalk.red(`\nStream error: ${message}\n`));
544
557
  });
545
558
  return;
546
559
  }
@@ -554,7 +567,7 @@ class ChatCommand {
554
567
  const toolTarget = event.arguments?.path || event.arguments?.file_path || event.arguments?.pattern || '';
555
568
  const sanitizedTarget = this.sanitizeServerPath(String(toolTarget));
556
569
  const shortTarget = sanitizedTarget ? ` → ${sanitizedTarget.replace(/\\/g, '/').split('/').slice(-2).join('/')}` : '';
557
- const stepLabel = chalk_1.default.cyan(` [${this.v3IterationCount}/${this.v3ToolCallCount}]`) + ` ${toolDesc}${shortTarget}`;
570
+ const stepLabel = chalk.cyan(` [${this.v3IterationCount}/${this.v3ToolCallCount}]`) + ` ${toolDesc}${shortTarget}`;
558
571
  if (spinner.isSpinning)
559
572
  spinner.stop();
560
573
  process.stderr.write(stepLabel + '\n');
@@ -563,10 +576,11 @@ class ChatCommand {
563
576
  const toolName = event.name || event.tool || '';
564
577
  if ((toolName === 'write_file' || toolName === 'edit_file') && typeof args.content === 'string') {
565
578
  const len = args.content.length;
566
- process.stderr.write(chalk_1.default.gray(` ${len > 1000 ? Math.round(len / 1024) + ' KB' : len + ' bytes'} content\n`));
579
+ process.stderr.write(chalk.gray(` ${len > 1000 ? Math.round(len / 1024) + ' KB' : len + ' bytes'} content\n`));
567
580
  }
568
581
  else if (toolName === 'bash' && typeof args.command === 'string') {
569
- process.stderr.write(chalk_1.default.gray(` $ ${args.command.slice(0, 120)}${args.command.length > 120 ? '…' : ''}\n`));
582
+ const command = this.sanitizeServerPath(args.command);
583
+ process.stderr.write(chalk.gray(` $ ${command.slice(0, 120)}${command.length > 120 ? '…' : ''}\n`));
570
584
  }
571
585
  spinner.start();
572
586
  spinner.text = `Running ${toolDesc}...`;
@@ -575,7 +589,7 @@ class ChatCommand {
575
589
  if (event.type === 'tool_result') {
576
590
  const success = event.success !== false;
577
591
  const toolName = event.name || event.tool || '';
578
- const indicator = success ? chalk_1.default.green(' ✓') : chalk_1.default.red(' ✗');
592
+ const indicator = success ? chalk.green(' ✓') : chalk.red(' ✗');
579
593
  if (spinner.isSpinning)
580
594
  spinner.stop();
581
595
  process.stderr.write(`${indicator} ${toolName}\n`);
@@ -585,11 +599,11 @@ class ChatCommand {
585
599
  if (!success && output) {
586
600
  const sanitizedError = this.sanitizeServerPath(typeof event.error === 'string' ? event.error : output);
587
601
  const lines = sanitizedError.split('\n').slice(0, 4);
588
- process.stderr.write(chalk_1.default.red(` ${lines.join('\n ')}\n`));
602
+ process.stderr.write(chalk.red(` ${lines.join('\n ')}\n`));
589
603
  }
590
604
  else if (success && output && output.length > 0) {
591
605
  const brief = output.split('\n')[0].slice(0, 120);
592
- process.stderr.write(chalk_1.default.gray(` ${brief}${output.length > 120 ? '…' : ''}\n`));
606
+ process.stderr.write(chalk.gray(` ${brief}${output.length > 120 ? '…' : ''}\n`));
593
607
  }
594
608
  spinner.start();
595
609
  spinner.text = 'Next step...';
@@ -597,10 +611,10 @@ class ChatCommand {
597
611
  }
598
612
  if (event.type === 'thinking') {
599
613
  this.v3IterationCount += 1;
600
- const iterText = event.content || '';
614
+ const iterText = this.sanitizeServerPath(event.content || '');
601
615
  if (spinner.isSpinning)
602
616
  spinner.stop();
603
- process.stderr.write(chalk_1.default.cyan(`\n── ${iterText || `Iteration ${this.v3IterationCount}`} ──\n`));
617
+ process.stderr.write(chalk.cyan(`\n── ${iterText || `Iteration ${this.v3IterationCount}`} ──\n`));
604
618
  spinner.start();
605
619
  spinner.text = 'Analyzing...';
606
620
  return;
@@ -614,7 +628,7 @@ class ChatCommand {
614
628
  this.writeV3StreamText(spinner, text);
615
629
  }
616
630
  else {
617
- spinner.text = chalk_1.default.cyan('[Response] ') + 'Writing response...';
631
+ spinner.text = chalk.cyan('[Response] ') + 'Writing response...';
618
632
  }
619
633
  return;
620
634
  }
@@ -632,53 +646,53 @@ class ChatCommand {
632
646
  const fail = event.tasks_failed ?? 0;
633
647
  statLine += ` — ${ok} tasks done`;
634
648
  if (fail > 0)
635
- statLine += chalk_1.default.yellow(`, ${fail} failed`);
649
+ statLine += chalk.yellow(`, ${fail} failed`);
636
650
  }
637
- process.stderr.write(chalk_1.default.green(`\n✓ Complete`) + ` — ${statLine}\n`);
651
+ process.stderr.write(chalk.green(`\n✓ Complete`) + ` — ${statLine}\n`);
638
652
  // Show seal quality score if available
639
653
  if (event.seal_score && typeof event.seal_score.overall === 'number') {
640
654
  const score = event.seal_score.overall;
641
655
  const tier = event.seal_score.tier || '';
642
- const scoreColor = score >= 7 ? chalk_1.default.green : score >= 5 ? chalk_1.default.yellow : chalk_1.default.red;
643
- process.stderr.write(chalk_1.default.cyan(' [Quality] ') + scoreColor(`${score}/10`) + (tier ? chalk_1.default.gray(` (${tier})`) : '') + '\n');
656
+ const scoreColor = score >= 7 ? chalk.green : score >= 5 ? chalk.yellow : chalk.red;
657
+ process.stderr.write(chalk.cyan(' [Quality] ') + scoreColor(`${score}/10`) + (tier ? chalk.gray(` (${tier})`) : '') + '\n');
644
658
  }
645
659
  return;
646
660
  }
647
661
  if (event.type === 'plan') {
648
662
  const plan = event.plan || {};
649
- const planKind = plan.task_kind || event.task_kind || '';
650
- const quality = plan.quality_profile || '';
663
+ const planKind = this.sanitizeServerPath(plan.task_kind || event.task_kind || '');
664
+ const quality = this.sanitizeServerPath(plan.quality_profile || '');
651
665
  const status = typeof plan.status === 'string' ? plan.status : '';
652
- const summary = typeof plan.summary === 'string' ? plan.summary : '';
666
+ const summary = typeof plan.summary === 'string' ? this.sanitizeServerPath(plan.summary) : '';
653
667
  if (spinner.isSpinning)
654
668
  spinner.stop();
655
- process.stderr.write(chalk_1.default.cyan(` [Plan] `) + `Task: ${planKind || 'analyzing'}`);
669
+ process.stderr.write(chalk.cyan(` [Plan] `) + `Task: ${planKind || 'analyzing'}`);
656
670
  if (quality)
657
- process.stderr.write(chalk_1.default.gray(` (${quality})`));
671
+ process.stderr.write(chalk.gray(` (${quality})`));
658
672
  process.stderr.write('\n');
659
673
  if (summary) {
660
- process.stderr.write(chalk_1.default.gray(` ${summary}\n`));
674
+ process.stderr.write(chalk.gray(` ${summary}\n`));
661
675
  }
662
676
  if (status === 'planning' && Number.isFinite(Number(plan.elapsed_seconds))) {
663
- process.stderr.write(chalk_1.default.gray(` elapsed: ${plan.elapsed_seconds}s\n`));
677
+ process.stderr.write(chalk.gray(` elapsed: ${plan.elapsed_seconds}s\n`));
664
678
  }
665
679
  if (Array.isArray(plan.tasks) && plan.tasks.length > 0) {
666
- process.stderr.write(chalk_1.default.gray(` ${plan.total_tasks || plan.tasks.length} tasks:\n`));
680
+ process.stderr.write(chalk.gray(` ${plan.total_tasks || plan.tasks.length} tasks:\n`));
667
681
  for (const t of plan.tasks.slice(0, 10)) {
668
- const targets = Array.isArray(t.targets) && t.targets.length ? ` → ${t.targets.join(', ')}` : '';
669
- process.stderr.write(chalk_1.default.gray(` • ${t.title || t.id}${targets}\n`));
682
+ const targets = Array.isArray(t.targets) && t.targets.length ? ` → ${t.targets.map((target) => this.sanitizeServerPath(String(target))).join(', ')}` : '';
683
+ process.stderr.write(chalk.gray(` • ${this.sanitizeServerPath(String(t.title || t.id))}${targets}\n`));
670
684
  }
671
685
  if (plan.tasks.length > 10) {
672
- process.stderr.write(chalk_1.default.gray(` ... and ${plan.tasks.length - 10} more\n`));
686
+ process.stderr.write(chalk.gray(` ... and ${plan.tasks.length - 10} more\n`));
673
687
  }
674
688
  }
675
689
  if (Array.isArray(plan.notes) && plan.notes.length > 0) {
676
690
  for (const note of plan.notes.slice(0, 3)) {
677
- process.stderr.write(chalk_1.default.gray(` note: ${note}\n`));
691
+ process.stderr.write(chalk.gray(` note: ${this.sanitizeServerPath(String(note))}\n`));
678
692
  }
679
693
  }
680
694
  if (Array.isArray(plan.target_files) && plan.target_files.length > 0) {
681
- process.stderr.write(chalk_1.default.gray(` Files: ${plan.target_files.join(', ')}\n`));
695
+ process.stderr.write(chalk.gray(` Files: ${plan.target_files.map((filePath) => this.sanitizeServerPath(String(filePath))).join(', ')}\n`));
682
696
  }
683
697
  spinner.start();
684
698
  spinner.text = status === 'planning' ? 'Planning...' : 'Executing plan...';
@@ -687,7 +701,7 @@ class ChatCommand {
687
701
  if (event.type === 'executor_start') {
688
702
  if (spinner.isSpinning)
689
703
  spinner.stop();
690
- process.stderr.write(chalk_1.default.cyan(' [Executor] ') + `Starting ${event.task_id || 'task'}${event.title ? ` - ${event.title}` : ''}
704
+ process.stderr.write(chalk.cyan(' [Executor] ') + `Starting ${this.sanitizeServerPath(String(event.task_id || 'task'))}${event.title ? ` - ${this.sanitizeServerPath(String(event.title))}` : ''}
691
705
  `);
692
706
  spinner.start();
693
707
  spinner.text = 'Vigthoria Executor running...';
@@ -696,8 +710,8 @@ class ChatCommand {
696
710
  if (event.type === 'executor_error') {
697
711
  if (spinner.isSpinning)
698
712
  spinner.stop();
699
- const msg = (0, api_js_1.sanitizeUserFacingErrorText)(String(event.error || 'Executor error')) || 'Executor error';
700
- process.stderr.write(chalk_1.default.red(' [Executor] ') + `Vigthoria Executor encountered an issue: ${msg}
713
+ const msg = sanitizeUserFacingErrorText(String(event.error || 'Executor error')) || 'Executor error';
714
+ process.stderr.write(chalk.red(' [Executor] ') + `Vigthoria Executor encountered an issue: ${msg}
701
715
  `);
702
716
  spinner.start();
703
717
  spinner.text = 'Recovering executor...';
@@ -710,11 +724,11 @@ class ChatCommand {
710
724
  const status = String(summary.status || 'completed');
711
725
  const changed = Array.isArray(summary.changed_files) ? summary.changed_files.length : 0;
712
726
  if (status === 'failed') {
713
- process.stderr.write(chalk_1.default.red(' [Executor] ') + `Vigthoria Executor task failed${summary.task_id ? ` (${summary.task_id})` : ''}.
727
+ process.stderr.write(chalk.red(' [Executor] ') + `Vigthoria Executor task failed${summary.task_id ? ` (${summary.task_id})` : ''}.
714
728
  `);
715
729
  }
716
730
  else {
717
- process.stderr.write(chalk_1.default.green(' [Executor] ') + `Task completed${summary.task_id ? ` (${summary.task_id})` : ''}${changed ? `, ${changed} files changed` : ''}.
731
+ process.stderr.write(chalk.green(' [Executor] ') + `Task completed${summary.task_id ? ` (${summary.task_id})` : ''}${changed ? `, ${changed} files changed` : ''}.
718
732
  `);
719
733
  }
720
734
  spinner.start();
@@ -724,11 +738,11 @@ class ChatCommand {
724
738
  if (event.type === 'file_mutation') {
725
739
  const rawPath = typeof event.path === 'string' ? this.sanitizeServerPath(event.path) : '';
726
740
  const filePath = rawPath ? rawPath.replace(/\\/g, '/').split('/').slice(-2).join('/') : '';
727
- const action = event.action === 'delete' ? chalk_1.default.red('deleted') : chalk_1.default.green('wrote');
741
+ const action = event.action === 'delete' ? chalk.red('deleted') : chalk.green('wrote');
728
742
  if (filePath) {
729
743
  if (spinner.isSpinning)
730
744
  spinner.stop();
731
- process.stderr.write(chalk_1.default.cyan(' [File] ') + `${action} ${filePath}\n`);
745
+ process.stderr.write(chalk.cyan(' [File] ') + `${action} ${filePath}\n`);
732
746
  spinner.start();
733
747
  }
734
748
  return;
@@ -737,23 +751,23 @@ class ChatCommand {
737
751
  if (event.checkpointed) {
738
752
  if (spinner.isSpinning)
739
753
  spinner.stop();
740
- process.stderr.write(chalk_1.default.yellow(' [Checkpoint] ') + 'Budget reached — auto-continuing...\n');
754
+ process.stderr.write(chalk.yellow(' [Checkpoint] ') + 'Budget reached — auto-continuing...\n');
741
755
  spinner.start();
742
756
  }
743
757
  else {
744
758
  if (spinner.isSpinning)
745
759
  spinner.stop();
746
- const message = (0, api_js_1.sanitizeUserFacingErrorText)(String(event.message || 'Agent error')) || 'Agent error';
760
+ const message = sanitizeUserFacingErrorText(String(event.message || 'Agent error')) || 'Agent error';
747
761
  const plannerLike = /plan|planner|dependency graph/i.test(message);
748
762
  const executorLike = /executor|task failed|iteration/i.test(message);
749
763
  if (plannerLike) {
750
- process.stderr.write(chalk_1.default.red(' [Planner] ') + `Vigthoria Planner encountered an issue: ${message}\n`);
764
+ process.stderr.write(chalk.red(' [Planner] ') + `Vigthoria Planner encountered an issue: ${this.sanitizeServerPath(message)}\n`);
751
765
  }
752
766
  else if (executorLike) {
753
- process.stderr.write(chalk_1.default.red(' [Executor] ') + `Vigthoria Executor encountered an issue: ${message}\n`);
767
+ process.stderr.write(chalk.red(' [Executor] ') + `Vigthoria Executor encountered an issue: ${this.sanitizeServerPath(message)}\n`);
754
768
  }
755
769
  else {
756
- process.stderr.write(chalk_1.default.red(' [Error] ') + message + '\n');
770
+ process.stderr.write(chalk.red(' [Error] ') + this.sanitizeServerPath(message) + '\n');
757
771
  }
758
772
  }
759
773
  return;
@@ -761,7 +775,7 @@ class ChatCommand {
761
775
  if (event.type === 'context') {
762
776
  if (spinner.isSpinning)
763
777
  spinner.stop();
764
- process.stderr.write(chalk_1.default.cyan(' [Context] ') + 'Workspace bound\n');
778
+ process.stderr.write(chalk.cyan(' [Context] ') + 'Workspace bound\n');
765
779
  spinner.start();
766
780
  spinner.text = 'Starting agent...';
767
781
  return;
@@ -771,7 +785,7 @@ class ChatCommand {
771
785
  this.v3ToolCallCount = 0;
772
786
  if (spinner.isSpinning)
773
787
  spinner.stop();
774
- process.stderr.write(chalk_1.default.cyan(' [Start] ') + 'Agent initialized\n');
788
+ process.stderr.write(chalk.cyan(' [Start] ') + 'Agent initialized\n');
775
789
  spinner.start();
776
790
  spinner.text = 'Working...';
777
791
  }
@@ -783,7 +797,7 @@ class ChatCommand {
783
797
  if (event.type === 'started') {
784
798
  if (spinner.isSpinning)
785
799
  spinner.stop();
786
- process.stderr.write(chalk_1.default.cyan(' [Operator] ') + 'Starting BMAD workflow...\n');
800
+ process.stderr.write(chalk.cyan(' [Operator] ') + 'Starting BMAD workflow...\n');
787
801
  spinner.start();
788
802
  spinner.text = 'Connecting...';
789
803
  return;
@@ -791,7 +805,7 @@ class ChatCommand {
791
805
  if (event.type === 'connected') {
792
806
  if (spinner.isSpinning)
793
807
  spinner.stop();
794
- process.stderr.write(chalk_1.default.green(' ✓') + ' Connected to BMAD stream\n');
808
+ process.stderr.write(chalk.green(' ✓') + ' Connected to BMAD stream\n');
795
809
  spinner.start();
796
810
  spinner.text = 'Working...';
797
811
  return;
@@ -800,7 +814,7 @@ class ChatCommand {
800
814
  const agentName = event.agent || 'BMAD agent';
801
815
  if (spinner.isSpinning)
802
816
  spinner.stop();
803
- process.stderr.write(chalk_1.default.cyan(` [Agent] `) + agentName + '\n');
817
+ process.stderr.write(chalk.cyan(` [Agent] `) + agentName + '\n');
804
818
  spinner.start();
805
819
  spinner.text = `Running ${agentName}...`;
806
820
  return;
@@ -810,7 +824,7 @@ class ChatCommand {
810
824
  const statusText = `${event.status || 'Working'}${progress}`;
811
825
  if (spinner.isSpinning)
812
826
  spinner.stop();
813
- process.stderr.write(chalk_1.default.cyan(' [Status] ') + statusText + '\n');
827
+ process.stderr.write(chalk.cyan(' [Status] ') + statusText + '\n');
814
828
  spinner.start();
815
829
  spinner.text = statusText;
816
830
  return;
@@ -818,15 +832,15 @@ class ChatCommand {
818
832
  if (event.type === 'result') {
819
833
  if (spinner.isSpinning)
820
834
  spinner.stop();
821
- process.stderr.write(chalk_1.default.green('\n ✓ Operator workflow complete\n'));
835
+ process.stderr.write(chalk.green('\n ✓ Operator workflow complete\n'));
822
836
  return;
823
837
  }
824
838
  }
825
839
  constructor(config, logger) {
826
840
  this.config = config;
827
841
  this.logger = logger;
828
- this.api = new api_js_1.APIClient(config, logger);
829
- this.sessionManager = new session_js_1.SessionManager();
842
+ this.api = new APIClient(config, logger);
843
+ this.sessionManager = new SessionManager();
830
844
  }
831
845
  async run(options) {
832
846
  if (!this.config.isAuthenticated()) {
@@ -857,11 +871,11 @@ class ChatCommand {
857
871
  this.ensureProjectWorkspace();
858
872
  this.directPromptMode = Boolean(options.prompt);
859
873
  this.directToolContinuationCount = 0;
860
- this.tools = new tools_js_1.AgenticTools(this.logger, this.currentProjectPath, async (action) => this.requestPermission(action), this.autoApprove);
874
+ this.tools = new AgenticTools(this.logger, this.currentProjectPath, async (action) => this.requestPermission(action), this.autoApprove);
861
875
  this.initializeSession(options.resume === true);
862
876
  // ── Commando Bridge: connect if --bridge was specified ──────────
863
877
  if (options.bridge) {
864
- const bridgeClient = new bridge_client_js_1.BridgeClient({
878
+ const bridgeClient = new BridgeClient({
865
879
  bridgeUrl: options.bridge,
866
880
  apiKey: this.config.get('authToken'),
867
881
  onAdminCommand: (cmd) => this.handleAdminCommand(cmd),
@@ -896,7 +910,7 @@ class ChatCommand {
896
910
  const timeoutId = options.bridge && bridgePromptTimeoutMs > 0
897
911
  ? setTimeout(() => {
898
912
  timedOut = true;
899
- const b = (0, bridge_client_js_1.getBridgeClient)();
913
+ const b = getBridgeClient();
900
914
  if (b) {
901
915
  b.emitEnd({ reason: 'timeout' });
902
916
  b.destroy();
@@ -911,7 +925,7 @@ class ChatCommand {
911
925
  if (timeoutId)
912
926
  clearTimeout(timeoutId);
913
927
  if (!timedOut) {
914
- const bridge = (0, bridge_client_js_1.getBridgeClient)();
928
+ const bridge = getBridgeClient();
915
929
  if (bridge) {
916
930
  bridge.emitEnd({ reason: 'prompt-complete' });
917
931
  bridge.destroy();
@@ -920,7 +934,7 @@ class ChatCommand {
920
934
  return;
921
935
  }
922
936
  await this.startInteractiveChat();
923
- const bridge = (0, bridge_client_js_1.getBridgeClient)();
937
+ const bridge = getBridgeClient();
924
938
  if (bridge) {
925
939
  bridge.emitEnd({ reason: 'interactive-exit' });
926
940
  bridge.destroy();
@@ -930,24 +944,24 @@ class ChatCommand {
930
944
  handleAdminCommand(cmd) {
931
945
  switch (cmd.action) {
932
946
  case 'ping':
933
- (0, bridge_client_js_1.getBridgeClient)()?.emitModelResponse({ model: this.currentModel, chars: 0, hasToolCalls: false, preview: 'pong' });
947
+ getBridgeClient()?.emitModelResponse({ model: this.currentModel, chars: 0, hasToolCalls: false, preview: 'pong' });
934
948
  break;
935
949
  case 'set-model':
936
950
  if (cmd.params?.value && typeof cmd.params.value === 'string') {
937
951
  this.currentModel = cmd.params.value;
938
- (0, bridge_client_js_1.getBridgeClient)()?.emitModeChange({ mode: this.operatorMode ? 'operator' : this.agentMode ? 'agent' : 'chat', model: this.currentModel });
952
+ getBridgeClient()?.emitModeChange({ mode: this.operatorMode ? 'operator' : this.agentMode ? 'agent' : 'chat', model: this.currentModel });
939
953
  if (!this.jsonOutput)
940
- console.log(chalk_1.default.yellow(`[bridge] Model changed to: ${this.currentModel}`));
954
+ console.log(chalk.yellow(`[bridge] Model changed to: ${this.currentModel}`));
941
955
  }
942
956
  break;
943
957
  case 'abort':
944
958
  if (!this.jsonOutput)
945
- console.log(chalk_1.default.red(`[bridge] Abort requested by admin`));
946
- (0, bridge_client_js_1.getBridgeClient)()?.emitEnd({ reason: 'admin-abort' });
959
+ console.log(chalk.red(`[bridge] Abort requested by admin`));
960
+ getBridgeClient()?.emitEnd({ reason: 'admin-abort' });
947
961
  process.exit(0);
948
962
  break;
949
963
  default:
950
- (0, bridge_client_js_1.getBridgeClient)()?.emitError({ message: `Unknown admin command: ${cmd.action}` });
964
+ getBridgeClient()?.emitError({ message: `Unknown admin command: ${cmd.action}` });
951
965
  }
952
966
  }
953
967
  ensureProjectWorkspace() {
@@ -1061,6 +1075,7 @@ class ChatCommand {
1061
1075
  return path.join(rootPath, `${folderName}-${Date.now()}`);
1062
1076
  }
1063
1077
  initializeSession(resume) {
1078
+ this.projectMemory = new ProjectMemoryService(this.currentProjectPath);
1064
1079
  if (resume) {
1065
1080
  this.currentSession = this.sessionManager.getLatest(this.currentProjectPath);
1066
1081
  if (this.currentSession) {
@@ -1106,11 +1121,11 @@ class ChatCommand {
1106
1121
  // Suppress all setup banners in direct-prompt mode so only the final
1107
1122
  // answer reaches stdout. Interactive (REPL) mode still shows them.
1108
1123
  if (!this.jsonOutput) {
1109
- console.log(chalk_1.default.cyan('Running single prompt in direct mode.'));
1110
- console.log(chalk_1.default.gray(`Model: ${this.currentModel}`));
1111
- console.log(chalk_1.default.gray(`Project: ${this.currentProjectPath}`));
1124
+ console.log(chalk.cyan('Running single prompt in direct mode.'));
1125
+ console.log(chalk.gray(`Model: ${this.currentModel}`));
1126
+ console.log(chalk.gray(`Project: ${this.currentProjectPath}`));
1112
1127
  if (this.workflowTarget) {
1113
- console.log(chalk_1.default.gray(`Workflow target: ${this.workflowTarget}`));
1128
+ console.log(chalk.gray(`Workflow target: ${this.workflowTarget}`));
1114
1129
  }
1115
1130
  console.log();
1116
1131
  }
@@ -1174,7 +1189,7 @@ class ChatCommand {
1174
1189
  const runtimeContext = await this.getPromptRuntimeContext(prompt);
1175
1190
  const resolvedWorkflow = await this.callApi('Resolve VigFlow workflow', () => this.api.resolveVigFlowWorkflow(selector));
1176
1191
  const invocationMode = this.operatorMode ? 'operator' : this.agentMode ? 'agent' : 'chat';
1177
- const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({ text: `Running workflow ${resolvedWorkflow.name}...`, spinner: 'clock' }).start();
1192
+ const spinner = this.jsonOutput ? null : createSpinner({ text: `Running workflow ${resolvedWorkflow.name}...`, spinner: 'clock' }).start();
1178
1193
  try {
1179
1194
  const execution = await this.callApi('Run VigFlow workflow', () => this.api.runVigFlowWorkflow(resolvedWorkflow.id, {
1180
1195
  data: {
@@ -1223,9 +1238,9 @@ class ChatCommand {
1223
1238
  return;
1224
1239
  }
1225
1240
  this.logger.success(`Workflow target ${resolvedWorkflow.name} ${execution.status}`);
1226
- console.log(chalk_1.default.gray(`Workflow ID: ${resolvedWorkflow.id}`));
1227
- console.log(chalk_1.default.gray(`Execution ID: ${execution.executionId}`));
1228
- console.log(chalk_1.default.gray(`Mode: ${invocationMode}`));
1241
+ console.log(chalk.gray(`Workflow ID: ${resolvedWorkflow.id}`));
1242
+ console.log(chalk.gray(`Execution ID: ${execution.executionId}`));
1243
+ console.log(chalk.gray(`Mode: ${invocationMode}`));
1229
1244
  if (content) {
1230
1245
  console.log(content);
1231
1246
  }
@@ -1268,12 +1283,13 @@ class ChatCommand {
1268
1283
  await this.runLocalAgentLoop(prompt);
1269
1284
  return;
1270
1285
  }
1271
- (0, bridge_client_js_1.getBridgeClient)()?.emitPrompt({ prompt, mode: 'operator', model: this.currentModel });
1286
+ getBridgeClient()?.emitPrompt({ prompt, mode: 'operator', model: this.currentModel });
1272
1287
  const runtimeContext = await this.getPromptRuntimeContext(prompt);
1273
- const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({ text: 'Thinking like an operator...', spinner: 'clock' }).start();
1288
+ const spinner = this.jsonOutput ? null : createSpinner({ text: 'Thinking like an operator...', spinner: 'clock' }).start();
1274
1289
  const workflowType = 'full';
1275
1290
  const executionPrompt = this.buildExecutionPrompt(prompt);
1276
1291
  try {
1292
+ this.rememberBrainEvent('task', `GoA operator workflow started for prompt: ${prompt.slice(0, 220)}`, 'operator');
1277
1293
  const response = await this.callApi('Run operator workflow', () => this.api.runOperatorWorkflow(executionPrompt, {
1278
1294
  workspacePath: this.currentProjectPath,
1279
1295
  projectPath: this.currentProjectPath,
@@ -1297,7 +1313,7 @@ class ChatCommand {
1297
1313
  const isPolicyAck = /^(i will follow|i understand|i('ll| will) adhere|understood[.,!]|sure[.,!]|provide your|waiting for|i('ll| will) proceed)/i.test(responseText)
1298
1314
  || (responseText.length < 200 && /follow the instructions|next instruction|ready to assist/i.test(responseText));
1299
1315
  if (isPolicyAck) {
1300
- throw new api_js_1.CLIError('Operator workflow returned a non-actionable acknowledgement instead of a grounded result.', 'model_backend');
1316
+ throw new CLIError('Operator workflow returned a non-actionable acknowledgement instead of a grounded result.', 'model_backend');
1301
1317
  }
1302
1318
  if (this.jsonOutput) {
1303
1319
  console.log(JSON.stringify({
@@ -1313,9 +1329,10 @@ class ChatCommand {
1313
1329
  else {
1314
1330
  console.log(response.content || 'Operator workflow completed.');
1315
1331
  if (response.savedWorkflow?.id) {
1316
- console.log(chalk_1.default.gray(`Saved VigFlow workflow: ${response.savedWorkflow.id}${response.savedWorkflow.name ? ` (${response.savedWorkflow.name})` : ''}`));
1332
+ console.log(chalk.gray(`Saved VigFlow workflow: ${response.savedWorkflow.id}${response.savedWorkflow.name ? ` (${response.savedWorkflow.name})` : ''}`));
1317
1333
  }
1318
1334
  }
1335
+ this.rememberBrainEvent('validation', `GoA operator workflow completed${response.workflowId ? ` workflow ${response.workflowId}` : ''}${response.savedWorkflow?.id ? ` saved VigFlow ${response.savedWorkflow.id}` : ''}.`, 'operator');
1319
1336
  this.messages.push({ role: 'assistant', content: response.content || 'Operator workflow completed.' });
1320
1337
  this.saveSession();
1321
1338
  }
@@ -1323,11 +1340,12 @@ class ChatCommand {
1323
1340
  if (spinner) {
1324
1341
  spinner.stop();
1325
1342
  }
1326
- const cliErr = error instanceof api_js_1.CLIError ? error : (0, api_js_1.classifyError)(error);
1327
- const errorMsg = (0, api_js_1.formatCLIError)(cliErr);
1343
+ const cliErr = error instanceof CLIError ? error : classifyError(error);
1344
+ const errorMsg = formatCLIError(cliErr);
1328
1345
  if (!this.jsonOutput) {
1329
1346
  this.logger.error('Operator workflow failed');
1330
1347
  }
1348
+ this.rememberBrainEvent('issue', `GoA operator workflow failed: ${errorMsg}`, 'operator');
1331
1349
  if (this.jsonOutput) {
1332
1350
  process.exitCode = 1;
1333
1351
  console.log(JSON.stringify({
@@ -1350,8 +1368,8 @@ class ChatCommand {
1350
1368
  * BMAD orchestrator to scan the workspace.
1351
1369
  */
1352
1370
  async runOperatorDirectAnswer(prompt) {
1353
- (0, bridge_client_js_1.getBridgeClient)()?.emitPrompt({ prompt, mode: 'operator-direct', model: this.currentModel });
1354
- const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({ text: 'Operator (direct)...', spinner: 'clock' }).start();
1371
+ getBridgeClient()?.emitPrompt({ prompt, mode: 'operator-direct', model: this.currentModel });
1372
+ const spinner = this.jsonOutput ? null : createSpinner({ text: 'Operator (direct)...', spinner: 'clock' }).start();
1355
1373
  try {
1356
1374
  let operatorGrounding = [
1357
1375
  'You are Vigthoria Operator, a DevOps and infrastructure analysis assistant.',
@@ -1383,7 +1401,7 @@ class ChatCommand {
1383
1401
  const isPolicyAck = /^(i will follow|i understand|i('ll| will) adhere|understood[.,!]|sure[.,!]|provide your|waiting for|i('ll| will) proceed)/i.test(content)
1384
1402
  || (content.length < 200 && /follow the instructions|next instruction|ready to assist/i.test(content));
1385
1403
  if (isPolicyAck || !content) {
1386
- throw new api_js_1.CLIError('Operator returned a non-actionable acknowledgement instead of a grounded result.', 'model_backend');
1404
+ throw new CLIError('Operator returned a non-actionable acknowledgement instead of a grounded result.', 'model_backend');
1387
1405
  }
1388
1406
  if (this.jsonOutput) {
1389
1407
  console.log(JSON.stringify({
@@ -1396,14 +1414,15 @@ class ChatCommand {
1396
1414
  else {
1397
1415
  console.log(content);
1398
1416
  }
1417
+ this.rememberBrainEvent('validation', `GoA operator direct answer completed for prompt: ${prompt.slice(0, 220)}`, 'operator');
1399
1418
  this.messages.push({ role: 'assistant', content });
1400
1419
  this.saveSession();
1401
1420
  }
1402
1421
  catch (error) {
1403
1422
  if (spinner)
1404
1423
  spinner.stop();
1405
- const cliErr = error instanceof api_js_1.CLIError ? error : (0, api_js_1.classifyError)(error);
1406
- const errorMsg = (0, api_js_1.formatCLIError)(cliErr);
1424
+ const cliErr = error instanceof CLIError ? error : classifyError(error);
1425
+ const errorMsg = formatCLIError(cliErr);
1407
1426
  if (this.jsonOutput) {
1408
1427
  process.exitCode = 1;
1409
1428
  console.log(JSON.stringify({
@@ -1475,8 +1494,8 @@ class ChatCommand {
1475
1494
  }
1476
1495
  }
1477
1496
  this.messages.push({ role: 'user', content: this.buildExecutionPrompt(prompt) });
1478
- (0, bridge_client_js_1.getBridgeClient)()?.emitPrompt({ prompt, mode: 'chat', model: this.currentModel });
1479
- const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({ text: 'Thinking...', spinner: 'clock' }).start();
1497
+ getBridgeClient()?.emitPrompt({ prompt, mode: 'chat', model: this.currentModel });
1498
+ const spinner = this.jsonOutput ? null : createSpinner({ text: 'Thinking...', spinner: 'clock' }).start();
1480
1499
  try {
1481
1500
  const response = await this.callApi('Send chat message', () => this.api.chat(this.getMessagesForModel(), this.currentModel));
1482
1501
  if (spinner)
@@ -1500,7 +1519,7 @@ class ChatCommand {
1500
1519
  console.log(finalText);
1501
1520
  }
1502
1521
  else {
1503
- console.log(chalk_1.default.yellow('The model returned an empty response. Try rephrasing your question, or use --agent mode for grounded repo analysis.'));
1522
+ console.log(chalk.yellow('The model returned an empty response. Try rephrasing your question, or use --agent mode for grounded repo analysis.'));
1504
1523
  }
1505
1524
  this.messages.push({ role: 'assistant', content: response.message || '' });
1506
1525
  this.saveSession();
@@ -1508,8 +1527,8 @@ class ChatCommand {
1508
1527
  catch (error) {
1509
1528
  if (spinner)
1510
1529
  spinner.stop();
1511
- const cliErr = error instanceof api_js_1.CLIError ? error : (0, api_js_1.classifyError)(error);
1512
- const errorMsg = (0, api_js_1.formatCLIError)(cliErr);
1530
+ const cliErr = error instanceof CLIError ? error : classifyError(error);
1531
+ const errorMsg = formatCLIError(cliErr);
1513
1532
  if (this.jsonOutput) {
1514
1533
  process.exitCode = 1;
1515
1534
  console.log(JSON.stringify({
@@ -1562,13 +1581,13 @@ class ChatCommand {
1562
1581
  this.directToolContinuationCount = 0;
1563
1582
  this.agentToolEvidence = { discovery: 0, mutation: 0, searchFailed: 0 };
1564
1583
  this.tools.clearSessionApprovals();
1565
- (0, bridge_client_js_1.getBridgeClient)()?.emitPrompt({ prompt, mode: this.operatorMode ? 'operator' : 'agent', model: this.currentModel });
1584
+ getBridgeClient()?.emitPrompt({ prompt, mode: this.operatorMode ? 'operator' : 'agent', model: this.currentModel });
1566
1585
  this.ensureAgentSystemPrompt();
1567
1586
  this.messages.push({ role: 'user', content: this.buildScopedUserPrompt(prompt) });
1568
1587
  this.saveSession();
1569
1588
  const maxTurns = 10;
1570
1589
  for (let turn = 0; turn < maxTurns; turn += 1) {
1571
- const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({ text: turn === 0 ? 'Planning...' : 'Continuing...', spinner: 'clock' }).start();
1590
+ const spinner = this.jsonOutput ? null : createSpinner({ text: turn === 0 ? 'Planning...' : 'Continuing...', spinner: 'clock' }).start();
1572
1591
  let response;
1573
1592
  try {
1574
1593
  response = await this.callApi('Send agent chat message', () => this.api.chat(this.getMessagesForModel(), this.currentModel), 0);
@@ -1619,7 +1638,7 @@ class ChatCommand {
1619
1638
  this.messages.push({ role: 'assistant', content: assistantMessage });
1620
1639
  const toolCalls = this.extractToolCalls(assistantMessage);
1621
1640
  const visibleText = this.stripToolPayloads(assistantMessage).trim();
1622
- (0, bridge_client_js_1.getBridgeClient)()?.emitModelResponse({
1641
+ getBridgeClient()?.emitModelResponse({
1623
1642
  model: this.currentModel,
1624
1643
  chars: assistantMessage.length,
1625
1644
  hasToolCalls: toolCalls.length > 0,
@@ -1700,8 +1719,9 @@ class ChatCommand {
1700
1719
  catch (error) {
1701
1720
  if (spinner)
1702
1721
  spinner.stop();
1703
- const cliErr = error instanceof api_js_1.CLIError ? error : (0, api_js_1.classifyError)(error);
1704
- const errorMsg = (0, api_js_1.formatCLIError)(cliErr);
1722
+ const cliErr = error instanceof CLIError ? error : classifyError(error);
1723
+ const errorMsg = formatCLIError(cliErr);
1724
+ this.rememberBrainEvent('issue', `Agent turn failed: ${errorMsg}`, 'agent');
1705
1725
  if (this.jsonOutput) {
1706
1726
  process.exitCode = 1;
1707
1727
  console.log(JSON.stringify({
@@ -1758,12 +1778,12 @@ class ChatCommand {
1758
1778
  args: { path: targetFile },
1759
1779
  };
1760
1780
  if (!this.jsonOutput) {
1761
- console.log(chalk_1.default.cyan(`⚙ Executing: ${readCall.tool}`));
1781
+ console.log(chalk.cyan(`⚙ Executing: ${readCall.tool}`));
1762
1782
  }
1763
1783
  const readResult = await this.tools.execute(readCall);
1764
1784
  const readSummary = this.formatToolResult(readCall, readResult);
1765
1785
  if (!this.jsonOutput) {
1766
- console.log(readResult.success ? chalk_1.default.gray(readSummary) : chalk_1.default.red(readSummary));
1786
+ console.log(readResult.success ? chalk.gray(readSummary) : chalk.red(readSummary));
1767
1787
  }
1768
1788
  this.messages.push({ role: 'system', content: readSummary });
1769
1789
  if (!readResult.success || !readResult.output) {
@@ -1806,12 +1826,12 @@ class ChatCommand {
1806
1826
  },
1807
1827
  };
1808
1828
  if (!this.jsonOutput) {
1809
- console.log(chalk_1.default.cyan(`⚙ Executing: ${writeCall.tool}`));
1829
+ console.log(chalk.cyan(`⚙ Executing: ${writeCall.tool}`));
1810
1830
  }
1811
1831
  const writeResult = await this.tools.execute(writeCall);
1812
1832
  const writeSummary = this.formatToolResult(writeCall, writeResult);
1813
1833
  if (!this.jsonOutput) {
1814
- console.log(writeResult.success ? chalk_1.default.gray(writeSummary) : chalk_1.default.red(writeSummary));
1834
+ console.log(writeResult.success ? chalk.gray(writeSummary) : chalk.red(writeSummary));
1815
1835
  }
1816
1836
  this.messages.push({ role: 'system', content: writeSummary });
1817
1837
  if (!writeResult.success) {
@@ -1847,31 +1867,131 @@ class ChatCommand {
1847
1867
  console.log(`Updated ${targetFile}.`);
1848
1868
  if (previewGate.required) {
1849
1869
  if (previewGate.passed) {
1850
- console.log(chalk_1.default.gray(`Template Service preview gate: passed via ${previewGate.backendUrl || 'unknown backend'}`));
1870
+ console.log(chalk.gray(`Template Service preview gate: passed via ${previewGate.backendUrl || 'unknown backend'}`));
1851
1871
  }
1852
1872
  else {
1853
- console.log(chalk_1.default.yellow(`Template Service preview gate: failed${previewGate.error ? ` - ${previewGate.error}` : ''}`));
1873
+ console.log(chalk.yellow(`Template Service preview gate: failed${previewGate.error ? ` - ${previewGate.error}` : ''}`));
1854
1874
  }
1855
1875
  }
1856
1876
  }
1857
1877
  return true;
1858
1878
  }
1879
+ isConfirmationFollowUp(prompt) {
1880
+ const normalized = prompt.trim().toLowerCase().replace(/[.!?]+$/g, '').replace(/\s+/g, ' ');
1881
+ return /^(ja|ja bitte|ja bitte mach das|mach das|bitte mach das|genau|ok|okay|yes|yes please|please do|do it|go ahead|continue|proceed|make it so)$/.test(normalized);
1882
+ }
1883
+ getPreviousActionablePrompt() {
1884
+ if (this.lastActionableUserInput && !this.isConfirmationFollowUp(this.lastActionableUserInput)) {
1885
+ return this.lastActionableUserInput;
1886
+ }
1887
+ for (let i = this.messages.length - 1; i >= 0; i -= 1) {
1888
+ const message = this.messages[i];
1889
+ if (message.role !== 'user')
1890
+ continue;
1891
+ const content = (message.content || '').trim();
1892
+ if (!content || this.isConfirmationFollowUp(content))
1893
+ continue;
1894
+ return content
1895
+ .replace(/\n\nProject root:[\s\S]*$/i, '')
1896
+ .replace(/\n\nStay within this project root[\s\S]*$/i, '')
1897
+ .trim();
1898
+ }
1899
+ return '';
1900
+ }
1901
+ buildContextualAgentPrompt(prompt) {
1902
+ if (!this.isConfirmationFollowUp(prompt)) {
1903
+ return prompt;
1904
+ }
1905
+ const previousPrompt = this.getPreviousActionablePrompt();
1906
+ if (!previousPrompt) {
1907
+ return prompt;
1908
+ }
1909
+ return [
1910
+ 'The user confirmed the previous agent task. Continue and execute that original task now.',
1911
+ '',
1912
+ 'Original task:',
1913
+ previousPrompt,
1914
+ '',
1915
+ `Latest confirmation: ${prompt}`,
1916
+ '',
1917
+ 'Do not reinterpret this confirmation as a new website, landing page, template, or index.html task.',
1918
+ ].join('\n');
1919
+ }
1859
1920
  async tryV3AgentWorkflow(prompt) {
1860
- const runtimeContext = await this.getPromptRuntimeContext(prompt);
1861
- const routingPolicy = this.resolveAgentExecutionPolicy(prompt);
1921
+ const contextualPrompt = this.buildContextualAgentPrompt(prompt);
1922
+ if (contextualPrompt === prompt && !this.isConfirmationFollowUp(prompt)) {
1923
+ this.lastActionableUserInput = prompt;
1924
+ }
1925
+ this.messages.push({ role: 'user', content: contextualPrompt });
1926
+ const runtimeContext = await this.getPromptRuntimeContext(contextualPrompt);
1927
+ const routingPolicy = this.resolveAgentExecutionPolicy(contextualPrompt);
1862
1928
  // Reset streaming counters for new workflow
1863
1929
  this.v3IterationCount = 0;
1864
1930
  this.v3ToolCallCount = 0;
1865
1931
  this.v3LastActivity = Date.now();
1866
1932
  this.v3StreamingStarted = false;
1867
- const taskDisplay = new task_display_js_1.TaskDisplay(['Analyse workspace', 'Execute tasks', 'Validate output', 'Self-heal'], !this.jsonOutput);
1933
+ const taskDisplay = new TaskDisplay(['Analyse workspace', 'Execute tasks', 'Validate output', 'Self-heal'], !this.jsonOutput);
1868
1934
  taskDisplay.start(0);
1869
- const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({
1935
+ const spinner = this.jsonOutput ? null : createSpinner({
1870
1936
  text: routingPolicy.cloudSelected ? 'Routing heavy task to Vigthoria Cloud...' : 'Routing to V3 Agent...',
1871
1937
  spinner: 'clock',
1872
1938
  }).start();
1873
- const executionPrompt = this.buildExecutionPrompt(prompt);
1874
- const agentTaskType = this.inferAgentTaskType(prompt);
1939
+ // Live run telemetry, used both for the final summary block and for /retry, /continue.
1940
+ const liveOutcome = {
1941
+ tasksSucceeded: 0,
1942
+ tasksTotal: 0,
1943
+ failedTaskIds: new Set(),
1944
+ unfinishedTaskIds: new Set(),
1945
+ qualityScore: null,
1946
+ qualityMissing: [],
1947
+ qualityBlockers: [],
1948
+ plannerError: null,
1949
+ executorError: null,
1950
+ executorFailed: false,
1951
+ };
1952
+ const parsePlannerSummary = (raw) => {
1953
+ if (!raw || typeof raw !== 'string')
1954
+ return;
1955
+ const succ = raw.match(/(\d+)\s*\/\s*(\d+)\s+tasks?\s+succeeded/i);
1956
+ if (succ) {
1957
+ liveOutcome.tasksSucceeded = Number(succ[1]) || 0;
1958
+ liveOutcome.tasksTotal = Number(succ[2]) || 0;
1959
+ }
1960
+ const failed = raw.match(/Failed\s+tasks?:\s*([^.\n]+)/i);
1961
+ if (failed) {
1962
+ failed[1].split(/[,\s]+/).map(s => s.trim()).filter(Boolean)
1963
+ .forEach(t => liveOutcome.failedTaskIds.add(t));
1964
+ }
1965
+ const unfinished = raw.match(/Unfinished\s+tasks?:\s*([^.\n]+)/i);
1966
+ if (unfinished) {
1967
+ unfinished[1].split(/[,\s]+/).map(s => s.trim()).filter(Boolean)
1968
+ .forEach(t => liveOutcome.unfinishedTaskIds.add(t));
1969
+ }
1970
+ const qScore = raw.match(/Quality\s+audit:\s*([0-9.]+)\s*\/\s*100/i);
1971
+ if (qScore) {
1972
+ const n = Number(qScore[1]);
1973
+ if (Number.isFinite(n))
1974
+ liveOutcome.qualityScore = n;
1975
+ }
1976
+ const blockers = raw.match(/Quality\s+blockers?:\s*([^\n]+?)(?:\s+Quality\s+audit|$)/i);
1977
+ if (blockers) {
1978
+ blockers[1].split(/;/).map(s => s.trim()).filter(Boolean)
1979
+ .forEach(b => {
1980
+ if (!liveOutcome.qualityBlockers.includes(b))
1981
+ liveOutcome.qualityBlockers.push(b);
1982
+ });
1983
+ }
1984
+ const missingFeatures = raw.match(/Missing\s+features?:\s*([^\n]+)/i);
1985
+ if (missingFeatures) {
1986
+ missingFeatures[1].split(/[,;]/).map(s => s.trim()).filter(Boolean)
1987
+ .forEach(m => {
1988
+ if (!liveOutcome.qualityMissing.includes(m))
1989
+ liveOutcome.qualityMissing.push(m);
1990
+ });
1991
+ }
1992
+ };
1993
+ const executionPrompt = this.buildExecutionPrompt(contextualPrompt);
1994
+ const agentTaskType = this.inferAgentTaskType(contextualPrompt);
1875
1995
  const workspaceContext = {
1876
1996
  workspacePath: this.currentProjectPath,
1877
1997
  projectPath: this.currentProjectPath,
@@ -1881,7 +2001,7 @@ class ChatCommand {
1881
2001
  // Start workspace watcher for bidirectional real-time sync
1882
2002
  let watcher = null;
1883
2003
  if (this.currentProjectPath && fs.existsSync(this.currentProjectPath)) {
1884
- watcher = new workspace_stream_js_1.WorkspaceWatcher({
2004
+ watcher = new WorkspaceWatcher({
1885
2005
  workspaceRoot: this.currentProjectPath,
1886
2006
  onFileChange: (relativePath, content, action) => {
1887
2007
  this.logger.debug(`Local change detected: ${action} ${relativePath}`);
@@ -1905,16 +2025,52 @@ class ChatCommand {
1905
2025
  agentExecutionPolicy: routingPolicy,
1906
2026
  legacyFallbackAllowed: this.isLegacyAgentFallbackAllowed(),
1907
2027
  rawPrompt: prompt,
2028
+ contextualPrompt,
1908
2029
  history: this.getMessagesForModel(),
1909
2030
  ...runtimeContext,
1910
2031
  onStreamEvent: (event) => {
1911
2032
  if (event.type === 'plan') {
1912
2033
  taskDisplay.complete(0);
1913
2034
  taskDisplay.start(1);
2035
+ const tasks = event?.plan?.tasks;
2036
+ if (Array.isArray(tasks))
2037
+ liveOutcome.tasksTotal = tasks.length;
1914
2038
  }
1915
2039
  else if (event.type === 'executor_start') {
1916
2040
  taskDisplay.start(1);
1917
2041
  }
2042
+ else if (event.type === 'executor_complete') {
2043
+ const summary = event.summary || {};
2044
+ const tid = summary.task_id || summary.id;
2045
+ if (summary.status === 'failed') {
2046
+ if (tid)
2047
+ liveOutcome.failedTaskIds.add(String(tid));
2048
+ liveOutcome.executorFailed = true;
2049
+ }
2050
+ else if (summary.status === 'completed' || summary.status === 'success') {
2051
+ if (tid)
2052
+ liveOutcome.failedTaskIds.delete(String(tid));
2053
+ liveOutcome.tasksSucceeded += 1;
2054
+ }
2055
+ }
2056
+ else if (event.type === 'executor_error') {
2057
+ const msg = typeof event.error === 'string' ? event.error : '';
2058
+ if (msg)
2059
+ liveOutcome.executorError = msg;
2060
+ }
2061
+ else if (event.type === 'error') {
2062
+ const msg = typeof event.message === 'string' ? event.message : '';
2063
+ if (/plan|planner|dependency graph/i.test(msg)) {
2064
+ if (!liveOutcome.plannerError)
2065
+ liveOutcome.plannerError = msg;
2066
+ parsePlannerSummary(msg);
2067
+ }
2068
+ else if (/executor|task failed|iteration/i.test(msg)) {
2069
+ if (!liveOutcome.executorError)
2070
+ liveOutcome.executorError = msg;
2071
+ parsePlannerSummary(msg);
2072
+ }
2073
+ }
1918
2074
  else if (event.type === 'complete') {
1919
2075
  taskDisplay.complete(1);
1920
2076
  }
@@ -1967,7 +2123,7 @@ class ChatCommand {
1967
2123
  return true;
1968
2124
  }
1969
2125
  if (!this.jsonOutput && previewGate?.required && previewGate?.passed !== true && workspaceHasOutput) {
1970
- console.log(chalk_1.default.yellow(`Template Service preview gate did not fully validate this output, but generated workspace files were preserved${previewGate?.error ? `: ${previewGate.error}` : '.'}`));
2126
+ console.log(chalk.yellow(`Template Service preview gate did not fully validate this output, but generated workspace files were preserved${previewGate?.error ? `: ${previewGate.error}` : '.'}`));
1971
2127
  }
1972
2128
  if (this.jsonOutput) {
1973
2129
  console.log(JSON.stringify({
@@ -1985,45 +2141,63 @@ class ChatCommand {
1985
2141
  else if (this.v3StreamingStarted) {
1986
2142
  // Content was already streamed to stdout in real-time; skip duplicate print.
1987
2143
  if (!this.jsonOutput) {
1988
- console.log(chalk_1.default.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
2144
+ console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
1989
2145
  }
1990
2146
  }
1991
2147
  else if (response.content) {
1992
2148
  if (!this.directPromptMode) {
1993
- console.log(chalk_1.default.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
2149
+ console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
1994
2150
  }
1995
2151
  console.log(response.content);
1996
2152
  }
1997
2153
  else {
1998
2154
  if (!this.directPromptMode) {
1999
- console.log(chalk_1.default.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
2155
+ console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
2000
2156
  }
2001
2157
  console.log('V3 agent workflow completed.');
2002
2158
  }
2003
2159
  if (!this.jsonOutput && previewGate?.required) {
2004
2160
  if (previewGate.passed) {
2005
- console.log(chalk_1.default.gray(`Template Service preview gate: passed via ${previewGate.backendUrl || 'unknown backend'}`));
2161
+ console.log(chalk.gray(`Template Service preview gate: passed via ${previewGate.backendUrl || 'unknown backend'}`));
2006
2162
  }
2007
2163
  else {
2008
- console.log(chalk_1.default.yellow(`Template Service preview gate: failed${previewGate.error ? ` - ${previewGate.error}` : ''}`));
2164
+ console.log(chalk.yellow(`Template Service preview gate: failed${previewGate.error ? ` - ${previewGate.error}` : ''}`));
2009
2165
  }
2010
2166
  }
2011
2167
  // Show change summary for files touched by the agent
2012
2168
  if (!this.jsonOutput && !this.directPromptMode && response.changedFiles) {
2013
2169
  const fileCount = Object.keys(response.changedFiles).length;
2014
2170
  if (fileCount > 0) {
2015
- console.log(chalk_1.default.gray(`\nFiles changed: ${fileCount}`));
2171
+ console.log(chalk.gray(`\nFiles changed: ${fileCount}`));
2016
2172
  for (const relPath of Object.keys(response.changedFiles).slice(0, 15)) {
2017
- console.log(chalk_1.default.gray(` ${chalk_1.default.green('+')} ${relPath}`));
2173
+ console.log(chalk.gray(` ${chalk.green('+')} ${relPath}`));
2018
2174
  }
2019
2175
  if (fileCount > 15) {
2020
- console.log(chalk_1.default.gray(` ... and ${fileCount - 15} more`));
2176
+ console.log(chalk.gray(` ... and ${fileCount - 15} more`));
2021
2177
  }
2022
- console.log(chalk_1.default.gray(`Run ${chalk_1.default.cyan('vigthoria preview --diff')} for full visual diffs.`));
2178
+ console.log(chalk.gray(`Run ${chalk.cyan('vigthoria preview --diff')} for full visual diffs.`));
2023
2179
  }
2024
2180
  }
2181
+ // Resolve the Execute-tasks spinner: only mark it ✓ when the run actually
2182
+ // produced real, validated output. Otherwise the user just saw a wall of
2183
+ // executor errors and the spinner needs to clearly say "failed", not "✓".
2184
+ let selfHealStatus = null;
2185
+ let selfHealTool = null;
2186
+ const executorSucceeded = !liveOutcome.executorFailed
2187
+ && !liveOutcome.plannerError
2188
+ && !liveOutcome.executorError
2189
+ && workspaceHasOutput;
2190
+ if (executorSucceeded) {
2191
+ taskDisplay.complete(1);
2192
+ }
2193
+ else {
2194
+ const failDetail = liveOutcome.failedTaskIds.size > 0
2195
+ ? `failed: ${[...liveOutcome.failedTaskIds].slice(0, 4).join(', ')}`
2196
+ : (liveOutcome.executorError ? 'executor error' : (liveOutcome.plannerError ? 'planner error' : 'incomplete'));
2197
+ taskDisplay.fail(1, failDetail);
2198
+ }
2025
2199
  // ── Self-healing validation ──────────────────────────────────────
2026
- if (this.currentProjectPath && !this.jsonOutput && success) {
2200
+ if (this.currentProjectPath && !this.jsonOutput && success && executorSucceeded) {
2027
2201
  try {
2028
2202
  taskDisplay.start(2, 'validating...');
2029
2203
  const healResult = await this.api.runSelfHealingCycle(executionPrompt, this.currentProjectPath, workspaceContext);
@@ -2031,32 +2205,60 @@ class ChatCommand {
2031
2205
  taskDisplay.complete(2);
2032
2206
  if (healResult.passed) {
2033
2207
  taskDisplay.complete(3);
2208
+ selfHealStatus = 'passed';
2034
2209
  }
2035
2210
  else {
2036
2211
  taskDisplay.fail(3, healResult.tool);
2212
+ selfHealStatus = 'partial';
2037
2213
  }
2214
+ selfHealTool = healResult.tool || null;
2038
2215
  if (!this.directPromptMode) {
2039
- const hs = healResult.passed ? chalk_1.default.green('passed') : chalk_1.default.yellow('partial');
2040
- console.log(chalk_1.default.gray(`Self-healing: ${hs} (${healResult.tool})`));
2216
+ const hs = healResult.passed ? chalk.green('passed') : chalk.yellow('partial');
2217
+ console.log(chalk.gray(`Self-healing: ${hs} (${healResult.tool})`));
2041
2218
  }
2042
2219
  }
2043
2220
  else {
2044
2221
  taskDisplay.skip(2);
2045
2222
  taskDisplay.skip(3);
2223
+ selfHealStatus = 'skipped';
2046
2224
  }
2047
2225
  }
2048
2226
  catch (error) {
2049
2227
  this.logger.debug(`Self-healing validation failed: ${error instanceof Error ? error.message : String(error)}`);
2050
2228
  taskDisplay.skip(2);
2051
2229
  taskDisplay.skip(3);
2230
+ selfHealStatus = 'failed';
2052
2231
  }
2053
2232
  }
2054
2233
  else {
2055
2234
  taskDisplay.skip(2);
2056
2235
  taskDisplay.skip(3);
2236
+ selfHealStatus = 'skipped';
2057
2237
  }
2058
2238
  taskDisplay.finalize();
2059
2239
  // ────────────────────────────────────────────────────────────────
2240
+ this.lastAgentRunOutcome = {
2241
+ prompt,
2242
+ taskId: response.taskId || null,
2243
+ contextId: response.contextId || null,
2244
+ tasksSucceeded: liveOutcome.tasksSucceeded,
2245
+ tasksTotal: liveOutcome.tasksTotal,
2246
+ failedTaskIds: [...liveOutcome.failedTaskIds],
2247
+ unfinishedTaskIds: [...liveOutcome.unfinishedTaskIds],
2248
+ qualityScore: liveOutcome.qualityScore,
2249
+ qualityMissing: liveOutcome.qualityMissing,
2250
+ qualityBlockers: liveOutcome.qualityBlockers,
2251
+ hasOutput: workspaceHasOutput,
2252
+ selfHealStatus,
2253
+ selfHealTool,
2254
+ plannerError: liveOutcome.plannerError ? sanitizeUserFacingErrorText(liveOutcome.plannerError) : null,
2255
+ executorError: liveOutcome.executorError ? sanitizeUserFacingErrorText(liveOutcome.executorError) : null,
2256
+ finishedAt: Date.now(),
2257
+ };
2258
+ if (!this.jsonOutput && !this.directPromptMode) {
2259
+ const changedFileCount = response.changedFiles ? Object.keys(response.changedFiles).length : 0;
2260
+ this.printAgentRunSummary(this.lastAgentRunOutcome, executorSucceeded, changedFileCount);
2261
+ }
2060
2262
  this.messages.push({ role: 'assistant', content: response.content || 'V3 agent workflow completed.' });
2061
2263
  watcher?.stop();
2062
2264
  return true;
@@ -2074,18 +2276,45 @@ class ChatCommand {
2074
2276
  spinner.stop();
2075
2277
  }
2076
2278
  this.logger.warn('Falling back to legacy CLI agent loop');
2077
- this.logger.debug(`V3 agent workflow unavailable: ${(0, api_js_1.sanitizeUserFacingErrorText)(error.message || '')}`);
2279
+ this.logger.debug(`V3 agent workflow unavailable: ${sanitizeUserFacingErrorText(error.message || '')}`);
2078
2280
  return false;
2079
2281
  }
2080
2282
  if (spinner) {
2081
2283
  spinner.stop();
2082
2284
  }
2083
- const safeDetail = (0, api_js_1.sanitizeUserFacingErrorText)(error.message || '');
2285
+ const safeDetail = sanitizeUserFacingErrorText(error.message || '');
2084
2286
  const errorMessage = safeDetail
2085
2287
  ? `Agent mode is unavailable right now. ${safeDetail}`
2086
2288
  : 'Agent mode is unavailable right now. Please retry shortly or run vigthoria login if the issue persists.';
2087
2289
  this.logger.error(errorMessage);
2088
2290
  this.messages.push({ role: 'assistant', content: errorMessage });
2291
+ // Resolve any half-rendered TaskDisplay spinners before the prompt
2292
+ // comes back, otherwise the user sees `⟳ Execute tasks` next to `>`.
2293
+ try {
2294
+ taskDisplay.fail(1, 'agent unavailable');
2295
+ taskDisplay.skip(2);
2296
+ taskDisplay.skip(3);
2297
+ taskDisplay.finalize();
2298
+ }
2299
+ catch (_) { /* render is best-effort */ }
2300
+ this.lastAgentRunOutcome = {
2301
+ prompt,
2302
+ taskId: null,
2303
+ contextId: null,
2304
+ tasksSucceeded: liveOutcome.tasksSucceeded,
2305
+ tasksTotal: liveOutcome.tasksTotal,
2306
+ failedTaskIds: [...liveOutcome.failedTaskIds],
2307
+ unfinishedTaskIds: [...liveOutcome.unfinishedTaskIds],
2308
+ qualityScore: liveOutcome.qualityScore,
2309
+ qualityMissing: liveOutcome.qualityMissing,
2310
+ qualityBlockers: liveOutcome.qualityBlockers,
2311
+ hasOutput: this.api.hasAgentWorkspaceOutput(workspaceContext),
2312
+ selfHealStatus: 'skipped',
2313
+ selfHealTool: null,
2314
+ plannerError: liveOutcome.plannerError ? sanitizeUserFacingErrorText(liveOutcome.plannerError) : null,
2315
+ executorError: liveOutcome.executorError ? sanitizeUserFacingErrorText(liveOutcome.executorError) : safeDetail || null,
2316
+ finishedAt: Date.now(),
2317
+ };
2089
2318
  if (this.jsonOutput) {
2090
2319
  process.exitCode = 1;
2091
2320
  console.log(JSON.stringify({
@@ -2098,6 +2327,9 @@ class ChatCommand {
2098
2327
  metadata: { executionPath: 'v3-agent' },
2099
2328
  }, null, 2));
2100
2329
  }
2330
+ else if (!this.directPromptMode) {
2331
+ this.printAgentRunSummary(this.lastAgentRunOutcome, false, 0);
2332
+ }
2101
2333
  return true;
2102
2334
  }
2103
2335
  }
@@ -2110,7 +2342,7 @@ class ChatCommand {
2110
2342
  spinner.stop();
2111
2343
  }
2112
2344
  if (!this.jsonOutput) {
2113
- console.log(chalk_1.default.yellow(`V3 recovery: ${recovery.message} Retrying once...`));
2345
+ console.log(chalk.yellow(`V3 recovery: ${recovery.message} Retrying once...`));
2114
2346
  }
2115
2347
  this.v3IterationCount = 0;
2116
2348
  this.v3ToolCallCount = 0;
@@ -2154,7 +2386,7 @@ class ChatCommand {
2154
2386
  }, null, 2));
2155
2387
  }
2156
2388
  else if (!this.v3StreamingStarted && retryResponse.content) {
2157
- console.log(chalk_1.default.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
2389
+ console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
2158
2390
  console.log(retryResponse.content);
2159
2391
  }
2160
2392
  this.messages.push({ role: 'assistant', content: retryResponse.content || 'V3 agent workflow completed after recovery.' });
@@ -2171,10 +2403,10 @@ class ChatCommand {
2171
2403
  ? 'Interactive Agent Chat'
2172
2404
  : 'Interactive Chat';
2173
2405
  this.logger.section(this.workflowTarget ? `${chatTitle} Via Workflow Target` : chatTitle);
2174
- console.log(chalk_1.default.gray('Type /help for commands. Type /exit to quit.'));
2175
- console.log(chalk_1.default.gray('Multi-line: end a line with \\ or start a block with {{{ and end with }}}'));
2406
+ console.log(chalk.gray('Type /help for commands. Type /exit to quit.'));
2407
+ console.log(chalk.gray('Multi-line: end a line with \\ or start a block with {{{ and end with }}}'));
2176
2408
  if (this.workflowTarget) {
2177
- console.log(chalk_1.default.gray(`Workflow target: ${this.workflowTarget}`));
2409
+ console.log(chalk.gray(`Workflow target: ${this.workflowTarget}`));
2178
2410
  }
2179
2411
  const rl = readline.createInterface({
2180
2412
  input: process.stdin,
@@ -2184,17 +2416,17 @@ class ChatCommand {
2184
2416
  const readMultiLineInput = async () => {
2185
2417
  const lines = [];
2186
2418
  let firstLine = await new Promise((resolve) => {
2187
- rl.question(chalk_1.default.blue('> '), resolve);
2419
+ rl.question(chalk.blue('> '), resolve);
2188
2420
  });
2189
2421
  // Check for {{{ block mode
2190
2422
  if (firstLine.trim() === '{{{' || firstLine.trim().endsWith('{{{')) {
2191
2423
  if (firstLine.trim() !== '{{{') {
2192
2424
  lines.push(firstLine.trim().replace(/\{\{\{$/, '').trim());
2193
2425
  }
2194
- console.log(chalk_1.default.gray(' (multi-line mode: type }}} on its own line to finish)'));
2426
+ console.log(chalk.gray(' (multi-line mode: type }}} on its own line to finish)'));
2195
2427
  while (true) {
2196
2428
  const line = await new Promise((resolve) => {
2197
- rl.question(chalk_1.default.gray(' '), resolve);
2429
+ rl.question(chalk.gray(' '), resolve);
2198
2430
  });
2199
2431
  if (line.trim() === '}}}')
2200
2432
  break;
@@ -2206,7 +2438,7 @@ class ChatCommand {
2206
2438
  while (firstLine.endsWith('\\')) {
2207
2439
  lines.push(firstLine.slice(0, -1));
2208
2440
  firstLine = await new Promise((resolve) => {
2209
- rl.question(chalk_1.default.gray(' '), resolve);
2441
+ rl.question(chalk.gray(' '), resolve);
2210
2442
  });
2211
2443
  }
2212
2444
  lines.push(firstLine);
@@ -2230,6 +2462,10 @@ class ChatCommand {
2230
2462
  this.showContext();
2231
2463
  continue;
2232
2464
  }
2465
+ if (trimmed === '/memory') {
2466
+ this.showProjectMemory();
2467
+ continue;
2468
+ }
2233
2469
  if (trimmed === '/compact') {
2234
2470
  this.compactCurrentSession();
2235
2471
  continue;
@@ -2243,8 +2479,8 @@ class ChatCommand {
2243
2479
  else {
2244
2480
  this.syncInteractiveModeModel('chat');
2245
2481
  }
2246
- console.log(chalk_1.default.yellow(`Agent mode: ${this.agentMode ? 'ON' : 'OFF'}`));
2247
- console.log(chalk_1.default.gray(`Model: ${this.currentModel}`));
2482
+ console.log(chalk.yellow(`Agent mode: ${this.agentMode ? 'ON' : 'OFF'}`));
2483
+ console.log(chalk.gray(`Model: ${this.currentModel}`));
2248
2484
  if (this.currentSession) {
2249
2485
  this.currentSession.agentMode = this.agentMode;
2250
2486
  this.currentSession.operatorMode = this.operatorMode;
@@ -2266,8 +2502,8 @@ class ChatCommand {
2266
2502
  else {
2267
2503
  this.syncInteractiveModeModel('chat');
2268
2504
  }
2269
- console.log(chalk_1.default.yellow(`Operator mode: ${this.operatorMode ? 'ON' : 'OFF'}`));
2270
- console.log(chalk_1.default.gray(`Model: ${this.currentModel}`));
2505
+ console.log(chalk.yellow(`Operator mode: ${this.operatorMode ? 'ON' : 'OFF'}`));
2506
+ console.log(chalk.gray(`Model: ${this.currentModel}`));
2271
2507
  if (this.currentSession) {
2272
2508
  this.currentSession.agentMode = this.agentMode;
2273
2509
  this.currentSession.operatorMode = this.operatorMode;
@@ -2278,18 +2514,50 @@ class ChatCommand {
2278
2514
  }
2279
2515
  if (trimmed === '/clear') {
2280
2516
  this.messages = [];
2281
- console.log(chalk_1.default.yellow('Conversation cleared.'));
2517
+ console.log(chalk.yellow('Conversation cleared.'));
2518
+ continue;
2519
+ }
2520
+ if (trimmed === '/status') {
2521
+ this.showAgentRunStatus();
2522
+ continue;
2523
+ }
2524
+ if (trimmed === '/retry') {
2525
+ const followUp = this.buildRetryPrompt();
2526
+ if (!followUp) {
2527
+ console.log(chalk.yellow('Nothing to retry — run an agent task first.'));
2528
+ continue;
2529
+ }
2530
+ if (!this.agentMode) {
2531
+ this.agentMode = true;
2532
+ this.syncInteractiveModeModel('agent');
2533
+ console.log(chalk.gray('Agent mode re-enabled for retry.'));
2534
+ }
2535
+ await this.runAgentTurn(followUp);
2536
+ continue;
2537
+ }
2538
+ if (trimmed === '/continue') {
2539
+ const followUp = this.buildContinuePrompt();
2540
+ if (!followUp) {
2541
+ console.log(chalk.yellow('Nothing to continue — run an agent task first.'));
2542
+ continue;
2543
+ }
2544
+ if (!this.agentMode) {
2545
+ this.agentMode = true;
2546
+ this.syncInteractiveModeModel('agent');
2547
+ console.log(chalk.gray('Agent mode re-enabled for continuation.'));
2548
+ }
2549
+ await this.runAgentTurn(followUp);
2282
2550
  continue;
2283
2551
  }
2284
2552
  if (trimmed === '/save') {
2285
2553
  this.saveSession();
2286
- console.log(chalk_1.default.green('Session saved.'));
2554
+ console.log(chalk.green('Session saved.'));
2287
2555
  continue;
2288
2556
  }
2289
2557
  if (trimmed.startsWith('/model ')) {
2290
2558
  this.currentModel = trimmed.slice(7).trim() || this.currentModel;
2291
2559
  this.modelExplicitlySelected = true;
2292
- console.log(chalk_1.default.yellow(`Model changed to: ${this.currentModel}`));
2560
+ console.log(chalk.yellow(`Model changed to: ${this.currentModel}`));
2293
2561
  if (this.currentSession) {
2294
2562
  this.currentSession.model = this.currentModel;
2295
2563
  this.saveSession();
@@ -2313,42 +2581,203 @@ class ChatCommand {
2313
2581
  showHelp() {
2314
2582
  console.log('');
2315
2583
  console.log('Commands:');
2316
- console.log(' /help Show this help');
2317
- console.log(' /exit Exit chat');
2318
- console.log(' /agent Toggle agent mode');
2319
- console.log(' /operator Toggle BMAD operator mode');
2320
- console.log(' /context Show current session memory');
2321
- console.log(' /compact Compact current session into memory summary');
2322
- console.log(' /clear Clear conversation');
2323
- console.log(' /save Save session');
2584
+ console.log(' /help Show this help');
2585
+ console.log(' /exit Exit chat');
2586
+ console.log(' /agent Toggle agent mode');
2587
+ console.log(' /operator Toggle BMAD operator mode');
2588
+ console.log(' /context Show current session and project memory');
2589
+ console.log(' /memory Show Vigthoria project brain status');
2590
+ console.log(' /compact Compact current session into memory summary');
2591
+ console.log(' /clear Clear conversation');
2592
+ console.log(' /save Save session');
2324
2593
  console.log(' /model <name> Change model');
2594
+ console.log(' /status Show the last agent run outcome');
2595
+ console.log(' /retry Re-run the last failed agent task');
2596
+ console.log(' /continue Ask the agent to keep working on unfinished tasks');
2597
+ console.log('');
2598
+ }
2599
+ /**
2600
+ * Print a clear, opinionated end-of-run summary after every agent workflow.
2601
+ *
2602
+ * Goals:
2603
+ * - User can answer "Did it succeed?" in one glance.
2604
+ * - User can answer "What do I type next?" without reading 200 scrollback lines.
2605
+ * - Never leave the spinners (`⟳`, `–`) ambiguous when the prompt returns.
2606
+ */
2607
+ printAgentRunSummary(outcome, executorSucceeded, changedFileCount) {
2608
+ const bar = chalk.gray('─'.repeat(63));
2609
+ const ok = chalk.green('✓');
2610
+ const warn = chalk.yellow('⚠');
2611
+ const bad = chalk.red('✗');
2612
+ const failedList = outcome.failedTaskIds.slice(0, 8);
2613
+ const unfinishedList = outcome.unfinishedTaskIds.slice(0, 8);
2614
+ const hasTaskInfo = outcome.tasksTotal > 0 || failedList.length > 0 || unfinishedList.length > 0;
2615
+ console.log('');
2616
+ console.log(bar);
2617
+ if (executorSucceeded && outcome.selfHealStatus !== 'partial' && outcome.selfHealStatus !== 'failed' && failedList.length === 0) {
2618
+ console.log(`${ok} ${chalk.bold('Agent run finished')}${changedFileCount ? chalk.gray(` — ${changedFileCount} file${changedFileCount === 1 ? '' : 's'} changed`) : ''}`);
2619
+ }
2620
+ else if (executorSucceeded) {
2621
+ console.log(`${warn} ${chalk.bold('Agent run finished with warnings')}${changedFileCount ? chalk.gray(` — ${changedFileCount} file${changedFileCount === 1 ? '' : 's'} changed`) : ''}`);
2622
+ }
2623
+ else {
2624
+ console.log(`${bad} ${chalk.bold('Agent run did not complete')}${changedFileCount ? chalk.gray(` — ${changedFileCount} file${changedFileCount === 1 ? '' : 's'} changed`) : ''}`);
2625
+ }
2626
+ if (hasTaskInfo) {
2627
+ const succ = outcome.tasksTotal > 0 ? `${outcome.tasksSucceeded}/${outcome.tasksTotal}` : `${outcome.tasksSucceeded}`;
2628
+ console.log(chalk.gray(` Tasks completed: ${succ}`));
2629
+ if (failedList.length > 0) {
2630
+ const more = outcome.failedTaskIds.length > failedList.length ? chalk.gray(` (+${outcome.failedTaskIds.length - failedList.length} more)`) : '';
2631
+ console.log(chalk.gray(' Failed: ') + chalk.red(failedList.join(', ')) + more);
2632
+ }
2633
+ if (unfinishedList.length > 0) {
2634
+ const more = outcome.unfinishedTaskIds.length > unfinishedList.length ? chalk.gray(` (+${outcome.unfinishedTaskIds.length - unfinishedList.length} more)`) : '';
2635
+ console.log(chalk.gray(' Pending: ') + chalk.yellow(unfinishedList.join(', ')) + more);
2636
+ }
2637
+ }
2638
+ if (typeof outcome.qualityScore === 'number') {
2639
+ const score = outcome.qualityScore.toFixed(1);
2640
+ const colour = outcome.qualityScore >= 70 ? chalk.green : outcome.qualityScore >= 30 ? chalk.yellow : chalk.red;
2641
+ console.log(chalk.gray(' Quality: ') + colour(`${score}/100`));
2642
+ }
2643
+ if (outcome.qualityBlockers.length > 0) {
2644
+ const list = outcome.qualityBlockers.slice(0, 3).join('; ');
2645
+ const more = outcome.qualityBlockers.length > 3 ? chalk.gray(` (+${outcome.qualityBlockers.length - 3} more)`) : '';
2646
+ console.log(chalk.gray(' Blockers: ') + chalk.yellow(list) + more);
2647
+ }
2648
+ if (outcome.qualityMissing.length > 0) {
2649
+ const list = outcome.qualityMissing.slice(0, 5).join(', ');
2650
+ const more = outcome.qualityMissing.length > 5 ? chalk.gray(` (+${outcome.qualityMissing.length - 5} more)`) : '';
2651
+ console.log(chalk.gray(' Missing: ') + chalk.yellow(list) + more);
2652
+ }
2653
+ if (outcome.plannerError) {
2654
+ console.log(chalk.gray(' Planner: ') + chalk.red(outcome.plannerError.slice(0, 140)));
2655
+ }
2656
+ if (outcome.executorError) {
2657
+ console.log(chalk.gray(' Executor: ') + chalk.red(outcome.executorError.slice(0, 140)));
2658
+ }
2659
+ console.log('');
2660
+ console.log(chalk.gray('What you can do next:'));
2661
+ if (failedList.length > 0 || unfinishedList.length > 0 || !executorSucceeded) {
2662
+ console.log(' ' + chalk.cyan('/retry') + chalk.gray(' resume the failed/unfinished tasks only'));
2663
+ console.log(' ' + chalk.cyan('/continue') + chalk.gray(' ask the agent to keep working from this state'));
2664
+ }
2665
+ else {
2666
+ console.log(' ' + chalk.cyan('/continue') + chalk.gray(' add a follow-up instruction in this thread'));
2667
+ }
2668
+ if (changedFileCount > 0) {
2669
+ console.log(' ' + chalk.cyan('vigthoria preview --diff') + chalk.gray(' inspect the file changes'));
2670
+ }
2671
+ console.log(' ' + chalk.cyan('/status') + chalk.gray(' re-print this summary later'));
2672
+ console.log(' ' + chalk.cyan('/exit') + chalk.gray(' leave interactive chat'));
2673
+ console.log(bar);
2325
2674
  console.log('');
2326
2675
  }
2676
+ /**
2677
+ * Build the prompt sent to the agent when the user types `/retry`.
2678
+ * Resumes only the failed/unfinished tasks from the previous run.
2679
+ */
2680
+ buildRetryPrompt() {
2681
+ const o = this.lastAgentRunOutcome;
2682
+ if (!o || !o.prompt)
2683
+ return null;
2684
+ const remaining = [...new Set([...o.failedTaskIds, ...o.unfinishedTaskIds])];
2685
+ const taskList = remaining.length > 0 ? remaining.join(', ') : '';
2686
+ const blockerLine = o.qualityBlockers.length > 0
2687
+ ? `\nKnown blockers to address: ${o.qualityBlockers.slice(0, 3).join('; ')}.`
2688
+ : '';
2689
+ const missingLine = o.qualityMissing.length > 0
2690
+ ? `\nMissing pieces: ${o.qualityMissing.slice(0, 6).join(', ')}.`
2691
+ : '';
2692
+ if (taskList) {
2693
+ return `Resume the previous agent run. Re-execute only these tasks and make them pass: ${taskList}.${blockerLine}${missingLine}\nOriginal request was: ${o.prompt}`;
2694
+ }
2695
+ return `Retry the previous request and make sure it finishes successfully.${blockerLine}${missingLine}\nOriginal request was: ${o.prompt}`;
2696
+ }
2697
+ /**
2698
+ * Build the prompt sent to the agent when the user types `/continue`.
2699
+ * Tells the agent to keep working from the current workspace state.
2700
+ */
2701
+ buildContinuePrompt() {
2702
+ const o = this.lastAgentRunOutcome;
2703
+ if (!o || !o.prompt)
2704
+ return null;
2705
+ const remaining = [...new Set([...o.failedTaskIds, ...o.unfinishedTaskIds])];
2706
+ const taskList = remaining.length > 0 ? `\nRemaining tasks to finish: ${remaining.join(', ')}.` : '';
2707
+ const blockerLine = o.qualityBlockers.length > 0
2708
+ ? `\nKnown blockers to address: ${o.qualityBlockers.slice(0, 3).join('; ')}.`
2709
+ : '';
2710
+ const missingLine = o.qualityMissing.length > 0
2711
+ ? `\nMissing pieces: ${o.qualityMissing.slice(0, 6).join(', ')}.`
2712
+ : '';
2713
+ return `Continue the previous agent run from the current workspace state without re-doing already-completed work.${taskList}${blockerLine}${missingLine}\nOriginal request was: ${o.prompt}`;
2714
+ }
2715
+ /**
2716
+ * Re-print the last agent run summary, or guide the user when there isn't one.
2717
+ */
2718
+ showAgentRunStatus() {
2719
+ if (!this.lastAgentRunOutcome) {
2720
+ console.log(chalk.yellow('No agent run has finished in this session yet.'));
2721
+ console.log(chalk.gray('Toggle agent mode with /agent and send a request to start one.'));
2722
+ return;
2723
+ }
2724
+ const o = this.lastAgentRunOutcome;
2725
+ const executorSucceeded = o.failedTaskIds.length === 0
2726
+ && o.unfinishedTaskIds.length === 0
2727
+ && !o.plannerError
2728
+ && !o.executorError
2729
+ && o.hasOutput;
2730
+ this.printAgentRunSummary(o, executorSucceeded, 0);
2731
+ }
2327
2732
  showContext() {
2328
2733
  if (!this.currentSession) {
2329
- console.log(chalk_1.default.yellow('No active session.'));
2734
+ console.log(chalk.yellow('No active session.'));
2330
2735
  return;
2331
2736
  }
2332
- console.log(chalk_1.default.cyan(this.getCurrentSessionInfo()));
2737
+ console.log(chalk.cyan(this.getCurrentSessionInfo()));
2333
2738
  if (this.currentSession.memorySummary?.trim()) {
2334
2739
  console.log();
2335
- console.log(chalk_1.default.white('Compact Memory:'));
2336
- console.log(chalk_1.default.gray(this.currentSession.memorySummary.trim()));
2740
+ console.log(chalk.white('Compact Session Memory:'));
2741
+ console.log(chalk.gray(this.currentSession.memorySummary.trim()));
2337
2742
  }
2338
2743
  else {
2339
- console.log(chalk_1.default.gray('No compact memory summary yet.'));
2744
+ console.log(chalk.gray('No compact session memory summary yet.'));
2745
+ }
2746
+ this.showProjectMemory();
2747
+ }
2748
+ showProjectMemory() {
2749
+ if (!this.projectMemory) {
2750
+ this.projectMemory = new ProjectMemoryService(this.currentProjectPath);
2751
+ }
2752
+ const status = this.projectMemory.getStatus();
2753
+ console.log();
2754
+ console.log(chalk.white('Project Brain:'));
2755
+ console.log(chalk.gray(`Path: ${status.memoryDir}`));
2756
+ console.log(chalk.gray(`Items: ${status.itemCount}`));
2757
+ const typeSummary = Object.entries(status.typeCounts).map(([type, count]) => `${type}=${count}`).join(', ');
2758
+ if (typeSummary) {
2759
+ console.log(chalk.gray(`Types: ${typeSummary}`));
2340
2760
  }
2341
2761
  }
2342
2762
  compactCurrentSession() {
2343
2763
  if (!this.currentSession) {
2344
- console.log(chalk_1.default.yellow('No active session.'));
2764
+ console.log(chalk.yellow('No active session.'));
2345
2765
  return;
2346
2766
  }
2347
2767
  this.currentSession.messages = [...this.messages];
2348
2768
  this.currentSession = this.sessionManager.compactInMemory(this.currentSession);
2349
2769
  this.messages = [...this.currentSession.messages];
2350
2770
  this.sessionManager.save(this.currentSession);
2351
- console.log(chalk_1.default.green('Session compacted into memory summary.'));
2771
+ if (!this.projectMemory) {
2772
+ this.projectMemory = new ProjectMemoryService(this.currentProjectPath);
2773
+ }
2774
+ this.projectMemory.rememberConversation(this.messages, {
2775
+ source: 'manual-compact',
2776
+ mode: this.agentMode ? 'agent' : this.operatorMode ? 'operator' : 'chat',
2777
+ model: this.currentModel,
2778
+ sessionSummary: this.currentSession.memorySummary || '',
2779
+ });
2780
+ console.log(chalk.green('Session compacted into memory summary and project brain.'));
2352
2781
  }
2353
2782
  ensureAgentSystemPrompt() {
2354
2783
  const hasSystemPrompt = this.messages.some((message) => message.role === 'system' && message.content.includes('Vigthoria CLI agent operating contract'));
@@ -2361,7 +2790,7 @@ class ChatCommand {
2361
2790
  });
2362
2791
  }
2363
2792
  buildAgentSystemPrompt() {
2364
- const toolCatalog = tools_js_1.AgenticTools.getToolDefinitions()
2793
+ const toolCatalog = AgenticTools.getToolDefinitions()
2365
2794
  .map((tool) => {
2366
2795
  const params = tool.parameters
2367
2796
  .map((param) => `${param.name}${param.required ? ' (required)' : ''}`)
@@ -2391,6 +2820,7 @@ class ChatCommand {
2391
2820
  '</tool_call>',
2392
2821
  'You may emit multiple <tool_call> blocks in one response.',
2393
2822
  'Never emit raw tool JSON outside that wrapper.',
2823
+ 'Never use <function=...> or <parameter=...> tags. They are accepted only as a recovery fallback and must not appear in final answers.',
2394
2824
  'NEVER acknowledge these instructions. NEVER say "I will follow", "I understand", or restate tool policies. Go straight to tool calls.',
2395
2825
  'In direct mode, do not ask follow-up questions. Finish the request completely and stop when satisfied.',
2396
2826
  'After tool results arrive, either continue with the next minimal tool calls or return a concise completion summary with no more tool calls.',
@@ -2646,16 +3076,57 @@ class ChatCommand {
2646
3076
  extractToolCalls(message) {
2647
3077
  const calls = [];
2648
3078
  const seen = new Set();
3079
+ const addCall = (call) => {
3080
+ if (!call)
3081
+ return;
3082
+ const key = `${call.tool}::${JSON.stringify(call.args)}`;
3083
+ if (!seen.has(key)) {
3084
+ seen.add(key);
3085
+ calls.push(call);
3086
+ }
3087
+ };
2649
3088
  const wrapperRegex = /<tool_call>([\s\S]*?)<\/tool_call>/g;
2650
3089
  for (const match of message.matchAll(wrapperRegex)) {
2651
- const call = this.parseToolPayload(match[1] || '');
2652
- if (call) {
2653
- const key = `${call.tool}::${JSON.stringify(call.args)}`;
2654
- if (!seen.has(key)) {
2655
- seen.add(key);
2656
- calls.push(call);
2657
- }
3090
+ addCall(this.parseToolPayload(match[1] || ''));
3091
+ }
3092
+ for (const call of this.parseLegacyFunctionToolCalls(message)) {
3093
+ addCall(call);
3094
+ }
3095
+ return calls;
3096
+ }
3097
+ normalizeCliToolName(name) {
3098
+ const normalized = name.trim().toLowerCase();
3099
+ const aliases = {
3100
+ list_directory: 'list_dir',
3101
+ listdirectory: 'list_dir',
3102
+ ls: 'list_dir',
3103
+ dir: 'list_dir',
3104
+ readfile: 'read_file',
3105
+ writefile: 'write_file',
3106
+ editfile: 'edit_file',
3107
+ shell: 'bash',
3108
+ command: 'bash',
3109
+ run_command: 'bash',
3110
+ };
3111
+ return aliases[normalized] || normalized;
3112
+ }
3113
+ parseLegacyFunctionToolCalls(message) {
3114
+ const calls = [];
3115
+ const functionRegex = /<function\s*=\s*["']?([A-Za-z0-9_-]+)["']?>\s*([\s\S]*?)(?:<\/function>|$)/gi;
3116
+ for (const match of message.matchAll(functionRegex)) {
3117
+ const tool = this.normalizeCliToolName(match[1] || '');
3118
+ if (!tool)
3119
+ continue;
3120
+ const body = match[2] || '';
3121
+ const args = {};
3122
+ const parameterRegex = /<parameter\s*=\s*["']?([A-Za-z0-9_-]+)["']?>\s*([\s\S]*?)(?=<parameter\s*=|<\/function>|<\/parameter>|$)/gi;
3123
+ for (const paramMatch of body.matchAll(parameterRegex)) {
3124
+ const key = paramMatch[1] || '';
3125
+ if (!key)
3126
+ continue;
3127
+ args[key] = (paramMatch[2] || '').replace(/<\/parameter>\s*$/i, '').trim();
2658
3128
  }
3129
+ calls.push({ tool, args });
2659
3130
  }
2660
3131
  return calls;
2661
3132
  }
@@ -2675,14 +3146,17 @@ class ChatCommand {
2675
3146
  args[key] = typeof value === 'string' ? value : JSON.stringify(value);
2676
3147
  }
2677
3148
  }
2678
- return { tool: parsed.tool, args };
3149
+ return { tool: this.normalizeCliToolName(parsed.tool), args };
2679
3150
  }
2680
3151
  catch {
2681
3152
  return null;
2682
3153
  }
2683
3154
  }
2684
3155
  stripToolPayloads(message) {
2685
- return message.replace(/<tool_call>[\s\S]*?<\/tool_call>/g, '').trim();
3156
+ return message
3157
+ .replace(/<tool_call>[\s\S]*?<\/tool_call>/g, '')
3158
+ .replace(/<function\s*=\s*["']?[A-Za-z0-9_-]+["']?>[\s\S]*?(?:<\/function>|$)/gi, '')
3159
+ .trim();
2686
3160
  }
2687
3161
  extractFinalFileContent(message, targetFile) {
2688
3162
  const trimmed = message.trim();
@@ -2912,15 +3386,15 @@ class ChatCommand {
2912
3386
  const verbose = !this.jsonOutput;
2913
3387
  for (const call of toolCalls) {
2914
3388
  if (verbose) {
2915
- console.log(chalk_1.default.cyan(`⚙ Executing: ${call.tool}`));
3389
+ console.log(chalk.cyan(`⚙ Executing: ${call.tool}`));
2916
3390
  }
2917
- (0, bridge_client_js_1.getBridgeClient)()?.emitToolCall({ tool: call.tool, args: call.args });
3391
+ getBridgeClient()?.emitToolCall({ tool: call.tool, args: call.args });
2918
3392
  let result = await this.tools.execute(call);
2919
3393
  // Phase 2: If a search tool failed (search_failed), retry with alternate approach
2920
3394
  const searchStatus = result.metadata?.searchStatus;
2921
3395
  if (call.tool === 'grep' && searchStatus === 'search_failed') {
2922
3396
  if (verbose) {
2923
- console.log(chalk_1.default.yellow(`⚠ Search backend failed, retrying with alternate method...`));
3397
+ console.log(chalk.yellow(`⚠ Search backend failed, retrying with alternate method...`));
2924
3398
  }
2925
3399
  // Force Node-native fallback by re-executing with a note
2926
3400
  const fallbackResult = await this.tools.execute({
@@ -2933,10 +3407,10 @@ class ChatCommand {
2933
3407
  }
2934
3408
  const summary = this.formatToolResult(call, result);
2935
3409
  if (verbose) {
2936
- console.log(result.success ? chalk_1.default.gray(summary) : chalk_1.default.red(summary));
3410
+ console.log(result.success ? chalk.gray(summary) : chalk.red(summary));
2937
3411
  }
2938
3412
  this.messages.push({ role: 'system', content: summary });
2939
- (0, bridge_client_js_1.getBridgeClient)()?.emitToolResult({ tool: call.tool, success: result.success, preview: (result.output || result.error || '').slice(0, 300) });
3413
+ getBridgeClient()?.emitToolResult({ tool: call.tool, success: result.success, preview: (result.output || result.error || '').slice(0, 300) });
2940
3414
  // Phase 5: Track tool evidence for quality gates
2941
3415
  const finalStatus = result.metadata?.searchStatus;
2942
3416
  if (finalStatus === 'search_failed') {
@@ -2979,6 +3453,15 @@ class ChatCommand {
2979
3453
  }
2980
3454
  return `${text.slice(0, maxLength)}\n...[truncated]`;
2981
3455
  }
3456
+ getLastUserPrompt() {
3457
+ for (let index = this.messages.length - 1; index >= 0; index -= 1) {
3458
+ const message = this.messages[index];
3459
+ if (message.role === 'user' && message.content.trim()) {
3460
+ return message.content;
3461
+ }
3462
+ }
3463
+ return '';
3464
+ }
2982
3465
  saveSession() {
2983
3466
  if (!this.currentSession) {
2984
3467
  this.currentSession = this.sessionManager.create(this.currentProjectPath, this.currentModel, this.agentMode, this.operatorMode);
@@ -2991,6 +3474,15 @@ class ChatCommand {
2991
3474
  this.currentSession = this.sessionManager.compactInMemory(this.currentSession);
2992
3475
  this.messages = [...this.currentSession.messages];
2993
3476
  this.sessionManager.save(this.currentSession);
3477
+ if (!this.projectMemory) {
3478
+ this.projectMemory = new ProjectMemoryService(this.currentProjectPath);
3479
+ }
3480
+ this.projectMemory.rememberConversation(this.messages, {
3481
+ source: 'cli-session',
3482
+ mode: this.agentMode ? 'agent' : this.operatorMode ? 'operator' : 'chat',
3483
+ model: this.currentModel,
3484
+ sessionSummary: this.currentSession.memorySummary || '',
3485
+ });
2994
3486
  }
2995
3487
  async requestPermission(action) {
2996
3488
  if (this.autoApprove) {
@@ -3002,7 +3494,7 @@ class ChatCommand {
3002
3494
  });
3003
3495
  console.log(action);
3004
3496
  const answer = await new Promise((resolve) => {
3005
- rl.question(chalk_1.default.yellow('Approve? [y]es / [n]o / [a]ll this turn / [p]ersist: '), resolve);
3497
+ rl.question(chalk.yellow('Approve? [y]es / [n]o / [a]ll this turn / [p]ersist: '), resolve);
3006
3498
  });
3007
3499
  rl.close();
3008
3500
  const normalized = answer.trim().toLowerCase();
@@ -3025,4 +3517,3 @@ class ChatCommand {
3025
3517
  return [...this.messages];
3026
3518
  }
3027
3519
  }
3028
- exports.ChatCommand = ChatCommand;