vigthoria-cli 1.10.51 → 1.11.0

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.
@@ -0,0 +1,19 @@
1
+ import { Config } from '../utils/config.js';
2
+ import { Logger } from '../utils/logger.js';
3
+ interface BackgroundOptions {
4
+ json?: boolean;
5
+ limit?: number;
6
+ workspace?: string;
7
+ }
8
+ export declare class BackgroundCommand {
9
+ private config;
10
+ private logger;
11
+ private api;
12
+ constructor(config: Config, logger: Logger);
13
+ start(promptParts: string[], options?: BackgroundOptions): Promise<void>;
14
+ list(options?: BackgroundOptions): Promise<void>;
15
+ status(jobId: string, options?: BackgroundOptions): Promise<void>;
16
+ apply(jobId: string, options?: BackgroundOptions): Promise<void>;
17
+ cancel(jobId: string, options?: BackgroundOptions): Promise<void>;
18
+ }
19
+ export {};
@@ -0,0 +1,160 @@
1
+ import chalk from 'chalk';
2
+ import { createSpinner, CH } from '../utils/logger.js';
3
+ import { APIClient } from '../utils/api.js';
4
+ export class BackgroundCommand {
5
+ config;
6
+ logger;
7
+ api;
8
+ constructor(config, logger) {
9
+ this.config = config;
10
+ this.logger = logger;
11
+ this.api = new APIClient(config, logger);
12
+ }
13
+ async start(promptParts, options = {}) {
14
+ const prompt = promptParts.join(' ').trim();
15
+ if (!prompt) {
16
+ this.logger.error('Usage: vigthoria background start "<task>"');
17
+ return;
18
+ }
19
+ const workspacePath = options.workspace || process.cwd();
20
+ const spinner = createSpinner('Starting background agent job...').start();
21
+ try {
22
+ const job = await this.api.startV3BackgroundJob(prompt, {
23
+ workspacePath,
24
+ projectPath: workspacePath,
25
+ targetPath: workspacePath,
26
+ localMachineCapable: true,
27
+ executionSurface: 'cli',
28
+ clientSurface: 'cli',
29
+ clientToolExecution: false,
30
+ });
31
+ spinner.stop();
32
+ if (options.json) {
33
+ console.log(JSON.stringify(job, null, 2));
34
+ return;
35
+ }
36
+ console.log(chalk.green(`${CH.success} Background job started`));
37
+ console.log(chalk.gray(` Job: ${job.job_id}`));
38
+ console.log(chalk.gray(` Status: ${job.status}`));
39
+ console.log(chalk.gray(` Workspace: ${workspacePath}`));
40
+ console.log();
41
+ console.log(chalk.gray(`Use ${chalk.cyan(`vigthoria background status ${job.job_id}`)} to check progress.`));
42
+ console.log(chalk.gray(`Use ${chalk.cyan(`vigthoria background apply ${job.job_id}`)} when it is completed.`));
43
+ }
44
+ catch (error) {
45
+ spinner.stop();
46
+ this.logger.error(error?.message || String(error));
47
+ }
48
+ }
49
+ async list(options = {}) {
50
+ const spinner = createSpinner('Loading background jobs...').start();
51
+ try {
52
+ const jobs = await this.api.listV3BackgroundJobs(options.limit || 20);
53
+ spinner.stop();
54
+ if (options.json) {
55
+ console.log(JSON.stringify({ jobs }, null, 2));
56
+ return;
57
+ }
58
+ if (jobs.length === 0) {
59
+ console.log(chalk.gray('No background jobs found.'));
60
+ return;
61
+ }
62
+ console.log(chalk.bold(`\n${CH.success} Background Jobs\n`));
63
+ for (const job of jobs) {
64
+ const color = job.status === 'completed' ? chalk.green : job.status === 'failed' ? chalk.red : job.status === 'cancelled' ? chalk.yellow : chalk.cyan;
65
+ console.log(`${color(String(job.status || 'unknown').padEnd(10))} ${chalk.cyan(job.job_id)} ${chalk.gray(job.updated_at || '')}`);
66
+ console.log(chalk.gray(` ${String(job.request || '').slice(0, 100)}`));
67
+ console.log(chalk.gray(` files: ${job.file_count || 0} workspace: ${job.local_workspace_path || job.workspace_name || '-'}`));
68
+ }
69
+ console.log();
70
+ }
71
+ catch (error) {
72
+ spinner.stop();
73
+ this.logger.error(error?.message || String(error));
74
+ }
75
+ }
76
+ async status(jobId, options = {}) {
77
+ if (!jobId) {
78
+ this.logger.error('Usage: vigthoria background status <job-id>');
79
+ return;
80
+ }
81
+ const spinner = createSpinner('Loading background job...').start();
82
+ try {
83
+ const job = await this.api.getV3BackgroundJob(jobId);
84
+ spinner.stop();
85
+ if (options.json) {
86
+ console.log(JSON.stringify(job, null, 2));
87
+ return;
88
+ }
89
+ console.log(chalk.bold(`\nBackground Job ${chalk.cyan(job.job_id)}\n`));
90
+ console.log(`Status: ${job.status}`);
91
+ console.log(`Files: ${job.file_count || 0}`);
92
+ console.log(`Updated: ${job.updated_at || '-'}`);
93
+ console.log(`Workspace: ${job.local_workspace_path || job.workspace_name || '-'}`);
94
+ if (job.error)
95
+ console.log(chalk.red(`Error: ${job.error}`));
96
+ console.log(chalk.gray(`\n${String(job.request || '').slice(0, 500)}`));
97
+ if (job.status === 'completed' && (job.file_count || 0) > 0) {
98
+ console.log(chalk.gray(`\nUse ${chalk.cyan(`vigthoria background apply ${job.job_id}`)} to apply files locally.`));
99
+ }
100
+ }
101
+ catch (error) {
102
+ spinner.stop();
103
+ this.logger.error(error?.message || String(error));
104
+ }
105
+ }
106
+ async apply(jobId, options = {}) {
107
+ if (!jobId) {
108
+ this.logger.error('Usage: vigthoria background apply <job-id>');
109
+ return;
110
+ }
111
+ const workspacePath = options.workspace || process.cwd();
112
+ const spinner = createSpinner('Applying background job files...').start();
113
+ try {
114
+ const result = await this.api.applyV3BackgroundJobFiles(jobId, {
115
+ workspacePath,
116
+ projectPath: workspacePath,
117
+ targetPath: workspacePath,
118
+ });
119
+ spinner.stop();
120
+ if (options.json) {
121
+ console.log(JSON.stringify(result, null, 2));
122
+ return;
123
+ }
124
+ console.log(chalk.green(`${CH.success} Applied ${result.applied.length} file(s) from ${jobId}`));
125
+ for (const file of result.applied.slice(0, 20)) {
126
+ console.log(chalk.gray(` + ${file}`));
127
+ }
128
+ if (result.applied.length > 20) {
129
+ console.log(chalk.gray(` ... and ${result.applied.length - 20} more`));
130
+ }
131
+ if (result.skipped.length > 0) {
132
+ console.log(chalk.yellow(`Skipped ${result.skipped.length} unchanged or unsafe file(s).`));
133
+ }
134
+ }
135
+ catch (error) {
136
+ spinner.stop();
137
+ this.logger.error(error?.message || String(error));
138
+ }
139
+ }
140
+ async cancel(jobId, options = {}) {
141
+ if (!jobId) {
142
+ this.logger.error('Usage: vigthoria background cancel <job-id>');
143
+ return;
144
+ }
145
+ const spinner = createSpinner('Cancelling background job...').start();
146
+ try {
147
+ const result = await this.api.cancelV3BackgroundJob(jobId);
148
+ spinner.stop();
149
+ if (options.json) {
150
+ console.log(JSON.stringify(result, null, 2));
151
+ return;
152
+ }
153
+ console.log(chalk.yellow(`${CH.warn} Background job cancelled: ${jobId}`));
154
+ }
155
+ catch (error) {
156
+ spinner.stop();
157
+ this.logger.error(error?.message || String(error));
158
+ }
159
+ }
160
+ }
@@ -13,6 +13,7 @@ import { TaskDisplay } from '../utils/task-display.js';
13
13
  import { ProjectMemoryService } from '../utils/project-memory.js';
14
14
  import { buildPersonaOverlay, normalizePersonaMode } from '../utils/persona.js';
15
15
  import { runAgentSessionMenu, shouldShowAgentSessionMenu } from './agent-session-menu.js';
16
+ import { evaluateExecutorSuccess, handleTaskEvent, noteAnalysisToolUse, } from '../utils/agentRunOutcome.js';
16
17
  const DEFAULT_V3_AGENT_TIMEOUT_MS = (() => {
17
18
  const rawValue = process.env.VIGTHORIA_AGENT_TIMEOUT_MS || process.env.V3_AGENT_TIMEOUT_MS;
18
19
  if (!rawValue) {
@@ -353,7 +354,12 @@ export class ChatCommand {
353
354
  * question — these should use analysis_only workflow, not full_autonomy.
354
355
  */
355
356
  isAnalysisLookupPrompt(prompt) {
356
- return /^(what|which|where|how many|who|find|list|show|check|inspect|analyze|analyse|audit|explain|describe|summarize|summarise|review|overview|count|read|look at|tell me|locate|search for|does .* exist)/i.test(prompt.trim());
357
+ const trimmed = prompt.trim();
358
+ if (/^(what|which|where|how many|who|find|list|show|check|inspect|analyze|analyse|audit|explain|describe|summarize|summarise|review|overview|count|read|look at|tell me|locate|search for|does .* exist)/i.test(trimmed)) {
359
+ return true;
360
+ }
361
+ // Mid-conversation accountability / lookup questions (Gideon regression)
362
+ return /\b(where\s+(?:is|are)|what\s+(?:is|are|have|did|was|were)|how\s+(?:do|does|is|are)|what\s+(?:have\s+)?you\s+(?:done|changed|built)|what\s+(?:is|was)\s+(?:your|the)\s+answer)\b/i.test(trimmed);
357
363
  }
358
364
  extractExplicitLocalPath(prompt) {
359
365
  // Try to extract Windows paths (C:\ D:\ etc.)
@@ -746,6 +752,7 @@ export class ChatCommand {
746
752
  .replace(/^\s*(?:json\s*)?\[\s*\{[\s\S]*?"tool"[\s\S]*$/gim, '')
747
753
  .replace(/^\s*(?:list_dir|read_file|write_file|edit_file|glob|grep|bash)\s*$/gim, '')
748
754
  .replace(/<tool_call>[\s\S]*?<\/tool_call>/gi, '')
755
+ .replace(/<tool_code>[\s\S]*?<\/tool_code>/gi, '')
749
756
  .replace(/\n{3,}/g, '\n\n');
750
757
  return output;
751
758
  }
@@ -2371,6 +2378,10 @@ export class ChatCommand {
2371
2378
  plannerError: null,
2372
2379
  executorError: null,
2373
2380
  executorFailed: false,
2381
+ analysisToolsUsed: 0,
2382
+ changedFileCount: 0,
2383
+ requiresWorkspaceChanges: false,
2384
+ workspaceHasOutput: false,
2374
2385
  };
2375
2386
  const parsePlannerSummary = (raw) => {
2376
2387
  if (!raw || typeof raw !== 'string')
@@ -2486,17 +2497,23 @@ export class ChatCommand {
2486
2497
  }
2487
2498
  else if (event.type === 'executor_complete') {
2488
2499
  const summary = event.summary || {};
2489
- const tid = summary.task_id || summary.id;
2500
+ handleTaskEvent(summary, liveOutcome);
2490
2501
  if (summary.status === 'failed') {
2491
- if (tid)
2492
- liveOutcome.failedTaskIds.add(String(tid));
2493
- liveOutcome.executorFailed = true;
2502
+ const err = typeof summary.error === 'string' ? summary.error.trim() : '';
2503
+ if (err && !liveOutcome.executorError)
2504
+ liveOutcome.executorError = err;
2494
2505
  }
2495
- else if (summary.status === 'completed' || summary.status === 'success') {
2496
- if (tid)
2497
- liveOutcome.failedTaskIds.delete(String(tid));
2498
- liveOutcome.tasksSucceeded += 1;
2506
+ }
2507
+ else if (event.type === 'tool_call' || event.type === 'tool_result') {
2508
+ const toolName = String(event.name || event.tool || '').trim();
2509
+ if (toolName)
2510
+ noteAnalysisToolUse(toolName, liveOutcome);
2511
+ }
2512
+ else if (event.type === 'complete') {
2513
+ if (Number(event.discovery_tools_used) > 0) {
2514
+ liveOutcome.analysisToolsUsed = Math.max(liveOutcome.analysisToolsUsed, Number(event.discovery_tools_used));
2499
2515
  }
2516
+ taskDisplay.complete(1);
2500
2517
  }
2501
2518
  else if (event.type === 'executor_error') {
2502
2519
  const msg = typeof event.error === 'string' ? event.error : '';
@@ -2516,9 +2533,6 @@ export class ChatCommand {
2516
2533
  parsePlannerSummary(msg);
2517
2534
  }
2518
2535
  }
2519
- else if (event.type === 'complete') {
2520
- taskDisplay.complete(1);
2521
- }
2522
2536
  if (spinner)
2523
2537
  this.updateV3AgentSpinner(spinner, event);
2524
2538
  },
@@ -2535,6 +2549,9 @@ export class ChatCommand {
2535
2549
  const workspaceHasOutput = this.api.hasAgentWorkspaceOutput(workspaceContext);
2536
2550
  const changedFileCount = response.changedFiles ? Object.keys(response.changedFiles).length : 0;
2537
2551
  const requiresWorkspaceChanges = this.taskRequiresWorkspaceChanges(prompt);
2552
+ liveOutcome.changedFileCount = changedFileCount;
2553
+ liveOutcome.requiresWorkspaceChanges = requiresWorkspaceChanges;
2554
+ liveOutcome.workspaceHasOutput = workspaceHasOutput;
2538
2555
  const success = previewGate?.required === true
2539
2556
  ? (previewGate?.passed === true || workspaceHasOutput)
2540
2557
  : true;
@@ -2630,12 +2647,23 @@ export class ChatCommand {
2630
2647
  // executor errors and the spinner needs to clearly say "failed", not "✓".
2631
2648
  let selfHealStatus = null;
2632
2649
  let selfHealTool = null;
2633
- const executorSucceeded = !liveOutcome.executorFailed
2634
- && !liveOutcome.plannerError
2635
- && !liveOutcome.executorError
2636
- && (requiresWorkspaceChanges ? (workspaceHasOutput && changedFileCount > 0) : true);
2650
+ const runEvaluation = evaluateExecutorSuccess(liveOutcome);
2651
+ const executorSucceeded = runEvaluation.executorSucceeded;
2637
2652
  if (!executorSucceeded && requiresWorkspaceChanges && changedFileCount === 0 && !liveOutcome.executorError) {
2638
- liveOutcome.executorError = 'No workspace files were changed for a build/edit request.';
2653
+ const clientToolErrors = this.api.getClientToolErrors();
2654
+ const transportErrors = this.api.getLastChatTransportErrors();
2655
+ if (clientToolErrors.length > 0) {
2656
+ liveOutcome.executorError = clientToolErrors[0];
2657
+ }
2658
+ else if (workspaceHasOutput && changedFileCount === 0) {
2659
+ liveOutcome.executorError = 'Files may exist locally but were not tracked by the agent stream. Try /retry or check your workspace path.';
2660
+ }
2661
+ else {
2662
+ liveOutcome.executorError = 'No workspace files were changed for a build/edit request.';
2663
+ }
2664
+ if (transportErrors.length > 0 && !liveOutcome.plannerError) {
2665
+ liveOutcome.plannerError = transportErrors[0];
2666
+ }
2639
2667
  }
2640
2668
  if (executorSucceeded) {
2641
2669
  taskDisplay.complete(1);
@@ -2708,10 +2736,16 @@ export class ChatCommand {
2708
2736
  selfHealTool,
2709
2737
  plannerError: liveOutcome.plannerError ? sanitizeUserFacingErrorText(liveOutcome.plannerError) : null,
2710
2738
  executorError: liveOutcome.executorError ? sanitizeUserFacingErrorText(liveOutcome.executorError) : null,
2739
+ clientToolErrors: this.api.getClientToolErrors(),
2740
+ transportErrors: this.api.getLastChatTransportErrors(),
2741
+ workspacePath: workspacePath || null,
2742
+ workspaceSyncIssue: (!executorSucceeded && workspaceHasOutput && changedFileCount === 0)
2743
+ ? 'Local workspace has files but the agent did not confirm tracked changes.'
2744
+ : null,
2711
2745
  finishedAt: Date.now(),
2712
2746
  };
2713
2747
  if (!this.jsonOutput && !this.directPromptMode) {
2714
- this.printAgentRunSummary(this.lastAgentRunOutcome, executorSucceeded, changedFileCount);
2748
+ this.printAgentRunSummary(this.lastAgentRunOutcome, runEvaluation, changedFileCount);
2715
2749
  }
2716
2750
  this.messages.push({ role: 'assistant', content: response.content || 'V3 agent workflow completed.' });
2717
2751
  watcher?.stop();
@@ -2767,6 +2801,10 @@ export class ChatCommand {
2767
2801
  selfHealTool: null,
2768
2802
  plannerError: liveOutcome.plannerError ? sanitizeUserFacingErrorText(liveOutcome.plannerError) : null,
2769
2803
  executorError: liveOutcome.executorError ? sanitizeUserFacingErrorText(liveOutcome.executorError) : safeDetail || null,
2804
+ clientToolErrors: this.api.getClientToolErrors(),
2805
+ transportErrors: this.api.getLastChatTransportErrors(),
2806
+ workspacePath: workspacePath || null,
2807
+ workspaceSyncIssue: null,
2770
2808
  finishedAt: Date.now(),
2771
2809
  };
2772
2810
  if (this.jsonOutput) {
@@ -2782,7 +2820,19 @@ export class ChatCommand {
2782
2820
  }, null, 2));
2783
2821
  }
2784
2822
  else if (!this.directPromptMode) {
2785
- this.printAgentRunSummary(this.lastAgentRunOutcome, false, 0);
2823
+ const failedEval = evaluateExecutorSuccess({
2824
+ executorFailed: true,
2825
+ plannerError: liveOutcome.plannerError,
2826
+ executorError: liveOutcome.executorError || safeDetail,
2827
+ tasksTotal: liveOutcome.tasksTotal,
2828
+ tasksSucceeded: liveOutcome.tasksSucceeded,
2829
+ changedFileCount: 0,
2830
+ requiresWorkspaceChanges: this.taskRequiresWorkspaceChanges(prompt),
2831
+ workspaceHasOutput: this.api.hasAgentWorkspaceOutput(workspaceContext),
2832
+ failedTaskIds: liveOutcome.failedTaskIds,
2833
+ analysisToolsUsed: liveOutcome.analysisToolsUsed,
2834
+ });
2835
+ this.printAgentRunSummary(this.lastAgentRunOutcome, failedEval, 0);
2786
2836
  }
2787
2837
  return true;
2788
2838
  }
@@ -3079,7 +3129,8 @@ export class ChatCommand {
3079
3129
  * - User can answer "What do I type next?" without reading 200 scrollback lines.
3080
3130
  * - Never leave the spinners (`⟳`, `–`) ambiguous when the prompt returns.
3081
3131
  */
3082
- printAgentRunSummary(outcome, executorSucceeded, changedFileCount) {
3132
+ printAgentRunSummary(outcome, evaluation, changedFileCount) {
3133
+ const executorSucceeded = evaluation.executorSucceeded;
3083
3134
  const bar = chalk.gray('─'.repeat(63));
3084
3135
  const ok = chalk.green('✓');
3085
3136
  const warn = chalk.yellow('⚠');
@@ -3089,15 +3140,9 @@ export class ChatCommand {
3089
3140
  const hasTaskInfo = outcome.tasksTotal > 0 || failedList.length > 0 || unfinishedList.length > 0;
3090
3141
  console.log('');
3091
3142
  console.log(bar);
3092
- if (executorSucceeded && outcome.selfHealStatus !== 'partial' && outcome.selfHealStatus !== 'failed' && failedList.length === 0) {
3093
- console.log(`${ok} ${chalk.bold('Agent run finished')}${changedFileCount ? chalk.gray(` — ${changedFileCount} file${changedFileCount === 1 ? '' : 's'} changed`) : ''}`);
3094
- }
3095
- else if (executorSucceeded) {
3096
- console.log(`${warn} ${chalk.bold('Agent run finished with warnings')}${changedFileCount ? chalk.gray(` — ${changedFileCount} file${changedFileCount === 1 ? '' : 's'} changed`) : ''}`);
3097
- }
3098
- else {
3099
- console.log(`${bad} ${chalk.bold('Agent run did not complete')}${changedFileCount ? chalk.gray(` — ${changedFileCount} file${changedFileCount === 1 ? '' : 's'} changed`) : ''}`);
3100
- }
3143
+ const headlineIcon = evaluation.uiTheme === 'success' ? ok : evaluation.uiTheme === 'warning' ? warn : bad;
3144
+ const headlineText = evaluation.statusHeadline.replace(/^[✓⚠✕]\s*/, '');
3145
+ console.log(`${headlineIcon} ${chalk.bold(headlineText)}${changedFileCount ? chalk.gray(` — ${changedFileCount} file${changedFileCount === 1 ? '' : 's'} changed`) : ''}`);
3101
3146
  if (hasTaskInfo) {
3102
3147
  const succ = outcome.tasksTotal > 0 ? `${outcome.tasksSucceeded}/${outcome.tasksTotal}` : `${outcome.tasksSucceeded}`;
3103
3148
  console.log(chalk.gray(` Tasks completed: ${succ}`));
@@ -3129,7 +3174,29 @@ export class ChatCommand {
3129
3174
  console.log(chalk.gray(' Planner: ') + chalk.red(outcome.plannerError.slice(0, 140)));
3130
3175
  }
3131
3176
  if (outcome.executorError) {
3132
- console.log(chalk.gray(' Executor: ') + chalk.red(outcome.executorError.slice(0, 140)));
3177
+ console.log(chalk.gray(' Executor: ') + chalk.red(outcome.executorError.slice(0, 240)));
3178
+ }
3179
+ const hasDiagnostics = outcome.clientToolErrors.length > 0
3180
+ || outcome.transportErrors.length > 0
3181
+ || outcome.workspaceSyncIssue;
3182
+ if (hasDiagnostics) {
3183
+ console.log('');
3184
+ console.log(chalk.gray('Why this run struggled:'));
3185
+ if (outcome.workspacePath) {
3186
+ console.log(' ' + chalk.white(`Workspace: ${outcome.workspacePath}`));
3187
+ }
3188
+ for (const err of outcome.clientToolErrors.slice(0, 3)) {
3189
+ console.log(' ' + chalk.yellow('Local tools: ') + chalk.white(err.slice(0, 220)));
3190
+ }
3191
+ for (const err of outcome.transportErrors.slice(0, 2)) {
3192
+ console.log(' ' + chalk.yellow('Connectivity: ') + chalk.white(err.slice(0, 220)));
3193
+ }
3194
+ if (outcome.workspaceSyncIssue) {
3195
+ console.log(' ' + chalk.yellow('Sync: ') + chalk.white(outcome.workspaceSyncIssue));
3196
+ }
3197
+ if (outcome.clientToolErrors.some((e) => /no pending client tool call|rejected/i.test(e))) {
3198
+ console.log(' ' + chalk.gray('The agent tried to use your local machine for file tools, but the server timed out waiting. /retry usually works after the SSE bridge fix.'));
3199
+ }
3133
3200
  }
3134
3201
  console.log('');
3135
3202
  console.log(chalk.gray('Result:'));
@@ -3244,12 +3311,19 @@ export class ChatCommand {
3244
3311
  return;
3245
3312
  }
3246
3313
  const o = this.lastAgentRunOutcome;
3247
- const executorSucceeded = o.failedTaskIds.length === 0
3248
- && o.unfinishedTaskIds.length === 0
3249
- && !o.plannerError
3250
- && !o.executorError
3251
- && o.hasOutput;
3252
- this.printAgentRunSummary(o, executorSucceeded, 0);
3314
+ const statusEval = evaluateExecutorSuccess({
3315
+ executorFailed: o.failedTaskIds.length > 0,
3316
+ plannerError: o.plannerError,
3317
+ executorError: o.executorError,
3318
+ tasksTotal: o.tasksTotal,
3319
+ tasksSucceeded: o.tasksSucceeded,
3320
+ changedFileCount: 0,
3321
+ requiresWorkspaceChanges: this.taskRequiresWorkspaceChanges(o.prompt),
3322
+ workspaceHasOutput: o.hasOutput,
3323
+ failedTaskIds: new Set(o.failedTaskIds),
3324
+ analysisToolsUsed: 0,
3325
+ });
3326
+ this.printAgentRunSummary(o, statusEval, 0);
3253
3327
  }
3254
3328
  showContext() {
3255
3329
  if (!this.currentSession) {
package/dist/index.js CHANGED
@@ -35,6 +35,7 @@ import { HistoryCommand } from './commands/history.js';
35
35
  import { ReplayCommand } from './commands/replay.js';
36
36
  import { ForkCommand } from './commands/fork.js';
37
37
  import { CancelCommand } from './commands/cancel.js';
38
+ import { BackgroundCommand } from './commands/background.js';
38
39
  import { SecurityCommand } from './commands/security.js';
39
40
  import { Config } from './utils/config.js';
40
41
  import { Logger, CH } from './utils/logger.js';
@@ -333,7 +334,7 @@ function isAuthProtectedCommand(command) {
333
334
  'agent', 'a', 'operator', 'op',
334
335
  'edit', 'e', 'generate', 'g', 'explain', 'x', 'fix', 'f', 'review', 'r',
335
336
  'workflow', 'flow', 'hub', 'marketplace', 'deploy', 'host', 'preview', 'device',
336
- 'legion', 'history', 'runs', 'replay', 'fork', 'cancel',
337
+ 'legion', 'history', 'runs', 'replay', 'fork', 'cancel', 'background', 'bg',
337
338
  'repo', 'repository',
338
339
  ]);
339
340
  return protectedCommands.has(command);
@@ -489,7 +490,7 @@ export async function main(args) {
489
490
  const isLegionCortexRequest = invokedBinaryName === 'vigthoria' && argv[2] === 'legion' && argv.includes('--cortex');
490
491
  if (invokedBinaryName === 'vigthoria-chat') {
491
492
  const knownCommands = new Set([
492
- 'chat', 'chat-resume', 'agent', 'edit', 'generate', 'explain', 'fix', 'review', 'cancel',
493
+ 'chat', 'chat-resume', 'agent', 'edit', 'generate', 'explain', 'fix', 'review', 'cancel', 'background', 'bg',
493
494
  'hub', 'repo', 'deploy', 'operator', 'workflow', 'flow', 'device', 'security', 'vsec', 'login', 'logout', 'status', 'config', 'update',
494
495
  '--help', '-h', '--version', '-V', 'help', 'version',
495
496
  ]);
@@ -1088,6 +1089,58 @@ Examples:
1088
1089
  repoCommand.action(() => {
1089
1090
  repoCommand.outputHelp();
1090
1091
  });
1092
+ // ==================== BACKGROUND AGENT JOBS ====================
1093
+ const backgroundCommand = program
1094
+ .command('background')
1095
+ .alias('bg')
1096
+ .description('Run and manage detached Vigthoria Agent jobs');
1097
+ backgroundCommand
1098
+ .command('start <prompt...>')
1099
+ .description('Start an Agent job in the background and return immediately')
1100
+ .option('--workspace <path>', 'Local workspace path', process.cwd())
1101
+ .option('--json', 'Emit JSON output', false)
1102
+ .action(async (prompt, options) => {
1103
+ const bg = new BackgroundCommand(config, logger);
1104
+ await bg.start(prompt, options);
1105
+ });
1106
+ backgroundCommand
1107
+ .command('list')
1108
+ .alias('ls')
1109
+ .description('List recent background jobs')
1110
+ .option('--limit <n>', 'Maximum jobs to show', '20')
1111
+ .option('--json', 'Emit JSON output', false)
1112
+ .action(async (options) => {
1113
+ const bg = new BackgroundCommand(config, logger);
1114
+ await bg.list({ ...options, limit: Number(options.limit) || 20 });
1115
+ });
1116
+ backgroundCommand
1117
+ .command('status <jobId>')
1118
+ .description('Show background job status')
1119
+ .option('--json', 'Emit JSON output', false)
1120
+ .action(async (jobId, options) => {
1121
+ const bg = new BackgroundCommand(config, logger);
1122
+ await bg.status(jobId, options);
1123
+ });
1124
+ backgroundCommand
1125
+ .command('apply <jobId>')
1126
+ .description('Apply completed background job file changes to the local workspace')
1127
+ .option('--workspace <path>', 'Local workspace path', process.cwd())
1128
+ .option('--json', 'Emit JSON output', false)
1129
+ .action(async (jobId, options) => {
1130
+ const bg = new BackgroundCommand(config, logger);
1131
+ await bg.apply(jobId, options);
1132
+ });
1133
+ backgroundCommand
1134
+ .command('cancel <jobId>')
1135
+ .description('Cancel a running background job')
1136
+ .option('--json', 'Emit JSON output', false)
1137
+ .action(async (jobId, options) => {
1138
+ const bg = new BackgroundCommand(config, logger);
1139
+ await bg.cancel(jobId, options);
1140
+ });
1141
+ backgroundCommand.action(() => {
1142
+ backgroundCommand.outputHelp();
1143
+ });
1091
1144
  // ==================== DEPLOY COMMANDS ====================
1092
1145
  // Deploy command - Host projects on Vigthoria
1093
1146
  const deployCommand = program
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Task-and-quality-validated success evaluation for V3 agent runs.
3
+ * Decouples CLI verdict from raw file-change counts (1.11.0+).
4
+ */
5
+ export interface LiveOutcome {
6
+ executorFailed: boolean;
7
+ plannerError: string | null;
8
+ executorError: string | null;
9
+ tasksTotal: number;
10
+ tasksSucceeded: number;
11
+ changedFileCount: number;
12
+ requiresWorkspaceChanges: boolean;
13
+ workspaceHasOutput: boolean;
14
+ failedTaskIds: Set<string>;
15
+ analysisToolsUsed: number;
16
+ }
17
+ export interface TaskSummaryEvent {
18
+ status?: string;
19
+ task_id?: string;
20
+ id?: string;
21
+ }
22
+ export interface RunEvaluation {
23
+ executorSucceeded: boolean;
24
+ statusHeadline: string;
25
+ uiTheme: 'success' | 'warning' | 'error';
26
+ }
27
+ export declare function createLiveOutcome(): LiveOutcome;
28
+ /** Process executor_complete / task summary events from the V3 SSE stream. */
29
+ export declare function handleTaskEvent(summary: TaskSummaryEvent, liveOutcome: LiveOutcome): void;
30
+ /** Track read/discovery tool usage for analysis-turn accountability. */
31
+ export declare function noteAnalysisToolUse(toolName: string, liveOutcome: LiveOutcome): void;
32
+ /**
33
+ * Compute the real CLI success verdict.
34
+ * When tasks were planned, ALL must succeed — file changes alone never imply victory.
35
+ */
36
+ export declare function evaluateExecutorSuccess(liveOutcome: LiveOutcome): RunEvaluation;