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.
Files changed (59) hide show
  1. package/README.md +12 -0
  2. package/completions/_vigthoria +1 -0
  3. package/completions/vigthoria.bash +1 -1
  4. package/completions/vigthoria.fish +1 -0
  5. package/dist/commands/chat.js +73 -26
  6. package/dist/commands/config.js +4 -4
  7. package/dist/commands/creative-registration.d.ts +13 -0
  8. package/dist/commands/creative-registration.js +88 -0
  9. package/dist/commands/fork.d.ts +3 -2
  10. package/dist/commands/fork.js +124 -123
  11. package/dist/commands/game.d.ts +8 -0
  12. package/dist/commands/game.js +113 -9
  13. package/dist/commands/history.d.ts +0 -1
  14. package/dist/commands/history.js +8 -22
  15. package/dist/commands/hub.d.ts +20 -0
  16. package/dist/commands/hub.js +17 -3
  17. package/dist/commands/preview.js +7 -2
  18. package/dist/commands/product-run-registration.js +1 -1
  19. package/dist/commands/replay.d.ts +0 -1
  20. package/dist/commands/replay.js +10 -19
  21. package/dist/commands/repo.js +16 -4
  22. package/dist/commands/update-registration.js +2 -2
  23. package/dist/commands/workflow.d.ts +4 -0
  24. package/dist/commands/workflow.js +27 -0
  25. package/dist/index.js +8 -4
  26. package/dist/utils/agentRunOutcome.d.ts +7 -0
  27. package/dist/utils/agentRunOutcome.js +13 -0
  28. package/dist/utils/api.d.ts +20 -5
  29. package/dist/utils/api.js +428 -43
  30. package/dist/utils/command-policy.js +3 -1
  31. package/dist/utils/config.d.ts +2 -0
  32. package/dist/utils/config.js +22 -9
  33. package/dist/utils/frontend-preview-service.d.ts +1 -0
  34. package/dist/utils/frontend-preview-service.js +54 -5
  35. package/dist/utils/model-governance.js +23 -14
  36. package/dist/utils/model-transport-service.js +1 -1
  37. package/dist/utils/network-policy.js +15 -3
  38. package/dist/utils/operator-client.js +23 -4
  39. package/dist/utils/post-write-validator.js +7 -3
  40. package/dist/utils/preview-screenshot-adapter.d.ts +16 -41
  41. package/dist/utils/preview-screenshot-adapter.js +273 -64
  42. package/dist/utils/runtime-capability.d.ts +7 -0
  43. package/dist/utils/runtime-capability.js +11 -0
  44. package/dist/utils/runtime-temp.d.ts +5 -2
  45. package/dist/utils/runtime-temp.js +131 -30
  46. package/dist/utils/tools.js +1 -1
  47. package/dist/utils/v3-stream-events.js +10 -2
  48. package/dist/utils/v3-workspace-service.d.ts +1 -0
  49. package/dist/utils/v3-workspace-service.js +38 -1
  50. package/dist/utils/vigflow-client.d.ts +9 -0
  51. package/dist/utils/vigflow-client.js +48 -2
  52. package/dist/utils/workspace-reference.d.ts +8 -0
  53. package/dist/utils/workspace-reference.js +21 -0
  54. package/install.ps1 +2 -2
  55. package/install.sh +2 -2
  56. package/package.json +4 -6
  57. package/scripts/release/LOCAL_MACHINE_USER_VERIFICATION.md +2 -2
  58. package/scripts/release/validate-live-service-gates.sh +3 -3
  59. 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
- 'workflow templates', 'workflow list', 'workflow use-template', 'workflow run', 'workflow status',
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',
@@ -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
@@ -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
- merged.authToken = legacyCredentials.authToken;
165
- merged.refreshToken = legacyCredentials.refreshToken;
166
- merged.userId = legacyCredentials.userId;
167
- merged.email = legacyCredentials.email;
168
- merged.v3ServiceKey = legacyCredentials.v3ServiceKey;
169
- merged.subscription = legacyCredentials.subscription;
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: 'Architect, planning, deep reasoning, and creative systems work', tier: 'local', backendModel: 'Vigthoria-v4-Creative-27B' },
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
- if (!hasInteractionOrResponsiveEvidence) {
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 visualProof = this.evaluateFrontendVisualProof(summary, artifacts);
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' && !isLoopback(parsed)) {
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' && !isLoopback(parsed)) {
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
- workflow_type: context.workflowType || 'full',
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}`, Cookie: `vigthoria-auth-token=${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 Error(`Operator stream ${response.status}: ${this.dependencies.sanitizeError(detail).slice(0, 200)}`);
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
- errors.push(`${baseUrl}: ${error?.message || String(error)}`);
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 localTsc = path.join(workspacePath, 'node_modules', '.bin', 'tsc');
35
- if (fs.existsSync(localTsc))
36
- return { cmd: localTsc, args: ["--noEmit", "--skipLibCheck"] };
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?: string;
43
- headless: true | 'shell';
44
- source: 'packaged' | 'system' | 'injected';
9
+ executablePath: string;
10
+ source: 'system';
45
11
  };
46
- export declare function resolvePreviewBrowserExecutable(puppeteer: PuppeteerLike, environment?: NodeJS.ProcessEnv, platform?: NodeJS.Platform, exists?: (candidate: string) => boolean): PreviewBrowserResolution | null;
47
- export declare class OptionalPuppeteerScreenshotAdapter implements PreviewScreenshotPort {
48
- private readonly loadPuppeteer;
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
- constructor(loadPuppeteer?: () => Promise<unknown>, environment?: NodeJS.ProcessEnv, allocateTemp?: (prefix: string) => string, releaseTemp?: (directory: string) => void, platform?: NodeJS.Platform);
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
- export {};
30
+ /** @deprecated Internal compatibility alias retained for older deep imports. */
31
+ export { SystemBrowserScreenshotAdapter as OptionalPuppeteerScreenshotAdapter };