vigthoria-cli 1.11.19 → 1.11.25

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.
@@ -52,6 +52,7 @@ export declare class ChatCommand {
52
52
  private v3StreamedAnswerDisplayed;
53
53
  private v3SeenToolCalls;
54
54
  private v3SeenToolResults;
55
+ private v3StartSeen;
55
56
  private lastAgentRoute;
56
57
  private lastAgentRunOutcome;
57
58
  private isJwtExpirationError;
@@ -77,6 +77,7 @@ export class ChatCommand {
77
77
  v3StreamedAnswerDisplayed = false;
78
78
  v3SeenToolCalls = new Set();
79
79
  v3SeenToolResults = new Set();
80
+ v3StartSeen = false;
80
81
  lastAgentRoute = null;
81
82
  // Last completed Agent run — used by /retry, /continue, and the final summary block.
82
83
  lastAgentRunOutcome = null;
@@ -299,7 +300,7 @@ export class ChatCommand {
299
300
  resolveAgentExecutionPolicy(prompt) {
300
301
  const explicitModel = this.modelExplicitlySelected;
301
302
  const heavyTask = this.config.isComplexTask(prompt);
302
- const cloudEligible = this.config.hasCloudAccess() && this.hasOperatorAccess();
303
+ const cloudEligible = this.config.hasCloudAccess();
303
304
  const requiresV3Workflow = this.shouldRequireV3AgentWorkflow(prompt);
304
305
  if (explicitModel) {
305
306
  return {
@@ -1282,8 +1283,11 @@ export class ChatCommand {
1282
1283
  return;
1283
1284
  }
1284
1285
  if (event.type === 'start') {
1285
- this.v3IterationCount = 0;
1286
- this.v3ToolCallCount = 0;
1286
+ if (this.v3StartSeen) {
1287
+ spinner.text = 'Working...';
1288
+ return;
1289
+ }
1290
+ this.v3StartSeen = true;
1287
1291
  if (spinner.isSpinning)
1288
1292
  spinner.stop();
1289
1293
  process.stderr.write(chalk.cyan(' [Start] ') + 'Agent initialized\n');
@@ -2793,6 +2797,7 @@ export class ChatCommand {
2793
2797
  console.log(chalk.gray('━━━ ROUTING DECISION ━━━'));
2794
2798
  console.log(chalk.gray(`Reason: ${routingPolicy.routeReason}`));
2795
2799
  console.log(chalk.gray(`Model: ${routingPolicy.selectedModel}`));
2800
+ console.log(chalk.gray('Remote Engine Session: true'));
2796
2801
  console.log(chalk.gray(`Cloud Eligible: ${routingPolicy.cloudEligible}`));
2797
2802
  console.log(chalk.gray(`Cloud Selected: ${routingPolicy.cloudSelected}`));
2798
2803
  if (routingPolicy.heavyTask) {
@@ -2808,6 +2813,7 @@ export class ChatCommand {
2808
2813
  this.v3StreamingStarted = false;
2809
2814
  this.v3StreamedAnswerBuffer = '';
2810
2815
  this.v3StreamedAnswerDisplayed = false;
2816
+ this.v3StartSeen = false;
2811
2817
  this.v3SeenToolCalls.clear();
2812
2818
  this.v3SeenToolResults.clear();
2813
2819
  emitDeckEvent({ type: 'agent_start' });
@@ -3363,6 +3369,7 @@ export class ChatCommand {
3363
3369
  this.v3ToolCallCount = 0;
3364
3370
  this.v3LastActivity = Date.now();
3365
3371
  this.v3StreamingStarted = false;
3372
+ this.v3StartSeen = false;
3366
3373
  try {
3367
3374
  const retryTaskType = this.inferAgentTaskType(rawPrompt);
3368
3375
  const retryResponse = await this.api.runV3AgentWorkflow(executionPrompt, {
@@ -11,6 +11,7 @@ import { GenerateCommand } from './generate.js';
11
11
  import { HistoryCommand } from './history.js';
12
12
  import { HubCommand } from './hub.js';
13
13
  import { LegionCommand } from './legion.js';
14
+ import { MusicCommand } from './music.js';
14
15
  import { PreviewCommand } from './preview.js';
15
16
  import { ReplayCommand } from './replay.js';
16
17
  import { RepoCommand } from './repo.js';
@@ -96,6 +97,11 @@ const commandRegistry = [
96
97
  name: 'hub',
97
98
  handler: createClassRegistrar(HubCommand),
98
99
  },
100
+ {
101
+ name: 'music',
102
+ aliases: ['audio'],
103
+ handler: createClassRegistrar(MusicCommand),
104
+ },
99
105
  {
100
106
  name: 'repo',
101
107
  handler: createClassRegistrar(RepoCommand),
@@ -0,0 +1,31 @@
1
+ import { Config } from '../utils/config.js';
2
+ import { Logger } from '../utils/logger.js';
3
+ type MusicGenerateOptions = {
4
+ prompt?: string;
5
+ lyrics?: string;
6
+ duration?: string | number;
7
+ genre?: string;
8
+ language?: string;
9
+ voiceType?: string;
10
+ styleTags?: string;
11
+ voice?: string;
12
+ wait?: boolean;
13
+ pollInterval?: string | number;
14
+ json?: boolean;
15
+ };
16
+ type MusicStatusOptions = {
17
+ json?: boolean;
18
+ };
19
+ export declare class MusicCommand {
20
+ private config;
21
+ private logger;
22
+ constructor(config: Config, logger: Logger);
23
+ private apiBase;
24
+ private authHeaders;
25
+ private request;
26
+ private uploadVoiceReference;
27
+ generate(promptArg: string | undefined, options: MusicGenerateOptions): Promise<void>;
28
+ status(taskId: string, options?: MusicStatusOptions): Promise<void>;
29
+ private waitForStatus;
30
+ }
31
+ export {};
@@ -0,0 +1,137 @@
1
+ import chalk from 'chalk';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ export class MusicCommand {
5
+ config;
6
+ logger;
7
+ constructor(config, logger) {
8
+ this.config = config;
9
+ this.logger = logger;
10
+ }
11
+ apiBase() {
12
+ const configured = String(this.config.getAll().musicApiUrl || '').trim();
13
+ return (process.env.VIGTHORIA_MUSIC_API_URL || configured || 'https://music.vigthoria.io').replace(/\/+$/, '');
14
+ }
15
+ authHeaders() {
16
+ const token = this.config.get('authToken');
17
+ return token ? { Authorization: `Bearer ${token}` } : {};
18
+ }
19
+ async request(endpoint, init = {}) {
20
+ const response = await fetch(`${this.apiBase()}${endpoint}`, {
21
+ ...init,
22
+ headers: {
23
+ ...this.authHeaders(),
24
+ ...init.headers,
25
+ },
26
+ });
27
+ const text = await response.text();
28
+ let data = {};
29
+ try {
30
+ data = text ? JSON.parse(text) : {};
31
+ }
32
+ catch {
33
+ data = { message: text };
34
+ }
35
+ if (!response.ok) {
36
+ throw new Error(String(data.error || data.message || `Music API request failed: ${response.status}`));
37
+ }
38
+ return data;
39
+ }
40
+ async uploadVoiceReference(filePath) {
41
+ const absolutePath = path.resolve(filePath);
42
+ if (!fs.existsSync(absolutePath) || !fs.statSync(absolutePath).isFile()) {
43
+ throw new Error(`Voice reference file not found: ${filePath}`);
44
+ }
45
+ const form = new FormData();
46
+ const blob = new Blob([fs.readFileSync(absolutePath)]);
47
+ form.append('audio', blob, path.basename(absolutePath));
48
+ const data = await this.request('/api/voice/upload', {
49
+ method: 'POST',
50
+ body: form,
51
+ });
52
+ const referencePath = String(data.reference_audio_path || data.path || data.file_path || '').trim();
53
+ if (!referencePath) {
54
+ throw new Error('Voice upload succeeded but did not return a reference_audio_path.');
55
+ }
56
+ return referencePath;
57
+ }
58
+ async generate(promptArg, options) {
59
+ const prompt = String(promptArg || options.prompt || '').trim();
60
+ if (!prompt) {
61
+ throw new Error('Music generation requires a prompt.');
62
+ }
63
+ let referenceAudioPath = '';
64
+ if (options.voice) {
65
+ referenceAudioPath = await this.uploadVoiceReference(options.voice);
66
+ }
67
+ const duration = Math.max(1, Number(options.duration || 120));
68
+ const payload = {
69
+ prompt,
70
+ language: options.language || 'en',
71
+ voice_type: options.voiceType || 'female',
72
+ genre: options.genre || 'pop',
73
+ style_tags: options.styleTags || '',
74
+ lyrics: options.lyrics || undefined,
75
+ duration,
76
+ engine: 'v4',
77
+ is_preview: false,
78
+ };
79
+ if (referenceAudioPath) {
80
+ payload.voice_cloning = {
81
+ enabled: true,
82
+ reference_audio_path: referenceAudioPath,
83
+ };
84
+ }
85
+ const data = await this.request('/api/quick/generate', {
86
+ method: 'POST',
87
+ headers: { 'Content-Type': 'application/json' },
88
+ body: JSON.stringify(payload),
89
+ });
90
+ const taskId = String(data.task_id || data.session_id || '').trim();
91
+ if (options.json) {
92
+ console.log(JSON.stringify({ ...data, task_id: taskId || data.task_id }, null, 2));
93
+ }
94
+ else {
95
+ console.log(chalk.green('Music generation queued.'));
96
+ console.log(chalk.cyan('Task ID: ') + (taskId || '(pending)'));
97
+ if (data.queue_position)
98
+ console.log(chalk.gray(`Queue position: ${data.queue_position}`));
99
+ if (data.estimated_wait_seconds)
100
+ console.log(chalk.gray(`Estimated wait: ${data.estimated_wait_seconds}s`));
101
+ }
102
+ if (options.wait && taskId) {
103
+ await this.waitForStatus(taskId, Number(options.pollInterval || 5), Boolean(options.json));
104
+ }
105
+ }
106
+ async status(taskId, options = {}) {
107
+ const data = await this.request(`/api/status/${encodeURIComponent(taskId)}`);
108
+ if (options.json) {
109
+ console.log(JSON.stringify(data, null, 2));
110
+ return;
111
+ }
112
+ console.log(chalk.cyan(`Music task ${taskId}`));
113
+ console.log(`Status: ${data.status || 'unknown'}`);
114
+ if (data.progress !== undefined)
115
+ console.log(`Progress: ${data.progress}%`);
116
+ const downloadUrl = data.download_url || data.audio_url || data.file_url || data.result_url;
117
+ if (downloadUrl)
118
+ console.log(`Download: ${downloadUrl}`);
119
+ }
120
+ async waitForStatus(taskId, intervalSeconds, json) {
121
+ const delay = Math.max(1, intervalSeconds) * 1000;
122
+ for (;;) {
123
+ await new Promise((resolve) => setTimeout(resolve, delay));
124
+ const data = await this.request(`/api/status/${encodeURIComponent(taskId)}`);
125
+ const status = String(data.status || '').toLowerCase();
126
+ if (!json) {
127
+ const progress = data.progress !== undefined ? ` (${data.progress}%)` : '';
128
+ console.log(chalk.gray(`Status: ${status || 'unknown'}${progress}`));
129
+ }
130
+ if (['completed', 'complete', 'finished', 'failed', 'error', 'cancelled'].includes(status)) {
131
+ if (json)
132
+ console.log(JSON.stringify(data, null, 2));
133
+ return;
134
+ }
135
+ }
136
+ }
137
+ }
package/dist/index.js CHANGED
@@ -24,6 +24,7 @@ import { handleLogin, handleLogout, statusAction } from './commands/auth.js';
24
24
  import { ConfigCommand } from './commands/config.js';
25
25
  import { ReviewCommand } from './commands/review.js';
26
26
  import { HubCommand } from './commands/hub.js';
27
+ import { MusicCommand } from './commands/music.js';
27
28
  import { RepoCommand } from './commands/repo.js';
28
29
  import { DeployCommand } from './commands/deploy.js';
29
30
  import { BridgeCommand } from './commands/bridge.js';
@@ -207,7 +208,7 @@ async function fetchNpmLatestVersion() {
207
208
  }
208
209
  }
209
210
  const VERSION = getVersion();
210
- const VIGTHORIA_DEFAULT_MANIFEST_URL = process.env.VIGTHORIA_UPDATE_MANIFEST_URL || "https://coder.vigthoria.io/releases/manifest.json";
211
+ const VIGTHORIA_DEFAULT_MANIFEST_URL = process.env.VIGTHORIA_UPDATE_MANIFEST_URL || "https://extension.vigthoria.io/downloads/manifest.json";
211
212
  function resolveManifestEntry(manifest, channel) {
212
213
  const normalized = String(channel || 'stable').trim() || 'stable';
213
214
  if (manifest.channels && manifest.channels[normalized]) {
@@ -1043,6 +1044,36 @@ export async function main(args) {
1043
1044
  const hub = new HubCommand(config, logger);
1044
1045
  await hub.discover();
1045
1046
  });
1047
+ const musicCommand = program
1048
+ .command('music')
1049
+ .alias('audio')
1050
+ .description('Generate songs with Vigthoria Music AI / ACE-Step');
1051
+ musicCommand
1052
+ .command('generate [prompt]')
1053
+ .alias('g')
1054
+ .description('Queue full-song generation and return a task id')
1055
+ .option('--lyrics <text>', 'Provide fixed lyrics instead of auto-writing lyrics')
1056
+ .option('--duration <seconds>', 'Song duration in seconds', '120')
1057
+ .option('--genre <genre>', 'Music genre', 'pop')
1058
+ .option('--language <code>', 'Vocal language', 'en')
1059
+ .option('--voice-type <type>', 'Voice type (female, male, mixed)', 'female')
1060
+ .option('--style-tags <tags>', 'Additional style tags')
1061
+ .option('--voice <path>', 'Upload a local voice reference for cloning')
1062
+ .option('--wait', 'Poll until the task reaches a terminal state', false)
1063
+ .option('--poll-interval <seconds>', 'Polling interval when --wait is used', '5')
1064
+ .option('--json', 'Emit machine-readable JSON output', false)
1065
+ .action(async (prompt, options) => {
1066
+ const music = new MusicCommand(config, logger);
1067
+ await music.generate(prompt, options);
1068
+ });
1069
+ musicCommand
1070
+ .command('status <taskId>')
1071
+ .description('Check a queued music generation task')
1072
+ .option('--json', 'Emit machine-readable JSON output', false)
1073
+ .action(async (taskId, options) => {
1074
+ const music = new MusicCommand(config, logger);
1075
+ await music.status(taskId, options);
1076
+ });
1046
1077
  // ==================== REPO COMMANDS ====================
1047
1078
  // Repo command - Push/Pull projects to/from Vigthoria Repository
1048
1079
  const repoCommand = program
@@ -1514,7 +1545,7 @@ Examples:
1514
1545
  program
1515
1546
  .command('update')
1516
1547
  .alias('upgrade')
1517
- .description('Check for updates and upgrade Vigthoria CLI. Default manifest: https://coder.vigthoria.io/releases/manifest.json')
1548
+ .description('Check for updates and upgrade Vigthoria CLI. Default manifest: https://extension.vigthoria.io/downloads/manifest.json')
1518
1549
  .option('-c, --check', 'Only check for updates, don\'t install')
1519
1550
  .option('-f, --from <target>', 'Install update from npm spec, local .tgz path, or URL')
1520
1551
  .option('-m, --manifest <url>', 'Update manifest URL for server-driven releases')
@@ -33,6 +33,7 @@ export interface RunEvaluation {
33
33
  uiTheme: 'success' | 'warning' | 'error';
34
34
  }
35
35
  export declare function createLiveOutcome(): LiveOutcome;
36
+ export declare function isExecutorTimeoutFailure(liveOutcome: LiveOutcome): boolean;
36
37
  /** Tool-trace stubs like `[read_file] "index.html"` or `<read_file>…</read_file>` — not a user-facing answer. */
37
38
  export declare function isToolEvidenceStubAnswer(text: string): boolean;
38
39
  /** True when text looks like a real answer, not an executor/system placeholder. */
@@ -30,6 +30,10 @@ const LIST_ONLY_DISCOVERY_TOOLS = new Set([
30
30
  'dir',
31
31
  ]);
32
32
  const GENERIC_SUMMARY_RE = /^(completed the requested analysis|reviewed the workspace without writing changes|task completed|done|finished|analysis completed)([.\s]|$)/i;
33
+ export function isExecutorTimeoutFailure(liveOutcome) {
34
+ const errorText = String(liveOutcome.executorError || '').trim();
35
+ return /executor llm call timed out|fast fallback also failed|executor model timed out/i.test(errorText);
36
+ }
33
37
  function looksLikeRawV3EventPayload(text) {
34
38
  const trimmed = String(text || '').trim();
35
39
  if (!/^[{[]/.test(trimmed))
@@ -164,6 +168,15 @@ export function evaluateExecutorSuccess(liveOutcome) {
164
168
  || Boolean(liveOutcome.streamAborted);
165
169
  if (liveOutcome.streamAborted && liveOutcome.tasksTotal > 0) {
166
170
  const partial = liveOutcome.tasksSucceeded > 0;
171
+ if (isExecutorTimeoutFailure(liveOutcome)) {
172
+ return {
173
+ executorSucceeded: false,
174
+ statusHeadline: partial
175
+ ? `⚠ ${liveOutcome.tasksSucceeded}/${liveOutcome.tasksTotal} tasks completed — executor model timed out`
176
+ : `✕ Executor model timed out — 0/${liveOutcome.tasksTotal} tasks completed`,
177
+ uiTheme: 'warning',
178
+ };
179
+ }
167
180
  return {
168
181
  executorSucceeded: false,
169
182
  statusHeadline: partial
@@ -172,6 +185,16 @@ export function evaluateExecutorSuccess(liveOutcome) {
172
185
  uiTheme: 'warning',
173
186
  };
174
187
  }
188
+ if (liveOutcome.tasksTotal > 0 && isExecutorTimeoutFailure(liveOutcome)) {
189
+ const partial = liveOutcome.tasksSucceeded > 0;
190
+ return {
191
+ executorSucceeded: false,
192
+ statusHeadline: partial
193
+ ? `⚠ ${liveOutcome.tasksSucceeded}/${liveOutcome.tasksTotal} tasks completed — executor model timed out`
194
+ : `✕ Executor model timed out — 0/${liveOutcome.tasksTotal} tasks completed`,
195
+ uiTheme: 'error',
196
+ };
197
+ }
175
198
  if (hasFatalError && liveOutcome.tasksTotal === 0) {
176
199
  return {
177
200
  executorSucceeded: false,
@@ -342,6 +342,7 @@ export declare class APIClient {
342
342
  private materializeEmergencySaaSWorkspace;
343
343
  private ensureExecutionContext;
344
344
  bindExecutionContext(context?: Record<string, any>): Promise<Record<string, any>>;
345
+ private isServerManagedTempWorkspacePath;
345
346
  private resolveAgentTargetPath;
346
347
  private isLikelyWindowsPath;
347
348
  private resolveServerBindableWorkspacePath;
package/dist/utils/api.js CHANGED
@@ -2236,6 +2236,10 @@ menu {
2236
2236
  }
2237
2237
  return {
2238
2238
  ...executionContext,
2239
+ localWorkspacePath: localWorkspacePath || executionContext.localWorkspacePath || null,
2240
+ targetPath: localWorkspacePath || executionContext.targetPath || executionContext.projectPath || executionContext.workspacePath || null,
2241
+ projectPath: localWorkspacePath || executionContext.projectPath || executionContext.targetPath || executionContext.workspacePath || null,
2242
+ workspacePath: localWorkspacePath || executionContext.workspacePath || executionContext.projectPath || executionContext.targetPath || null,
2239
2243
  mcpContextId: binding.mcpContextId,
2240
2244
  mcpContextBackendUrl: binding.backendUrl,
2241
2245
  };
@@ -2276,10 +2280,38 @@ menu {
2276
2280
  this.logger.debug(`Failed to bind MCP context via ${baseUrl}:`, error.message);
2277
2281
  }
2278
2282
  }
2279
- return executionContext;
2283
+ return {
2284
+ ...executionContext,
2285
+ localWorkspacePath: localWorkspacePath || executionContext.localWorkspacePath || null,
2286
+ targetPath: localWorkspacePath || executionContext.targetPath || executionContext.projectPath || executionContext.workspacePath || null,
2287
+ projectPath: localWorkspacePath || executionContext.projectPath || executionContext.targetPath || executionContext.workspacePath || null,
2288
+ workspacePath: localWorkspacePath || executionContext.workspacePath || executionContext.projectPath || executionContext.targetPath || null,
2289
+ };
2290
+ }
2291
+ isServerManagedTempWorkspacePath(pathValue) {
2292
+ const normalized = String(pathValue || '').replace(/\\/g, '/');
2293
+ return /(?:^|\/)v3-temp\/vig-remote-/i.test(normalized)
2294
+ || /(?:^|\/)tmp\/vig-remote(?:-server)?-/i.test(normalized)
2295
+ || /(?:^|\/)var\/tmp\/vig-remote(?:-server)?-/i.test(normalized);
2280
2296
  }
2281
2297
  resolveAgentTargetPath(context = {}) {
2282
- return context.targetPath || context.projectPath || context.workspacePath || context.projectRoot || process.cwd();
2298
+ const explicitLocal = String(context.localWorkspacePath || '').trim();
2299
+ if (explicitLocal
2300
+ && !explicitLocal.startsWith('vigthoria://')
2301
+ && (this.isLikelyWindowsPath(explicitLocal) || path.isAbsolute(explicitLocal))) {
2302
+ return explicitLocal;
2303
+ }
2304
+ const candidate = context.targetPath || context.projectPath || context.workspacePath || context.projectRoot || process.cwd();
2305
+ if (this.isServerManagedTempWorkspacePath(candidate) && explicitLocal && !explicitLocal.startsWith('vigthoria://')) {
2306
+ return explicitLocal;
2307
+ }
2308
+ if (this.isServerManagedTempWorkspacePath(candidate)) {
2309
+ const preserved = String(context.__localWorkspacePath || context.clientWorkspacePath || '').trim();
2310
+ if (preserved && !preserved.startsWith('vigthoria://')) {
2311
+ return preserved;
2312
+ }
2313
+ }
2314
+ return candidate;
2283
2315
  }
2284
2316
  isLikelyWindowsPath(pathValue) {
2285
2317
  return /^[a-zA-Z]:[\\/]/.test(pathValue) || /^\\\\/.test(pathValue);
@@ -2955,7 +2987,9 @@ menu {
2955
2987
  return;
2956
2988
  }
2957
2989
  }
2958
- const targets = expectedFiles.length > 0 ? expectedFiles : Object.keys(streamedFiles);
2990
+ const targets = expectedFiles.length > 0
2991
+ ? Array.from(new Set([...expectedFiles, ...Object.keys(streamedFiles)]))
2992
+ : Object.keys(streamedFiles);
2959
2993
  for (const targetPath of targets) {
2960
2994
  const relativePath = this.normalizeAgentWorkspaceRelativePath(targetPath, rootPath);
2961
2995
  if (!relativePath) {
@@ -3521,6 +3555,7 @@ document.addEventListener('DOMContentLoaded', () => {
3521
3555
  let buffer = '';
3522
3556
  const events = [];
3523
3557
  let final = null;
3558
+ let streamDone = false;
3524
3559
  let contextId = response.headers.get('x-context-id') || String(context.contextId || '').trim() || null;
3525
3560
  let serverWorkspaceRoot = null;
3526
3561
  const streamedFiles = {};
@@ -3609,9 +3644,13 @@ document.addEventListener('DOMContentLoaded', () => {
3609
3644
  }
3610
3645
  }
3611
3646
  const payload = dataLines.join('\n').trim();
3612
- if (!payload || payload === '[DONE]') {
3647
+ if (!payload) {
3613
3648
  continue;
3614
3649
  }
3650
+ if (payload === '[DONE]') {
3651
+ streamDone = true;
3652
+ break;
3653
+ }
3615
3654
  let event;
3616
3655
  try {
3617
3656
  event = JSON.parse(payload);
@@ -3702,20 +3741,9 @@ document.addEventListener('DOMContentLoaded', () => {
3702
3741
  if (event.type === 'complete' || event.type === 'message') {
3703
3742
  final = event;
3704
3743
  }
3705
- // Exit stream early on complete — agent is done; server-side teardown
3706
- // can hold the connection open for many seconds otherwise.
3707
- if (event.type === 'complete') {
3708
- await Promise.allSettled([...this.pendingV3ClientToolTasks]);
3709
- reader.cancel().catch(() => { });
3710
- return {
3711
- task_id: events.find((entry) => entry && entry.task_id)?.task_id || null,
3712
- context_id: contextId,
3713
- result: final,
3714
- events,
3715
- files: streamedFiles,
3716
- serverWorkspaceRoot: serverWorkspaceRoot || null,
3717
- };
3718
- }
3744
+ }
3745
+ if (streamDone) {
3746
+ break;
3719
3747
  }
3720
3748
  }
3721
3749
  await Promise.allSettled([...this.pendingV3ClientToolTasks]);
@@ -5991,9 +6019,23 @@ document.addEventListener('DOMContentLoaded', () => {
5991
6019
  }
5992
6020
  async attemptV3ServiceRecovery(reason = '', _options = {}) {
5993
6021
  const safeReason = sanitizeUserFacingErrorText(reason || 'unknown failure');
6022
+ const isTransient = /timed out|aborted|econnreset|fetch failed|network|503|502|504|socket hang up/i.test(safeReason);
6023
+ if (!isTransient) {
6024
+ return {
6025
+ recovered: false,
6026
+ message: safeReason ? `Recovery unavailable: ${safeReason}` : 'Recovery unavailable',
6027
+ };
6028
+ }
6029
+ const health = await this.runV3HealthCheck();
6030
+ if (!health.healthy) {
6031
+ return {
6032
+ recovered: false,
6033
+ message: health.error || 'V3 service is still unreachable',
6034
+ };
6035
+ }
5994
6036
  return {
5995
- recovered: false,
5996
- message: safeReason ? `Recovery unavailable: ${safeReason}` : 'Recovery unavailable',
6037
+ recovered: true,
6038
+ message: 'V3 service is reachable again retrying the workflow once.',
5997
6039
  };
5998
6040
  }
5999
6041
  async getDevtoolsBridgeStatus() {
@@ -1,6 +1,7 @@
1
1
  export interface VigthoriaCLIConfig {
2
2
  apiUrl: string;
3
3
  modelsApiUrl: string;
4
+ musicApiUrl: string;
4
5
  wsUrl: string;
5
6
  selfHostedModelsApiUrl: string | null;
6
7
  authToken: string | null;
@@ -6,6 +6,7 @@ import { secureFileMode } from './cli-state.js';
6
6
  const defaultConfig = {
7
7
  apiUrl: 'https://coder.vigthoria.io',
8
8
  modelsApiUrl: 'https://api.vigthoria.io', // Direct AI Models API - no proxying through Coder
9
+ musicApiUrl: 'https://music.vigthoria.io',
9
10
  wsUrl: 'wss://coder.vigthoria.io/ws',
10
11
  selfHostedModelsApiUrl: null,
11
12
  authToken: null,
@@ -59,6 +60,7 @@ export class Config {
59
60
  schema: {
60
61
  apiUrl: { type: 'string' },
61
62
  modelsApiUrl: { type: 'string' },
63
+ musicApiUrl: { type: 'string' },
62
64
  wsUrl: { type: 'string' },
63
65
  selfHostedModelsApiUrl: { type: ['string', 'null'] },
64
66
  authToken: { type: ['string', 'null'] },
@@ -4,8 +4,8 @@
4
4
  */
5
5
  const CLI_SHAPING_BLOCK = /(?:^|\n\n)Platform:\s*(?:Windows|macOS|Linux)\.[\s\S]*?(?=\n\n[A-Z][^\n]{0,40}:|\s*$)/i;
6
6
  const READONLY_SHAPING_BLOCK = /(?:^|\n\n)(?:Read-only analysis mode is active\.|Diagnostic mode is active\.)[\s\S]*?(?=\n\n[A-Z][^\n]{0,40}:|\s*$)/i;
7
- const BUILD_VERBS = /\b(build|create|make|implement|complete|fix|repair|edit|modify|write|generate|add|finish|scaffold|develop|update|change|refactor|let's|let us|we need|we should|erstelle|erstellen|schreib|schreibe|bearbeite)\b/i;
8
- const ARTIFACT_NOUNS = /\b(file|files|project|game|spiel|app|website|html5|frontend|component|feature|code|workspace|repo|clone|replica|remake|page|site|canvas|sprite|level|levels|enemy|enemies|character|npc|world|scene|script|module|api|database|config|landing|dashboard|platformer|arcade|playable|collectible|scoreboard|leaderboard|pitfall|pacman|rogue|html|css|javascript|typescript)\b/i;
7
+ const BUILD_VERBS = /\b(build|create|make|implement|complete|fix|repair|edit|modify|write|generate|add|finish|scaffold|develop|update|change|refactor|want|need|require|looking for|let's|let us|we need|we should|erstelle|erstellen|schreib|schreibe|bearbeite)\b/i;
8
+ const ARTIFACT_NOUNS = /\b(file|files|project|game|spiel|app|website|html5|frontend|component|feature|code|workspace|repo|clone|replica|remake|page|site|screen|button|popup|modal|alert|canvas|sprite|level|levels|enemy|enemies|character|npc|world|scene|script|module|api|database|config|landing|dashboard|platformer|arcade|playable|collectible|scoreboard|leaderboard|pitfall|pacman|rogue|html|css|javascript|typescript)\b/i;
9
9
  /** Canvas/playable games only — bare "html5" or "website" must NOT match. */
10
10
  const GAME_INTENT = /\b(game|spiel|playable|html5\s+(?:game|canvas)|html5\s+game|canvas\s+game|game\s+canvas|arcade|platformer|side[- ]?scroller|pitfall|pac[- ]?man|tetris|snake|breakout|pong|roguelike|metroidvania|tower\s+defense|clone\s+of\s+(?:a\s+)?(?:game|pitfall|pac|mario|zelda|arcade)|gameplay|collectible|boss\s+fight|level\s+design|sprite\s+sheet|wild\s+(?:boar|pig|forest)|forest\s+pig)\b/i;
11
11
  const WEB_PAGE_INTENT = /\b(website|web\s*site|webpage|web\s*page|landing\s+page|home\s*page|index\.html|html\s+page|static\s+page|single\s+page|popup|alert|modal|hello\s+world)\b/i;
@@ -54,7 +54,7 @@ export function isTrivialHtmlPageRequest(prompt) {
54
54
  if (isContinueOrRetryPrompt(prompt)) {
55
55
  return false;
56
56
  }
57
- if (!hasBuildVerb(prompt) && !/\b(write|create|make|show)\b/i.test(text)) {
57
+ if (!hasBuildVerb(prompt) && !/\b(write|create|make|show|want|need|looking\s+for)\b/i.test(text)) {
58
58
  return false;
59
59
  }
60
60
  if (hasGameIntent(prompt)) {
@@ -4,22 +4,108 @@
4
4
  import * as fs from 'fs';
5
5
  import * as path from 'path';
6
6
  import { stripExecutionShaping } from './requestIntent.js';
7
+ function escapeJsString(value) {
8
+ return String(value || '').replace(/\\/g, '\\\\').replace(/'/g, "\\'");
9
+ }
10
+ function escapeHtml(value) {
11
+ return String(value || '')
12
+ .replace(/&/g, '&amp;')
13
+ .replace(/</g, '&lt;')
14
+ .replace(/>/g, '&gt;')
15
+ .replace(/"/g, '&quot;');
16
+ }
17
+ function extractPopupMessage(vision) {
18
+ const explicitShown = vision.match(/(?:popup\s+is\s+shown|popup\s+shown|shown|shows?|display(?:s|ed)?)\s*:?\s*["']([^"']+)["']/i);
19
+ if (explicitShown?.[1])
20
+ return explicitShown[1];
21
+ const popupPhrases = [...vision.matchAll(/popup[^"']*(?:message|text|says?|shown|with)\s*:?\s*["']([^"']+)["']/gi)];
22
+ if (popupPhrases.length > 0)
23
+ return popupPhrases[popupPhrases.length - 1][1];
24
+ const quoted = vision.match(/(?:alert|message)\s*(?:with|says?|text|:)\s*["']([^"']+)["']/i);
25
+ if (quoted?.[1])
26
+ return quoted[1];
27
+ if (/\bhello\s*world\b/i.test(vision))
28
+ return 'hello world';
29
+ return 'Hello World';
30
+ }
31
+ function extractButtonLabel(vision) {
32
+ const label = vision.match(/\blabel\s+["']([^"']+)["']/i)
33
+ || vision.match(/\bbutton(?:\s+(?:text|label|called|named))?\s+["']([^"']+)["']/i);
34
+ if (label?.[1])
35
+ return label[1];
36
+ if (/\bclick\b/i.test(vision))
37
+ return 'CLICK';
38
+ return 'Click';
39
+ }
40
+ function isSimplePopupButtonPrompt(vision) {
41
+ const text = String(vision || '').toLowerCase();
42
+ return /\b(popup|alert|message)\b/.test(text)
43
+ && /\b(button|click|label)\b/.test(text)
44
+ && /\bhello\s*world\b/.test(text);
45
+ }
46
+ function exactPopupButtonHtml(vision) {
47
+ const popupMessage = extractPopupMessage(vision);
48
+ const buttonLabel = extractButtonLabel(vision);
49
+ const blue = /\bblue\b/i.test(vision) ? '#0000ff' : '#0a0a1a';
50
+ return `<!DOCTYPE html>
51
+ <html lang="en">
52
+ <head>
53
+ <meta charset="UTF-8">
54
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
55
+ <title>${escapeHtml(buttonLabel)} Popup</title>
56
+ <style>
57
+ * { box-sizing: border-box; }
58
+ body {
59
+ min-height: 100vh;
60
+ margin: 0;
61
+ display: grid;
62
+ place-items: center;
63
+ background: ${blue};
64
+ font-family: Arial, sans-serif;
65
+ }
66
+ button {
67
+ padding: 14px 28px;
68
+ border: 0;
69
+ border-radius: 10px;
70
+ background: #ffffff;
71
+ color: #0000aa;
72
+ font-size: 20px;
73
+ font-weight: 700;
74
+ cursor: pointer;
75
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
76
+ }
77
+ </style>
78
+ </head>
79
+ <body>
80
+ <button id="vigthoria-popup-button" type="button">${escapeHtml(buttonLabel)}</button>
81
+ <script>
82
+ document.getElementById('vigthoria-popup-button').addEventListener('click', function () {
83
+ alert('${escapeJsString(popupMessage)}');
84
+ });
85
+ </script>
86
+ </body>
87
+ </html>
88
+ `;
89
+ }
7
90
  function ensurePopupScript(html, vision) {
8
91
  const text = String(vision || '').toLowerCase();
9
92
  if (!/\b(popup|alert|hello\s*world)\b/.test(text)) {
10
93
  return html;
11
94
  }
95
+ if (isSimplePopupButtonPrompt(vision)) {
96
+ return exactPopupButtonHtml(vision);
97
+ }
12
98
  if (/\balert\s*\(/i.test(html)) {
13
99
  return html;
14
100
  }
15
- const script = "<script>window.addEventListener('load',()=>alert('Hello World'));</script>";
101
+ const script = `<script>document.addEventListener('DOMContentLoaded',()=>{const b=document.querySelector('button,a.cta,.cta');if(b)b.addEventListener('click',()=>alert('${escapeJsString(extractPopupMessage(vision))}'));});</script>`;
16
102
  if (/<\/body>/i.test(html)) {
17
103
  return html.replace(/<\/body>/i, `${script}\n</body>`);
18
104
  }
19
105
  return `${html}\n${script}`;
20
106
  }
21
107
  function minimalHelloWorldHtml(popupMessage = 'Hello World') {
22
- const safe = String(popupMessage || 'Hello World').replace(/\\/g, '\\\\').replace(/'/g, "\\'");
108
+ const safe = escapeJsString(popupMessage || 'Hello World');
23
109
  return `<!DOCTYPE html>
24
110
  <html lang="en">
25
111
  <head>
@@ -33,7 +119,7 @@ function minimalHelloWorldHtml(popupMessage = 'Hello World') {
33
119
  </head>
34
120
  <body>
35
121
  <h1>Hello World</h1>
36
- <script>window.addEventListener('load', () => alert('${safe}'));</script>
122
+ <button type="button" onclick="alert('${safe}')">CLICK</button>
37
123
  </body>
38
124
  </html>
39
125
  `;
@@ -79,14 +165,6 @@ async function fetchTemplateMatch(api, vision, timeoutMs = 15000) {
79
165
  }
80
166
  return { error: lastError };
81
167
  }
82
- function extractPopupMessage(vision) {
83
- const quoted = vision.match(/popup\s+with\s+["']([^"']+)["']/i);
84
- if (quoted?.[1])
85
- return quoted[1];
86
- if (/\bhello\s*world\b/i.test(vision))
87
- return 'Hello World';
88
- return 'Hello World';
89
- }
90
168
  export async function runTemplateInstantPath(api, vision, workspacePath, options = {}) {
91
169
  const entryPath = options.entryPath || 'index.html';
92
170
  const target = path.join(workspacePath, entryPath);
@@ -2554,6 +2554,7 @@ export class AgenticTools {
2554
2554
  `${workspaceRootPosix}/`,
2555
2555
  `${normalizedRootNoSlash}/`,
2556
2556
  `${workspaceBase}/`,
2557
+ 'workspace/',
2557
2558
  ];
2558
2559
  for (const prefix of prefixes) {
2559
2560
  if (candidate.startsWith(prefix)) {
@@ -25,6 +25,16 @@ export function normalizeV3WorkspaceRelativePath(rawPath, rootPath) {
25
25
  }
26
26
  return normalized;
27
27
  };
28
+ const stripWorkspaceAlias = (value) => {
29
+ const candidate = String(value || '').replace(/^\/+/, '');
30
+ if (!candidate || candidate === '.' || /^workspace\/?$/i.test(candidate)) {
31
+ return '';
32
+ }
33
+ if (/^workspace\//i.test(candidate)) {
34
+ return safeRelative(candidate.replace(/^workspace\/+/i, ''));
35
+ }
36
+ return null;
37
+ };
28
38
  const decodeBoundaryUri = (value) => {
29
39
  const match = value.match(/^vigthoria:\/\/(?:workspace|server-internal)\/?(.*)$/i);
30
40
  if (match) {
@@ -39,6 +49,10 @@ export function normalizeV3WorkspaceRelativePath(rawPath, rootPath) {
39
49
  if (/^vigthoria:\/\//i.test(input)) {
40
50
  return '';
41
51
  }
52
+ const workspaceAlias = stripWorkspaceAlias(input);
53
+ if (workspaceAlias !== null) {
54
+ return workspaceAlias;
55
+ }
42
56
  const internalWorkspacePatterns = [
43
57
  /^\/?var\/www\/\.vigthoria\/v3-temp\/vig-remote-[^/]+\/(.+)$/i,
44
58
  /^\/?var\/www\/vigthoria:\/\/?server-internal\/?(.+)$/i,
package/install.ps1 CHANGED
@@ -5,25 +5,33 @@
5
5
  $ErrorActionPreference = "Stop"
6
6
 
7
7
  # Configuration
8
- $CLI_VERSION = "1.10.18"
8
+ $CLI_VERSION = "1.11.25"
9
9
  $INSTALL_DIR = "$env:USERPROFILE\.vigthoria"
10
10
  $NPM_PACKAGE = "vigthoria-cli"
11
11
  $GIT_PACKAGE_URL = "git+https://market.vigthoria.io/vigthoria/vigthoria-cli.git"
12
- $HOSTED_TARBALL_URL = "https://coder.vigthoria.io/releases/vigthoria-cli-$CLI_VERSION.tgz"
12
+ $MANIFEST_URL = "https://extension.vigthoria.io/downloads/manifest.json"
13
+ $HOSTED_TARBALL_URL = "https://extension.vigthoria.io/downloads/vigthoria-cli-$CLI_VERSION.tgz"
13
14
 
14
- function Resolve-ReleaseTruth {
15
+ function Resolve-ReleaseManifest {
15
16
  try {
16
- $truth = Invoke-WebRequest -Uri $RELEASE_TRUTH_URL -UseBasicParsing -TimeoutSec 10 -ErrorAction Stop | Select-Object -ExpandProperty Content | ConvertFrom-Json
17
- if ($truth.products.vigthoriaCli.version -and $truth.products.vigthoriaCli.url) {
18
- $script:CLI_VERSION = $truth.products.vigthoriaCli.version
19
- $script:HOSTED_TARBALL_URL = $truth.products.vigthoriaCli.url
17
+ $manifest = Invoke-WebRequest -Uri $MANIFEST_URL -UseBasicParsing -TimeoutSec 10 -ErrorAction Stop |
18
+ Select-Object -ExpandProperty Content |
19
+ ConvertFrom-Json
20
+ $stable = $manifest.channels.stable
21
+ if ($stable.version) {
22
+ $script:CLI_VERSION = [string]$stable.version
23
+ }
24
+ if ($stable.url) {
25
+ $script:HOSTED_TARBALL_URL = [string]$stable.url
26
+ } elseif ($stable.version) {
27
+ $script:HOSTED_TARBALL_URL = "https://extension.vigthoria.io/downloads/vigthoria-cli-$($stable.version).tgz"
20
28
  }
21
29
  } catch {
22
- Write-Host "Release truth unavailable, using bundled fallback $CLI_VERSION" -ForegroundColor Yellow
30
+ Write-Host "Release manifest unavailable, using bundled fallback $CLI_VERSION" -ForegroundColor Yellow
23
31
  }
24
32
  }
25
33
 
26
- Resolve-ReleaseTruth
34
+ Resolve-ReleaseManifest
27
35
 
28
36
  function Write-Banner {
29
37
  Write-Host ""
package/install.sh CHANGED
@@ -26,11 +26,54 @@ else
26
26
  fi
27
27
 
28
28
  # Configuration
29
- CLI_VERSION="1.10.18"
29
+ CLI_VERSION="1.11.25"
30
30
  INSTALL_DIR="$HOME/.vigthoria"
31
31
  REPO_URL="https://market.vigthoria.io/vigthoria/vigthoria-cli"
32
32
  GIT_PACKAGE_URL="git+https://market.vigthoria.io/vigthoria/vigthoria-cli.git"
33
- HOSTED_TARBALL_URL="https://coder.vigthoria.io/releases/vigthoria-cli-${CLI_VERSION}.tgz"
33
+ MANIFEST_URL="${VIGTHORIA_UPDATE_MANIFEST_URL:-https://extension.vigthoria.io/downloads/manifest.json}"
34
+ HOSTED_TARBALL_URL="https://extension.vigthoria.io/downloads/vigthoria-cli-${CLI_VERSION}.tgz"
35
+
36
+ resolve_release_manifest() {
37
+ if ! command -v curl >/dev/null 2>&1 || ! command -v python3 >/dev/null 2>&1; then
38
+ return 0
39
+ fi
40
+
41
+ local manifest_json
42
+ manifest_json="$(curl -fsSL "$MANIFEST_URL" 2>/dev/null || true)"
43
+ if [[ -z "$manifest_json" ]]; then
44
+ return 0
45
+ fi
46
+
47
+ local resolved
48
+ resolved="$(printf '%s' "$manifest_json" | python3 -c "
49
+ import json, sys
50
+ try:
51
+ data = json.load(sys.stdin)
52
+ stable = (data.get('channels') or {}).get('stable') or {}
53
+ version = str(stable.get('version') or '').strip()
54
+ url = str(stable.get('url') or '').strip()
55
+ if version:
56
+ print(version)
57
+ if url:
58
+ print(url)
59
+ except Exception:
60
+ pass
61
+ " 2>/dev/null || true)"
62
+
63
+ if [[ -n "$resolved" ]]; then
64
+ mapfile -t _manifest_lines <<< "$resolved"
65
+ if [[ -n "${_manifest_lines[0]:-}" ]]; then
66
+ CLI_VERSION="${_manifest_lines[0]}"
67
+ fi
68
+ if [[ -n "${_manifest_lines[1]:-}" ]]; then
69
+ HOSTED_TARBALL_URL="${_manifest_lines[1]}"
70
+ elif [[ -n "${_manifest_lines[0]:-}" ]]; then
71
+ HOSTED_TARBALL_URL="https://extension.vigthoria.io/downloads/vigthoria-cli-${CLI_VERSION}.tgz"
72
+ fi
73
+ fi
74
+ }
75
+
76
+ resolve_release_manifest
34
77
 
35
78
  # Detect platform and set appropriate bin directory
36
79
  detect_platform() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigthoria-cli",
3
- "version": "1.11.19",
3
+ "version": "1.11.25",
4
4
  "description": "Vigthoria Coder CLI - AI-powered terminal coding assistant",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -17,7 +17,7 @@ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
17
17
  PKG_FILE="$REPO_ROOT/vigthoria-cli-${VERSION}.tgz"
18
18
  HOST_DOWNLOADS_DIR="${HOST_DOWNLOADS_DIR:-}"
19
19
  HOST_MANIFEST_FILE="${HOST_MANIFEST_FILE:-}"
20
- PUBLIC_BASE_URL="${PUBLIC_BASE_URL:-https://cli.vigthoria.io/downloads}"
20
+ PUBLIC_BASE_URL="${PUBLIC_BASE_URL:-https://extension.vigthoria.io/downloads}"
21
21
  CHANNEL="${CHANNEL:-stable}"
22
22
 
23
23
  if [[ ! -f "$PKG_FILE" ]]; then