vigthoria-cli 1.9.10 → 1.9.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +4 -4
  2. package/dist/commands/auth.js +48 -65
  3. package/dist/commands/bridge.js +12 -19
  4. package/dist/commands/cancel.js +15 -22
  5. package/dist/commands/chat.d.ts +11 -0
  6. package/dist/commands/chat.js +404 -248
  7. package/dist/commands/config.js +31 -71
  8. package/dist/commands/deploy.js +83 -123
  9. package/dist/commands/device.d.ts +35 -0
  10. package/dist/commands/device.js +239 -0
  11. package/dist/commands/edit.js +32 -39
  12. package/dist/commands/explain.js +18 -25
  13. package/dist/commands/fork.js +22 -27
  14. package/dist/commands/generate.js +37 -44
  15. package/dist/commands/history.js +20 -25
  16. package/dist/commands/hub.js +95 -102
  17. package/dist/commands/index.js +41 -46
  18. package/dist/commands/legion.d.ts +1 -0
  19. package/dist/commands/legion.js +162 -209
  20. package/dist/commands/preview.js +60 -98
  21. package/dist/commands/replay.js +27 -32
  22. package/dist/commands/repo.js +103 -141
  23. package/dist/commands/review.js +29 -36
  24. package/dist/commands/security.js +5 -12
  25. package/dist/commands/update.js +15 -49
  26. package/dist/commands/workflow.d.ts +8 -1
  27. package/dist/commands/workflow.js +53 -19
  28. package/dist/index.js +409 -234
  29. package/dist/utils/api.d.ts +5 -0
  30. package/dist/utils/api.js +373 -166
  31. package/dist/utils/bridge-client.js +11 -52
  32. package/dist/utils/cli-state.d.ts +54 -0
  33. package/dist/utils/cli-state.js +185 -0
  34. package/dist/utils/config.d.ts +5 -0
  35. package/dist/utils/config.js +35 -14
  36. package/dist/utils/context-ranker.js +15 -21
  37. package/dist/utils/files.js +5 -42
  38. package/dist/utils/logger.js +42 -50
  39. package/dist/utils/post-write-validator.js +22 -29
  40. package/dist/utils/project-memory.d.ts +56 -0
  41. package/dist/utils/project-memory.js +289 -0
  42. package/dist/utils/session.d.ts +29 -3
  43. package/dist/utils/session.js +137 -85
  44. package/dist/utils/task-display.js +13 -20
  45. package/dist/utils/tools.d.ts +19 -0
  46. package/dist/utils/tools.js +84 -87
  47. package/dist/utils/workspace-cache.js +18 -26
  48. package/dist/utils/workspace-stream.js +26 -64
  49. package/install.ps1 +14 -0
  50. package/package.json +5 -3
  51. package/scripts/release/LOCAL_MACHINE_USER_VERIFICATION.md +1 -1
  52. package/scripts/release/validate-no-go-gates.sh +2 -2
package/dist/utils/api.js CHANGED
@@ -1,27 +1,15 @@
1
- "use strict";
2
1
  /**
3
2
  * API Client for Vigthoria Backend
4
3
  * Connects to coder.vigthoria.io API endpoints
5
4
  */
6
- var __importDefault = (this && this.__importDefault) || function (mod) {
7
- return (mod && mod.__esModule) ? mod : { "default": mod };
8
- };
9
- Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.APIClient = exports.CLIError = void 0;
11
- exports.classifyError = classifyError;
12
- exports.formatCLIError = formatCLIError;
13
- exports.sanitizeUserFacingErrorText = sanitizeUserFacingErrorText;
14
- exports.isServerRuntime = isServerRuntime;
15
- exports.describeUpstreamStatus = describeUpstreamStatus;
16
- exports.propagateError = propagateError;
17
- const axios_1 = __importDefault(require("axios"));
18
- const crypto_1 = require("crypto");
19
- const fs_1 = __importDefault(require("fs"));
20
- const https_1 = __importDefault(require("https"));
21
- const net_1 = __importDefault(require("net"));
22
- const path_1 = __importDefault(require("path"));
23
- const ws_1 = __importDefault(require("ws"));
24
- class CLIError extends Error {
5
+ import axios from 'axios';
6
+ import { randomUUID } from 'crypto';
7
+ import fs from 'fs';
8
+ import https from 'https';
9
+ import net from 'net';
10
+ import path from 'path';
11
+ import WebSocket from 'ws';
12
+ export class CLIError extends Error {
25
13
  category;
26
14
  statusCode;
27
15
  endpoint;
@@ -35,9 +23,8 @@ class CLIError extends Error {
35
23
  this.cause = opts.cause;
36
24
  }
37
25
  }
38
- exports.CLIError = CLIError;
39
26
  /** Classify an axios or fetch error into a structured CLIError. */
40
- function classifyError(error, fallbackCategory = 'network') {
27
+ export function classifyError(error, fallbackCategory = 'network') {
41
28
  if (error instanceof CLIError)
42
29
  return error;
43
30
  const axErr = error;
@@ -69,7 +56,7 @@ function classifyError(error, fallbackCategory = 'network') {
69
56
  return new CLIError(message, fallbackCategory, { statusCode: status, endpoint, cause: error instanceof Error ? error : undefined });
70
57
  }
71
58
  /** Format a CLIError for user-facing display. */
72
- function formatCLIError(err) {
59
+ export function formatCLIError(err) {
73
60
  const tag = `[${err.category}]`;
74
61
  switch (err.category) {
75
62
  case 'auth':
@@ -92,13 +79,76 @@ function formatCLIError(err) {
92
79
  return `${tag} ${err.message}`;
93
80
  }
94
81
  }
95
- function sanitizeUserFacingErrorText(input) {
82
+ export function sanitizeUserFacingPathText(input) {
83
+ let text = String(input || '');
84
+ if (!text) {
85
+ return text;
86
+ }
87
+ // Path token used for "<service>/<sub>" captures. Excludes whitespace and
88
+ // any character that commonly terminates a path in CLI output.
89
+ const pathToken = String.raw `[^\s"'\)\]\}<>]+`;
90
+ // The replacements are applied in order — more specific patterns first.
91
+ // Every entry must cover the case WITH AND WITHOUT a trailing slash so
92
+ // bracketed paths like `[/tmp/vig-remote-xyz]` are also caught.
93
+ const replacements = [
94
+ // ── Temp-workspace directories used for remote (Windows/macOS) clients
95
+ [new RegExp(String.raw `/var/www/\.vigthoria/v3-temp/vig-remote-[A-Za-z0-9._-]+(?:/${pathToken})?`, 'gi'), 'workspace'],
96
+ [new RegExp(String.raw `\.vigthoria/v3-temp/vig-remote-[A-Za-z0-9._-]+(?:/${pathToken})?`, 'gi'), 'workspace'],
97
+ [new RegExp(String.raw `/tmp/vig-remote(?:-server)?-[A-Za-z0-9._-]+(?:/${pathToken})?`, 'gi'), 'workspace'],
98
+ [new RegExp(String.raw `/var/tmp/vig-remote(?:-server)?-[A-Za-z0-9._-]+(?:/${pathToken})?`, 'gi'), 'workspace'],
99
+ [new RegExp(String.raw `/tmp/vig-fork-[A-Za-z0-9._-]+(?:/${pathToken})?`, 'gi'), 'workspace'],
100
+ // ── Bare temp basenames after their /tmp/ prefix is already gone
101
+ [/\bvig-remote(?:-server)?-[A-Za-z0-9._-]+/gi, 'workspace'],
102
+ [/\bvig-fork-[A-Za-z0-9._-]+/gi, 'workspace'],
103
+ // ── V3 Code Agent installation tree and sibling services
104
+ [new RegExp(String.raw `/var/www/V3-Code-Agent(?:/${pathToken})?`, 'gi'), '[Vigthoria Agent]'],
105
+ [new RegExp(String.raw `/var/www/(?:agent-vigthoria|vigthoria-model-router|vigthoria-devtools-bridge|vigthoria-hyper-loop|vigthoria-mcp-server|vigthoria-template-service|vigthoria-security-devops|vigthoria-coder|vigthoria-code|Vigthoria-Code-2|vigthoria-asset-storage|vigthoria-storage-service|vigthoria-hosted)(?:/${pathToken})?`, 'gi'), '[Vigthoria service]'],
106
+ // ── User workspace mount + long-term storage backing path
107
+ [new RegExp(String.raw `/var/www/(?:vigthoria-user-workspaces|\.vigthoria)(?:/${pathToken})?`, 'gi'), 'workspace'],
108
+ [new RegExp(String.raw `/var/lib/vigthoria-workspaces(?:/${pathToken})?`, 'gi'), 'workspace'],
109
+ [new RegExp(String.raw `/mnt/storage-box/vigthoria(?:/${pathToken})?`, 'gi'), '[Vigthoria storage]'],
110
+ // ── Home / root leaks
111
+ [/\/home\/[A-Za-z0-9._-]+/gi, '[home]'],
112
+ [new RegExp(String.raw `/root(?:/${pathToken})?`, 'gi'), '[home]'],
113
+ // ── Backstop: any other /var/www tree
114
+ [new RegExp(String.raw `/var/www(?:/${pathToken})?`, 'gi'), '[Vigthoria service]'],
115
+ // ── Generic temp roots (after the more specific vig- patterns above)
116
+ [new RegExp(String.raw `/tmp(?:/${pathToken})?`, 'gi'), '[temp]'],
117
+ [new RegExp(String.raw `/var/tmp(?:/${pathToken})?`, 'gi'), '[temp]'],
118
+ ];
119
+ text = text.replace(/\\/g, '/');
120
+ for (const [pattern, replacement] of replacements) {
121
+ text = text.replace(pattern, replacement);
122
+ }
123
+ // Normalise abstract boundary URIs for human-friendly display.
124
+ text = text
125
+ .replace(/vigthoria:\/\/workspace\/?/gi, 'workspace/')
126
+ .replace(/vigthoria:\/\/temp\/?/gi, '[temp]/')
127
+ .replace(/vigthoria:\/\/(?:agent|service|storage|home|server-internal)\/?[^\s"'\)\]\}<>]*/gi, '[internal]');
128
+ // Strip server-internal phrasing so the LLM/CLI never says "server filesystem".
129
+ text = text
130
+ .replace(/outside bound workspace root(?:\s+[^\s.,;:)\]]+)?/gi, 'outside the workspace')
131
+ .replace(/\bbound workspace root(?:\s+[^\s.,;:)\]]+)?/gi, 'workspace')
132
+ .replace(/\boutside the active workspace sandbox(?::\s*[^\s.,;:)\]]+)?/gi, 'outside the workspace')
133
+ .replace(/\bescapes workspace root(?:\s+[^\s.,;:)\]]+)?/gi, 'escapes the workspace')
134
+ .replace(/\boutside allowed roots[^.]*\./gi, 'outside the workspace.')
135
+ .replace(/\bAllowed (?:server )?roots:[^\n]*/gi, 'Allowed locations: workspace only')
136
+ .replace(/\bBound server workspace:\s*\S+/gi, 'Bound workspace: workspace/')
137
+ .replace(/\bserver filesystem\b/gi, 'workspace sandbox')
138
+ .replace(/\bon the server\b/gi, 'in the workspace')
139
+ .replace(/\[(?:\s*)\]/g, '[workspace]')
140
+ .replace(/(^|\s)\.\//g, '$1')
141
+ .replace(/([^:])\/\/+/g, '$1/')
142
+ .replace(/[ \t]+/g, ' ');
143
+ return text;
144
+ }
145
+ export function sanitizeUserFacingErrorText(input) {
96
146
  const raw = String(input || '').trim();
97
147
  if (!raw) {
98
148
  return '';
99
149
  }
100
150
  const withoutTags = raw.replace(/<[^>]+>/g, ' ');
101
- return withoutTags.replace(/\s+/g, ' ').trim();
151
+ return sanitizeUserFacingPathText(withoutTags).replace(/\s+/g, ' ').trim();
102
152
  }
103
153
  const TRUSTED_TOKEN_HOST_PATTERN = /(^|\.)vigthoria\.io$/i;
104
154
  function isLoopbackHost(hostname) {
@@ -132,7 +182,7 @@ function resolveAxiosRequestUrl(req) {
132
182
  }
133
183
  return direct;
134
184
  }
135
- function isServerRuntime() {
185
+ export function isServerRuntime() {
136
186
  if (process.env.VIGTHORIA_ALLOW_LOCAL_SERVICES === '1') {
137
187
  return true;
138
188
  }
@@ -140,7 +190,7 @@ function isServerRuntime() {
140
190
  const cwd = String(process.cwd() || '').toLowerCase();
141
191
  return host.includes('ubuntu') || cwd.startsWith('/var/www');
142
192
  }
143
- function describeUpstreamStatus(status) {
193
+ export function describeUpstreamStatus(status) {
144
194
  if (status >= 500)
145
195
  return 'upstream internal error';
146
196
  if (status === 429)
@@ -155,7 +205,7 @@ function describeUpstreamStatus(status) {
155
205
  return 'bad request';
156
206
  return 'ok';
157
207
  }
158
- function propagateError(err) {
208
+ export function propagateError(err) {
159
209
  const status = typeof err?.statusCode === 'number'
160
210
  ? err.statusCode
161
211
  : typeof err?.status === 'number'
@@ -198,7 +248,7 @@ const DEFAULT_OPERATOR_TIMEOUT_MS = (() => {
198
248
  const parsed = Number.parseInt(rawValue, 10);
199
249
  return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
200
250
  })();
201
- class APIClient {
251
+ export class APIClient {
202
252
  client;
203
253
  modelRouterClient;
204
254
  selfHostedModelRouterClient;
@@ -212,14 +262,14 @@ class APIClient {
212
262
  this.config = config;
213
263
  this.logger = logger;
214
264
  // Create HTTPS agent with proper settings for cross-platform compatibility
215
- const httpsAgent = new https_1.default.Agent({
265
+ const httpsAgent = new https.Agent({
216
266
  rejectUnauthorized: true, // Enforce SSL verification
217
267
  keepAlive: true,
218
268
  timeout: 30000,
219
269
  });
220
270
  this._httpsAgent = httpsAgent;
221
271
  // Main Vigthoria Coder API (coder.vigthoria.io)
222
- this.client = axios_1.default.create({
272
+ this.client = axios.create({
223
273
  baseURL: config.get('apiUrl'),
224
274
  timeout: 300000, // 5 minutes — covers review, fix, and agent relay flows
225
275
  httpsAgent,
@@ -230,7 +280,7 @@ class APIClient {
230
280
  });
231
281
  // Direct AI Models API - bypasses Coder for direct model access
232
282
  // This is the centralized Vigthoria API that routes to Model Router
233
- this.modelRouterClient = axios_1.default.create({
283
+ this.modelRouterClient = axios.create({
234
284
  baseURL: config.get('modelsApiUrl') || 'https://api.vigthoria.io',
235
285
  timeout: 180000, // 3 minutes for model inference
236
286
  httpsAgent,
@@ -241,7 +291,7 @@ class APIClient {
241
291
  });
242
292
  // Self-hosted model router is opt-in only.
243
293
  const selfHostedModelsApiUrl = this.getSelfHostedModelsApiUrl();
244
- this.selfHostedModelRouterClient = selfHostedModelsApiUrl ? axios_1.default.create({
294
+ this.selfHostedModelRouterClient = selfHostedModelsApiUrl ? axios.create({
245
295
  baseURL: selfHostedModelsApiUrl,
246
296
  timeout: 240000,
247
297
  headers: {
@@ -338,7 +388,7 @@ class APIClient {
338
388
  });
339
389
  // Set subscription from user data
340
390
  this.config.setSubscription({
341
- plan: user.subscription?.plan || 'developer',
391
+ plan: user.subscription?.plan || 'community',
342
392
  status: 'active',
343
393
  expiresAt: undefined,
344
394
  });
@@ -413,7 +463,7 @@ class APIClient {
413
463
  || rawUser.subscription?.plan
414
464
  || rawUser.subscription_plan
415
465
  || data.subscription_plan
416
- || 'developer';
466
+ || 'community';
417
467
  return {
418
468
  id: userId,
419
469
  email,
@@ -445,7 +495,7 @@ class APIClient {
445
495
  const response = await this.client.get('/api/user/subscription');
446
496
  const data = response.data;
447
497
  this.config.setSubscription({
448
- plan: data.plan || data.subscription_plan || 'developer',
498
+ plan: data.plan || data.subscription_plan || 'community',
449
499
  status: data.status || 'active',
450
500
  expiresAt: data.expiresAt || data.expires_at
451
501
  });
@@ -489,8 +539,8 @@ class APIClient {
489
539
  // Probe protected canonical endpoints in parallel so stale local endpoint overrides
490
540
  // cannot mask an invalid gateway token during preflight.
491
541
  const results = await Promise.allSettled([
492
- axios_1.default.get(`${canonicalBaseUrl}/api/user/profile`, { timeout: 5000, headers, httpsAgent: this._httpsAgent ?? undefined }),
493
- axios_1.default.get(`${canonicalBaseUrl}/api/user/subscription`, { timeout: 5000, headers, httpsAgent: this._httpsAgent ?? undefined }),
542
+ axios.get(`${canonicalBaseUrl}/api/user/profile`, { timeout: 5000, headers, httpsAgent: this._httpsAgent ?? undefined }),
543
+ axios.get(`${canonicalBaseUrl}/api/user/subscription`, { timeout: 5000, headers, httpsAgent: this._httpsAgent ?? undefined }),
494
544
  ]);
495
545
  for (const r of results) {
496
546
  if (r.status === 'fulfilled')
@@ -509,8 +559,8 @@ class APIClient {
509
559
  Cookie: `vigthoria-auth-token=${retryToken}`,
510
560
  };
511
561
  const retryResults = await Promise.allSettled([
512
- axios_1.default.get(`${canonicalBaseUrl}/api/user/profile`, { timeout: 5000, headers: retryHeaders, httpsAgent: this._httpsAgent ?? undefined }),
513
- axios_1.default.get(`${canonicalBaseUrl}/api/user/subscription`, { timeout: 5000, headers: retryHeaders, httpsAgent: this._httpsAgent ?? undefined }),
562
+ axios.get(`${canonicalBaseUrl}/api/user/profile`, { timeout: 5000, headers: retryHeaders, httpsAgent: this._httpsAgent ?? undefined }),
563
+ axios.get(`${canonicalBaseUrl}/api/user/subscription`, { timeout: 5000, headers: retryHeaders, httpsAgent: this._httpsAgent ?? undefined }),
514
564
  ]);
515
565
  for (const rr of retryResults) {
516
566
  if (rr.status === 'fulfilled')
@@ -639,11 +689,11 @@ class APIClient {
639
689
  if (!current) {
640
690
  continue;
641
691
  }
642
- for (const entry of fs_1.default.readdirSync(current, { withFileTypes: true })) {
692
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
643
693
  if (entry.name === '.git' || entry.name === 'node_modules') {
644
694
  continue;
645
695
  }
646
- const fullPath = path_1.default.join(current, entry.name);
696
+ const fullPath = path.join(current, entry.name);
647
697
  if (entry.isDirectory()) {
648
698
  stack.push(fullPath);
649
699
  continue;
@@ -651,7 +701,7 @@ class APIClient {
651
701
  if (!entry.isFile()) {
652
702
  continue;
653
703
  }
654
- const relativePath = this.normalizeWorkspaceRelativePath(path_1.default.relative(rootPath, fullPath));
704
+ const relativePath = this.normalizeWorkspaceRelativePath(path.relative(rootPath, fullPath));
655
705
  if (relativePath) {
656
706
  results.push(relativePath);
657
707
  }
@@ -670,7 +720,7 @@ class APIClient {
670
720
  }
671
721
  }
672
722
  const scored = htmlPaths.map((relativePath) => {
673
- const baseName = path_1.default.posix.basename(relativePath).toLowerCase();
723
+ const baseName = path.posix.basename(relativePath).toLowerCase();
674
724
  const depth = relativePath.split('/').length - 1;
675
725
  let score = 0;
676
726
  if (relativePath.toLowerCase() === 'index.html') {
@@ -692,7 +742,7 @@ class APIClient {
692
742
  extractLinkedFrontendAssets(html, entryPath) {
693
743
  const css = new Set();
694
744
  const js = new Set();
695
- const entryDir = path_1.default.posix.dirname(this.normalizeWorkspaceRelativePath(entryPath) || '.');
745
+ const entryDir = path.posix.dirname(this.normalizeWorkspaceRelativePath(entryPath) || '.');
696
746
  const normalizeAsset = (assetPath) => {
697
747
  const clean = String(assetPath || '').trim();
698
748
  if (!clean || clean.startsWith('http://') || clean.startsWith('https://') || clean.startsWith('//') || clean.startsWith('data:') || clean.startsWith('#')) {
@@ -705,7 +755,7 @@ class APIClient {
705
755
  if (withoutQuery.startsWith('/')) {
706
756
  return this.normalizeWorkspaceRelativePath(withoutQuery.slice(1));
707
757
  }
708
- return this.normalizeWorkspaceRelativePath(path_1.default.posix.normalize(path_1.default.posix.join(entryDir, withoutQuery)));
758
+ return this.normalizeWorkspaceRelativePath(path.posix.normalize(path.posix.join(entryDir, withoutQuery)));
709
759
  };
710
760
  const cssPattern = /<link[^>]+href=["']([^"']+\.css(?:[?#][^"']*)?)["'][^>]*>/gi;
711
761
  const jsPattern = /<script[^>]+src=["']([^"']+\.(?:js|mjs|cjs)(?:[?#][^"']*)?)["'][^>]*>/gi;
@@ -729,7 +779,7 @@ class APIClient {
729
779
  }
730
780
  gatherFrontendPreviewArtifacts(message = '', context = {}) {
731
781
  const rootPath = this.resolveAgentTargetPath(context);
732
- if (!rootPath || !fs_1.default.existsSync(rootPath)) {
782
+ if (!rootPath || !fs.existsSync(rootPath)) {
733
783
  return { error: 'Frontend preview gate requires a readable project workspace.' };
734
784
  }
735
785
  const workspaceFiles = this.listFrontendWorkspaceFiles(rootPath);
@@ -742,7 +792,7 @@ class APIClient {
742
792
  if (!htmlPath) {
743
793
  return { error: 'Frontend preview gate could not determine a primary HTML entry file.' };
744
794
  }
745
- const html = fs_1.default.readFileSync(path_1.default.join(rootPath, htmlPath), 'utf8');
795
+ const html = fs.readFileSync(path.join(rootPath, htmlPath), 'utf8');
746
796
  const linkedAssets = this.extractLinkedFrontendAssets(html, htmlPath);
747
797
  const cssFiles = workspaceFiles.filter((filePath) => /\.css$/i.test(filePath));
748
798
  const jsFiles = workspaceFiles.filter((filePath) => /\.(?:js|mjs|cjs)$/i.test(filePath));
@@ -759,7 +809,7 @@ class APIClient {
759
809
  if (!normalized || selected.has(normalized)) {
760
810
  continue;
761
811
  }
762
- if (!fs_1.default.existsSync(path_1.default.join(rootPath, normalized))) {
812
+ if (!fs.existsSync(path.join(rootPath, normalized))) {
763
813
  continue;
764
814
  }
765
815
  selected.add(normalized);
@@ -768,8 +818,8 @@ class APIClient {
768
818
  };
769
819
  const selectedCssFiles = chooseExisting(linkedAssets.css, [...expectedCss, ...cssFiles]).slice(0, 12);
770
820
  const selectedJsFiles = chooseExisting(linkedAssets.js, [...expectedJs, ...jsFiles]).slice(0, 12);
771
- const css = selectedCssFiles.map((filePath) => fs_1.default.readFileSync(path_1.default.join(rootPath, filePath), 'utf8')).join('\n\n');
772
- const js = selectedJsFiles.map((filePath) => fs_1.default.readFileSync(path_1.default.join(rootPath, filePath), 'utf8')).join('\n\n');
821
+ const css = selectedCssFiles.map((filePath) => fs.readFileSync(path.join(rootPath, filePath), 'utf8')).join('\n\n');
822
+ const js = selectedJsFiles.map((filePath) => fs.readFileSync(path.join(rootPath, filePath), 'utf8')).join('\n\n');
773
823
  return { htmlPath, html, css, js, cssPaths: selectedCssFiles, jsPaths: selectedJsFiles };
774
824
  }
775
825
  async captureFrontendPreviewScreenshot(entryAbsolutePath, screenshotPath) {
@@ -822,14 +872,14 @@ class APIClient {
822
872
  return previewGate;
823
873
  }
824
874
  try {
825
- const artifactsDir = path_1.default.join(rootPath, '.vigthoria', 'proof', 'preview');
826
- fs_1.default.mkdirSync(artifactsDir, { recursive: true });
875
+ const artifactsDir = path.join(rootPath, '.vigthoria', 'proof', 'preview');
876
+ fs.mkdirSync(artifactsDir, { recursive: true });
827
877
  const contextId = String(context.contextId || 'preview').trim() || 'preview';
828
878
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
829
879
  const baseName = `${timestamp}-${contextId}`;
830
- const manifestPath = path_1.default.join(artifactsDir, `${baseName}.json`);
831
- const screenshotPath = path_1.default.join(artifactsDir, `${baseName}.png`);
832
- const entryAbsolutePath = path_1.default.join(rootPath, previewGate.entryPath);
880
+ const manifestPath = path.join(artifactsDir, `${baseName}.json`);
881
+ const screenshotPath = path.join(artifactsDir, `${baseName}.png`);
882
+ const entryAbsolutePath = path.join(rootPath, previewGate.entryPath);
833
883
  const previewFileUrl = `file://${entryAbsolutePath}`;
834
884
  const screenshot = await this.captureFrontendPreviewScreenshot(entryAbsolutePath, screenshotPath);
835
885
  const manifest = {
@@ -854,7 +904,7 @@ class APIClient {
854
904
  error: screenshot.error || null,
855
905
  },
856
906
  };
857
- fs_1.default.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
907
+ fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
858
908
  return {
859
909
  ...previewGate,
860
910
  artifacts: {
@@ -904,7 +954,7 @@ class APIClient {
904
954
  css,
905
955
  js,
906
956
  entryPath: artifacts.htmlPath,
907
- workspaceName: path_1.default.basename(rootPath),
957
+ workspaceName: path.basename(rootPath),
908
958
  });
909
959
  // Skip preview proof if payload is still too large (> 4 MB)
910
960
  if (Buffer.byteLength(proofPayload, 'utf8') > 4 * 1024 * 1024) {
@@ -1036,7 +1086,7 @@ class APIClient {
1036
1086
  if (!authToken) {
1037
1087
  throw new Error('Not authenticated. Run vigthoria login first.');
1038
1088
  }
1039
- const response = await axios_1.default.post(`${baseUrl}/api/auth/sso`, { token: authToken }, {
1089
+ const response = await axios.post(`${baseUrl}/api/auth/sso`, { token: authToken }, {
1040
1090
  headers: {
1041
1091
  'Content-Type': 'application/json',
1042
1092
  },
@@ -1096,7 +1146,7 @@ class APIClient {
1096
1146
  query.set('search', options.search);
1097
1147
  }
1098
1148
  const url = `${this.vigFlowEndpoint(baseUrl, '/templates')}${query.size > 0 ? `?${query.toString()}` : ''}`;
1099
- const response = await axios_1.default.get(url, {
1149
+ const response = await axios.get(url, {
1100
1150
  headers,
1101
1151
  timeout: 30000,
1102
1152
  });
@@ -1106,7 +1156,7 @@ class APIClient {
1106
1156
  }
1107
1157
  async listVigFlowWorkflows() {
1108
1158
  return this.withVigFlow('list workflows', async (baseUrl, headers) => {
1109
- const response = await axios_1.default.get(this.vigFlowEndpoint(baseUrl, '/workflows'), {
1159
+ const response = await axios.get(this.vigFlowEndpoint(baseUrl, '/workflows'), {
1110
1160
  headers,
1111
1161
  timeout: 30000,
1112
1162
  });
@@ -1158,7 +1208,7 @@ class APIClient {
1158
1208
  }
1159
1209
  async useVigFlowTemplate(templateId, options = {}) {
1160
1210
  return this.withVigFlow('use template', async (baseUrl, headers) => {
1161
- const response = await axios_1.default.post(`${this.vigFlowEndpoint(baseUrl, `/templates/${encodeURIComponent(templateId)}/use`)}`, {
1211
+ const response = await axios.post(`${this.vigFlowEndpoint(baseUrl, `/templates/${encodeURIComponent(templateId)}/use`)}`, {
1162
1212
  name: options.name,
1163
1213
  variables: options.variables || {},
1164
1214
  }, {
@@ -1174,7 +1224,7 @@ class APIClient {
1174
1224
  }
1175
1225
  async runVigFlowWorkflow(workflowId, options = {}) {
1176
1226
  return this.withVigFlow('run workflow', async (baseUrl, headers) => {
1177
- const response = await axios_1.default.post(`${this.vigFlowEndpoint(baseUrl, `/executions/run/${encodeURIComponent(workflowId)}`)}`, {
1227
+ const response = await axios.post(`${this.vigFlowEndpoint(baseUrl, `/executions/run/${encodeURIComponent(workflowId)}`)}`, {
1178
1228
  data: options.data || {},
1179
1229
  options: options.executionOptions || {},
1180
1230
  }, {
@@ -1190,7 +1240,7 @@ class APIClient {
1190
1240
  }
1191
1241
  async getVigFlowExecutionStatus(executionId) {
1192
1242
  return this.withVigFlow('execution status', async (baseUrl, headers) => {
1193
- const response = await axios_1.default.get(`${this.vigFlowEndpoint(baseUrl, `/executions/${encodeURIComponent(executionId)}`)}`, {
1243
+ const response = await axios.get(`${this.vigFlowEndpoint(baseUrl, `/executions/${encodeURIComponent(executionId)}`)}`, {
1194
1244
  headers,
1195
1245
  timeout: 30000,
1196
1246
  });
@@ -1233,7 +1283,7 @@ class APIClient {
1233
1283
  projectPath: effectiveWorkspacePath,
1234
1284
  targetPath: effectiveWorkspacePath,
1235
1285
  localWorkspacePath: localWorkspacePath || null,
1236
- localWorkspaceName: localWorkspacePath ? path_1.default.basename(localWorkspacePath) : null,
1286
+ localWorkspaceName: localWorkspacePath ? path.basename(localWorkspacePath) : null,
1237
1287
  localWorkspaceSummary,
1238
1288
  // Signal to the server that the workspace filesystem is not locally
1239
1289
  // accessible — it must hydrate a temp directory from the provided
@@ -1353,7 +1403,7 @@ class APIClient {
1353
1403
  projectPath: resolvedContext.projectPath || targetPath,
1354
1404
  targetPath,
1355
1405
  localWorkspacePath: targetPath,
1356
- localWorkspaceName: targetPath ? path_1.default.basename(targetPath) : null,
1406
+ localWorkspaceName: targetPath ? path.basename(targetPath) : null,
1357
1407
  contextId: resolvedContext.contextId,
1358
1408
  traceId: resolvedContext.traceId,
1359
1409
  requestStartedAt: resolvedContext.requestStartedAt,
@@ -1368,7 +1418,7 @@ class APIClient {
1368
1418
  if (!rootPath) {
1369
1419
  return null;
1370
1420
  }
1371
- fs_1.default.mkdirSync(rootPath, { recursive: true });
1421
+ fs.mkdirSync(rootPath, { recursive: true });
1372
1422
  const appName = this.extractEmergencyAppName(message);
1373
1423
  const html = `<!DOCTYPE html>
1374
1424
  <html lang="en">
@@ -1810,14 +1860,14 @@ menu {
1810
1860
  });
1811
1861
  });
1812
1862
  `;
1813
- fs_1.default.writeFileSync(path_1.default.join(rootPath, 'index.html'), `${html.trimEnd()}\n`, 'utf8');
1814
- fs_1.default.writeFileSync(path_1.default.join(rootPath, 'styles.css'), `${css.trimEnd()}\n`, 'utf8');
1815
- fs_1.default.writeFileSync(path_1.default.join(rootPath, 'scripts.js'), `${js.trimEnd()}\n`, 'utf8');
1863
+ fs.writeFileSync(path.join(rootPath, 'index.html'), `${html.trimEnd()}\n`, 'utf8');
1864
+ fs.writeFileSync(path.join(rootPath, 'styles.css'), `${css.trimEnd()}\n`, 'utf8');
1865
+ fs.writeFileSync(path.join(rootPath, 'scripts.js'), `${js.trimEnd()}\n`, 'utf8');
1816
1866
  return appName;
1817
1867
  }
1818
1868
  ensureExecutionContext(context = {}) {
1819
1869
  const existingId = String(context.contextId || context.traceId || '').trim();
1820
- const contextId = existingId || `vig-${Date.now()}-${(0, crypto_1.randomUUID)().slice(0, 8)}`;
1870
+ const contextId = existingId || `vig-${Date.now()}-${randomUUID().slice(0, 8)}`;
1821
1871
  return {
1822
1872
  ...context,
1823
1873
  contextId,
@@ -1921,21 +1971,21 @@ menu {
1921
1971
  }
1922
1972
  resolveServerBindableWorkspacePath(context = {}) {
1923
1973
  const candidate = this.resolveAgentTargetPath(context);
1924
- if (!candidate || this.isLikelyWindowsPath(candidate) || !path_1.default.isAbsolute(candidate) || !fs_1.default.existsSync(candidate)) {
1974
+ if (!candidate || this.isLikelyWindowsPath(candidate) || !path.isAbsolute(candidate) || !fs.existsSync(candidate)) {
1925
1975
  return '';
1926
1976
  }
1927
1977
  const configuredRoots = (process.env.VIGTHORIA_SERVER_WORKSPACE_ROOTS || '/var/www/vigthoria-user-workspaces,/var/lib/vigthoria-workspaces')
1928
1978
  .split(',')
1929
1979
  .map((entry) => entry.trim())
1930
1980
  .filter(Boolean);
1931
- const resolvedCandidate = fs_1.default.realpathSync(candidate);
1981
+ const resolvedCandidate = fs.realpathSync(candidate);
1932
1982
  for (const root of configuredRoots) {
1933
- if (!fs_1.default.existsSync(root)) {
1983
+ if (!fs.existsSync(root)) {
1934
1984
  continue;
1935
1985
  }
1936
- const resolvedRoot = fs_1.default.realpathSync(root);
1986
+ const resolvedRoot = fs.realpathSync(root);
1937
1987
  try {
1938
- if (path_1.default.relative(resolvedRoot, resolvedCandidate) === '' || !path_1.default.relative(resolvedRoot, resolvedCandidate).startsWith('..')) {
1988
+ if (path.relative(resolvedRoot, resolvedCandidate) === '' || !path.relative(resolvedRoot, resolvedCandidate).startsWith('..')) {
1939
1989
  return resolvedCandidate;
1940
1990
  }
1941
1991
  }
@@ -1946,21 +1996,21 @@ menu {
1946
1996
  return '';
1947
1997
  }
1948
1998
  buildLocalWorkspaceSummary(rootPath) {
1949
- if (!rootPath || !fs_1.default.existsSync(rootPath)) {
1999
+ if (!rootPath || !fs.existsSync(rootPath)) {
1950
2000
  return null;
1951
2001
  }
1952
2002
  try {
1953
2003
  const summary = {
1954
2004
  path: rootPath,
1955
- name: path_1.default.basename(rootPath),
2005
+ name: path.basename(rootPath),
1956
2006
  files: [],
1957
2007
  };
1958
2008
  const snapshot = this.getAgentWorkspaceSnapshot(rootPath);
1959
2009
  summary.fileCount = snapshot.fileCount;
1960
2010
  summary.files = snapshot.paths.slice(0, 40);
1961
- const packageJsonPath = path_1.default.join(rootPath, 'package.json');
1962
- if (fs_1.default.existsSync(packageJsonPath)) {
1963
- const pkg = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf8'));
2011
+ const packageJsonPath = path.join(rootPath, 'package.json');
2012
+ if (fs.existsSync(packageJsonPath)) {
2013
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
1964
2014
  summary.packageJson = {
1965
2015
  name: pkg.name || null,
1966
2016
  version: pkg.version || null,
@@ -1969,9 +2019,9 @@ menu {
1969
2019
  devDependencies: Object.keys(pkg.devDependencies || {}).slice(0, 20),
1970
2020
  };
1971
2021
  }
1972
- const readmePath = path_1.default.join(rootPath, 'README.md');
1973
- if (fs_1.default.existsSync(readmePath)) {
1974
- summary.readmeExcerpt = fs_1.default.readFileSync(readmePath, 'utf8').slice(0, 2500);
2022
+ const readmePath = path.join(rootPath, 'README.md');
2023
+ if (fs.existsSync(readmePath)) {
2024
+ summary.readmeExcerpt = fs.readFileSync(readmePath, 'utf8').slice(0, 2500);
1975
2025
  }
1976
2026
  // Hydrate workspace: include actual file contents so the V3 server
1977
2027
  // can populate the remote workspace before the agent starts.
@@ -2006,17 +2056,17 @@ menu {
2006
2056
  for (const relativePath of filePaths) {
2007
2057
  if (totalBytes >= MAX_TOTAL_BYTES)
2008
2058
  break;
2009
- const ext = path_1.default.extname(relativePath).toLowerCase();
2059
+ const ext = path.extname(relativePath).toLowerCase();
2010
2060
  if (BINARY_EXTENSIONS.has(ext))
2011
2061
  continue;
2012
2062
  if (/(^|[\/\\])\.(git|hg)([\/\\]|$)/.test(relativePath))
2013
2063
  continue;
2014
- const absolutePath = path_1.default.join(rootPath, relativePath);
2064
+ const absolutePath = path.join(rootPath, relativePath);
2015
2065
  try {
2016
- const stat = fs_1.default.statSync(absolutePath);
2066
+ const stat = fs.statSync(absolutePath);
2017
2067
  if (!stat.isFile() || stat.size > MAX_FILE_BYTES || stat.size === 0)
2018
2068
  continue;
2019
- const content = fs_1.default.readFileSync(absolutePath, 'utf8');
2069
+ const content = fs.readFileSync(absolutePath, 'utf8');
2020
2070
  // Skip likely binary content (high ratio of non-printable chars)
2021
2071
  if (/[\x00-\x08\x0e-\x1f]/.test(content.slice(0, 512)))
2022
2072
  continue;
@@ -2032,7 +2082,7 @@ menu {
2032
2082
  hasAgentWorkspaceOutput(context = {}) {
2033
2083
  try {
2034
2084
  const root = this.resolveAgentTargetPath(context);
2035
- if (!root || !fs_1.default.existsSync(root)) {
2085
+ if (!root || !fs.existsSync(root)) {
2036
2086
  return false;
2037
2087
  }
2038
2088
  const stack = [root];
@@ -2040,12 +2090,12 @@ menu {
2040
2090
  const current = stack.pop();
2041
2091
  if (!current)
2042
2092
  continue;
2043
- const entries = fs_1.default.readdirSync(current, { withFileTypes: true });
2093
+ const entries = fs.readdirSync(current, { withFileTypes: true });
2044
2094
  for (const entry of entries) {
2045
2095
  if (entry.name === '.git' || entry.name === 'node_modules') {
2046
2096
  continue;
2047
2097
  }
2048
- const fullPath = path_1.default.join(current, entry.name);
2098
+ const fullPath = path.join(current, entry.name);
2049
2099
  if (entry.isFile()) {
2050
2100
  return true;
2051
2101
  }
@@ -2069,11 +2119,11 @@ menu {
2069
2119
  if (!current) {
2070
2120
  continue;
2071
2121
  }
2072
- for (const entry of fs_1.default.readdirSync(current, { withFileTypes: true })) {
2122
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
2073
2123
  if (entry.name === '.git' || entry.name === 'node_modules') {
2074
2124
  continue;
2075
2125
  }
2076
- const fullPath = path_1.default.join(current, entry.name);
2126
+ const fullPath = path.join(current, entry.name);
2077
2127
  if (entry.isDirectory()) {
2078
2128
  stack.push(fullPath);
2079
2129
  continue;
@@ -2082,8 +2132,8 @@ menu {
2082
2132
  continue;
2083
2133
  }
2084
2134
  fileCount += 1;
2085
- const stat = fs_1.default.statSync(fullPath);
2086
- entries.push(`${path_1.default.relative(rootPath, fullPath)}:${stat.size}:${stat.mtimeMs}`);
2135
+ const stat = fs.statSync(fullPath);
2136
+ entries.push(`${path.relative(rootPath, fullPath)}:${stat.size}:${stat.mtimeMs}`);
2087
2137
  }
2088
2138
  }
2089
2139
  entries.sort();
@@ -2095,7 +2145,7 @@ menu {
2095
2145
  }
2096
2146
  async waitForAgentWorkspaceSettle(context = {}, options = {}) {
2097
2147
  const rootPath = this.resolveAgentTargetPath(context);
2098
- if (!rootPath || !fs_1.default.existsSync(rootPath)) {
2148
+ if (!rootPath || !fs.existsSync(rootPath)) {
2099
2149
  return;
2100
2150
  }
2101
2151
  const timeoutMs = options.timeoutMs || 15000;
@@ -2160,7 +2210,36 @@ menu {
2160
2210
  return Array.from(candidates);
2161
2211
  }
2162
2212
  captureV3AgentStreamMutation(event, streamedFiles, serverRoot) {
2163
- if (!event || event.type !== 'tool_call' || !streamedFiles) {
2213
+ if (!event || !streamedFiles) {
2214
+ return;
2215
+ }
2216
+ if (event.type === 'file_mutation' && typeof event.path === 'string') {
2217
+ const filePath = this.normalizeAgentWorkspaceRelativePath(event.path, serverRoot || undefined);
2218
+ if (!filePath) {
2219
+ return;
2220
+ }
2221
+ if (event.action === 'delete') {
2222
+ delete streamedFiles[filePath];
2223
+ return;
2224
+ }
2225
+ if (typeof event.content === 'string') {
2226
+ streamedFiles[filePath] = event.content;
2227
+ }
2228
+ return;
2229
+ }
2230
+ if (event.type === 'workspace_snapshot' && event.files && typeof event.files === 'object') {
2231
+ for (const [rawPath, content] of Object.entries(event.files)) {
2232
+ if (typeof content !== 'string') {
2233
+ continue;
2234
+ }
2235
+ const filePath = this.normalizeAgentWorkspaceRelativePath(rawPath, serverRoot || undefined);
2236
+ if (filePath) {
2237
+ streamedFiles[filePath] = content;
2238
+ }
2239
+ }
2240
+ return;
2241
+ }
2242
+ if (event.type !== 'tool_call') {
2164
2243
  return;
2165
2244
  }
2166
2245
  const args = event.arguments || {};
@@ -2181,18 +2260,95 @@ menu {
2181
2260
  }
2182
2261
  }
2183
2262
  }
2263
+ applyV3AgentStreamEventToWorkspace(event, context = {}, serverRoot) {
2264
+ if (!event || !['file_mutation', 'workspace_snapshot'].includes(String(event.type || ''))) {
2265
+ return false;
2266
+ }
2267
+ const rootPath = this.resolveAgentTargetPath(context);
2268
+ if (!rootPath) {
2269
+ return false;
2270
+ }
2271
+ if (event.type === 'workspace_snapshot' && event.files && typeof event.files === 'object') {
2272
+ let applied = false;
2273
+ for (const [rawPath, content] of Object.entries(event.files)) {
2274
+ if (typeof content !== 'string') {
2275
+ continue;
2276
+ }
2277
+ applied = this.writeV3AgentWorkspaceFile(rootPath, rawPath, content, serverRoot || undefined) || applied;
2278
+ }
2279
+ return applied;
2280
+ }
2281
+ if (event.type === 'file_mutation' && typeof event.path === 'string') {
2282
+ const relativePath = this.normalizeAgentWorkspaceRelativePath(event.path, serverRoot || undefined);
2283
+ if (!relativePath) {
2284
+ return false;
2285
+ }
2286
+ if (event.action === 'delete') {
2287
+ return this.deleteV3AgentWorkspaceFile(rootPath, relativePath);
2288
+ }
2289
+ if (typeof event.content === 'string') {
2290
+ return this.writeV3AgentWorkspaceFile(rootPath, relativePath, event.content, rootPath);
2291
+ }
2292
+ }
2293
+ return false;
2294
+ }
2295
+ writeV3AgentWorkspaceFile(rootPath, rawPath, content, sourceRoot) {
2296
+ const relativePath = this.normalizeAgentWorkspaceRelativePath(rawPath, sourceRoot || rootPath);
2297
+ if (!relativePath) {
2298
+ return false;
2299
+ }
2300
+ const absolutePath = path.resolve(rootPath, relativePath);
2301
+ const resolvedRoot = path.resolve(rootPath);
2302
+ if (absolutePath !== resolvedRoot && !absolutePath.startsWith(resolvedRoot + path.sep)) {
2303
+ this.logger.warn(`Refusing to write V3 file outside workspace: ${rawPath}`);
2304
+ return false;
2305
+ }
2306
+ try {
2307
+ fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
2308
+ if (fs.existsSync(absolutePath)) {
2309
+ const existing = fs.readFileSync(absolutePath, 'utf8');
2310
+ if (existing === content) {
2311
+ return false;
2312
+ }
2313
+ }
2314
+ fs.writeFileSync(absolutePath, content, 'utf8');
2315
+ return true;
2316
+ }
2317
+ catch (error) {
2318
+ this.logger.warn(`Failed to write V3 file ${relativePath}: ${error.message}`);
2319
+ return false;
2320
+ }
2321
+ }
2322
+ deleteV3AgentWorkspaceFile(rootPath, rawPath) {
2323
+ const relativePath = this.normalizeAgentWorkspaceRelativePath(rawPath, rootPath);
2324
+ if (!relativePath) {
2325
+ return false;
2326
+ }
2327
+ const absolutePath = path.resolve(rootPath, relativePath);
2328
+ const resolvedRoot = path.resolve(rootPath);
2329
+ if (absolutePath !== resolvedRoot && !absolutePath.startsWith(resolvedRoot + path.sep)) {
2330
+ this.logger.warn(`Refusing to delete V3 file outside workspace: ${rawPath}`);
2331
+ return false;
2332
+ }
2333
+ try {
2334
+ if (fs.existsSync(absolutePath) && fs.statSync(absolutePath).isFile()) {
2335
+ fs.unlinkSync(absolutePath);
2336
+ return true;
2337
+ }
2338
+ }
2339
+ catch (error) {
2340
+ this.logger.warn(`Failed to delete V3 file ${relativePath}: ${error.message}`);
2341
+ }
2342
+ return false;
2343
+ }
2184
2344
  recoverAgentWorkspaceFiles(context = {}, streamedFiles = {}, expectedFiles = []) {
2185
2345
  const rootPath = this.resolveAgentTargetPath(context);
2186
2346
  if (!rootPath || Object.keys(streamedFiles).length === 0) {
2187
2347
  return;
2188
2348
  }
2189
- // Create the local workspace directory if it doesn't exist yet.
2190
- // This is needed for remote CLI clients where the user specified a
2191
- // path (e.g., C:\vigthoria\Apps\pacman_rogue) that may not have
2192
- // been created before the V3 run.
2193
- if (!fs_1.default.existsSync(rootPath)) {
2349
+ if (!fs.existsSync(rootPath)) {
2194
2350
  try {
2195
- fs_1.default.mkdirSync(rootPath, { recursive: true });
2351
+ fs.mkdirSync(rootPath, { recursive: true });
2196
2352
  }
2197
2353
  catch {
2198
2354
  return;
@@ -2208,12 +2364,7 @@ menu {
2208
2364
  if (typeof content !== 'string') {
2209
2365
  continue;
2210
2366
  }
2211
- const absolutePath = path_1.default.join(rootPath, relativePath);
2212
- if (fs_1.default.existsSync(absolutePath)) {
2213
- continue;
2214
- }
2215
- fs_1.default.mkdirSync(path_1.default.dirname(absolutePath), { recursive: true });
2216
- fs_1.default.writeFileSync(absolutePath, content, 'utf8');
2367
+ this.writeV3AgentWorkspaceFile(rootPath, relativePath, content, rootPath);
2217
2368
  }
2218
2369
  }
2219
2370
  normalizeAgentWorkspaceRelativePath(rawPath, rootPath) {
@@ -2221,10 +2372,35 @@ menu {
2221
2372
  if (!input) {
2222
2373
  return '';
2223
2374
  }
2375
+ const safeRelative = (candidate) => {
2376
+ const stripped = String(candidate || '').replace(/^\/+/, '');
2377
+ if (!stripped || /^[a-zA-Z]:\//.test(stripped)) {
2378
+ return '';
2379
+ }
2380
+ const normalized = path.posix.normalize(stripped);
2381
+ if (!normalized || normalized === '.' || normalized === '..' || normalized.startsWith('../') || path.posix.isAbsolute(normalized)) {
2382
+ return '';
2383
+ }
2384
+ return normalized;
2385
+ };
2386
+ const internalWorkspacePatterns = [
2387
+ /^\/?var\/www\/\.vigthoria\/v3-temp\/vig-remote-[^/]+\/(.+)$/i,
2388
+ /^\.vigthoria\/v3-temp\/vig-remote-[^/]+\/(.+)$/i,
2389
+ /^\/?tmp\/vig-remote(?:-server)?-[^/]+\/(.+)$/i,
2390
+ /^\/?tmp\/vig-fork-[^/]+\/(.+)$/i,
2391
+ /^\/?home\/user\/(.+)$/i,
2392
+ /^\/?root\/(.+)$/i,
2393
+ ];
2394
+ for (const pattern of internalWorkspacePatterns) {
2395
+ const match = input.match(pattern);
2396
+ if (match && match[1]) {
2397
+ return safeRelative(match[1]);
2398
+ }
2399
+ }
2224
2400
  const normalizedRoot = String(rootPath || '').trim().replace(/\\/g, '/').replace(/\/+$/g, '');
2225
2401
  if (normalizedRoot) {
2226
2402
  const rootNoLeadingSlash = normalizedRoot.replace(/^\//, '');
2227
- const rootBase = path_1.default.posix.basename(normalizedRoot);
2403
+ const rootBase = path.posix.basename(normalizedRoot);
2228
2404
  const prefixes = [
2229
2405
  `${normalizedRoot}/`,
2230
2406
  `${rootNoLeadingSlash}/`,
@@ -2232,20 +2408,20 @@ menu {
2232
2408
  ];
2233
2409
  for (const prefix of prefixes) {
2234
2410
  if (input.startsWith(prefix)) {
2235
- return input.slice(prefix.length).replace(/^\//, '');
2411
+ return safeRelative(input.slice(prefix.length));
2236
2412
  }
2237
2413
  }
2238
2414
  const embeddedRoot = `/${rootBase}/`;
2239
2415
  const embeddedIndex = input.indexOf(embeddedRoot);
2240
2416
  if (embeddedIndex >= 0) {
2241
- return input.slice(embeddedIndex + embeddedRoot.length).replace(/^\//, '');
2417
+ return safeRelative(input.slice(embeddedIndex + embeddedRoot.length));
2242
2418
  }
2243
2419
  }
2244
- return input.replace(/^\//, '');
2420
+ return safeRelative(input);
2245
2421
  }
2246
2422
  async ensureAgentFrontendPolish(message = '', context = {}) {
2247
2423
  const rootPath = this.resolveAgentTargetPath(context);
2248
- if (!rootPath || !fs_1.default.existsSync(rootPath)) {
2424
+ if (!rootPath || !fs.existsSync(rootPath)) {
2249
2425
  return;
2250
2426
  }
2251
2427
  const prompt = String(message || '');
@@ -2255,20 +2431,20 @@ menu {
2255
2431
  if (!looksLikeFrontendTask) {
2256
2432
  return;
2257
2433
  }
2258
- const htmlPath = path_1.default.join(rootPath, 'index.html');
2259
- if (!fs_1.default.existsSync(htmlPath)) {
2434
+ const htmlPath = path.join(rootPath, 'index.html');
2435
+ if (!fs.existsSync(htmlPath)) {
2260
2436
  return;
2261
2437
  }
2262
- const html = fs_1.default.readFileSync(htmlPath, 'utf8');
2438
+ const html = fs.readFileSync(htmlPath, 'utf8');
2263
2439
  const ensuredAssets = this.ensureReferencedFrontendAssets(rootPath, html, prompt, 'Vigthoria CLI');
2264
2440
  const cssPath = ensuredAssets.cssPath;
2265
2441
  const jsPath = ensuredAssets.jsPath;
2266
2442
  let nextHtml = ensuredAssets.html;
2267
- if (!cssPath || !fs_1.default.existsSync(cssPath)) {
2443
+ if (!cssPath || !fs.existsSync(cssPath)) {
2268
2444
  return;
2269
2445
  }
2270
- let css = fs_1.default.readFileSync(cssPath, 'utf8');
2271
- let js = fs_1.default.existsSync(jsPath) ? fs_1.default.readFileSync(jsPath, 'utf8') : '';
2446
+ let css = fs.readFileSync(cssPath, 'utf8');
2447
+ let js = fs.existsSync(jsPath) ? fs.readFileSync(jsPath, 'utf8') : '';
2272
2448
  const keyframesBlocks = Array.from(js.matchAll(/@keyframes[\s\S]*?\n\}/g)).map((match) => match[0]);
2273
2449
  if (keyframesBlocks.length > 0) {
2274
2450
  const migrated = keyframesBlocks.filter((block) => !css.includes(block));
@@ -2276,8 +2452,8 @@ menu {
2276
2452
  css = `${css.trimEnd()}\n\n/* Vigthoria CLI Recovered CSS */\n${migrated.join('\n\n')}\n`;
2277
2453
  }
2278
2454
  js = js.replace(/\n?@keyframes[\s\S]*?\n\}/g, '').trim();
2279
- fs_1.default.writeFileSync(cssPath, `${css.trimEnd()}\n`, 'utf8');
2280
- fs_1.default.writeFileSync(jsPath, js ? `${js.trimEnd()}\n` : '', 'utf8');
2455
+ fs.writeFileSync(cssPath, `${css.trimEnd()}\n`, 'utf8');
2456
+ fs.writeFileSync(jsPath, js ? `${js.trimEnd()}\n` : '', 'utf8');
2281
2457
  }
2282
2458
  const wantsPricing = /(pay-as-you-go|pricing)/i.test(prompt);
2283
2459
  if (wantsPricing && !/(id="pricing"|Pay-as-you-go|pay-as-you-go|pricing tiers)/i.test(nextHtml)) {
@@ -2320,24 +2496,24 @@ menu {
2320
2496
  nextHtml = repairedAssets.html;
2321
2497
  css = repairedAssets.css;
2322
2498
  if (nextHtml !== html) {
2323
- fs_1.default.writeFileSync(htmlPath, `${nextHtml.trimEnd()}\n`, 'utf8');
2324
- nextHtml = fs_1.default.readFileSync(htmlPath, 'utf8');
2499
+ fs.writeFileSync(htmlPath, `${nextHtml.trimEnd()}\n`, 'utf8');
2500
+ nextHtml = fs.readFileSync(htmlPath, 'utf8');
2325
2501
  }
2326
- if (css !== fs_1.default.readFileSync(cssPath, 'utf8')) {
2327
- fs_1.default.writeFileSync(cssPath, `${css.trimEnd()}\n`, 'utf8');
2502
+ if (css !== fs.readFileSync(cssPath, 'utf8')) {
2503
+ fs.writeFileSync(cssPath, `${css.trimEnd()}\n`, 'utf8');
2328
2504
  }
2329
2505
  if (/classList\.add\('hidden'\)|classList\.add\("hidden"\)|classList\.add\('revealed'\)|classList\.add\("revealed"\)/.test(js)
2330
2506
  && !/\.hidden\b|\.revealed\b/.test(css)) {
2331
2507
  css = `${css.trimEnd()}\n\n/* Vigthoria CLI Visibility States */\n.hidden {\n opacity: 0;\n transform: translateY(24px);\n}\n\n.revealed {\n opacity: 1;\n transform: translateY(0);\n transition: opacity 0.7s ease, transform 0.7s ease;\n}\n`;
2332
- fs_1.default.writeFileSync(cssPath, `${css.trimEnd()}\n`, 'utf8');
2508
+ fs.writeFileSync(cssPath, `${css.trimEnd()}\n`, 'utf8');
2333
2509
  }
2334
2510
  if (/^\s*sections\./m.test(js) && !/(?:const|let|var)\s+sections\s*=/.test(js)) {
2335
2511
  js = `const sections = document.querySelectorAll('section');\n\n${js.trimStart()}`;
2336
- fs_1.default.writeFileSync(jsPath, `${js.trimEnd()}\n`, 'utf8');
2512
+ fs.writeFileSync(jsPath, `${js.trimEnd()}\n`, 'utf8');
2337
2513
  }
2338
2514
  if (!/@media|matchMedia|mobile-nav|hamburger|menu-toggle/i.test(`${nextHtml}\n${css}\n${js}`)) {
2339
2515
  css = `${css.trimEnd()}\n\n/* Vigthoria CLI Responsive Baseline */\n@media (max-width: 900px) {\n .nav-links, .nav-list, nav ul {\n display: none;\n flex-direction: column;\n gap: 0.75rem;\n }\n\n .nav-links.is-open, .nav-list.is-open, nav ul.is-open {\n display: flex;\n }\n\n .mobile-nav-toggle, #menu-toggle, .menu-toggle {\n display: inline-flex;\n }\n}\n`;
2340
- fs_1.default.writeFileSync(cssPath, `${css.trimEnd()}\n`, 'utf8');
2516
+ fs.writeFileSync(cssPath, `${css.trimEnd()}\n`, 'utf8');
2341
2517
  }
2342
2518
  const combined = `${nextHtml}\n${css}\n${js}`;
2343
2519
  if (/IntersectionObserver|motion-reveal|\.revealed\b|vigCliFadeIn|classList\.add\('is-visible'\)|classList\.add\("is-visible"\)/i.test(combined)) {
@@ -2346,10 +2522,10 @@ menu {
2346
2522
  const cssMarker = '/* Vigthoria CLI Motion Enhancement */';
2347
2523
  const jsMarker = '/* Vigthoria CLI Motion Enhancement */';
2348
2524
  if (!css.includes(cssMarker)) {
2349
- fs_1.default.appendFileSync(cssPath, `\n\n${cssMarker}\n.hero, section {\n opacity: 0;\n transform: translateY(24px);\n animation: vigCliFadeIn 0.8s ease forwards;\n}\n\nsection {\n animation-delay: 0.12s;\n}\n\nbutton, .cta, a {\n transition: transform 0.25s ease, opacity 0.25s ease;\n}\n\nbutton:hover, .cta:hover, a:hover {\n transform: translateY(-2px);\n}\n\n.motion-reveal {\n opacity: 0;\n transform: translateY(24px);\n transition: opacity 0.7s ease, transform 0.7s ease;\n}\n\n.motion-reveal.is-visible {\n opacity: 1;\n transform: translateY(0);\n}\n\n@keyframes vigCliFadeIn {\n from {\n opacity: 0;\n transform: translateY(24px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n`, 'utf8');
2525
+ fs.appendFileSync(cssPath, `\n\n${cssMarker}\n.hero, section {\n opacity: 0;\n transform: translateY(24px);\n animation: vigCliFadeIn 0.8s ease forwards;\n}\n\nsection {\n animation-delay: 0.12s;\n}\n\nbutton, .cta, a {\n transition: transform 0.25s ease, opacity 0.25s ease;\n}\n\nbutton:hover, .cta:hover, a:hover {\n transform: translateY(-2px);\n}\n\n.motion-reveal {\n opacity: 0;\n transform: translateY(24px);\n transition: opacity 0.7s ease, transform 0.7s ease;\n}\n\n.motion-reveal.is-visible {\n opacity: 1;\n transform: translateY(0);\n}\n\n@keyframes vigCliFadeIn {\n from {\n opacity: 0;\n transform: translateY(24px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n`, 'utf8');
2350
2526
  }
2351
2527
  if (!js.includes(jsMarker)) {
2352
- fs_1.default.appendFileSync(jsPath, `\n\n${jsMarker}\ndocument.addEventListener('DOMContentLoaded', () => {\n const revealTargets = document.querySelectorAll('section, .hero, .project-grid > *, .journal-preview > *');\n if (typeof IntersectionObserver !== 'function') {\n revealTargets.forEach((element) => element.classList.add('is-visible'));\n return;\n }\n\n const observer = new IntersectionObserver((entries) => {\n entries.forEach((entry) => {\n if (entry.isIntersecting) {\n entry.target.classList.add('is-visible');\n observer.unobserve(entry.target);\n }\n });\n }, { threshold: 0.16 });\n\n revealTargets.forEach((element, index) => {\n element.classList.add('motion-reveal');\n element.style.transitionDelay = String(Math.min(index * 60, 320)) + 'ms';\n observer.observe(element);\n });\n});\n`, 'utf8');
2528
+ fs.appendFileSync(jsPath, `\n\n${jsMarker}\ndocument.addEventListener('DOMContentLoaded', () => {\n const revealTargets = document.querySelectorAll('section, .hero, .project-grid > *, .journal-preview > *');\n if (typeof IntersectionObserver !== 'function') {\n revealTargets.forEach((element) => element.classList.add('is-visible'));\n return;\n }\n\n const observer = new IntersectionObserver((entries) => {\n entries.forEach((entry) => {\n if (entry.isIntersecting) {\n entry.target.classList.add('is-visible');\n observer.unobserve(entry.target);\n }\n });\n }, { threshold: 0.16 });\n\n revealTargets.forEach((element, index) => {\n element.classList.add('motion-reveal');\n element.style.transitionDelay = String(Math.min(index * 60, 320)) + 'ms';\n observer.observe(element);\n });\n});\n`, 'utf8');
2353
2529
  }
2354
2530
  }
2355
2531
  injectSectionBeforeFooter(html, sectionMarkup) {
@@ -2383,10 +2559,10 @@ menu {
2383
2559
  if (!normalized) {
2384
2560
  return '';
2385
2561
  }
2386
- const absolutePath = path_1.default.join(rootPath, normalized);
2387
- if (!fs_1.default.existsSync(absolutePath)) {
2388
- fs_1.default.mkdirSync(path_1.default.dirname(absolutePath), { recursive: true });
2389
- fs_1.default.writeFileSync(absolutePath, content, 'utf8');
2562
+ const absolutePath = path.join(rootPath, normalized);
2563
+ if (!fs.existsSync(absolutePath)) {
2564
+ fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
2565
+ fs.writeFileSync(absolutePath, content, 'utf8');
2390
2566
  }
2391
2567
  return absolutePath;
2392
2568
  };
@@ -2396,8 +2572,8 @@ menu {
2396
2572
  const jsRefs = Array.from(html.matchAll(/<script[^>]+src=["']([^"']+\.(?:js|mjs)(?:\?[^"']*)?)["']/gi))
2397
2573
  .map((match) => localAssetPath(match[1]))
2398
2574
  .filter(Boolean);
2399
- let cssPath = cssRefs.map((ref) => path_1.default.join(rootPath, ref)).find((candidate) => fs_1.default.existsSync(candidate)) || '';
2400
- let jsPath = jsRefs.map((ref) => path_1.default.join(rootPath, ref)).find((candidate) => fs_1.default.existsSync(candidate)) || '';
2575
+ let cssPath = cssRefs.map((ref) => path.join(rootPath, ref)).find((candidate) => fs.existsSync(candidate)) || '';
2576
+ let jsPath = jsRefs.map((ref) => path.join(rootPath, ref)).find((candidate) => fs.existsSync(candidate)) || '';
2401
2577
  const cssTemplate = this.buildFallbackFrontendCss(surfaceLabel);
2402
2578
  const jsTemplate = this.buildFallbackFrontendJs(surfaceLabel);
2403
2579
  if (!cssPath && cssRefs.length > 0) {
@@ -2425,7 +2601,7 @@ menu {
2425
2601
  return {
2426
2602
  html,
2427
2603
  cssPath,
2428
- jsPath: jsPath || path_1.default.join(rootPath, 'scripts.js'),
2604
+ jsPath: jsPath || path.join(rootPath, 'scripts.js'),
2429
2605
  };
2430
2606
  }
2431
2607
  buildFallbackFrontendCss(surfaceLabel) {
@@ -2576,7 +2752,7 @@ document.addEventListener('DOMContentLoaded', () => {
2576
2752
  return true;
2577
2753
  }
2578
2754
  const sanitized = candidate.split('?')[0].split('#')[0].replace(/^\//, '');
2579
- return !sanitized || fs_1.default.existsSync(path_1.default.join(rootPath, sanitized));
2755
+ return !sanitized || fs.existsSync(path.join(rootPath, sanitized));
2580
2756
  };
2581
2757
  const repairedHtml = html.replace(/(<img\b[^>]*\ssrc=["'])([^"']+)(["'][^>]*>)/gi, (match, prefix, assetPath, suffix) => {
2582
2758
  if (hasLocalAsset(assetPath)) {
@@ -2598,22 +2774,22 @@ document.addEventListener('DOMContentLoaded', () => {
2598
2774
  formatV3AgentResponse(data) {
2599
2775
  const result = data?.result || {};
2600
2776
  if (typeof result === 'string') {
2601
- return result;
2777
+ return sanitizeUserFacingPathText(result);
2602
2778
  }
2603
2779
  if (typeof result?.summary === 'string' && result.summary.trim()) {
2604
- return result.summary;
2780
+ return sanitizeUserFacingPathText(result.summary);
2605
2781
  }
2606
2782
  if (typeof result?.message === 'string' && result.message.trim()) {
2607
- return result.message;
2783
+ return sanitizeUserFacingPathText(result.message);
2608
2784
  }
2609
2785
  if (Array.isArray(data?.events)) {
2610
2786
  const completionEvent = [...data.events].reverse().find((event) => event && event.type === 'complete' && typeof event.summary === 'string' && event.summary.trim());
2611
2787
  if (completionEvent) {
2612
- return completionEvent.summary;
2788
+ return sanitizeUserFacingPathText(completionEvent.summary);
2613
2789
  }
2614
2790
  const messageEvent = [...data.events].reverse().find((event) => event && event.type === 'message' && typeof event.content === 'string' && event.content.trim());
2615
2791
  if (messageEvent) {
2616
- return messageEvent.content;
2792
+ return sanitizeUserFacingPathText(messageEvent.content);
2617
2793
  }
2618
2794
  // Synthesize a grounded answer from the tool-call evidence the
2619
2795
  // agent produced, rather than dumping the raw event trace.
@@ -2625,9 +2801,9 @@ document.addEventListener('DOMContentLoaded', () => {
2625
2801
  // Last resort: if data has files written, report them.
2626
2802
  if (data?.files && typeof data.files === 'object' && Object.keys(data.files).length > 0) {
2627
2803
  const fileList = Object.keys(data.files).join(', ');
2628
- return `Agent wrote workspace files: ${fileList}`;
2804
+ return `Agent wrote workspace files: ${sanitizeUserFacingPathText(fileList)}`;
2629
2805
  }
2630
- const text = JSON.stringify(data, null, 2);
2806
+ const text = sanitizeUserFacingPathText(JSON.stringify(data, null, 2));
2631
2807
  return text.length > 12000 ? `${text.slice(0, 12000)}\n\n[V3 agent output truncated]` : text;
2632
2808
  }
2633
2809
  /**
@@ -2645,27 +2821,27 @@ document.addEventListener('DOMContentLoaded', () => {
2645
2821
  if (event.type === 'tool_result' && event.success && typeof event.output === 'string') {
2646
2822
  const name = event.name || 'unknown_tool';
2647
2823
  if (name === 'read_file' && typeof event.target === 'string') {
2648
- filesRead.push(event.target);
2824
+ filesRead.push(sanitizeUserFacingPathText(event.target));
2649
2825
  }
2650
2826
  else if ((name === 'write_file' || name === 'create_file') && typeof event.target === 'string') {
2651
- filesWritten.push(event.target);
2827
+ filesWritten.push(sanitizeUserFacingPathText(event.target));
2652
2828
  }
2653
2829
  else {
2654
2830
  // Keep last ~300 chars of output for context
2655
2831
  const excerpt = event.output.length > 300 ? event.output.slice(-300) : event.output;
2656
- toolResults.push(`[${name}] ${excerpt}`);
2832
+ toolResults.push(`[${name}] ${sanitizeUserFacingPathText(excerpt)}`);
2657
2833
  }
2658
2834
  }
2659
2835
  if (event.type === 'assistant' && typeof event.content === 'string' && event.content.trim()) {
2660
- assistantFragments.push(event.content.trim());
2836
+ assistantFragments.push(sanitizeUserFacingPathText(event.content.trim()));
2661
2837
  }
2662
2838
  // Some servers emit 'text' events for incremental assistant text
2663
2839
  if (event.type === 'text' && typeof event.content === 'string' && event.content.trim()) {
2664
- assistantFragments.push(event.content.trim());
2840
+ assistantFragments.push(sanitizeUserFacingPathText(event.content.trim()));
2665
2841
  }
2666
2842
  // Some servers emit content_block_delta for streamed text
2667
2843
  if (event.type === 'content_block_delta' && typeof event.delta?.text === 'string' && event.delta.text.trim()) {
2668
- assistantFragments.push(event.delta.text.trim());
2844
+ assistantFragments.push(sanitizeUserFacingPathText(event.delta.text.trim()));
2669
2845
  }
2670
2846
  }
2671
2847
  // Concatenate ALL assistant text fragments in order — keeps full
@@ -2689,6 +2865,25 @@ document.addEventListener('DOMContentLoaded', () => {
2689
2865
  ? sections.join('\n\n')
2690
2866
  : '';
2691
2867
  }
2868
+ sanitizeV3AgentEventForUser(event) {
2869
+ const sanitizeValue = (value) => {
2870
+ if (typeof value === 'string') {
2871
+ return sanitizeUserFacingPathText(value);
2872
+ }
2873
+ if (Array.isArray(value)) {
2874
+ return value.map((entry) => sanitizeValue(entry));
2875
+ }
2876
+ if (value && typeof value === 'object') {
2877
+ const copy = {};
2878
+ for (const [key, entry] of Object.entries(value)) {
2879
+ copy[key] = sanitizeValue(entry);
2880
+ }
2881
+ return copy;
2882
+ }
2883
+ return value;
2884
+ };
2885
+ return sanitizeValue(event);
2886
+ }
2692
2887
  async collectV3AgentStream(response, context = {}) {
2693
2888
  if (!response.body || typeof response.body.getReader !== 'function') {
2694
2889
  return response.json();
@@ -2762,7 +2957,8 @@ document.addEventListener('DOMContentLoaded', () => {
2762
2957
  continue;
2763
2958
  }
2764
2959
  const event = JSON.parse(payload);
2765
- events.push(event);
2960
+ const userEvent = this.sanitizeV3AgentEventForUser(event);
2961
+ events.push(userEvent);
2766
2962
  if (!contextId && typeof event.context_id === 'string' && event.context_id.trim()) {
2767
2963
  contextId = event.context_id.trim();
2768
2964
  }
@@ -2770,6 +2966,7 @@ document.addEventListener('DOMContentLoaded', () => {
2770
2966
  serverWorkspaceRoot = event.workspace_root.trim();
2771
2967
  }
2772
2968
  this.captureV3AgentStreamMutation(event, streamedFiles, serverWorkspaceRoot);
2969
+ this.applyV3AgentStreamEventToWorkspace(event, context, serverWorkspaceRoot);
2773
2970
  // Empty workspace guard: if the remote agent lists its root
2774
2971
  // and finds nothing while our local workspace has files, the
2775
2972
  // workspace was not hydrated. Abort early with a clear error
@@ -2786,7 +2983,7 @@ document.addEventListener('DOMContentLoaded', () => {
2786
2983
  || /^\[\/tmp\/vig-remote-server-[^\]]+\]\s*$/.test(listOutput);
2787
2984
  if (looksEmpty) {
2788
2985
  const localPath = this.resolveAgentTargetPath(context);
2789
- const localHasFiles = localPath && fs_1.default.existsSync(localPath) && this.hasAgentWorkspaceOutput({ ...context, projectPath: localPath, targetPath: localPath });
2986
+ const localHasFiles = localPath && fs.existsSync(localPath) && this.hasAgentWorkspaceOutput({ ...context, projectPath: localPath, targetPath: localPath });
2790
2987
  if (localHasFiles) {
2791
2988
  throw new Error('Remote workspace is empty — the V3 server did not receive your project files. '
2792
2989
  + 'Your local workspace has files but the remote agent sees an empty directory. '
@@ -2796,7 +2993,7 @@ document.addEventListener('DOMContentLoaded', () => {
2796
2993
  }
2797
2994
  if (typeof context.onStreamEvent === 'function') {
2798
2995
  try {
2799
- context.onStreamEvent(event);
2996
+ context.onStreamEvent(userEvent);
2800
2997
  }
2801
2998
  catch {
2802
2999
  // Ignore UI callback failures; never break the agent stream for them.
@@ -3110,6 +3307,8 @@ document.addEventListener('DOMContentLoaded', () => {
3110
3307
  mcp_context_id: executionContext.mcpContextId || null,
3111
3308
  rawPrompt: executionContext.rawPrompt || null,
3112
3309
  requestStartedAt: executionContext.requestStartedAt,
3310
+ vigthoriaBrain: executionContext.vigthoriaBrain || null,
3311
+ vigthoria_brain: executionContext.vigthoriaBrain || null,
3113
3312
  },
3114
3313
  workflow_type: executionContext.workflowType || 'full',
3115
3314
  options: {
@@ -3350,7 +3549,7 @@ document.addEventListener('DOMContentLoaded', () => {
3350
3549
  try {
3351
3550
  this.logger.debug(`Canonical Vigthoria Cloud fallback: ${resolvedModel}`);
3352
3551
  const token = this.getAccessToken();
3353
- const response = await axios_1.default.post('https://coder.vigthoria.io/api/ai/chat', {
3552
+ const response = await axios.post('https://coder.vigthoria.io/api/ai/chat', {
3354
3553
  messages,
3355
3554
  model: resolvedModel,
3356
3555
  maxTokens: this.config.get('preferences').maxTokens,
@@ -3484,13 +3683,20 @@ document.addEventListener('DOMContentLoaded', () => {
3484
3683
  const selfHostedModels = new Set([
3485
3684
  'vigthoria-v3-code-35b',
3486
3685
  'vigthoria-v3-code-35b:latest',
3686
+ 'vigthoria-v3-code-9b',
3687
+ 'vigthoria-v3-code-9b:latest',
3688
+ 'vigthoria-v3-balanced-4b',
3689
+ 'vigthoria-v3-balanced-4b:latest',
3487
3690
  'qwen3-coder:latest',
3488
- 'vigthoria-v2-code-8b',
3489
3691
  ]);
3490
3692
  return selfHostedModels.has(resolvedModel)
3491
3693
  || normalizedRequested === 'agent'
3492
3694
  || normalizedRequested === 'code'
3493
3695
  || normalizedRequested === 'code-30b'
3696
+ || normalizedRequested === 'code-35b'
3697
+ || normalizedRequested === 'code-9b'
3698
+ || normalizedRequested === 'balanced'
3699
+ || normalizedRequested === 'balanced-4b'
3494
3700
  || normalizedRequested === 'pro';
3495
3701
  }
3496
3702
  getSelfHostedFallbackModelId(resolvedModel, requestedModel) {
@@ -3504,7 +3710,7 @@ document.addEventListener('DOMContentLoaded', () => {
3504
3710
  const wsUrl = this.config.get('wsUrl');
3505
3711
  const token = this.config.get('authToken');
3506
3712
  return new Promise((resolve, reject) => {
3507
- const ws = new ws_1.default(`${wsUrl}/chat`, {
3713
+ const ws = new WebSocket(`${wsUrl}/chat`, {
3508
3714
  headers: { Authorization: `Bearer ${token}` },
3509
3715
  });
3510
3716
  ws.on('open', () => {
@@ -3533,7 +3739,7 @@ document.addEventListener('DOMContentLoaded', () => {
3533
3739
  const wsUrl = this.config.get('wsUrl');
3534
3740
  const token = this.config.get('authToken');
3535
3741
  return new Promise((resolve, reject) => {
3536
- const ws = new ws_1.default(`${wsUrl}/chat`, {
3742
+ const ws = new WebSocket(`${wsUrl}/chat`, {
3537
3743
  headers: { Authorization: `Bearer ${token}` },
3538
3744
  });
3539
3745
  ws.on('open', () => {
@@ -4430,15 +4636,17 @@ document.addEventListener('DOMContentLoaded', () => {
4430
4636
  // ═══════════════════════════════════════════════════════════════
4431
4637
  // VIGTHORIA LOCAL - Self-hosted models
4432
4638
  // ═══════════════════════════════════════════════════════════════
4433
- 'fast': 'vigthoria-fast-1.7b',
4639
+ 'fast': 'vigthoria-v3-balanced-4b',
4434
4640
  'mini': 'vigthoria-mini-0.6b',
4435
- 'balanced': 'vigthoria-balanced-4b',
4436
- 'balanced-4b': 'vigthoria-balanced-4b',
4641
+ 'balanced': 'vigthoria-v3-balanced-4b',
4642
+ 'balanced-4b': 'vigthoria-v3-balanced-4b',
4437
4643
  'creative': 'vigthoria-creative-9b-v4',
4438
- // Code Models - 30B is the default powerhouse
4644
+ // Code Models - 35B is the default powerhouse
4439
4645
  'code': 'vigthoria-v3-code-35b', // Internal: self-hosted 35B on Blackwell
4440
4646
  'code-30b': 'vigthoria-v3-code-35b',
4441
- 'code-8b': 'vigthoria-v2-code-8b',
4647
+ 'code-35b': 'vigthoria-v3-code-35b',
4648
+ 'code-8b': 'vigthoria-v3-code-9b',
4649
+ 'code-9b': 'vigthoria-v3-code-9b',
4442
4650
  'pro': 'vigthoria-v3-code-35b',
4443
4651
  'agent': 'vigthoria-v3-code-35b',
4444
4652
  'vigthoria-code': 'vigthoria-v3-code-35b',
@@ -4738,7 +4946,7 @@ document.addEventListener('DOMContentLoaded', () => {
4738
4946
  const port = Number.parseInt(process.env.VIGTHORIA_DEVTOOLS_BRIDGE_PORT || '4016', 10);
4739
4947
  const endpoint = `ws://${host}:${port}/ws`;
4740
4948
  return new Promise((resolve) => {
4741
- const socket = net_1.default.connect({ host, port, timeout: 1500 }, () => {
4949
+ const socket = net.connect({ host, port, timeout: 1500 }, () => {
4742
4950
  socket.end();
4743
4951
  resolve({
4744
4952
  name: 'DevTools Bridge',
@@ -4807,4 +5015,3 @@ document.addEventListener('DOMContentLoaded', () => {
4807
5015
  return status.overallOk;
4808
5016
  }
4809
5017
  }
4810
- exports.APIClient = APIClient;