vigthoria-cli 1.11.19 → 1.11.27

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;
@@ -23,26 +23,23 @@ import { runTemplateInstantPath } from '../utils/templateInstantPath.js';
23
23
  const DEFAULT_V3_AGENT_TIMEOUT_MS = (() => {
24
24
  const rawValue = process.env.VIGTHORIA_AGENT_TIMEOUT_MS || process.env.V3_AGENT_TIMEOUT_MS;
25
25
  if (!rawValue) {
26
- return 300000;
26
+ return 0;
27
27
  }
28
28
  const parsed = Number.parseInt(rawValue, 10);
29
- return Number.isFinite(parsed) && parsed > 0 ? parsed : 300000;
29
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
30
30
  })();
31
31
  const DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS = (() => {
32
32
  const rawValue = process.env.VIGTHORIA_AGENT_IDLE_TIMEOUT_MS || process.env.V3_AGENT_IDLE_TIMEOUT_MS;
33
33
  if (!rawValue) {
34
- return 90000;
34
+ return 0;
35
35
  }
36
36
  const parsed = Number.parseInt(rawValue, 10);
37
- return Number.isFinite(parsed) && parsed > 0 ? parsed : 90000;
37
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
38
38
  })();
39
39
  const DEFAULT_V3_AGENT_SOFT_TIMEOUT_MS = (() => {
40
- const rawValue = process.env.VIGTHORIA_AGENT_SOFT_TIMEOUT_MS || process.env.V3_AGENT_SOFT_TIMEOUT_MS;
41
- if (!rawValue) {
42
- return 180000;
43
- }
40
+ const rawValue = process.env.VIGTHORIA_AGENT_SOFT_TIMEOUT_MS || process.env.V3_AGENT_SOFT_TIMEOUT_MS || '300000';
44
41
  const parsed = Number.parseInt(rawValue, 10);
45
- return Number.isFinite(parsed) && parsed > 0 ? parsed : 180000;
42
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 300000;
46
43
  })();
47
44
  export class ChatCommand {
48
45
  config;
@@ -77,6 +74,7 @@ export class ChatCommand {
77
74
  v3StreamedAnswerDisplayed = false;
78
75
  v3SeenToolCalls = new Set();
79
76
  v3SeenToolResults = new Set();
77
+ v3StartSeen = false;
80
78
  lastAgentRoute = null;
81
79
  // Last completed Agent run — used by /retry, /continue, and the final summary block.
82
80
  lastAgentRunOutcome = null;
@@ -299,7 +297,7 @@ export class ChatCommand {
299
297
  resolveAgentExecutionPolicy(prompt) {
300
298
  const explicitModel = this.modelExplicitlySelected;
301
299
  const heavyTask = this.config.isComplexTask(prompt);
302
- const cloudEligible = this.config.hasCloudAccess() && this.hasOperatorAccess();
300
+ const cloudEligible = this.config.hasCloudAccess();
303
301
  const requiresV3Workflow = this.shouldRequireV3AgentWorkflow(prompt);
304
302
  if (explicitModel) {
305
303
  return {
@@ -1282,8 +1280,11 @@ export class ChatCommand {
1282
1280
  return;
1283
1281
  }
1284
1282
  if (event.type === 'start') {
1285
- this.v3IterationCount = 0;
1286
- this.v3ToolCallCount = 0;
1283
+ if (this.v3StartSeen) {
1284
+ spinner.text = 'Working...';
1285
+ return;
1286
+ }
1287
+ this.v3StartSeen = true;
1287
1288
  if (spinner.isSpinning)
1288
1289
  spinner.stop();
1289
1290
  process.stderr.write(chalk.cyan(' [Start] ') + 'Agent initialized\n');
@@ -2793,6 +2794,7 @@ export class ChatCommand {
2793
2794
  console.log(chalk.gray('━━━ ROUTING DECISION ━━━'));
2794
2795
  console.log(chalk.gray(`Reason: ${routingPolicy.routeReason}`));
2795
2796
  console.log(chalk.gray(`Model: ${routingPolicy.selectedModel}`));
2797
+ console.log(chalk.gray('Remote Engine Session: true'));
2796
2798
  console.log(chalk.gray(`Cloud Eligible: ${routingPolicy.cloudEligible}`));
2797
2799
  console.log(chalk.gray(`Cloud Selected: ${routingPolicy.cloudSelected}`));
2798
2800
  if (routingPolicy.heavyTask) {
@@ -2808,6 +2810,7 @@ export class ChatCommand {
2808
2810
  this.v3StreamingStarted = false;
2809
2811
  this.v3StreamedAnswerBuffer = '';
2810
2812
  this.v3StreamedAnswerDisplayed = false;
2813
+ this.v3StartSeen = false;
2811
2814
  this.v3SeenToolCalls.clear();
2812
2815
  this.v3SeenToolResults.clear();
2813
2816
  emitDeckEvent({ type: 'agent_start' });
@@ -3363,6 +3366,7 @@ export class ChatCommand {
3363
3366
  this.v3ToolCallCount = 0;
3364
3367
  this.v3LastActivity = Date.now();
3365
3368
  this.v3StreamingStarted = false;
3369
+ this.v3StartSeen = false;
3366
3370
  try {
3367
3371
  const retryTaskType = this.inferAgentTaskType(rawPrompt);
3368
3372
  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
@@ -259,15 +259,18 @@ export function propagateError(err) {
259
259
  const DEFAULT_V3_AGENT_TIMEOUT_MS = (() => {
260
260
  const rawValue = process.env.VIGTHORIA_AGENT_TIMEOUT_MS || process.env.V3_AGENT_TIMEOUT_MS;
261
261
  if (!rawValue) {
262
- return 300000;
262
+ return 0;
263
263
  }
264
264
  const parsed = Number.parseInt(rawValue, 10);
265
- return Number.isFinite(parsed) && parsed > 0 ? parsed : 300000;
265
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
266
266
  })();
267
267
  const DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS = (() => {
268
- const rawValue = process.env.VIGTHORIA_AGENT_IDLE_TIMEOUT_MS || process.env.V3_AGENT_IDLE_TIMEOUT_MS || '90000';
268
+ const rawValue = process.env.VIGTHORIA_AGENT_IDLE_TIMEOUT_MS || process.env.V3_AGENT_IDLE_TIMEOUT_MS;
269
+ if (!rawValue) {
270
+ return 0;
271
+ }
269
272
  const parsed = Number.parseInt(rawValue, 10);
270
- return Number.isFinite(parsed) && parsed > 0 ? parsed : 90000;
273
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
271
274
  })();
272
275
  const DEFAULT_OPERATOR_TIMEOUT_MS = (() => {
273
276
  const rawValue = process.env.VIGTHORIA_OPERATOR_TIMEOUT_MS || process.env.OPERATOR_TIMEOUT_MS;
@@ -2236,6 +2239,10 @@ menu {
2236
2239
  }
2237
2240
  return {
2238
2241
  ...executionContext,
2242
+ localWorkspacePath: localWorkspacePath || executionContext.localWorkspacePath || null,
2243
+ targetPath: localWorkspacePath || executionContext.targetPath || executionContext.projectPath || executionContext.workspacePath || null,
2244
+ projectPath: localWorkspacePath || executionContext.projectPath || executionContext.targetPath || executionContext.workspacePath || null,
2245
+ workspacePath: localWorkspacePath || executionContext.workspacePath || executionContext.projectPath || executionContext.targetPath || null,
2239
2246
  mcpContextId: binding.mcpContextId,
2240
2247
  mcpContextBackendUrl: binding.backendUrl,
2241
2248
  };
@@ -2276,10 +2283,38 @@ menu {
2276
2283
  this.logger.debug(`Failed to bind MCP context via ${baseUrl}:`, error.message);
2277
2284
  }
2278
2285
  }
2279
- return executionContext;
2286
+ return {
2287
+ ...executionContext,
2288
+ localWorkspacePath: localWorkspacePath || executionContext.localWorkspacePath || null,
2289
+ targetPath: localWorkspacePath || executionContext.targetPath || executionContext.projectPath || executionContext.workspacePath || null,
2290
+ projectPath: localWorkspacePath || executionContext.projectPath || executionContext.targetPath || executionContext.workspacePath || null,
2291
+ workspacePath: localWorkspacePath || executionContext.workspacePath || executionContext.projectPath || executionContext.targetPath || null,
2292
+ };
2293
+ }
2294
+ isServerManagedTempWorkspacePath(pathValue) {
2295
+ const normalized = String(pathValue || '').replace(/\\/g, '/');
2296
+ return /(?:^|\/)v3-temp\/vig-remote-/i.test(normalized)
2297
+ || /(?:^|\/)tmp\/vig-remote(?:-server)?-/i.test(normalized)
2298
+ || /(?:^|\/)var\/tmp\/vig-remote(?:-server)?-/i.test(normalized);
2280
2299
  }
2281
2300
  resolveAgentTargetPath(context = {}) {
2282
- return context.targetPath || context.projectPath || context.workspacePath || context.projectRoot || process.cwd();
2301
+ const explicitLocal = String(context.localWorkspacePath || '').trim();
2302
+ if (explicitLocal
2303
+ && !explicitLocal.startsWith('vigthoria://')
2304
+ && (this.isLikelyWindowsPath(explicitLocal) || path.isAbsolute(explicitLocal))) {
2305
+ return explicitLocal;
2306
+ }
2307
+ const candidate = context.targetPath || context.projectPath || context.workspacePath || context.projectRoot || process.cwd();
2308
+ if (this.isServerManagedTempWorkspacePath(candidate) && explicitLocal && !explicitLocal.startsWith('vigthoria://')) {
2309
+ return explicitLocal;
2310
+ }
2311
+ if (this.isServerManagedTempWorkspacePath(candidate)) {
2312
+ const preserved = String(context.__localWorkspacePath || context.clientWorkspacePath || '').trim();
2313
+ if (preserved && !preserved.startsWith('vigthoria://')) {
2314
+ return preserved;
2315
+ }
2316
+ }
2317
+ return candidate;
2283
2318
  }
2284
2319
  isLikelyWindowsPath(pathValue) {
2285
2320
  return /^[a-zA-Z]:[\\/]/.test(pathValue) || /^\\\\/.test(pathValue);
@@ -2819,9 +2854,30 @@ menu {
2819
2854
  return { success: true, output: matches.slice(0, 100).join('\n') || '(no matches)' };
2820
2855
  }
2821
2856
  if (name === 'syntax_check') {
2822
- const content = fs.readFileSync(target.absolutePath, 'utf8');
2823
- if (/\.json$/i.test(target.absolutePath))
2824
- JSON.parse(content);
2857
+ if (!fs.existsSync(target.absolutePath)) {
2858
+ return {
2859
+ success: false,
2860
+ output: '',
2861
+ error: `File not found: ${target.relativePath}. Write the file before running syntax_check.`,
2862
+ };
2863
+ }
2864
+ const ext = path.extname(target.absolutePath).toLowerCase();
2865
+ if (ext === '.json') {
2866
+ JSON.parse(fs.readFileSync(target.absolutePath, 'utf8'));
2867
+ return { success: true, output: `Syntax check passed: ${target.relativePath}` };
2868
+ }
2869
+ if (ext === '.js' || ext === '.mjs' || ext === '.cjs') {
2870
+ const { execSync } = await import('child_process');
2871
+ try {
2872
+ execSync(`node --check "${target.absolutePath}"`, { stdio: 'pipe' });
2873
+ return { success: true, output: `Syntax check passed: ${target.relativePath}` };
2874
+ }
2875
+ catch (error) {
2876
+ const stderr = error?.stderr?.toString?.() || error?.message || String(error);
2877
+ return { success: false, output: '', error: stderr.trim() || `Syntax check failed: ${target.relativePath}` };
2878
+ }
2879
+ }
2880
+ fs.readFileSync(target.absolutePath, 'utf8');
2825
2881
  return { success: true, output: `Syntax check passed: ${target.relativePath}` };
2826
2882
  }
2827
2883
  if (name === 'run_command') {
@@ -2955,7 +3011,9 @@ menu {
2955
3011
  return;
2956
3012
  }
2957
3013
  }
2958
- const targets = expectedFiles.length > 0 ? expectedFiles : Object.keys(streamedFiles);
3014
+ const targets = expectedFiles.length > 0
3015
+ ? Array.from(new Set([...expectedFiles, ...Object.keys(streamedFiles)]))
3016
+ : Object.keys(streamedFiles);
2959
3017
  for (const targetPath of targets) {
2960
3018
  const relativePath = this.normalizeAgentWorkspaceRelativePath(targetPath, rootPath);
2961
3019
  if (!relativePath) {
@@ -3521,6 +3579,7 @@ document.addEventListener('DOMContentLoaded', () => {
3521
3579
  let buffer = '';
3522
3580
  const events = [];
3523
3581
  let final = null;
3582
+ let streamDone = false;
3524
3583
  let contextId = response.headers.get('x-context-id') || String(context.contextId || '').trim() || null;
3525
3584
  let serverWorkspaceRoot = null;
3526
3585
  const streamedFiles = {};
@@ -3528,48 +3587,71 @@ document.addEventListener('DOMContentLoaded', () => {
3528
3587
  this.activeV3StreamContext = context;
3529
3588
  context.__v3StreamedFiles = streamedFiles;
3530
3589
  const idleTimeoutMs = (() => {
3531
- const base = context.agentIdleTimeoutMs || DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS;
3532
- if (isPlannerBuildTask(String(context.agentTaskType || ''), String(context.rawPrompt || ''))) {
3533
- const raw = process.env.VIGTHORIA_AGENT_PLANNER_IDLE_TIMEOUT_MS || '300000';
3534
- const extended = Number.parseInt(raw, 10);
3535
- return Math.max(base, Number.isFinite(extended) && extended > 0 ? extended : 300000);
3590
+ const base = context.agentIdleTimeoutMs ?? DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS;
3591
+ if (base <= 0) {
3592
+ return 0;
3536
3593
  }
3537
- return base;
3594
+ if (!isPlannerBuildTask(String(context.agentTaskType || ''), String(context.rawPrompt || ''))) {
3595
+ return base;
3596
+ }
3597
+ const raw = process.env.VIGTHORIA_AGENT_PLANNER_IDLE_TIMEOUT_MS || process.env.V3_AGENT_PLANNER_IDLE_TIMEOUT_MS;
3598
+ if (!raw) {
3599
+ return base;
3600
+ }
3601
+ const extended = Number.parseInt(raw, 10);
3602
+ return Number.isFinite(extended) && extended > 0 ? Math.max(base, extended) : base;
3538
3603
  })();
3539
3604
  try {
3540
3605
  while (true) {
3541
3606
  let chunk;
3542
3607
  try {
3543
- const readPromise = reader.read();
3544
- while (true) {
3545
- const timeoutSentinel = Symbol('v3-agent-idle-timeout');
3546
- const result = idleTimeoutMs > 0
3547
- ? await Promise.race([
3548
- readPromise,
3549
- new Promise((resolve) => {
3550
- setTimeout(() => resolve(timeoutSentinel), idleTimeoutMs);
3551
- }),
3552
- ])
3553
- : await readPromise;
3554
- if (result !== timeoutSentinel) {
3555
- chunk = result;
3556
- break;
3557
- }
3558
- if (this.pendingV3ClientToolTasks.size > 0) {
3559
- continue;
3560
- }
3561
- if (this.hasAgentWorkspaceOutput(context)) {
3562
- const stalledError = new Error('V3 agent stream stalled after writing workspace output');
3563
- stalledError.name = 'AbortError';
3564
- stalledError.partialData = {
3565
- task_id: events.find((event) => event && event.task_id)?.task_id || null,
3566
- context_id: contextId,
3567
- result: final,
3568
- events,
3569
- files: streamedFiles,
3608
+ if (idleTimeoutMs <= 0) {
3609
+ chunk = await reader.read();
3610
+ }
3611
+ else {
3612
+ chunk = await new Promise((resolve, reject) => {
3613
+ let idleTimer = null;
3614
+ const clearIdleTimer = () => {
3615
+ if (idleTimer) {
3616
+ clearTimeout(idleTimer);
3617
+ idleTimer = null;
3618
+ }
3570
3619
  };
3571
- throw stalledError;
3572
- }
3620
+ const scheduleIdleTimer = () => {
3621
+ clearIdleTimer();
3622
+ idleTimer = setTimeout(() => {
3623
+ if (this.pendingV3ClientToolTasks.size > 0) {
3624
+ scheduleIdleTimer();
3625
+ return;
3626
+ }
3627
+ if (this.hasAgentWorkspaceOutput(context)) {
3628
+ const stalledError = new Error('V3 agent stream stalled after writing workspace output');
3629
+ stalledError.name = 'AbortError';
3630
+ stalledError.partialData = {
3631
+ task_id: events.find((event) => event && event.task_id)?.task_id || null,
3632
+ context_id: contextId,
3633
+ result: final,
3634
+ events,
3635
+ files: streamedFiles,
3636
+ };
3637
+ clearIdleTimer();
3638
+ reject(stalledError);
3639
+ return;
3640
+ }
3641
+ scheduleIdleTimer();
3642
+ }, idleTimeoutMs);
3643
+ };
3644
+ scheduleIdleTimer();
3645
+ reader.read()
3646
+ .then((result) => {
3647
+ clearIdleTimer();
3648
+ resolve(result);
3649
+ })
3650
+ .catch((error) => {
3651
+ clearIdleTimer();
3652
+ reject(error);
3653
+ });
3654
+ });
3573
3655
  }
3574
3656
  }
3575
3657
  catch (error) {
@@ -3609,9 +3691,13 @@ document.addEventListener('DOMContentLoaded', () => {
3609
3691
  }
3610
3692
  }
3611
3693
  const payload = dataLines.join('\n').trim();
3612
- if (!payload || payload === '[DONE]') {
3694
+ if (!payload) {
3613
3695
  continue;
3614
3696
  }
3697
+ if (payload === '[DONE]') {
3698
+ streamDone = true;
3699
+ break;
3700
+ }
3615
3701
  let event;
3616
3702
  try {
3617
3703
  event = JSON.parse(payload);
@@ -3702,20 +3788,9 @@ document.addEventListener('DOMContentLoaded', () => {
3702
3788
  if (event.type === 'complete' || event.type === 'message') {
3703
3789
  final = event;
3704
3790
  }
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
- }
3791
+ }
3792
+ if (streamDone) {
3793
+ break;
3719
3794
  }
3720
3795
  }
3721
3796
  await Promise.allSettled([...this.pendingV3ClientToolTasks]);
@@ -3742,11 +3817,7 @@ document.addEventListener('DOMContentLoaded', () => {
3742
3817
  const resolvedModel = this.resolvePermittedModelId(requestedModel);
3743
3818
  const preferLocalV3 = /(premium|polished|landing|site|page|dashboard|saas|frontend|ui|responsive|animated|create the required project files and write them to the workspace)/i.test(message)
3744
3819
  && context.localMachineCapable !== false;
3745
- const rescueEligibleSaaS = preferLocalV3
3746
- && /(saas|dashboard|analytics|billing|team management|activity feed|login screen)/i.test(message);
3747
- const timeoutMs = rescueEligibleSaaS
3748
- ? Math.min(resolvePlannerAgentTimeoutMs(baseTimeoutMs, String(executionContext.agentTaskType || ''), String(executionContext.rawPrompt || message || '')), 210000)
3749
- : resolvePlannerAgentTimeoutMs(baseTimeoutMs, String(executionContext.agentTaskType || ''), String(executionContext.rawPrompt || message || ''));
3820
+ const timeoutMs = resolvePlannerAgentTimeoutMs(baseTimeoutMs, String(executionContext.agentTaskType || ''), String(executionContext.rawPrompt || message || ''));
3750
3821
  const maxAttempts = preferLocalV3 ? 2 : 1;
3751
3822
  let lastErrors = [];
3752
3823
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
@@ -5532,11 +5603,20 @@ document.addEventListener('DOMContentLoaded', () => {
5532
5603
  'vigthoria-code': 'vigthoria-v3-code-35b',
5533
5604
  'vigthoria-agent': 'vigthoria-v3-code-35b',
5534
5605
  // ═══════════════════════════════════════════════════════════════
5535
- // VIGTHORIA CLOUD - Premium cloud models (internal routing)
5606
+ // VIGTHORIA CLOUD - current billing catalog aliases
5536
5607
  // ═══════════════════════════════════════════════════════════════
5537
- 'cloud': 'vigthoria-cloud-pro',
5538
- 'cloud-reason': 'vigthoria-cloud-k2',
5539
- 'ultra': 'vigthoria-cloud-ultra',
5608
+ 'cloud': 'vigthoria-cloud-balanced',
5609
+ 'cloud-code': 'vigthoria-cloud-code',
5610
+ 'cloud-balanced': 'vigthoria-cloud-balanced',
5611
+ 'cloud-fast': 'vigthoria-cloud-fast',
5612
+ 'cloud-power': 'vigthoria-cloud-power',
5613
+ 'cloud-maximum': 'vigthoria-cloud-maximum',
5614
+ 'cloud-reason': 'vigthoria-cloud-power',
5615
+ 'ultra': 'vigthoria-cloud-maximum',
5616
+ // Legacy aliases kept for backward compatibility
5617
+ 'cloud-pro': 'vigthoria-cloud-power',
5618
+ 'cloud-k2': 'vigthoria-cloud-power',
5619
+ 'cloud-ultra': 'vigthoria-cloud-maximum',
5540
5620
  };
5541
5621
  // If already a full model ID, return as-is
5542
5622
  if (shortName.includes('vigthoria') || shortName.includes('/') || shortName.includes(':')) {
@@ -5991,9 +6071,23 @@ document.addEventListener('DOMContentLoaded', () => {
5991
6071
  }
5992
6072
  async attemptV3ServiceRecovery(reason = '', _options = {}) {
5993
6073
  const safeReason = sanitizeUserFacingErrorText(reason || 'unknown failure');
6074
+ const isTransient = /timed out|aborted|econnreset|fetch failed|network|503|502|504|socket hang up/i.test(safeReason);
6075
+ if (!isTransient) {
6076
+ return {
6077
+ recovered: false,
6078
+ message: safeReason ? `Recovery unavailable: ${safeReason}` : 'Recovery unavailable',
6079
+ };
6080
+ }
6081
+ const health = await this.runV3HealthCheck();
6082
+ if (!health.healthy) {
6083
+ return {
6084
+ recovered: false,
6085
+ message: health.error || 'V3 service is still unreachable',
6086
+ };
6087
+ }
5994
6088
  return {
5995
- recovered: false,
5996
- message: safeReason ? `Recovery unavailable: ${safeReason}` : 'Recovery unavailable',
6089
+ recovered: true,
6090
+ message: 'V3 service is reachable again retrying the workflow once.',
5997
6091
  };
5998
6092
  }
5999
6093
  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)) {
@@ -150,10 +150,15 @@ export function resolvePlannerAgentTimeoutMs(baseTimeoutMs, agentTaskType, promp
150
150
  if (!isPlannerBuildTask(agentTaskType, prompt)) {
151
151
  return baseTimeoutMs;
152
152
  }
153
- const raw = process.env.VIGTHORIA_AGENT_PLANNER_TIMEOUT_MS || process.env.V3_AGENT_PLANNER_TIMEOUT_MS || '900000';
153
+ const raw = process.env.VIGTHORIA_AGENT_PLANNER_TIMEOUT_MS || process.env.V3_AGENT_PLANNER_TIMEOUT_MS;
154
+ if (!raw) {
155
+ return baseTimeoutMs;
156
+ }
154
157
  const extended = Number.parseInt(raw, 10);
155
- const floor = Number.isFinite(extended) && extended > 0 ? extended : 900000;
156
- return Math.max(baseTimeoutMs, floor);
158
+ if (!Number.isFinite(extended) || extended <= 0) {
159
+ return baseTimeoutMs;
160
+ }
161
+ return Math.max(baseTimeoutMs, extended);
157
162
  }
158
163
  /** V3 workflow mode: suppress write path when the user did not ask for file changes. */
159
164
  export function resolveWorkflowType(agentTaskType, 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,8 +25,18 @@ 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
- const match = value.match(/^vigthoria:\/\/(?:workspace|server-internal)\/?(.*)$/i);
39
+ const match = value.match(/^vigthoria:\/\/(?:workspace|server-internal|local-workspace)\/?(.*)$/i);
30
40
  if (match) {
31
41
  return safeRelative(match[1] || '');
32
42
  }
@@ -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.27",
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