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.
- package/dist/commands/background.d.ts +19 -0
- package/dist/commands/background.js +160 -0
- package/dist/commands/chat.js +110 -36
- package/dist/index.js +55 -2
- package/dist/utils/agentRunOutcome.d.ts +36 -0
- package/dist/utils/agentRunOutcome.js +123 -0
- package/dist/utils/api.d.ts +32 -0
- package/dist/utils/api.js +657 -160
- package/package.json +1 -1
|
@@ -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
|
+
}
|
package/dist/commands/chat.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
2500
|
+
handleTaskEvent(summary, liveOutcome);
|
|
2490
2501
|
if (summary.status === 'failed') {
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2502
|
+
const err = typeof summary.error === 'string' ? summary.error.trim() : '';
|
|
2503
|
+
if (err && !liveOutcome.executorError)
|
|
2504
|
+
liveOutcome.executorError = err;
|
|
2494
2505
|
}
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
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
|
|
2634
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
3093
|
-
|
|
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,
|
|
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
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
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;
|