vigthoria-cli 1.13.31 → 1.13.42
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 +11 -56
- package/SECURITY_HARDENING.md +2 -4
- package/dist/commands/auth.js +16 -14
- package/dist/commands/cancel.d.ts +1 -0
- package/dist/commands/cancel.js +21 -4
- package/dist/commands/chat.d.ts +13 -0
- package/dist/commands/chat.js +170 -14
- package/dist/commands/fork.js +8 -3
- package/dist/commands/history.js +8 -3
- package/dist/commands/legion.js +1 -1
- package/dist/commands/replay.js +8 -3
- package/dist/commands/update-registration.js +63 -206
- package/dist/commands/v4-menu.d.ts +0 -1
- package/dist/commands/v4-menu.js +8 -6
- package/dist/commands/v4-registration.js +0 -2
- package/dist/commands/v4.js +9 -4
- package/dist/commands/wallet.d.ts +1 -1
- package/dist/commands/wallet.js +3 -3
- package/dist/index.js +2 -3
- package/dist/utils/agent-stream-state.d.ts +7 -0
- package/dist/utils/agent-stream-state.js +31 -0
- package/dist/utils/agentRunOutcome.js +5 -1
- package/dist/utils/api.d.ts +21 -3
- package/dist/utils/api.js +379 -550
- package/dist/utils/frontend-preview-service.d.ts +12 -2
- package/dist/utils/frontend-preview-service.js +214 -24
- package/dist/utils/javascript-syntax.d.ts +19 -0
- package/dist/utils/javascript-syntax.js +35 -0
- package/dist/utils/localTestMode.js +1 -1
- package/dist/utils/mutation-journal.d.ts +10 -0
- package/dist/utils/mutation-journal.js +104 -76
- package/dist/utils/network-policy.js +7 -10
- package/dist/utils/preview-screenshot-adapter.d.ts +111 -1
- package/dist/utils/preview-screenshot-adapter.js +990 -7
- package/dist/utils/release-install.d.ts +0 -1
- package/dist/utils/release-install.js +44 -7
- package/dist/utils/requestIntent.d.ts +1 -1
- package/dist/utils/requestIntent.js +17 -3
- package/dist/utils/tools.js +21 -15
- package/dist/utils/update-policy.d.ts +0 -4
- package/dist/utils/update-policy.js +5 -9
- package/dist/utils/v3-agent-client.js +25 -11
- package/install.ps1 +30 -50
- package/install.sh +12 -23
- package/package.json +4 -2
- package/release-policy.json +1 -5
- package/scripts/release/LOCAL_MACHINE_USER_VERIFICATION.md +6 -7
- package/scripts/release/install-release.mjs +5 -4
- package/scripts/release/publish-cli-release.mjs +21 -8
- package/scripts/release/test-balanced-model-live.sh +4 -1
- package/scripts/release/validate-live-service-gates.sh +27 -5
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MutationJournalReport } from './mutation-journal.js';
|
|
2
|
-
import type { PreviewScreenshotPort } from './preview-screenshot-adapter.js';
|
|
2
|
+
import type { PreviewRuntimeInteractionResult, PreviewScreenshotPort } from './preview-screenshot-adapter.js';
|
|
3
3
|
export interface FrontendPreviewGateResult {
|
|
4
4
|
required: boolean;
|
|
5
5
|
passed: boolean;
|
|
@@ -19,6 +19,7 @@ export interface FrontendPreviewGateResult {
|
|
|
19
19
|
previewFileUrl?: string;
|
|
20
20
|
screenshotCaptured?: boolean;
|
|
21
21
|
screenshotError?: string;
|
|
22
|
+
runtimeInteraction?: PreviewRuntimeInteractionResult;
|
|
22
23
|
};
|
|
23
24
|
modes?: {
|
|
24
25
|
design?: {
|
|
@@ -43,6 +44,7 @@ export interface FrontendPreviewGateResult {
|
|
|
43
44
|
export interface FrontendPreviewServiceDependencies {
|
|
44
45
|
getBaseUrls(): string[];
|
|
45
46
|
getAccessToken(): string | null;
|
|
47
|
+
getServiceKey?(): string | null;
|
|
46
48
|
fetch(input: string, init: RequestInit): Promise<Response>;
|
|
47
49
|
resolveTargetPath(context: Record<string, any>): string;
|
|
48
50
|
extractExpectedFiles(message: string, context: Record<string, any>): string[];
|
|
@@ -51,10 +53,17 @@ export interface FrontendPreviewServiceDependencies {
|
|
|
51
53
|
screenshotAdapter: PreviewScreenshotPort;
|
|
52
54
|
}
|
|
53
55
|
export declare function isConstrainedStaticFrontendRequest(message?: string): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* A code artifact can belong to a browser product without itself being a
|
|
58
|
+
* previewable application. Requiring an HTML entry for an explicitly scoped
|
|
59
|
+
* module/library request turns a successful native verification into a false
|
|
60
|
+
* failure and rolls the requested file back.
|
|
61
|
+
*/
|
|
62
|
+
export declare function isFocusedNonPreviewableCodeArtifactRequest(message?: string, expectedFiles?: string[]): boolean;
|
|
54
63
|
export declare class FrontendPreviewService {
|
|
55
64
|
private readonly dependencies;
|
|
56
65
|
constructor(dependencies: FrontendPreviewServiceDependencies);
|
|
57
|
-
|
|
66
|
+
isFrontendTask(message?: string, context?: Record<string, any>): boolean;
|
|
58
67
|
/**
|
|
59
68
|
* Returns true when the prompt describes a read-only / analysis task.
|
|
60
69
|
* Used to suppress preview gate and other write-oriented side effects.
|
|
@@ -69,6 +78,7 @@ export declare class FrontendPreviewService {
|
|
|
69
78
|
js: string[];
|
|
70
79
|
assets: string[];
|
|
71
80
|
};
|
|
81
|
+
private expandLinkedFrontendDependencyGraph;
|
|
72
82
|
private buildFrontendForPreview;
|
|
73
83
|
private gatherFrontendPreviewArtifacts;
|
|
74
84
|
private evaluateFrontendVisualProof;
|
|
@@ -11,6 +11,47 @@ export function isConstrainedStaticFrontendRequest(message = '') {
|
|
|
11
11
|
&& /\b(?:single[- ]file|one\s+paragraph|exactly\s+one|current\s+directory\s+only|write\s+the\s+file\b[^.\n]{0,80}\bonly)\b/.test(prompt)
|
|
12
12
|
&& !/\b(?:premium|signature|showcase|redesign|overhaul|dashboard|portal|saas|interactive|animation|motion|form|checkout|booking|commerce)\b/.test(prompt);
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* A code artifact can belong to a browser product without itself being a
|
|
16
|
+
* previewable application. Requiring an HTML entry for an explicitly scoped
|
|
17
|
+
* module/library request turns a successful native verification into a false
|
|
18
|
+
* failure and rolls the requested file back.
|
|
19
|
+
*/
|
|
20
|
+
export function isFocusedNonPreviewableCodeArtifactRequest(message = '', expectedFiles = []) {
|
|
21
|
+
const prompt = String(message || '').toLowerCase();
|
|
22
|
+
const normalizedFiles = Array.from(new Set(expectedFiles
|
|
23
|
+
.map((filePath) => String(filePath || '').trim().replace(/\\/g, '/').replace(/^\.\//, ''))
|
|
24
|
+
.filter(Boolean)));
|
|
25
|
+
if (normalizedFiles.length === 0 || normalizedFiles.length > 4)
|
|
26
|
+
return false;
|
|
27
|
+
if (normalizedFiles.some((filePath) => /\.(?:html?|css|scss|sass|less)$/i.test(filePath)))
|
|
28
|
+
return false;
|
|
29
|
+
if (!normalizedFiles.every((filePath) => /\.(?:[cm]?[jt]sx?|py|go|rs|java|kt|swift|rb|php|cs|cpp|cc|c|h|hpp)$/i.test(filePath))) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
const explicitArtifactScope = /\b(?:es\s+module|module|library|utility|helper|class|function|service|adapter|middleware|route|schema)\b/i.test(prompt)
|
|
33
|
+
|| /\bexport\s+(?:exactly|only)\b/i.test(prompt)
|
|
34
|
+
|| /\b(?:create|write|implement|update|fix|repair)\b[^.\n]{0,120}\b(?:file|files)\b/i.test(prompt);
|
|
35
|
+
if (!explicitArtifactScope)
|
|
36
|
+
return false;
|
|
37
|
+
const wholeProductScope = /\b(?:build|create|implement|deliver|make)\b[^.\n]{0,100}\b(?:complete|full|entire|end-to-end|from scratch|production-ready)\b[^.\n]{0,80}\b(?:website|site|page|dashboard|portal|app|application|game)\b/i.test(prompt)
|
|
38
|
+
|| /\b(?:complete|full|entire|end-to-end|playable)\s+(?:website|site|page|dashboard|portal|app|application|game)\b/i.test(prompt);
|
|
39
|
+
return !wholeProductScope;
|
|
40
|
+
}
|
|
41
|
+
function htmlAttributeValue(openingTag, name) {
|
|
42
|
+
const match = String(openingTag || '').match(new RegExp(`\\b${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s'"\\x60=<>]+))`, 'i'));
|
|
43
|
+
return match ? String(match[1] ?? match[2] ?? match[3] ?? '') : '';
|
|
44
|
+
}
|
|
45
|
+
function hasViewportMeta(html) {
|
|
46
|
+
return Array.from(String(html || '').matchAll(/<meta\b[^>]*>/gi)).some((match) => htmlAttributeValue(match[0], 'name').toLowerCase() === 'viewport');
|
|
47
|
+
}
|
|
48
|
+
function canonicalizePreviewHtmlAttributes(html) {
|
|
49
|
+
// The browser accepts valid unquoted attributes. Some older preview-service
|
|
50
|
+
// deployments still parse only quoted name/src/href/type tokens. Send a
|
|
51
|
+
// semantically equivalent canonical proof document without rewriting the
|
|
52
|
+
// user's source file, so standards-compliant markup is not falsely rejected.
|
|
53
|
+
return String(html || '').replace(/\b(name|href|src|rel|type)\s*=\s*([^\s'"\x60=<>]+)/gi, (_match, name, value) => `${name}="${value}"`);
|
|
54
|
+
}
|
|
14
55
|
export class FrontendPreviewService {
|
|
15
56
|
dependencies;
|
|
16
57
|
constructor(dependencies) {
|
|
@@ -28,6 +69,9 @@ export class FrontendPreviewService {
|
|
|
28
69
|
const prompt = String(message || '');
|
|
29
70
|
const expectedFiles = this.dependencies.extractExpectedFiles(message, context);
|
|
30
71
|
const hasHtmlEntry = expectedFiles.some((filePath) => /\.html?$/i.test(filePath));
|
|
72
|
+
if (isFocusedNonPreviewableCodeArtifactRequest(prompt, expectedFiles)) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
31
75
|
return /(premium|polished|landing|site|page|dashboard|saas|frontend|ui|pricing|showcase|hero|responsive|interactive|game|canvas)/i.test(prompt)
|
|
32
76
|
|| hasHtmlEntry;
|
|
33
77
|
}
|
|
@@ -156,24 +200,30 @@ export class FrontendPreviewService {
|
|
|
156
200
|
}
|
|
157
201
|
return this.normalizeWorkspaceRelativePath(path.posix.normalize(path.posix.join(entryDir, withoutQuery)));
|
|
158
202
|
};
|
|
159
|
-
const cssPattern = /<link[^>]+href=["']([^"']+\.css(?:[?#][^"']*)?)["'][^>]*>/gi;
|
|
160
|
-
const jsPattern = /<script[^>]+src=["']([^"']+\.(?:js|mjs|cjs)(?:[?#][^"']*)?)["'][^>]*>/gi;
|
|
161
203
|
let match;
|
|
162
|
-
|
|
163
|
-
|
|
204
|
+
const linkPattern = /<link\b[^>]*>/gi;
|
|
205
|
+
while ((match = linkPattern.exec(String(html || ''))) !== null) {
|
|
206
|
+
const href = htmlAttributeValue(match[0], 'href');
|
|
207
|
+
if (!/\.css(?:[?#]|$)/i.test(href))
|
|
208
|
+
continue;
|
|
209
|
+
const resolved = normalizeAsset(href);
|
|
164
210
|
if (resolved) {
|
|
165
211
|
css.add(resolved);
|
|
166
212
|
}
|
|
167
213
|
}
|
|
168
|
-
|
|
169
|
-
|
|
214
|
+
const scriptPattern = /<script\b[^>]*>/gi;
|
|
215
|
+
while ((match = scriptPattern.exec(String(html || ''))) !== null) {
|
|
216
|
+
const src = htmlAttributeValue(match[0], 'src');
|
|
217
|
+
if (!/\.(?:js|mjs|cjs)(?:[?#]|$)/i.test(src))
|
|
218
|
+
continue;
|
|
219
|
+
const resolved = normalizeAsset(src);
|
|
170
220
|
if (resolved) {
|
|
171
221
|
js.add(resolved);
|
|
172
222
|
}
|
|
173
223
|
}
|
|
174
|
-
const assetPattern = /<(?:link|img|source|video|audio)\b[^>]
|
|
224
|
+
const assetPattern = /<(?:link|img|source|video|audio)\b[^>]*>/gi;
|
|
175
225
|
while ((match = assetPattern.exec(String(html || ''))) !== null) {
|
|
176
|
-
const resolved = normalizeAsset(match[
|
|
226
|
+
const resolved = normalizeAsset(htmlAttributeValue(match[0], 'href') || htmlAttributeValue(match[0], 'src'));
|
|
177
227
|
if (resolved)
|
|
178
228
|
assets.add(resolved);
|
|
179
229
|
}
|
|
@@ -183,6 +233,93 @@ export class FrontendPreviewService {
|
|
|
183
233
|
assets: Array.from(assets),
|
|
184
234
|
};
|
|
185
235
|
}
|
|
236
|
+
expandLinkedFrontendDependencyGraph(rootPath, entryFiles, kind) {
|
|
237
|
+
const selected = [];
|
|
238
|
+
const seen = new Set();
|
|
239
|
+
const queued = new Set();
|
|
240
|
+
const queue = entryFiles
|
|
241
|
+
.map((filePath) => this.normalizeWorkspaceRelativePath(filePath))
|
|
242
|
+
.filter(Boolean);
|
|
243
|
+
for (const filePath of queue)
|
|
244
|
+
queued.add(filePath);
|
|
245
|
+
const extensions = kind === 'css' ? ['.css'] : ['.js', '.mjs', '.cjs'];
|
|
246
|
+
const resolveImport = (ownerPath, specifier) => {
|
|
247
|
+
const clean = String(specifier || '').trim().split('?')[0].split('#')[0];
|
|
248
|
+
if (!clean
|
|
249
|
+
|| clean.startsWith('#')
|
|
250
|
+
|| clean.startsWith('//')
|
|
251
|
+
|| /^(?:https?:|data:|node:)/i.test(clean)
|
|
252
|
+
|| (!clean.startsWith('.') && !clean.startsWith('/'))) {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
const ownerDir = path.posix.dirname(this.normalizeWorkspaceRelativePath(ownerPath) || '.');
|
|
256
|
+
const base = this.normalizeWorkspaceRelativePath(path.posix.normalize(clean.startsWith('/')
|
|
257
|
+
? clean.slice(1)
|
|
258
|
+
: path.posix.join(ownerDir, clean)));
|
|
259
|
+
const candidates = [base];
|
|
260
|
+
if (!path.posix.extname(base)) {
|
|
261
|
+
for (const extension of extensions)
|
|
262
|
+
candidates.push(`${base}${extension}`);
|
|
263
|
+
for (const extension of extensions)
|
|
264
|
+
candidates.push(path.posix.join(base, `index${extension}`));
|
|
265
|
+
}
|
|
266
|
+
for (const candidate of candidates) {
|
|
267
|
+
if (!extensions.includes(path.posix.extname(candidate).toLowerCase()))
|
|
268
|
+
continue;
|
|
269
|
+
try {
|
|
270
|
+
const absolute = resolveWorkspacePath(rootPath, candidate);
|
|
271
|
+
if (fs.statSync(absolute).isFile())
|
|
272
|
+
return candidate;
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
// The candidate is absent or outside the workspace boundary.
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
};
|
|
280
|
+
// A dependency graph is authoritative because every admitted file is
|
|
281
|
+
// reachable from the HTML entry. The cap prevents cyclic or adversarial
|
|
282
|
+
// graphs from inflating a preview payload; the final payload has its own
|
|
283
|
+
// byte limit as a second boundary.
|
|
284
|
+
while (queue.length > 0 && selected.length < 64) {
|
|
285
|
+
const ownerPath = queue.shift();
|
|
286
|
+
if (!ownerPath || seen.has(ownerPath))
|
|
287
|
+
continue;
|
|
288
|
+
seen.add(ownerPath);
|
|
289
|
+
let source = '';
|
|
290
|
+
try {
|
|
291
|
+
source = fs.readFileSync(resolveWorkspacePath(rootPath, ownerPath), 'utf8');
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
selected.push(ownerPath);
|
|
297
|
+
const specifiers = [];
|
|
298
|
+
if (kind === 'css') {
|
|
299
|
+
const importPattern = /@import\s+(?:url\(\s*)?(?:["']([^"']+)["']|([^\s)'";]+))\s*\)?/gi;
|
|
300
|
+
let match;
|
|
301
|
+
while ((match = importPattern.exec(source)) !== null)
|
|
302
|
+
specifiers.push(match[1] || match[2]);
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
const staticPattern = /\b(?:import|export)\s+(?:[^'";]*?\sfrom\s*)?["']([^"']+)["']/g;
|
|
306
|
+
const dynamicPattern = /\bimport\s*\(\s*["']([^"']+)["']\s*\)/g;
|
|
307
|
+
let match;
|
|
308
|
+
while ((match = staticPattern.exec(source)) !== null)
|
|
309
|
+
specifiers.push(match[1]);
|
|
310
|
+
while ((match = dynamicPattern.exec(source)) !== null)
|
|
311
|
+
specifiers.push(match[1]);
|
|
312
|
+
}
|
|
313
|
+
for (const specifier of specifiers) {
|
|
314
|
+
const dependency = resolveImport(ownerPath, specifier);
|
|
315
|
+
if (dependency && !seen.has(dependency) && !queued.has(dependency)) {
|
|
316
|
+
queue.push(dependency);
|
|
317
|
+
queued.add(dependency);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return selected;
|
|
322
|
+
}
|
|
186
323
|
async buildFrontendForPreview(rootPath) {
|
|
187
324
|
const packagePath = path.join(rootPath, 'package.json');
|
|
188
325
|
if (!fs.existsSync(packagePath)) {
|
|
@@ -256,12 +393,14 @@ export class FrontendPreviewService {
|
|
|
256
393
|
// Pulling every CSS/JS file from the workspace also ingested audit copies,
|
|
257
394
|
// rejected model drafts, and test harnesses into the production proof body.
|
|
258
395
|
// Fall back to expected/workspace files only for documents without links.
|
|
259
|
-
const
|
|
396
|
+
const directCssFiles = (linkedAssets.css.length > 0
|
|
260
397
|
? chooseExisting(linkedAssets.css, [])
|
|
261
398
|
: chooseExisting(expectedCss, cssFiles)).slice(0, 12);
|
|
262
|
-
const
|
|
399
|
+
const directJsFiles = (linkedAssets.js.length > 0
|
|
263
400
|
? chooseExisting(linkedAssets.js, [])
|
|
264
401
|
: chooseExisting(expectedJs, jsFiles)).slice(0, 12);
|
|
402
|
+
const selectedCssFiles = this.expandLinkedFrontendDependencyGraph(rootPath, directCssFiles, 'css');
|
|
403
|
+
const selectedJsFiles = this.expandLinkedFrontendDependencyGraph(rootPath, directJsFiles, 'js');
|
|
265
404
|
const css = selectedCssFiles.map((filePath) => fs.readFileSync(resolveWorkspacePath(rootPath, filePath), 'utf8')).join('\n\n');
|
|
266
405
|
const js = selectedJsFiles.map((filePath) => fs.readFileSync(resolveWorkspacePath(rootPath, filePath), 'utf8')).join('\n\n');
|
|
267
406
|
return { htmlPath, html, css, js, cssPaths: selectedCssFiles, jsPaths: selectedJsFiles };
|
|
@@ -272,6 +411,14 @@ export class FrontendPreviewService {
|
|
|
272
411
|
const html = String(artifacts.html || '');
|
|
273
412
|
const css = String(artifacts.css || '');
|
|
274
413
|
const js = String(artifacts.js || '');
|
|
414
|
+
// runtime_check proves that an existing page can be opened by a real local
|
|
415
|
+
// browser. It is not a responsive-design audit. Requiring viewport metadata,
|
|
416
|
+
// stylesheets, and responsive CSS made focused JavaScript/CORS repairs fail
|
|
417
|
+
// even after Chromium produced the required screenshot. The screenshot and
|
|
418
|
+
// artifact persistence remain fail-closed in persistFrontendPreviewArtifacts.
|
|
419
|
+
if (options.functionalRuntime) {
|
|
420
|
+
return { ok: true, reasons: [] };
|
|
421
|
+
}
|
|
275
422
|
if (!summary.hasViewportMeta) {
|
|
276
423
|
reasons.push('missing viewport metadata');
|
|
277
424
|
}
|
|
@@ -305,13 +452,30 @@ export class FrontendPreviewService {
|
|
|
305
452
|
const screenshotPath = resolveWorkspacePath(rootPath, `.vigthoria/proof/preview/${baseName}.png`, { allowMissing: true });
|
|
306
453
|
const entryAbsolutePath = resolveWorkspacePath(rootPath, previewGate.entryPath);
|
|
307
454
|
const previewFileUrl = `file://${entryAbsolutePath}`;
|
|
308
|
-
const
|
|
455
|
+
const runtimeOptions = context.runtimeInteractionProof && typeof context.runtimeInteractionProof === 'object'
|
|
456
|
+
? context.runtimeInteractionProof
|
|
457
|
+
: null;
|
|
458
|
+
const runtimeInteraction = runtimeOptions
|
|
459
|
+
? (typeof this.dependencies.screenshotAdapter.captureRuntime === 'function'
|
|
460
|
+
? await this.dependencies.screenshotAdapter.captureRuntime(entryAbsolutePath, screenshotPath, { ...runtimeOptions, workspaceRoot: rootPath })
|
|
461
|
+
: {
|
|
462
|
+
captured: false,
|
|
463
|
+
passed: false,
|
|
464
|
+
error: 'required loopback browser interaction adapter is unavailable',
|
|
465
|
+
})
|
|
466
|
+
: null;
|
|
467
|
+
const screenshot = runtimeInteraction
|
|
468
|
+
|| await this.dependencies.screenshotAdapter.capture(entryAbsolutePath, screenshotPath);
|
|
309
469
|
const screenshotRequired = context.requireScreenshot === true;
|
|
310
|
-
const
|
|
470
|
+
const screenshotFailure = screenshotRequired && !screenshot.captured;
|
|
471
|
+
const runtimeFailure = runtimeInteraction !== null && !runtimeInteraction.passed;
|
|
472
|
+
const effectivePreviewGate = screenshotFailure || runtimeFailure
|
|
311
473
|
? {
|
|
312
474
|
...previewGate,
|
|
313
475
|
passed: false,
|
|
314
|
-
error: `${previewGate.error ? `${previewGate.error} ` : ''}
|
|
476
|
+
error: `${previewGate.error ? `${previewGate.error} ` : ''}${runtimeFailure
|
|
477
|
+
? (runtimeInteraction.error || 'Required loopback browser interaction proof failed.')
|
|
478
|
+
: `Required screenshot proof is unavailable: ${screenshot.error || 'capture failed'}.`}`,
|
|
315
479
|
}
|
|
316
480
|
: previewGate;
|
|
317
481
|
const manifest = {
|
|
@@ -335,6 +499,16 @@ export class FrontendPreviewService {
|
|
|
335
499
|
captured: screenshot.captured,
|
|
336
500
|
error: screenshot.error || null,
|
|
337
501
|
},
|
|
502
|
+
runtimeInteraction: runtimeInteraction ? {
|
|
503
|
+
passed: runtimeInteraction.passed,
|
|
504
|
+
action: runtimeInteraction.action || null,
|
|
505
|
+
initialBodyText: runtimeInteraction.initialBodyText || '',
|
|
506
|
+
finalBodyText: runtimeInteraction.finalBodyText || '',
|
|
507
|
+
bodyChanged: runtimeInteraction.bodyChanged === true,
|
|
508
|
+
visualChanged: runtimeInteraction.visualChanged === true,
|
|
509
|
+
runtimeErrors: runtimeInteraction.runtimeErrors || [],
|
|
510
|
+
error: runtimeInteraction.error || null,
|
|
511
|
+
} : null,
|
|
338
512
|
};
|
|
339
513
|
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
|
|
340
514
|
return {
|
|
@@ -345,6 +519,7 @@ export class FrontendPreviewService {
|
|
|
345
519
|
previewFileUrl,
|
|
346
520
|
screenshotCaptured: screenshot.captured,
|
|
347
521
|
screenshotError: screenshot.error,
|
|
522
|
+
...(runtimeInteraction ? { runtimeInteraction } : {}),
|
|
348
523
|
},
|
|
349
524
|
};
|
|
350
525
|
}
|
|
@@ -408,14 +583,17 @@ export class FrontendPreviewService {
|
|
|
408
583
|
const errors = [];
|
|
409
584
|
if (context.localScreenshotProof === true) {
|
|
410
585
|
const summary = {
|
|
411
|
-
hasViewportMeta:
|
|
586
|
+
hasViewportMeta: hasViewportMeta(html),
|
|
412
587
|
hasResponsiveSignals: /@media\b/i.test(`${html}\n${css}`),
|
|
413
588
|
hasInteractiveSignals: /<script[\s>]/i.test(html) || js.trim().length > 0,
|
|
414
589
|
sectionCount: (html.match(/<section\b/gi) || []).length,
|
|
415
590
|
proofSource: 'local-screenshot-adapter',
|
|
416
591
|
};
|
|
417
592
|
const constrainedStatic = isConstrainedStaticFrontendRequest(String(context.rawPrompt || message || ''));
|
|
418
|
-
const visualProof = this.evaluateFrontendVisualProof(summary, artifacts, {
|
|
593
|
+
const visualProof = this.evaluateFrontendVisualProof(summary, artifacts, {
|
|
594
|
+
constrainedStatic,
|
|
595
|
+
functionalRuntime: context.functionalRuntimeProof === true,
|
|
596
|
+
});
|
|
419
597
|
const localGate = {
|
|
420
598
|
required: true,
|
|
421
599
|
passed: visualProof.ok,
|
|
@@ -447,7 +625,7 @@ export class FrontendPreviewService {
|
|
|
447
625
|
qualityProfile: isConstrainedStaticFrontendRequest(String(context.rawPrompt || message || ''))
|
|
448
626
|
? 'constrained-static'
|
|
449
627
|
: 'auto',
|
|
450
|
-
html,
|
|
628
|
+
html: canonicalizePreviewHtmlAttributes(html),
|
|
451
629
|
css,
|
|
452
630
|
js,
|
|
453
631
|
entryPath: artifacts.htmlPath,
|
|
@@ -466,8 +644,14 @@ export class FrontendPreviewService {
|
|
|
466
644
|
error: 'Preview proof skipped: payload exceeds size limit.',
|
|
467
645
|
};
|
|
468
646
|
}
|
|
469
|
-
const
|
|
470
|
-
|| process.env.
|
|
647
|
+
const internalServiceKeyCandidate = process.env.VIGTHORIA_TEMPLATE_SERVICE_KEY
|
|
648
|
+
|| process.env.VIGTHORIA_V3_SERVICE_KEY
|
|
649
|
+
|| process.env.V3_SERVICE_KEY
|
|
650
|
+
|| this.dependencies.getServiceKey?.();
|
|
651
|
+
const internalServiceKey = String(internalServiceKeyCandidate || '').trim();
|
|
652
|
+
if (internalServiceKey && /[\r\n\0]/.test(internalServiceKey)) {
|
|
653
|
+
throw new Error('Stored Template Service capability contains invalid header characters.');
|
|
654
|
+
}
|
|
471
655
|
const authToken = this.dependencies.getAccessToken();
|
|
472
656
|
const response = await this.dependencies.fetch(`${baseUrl}/preview-proof`, {
|
|
473
657
|
method: 'POST',
|
|
@@ -491,12 +675,18 @@ export class FrontendPreviewService {
|
|
|
491
675
|
const summary = payload?.summary || {};
|
|
492
676
|
const constrainedStatic = isConstrainedStaticFrontendRequest(String(context.rawPrompt || message || ''))
|
|
493
677
|
&& modes?.production?.qualityTier === 'scope-complete';
|
|
494
|
-
const
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
678
|
+
const functionalRuntime = context.functionalRuntimeProof === true;
|
|
679
|
+
const visualProof = this.evaluateFrontendVisualProof(summary, artifacts, {
|
|
680
|
+
constrainedStatic,
|
|
681
|
+
functionalRuntime,
|
|
682
|
+
});
|
|
683
|
+
const modeProofReady = functionalRuntime
|
|
684
|
+
? payload?.success === true && modes?.live?.ready === true
|
|
685
|
+
: payload?.success === true
|
|
686
|
+
&& modes?.design?.ready === true
|
|
687
|
+
&& modes?.live?.ready === true
|
|
688
|
+
&& modes?.production?.ready === true;
|
|
689
|
+
const passed = modeProofReady && visualProof.ok;
|
|
500
690
|
const previewGate = {
|
|
501
691
|
required: true,
|
|
502
692
|
passed,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detect static ESM syntax even when a compact generator places exports after
|
|
3
|
+
* declarations on the same line. Dynamic import() alone remains valid in
|
|
4
|
+
* CommonJS syntax mode and does not require ESM parsing.
|
|
5
|
+
*/
|
|
6
|
+
export declare function usesJavaScriptModuleSyntax(source: string, filePath?: string): boolean;
|
|
7
|
+
export type JavaScriptSyntaxMode = 'script' | 'module';
|
|
8
|
+
/**
|
|
9
|
+
* Return the grammars that may legitimately own a JavaScript source file.
|
|
10
|
+
*
|
|
11
|
+
* A plain `.js` file is ambiguous until its package or HTML loader is known.
|
|
12
|
+
* In particular, top-level await can make otherwise valid browser-module code
|
|
13
|
+
* fail a CommonJS-only preflight without leaving a static import/export token
|
|
14
|
+
* for the lightweight detector above. Trying both grammars keeps the gate
|
|
15
|
+
* strict (one parser must accept the complete source) without rejecting valid
|
|
16
|
+
* browser modules or classic scripts. Explicit `.mjs` and `.cjs` identities
|
|
17
|
+
* remain single-grammar and therefore fail closed.
|
|
18
|
+
*/
|
|
19
|
+
export declare function resolveJavaScriptSyntaxModes(source: string, filePath?: string): JavaScriptSyntaxMode[];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
/**
|
|
3
|
+
* Detect static ESM syntax even when a compact generator places exports after
|
|
4
|
+
* declarations on the same line. Dynamic import() alone remains valid in
|
|
5
|
+
* CommonJS syntax mode and does not require ESM parsing.
|
|
6
|
+
*/
|
|
7
|
+
export function usesJavaScriptModuleSyntax(source, filePath = '.js') {
|
|
8
|
+
const extension = path.extname(filePath).toLowerCase();
|
|
9
|
+
if (extension === '.mjs')
|
|
10
|
+
return true;
|
|
11
|
+
if (extension === '.cjs')
|
|
12
|
+
return false;
|
|
13
|
+
return /(?:^|[;}\n]|\*\/)[ \t]*(?:import(?!\s*\()|export\b)/m.test(source);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Return the grammars that may legitimately own a JavaScript source file.
|
|
17
|
+
*
|
|
18
|
+
* A plain `.js` file is ambiguous until its package or HTML loader is known.
|
|
19
|
+
* In particular, top-level await can make otherwise valid browser-module code
|
|
20
|
+
* fail a CommonJS-only preflight without leaving a static import/export token
|
|
21
|
+
* for the lightweight detector above. Trying both grammars keeps the gate
|
|
22
|
+
* strict (one parser must accept the complete source) without rejecting valid
|
|
23
|
+
* browser modules or classic scripts. Explicit `.mjs` and `.cjs` identities
|
|
24
|
+
* remain single-grammar and therefore fail closed.
|
|
25
|
+
*/
|
|
26
|
+
export function resolveJavaScriptSyntaxModes(source, filePath = '.js') {
|
|
27
|
+
const extension = path.extname(filePath).toLowerCase();
|
|
28
|
+
if (extension === '.mjs')
|
|
29
|
+
return ['module'];
|
|
30
|
+
if (extension === '.cjs')
|
|
31
|
+
return ['script'];
|
|
32
|
+
if (usesJavaScriptModuleSyntax(source, filePath))
|
|
33
|
+
return ['module'];
|
|
34
|
+
return ['script', 'module'];
|
|
35
|
+
}
|
|
@@ -20,7 +20,7 @@ export function applyLocalTestfarmDefaults() {
|
|
|
20
20
|
process.env.VIGTHORIA_HYPERLOOP_URL ??= 'http://127.0.0.1:8020/api/hyperloop/health';
|
|
21
21
|
process.env.VIGTHORIA_HYPERLOOP_EXECUTE_URL ??= 'http://127.0.0.1:8020/api/hyperloop/execute';
|
|
22
22
|
process.env.VIGTHORIA_HYPERLOOP_MODULES_URL ??= 'http://127.0.0.1:8020/api/hyperloop/modules';
|
|
23
|
-
process.env.VIGTHORIA_V3_AGENT_URL ??= 'http://127.0.0.1:
|
|
23
|
+
process.env.VIGTHORIA_V3_AGENT_URL ??= 'http://127.0.0.1:8032';
|
|
24
24
|
process.env.VIGTHORIA_TEMPLATE_SERVICE_URL ??= 'http://127.0.0.1:4011';
|
|
25
25
|
process.env.VIGTHORIA_HYPERLOOP_TIMEOUT_MS ??= '180000';
|
|
26
26
|
const testToken = String(process.env.VIGTHORIA_LOCAL_TEST_TOKEN || '').trim();
|
|
@@ -39,4 +39,14 @@ export declare class MutationJournal {
|
|
|
39
39
|
commit(): MutationJournalReport;
|
|
40
40
|
rollback(): MutationJournalReport;
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Last-resort synchronous rollback for process signals.
|
|
44
|
+
*
|
|
45
|
+
* APIClient normally owns the foreground journal, but command teardown or a
|
|
46
|
+
* second lifecycle wrapper must never be able to detach an open transaction
|
|
47
|
+
* from SIGINT/SIGTERM protection. Journals register at construction and
|
|
48
|
+
* unregister only after commit/rollback, so this fallback closes every still
|
|
49
|
+
* open workspace transaction before the original signal is re-raised.
|
|
50
|
+
*/
|
|
51
|
+
export declare function rollbackActiveMutationJournals(): MutationJournalReport[];
|
|
42
52
|
export declare function finalizeMutationTransaction(journal: MutationJournal, proofSucceeded: boolean): MutationJournalReport;
|