vigthoria-cli 1.9.10 → 1.9.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/commands/auth.js +48 -65
- package/dist/commands/bridge.js +12 -19
- package/dist/commands/cancel.js +15 -22
- package/dist/commands/chat.d.ts +35 -0
- package/dist/commands/chat.js +747 -256
- package/dist/commands/config.js +31 -71
- package/dist/commands/deploy.js +83 -123
- package/dist/commands/device.d.ts +35 -0
- package/dist/commands/device.js +239 -0
- package/dist/commands/edit.js +32 -39
- package/dist/commands/explain.js +18 -25
- package/dist/commands/fork.js +22 -27
- package/dist/commands/generate.js +37 -44
- package/dist/commands/history.js +20 -25
- package/dist/commands/hub.js +95 -102
- package/dist/commands/index.js +41 -46
- package/dist/commands/legion.d.ts +1 -0
- package/dist/commands/legion.js +162 -209
- package/dist/commands/preview.js +60 -98
- package/dist/commands/replay.js +27 -32
- package/dist/commands/repo.js +103 -141
- package/dist/commands/review.js +29 -36
- package/dist/commands/security.js +5 -12
- package/dist/commands/update.js +15 -49
- package/dist/commands/workflow.d.ts +8 -1
- package/dist/commands/workflow.js +53 -19
- package/dist/index.js +409 -234
- package/dist/utils/api.d.ts +5 -0
- package/dist/utils/api.js +373 -166
- package/dist/utils/bridge-client.js +11 -52
- package/dist/utils/cli-state.d.ts +54 -0
- package/dist/utils/cli-state.js +185 -0
- package/dist/utils/config.d.ts +5 -0
- package/dist/utils/config.js +35 -14
- package/dist/utils/context-ranker.js +15 -21
- package/dist/utils/files.js +5 -42
- package/dist/utils/logger.js +42 -50
- package/dist/utils/post-write-validator.js +22 -29
- package/dist/utils/project-memory.d.ts +56 -0
- package/dist/utils/project-memory.js +289 -0
- package/dist/utils/session.d.ts +29 -3
- package/dist/utils/session.js +137 -85
- package/dist/utils/task-display.js +13 -20
- package/dist/utils/tools.d.ts +19 -0
- package/dist/utils/tools.js +84 -87
- package/dist/utils/workspace-cache.js +18 -26
- package/dist/utils/workspace-stream.js +26 -64
- package/install.ps1 +15 -1
- package/install.sh +1 -1
- package/package.json +5 -3
- package/scripts/release/LOCAL_MACHINE_USER_VERIFICATION.md +1 -1
- 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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 ?
|
|
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 || '
|
|
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
|
-
|| '
|
|
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 || '
|
|
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
|
-
|
|
493
|
-
|
|
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
|
-
|
|
513
|
-
|
|
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
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 || !
|
|
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 =
|
|
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 (!
|
|
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) =>
|
|
772
|
-
const js = selectedJsFiles.map((filePath) =>
|
|
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 =
|
|
826
|
-
|
|
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 =
|
|
831
|
-
const screenshotPath =
|
|
832
|
-
const entryAbsolutePath =
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 ?
|
|
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 ?
|
|
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
|
-
|
|
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
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
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()}-${
|
|
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) || !
|
|
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 =
|
|
1981
|
+
const resolvedCandidate = fs.realpathSync(candidate);
|
|
1932
1982
|
for (const root of configuredRoots) {
|
|
1933
|
-
if (!
|
|
1983
|
+
if (!fs.existsSync(root)) {
|
|
1934
1984
|
continue;
|
|
1935
1985
|
}
|
|
1936
|
-
const resolvedRoot =
|
|
1986
|
+
const resolvedRoot = fs.realpathSync(root);
|
|
1937
1987
|
try {
|
|
1938
|
-
if (
|
|
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 || !
|
|
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:
|
|
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 =
|
|
1962
|
-
if (
|
|
1963
|
-
const pkg = JSON.parse(
|
|
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 =
|
|
1973
|
-
if (
|
|
1974
|
-
summary.readmeExcerpt =
|
|
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 =
|
|
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 =
|
|
2064
|
+
const absolutePath = path.join(rootPath, relativePath);
|
|
2015
2065
|
try {
|
|
2016
|
-
const stat =
|
|
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 =
|
|
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 || !
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
2086
|
-
entries.push(`${
|
|
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 || !
|
|
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 ||
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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)
|
|
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)
|
|
2417
|
+
return safeRelative(input.slice(embeddedIndex + embeddedRoot.length));
|
|
2242
2418
|
}
|
|
2243
2419
|
}
|
|
2244
|
-
return input
|
|
2420
|
+
return safeRelative(input);
|
|
2245
2421
|
}
|
|
2246
2422
|
async ensureAgentFrontendPolish(message = '', context = {}) {
|
|
2247
2423
|
const rootPath = this.resolveAgentTargetPath(context);
|
|
2248
|
-
if (!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 =
|
|
2259
|
-
if (!
|
|
2434
|
+
const htmlPath = path.join(rootPath, 'index.html');
|
|
2435
|
+
if (!fs.existsSync(htmlPath)) {
|
|
2260
2436
|
return;
|
|
2261
2437
|
}
|
|
2262
|
-
const html =
|
|
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 || !
|
|
2443
|
+
if (!cssPath || !fs.existsSync(cssPath)) {
|
|
2268
2444
|
return;
|
|
2269
2445
|
}
|
|
2270
|
-
let css =
|
|
2271
|
-
let js =
|
|
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
|
-
|
|
2280
|
-
|
|
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
|
-
|
|
2324
|
-
nextHtml =
|
|
2499
|
+
fs.writeFileSync(htmlPath, `${nextHtml.trimEnd()}\n`, 'utf8');
|
|
2500
|
+
nextHtml = fs.readFileSync(htmlPath, 'utf8');
|
|
2325
2501
|
}
|
|
2326
|
-
if (css !==
|
|
2327
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
2387
|
-
if (!
|
|
2388
|
-
|
|
2389
|
-
|
|
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) =>
|
|
2400
|
-
let jsPath = jsRefs.map((ref) =>
|
|
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 ||
|
|
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 ||
|
|
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
|
-
|
|
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 &&
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
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 -
|
|
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-
|
|
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 =
|
|
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;
|