vigthoria-cli 1.13.13 → 1.13.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/utils/api.js CHANGED
@@ -659,6 +659,7 @@ export class APIClient {
659
659
  getV3AgentBaseUrls(preferLocal = false) {
660
660
  const configuredApiUrl = String(this.config.get('apiUrl') || 'https://coder.vigthoria.io').replace(/\/$/, '');
661
661
  const localTestMode = isLocalTestfarmMode();
662
+ const explicitV3EndpointConfigured = Boolean(process.env.VIGTHORIA_V3_AGENT_URL || process.env.V3_AGENT_URL);
662
663
  const includeLoopbackV3Agent = process.env.VIGTHORIA_ALLOW_LOCAL_V3_AGENT === '1'
663
664
  || (preferLocal && isServerRuntime());
664
665
  const localCandidates = [
@@ -672,7 +673,9 @@ export class APIClient {
672
673
  const normalizedRemote = remoteCandidates
673
674
  .filter(Boolean)
674
675
  .map((url) => String(url).replace(/\/$/, ''));
675
- const urls = preferLocal && localCandidates.length > 0
676
+ // An explicit endpoint is an operator routing decision. External installs
677
+ // have none and continue to use the public Coder gateway first.
678
+ const urls = (preferLocal || localTestMode || explicitV3EndpointConfigured) && localCandidates.length > 0
676
679
  ? [...localCandidates, ...normalizedRemote]
677
680
  : [...normalizedRemote, ...localCandidates];
678
681
  return [...new Set(urls)];
@@ -696,6 +699,12 @@ export class APIClient {
696
699
  }
697
700
  return `${baseUrl}/api/v3-agent/background${cleanSuffix}`;
698
701
  }
702
+ getV3AgentApprovalUrl(baseUrl) {
703
+ if (this.isDirectV3AgentBaseUrl(baseUrl)) {
704
+ return `${baseUrl}/api/agent/approval`;
705
+ }
706
+ return `${baseUrl}/api/v3-agent/approval`;
707
+ }
699
708
  getOperatorBaseUrls() {
700
709
  const configuredModelsApiUrl = String(this.config.get('modelsApiUrl') || 'https://api.vigthoria.io').replace(/\/$/, '');
701
710
  const urls = [
@@ -876,6 +885,7 @@ export class APIClient {
876
885
  extractLinkedFrontendAssets(html, entryPath) {
877
886
  const css = new Set();
878
887
  const js = new Set();
888
+ const assets = new Set();
879
889
  const entryDir = path.posix.dirname(this.normalizeWorkspaceRelativePath(entryPath) || '.');
880
890
  const normalizeAsset = (assetPath) => {
881
891
  const clean = String(assetPath || '').trim();
@@ -887,7 +897,7 @@ export class APIClient {
887
897
  return null;
888
898
  }
889
899
  if (withoutQuery.startsWith('/')) {
890
- return this.normalizeWorkspaceRelativePath(withoutQuery.slice(1));
900
+ return this.normalizeWorkspaceRelativePath(path.posix.normalize(path.posix.join(entryDir, withoutQuery.slice(1))));
891
901
  }
892
902
  return this.normalizeWorkspaceRelativePath(path.posix.normalize(path.posix.join(entryDir, withoutQuery)));
893
903
  };
@@ -906,9 +916,16 @@ export class APIClient {
906
916
  js.add(resolved);
907
917
  }
908
918
  }
919
+ const assetPattern = /<(?:link|img|source|video|audio)\b[^>]+(?:href|src)=["']([^"']+)["'][^>]*>/gi;
920
+ while ((match = assetPattern.exec(String(html || ''))) !== null) {
921
+ const resolved = normalizeAsset(match[1]);
922
+ if (resolved)
923
+ assets.add(resolved);
924
+ }
909
925
  return {
910
926
  css: Array.from(css),
911
927
  js: Array.from(js),
928
+ assets: Array.from(assets),
912
929
  };
913
930
  }
914
931
  async buildFrontendForPreview(rootPath) {
@@ -1568,10 +1585,15 @@ export class APIClient {
1568
1585
  'styles.css', 'style.css', 'README.md', 'manifest.json',
1569
1586
  ]);
1570
1587
  /** Keep critical workspace files for server hydration when context must shrink. */
1571
- compactWorkspaceFilesToBudget(files, budgetChars) {
1588
+ compactWorkspaceFilesToBudget(files, budgetChars, priorityPaths = [], mandatoryPaths = priorityPaths) {
1572
1589
  const entries = Object.entries(files);
1590
+ const explicitPriority = new Map(priorityPaths.map((filePath, index) => [this.normalizeWorkspaceRelativePath(filePath), index]));
1591
+ const mandatory = new Set(mandatoryPaths.map((filePath) => this.normalizeWorkspaceRelativePath(filePath)));
1573
1592
  const rank = (filePath) => {
1574
1593
  const normalized = filePath.replace(/\\/g, '/');
1594
+ const explicitIndex = explicitPriority.get(normalized);
1595
+ if (explicitIndex !== undefined)
1596
+ return -100 + Math.min(explicitIndex, 50);
1575
1597
  const base = normalized.split('/').pop() || '';
1576
1598
  if (APIClient.HYDRATION_PRIORITY_BASENAMES.has(base))
1577
1599
  return 0;
@@ -1589,6 +1611,20 @@ export class APIClient {
1589
1611
  const clipped = content.length > maxContentLen ? `${content.slice(0, maxContentLen)}\n/* … truncated for remote context … */` : content;
1590
1612
  const entryLen = JSON.stringify(filePath).length + 1 + JSON.stringify(clipped).length + 1;
1591
1613
  if (used + entryLen > budgetChars) {
1614
+ const normalizedPath = this.normalizeWorkspaceRelativePath(filePath);
1615
+ if (mandatory.has(normalizedPath)) {
1616
+ trimmed[filePath] = clipped;
1617
+ used += entryLen;
1618
+ }
1619
+ else if (explicitPriority.has(normalizedPath)) {
1620
+ const envelope = JSON.stringify(filePath).length + 128;
1621
+ const available = Math.max(256, budgetChars - used - envelope);
1622
+ const dependencyClip = content.length > available
1623
+ ? `${content.slice(0, available)}\n/* … compacted linked preview dependency … */`
1624
+ : content;
1625
+ trimmed[filePath] = dependencyClip;
1626
+ used += JSON.stringify(filePath).length + 1 + JSON.stringify(dependencyClip).length + 1;
1627
+ }
1592
1628
  continue;
1593
1629
  }
1594
1630
  trimmed[filePath] = clipped;
@@ -1603,6 +1639,14 @@ export class APIClient {
1603
1639
  const serverWorkspacePath = this.resolveServerBindableWorkspacePath(resolvedContext);
1604
1640
  const promptFocus = String(resolvedContext.rawPrompt || resolvedContext.contextualPrompt || resolvedContext.prompt || '');
1605
1641
  const localWorkspaceSummary = this.buildLocalWorkspaceSummary(localWorkspacePath, promptFocus);
1642
+ if (resolvedContext.workspaceFilesOutOfBand === true && localWorkspaceSummary?.workspaceFiles) {
1643
+ localWorkspaceSummary.workspaceHydration = {
1644
+ delivery: 'out-of-band',
1645
+ fileCount: Object.keys(localWorkspaceSummary.workspaceFiles).length,
1646
+ complete: true,
1647
+ };
1648
+ delete localWorkspaceSummary.workspaceFiles;
1649
+ }
1606
1650
  const requestedModel = String(resolvedContext.model || resolvedContext.requestedModel || 'agent');
1607
1651
  const resolvedModel = this.resolvePermittedModelId(requestedModel);
1608
1652
  const localWorkspaceName = this.getDisplayWorkspaceName(localWorkspacePath);
@@ -1612,7 +1656,9 @@ export class APIClient {
1612
1656
  const clientToolExecution = resolvedContext.clientToolExecution === false
1613
1657
  ? false
1614
1658
  : (resolvedContext.clientToolExecution === true
1615
- || (localMachineCapable && ['cli', 'fork', 'local-ide', 'desktop', 'local'].includes(executionSurface)));
1659
+ || (!serverWorkspacePath
1660
+ && localMachineCapable
1661
+ && ['cli', 'fork', 'local-ide', 'desktop', 'local'].includes(executionSurface)));
1616
1662
  const publicRuntimeEnvironment = this.buildPublicRuntimeEnvironment(resolvedContext.agentRuntime, {
1617
1663
  localWorkspacePath,
1618
1664
  serverWorkspacePath,
@@ -1658,6 +1704,8 @@ export class APIClient {
1658
1704
  requestedModelResolved: resolvedModel,
1659
1705
  agentExecutionPolicy: resolvedContext.agentExecutionPolicy || null,
1660
1706
  legacyFallbackAllowed: resolvedContext.legacyFallbackAllowed === true,
1707
+ approvalLevel: resolvedContext.approvalLevel === 'auto' ? 'auto' : 'confirm',
1708
+ approval_level: resolvedContext.approvalLevel === 'auto' ? 'auto' : 'confirm',
1661
1709
  executionSurface: resolvedContext.executionSurface || 'cli',
1662
1710
  clientSurface: resolvedContext.clientSurface || 'cli',
1663
1711
  localMachineCapable,
@@ -1752,11 +1800,11 @@ export class APIClient {
1752
1800
  const overhead = json.length - JSON.stringify(summary.workspaceFiles).length;
1753
1801
  const budget = LIMIT - overhead - 1024;
1754
1802
  if (budget > 0) {
1755
- summary.workspaceFiles = this.compactWorkspaceFilesToBudget(summary.workspaceFiles, budget);
1803
+ summary.workspaceFiles = this.compactWorkspaceFilesToBudget(summary.workspaceFiles, budget, summary.focusFiles || [], summary.explicitFocusFiles || summary.focusFiles || []);
1756
1804
  summary.workspaceFilesCompaction = Object.keys(summary.workspaceFiles).length > 0 ? 'priority-trimmed' : 'empty';
1757
1805
  }
1758
1806
  else if (hydrationRequired) {
1759
- summary.workspaceFiles = this.compactWorkspaceFilesToBudget(summary.workspaceFiles, 8_000);
1807
+ summary.workspaceFiles = this.compactWorkspaceFilesToBudget(summary.workspaceFiles, 8_000, summary.focusFiles || [], summary.explicitFocusFiles || summary.focusFiles || []);
1760
1808
  summary.workspaceFilesCompaction = 'priority-minimal';
1761
1809
  }
1762
1810
  else {
@@ -1781,7 +1829,7 @@ export class APIClient {
1781
1829
  return finish();
1782
1830
  }
1783
1831
  if (hydrationRequired && summary?.workspaceFiles && typeof summary.workspaceFiles === 'object') {
1784
- summary.workspaceFiles = this.compactWorkspaceFilesToBudget(summary.workspaceFiles, 4_000);
1832
+ summary.workspaceFiles = this.compactWorkspaceFilesToBudget(summary.workspaceFiles, 4_000, summary.focusFiles || [], summary.explicitFocusFiles || summary.focusFiles || []);
1785
1833
  summary.workspaceFilesCompaction = 'priority-minimal';
1786
1834
  json = JSON.stringify(payload);
1787
1835
  if (json.length <= LIMIT)
@@ -2657,8 +2705,29 @@ menu {
2657
2705
  };
2658
2706
  const snapshot = this.getAgentWorkspaceSnapshot(rootPath);
2659
2707
  const candidatePaths = snapshot.paths.filter((entry) => !/(^|[\/\\])\.vigthoria([\/\\]|$)/.test(entry));
2708
+ const focusFiles = this.extractExpectedWorkspaceFiles(requestFocus)
2709
+ .map((entry) => this.normalizeWorkspaceRelativePath(entry))
2710
+ .filter((entry) => candidatePaths.includes(entry));
2711
+ const linkedFocusAssets = [];
2712
+ for (const focusPath of focusFiles) {
2713
+ if (!/\.html?$/i.test(focusPath))
2714
+ continue;
2715
+ try {
2716
+ const html = fs.readFileSync(path.join(rootPath, focusPath), 'utf8');
2717
+ const linkedAssets = this.extractLinkedFrontendAssets(html, focusPath);
2718
+ for (const assetPath of [...linkedAssets.assets, ...linkedAssets.css, ...linkedAssets.js]) {
2719
+ if (candidatePaths.includes(assetPath) && !linkedFocusAssets.includes(assetPath)) {
2720
+ linkedFocusAssets.push(assetPath);
2721
+ }
2722
+ }
2723
+ }
2724
+ catch {
2725
+ // Best effort: the explicit target remains in focusFiles.
2726
+ }
2727
+ }
2728
+ const hydrationFocusFiles = [...focusFiles, ...linkedFocusAssets];
2660
2729
  const agentStatePaths = this.collectAgentStateSyncPaths(rootPath);
2661
- let orderedPaths = [...agentStatePaths, ...candidatePaths];
2730
+ let orderedPaths = [...agentStatePaths, ...hydrationFocusFiles, ...candidatePaths];
2662
2731
  try {
2663
2732
  const ranked = buildSemanticContext(rootPath, requestFocus, 60).topFiles.map((file) => file.path);
2664
2733
  const changes = getChangedFiles(rootPath, candidatePaths);
@@ -2670,6 +2739,8 @@ menu {
2670
2739
  // silently dropped progress.json and forced /continue to re-plan.
2671
2740
  for (const filePath of agentStatePaths)
2672
2741
  ordered.add(filePath);
2742
+ for (const filePath of hydrationFocusFiles)
2743
+ ordered.add(filePath);
2673
2744
  for (const filePath of changes.changed)
2674
2745
  ordered.add(filePath);
2675
2746
  for (const filePath of ranked)
@@ -2685,9 +2756,11 @@ menu {
2685
2756
  };
2686
2757
  }
2687
2758
  catch {
2688
- orderedPaths = [...agentStatePaths, ...candidatePaths];
2759
+ orderedPaths = [...agentStatePaths, ...hydrationFocusFiles, ...candidatePaths];
2689
2760
  }
2690
2761
  summary.fileCount = snapshot.fileCount;
2762
+ summary.focusFiles = hydrationFocusFiles;
2763
+ summary.explicitFocusFiles = focusFiles;
2691
2764
  summary.files = orderedPaths.slice(0, 40);
2692
2765
  const packageJsonPath = path.join(rootPath, 'package.json');
2693
2766
  if (fs.existsSync(packageJsonPath)) {
@@ -2714,6 +2787,21 @@ menu {
2714
2787
  return null;
2715
2788
  }
2716
2789
  }
2790
+ /**
2791
+ * Build complete workspace hydration separately from model context.
2792
+ * Context compaction must not become filesystem compaction.
2793
+ */
2794
+ buildOutOfBandWorkspaceFiles(context) {
2795
+ const resolvedContext = this.ensureExecutionContext(context);
2796
+ const localWorkspacePath = this.resolveAgentTargetPath(resolvedContext);
2797
+ const serverWorkspacePath = this.resolveServerBindableWorkspacePath(resolvedContext);
2798
+ if (serverWorkspacePath || !localWorkspacePath)
2799
+ return undefined;
2800
+ const promptFocus = String(resolvedContext.rawPrompt || resolvedContext.contextualPrompt || resolvedContext.prompt || '');
2801
+ const summary = this.buildLocalWorkspaceSummary(localWorkspacePath, promptFocus);
2802
+ const files = summary?.workspaceFiles;
2803
+ return files && typeof files === 'object' ? files : undefined;
2804
+ }
2717
2805
  /**
2718
2806
  * Collect text file contents from the workspace for V3 agent hydration.
2719
2807
  * Budget: up to ~2 MB total, per-file cap 200 KB, skip binary extensions.
@@ -2722,7 +2810,7 @@ menu {
2722
2810
  const MAX_TOTAL_BYTES = 2 * 1024 * 1024;
2723
2811
  const MAX_FILE_BYTES = 200 * 1024;
2724
2812
  const BINARY_EXTENSIONS = new Set([
2725
- '.png', '.jpg', '.jpeg', '.gif', '.bmp', '.ico', '.svg', '.webp', '.avif',
2813
+ '.png', '.jpg', '.jpeg', '.gif', '.bmp', '.ico', '.webp', '.avif',
2726
2814
  '.mp3', '.mp4', '.wav', '.ogg', '.webm', '.flac', '.aac',
2727
2815
  '.zip', '.gz', '.tar', '.rar', '.7z', '.bz2',
2728
2816
  '.exe', '.dll', '.so', '.dylib', '.bin', '.dat',
@@ -3028,7 +3116,9 @@ menu {
3028
3116
  const args = event.arguments || {};
3029
3117
  const serverRoot = String(context.__v3ServerWorkspaceRoot || '').trim();
3030
3118
  const isRunCommand = name === 'run_command';
3031
- const pathArg = isRunCommand ? (args.cwd ?? args.path ?? '.') : (args.path || args.cwd || '.');
3119
+ const pathArg = isRunCommand
3120
+ ? (args.cwd ?? args.path ?? args.directory ?? '.')
3121
+ : (args.path || args.directory || args.cwd || '.');
3032
3122
  let target = this.resolveV3ClientToolPath(rootPath, pathArg, serverRoot ? [serverRoot] : []);
3033
3123
  if (!target && isRunCommand) {
3034
3124
  target = this.resolveV3ClientToolPath(rootPath, '.', serverRoot ? [serverRoot] : []);
@@ -4120,6 +4210,7 @@ document.addEventListener('DOMContentLoaded', () => {
4120
4210
  const expectedFiles = this.extractExpectedWorkspaceFiles(message, executionContext);
4121
4211
  const requestedModel = String(executionContext.model || executionContext.requestedModel || 'agent');
4122
4212
  const resolvedModel = this.resolvePermittedModelId(requestedModel);
4213
+ const workspaceFiles = this.buildOutOfBandWorkspaceFiles(executionContext);
4123
4214
  const preferLocalV3 = /(premium|polished|landing|site|page|dashboard|saas|frontend|ui|responsive|animated|create the required project files and write them to the workspace)/i.test(message)
4124
4215
  && context.localMachineCapable !== false;
4125
4216
  const timeoutMs = resolvePlannerAgentTimeoutMs(baseTimeoutMs, String(executionContext.agentTaskType || ''), String(executionContext.rawPrompt || message || ''));
@@ -4149,7 +4240,8 @@ document.addEventListener('DOMContentLoaded', () => {
4149
4240
  strict_mode: useRelaxedAttempt ? false : requestExecutionContext.legacyFallbackAllowed !== true,
4150
4241
  context: useRelaxedAttempt
4151
4242
  ? this.buildMinimalV3AgentContext(requestExecutionContext)
4152
- : this.buildV3AgentContext(requestExecutionContext),
4243
+ : this.buildV3AgentContext({ ...requestExecutionContext, workspaceFilesOutOfBand: !!workspaceFiles }),
4244
+ workspace_files: useRelaxedAttempt ? undefined : workspaceFiles,
4153
4245
  context_id: contextIdOverride ?? requestExecutionContext.contextId,
4154
4246
  mcp_context_id: useRelaxedAttempt ? null : requestExecutionContext.mcpContextId || null,
4155
4247
  stream: true,
@@ -4364,18 +4456,23 @@ document.addEventListener('DOMContentLoaded', () => {
4364
4456
  await this.ensureV3ServiceKey();
4365
4457
  const executionContext = await this.bindExecutionContext({
4366
4458
  ...context,
4459
+ rawPrompt: context.rawPrompt || context.prompt || message,
4460
+ prompt: context.prompt || context.rawPrompt || message,
4367
4461
  backgroundJob: true,
4368
4462
  clientToolExecution: false,
4369
4463
  });
4370
4464
  const requestedModel = String(executionContext.model || executionContext.requestedModel || 'agent');
4371
4465
  const resolvedModel = this.resolvePermittedModelId(requestedModel);
4466
+ const workspaceFiles = this.buildOutOfBandWorkspaceFiles(executionContext);
4372
4467
  const body = {
4373
4468
  request: message,
4374
4469
  context: this.buildV3AgentContext({
4375
4470
  ...executionContext,
4376
4471
  backgroundJob: true,
4377
4472
  clientToolExecution: false,
4473
+ workspaceFilesOutOfBand: !!workspaceFiles,
4378
4474
  }),
4475
+ workspace_files: workspaceFiles,
4379
4476
  stream: false,
4380
4477
  model: resolvedModel,
4381
4478
  mcp_context_id: executionContext.mcpContextId || undefined,
@@ -4464,6 +4561,33 @@ document.addEventListener('DOMContentLoaded', () => {
4464
4561
  }
4465
4562
  throw new Error(`Unable to cancel background job: ${errors.join('; ')}`);
4466
4563
  }
4564
+ async resolveV3BackgroundApproval(jobId, approvalId, approved, allowMode = 'once') {
4565
+ await this.ensureV3ServiceKey();
4566
+ const errors = [];
4567
+ for (const baseUrl of this.getV3AgentBaseUrls(false)) {
4568
+ try {
4569
+ const response = await fetch(this.getV3AgentApprovalUrl(baseUrl), {
4570
+ method: 'POST',
4571
+ headers: await this.getV3AgentHeaders(),
4572
+ body: JSON.stringify({
4573
+ context_id: jobId,
4574
+ approval_id: approvalId,
4575
+ approved,
4576
+ allow_mode: allowMode,
4577
+ }),
4578
+ });
4579
+ if (!response.ok) {
4580
+ errors.push(`${baseUrl}: ${response.status}`);
4581
+ continue;
4582
+ }
4583
+ return await response.json();
4584
+ }
4585
+ catch (error) {
4586
+ errors.push(`${baseUrl}: ${error?.message || String(error)}`);
4587
+ }
4588
+ }
4589
+ throw new Error(`Unable to resolve background approval: ${errors.join('; ')}`);
4590
+ }
4467
4591
  async getV3BackgroundJobFiles(jobId) {
4468
4592
  await this.ensureV3ServiceKey();
4469
4593
  const errors = [];
@@ -4486,7 +4610,45 @@ document.addEventListener('DOMContentLoaded', () => {
4486
4610
  throw new Error(`Unable to fetch background job files: ${errors.join('; ')}`);
4487
4611
  }
4488
4612
  async applyV3BackgroundJobFiles(jobId, context = {}) {
4489
- const data = await this.getV3BackgroundJobFiles(jobId);
4613
+ const [data, job] = await Promise.all([
4614
+ this.getV3BackgroundJobFiles(jobId),
4615
+ this.getV3BackgroundJob(jobId),
4616
+ ]);
4617
+ if (job?.status !== 'completed' || job?.result?.success === false || job?.result?.type === 'error') {
4618
+ throw new Error(`Background job ${jobId} is not a successful completed job and cannot be applied.`);
4619
+ }
4620
+ const mutatedPaths = new Set();
4621
+ const addMutatedPath = (value) => {
4622
+ const normalized = this.normalizeWorkspaceRelativePath(String(value || ''));
4623
+ if (!normalized || path.isAbsolute(normalized) || normalized.split('/').includes('..'))
4624
+ return;
4625
+ mutatedPaths.add(normalized);
4626
+ };
4627
+ for (const filePath of (data.confirmed_mutations || []))
4628
+ addMutatedPath(filePath);
4629
+ for (const event of (Array.isArray(job?.events) ? job.events : [])) {
4630
+ if (event?.type === 'file_mutation' && event?.action !== 'delete')
4631
+ addMutatedPath(event.path);
4632
+ if (event?.type === 'executor_complete') {
4633
+ let summary = event.summary;
4634
+ if (typeof summary === 'string') {
4635
+ try {
4636
+ summary = JSON.parse(summary);
4637
+ }
4638
+ catch {
4639
+ summary = null;
4640
+ }
4641
+ }
4642
+ if (summary?.status === 'completed') {
4643
+ for (const filePath of [...(summary.changed_files || []), ...(summary.created_files || [])]) {
4644
+ addMutatedPath(filePath);
4645
+ }
4646
+ }
4647
+ }
4648
+ }
4649
+ if (mutatedPaths.size === 0) {
4650
+ throw new Error(`Background job ${jobId} has no confirmed successful file mutations to apply.`);
4651
+ }
4490
4652
  const rootPath = this.resolveAgentTargetPath({
4491
4653
  ...context,
4492
4654
  workspacePath: context.workspacePath || data.local_workspace_path,
@@ -4499,6 +4661,11 @@ document.addEventListener('DOMContentLoaded', () => {
4499
4661
  const applied = [];
4500
4662
  const skipped = [];
4501
4663
  for (const [relativePath, content] of Object.entries(data.files || {})) {
4664
+ const normalizedRelativePath = this.normalizeWorkspaceRelativePath(relativePath);
4665
+ if (!mutatedPaths.has(normalizedRelativePath)) {
4666
+ skipped.push(relativePath);
4667
+ continue;
4668
+ }
4502
4669
  if (typeof content !== 'string') {
4503
4670
  skipped.push(relativePath);
4504
4671
  continue;
@@ -4924,7 +5091,7 @@ document.addEventListener('DOMContentLoaded', () => {
4924
5091
  'vigthoria-cloud-ultra',
4925
5092
  ]);
4926
5093
  if (cloudModels.has(resolvedModel)) {
4927
- return 'vigthoria-v3-code-35b';
5094
+ return 'Vigthoria-v4-Code-27B';
4928
5095
  }
4929
5096
  return null;
4930
5097
  }
@@ -4940,10 +5107,10 @@ document.addEventListener('DOMContentLoaded', () => {
4940
5107
  }
4941
5108
  resolvePermittedModelId(shortName) {
4942
5109
  const normalizedRequested = String(shortName || '').trim().toLowerCase();
4943
- const blockedModels = new Set(['fast', 'mini', 'creative', 'creative-v3', 'creative-v4']);
5110
+ const blockedModels = new Set(['mini', 'creative-v3', 'creative-v4']);
4944
5111
  if (blockedModels.has(normalizedRequested)) {
4945
- this.logger.debug(`Blocked governed model ${shortName}; using fallback vigthoria-v3-code-35b`);
4946
- return 'vigthoria-v3-code-35b';
5112
+ this.logger.debug(`Blocked retired model ${shortName}; using fallback Vigthoria-v4-Code-27B`);
5113
+ return 'Vigthoria-v4-Code-27B';
4947
5114
  }
4948
5115
  const resolvedModel = this.resolveModelId(shortName);
4949
5116
  if (this.isCloudModelId(resolvedModel) && !this.canUseCloudModel()) {
@@ -4969,6 +5136,9 @@ document.addEventListener('DOMContentLoaded', () => {
4969
5136
  isSelfHostedPreferredModel(resolvedModel, requestedModel) {
4970
5137
  const normalizedRequested = String(requestedModel || '').toLowerCase();
4971
5138
  const selfHostedModels = new Set([
5139
+ 'Vigthoria-v4-Creative-27B',
5140
+ 'Vigthoria-v4-Code-27B',
5141
+ 'Vigthoria-v4-Assistant-9B',
4972
5142
  'vigthoria-v3-code-35b',
4973
5143
  'vigthoria-v3-code-35b:latest',
4974
5144
  'vigthoria-v3-code-9b',
@@ -4979,6 +5149,8 @@ document.addEventListener('DOMContentLoaded', () => {
4979
5149
  ]);
4980
5150
  return selfHostedModels.has(resolvedModel)
4981
5151
  || normalizedRequested === 'agent'
5152
+ || normalizedRequested === 'architect'
5153
+ || normalizedRequested === 'assistant'
4982
5154
  || normalizedRequested === 'code'
4983
5155
  || normalizedRequested === 'code-30b'
4984
5156
  || normalizedRequested === 'code-35b'
@@ -4989,9 +5161,9 @@ document.addEventListener('DOMContentLoaded', () => {
4989
5161
  }
4990
5162
  getSelfHostedFallbackModelId(resolvedModel, requestedModel) {
4991
5163
  if (this.isSelfHostedPreferredModel(resolvedModel, requestedModel)) {
4992
- return resolvedModel === 'qwen3-coder:latest' ? 'vigthoria-v3-code-35b' : resolvedModel;
5164
+ return resolvedModel === 'qwen3-coder:latest' ? 'Vigthoria-v4-Code-27B' : resolvedModel;
4993
5165
  }
4994
- return 'vigthoria-v3-code-35b';
5166
+ return 'Vigthoria-v4-Code-27B';
4995
5167
  }
4996
5168
  // Streaming chat
4997
5169
  async *chatStream(messages, model) {
@@ -5074,7 +5246,7 @@ document.addEventListener('DOMContentLoaded', () => {
5074
5246
  // (/v1/chat/completions on api.vigthoria.io) which is the only
5075
5247
  // backend that reliably accepts our auth token.
5076
5248
  async chatComplete(systemPrompt, userPrompt, model, maxTokens) {
5077
- const resolvedModel = model ? this.resolvePermittedModelId(model) : 'vigthoria-v3-code-35b';
5249
+ const resolvedModel = model ? this.resolvePermittedModelId(model) : 'Vigthoria-v4-Code-27B';
5078
5250
  const response = await this.modelRouterClient.post('/v1/chat/completions', {
5079
5251
  model: resolvedModel,
5080
5252
  messages: [
@@ -5924,21 +6096,23 @@ document.addEventListener('DOMContentLoaded', () => {
5924
6096
  // ═══════════════════════════════════════════════════════════════
5925
6097
  // Vigthoria server infrastructure models
5926
6098
  // ═══════════════════════════════════════════════════════════════
5927
- 'fast': 'vigthoria-v3-balanced-4b',
6099
+ 'fast': 'Vigthoria-v4-Assistant-9B',
5928
6100
  'mini': 'vigthoria-mini-0.6b',
5929
- 'balanced': 'vigthoria-v3-balanced-4b',
5930
- 'balanced-4b': 'vigthoria-v3-balanced-4b',
5931
- 'creative': 'vigthoria-creative-9b-v4',
6101
+ 'balanced': 'Vigthoria-v4-Assistant-9B',
6102
+ 'balanced-4b': 'Vigthoria-v4-Assistant-9B',
6103
+ 'assistant': 'Vigthoria-v4-Assistant-9B',
6104
+ 'creative': 'Vigthoria-v4-Creative-27B',
6105
+ 'architect': 'Vigthoria-v4-Creative-27B',
5932
6106
  // Code Models - 35B is the default powerhouse
5933
- 'code': 'vigthoria-v3-code-35b', // Vigthoria server infrastructure 35B model
5934
- 'code-30b': 'vigthoria-v3-code-35b',
5935
- 'code-35b': 'vigthoria-v3-code-35b',
5936
- 'code-8b': 'vigthoria-v3-code-9b',
5937
- 'code-9b': 'vigthoria-v3-code-9b',
5938
- 'pro': 'vigthoria-v3-code-35b',
5939
- 'agent': 'vigthoria-v3-code-35b',
5940
- 'vigthoria-code': 'vigthoria-v3-code-35b',
5941
- 'vigthoria-agent': 'vigthoria-v3-code-35b',
6107
+ 'code': 'Vigthoria-v4-Code-27B',
6108
+ 'code-30b': 'Vigthoria-v4-Code-27B',
6109
+ 'code-35b': 'Vigthoria-v4-Code-27B',
6110
+ 'code-8b': 'Vigthoria-v4-Assistant-9B',
6111
+ 'code-9b': 'Vigthoria-v4-Assistant-9B',
6112
+ 'pro': 'Vigthoria-v4-Code-27B',
6113
+ 'agent': 'Vigthoria-v4-Code-27B',
6114
+ 'vigthoria-code': 'Vigthoria-v4-Code-27B',
6115
+ 'vigthoria-agent': 'Vigthoria-v4-Code-27B',
5942
6116
  // ═══════════════════════════════════════════════════════════════
5943
6117
  // VIGTHORIA CLOUD - current billing catalog aliases
5944
6118
  // ═══════════════════════════════════════════════════════════════
@@ -5956,17 +6130,18 @@ document.addEventListener('DOMContentLoaded', () => {
5956
6130
  'cloud-ultra': 'vigthoria-cloud-maximum',
5957
6131
  };
5958
6132
  // If already a full model ID, return as-is
5959
- if (shortName.includes('vigthoria') || shortName.includes('/') || shortName.includes(':')) {
5960
- if (modelMap[shortName]) {
5961
- return modelMap[shortName];
6133
+ const normalizedShortName = String(shortName || '').toLowerCase();
6134
+ if (normalizedShortName.includes('vigthoria') || shortName.includes('/') || shortName.includes(':')) {
6135
+ if (modelMap[normalizedShortName]) {
6136
+ return modelMap[normalizedShortName];
5962
6137
  }
5963
6138
  return shortName;
5964
6139
  }
5965
- return modelMap[shortName] || 'vigthoria-v3-code-35b';
6140
+ return modelMap[normalizedShortName] || 'Vigthoria-v4-Code-27B';
5966
6141
  }
5967
6142
  async getCoderHealth() {
5968
6143
  try {
5969
- const response = await this.client.get('/api/health', { timeout: 5000 });
6144
+ const response = await this.client.get('/api/health', { timeout: 12000 });
5970
6145
  const ok = response.data?.status === 'ok' || response.data?.healthy === true;
5971
6146
  return {
5972
6147
  name: 'Coder API',
@@ -6141,7 +6316,7 @@ document.addEventListener('DOMContentLoaded', () => {
6141
6316
  const candidates = ['/v1/models', '/models', '/api/models', '/api/tags'];
6142
6317
  for (const endpoint of candidates) {
6143
6318
  try {
6144
- const response = await client.get(endpoint, { timeout: 5000 });
6319
+ const response = await client.get(endpoint, { timeout: 12000 });
6145
6320
  const modelCount = this.extractModelCount(response.data);
6146
6321
  if (modelCount > 0) {
6147
6322
  return { modelCount, endpoint };
@@ -6157,7 +6332,7 @@ document.addEventListener('DOMContentLoaded', () => {
6157
6332
  const modelsApiUrl = this.config.get('modelsApiUrl');
6158
6333
  try {
6159
6334
  const [healthResponse, modelProbe] = await Promise.all([
6160
- this.modelRouterClient.get('/health', { timeout: 5000 }),
6335
+ this.modelRouterClient.get('/health', { timeout: 12000 }),
6161
6336
  this.probeModelList(this.modelRouterClient),
6162
6337
  ]);
6163
6338
  const healthOk = this.isHealthyServicePayload(healthResponse.data);
@@ -6190,7 +6365,7 @@ document.addEventListener('DOMContentLoaded', () => {
6190
6365
  }
6191
6366
  try {
6192
6367
  const [healthResponse, modelProbe] = await Promise.all([
6193
- this.selfHostedModelRouterClient.get('/health', { timeout: 5000 }),
6368
+ this.selfHostedModelRouterClient.get('/health', { timeout: 12000 }),
6194
6369
  this.probeModelList(this.selfHostedModelRouterClient),
6195
6370
  ]);
6196
6371
  const healthOk = this.isHealthyServicePayload(healthResponse.data);
@@ -6228,7 +6403,7 @@ document.addEventListener('DOMContentLoaded', () => {
6228
6403
  for (const endpoint of candidates) {
6229
6404
  try {
6230
6405
  const controller = new AbortController();
6231
- const timer = setTimeout(() => controller.abort(), 3000);
6406
+ const timer = setTimeout(() => controller.abort(), 12000);
6232
6407
  const response = await fetch(endpoint, {
6233
6408
  method: 'GET',
6234
6409
  headers,
@@ -6303,9 +6478,13 @@ document.addEventListener('DOMContentLoaded', () => {
6303
6478
  const endpoint = process.env.VIGTHORIA_HYPERLOOP_URL || `${configuredApiUrl}/api/hyperloop/health`;
6304
6479
  try {
6305
6480
  const token = this.getAccessToken();
6481
+ const hyperLoopServiceKey = process.env.VIGTHORIA_HYPERLOOP_SERVICE_KEY || process.env.HYPERLOOP_SERVICE_KEY || '';
6306
6482
  const response = await fetchWithServiceTimeout(endpoint, {
6307
6483
  method: 'GET',
6308
- headers: token ? { Authorization: `Bearer ${token}` } : undefined,
6484
+ headers: {
6485
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
6486
+ ...(hyperLoopServiceKey ? { 'X-Service-Key': hyperLoopServiceKey } : {}),
6487
+ },
6309
6488
  });
6310
6489
  if (!response.ok) {
6311
6490
  throw new Error(`Hyper Loop health ${response.status}`);
@@ -6333,13 +6512,16 @@ document.addEventListener('DOMContentLoaded', () => {
6333
6512
  const endpoint = process.env.VIGTHORIA_HYPERLOOP_EXECUTE_URL || `${configuredApiUrl}/api/hyperloop/execute`;
6334
6513
  const modulesEndpoint = process.env.VIGTHORIA_HYPERLOOP_MODULES_URL || `${configuredApiUrl}/api/hyperloop/modules`;
6335
6514
  const token = this.getAccessToken();
6515
+ const hyperLoopServiceKey = process.env.VIGTHORIA_HYPERLOOP_SERVICE_KEY || process.env.HYPERLOOP_SERVICE_KEY || '';
6516
+ const hyperLoopHeaders = {
6517
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
6518
+ ...(hyperLoopServiceKey ? { 'X-Service-Key': hyperLoopServiceKey } : {}),
6519
+ };
6336
6520
  const projectPath = this.resolveAgentTargetPath(context);
6337
6521
  try {
6338
6522
  const modulesResponse = await fetchWithServiceTimeout(modulesEndpoint, {
6339
6523
  method: 'GET',
6340
- headers: {
6341
- ...(token ? { Authorization: `Bearer ${token}` } : {}),
6342
- },
6524
+ headers: hyperLoopHeaders,
6343
6525
  });
6344
6526
  if (!modulesResponse.ok) {
6345
6527
  throw new Error(`Repo memory modules ${modulesResponse.status}`);
@@ -6351,10 +6533,7 @@ document.addEventListener('DOMContentLoaded', () => {
6351
6533
  try {
6352
6534
  const probeResponse = await fetchWithServiceTimeout(endpoint, {
6353
6535
  method: 'POST',
6354
- headers: {
6355
- 'Content-Type': 'application/json',
6356
- ...(token ? { Authorization: `Bearer ${token}` } : {}),
6357
- },
6536
+ headers: { 'Content-Type': 'application/json', ...hyperLoopHeaders },
6358
6537
  body: JSON.stringify({
6359
6538
  module: 'repo_context_compactor',
6360
6539
  payload: {
@@ -6371,7 +6550,9 @@ document.addEventListener('DOMContentLoaded', () => {
6371
6550
  });
6372
6551
  if (probeResponse.ok) {
6373
6552
  const probeData = await probeResponse.json();
6374
- compactContextLength = String(probeData?.result?.compact_context || '').length;
6553
+ compactContextLength = String(probeData?.result?.compact_context
6554
+ || probeData?.result?.result?.compact_context
6555
+ || '').length;
6375
6556
  }
6376
6557
  }
6377
6558
  catch {
@@ -6488,12 +6669,12 @@ document.addEventListener('DOMContentLoaded', () => {
6488
6669
  });
6489
6670
  }
6490
6671
  async getCapabilityTruthStatus(context = {}) {
6491
- // Wrap each probe with its own 6 s timeout so they always resolve
6492
- // before the outer 8 s race in auth.ts, producing real error messages
6493
- // (ECONNREFUSED, 404, etc.) instead of the generic "Timed out (8s)".
6672
+ // The public edge can legitimately take 5-10 seconds during cold routing.
6673
+ // Keep a finite ceiling, but do not turn a healthy slow edge into the old
6674
+ // hard 10-second preflight failure seen on external client machines.
6494
6675
  const withTimeout = (p, name) => Promise.race([
6495
6676
  p,
6496
- new Promise(resolve => setTimeout(() => resolve({ name, endpoint: '', ok: false, error: 'Service not reachable (6 s timeout)' }), 6000)),
6677
+ new Promise(resolve => setTimeout(() => resolve({ name, endpoint: '', ok: false, error: 'Service not reachable (15 s timeout)' }), 15000)),
6497
6678
  ]);
6498
6679
  const [v3Agent, hyperLoop, repoMemory, devtoolsBridge] = await Promise.all([
6499
6680
  withTimeout(this.getV3AgentHealth(), 'V3 Agent'),
@@ -1,5 +1,14 @@
1
1
  export type ClientManifestPayload = {
2
2
  product_id: string;
3
+ context_contract_version: string;
4
+ source_product_id: string;
5
+ active_application_id: string;
6
+ active_surface_id: string;
7
+ target_product_id: string;
8
+ workspace_kind: string;
9
+ selected_engine_id: string;
10
+ capability_catalog_version: string;
11
+ capability_scope_id: string;
3
12
  version: string;
4
13
  os: string;
5
14
  execution_mode: string;