vigthoria-cli 1.13.26 → 1.13.30
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 +12 -0
- package/completions/_vigthoria +1 -0
- package/completions/vigthoria.bash +1 -1
- package/completions/vigthoria.fish +1 -0
- package/dist/commands/chat.js +73 -26
- package/dist/commands/config.js +4 -4
- package/dist/commands/creative-registration.d.ts +13 -0
- package/dist/commands/creative-registration.js +88 -0
- package/dist/commands/fork.d.ts +3 -2
- package/dist/commands/fork.js +124 -123
- package/dist/commands/game.d.ts +8 -0
- package/dist/commands/game.js +113 -9
- package/dist/commands/history.d.ts +0 -1
- package/dist/commands/history.js +8 -22
- package/dist/commands/hub.d.ts +20 -0
- package/dist/commands/hub.js +17 -3
- package/dist/commands/preview.js +7 -2
- package/dist/commands/product-run-registration.js +1 -1
- package/dist/commands/replay.d.ts +0 -1
- package/dist/commands/replay.js +10 -19
- package/dist/commands/repo.js +16 -4
- package/dist/commands/update-registration.js +2 -2
- package/dist/commands/workflow.d.ts +4 -0
- package/dist/commands/workflow.js +27 -0
- package/dist/index.js +8 -4
- package/dist/utils/agentRunOutcome.d.ts +7 -0
- package/dist/utils/agentRunOutcome.js +13 -0
- package/dist/utils/api.d.ts +20 -5
- package/dist/utils/api.js +428 -43
- package/dist/utils/command-policy.js +3 -1
- package/dist/utils/config.d.ts +2 -0
- package/dist/utils/config.js +22 -9
- package/dist/utils/frontend-preview-service.d.ts +1 -0
- package/dist/utils/frontend-preview-service.js +54 -5
- package/dist/utils/model-governance.js +23 -14
- package/dist/utils/model-transport-service.js +1 -1
- package/dist/utils/network-policy.js +15 -3
- package/dist/utils/operator-client.js +23 -4
- package/dist/utils/post-write-validator.js +7 -3
- package/dist/utils/preview-screenshot-adapter.d.ts +16 -41
- package/dist/utils/preview-screenshot-adapter.js +273 -64
- package/dist/utils/runtime-capability.d.ts +7 -0
- package/dist/utils/runtime-capability.js +11 -0
- package/dist/utils/runtime-temp.d.ts +5 -2
- package/dist/utils/runtime-temp.js +131 -30
- package/dist/utils/tools.js +1 -1
- package/dist/utils/v3-stream-events.js +10 -2
- package/dist/utils/v3-workspace-service.d.ts +1 -0
- package/dist/utils/v3-workspace-service.js +38 -1
- package/dist/utils/vigflow-client.d.ts +9 -0
- package/dist/utils/vigflow-client.js +48 -2
- package/dist/utils/workspace-reference.d.ts +8 -0
- package/dist/utils/workspace-reference.js +21 -0
- package/install.ps1 +2 -2
- package/install.sh +2 -2
- package/package.json +4 -6
- package/scripts/release/LOCAL_MACHINE_USER_VERIFICATION.md +2 -2
- package/scripts/release/validate-live-service-gates.sh +3 -3
- package/scripts/release/validate-no-go-gates.sh +2 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
const REQUIRED_SESSION_PATHS = new Set([
|
|
2
2
|
'chat', 'chat-resume', 'agent', 'operator',
|
|
3
3
|
'edit', 'generate', 'explain', 'fix', 'review',
|
|
4
|
-
'
|
|
4
|
+
'creative plan',
|
|
5
|
+
'workflow templates', 'workflow list', 'workflow use-template', 'workflow run', 'workflow status', 'workflow delete',
|
|
5
6
|
'hub activate', 'hub active',
|
|
6
7
|
'repo push', 'repo review', 'repo pull', 'repo list', 'repo status', 'repo share', 'repo delete', 'repo open-in',
|
|
7
8
|
'background start', 'background list', 'background status', 'background apply', 'background cancel', 'background approve',
|
|
@@ -16,6 +17,7 @@ const NO_SESSION_PATHS = new Set([
|
|
|
16
17
|
'device', 'device status', 'device list', 'device screenshot', 'device install', 'device launch', 'device logs', 'device tcpip', 'device connect', 'device disconnect',
|
|
17
18
|
'security', 'security scan', 'security score', 'security fix',
|
|
18
19
|
'workflow',
|
|
20
|
+
'creative', 'creative workflows',
|
|
19
21
|
'hub', 'hub discover', 'hub list', 'hub search', 'hub info',
|
|
20
22
|
'music', 'music generate', 'music status',
|
|
21
23
|
'repo', 'repo clone',
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -48,8 +48,10 @@ export interface ConfigOptions {
|
|
|
48
48
|
}
|
|
49
49
|
export declare class Config {
|
|
50
50
|
private store;
|
|
51
|
+
private readonly stateRoot;
|
|
51
52
|
private static readonly OPERATOR_PLANS;
|
|
52
53
|
constructor(options?: ConfigOptions);
|
|
54
|
+
getStateRoot(): string;
|
|
53
55
|
private retireLegacyConfStore;
|
|
54
56
|
/**
|
|
55
57
|
* Re-apply secure POSIX permissions to the on-disk config file. Safe
|
package/dist/utils/config.js
CHANGED
|
@@ -129,12 +129,14 @@ function isConfigValue(value) {
|
|
|
129
129
|
}
|
|
130
130
|
export class Config {
|
|
131
131
|
store;
|
|
132
|
+
stateRoot;
|
|
132
133
|
static OPERATOR_PLANS = new Set([
|
|
133
134
|
'basic', 'pro', 'professional', 'professional_ai', 'enterprise',
|
|
134
135
|
'enterprise_ai', 'whale', 'admin', 'master_admin', 'master_admin_plan',
|
|
135
136
|
]);
|
|
136
137
|
constructor(options = {}) {
|
|
137
138
|
const stateRoot = path.resolve(options.stateRoot || os.homedir());
|
|
139
|
+
this.stateRoot = stateRoot;
|
|
138
140
|
const canonicalPath = path.join(stateRoot, '.vigthoria', 'config.json');
|
|
139
141
|
const legacyConfPath = options.legacyConfPath
|
|
140
142
|
? path.resolve(options.legacyConfPath)
|
|
@@ -161,12 +163,20 @@ export class Config {
|
|
|
161
163
|
// canonical-file precedence.
|
|
162
164
|
if (legacyConf && legacyMtime > canonicalMtime) {
|
|
163
165
|
const legacyCredentials = normalizeConfigValue(legacyConf);
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
166
|
+
// Old `conf` releases could touch or recreate an empty
|
|
167
|
+
// AppData file during any command. That timestamp is not
|
|
168
|
+
// an explicit logout and must never erase a valid session
|
|
169
|
+
// already stored in the canonical ~/.vigthoria record.
|
|
170
|
+
// A token-bearing legacy bundle can still win by recency;
|
|
171
|
+
// current releases record logout atomically in canonical.
|
|
172
|
+
if (legacyCredentials.authToken || !merged.authToken) {
|
|
173
|
+
merged.authToken = legacyCredentials.authToken;
|
|
174
|
+
merged.refreshToken = legacyCredentials.refreshToken;
|
|
175
|
+
merged.userId = legacyCredentials.userId;
|
|
176
|
+
merged.email = legacyCredentials.email;
|
|
177
|
+
merged.v3ServiceKey = legacyCredentials.v3ServiceKey;
|
|
178
|
+
merged.subscription = legacyCredentials.subscription;
|
|
179
|
+
}
|
|
170
180
|
}
|
|
171
181
|
return merged;
|
|
172
182
|
},
|
|
@@ -184,6 +194,9 @@ export class Config {
|
|
|
184
194
|
this.retireLegacyConfStore(legacyConfPath, canonicalPath);
|
|
185
195
|
this.secureOnDisk();
|
|
186
196
|
}
|
|
197
|
+
getStateRoot() {
|
|
198
|
+
return this.stateRoot;
|
|
199
|
+
}
|
|
187
200
|
retireLegacyConfStore(legacyPath, canonicalPath) {
|
|
188
201
|
const retired = new DurableJsonStore({
|
|
189
202
|
filePath: legacyPath,
|
|
@@ -324,10 +337,10 @@ export class Config {
|
|
|
324
337
|
// Vigthoria server infrastructure operational models
|
|
325
338
|
// ═══════════════════════════════════════════════════════════════
|
|
326
339
|
const models = [
|
|
327
|
-
{ id: 'architect', name: 'Vigthoria v4 Creative 27B', description: '
|
|
328
|
-
{ id: 'code', name: 'Vigthoria v4 Code 27B', description: 'Dense production executor for coding, debugging, and repository changes', tier: 'local', backendModel: 'Vigthoria-v4-Code-27B' },
|
|
340
|
+
{ id: 'architect', name: 'Vigthoria v4.2 Creative 27B', description: 'Multimodal architect for planning, deep reasoning, image/video analysis, and creative systems work', tier: 'local', backendModel: 'Vigthoria-v4.2-Creative-27B' },
|
|
341
|
+
{ id: 'code', name: 'Vigthoria v4.2 Code 27B', description: 'Dense production executor for coding, debugging, and repository changes', tier: 'local', backendModel: 'Vigthoria-v4.2-Code-27B' },
|
|
329
342
|
{ id: 'assistant', name: 'Vigthoria v4 Assistant 9B', description: 'Low-latency FIM, completion, voice, and diagnostic model', tier: 'local', backendModel: 'Vigthoria-v4-Assistant-9B' },
|
|
330
|
-
{ id: 'code-35b', name: 'Vigthoria v4 Code 27B', description: 'Legacy selector mapped to the V4 executor', tier: 'local', backendModel: 'Vigthoria-v4-Code-27B' },
|
|
343
|
+
{ id: 'code-35b', name: 'Vigthoria v4.2 Code 27B', description: 'Legacy selector mapped to the V4 executor', tier: 'local', backendModel: 'Vigthoria-v4.2-Code-27B' },
|
|
331
344
|
{ id: 'code-9b', name: 'Vigthoria v4 Assistant 9B', description: 'Legacy selector mapped to the V4 assistant', tier: 'local', backendModel: 'Vigthoria-v4-Assistant-9B' },
|
|
332
345
|
{ id: 'balanced', name: 'Vigthoria v4 Assistant 9B', description: 'Legacy selector mapped to the V4 assistant', tier: 'local', backendModel: 'Vigthoria-v4-Assistant-9B' },
|
|
333
346
|
{ id: 'balanced-4b', name: 'Vigthoria v4 Assistant 9B', description: 'Legacy selector mapped to the V4 assistant', tier: 'local', backendModel: 'Vigthoria-v4-Assistant-9B' },
|
|
@@ -50,6 +50,7 @@ export interface FrontendPreviewServiceDependencies {
|
|
|
50
50
|
sanitizeError(text: string): string;
|
|
51
51
|
screenshotAdapter: PreviewScreenshotPort;
|
|
52
52
|
}
|
|
53
|
+
export declare function isConstrainedStaticFrontendRequest(message?: string): boolean;
|
|
53
54
|
export declare class FrontendPreviewService {
|
|
54
55
|
private readonly dependencies;
|
|
55
56
|
constructor(dependencies: FrontendPreviewServiceDependencies);
|
|
@@ -4,6 +4,13 @@ import * as path from 'node:path';
|
|
|
4
4
|
import { assertProcessNetworkAllowed } from './process-policy.js';
|
|
5
5
|
import { safeChildProcessEnv, scanOutboundContext } from './secret-policy.js';
|
|
6
6
|
import { resolveWorkspacePath } from './workspace-boundary.js';
|
|
7
|
+
export function isConstrainedStaticFrontendRequest(message = '') {
|
|
8
|
+
const prompt = String(message || '').toLowerCase();
|
|
9
|
+
return /\b(?:tiny|minimal|simple)\b/.test(prompt)
|
|
10
|
+
&& /\bstatic\b/.test(prompt)
|
|
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
|
+
&& !/\b(?:premium|signature|showcase|redesign|overhaul|dashboard|portal|saas|interactive|animation|motion|form|checkout|booking|commerce)\b/.test(prompt);
|
|
13
|
+
}
|
|
7
14
|
export class FrontendPreviewService {
|
|
8
15
|
dependencies;
|
|
9
16
|
constructor(dependencies) {
|
|
@@ -21,7 +28,7 @@ export class FrontendPreviewService {
|
|
|
21
28
|
const prompt = String(message || '');
|
|
22
29
|
const expectedFiles = this.dependencies.extractExpectedFiles(message, context);
|
|
23
30
|
const hasHtmlEntry = expectedFiles.some((filePath) => /\.html?$/i.test(filePath));
|
|
24
|
-
return /(premium|polished|landing|site|page|dashboard|saas|frontend|ui|pricing|showcase|hero|responsive)/i.test(prompt)
|
|
31
|
+
return /(premium|polished|landing|site|page|dashboard|saas|frontend|ui|pricing|showcase|hero|responsive|interactive|game|canvas)/i.test(prompt)
|
|
25
32
|
|| hasHtmlEntry;
|
|
26
33
|
}
|
|
27
34
|
/**
|
|
@@ -259,7 +266,7 @@ export class FrontendPreviewService {
|
|
|
259
266
|
const js = selectedJsFiles.map((filePath) => fs.readFileSync(resolveWorkspacePath(rootPath, filePath), 'utf8')).join('\n\n');
|
|
260
267
|
return { htmlPath, html, css, js, cssPaths: selectedCssFiles, jsPaths: selectedJsFiles };
|
|
261
268
|
}
|
|
262
|
-
evaluateFrontendVisualProof(summary = {}, artifacts = {}) {
|
|
269
|
+
evaluateFrontendVisualProof(summary = {}, artifacts = {}, options = {}) {
|
|
263
270
|
const reasons = [];
|
|
264
271
|
const cssPaths = Array.isArray(artifacts.cssPaths) ? artifacts.cssPaths : [];
|
|
265
272
|
const html = String(artifacts.html || '');
|
|
@@ -269,11 +276,14 @@ export class FrontendPreviewService {
|
|
|
269
276
|
reasons.push('missing viewport metadata');
|
|
270
277
|
}
|
|
271
278
|
const hasStyleEvidence = cssPaths.length > 0 || /<style[\s>]/i.test(html) || css.trim().length > 0;
|
|
272
|
-
if (!hasStyleEvidence) {
|
|
279
|
+
if (!options.constrainedStatic && !hasStyleEvidence) {
|
|
273
280
|
reasons.push('missing stylesheet or inline style evidence');
|
|
274
281
|
}
|
|
275
282
|
const hasInteractionOrResponsiveEvidence = Boolean(summary.hasResponsiveSignals) || Boolean(summary.hasInteractiveSignals) || /<script[\s>]/i.test(html) || js.trim().length > 0;
|
|
276
|
-
|
|
283
|
+
// A server-confirmed constrained static document is complete without
|
|
284
|
+
// invented interactivity or responsive behavior. Richer frontend requests
|
|
285
|
+
// retain the fail-closed visual-evidence requirement.
|
|
286
|
+
if (!options.constrainedStatic && !hasInteractionOrResponsiveEvidence) {
|
|
277
287
|
reasons.push('missing responsive or interactive evidence');
|
|
278
288
|
}
|
|
279
289
|
return {
|
|
@@ -396,10 +406,47 @@ export class FrontendPreviewService {
|
|
|
396
406
|
const css = (artifacts.css || '').slice(0, PREVIEW_MAX_ARTIFACT_BYTES);
|
|
397
407
|
const js = (artifacts.js || '').slice(0, PREVIEW_MAX_ARTIFACT_BYTES);
|
|
398
408
|
const errors = [];
|
|
409
|
+
if (context.localScreenshotProof === true) {
|
|
410
|
+
const summary = {
|
|
411
|
+
hasViewportMeta: /<meta\b[^>]*name=["']viewport["'][^>]*>/i.test(html),
|
|
412
|
+
hasResponsiveSignals: /@media\b/i.test(`${html}\n${css}`),
|
|
413
|
+
hasInteractiveSignals: /<script[\s>]/i.test(html) || js.trim().length > 0,
|
|
414
|
+
sectionCount: (html.match(/<section\b/gi) || []).length,
|
|
415
|
+
proofSource: 'local-screenshot-adapter',
|
|
416
|
+
};
|
|
417
|
+
const constrainedStatic = isConstrainedStaticFrontendRequest(String(context.rawPrompt || message || ''));
|
|
418
|
+
const visualProof = this.evaluateFrontendVisualProof(summary, artifacts, { constrainedStatic });
|
|
419
|
+
const localGate = {
|
|
420
|
+
required: true,
|
|
421
|
+
passed: visualProof.ok,
|
|
422
|
+
backendUrl: 'local-screenshot-adapter',
|
|
423
|
+
entryPath: artifacts.htmlPath,
|
|
424
|
+
assetPaths: {
|
|
425
|
+
css: artifacts.cssPaths || [],
|
|
426
|
+
js: artifacts.jsPaths || [],
|
|
427
|
+
},
|
|
428
|
+
modes: {
|
|
429
|
+
design: { ready: visualProof.ok, devices: ['local-browser'], variantCount: 1 },
|
|
430
|
+
live: { ready: visualProof.ok, entryPoint: artifacts.htmlPath },
|
|
431
|
+
production: { ready: visualProof.ok, deploymentTarget: 'local-static-artifact' },
|
|
432
|
+
},
|
|
433
|
+
summary,
|
|
434
|
+
error: visualProof.ok
|
|
435
|
+
? undefined
|
|
436
|
+
: `Local screenshot proof is missing required visual evidence: ${visualProof.reasons.join(', ')}.`,
|
|
437
|
+
};
|
|
438
|
+
return await this.persistFrontendPreviewArtifacts(localGate, rootPath, {
|
|
439
|
+
...context,
|
|
440
|
+
requireScreenshot: true,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
399
443
|
for (const baseUrl of this.dependencies.getBaseUrls()) {
|
|
400
444
|
try {
|
|
401
445
|
const proofPayload = JSON.stringify({
|
|
402
446
|
vision: String(context.rawPrompt || message || '').slice(0, 2000),
|
|
447
|
+
qualityProfile: isConstrainedStaticFrontendRequest(String(context.rawPrompt || message || ''))
|
|
448
|
+
? 'constrained-static'
|
|
449
|
+
: 'auto',
|
|
403
450
|
html,
|
|
404
451
|
css,
|
|
405
452
|
js,
|
|
@@ -442,7 +489,9 @@ export class FrontendPreviewService {
|
|
|
442
489
|
const payload = await response.json();
|
|
443
490
|
const modes = payload?.modes || {};
|
|
444
491
|
const summary = payload?.summary || {};
|
|
445
|
-
const
|
|
492
|
+
const constrainedStatic = isConstrainedStaticFrontendRequest(String(context.rawPrompt || message || ''))
|
|
493
|
+
&& modes?.production?.qualityTier === 'scope-complete';
|
|
494
|
+
const visualProof = this.evaluateFrontendVisualProof(summary, artifacts, { constrainedStatic });
|
|
446
495
|
const passed = payload?.success === true
|
|
447
496
|
&& modes?.design?.ready === true
|
|
448
497
|
&& modes?.live?.ready === true
|
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
const MODEL_ALIASES = {
|
|
2
2
|
fast: 'Vigthoria-v4-Assistant-9B', mini: 'vigthoria-mini-0.6b', balanced: 'Vigthoria-v4-Assistant-9B',
|
|
3
|
-
'balanced-4b': 'Vigthoria-v4-Assistant-9B', assistant: 'Vigthoria-v4-Assistant-9B', creative: 'Vigthoria-v4-Creative-27B',
|
|
4
|
-
architect: 'Vigthoria-v4-Creative-27B', code: 'Vigthoria-v4-Code-27B', 'code-30b': 'Vigthoria-v4-Code-27B',
|
|
5
|
-
'code-35b': 'Vigthoria-v4-Code-27B', 'code-8b': 'Vigthoria-v4-Assistant-9B', 'code-9b': 'Vigthoria-v4-Assistant-9B',
|
|
6
|
-
pro: 'Vigthoria-v4-Code-27B', agent: 'Vigthoria-v4-Code-27B', 'vigthoria-code': 'Vigthoria-v4-Code-27B',
|
|
7
|
-
'vigthoria-agent': 'Vigthoria-v4-Code-27B', cloud: 'vigthoria-cloud-balanced', 'cloud-code': 'vigthoria-cloud-code',
|
|
3
|
+
'balanced-4b': 'Vigthoria-v4-Assistant-9B', assistant: 'Vigthoria-v4-Assistant-9B', creative: 'Vigthoria-v4.2-Creative-27B',
|
|
4
|
+
architect: 'Vigthoria-v4.2-Creative-27B', code: 'Vigthoria-v4.2-Code-27B', 'code-30b': 'Vigthoria-v4.2-Code-27B',
|
|
5
|
+
'code-35b': 'Vigthoria-v4.2-Code-27B', 'code-8b': 'Vigthoria-v4-Assistant-9B', 'code-9b': 'Vigthoria-v4-Assistant-9B',
|
|
6
|
+
pro: 'Vigthoria-v4.2-Code-27B', agent: 'Vigthoria-v4.2-Code-27B', 'vigthoria-code': 'Vigthoria-v4.2-Code-27B',
|
|
7
|
+
'vigthoria-agent': 'Vigthoria-v4.2-Code-27B', cloud: 'vigthoria-cloud-balanced', 'cloud-code': 'vigthoria-cloud-code',
|
|
8
8
|
'cloud-balanced': 'vigthoria-cloud-balanced', 'cloud-fast': 'vigthoria-cloud-fast', 'cloud-power': 'vigthoria-cloud-power',
|
|
9
9
|
'cloud-maximum': 'vigthoria-cloud-maximum', 'cloud-reason': 'vigthoria-cloud-power', ultra: 'vigthoria-cloud-maximum',
|
|
10
10
|
'cloud-pro': 'vigthoria-cloud-power', 'cloud-k2': 'vigthoria-cloud-power', 'cloud-ultra': 'vigthoria-cloud-maximum',
|
|
11
|
+
'vigthoria-v4-creative-27b': 'Vigthoria-v4.2-Creative-27B',
|
|
12
|
+
'vigthoria-v4-code-27b': 'Vigthoria-v4.2-Code-27B',
|
|
13
|
+
'vigthoria-v4.2-code-27b': 'Vigthoria-v4.2-Code-27B',
|
|
14
|
+
'vigthoria-v3-code-35b': 'Vigthoria-v4.2-Code-27B',
|
|
15
|
+
'vigthoria-v3-code-35b:latest': 'Vigthoria-v4.2-Code-27B',
|
|
16
|
+
'vigthoria-v3-code-9b': 'Vigthoria-v4-Assistant-9B',
|
|
17
|
+
'vigthoria-v3-code-9b:latest': 'Vigthoria-v4-Assistant-9B',
|
|
18
|
+
'vigthoria-v3-balanced-4b': 'Vigthoria-v4-Assistant-9B',
|
|
19
|
+
'vigthoria-v3-balanced-4b:latest': 'Vigthoria-v4-Assistant-9B',
|
|
20
|
+
'vigthoria-mini-0.6b': 'Vigthoria-v4-Assistant-9B',
|
|
21
|
+
'qwen3-coder:latest': 'Vigthoria-v4.2-Code-27B',
|
|
11
22
|
};
|
|
12
23
|
const RETIRED_MODELS = new Set(['mini', 'creative-v3', 'creative-v4']);
|
|
13
24
|
const LEGACY_CLOUD_IDS = new Set(['deepseek-v3.1:671b-cloud', 'moonshotai/kimi-k2.5']);
|
|
14
25
|
const SELF_HOSTED_MODELS = new Set([
|
|
15
|
-
'Vigthoria-v4-Creative-27B', 'Vigthoria-v4-Code-27B', 'Vigthoria-v4-Assistant-9B',
|
|
16
|
-
'vigthoria-v3-code-35b', 'vigthoria-v3-code-35b:latest', 'vigthoria-v3-code-9b',
|
|
17
|
-
'vigthoria-v3-code-9b:latest', 'vigthoria-v3-balanced-4b', 'vigthoria-v3-balanced-4b:latest', 'qwen3-coder:latest',
|
|
26
|
+
'Vigthoria-v4.2-Creative-27B', 'Vigthoria-v4.2-Code-27B', 'Vigthoria-v4-Assistant-9B',
|
|
18
27
|
]);
|
|
19
28
|
const SELF_HOSTED_ALIASES = new Set([
|
|
20
29
|
'agent', 'architect', 'assistant', 'code', 'code-30b', 'code-35b', 'code-9b', 'balanced', 'balanced-4b', 'pro',
|
|
@@ -30,7 +39,7 @@ export class ModelGovernance {
|
|
|
30
39
|
if (normalized.includes('vigthoria') || requested.includes('/') || requested.includes(':')) {
|
|
31
40
|
return MODEL_ALIASES[normalized] || requested;
|
|
32
41
|
}
|
|
33
|
-
return MODEL_ALIASES[normalized] || 'Vigthoria-v4-Code-27B';
|
|
42
|
+
return MODEL_ALIASES[normalized] || 'Vigthoria-v4.2-Code-27B';
|
|
34
43
|
}
|
|
35
44
|
isCloudModelId(modelId) {
|
|
36
45
|
const normalized = String(modelId || '').toLowerCase();
|
|
@@ -39,8 +48,8 @@ export class ModelGovernance {
|
|
|
39
48
|
resolvePermittedModelId(shortName) {
|
|
40
49
|
const normalized = String(shortName || '').trim().toLowerCase();
|
|
41
50
|
if (RETIRED_MODELS.has(normalized)) {
|
|
42
|
-
this.dependencies.debug(`Blocked retired model ${shortName}; using fallback Vigthoria-v4-Code-27B`);
|
|
43
|
-
return 'Vigthoria-v4-Code-27B';
|
|
51
|
+
this.dependencies.debug(`Blocked retired model ${shortName}; using fallback Vigthoria-v4.2-Code-27B`);
|
|
52
|
+
return 'Vigthoria-v4.2-Code-27B';
|
|
44
53
|
}
|
|
45
54
|
const resolved = this.resolveModelId(shortName);
|
|
46
55
|
if (this.isCloudModelId(resolved) && !this.dependencies.hasCloudAccess()) {
|
|
@@ -54,7 +63,7 @@ export class ModelGovernance {
|
|
|
54
63
|
return this.dependencies.simulateCloudFailure() && this.isCloudModelId(modelId);
|
|
55
64
|
}
|
|
56
65
|
getFallbackModelId(modelId) {
|
|
57
|
-
return this.isCloudModelId(modelId) ? 'Vigthoria-v4-Code-27B' : null;
|
|
66
|
+
return this.isCloudModelId(modelId) ? 'Vigthoria-v4.2-Code-27B' : null;
|
|
58
67
|
}
|
|
59
68
|
isSelfHostedPreferredModel(modelId, requestedModel) {
|
|
60
69
|
return SELF_HOSTED_MODELS.has(modelId) || SELF_HOSTED_ALIASES.has(String(requestedModel || '').toLowerCase());
|
|
@@ -68,8 +77,8 @@ export class ModelGovernance {
|
|
|
68
77
|
}
|
|
69
78
|
getSelfHostedFallbackModelId(modelId, requestedModel) {
|
|
70
79
|
if (this.isSelfHostedPreferredModel(modelId, requestedModel)) {
|
|
71
|
-
return modelId === 'qwen3-coder:latest' ? 'Vigthoria-v4-Code-27B' : modelId;
|
|
80
|
+
return modelId === 'qwen3-coder:latest' ? 'Vigthoria-v4.2-Code-27B' : modelId;
|
|
72
81
|
}
|
|
73
|
-
return 'Vigthoria-v4-Code-27B';
|
|
82
|
+
return 'Vigthoria-v4.2-Code-27B';
|
|
74
83
|
}
|
|
75
84
|
}
|
|
@@ -27,7 +27,7 @@ export class ModelTransportService {
|
|
|
27
27
|
return null;
|
|
28
28
|
}
|
|
29
29
|
async complete(systemPrompt, userPrompt, model, maxTokens) {
|
|
30
|
-
const resolvedModel = model ? this.dependencies.resolvePermittedModelId(model) : 'Vigthoria-v4-Code-27B';
|
|
30
|
+
const resolvedModel = model ? this.dependencies.resolvePermittedModelId(model) : 'Vigthoria-v4.2-Code-27B';
|
|
31
31
|
const messages = scanOutboundContext([{ role: 'system', content: systemPrompt }, { role: 'user', content: userPrompt }]).value;
|
|
32
32
|
const body = {
|
|
33
33
|
model: resolvedModel,
|
|
@@ -31,6 +31,8 @@ const AUDIENCE_HOSTS = {
|
|
|
31
31
|
const AUTO_CREDENTIAL_HOSTS = AUDIENCE_HOSTS.coder;
|
|
32
32
|
const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '::1']);
|
|
33
33
|
const LOOPBACK_PORTS = new Set(['4008', '4009', '4011', '4016', '49160', '8030', '8020', '5060', '5050', '8016', '9000']);
|
|
34
|
+
const TESTFARM_BRIDGE_HOSTS = new Set(['host.lan']);
|
|
35
|
+
const TESTFARM_BRIDGE_PORTS = new Set(['14009', '18020', '18030', '4011']);
|
|
34
36
|
const SENSITIVE_HEADER_NAMES = new Set(['authorization', 'cookie', 'proxy-authorization', 'x-api-key', 'x-service-key']);
|
|
35
37
|
export const DEFAULT_NETWORK_TIMEOUT_MS = 30_000;
|
|
36
38
|
export class NetworkPolicyError extends Error {
|
|
@@ -64,6 +66,16 @@ function isLoopback(url) {
|
|
|
64
66
|
function isAllowedLoopback(url) {
|
|
65
67
|
return isExplicitLoopbackEnabled() && LOOPBACK_PORTS.has(normalizePort(url));
|
|
66
68
|
}
|
|
69
|
+
function isAllowedTestfarmBridge(url) {
|
|
70
|
+
return process.env.VIGTHORIA_LOCAL_TEST_MODE === '1'
|
|
71
|
+
&& process.env.VIGTHORIA_TESTFARM_REMOTE === '1'
|
|
72
|
+
&& process.env.VIGTHORIA_ALLOW_LOCAL_SERVICES === '1'
|
|
73
|
+
&& TESTFARM_BRIDGE_HOSTS.has(url.hostname.toLowerCase())
|
|
74
|
+
&& TESTFARM_BRIDGE_PORTS.has(normalizePort(url));
|
|
75
|
+
}
|
|
76
|
+
function isLocalTrustBoundary(url) {
|
|
77
|
+
return isLoopback(url) || isAllowedTestfarmBridge(url);
|
|
78
|
+
}
|
|
67
79
|
function parseEndpoint(raw) {
|
|
68
80
|
const value = String(raw || '').trim();
|
|
69
81
|
if (!value || /[\\\u0000-\u001f\u007f]/.test(value)) {
|
|
@@ -92,7 +104,7 @@ export function assertTrustedEndpoint(raw, options = {}) {
|
|
|
92
104
|
if (parsed.protocol !== secureProtocol && parsed.protocol !== insecureProtocol) {
|
|
93
105
|
throw new NetworkPolicyError('untrusted_endpoint', `Unsupported endpoint protocol: ${parsed.protocol}`);
|
|
94
106
|
}
|
|
95
|
-
if (parsed.protocol === insecureProtocol && !isAllowedLoopback(parsed)) {
|
|
107
|
+
if (parsed.protocol === insecureProtocol && !isAllowedLoopback(parsed) && !isAllowedTestfarmBridge(parsed)) {
|
|
96
108
|
throw new NetworkPolicyError('untrusted_endpoint', 'Insecure endpoints require explicit loopback opt-in on an approved port');
|
|
97
109
|
}
|
|
98
110
|
if (isLoopback(parsed) && !isAllowedLoopback(parsed)) {
|
|
@@ -100,13 +112,13 @@ export function assertTrustedEndpoint(raw, options = {}) {
|
|
|
100
112
|
}
|
|
101
113
|
const audience = options.audience || 'auto';
|
|
102
114
|
const credentialBearing = options.credentialBearing === true;
|
|
103
|
-
if (audience !== 'public' && audience !== 'auto' && !
|
|
115
|
+
if (audience !== 'public' && audience !== 'auto' && !isLocalTrustBoundary(parsed)) {
|
|
104
116
|
const allowed = AUDIENCE_HOSTS[audience];
|
|
105
117
|
if (!allowed.has(parsed.hostname.toLowerCase()) || normalizePort(parsed) !== '443') {
|
|
106
118
|
throw new NetworkPolicyError('credential_audience', `Endpoint is outside the ${audience} credential audience`);
|
|
107
119
|
}
|
|
108
120
|
}
|
|
109
|
-
if (credentialBearing && audience === 'auto' && !
|
|
121
|
+
if (credentialBearing && audience === 'auto' && !isLocalTrustBoundary(parsed)) {
|
|
110
122
|
if (!AUTO_CREDENTIAL_HOSTS.has(parsed.hostname.toLowerCase()) || normalizePort(parsed) !== '443') {
|
|
111
123
|
throw new NetworkPolicyError('credential_audience', 'Credential-bearing request requires an explicit service audience');
|
|
112
124
|
}
|
|
@@ -7,6 +7,14 @@ export class OperatorClientError extends Error {
|
|
|
7
7
|
this.name = 'OperatorClientError';
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
|
+
class OperatorEndpointRejectedError extends Error {
|
|
11
|
+
safeToTryNextEndpoint;
|
|
12
|
+
constructor(message, safeToTryNextEndpoint) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.safeToTryNextEndpoint = safeToTryNextEndpoint;
|
|
15
|
+
this.name = 'OperatorEndpointRejectedError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
10
18
|
export class OperatorClient {
|
|
11
19
|
dependencies;
|
|
12
20
|
constructor(dependencies) {
|
|
@@ -50,6 +58,8 @@ export class OperatorClient {
|
|
|
50
58
|
workspace: { path: input.workspaceAlias },
|
|
51
59
|
workspace_path: input.workspaceAlias,
|
|
52
60
|
workspace_summary: input.workspaceSummary,
|
|
61
|
+
workspace_authority: context.workspaceAuthority || null,
|
|
62
|
+
client_grounding_report: context.clientGroundingReport || null,
|
|
53
63
|
model: input.resolvedModel,
|
|
54
64
|
history: context.history || [],
|
|
55
65
|
executionSurface: context.executionSurface || 'cli',
|
|
@@ -63,7 +73,9 @@ export class OperatorClient {
|
|
|
63
73
|
vigthoriaBrain: context.vigthoriaBrain || null,
|
|
64
74
|
vigthoria_brain: context.vigthoriaBrain || null,
|
|
65
75
|
},
|
|
66
|
-
|
|
76
|
+
// Mutation-capable BMAD workflows must be selected explicitly by
|
|
77
|
+
// the intent boundary. Direct API callers default to read-only.
|
|
78
|
+
workflow_type: context.workflowType || 'analysis_only',
|
|
67
79
|
options: { stream: true, save_to_vigflow: context.savePlanToVigFlow === true },
|
|
68
80
|
}).value;
|
|
69
81
|
const token = this.dependencies.getAuthToken();
|
|
@@ -72,13 +84,13 @@ export class OperatorClient {
|
|
|
72
84
|
headers: {
|
|
73
85
|
'Content-Type': 'application/json', Accept: 'text/event-stream',
|
|
74
86
|
...(context.mcpContextId ? { 'X-MCP-Context-Id': String(context.mcpContextId) } : {}),
|
|
75
|
-
...(token ? { Authorization: `Bearer ${token}
|
|
87
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
76
88
|
},
|
|
77
89
|
body: JSON.stringify(body), signal: controller.signal,
|
|
78
90
|
});
|
|
79
91
|
if (!response.ok) {
|
|
80
92
|
const detail = await response.text().catch(() => '');
|
|
81
|
-
throw new
|
|
93
|
+
throw new OperatorEndpointRejectedError(`Operator stream ${response.status}: ${this.dependencies.sanitizeError(detail).slice(0, 200)}`, response.status === 404 || response.status === 503);
|
|
82
94
|
}
|
|
83
95
|
const contentType = response.headers.get('content-type')?.toLowerCase() || '';
|
|
84
96
|
const isEventStream = contentType.includes('text/event-stream');
|
|
@@ -140,6 +152,9 @@ export class OperatorClient {
|
|
|
140
152
|
throw new Error(payload.error || payload.message || 'Operator workflow failed');
|
|
141
153
|
}
|
|
142
154
|
}
|
|
155
|
+
if (!result) {
|
|
156
|
+
throw new Error('Operator stream ended without a terminal result event. Reconcile the workflow id before retrying.');
|
|
157
|
+
}
|
|
143
158
|
return { content: this.formatResponse(result || {}), workflowId, contextId, backendUrl: baseUrl, savedWorkflow,
|
|
144
159
|
metadata: { source: 'operator', mode: 'operator', contextId, mcpContextId, savedWorkflowId: savedWorkflow?.id || null } };
|
|
145
160
|
}
|
|
@@ -147,7 +162,11 @@ export class OperatorClient {
|
|
|
147
162
|
if (error?.name === 'AbortError' || error?.code === 'ABORT_ERR') {
|
|
148
163
|
throw new OperatorClientError(`Operator workflow timed out after ${Math.round(input.timeoutMs / 60_000)} minute(s). You can increase the timeout with VIGTHORIA_OPERATOR_TIMEOUT_MS.`, 'timeout');
|
|
149
164
|
}
|
|
150
|
-
|
|
165
|
+
const safeMessage = this.dependencies.sanitizeError(error?.message || String(error));
|
|
166
|
+
errors.push(`${baseUrl}: ${safeMessage}`);
|
|
167
|
+
if (!(error instanceof OperatorEndpointRejectedError) || !error.safeToTryNextEndpoint) {
|
|
168
|
+
throw new OperatorClientError(`Operator workflow outcome may be ambiguous; it was not retried against another endpoint: ${baseUrl}: ${safeMessage}`, 'model_backend');
|
|
169
|
+
}
|
|
151
170
|
}
|
|
152
171
|
finally {
|
|
153
172
|
if (timeoutId)
|
|
@@ -31,9 +31,13 @@ function hasTsConfig(dir) {
|
|
|
31
31
|
* which would violate offline mode and the process approval boundary.
|
|
32
32
|
*/
|
|
33
33
|
function resolveTscBin(workspacePath) {
|
|
34
|
-
const
|
|
35
|
-
if (fs.existsSync(
|
|
36
|
-
|
|
34
|
+
const localTscEntrypoint = path.join(workspacePath, 'node_modules', 'typescript', 'bin', 'tsc');
|
|
35
|
+
if (fs.existsSync(localTscEntrypoint)) {
|
|
36
|
+
// Invoke the JavaScript entrypoint with the current Node executable. The
|
|
37
|
+
// node_modules/.bin/tsc file is a POSIX shim on Windows installations and
|
|
38
|
+
// execFileSync fails it with EINVAL even after `npm run build` succeeded.
|
|
39
|
+
return { cmd: process.execPath, args: [localTscEntrypoint, '--noEmit', '--skipLibCheck'] };
|
|
40
|
+
}
|
|
37
41
|
try {
|
|
38
42
|
execFileSync("tsc", ["--version"], { stdio: "pipe", timeout: 4000, windowsHide: true, env: safeChildProcessEnv() });
|
|
39
43
|
return { cmd: "tsc", args: ["--noEmit", "--skipLibCheck"] };
|
|
@@ -5,52 +5,27 @@ export type PreviewScreenshotResult = {
|
|
|
5
5
|
export interface PreviewScreenshotPort {
|
|
6
6
|
capture(entryAbsolutePath: string, screenshotPath: string): Promise<PreviewScreenshotResult>;
|
|
7
7
|
}
|
|
8
|
-
type BrowserPage = {
|
|
9
|
-
setViewport(options: {
|
|
10
|
-
width: number;
|
|
11
|
-
height: number;
|
|
12
|
-
deviceScaleFactor: number;
|
|
13
|
-
}): Promise<void>;
|
|
14
|
-
goto(url: string, options: {
|
|
15
|
-
waitUntil: string;
|
|
16
|
-
timeout: number;
|
|
17
|
-
}): Promise<void>;
|
|
18
|
-
screenshot(options: {
|
|
19
|
-
path: string;
|
|
20
|
-
fullPage: boolean;
|
|
21
|
-
}): Promise<unknown>;
|
|
22
|
-
};
|
|
23
|
-
type Browser = {
|
|
24
|
-
newPage(): Promise<BrowserPage>;
|
|
25
|
-
close(): Promise<void>;
|
|
26
|
-
process?(): {
|
|
27
|
-
kill(signal?: NodeJS.Signals): boolean;
|
|
28
|
-
} | null;
|
|
29
|
-
};
|
|
30
|
-
type PuppeteerLike = {
|
|
31
|
-
executablePath?(): string;
|
|
32
|
-
launch(options: {
|
|
33
|
-
headless: true | 'shell';
|
|
34
|
-
args: string[];
|
|
35
|
-
userDataDir: string;
|
|
36
|
-
executablePath?: string;
|
|
37
|
-
timeout: number;
|
|
38
|
-
protocolTimeout: number;
|
|
39
|
-
}): Promise<Browser>;
|
|
40
|
-
};
|
|
41
8
|
export type PreviewBrowserResolution = {
|
|
42
|
-
executablePath
|
|
43
|
-
|
|
44
|
-
source: 'packaged' | 'system' | 'injected';
|
|
9
|
+
executablePath: string;
|
|
10
|
+
source: 'system';
|
|
45
11
|
};
|
|
46
|
-
export
|
|
47
|
-
export declare
|
|
48
|
-
|
|
12
|
+
export type PreviewBrowserRunner = (executablePath: string, args: readonly string[], platform: NodeJS.Platform, environment?: NodeJS.ProcessEnv) => Promise<void>;
|
|
13
|
+
export declare function resolvePreviewBrowserExecutable(environment?: NodeJS.ProcessEnv, platform?: NodeJS.Platform, exists?: (candidate: string) => boolean): PreviewBrowserResolution | null;
|
|
14
|
+
export declare const runPreviewBrowser: PreviewBrowserRunner;
|
|
15
|
+
/** Dependency-free system-browser adapter. No browser download or archive
|
|
16
|
+
* extractor is shipped, and only fixed operating-system installation paths
|
|
17
|
+
* are eligible.
|
|
18
|
+
*/
|
|
19
|
+
export declare class SystemBrowserScreenshotAdapter implements PreviewScreenshotPort {
|
|
49
20
|
private readonly environment;
|
|
50
21
|
private readonly allocateTemp;
|
|
51
22
|
private readonly releaseTemp;
|
|
52
23
|
private readonly platform;
|
|
53
|
-
|
|
24
|
+
private readonly exists;
|
|
25
|
+
private readonly runner;
|
|
26
|
+
constructor(environment?: NodeJS.ProcessEnv, allocateTemp?: (prefix: string, requestedMaxBytes?: number) => string, releaseTemp?: (directory: string) => void, platform?: NodeJS.Platform, exists?: (candidate: string) => boolean, runner?: PreviewBrowserRunner);
|
|
54
27
|
capture(entryAbsolutePath: string, screenshotPath: string): Promise<PreviewScreenshotResult>;
|
|
28
|
+
private captureOnce;
|
|
55
29
|
}
|
|
56
|
-
|
|
30
|
+
/** @deprecated Internal compatibility alias retained for older deep imports. */
|
|
31
|
+
export { SystemBrowserScreenshotAdapter as OptionalPuppeteerScreenshotAdapter };
|