vigthoria-cli 1.11.12 → 1.11.18

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
@@ -12,6 +12,8 @@ import WebSocket from 'ws';
12
12
  import { buildSemanticContext } from './context-ranker.js';
13
13
  import { getChangedFiles } from './workspace-cache.js';
14
14
  import { isSubstantiveAgentAnswer, isToolEvidenceStubAnswer } from './agentRunOutcome.js';
15
+ import { joinV3WorkspacePath, normalizeV3WorkspaceRelativePath } from './v3-workspace-path.js';
16
+ import { buildExecutionHints, isPlannerBuildTask, resolvePlannerAgentTimeoutMs } from './requestIntent.js';
15
17
  export const VIGTHORIA_HUB_CREDITS_URL = 'https://hub.vigthoria.io/credits';
16
18
  export const VIGTHORIA_SERVER_TEMPORARILY_UNAVAILABLE_MESSAGE = 'Vigthoria Server is temporarily not available. Please try again later. If the issue persists, please contact support.';
17
19
  export class CLIError extends Error {
@@ -396,6 +398,7 @@ export class APIClient {
396
398
  this.ws = null;
397
399
  }
398
400
  }
401
+ /** Exposed for Balanced 4B agent routing (local inference :8016). */
399
402
  getSelfHostedModelsApiUrl() {
400
403
  const configuredUrl = process.env.VIGTHORIA_SELF_HOSTED_MODELS_API_URL
401
404
  || this.config.get('selfHostedModelsApiUrl');
@@ -1446,6 +1449,25 @@ export class APIClient {
1446
1449
  // files are provided inline in localWorkspaceSummary.workspaceFiles.
1447
1450
  const effectiveWorkspacePath = serverWorkspacePath || null;
1448
1451
  const needsHydration = !serverWorkspacePath && !!localWorkspacePath;
1452
+ const agentTaskType = resolvedContext.agentTaskType || 'general';
1453
+ const rawPrompt = resolvedContext.rawPrompt || resolvedContext.prompt || '';
1454
+ const executionHints = (resolvedContext.executionHints && typeof resolvedContext.executionHints === 'object')
1455
+ ? { ...resolvedContext.executionHints }
1456
+ : buildExecutionHints(agentTaskType, rawPrompt, {
1457
+ fastPath: resolvedContext.fastPath,
1458
+ });
1459
+ if (!executionHints.task_kind) {
1460
+ executionHints.task_kind = agentTaskType;
1461
+ }
1462
+ if (!executionHints.classify_from) {
1463
+ executionHints.classify_from = rawPrompt;
1464
+ }
1465
+ if (resolvedContext.workflowType === 'analysis_only' || resolvedContext.workflowType === 'full') {
1466
+ executionHints.workflow_type = resolvedContext.workflowType;
1467
+ if (resolvedContext.workflowType === 'analysis_only') {
1468
+ executionHints.requires_file_changes = false;
1469
+ }
1470
+ }
1449
1471
  const payload = {
1450
1472
  workspace: this.buildPublicWorkspaceDescriptor(resolvedContext.workspace, {
1451
1473
  localWorkspacePath,
@@ -1453,16 +1475,11 @@ export class APIClient {
1453
1475
  }),
1454
1476
  activeFile: resolvedContext.activeFile || null,
1455
1477
  history: resolvedContext.history || [],
1456
- agentTaskType: resolvedContext.agentTaskType || 'general',
1457
- rawPrompt: resolvedContext.rawPrompt || resolvedContext.prompt || '',
1478
+ agentTaskType,
1479
+ workflowType: executionHints.workflow_type,
1480
+ rawPrompt,
1458
1481
  contextualPrompt: resolvedContext.contextualPrompt || '',
1459
- executionHints: {
1460
- task_kind: resolvedContext.agentTaskType || 'general',
1461
- requires_file_changes: resolvedContext.agentTaskType === 'analysis' || resolvedContext.agentTaskType === 'verification'
1462
- ? false
1463
- : undefined,
1464
- classify_from: resolvedContext.rawPrompt || resolvedContext.prompt || '',
1465
- },
1482
+ executionHints,
1466
1483
  model: resolvedModel,
1467
1484
  requestedModel,
1468
1485
  requestedModelResolved: resolvedModel,
@@ -1523,6 +1540,9 @@ export class APIClient {
1523
1540
  let json = JSON.stringify(payload);
1524
1541
  if (json.length <= LIMIT)
1525
1542
  return json;
1543
+ if (!/^(1|true|yes)$/i.test(String(process.env.VIGTHORIA_QUIET_CONTEXT_COMPACTION || ''))) {
1544
+ process.stderr.write(`Workspace context is large - sent a focused summary (${Math.round(LIMIT / 1000)}k limit).\n`);
1545
+ }
1526
1546
  if (process.env.DEBUG || process.env.VIGTHORIA_DEBUG) {
1527
1547
  process.stderr.write(`[context] Payload ${json.length} chars exceeds ${LIMIT} limit, compacting...\n`);
1528
1548
  }
@@ -1651,6 +1671,25 @@ export class APIClient {
1651
1671
  const localWorkspaceRef = localWorkspaceName ? `vigthoria://local-workspace/${localWorkspaceName}` : null;
1652
1672
  const rawBrain = resolvedContext.vigthoriaBrain || resolvedContext.vigthoria_brain || null;
1653
1673
  const dedupedBrain = rawBrain ? this.dedupeBrainPayload(rawBrain) : null;
1674
+ const agentTaskType = resolvedContext.agentTaskType || 'general';
1675
+ const rawPrompt = resolvedContext.rawPrompt || resolvedContext.prompt || '';
1676
+ const executionHints = (resolvedContext.executionHints && typeof resolvedContext.executionHints === 'object')
1677
+ ? { ...resolvedContext.executionHints }
1678
+ : buildExecutionHints(agentTaskType, rawPrompt, {
1679
+ fastPath: resolvedContext.fastPath,
1680
+ });
1681
+ if (!executionHints.task_kind) {
1682
+ executionHints.task_kind = agentTaskType;
1683
+ }
1684
+ if (!executionHints.classify_from) {
1685
+ executionHints.classify_from = rawPrompt;
1686
+ }
1687
+ if (resolvedContext.workflowType === 'analysis_only' || resolvedContext.workflowType === 'full') {
1688
+ executionHints.workflow_type = resolvedContext.workflowType;
1689
+ if (resolvedContext.workflowType === 'analysis_only') {
1690
+ executionHints.requires_file_changes = false;
1691
+ }
1692
+ }
1654
1693
  return JSON.stringify({
1655
1694
  workspace: this.buildPublicWorkspaceDescriptor(resolvedContext.workspace, {
1656
1695
  localWorkspacePath: targetPath,
@@ -1658,16 +1697,11 @@ export class APIClient {
1658
1697
  }),
1659
1698
  activeFile: resolvedContext.activeFile || null,
1660
1699
  history: resolvedContext.history || [],
1661
- agentTaskType: resolvedContext.agentTaskType || 'general',
1662
- rawPrompt: resolvedContext.rawPrompt || resolvedContext.prompt || '',
1700
+ agentTaskType,
1701
+ workflowType: executionHints.workflow_type,
1702
+ rawPrompt,
1663
1703
  contextualPrompt: resolvedContext.contextualPrompt || '',
1664
- executionHints: {
1665
- task_kind: resolvedContext.agentTaskType || 'general',
1666
- requires_file_changes: resolvedContext.agentTaskType === 'analysis' || resolvedContext.agentTaskType === 'verification'
1667
- ? false
1668
- : undefined,
1669
- classify_from: resolvedContext.rawPrompt || resolvedContext.prompt || '',
1670
- },
1704
+ executionHints,
1671
1705
  executionSurface: resolvedContext.executionSurface || 'cli',
1672
1706
  clientSurface: resolvedContext.clientSurface || 'cli',
1673
1707
  localMachineCapable: resolvedContext.localMachineCapable !== false,
@@ -2660,7 +2694,7 @@ menu {
2660
2694
  if (!relativePath && String(rawPath || '.').trim() !== '.') {
2661
2695
  return null;
2662
2696
  }
2663
- const absolutePath = path.resolve(rootPath, relativePath || '.');
2697
+ const absolutePath = relativePath ? joinV3WorkspacePath(rootPath, relativePath) : path.resolve(rootPath);
2664
2698
  const resolvedRoot = path.resolve(rootPath);
2665
2699
  if (absolutePath !== resolvedRoot && !absolutePath.startsWith(resolvedRoot + path.sep)) {
2666
2700
  return null;
@@ -2864,7 +2898,7 @@ menu {
2864
2898
  if (!relativePath) {
2865
2899
  return false;
2866
2900
  }
2867
- const absolutePath = path.resolve(rootPath, relativePath);
2901
+ const absolutePath = joinV3WorkspacePath(rootPath, relativePath);
2868
2902
  const resolvedRoot = path.resolve(rootPath);
2869
2903
  if (absolutePath !== resolvedRoot && !absolutePath.startsWith(resolvedRoot + path.sep)) {
2870
2904
  this.logger.warn(`Refusing to write V3 file outside workspace: ${rawPath}`);
@@ -2891,7 +2925,7 @@ menu {
2891
2925
  if (!relativePath) {
2892
2926
  return false;
2893
2927
  }
2894
- const absolutePath = path.resolve(rootPath, relativePath);
2928
+ const absolutePath = joinV3WorkspacePath(rootPath, relativePath);
2895
2929
  const resolvedRoot = path.resolve(rootPath);
2896
2930
  if (absolutePath !== resolvedRoot && !absolutePath.startsWith(resolvedRoot + path.sep)) {
2897
2931
  this.logger.warn(`Refusing to delete V3 file outside workspace: ${rawPath}`);
@@ -2935,56 +2969,7 @@ menu {
2935
2969
  }
2936
2970
  }
2937
2971
  normalizeAgentWorkspaceRelativePath(rawPath, rootPath) {
2938
- const input = String(rawPath || '').trim().replace(/\\/g, '/').replace(/^\.\//, '');
2939
- if (!input) {
2940
- return '';
2941
- }
2942
- const safeRelative = (candidate) => {
2943
- const stripped = String(candidate || '').replace(/^\/+/, '');
2944
- if (!stripped || /^[a-zA-Z]:\//.test(stripped)) {
2945
- return '';
2946
- }
2947
- const normalized = path.posix.normalize(stripped);
2948
- if (!normalized || normalized === '.' || normalized === '..' || normalized.startsWith('../') || path.posix.isAbsolute(normalized)) {
2949
- return '';
2950
- }
2951
- return normalized;
2952
- };
2953
- const internalWorkspacePatterns = [
2954
- /^\/?var\/www\/\.vigthoria\/v3-temp\/vig-remote-[^/]+\/(.+)$/i,
2955
- /^\.vigthoria\/v3-temp\/vig-remote-[^/]+\/(.+)$/i,
2956
- /^\/?tmp\/vig-remote(?:-server)?-[^/]+\/(.+)$/i,
2957
- /^\/?tmp\/vig-fork-[^/]+\/(.+)$/i,
2958
- /^\/?home\/user\/(.+)$/i,
2959
- /^\/?root\/(.+)$/i,
2960
- ];
2961
- for (const pattern of internalWorkspacePatterns) {
2962
- const match = input.match(pattern);
2963
- if (match && match[1]) {
2964
- return safeRelative(match[1]);
2965
- }
2966
- }
2967
- const normalizedRoot = String(rootPath || '').trim().replace(/\\/g, '/').replace(/\/+$/g, '');
2968
- if (normalizedRoot) {
2969
- const rootNoLeadingSlash = normalizedRoot.replace(/^\//, '');
2970
- const rootBase = path.posix.basename(normalizedRoot);
2971
- const prefixes = [
2972
- `${normalizedRoot}/`,
2973
- `${rootNoLeadingSlash}/`,
2974
- `${rootBase}/`,
2975
- ];
2976
- for (const prefix of prefixes) {
2977
- if (input.startsWith(prefix)) {
2978
- return safeRelative(input.slice(prefix.length));
2979
- }
2980
- }
2981
- const embeddedRoot = `/${rootBase}/`;
2982
- const embeddedIndex = input.indexOf(embeddedRoot);
2983
- if (embeddedIndex >= 0) {
2984
- return safeRelative(input.slice(embeddedIndex + embeddedRoot.length));
2985
- }
2986
- }
2987
- return safeRelative(input);
2972
+ return normalizeV3WorkspaceRelativePath(rawPath, rootPath);
2988
2973
  }
2989
2974
  async ensureAgentFrontendPolish(message = '', context = {}) {
2990
2975
  const rootPath = this.resolveAgentTargetPath(context);
@@ -3542,7 +3527,15 @@ document.addEventListener('DOMContentLoaded', () => {
3542
3527
  this.activeV3StreamedFiles = streamedFiles;
3543
3528
  this.activeV3StreamContext = context;
3544
3529
  context.__v3StreamedFiles = streamedFiles;
3545
- const idleTimeoutMs = context.agentIdleTimeoutMs || DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS;
3530
+ const idleTimeoutMs = (() => {
3531
+ const base = context.agentIdleTimeoutMs || DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS;
3532
+ if (isPlannerBuildTask(String(context.agentTaskType || ''), String(context.rawPrompt || ''))) {
3533
+ const raw = process.env.VIGTHORIA_AGENT_PLANNER_IDLE_TIMEOUT_MS || '300000';
3534
+ const extended = Number.parseInt(raw, 10);
3535
+ return Math.max(base, Number.isFinite(extended) && extended > 0 ? extended : 300000);
3536
+ }
3537
+ return base;
3538
+ })();
3546
3539
  try {
3547
3540
  while (true) {
3548
3541
  let chunk;
@@ -3562,6 +3555,9 @@ document.addEventListener('DOMContentLoaded', () => {
3562
3555
  chunk = result;
3563
3556
  break;
3564
3557
  }
3558
+ if (this.pendingV3ClientToolTasks.size > 0) {
3559
+ continue;
3560
+ }
3565
3561
  if (this.hasAgentWorkspaceOutput(context)) {
3566
3562
  const stalledError = new Error('V3 agent stream stalled after writing workspace output');
3567
3563
  stalledError.name = 'AbortError';
@@ -3748,7 +3744,9 @@ document.addEventListener('DOMContentLoaded', () => {
3748
3744
  && context.localMachineCapable !== false;
3749
3745
  const rescueEligibleSaaS = preferLocalV3
3750
3746
  && /(saas|dashboard|analytics|billing|team management|activity feed|login screen)/i.test(message);
3751
- const timeoutMs = baseTimeoutMs > 0 && rescueEligibleSaaS ? Math.min(baseTimeoutMs, 210000) : baseTimeoutMs;
3747
+ const timeoutMs = rescueEligibleSaaS
3748
+ ? Math.min(resolvePlannerAgentTimeoutMs(baseTimeoutMs, String(executionContext.agentTaskType || ''), String(executionContext.rawPrompt || message || '')), 210000)
3749
+ : resolvePlannerAgentTimeoutMs(baseTimeoutMs, String(executionContext.agentTaskType || ''), String(executionContext.rawPrompt || message || ''));
3752
3750
  const maxAttempts = preferLocalV3 ? 2 : 1;
3753
3751
  let lastErrors = [];
3754
3752
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Vigthoria Balanced 4B — fast agent path classifier (~0.7–2s on local inference :8016).
3
+ * Regex/trivial detection always wins; LLM router refines ambiguous build prompts only.
4
+ */
5
+ export type AgentRoutePath = 'template-instant' | 'v3-agent' | 'analysis-only';
6
+ export interface BalancedRouterJson {
7
+ path?: string;
8
+ task_kind?: string;
9
+ taskKind?: string;
10
+ fast_path?: string | null;
11
+ fastPath?: string | null;
12
+ confidence?: number;
13
+ reason?: string;
14
+ }
15
+ export interface BalancedRouterResult {
16
+ ok: boolean;
17
+ raw?: string;
18
+ parsed?: BalancedRouterJson;
19
+ latencyMs?: number;
20
+ model?: string;
21
+ endpoint?: string;
22
+ error?: string;
23
+ }
24
+ export declare function parseBalancedRouterJson(raw: string): BalancedRouterJson | null;
25
+ /**
26
+ * Inference bases for Balanced 4B routing only.
27
+ * Local developer machines must opt in (env) — avoids ~1.5s localhost probe on every Windows turn.
28
+ */
29
+ export declare function getInferenceRouterBaseUrls(selfHostedUrl: string | null): string[];
30
+ export declare function isFastInferenceRouterAvailable(selfHostedUrl: string | null, timeoutMs?: number): Promise<boolean>;
31
+ export declare function routeWithBalanced4b(prompt: string, selfHostedUrl: string | null, options?: {
32
+ timeoutMs?: number;
33
+ }): Promise<BalancedRouterResult>;
34
+ export declare function shouldUseBalancedRouter(prompt: string, regexTaskKind?: string): boolean;
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Vigthoria Balanced 4B — fast agent path classifier (~0.7–2s on local inference :8016).
3
+ * Regex/trivial detection always wins; LLM router refines ambiguous build prompts only.
4
+ */
5
+ const ROUTER_SYSTEM = `You are Vigthoria Agent Router. Reply with ONE JSON object only. No markdown, no HTML, no code fences.
6
+
7
+ Paths:
8
+ - template-instant: single trivial HTML page (hello world, one popup, simple static site) — use Template Service
9
+ - v3-agent: multi-file builds, games, refactors, complex implementation
10
+ - analysis-only: read-only review/audit/explain, no workspace writes
11
+
12
+ task_kind: web-build | game-build | implementation | analysis | debugging
13
+ fast_path: "template-instant" or null
14
+
15
+ Example: {"path":"template-instant","task_kind":"web-build","fast_path":"template-instant","confidence":0.95,"reason":"trivial html popup page"}`;
16
+ const INFERENCE_MODEL_IDS = [
17
+ 'vigthoria-balanced-4b',
18
+ 'vigthoria-v3-balanced-4b',
19
+ ];
20
+ function normalizePath(value) {
21
+ const p = String(value || '').trim().toLowerCase();
22
+ if (p === 'template-instant' || p === 'template' || p === 'html-template') {
23
+ return 'template-instant';
24
+ }
25
+ if (p === 'analysis-only' || p === 'analysis') {
26
+ return 'analysis-only';
27
+ }
28
+ if (p === 'v3-agent' || p === 'v3' || p === 'agent') {
29
+ return 'v3-agent';
30
+ }
31
+ return null;
32
+ }
33
+ export function parseBalancedRouterJson(raw) {
34
+ const text = String(raw || '').trim();
35
+ if (!text)
36
+ return null;
37
+ const start = text.indexOf('{');
38
+ if (start < 0)
39
+ return null;
40
+ let depth = 0;
41
+ for (let i = start; i < text.length; i += 1) {
42
+ if (text[i] === '{')
43
+ depth += 1;
44
+ if (text[i] === '}') {
45
+ depth -= 1;
46
+ if (depth === 0) {
47
+ try {
48
+ return JSON.parse(text.slice(start, i + 1));
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ }
54
+ }
55
+ }
56
+ return null;
57
+ }
58
+ /**
59
+ * Inference bases for Balanced 4B routing only.
60
+ * Local developer machines must opt in (env) — avoids ~1.5s localhost probe on every Windows turn.
61
+ */
62
+ export function getInferenceRouterBaseUrls(selfHostedUrl) {
63
+ const urls = [
64
+ process.env.VIGTHORIA_INFERENCE_URL,
65
+ process.env.VIGTHORIA_SELF_HOSTED_MODELS_API_URL,
66
+ selfHostedUrl,
67
+ ].filter(Boolean).map((u) => String(u).replace(/\/$/, ''));
68
+ const allowLocalhost = process.env.VIGTHORIA_ENABLE_LOCAL_INFERENCE_ROUTER === '1'
69
+ || process.env.VIGTHORIA_AGENT_LLM_ROUTER === '1'
70
+ || process.platform === 'linux';
71
+ if (allowLocalhost) {
72
+ urls.push('http://127.0.0.1:8016');
73
+ }
74
+ return [...new Set(urls)];
75
+ }
76
+ let inferenceHealthCache = null;
77
+ export async function isFastInferenceRouterAvailable(selfHostedUrl, timeoutMs = 400) {
78
+ if (process.env.VIGTHORIA_AGENT_LLM_ROUTER === '0') {
79
+ return false;
80
+ }
81
+ const now = Date.now();
82
+ if (inferenceHealthCache && now - inferenceHealthCache.checkedAt < 60_000) {
83
+ return inferenceHealthCache.ok;
84
+ }
85
+ for (const base of getInferenceRouterBaseUrls(selfHostedUrl)) {
86
+ const controller = new AbortController();
87
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
88
+ try {
89
+ const response = await fetch(`${base}/health`, { signal: controller.signal });
90
+ clearTimeout(timer);
91
+ if (response.ok) {
92
+ inferenceHealthCache = { ok: true, checkedAt: now };
93
+ return true;
94
+ }
95
+ }
96
+ catch {
97
+ clearTimeout(timer);
98
+ }
99
+ }
100
+ inferenceHealthCache = { ok: false, checkedAt: now };
101
+ return false;
102
+ }
103
+ export async function routeWithBalanced4b(prompt, selfHostedUrl, options = {}) {
104
+ const timeoutMs = options.timeoutMs ?? Number.parseInt(process.env.VIGTHORIA_AGENT_ROUTER_TIMEOUT_MS || '8000', 10);
105
+ const bases = getInferenceRouterBaseUrls(selfHostedUrl);
106
+ let lastError = 'Inference router unreachable';
107
+ for (const base of bases) {
108
+ const controller = new AbortController();
109
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
110
+ const started = Date.now();
111
+ for (const model of INFERENCE_MODEL_IDS) {
112
+ try {
113
+ const response = await fetch(`${base}/v1/chat/completions`, {
114
+ method: 'POST',
115
+ headers: { 'Content-Type': 'application/json' },
116
+ body: JSON.stringify({
117
+ model,
118
+ messages: [
119
+ { role: 'system', content: ROUTER_SYSTEM },
120
+ { role: 'user', content: String(prompt || '').slice(0, 1200) },
121
+ ],
122
+ max_tokens: 96,
123
+ temperature: 0,
124
+ stream: false,
125
+ }),
126
+ signal: controller.signal,
127
+ });
128
+ clearTimeout(timer);
129
+ const payload = await response.json().catch(() => null);
130
+ if (!response.ok) {
131
+ lastError = `HTTP ${response.status} from ${base}`;
132
+ continue;
133
+ }
134
+ const raw = payload?.choices?.[0]?.message?.content || '';
135
+ const parsed = parseBalancedRouterJson(raw);
136
+ if (!parsed) {
137
+ lastError = 'Router returned non-JSON';
138
+ continue;
139
+ }
140
+ return {
141
+ ok: true,
142
+ raw,
143
+ parsed,
144
+ latencyMs: Date.now() - started,
145
+ model,
146
+ endpoint: base,
147
+ };
148
+ }
149
+ catch (err) {
150
+ lastError = err instanceof Error ? err.message : String(err);
151
+ }
152
+ }
153
+ clearTimeout(timer);
154
+ }
155
+ return { ok: false, error: lastError };
156
+ }
157
+ export function shouldUseBalancedRouter(prompt, regexTaskKind = '') {
158
+ if (process.env.VIGTHORIA_AGENT_LLM_ROUTER === '0') {
159
+ return false;
160
+ }
161
+ const kind = String(regexTaskKind || '').toLowerCase();
162
+ // Regex already resolved clear build families — LLM must not override (e.g. landing → implementation).
163
+ if (kind === 'web-build' || kind === 'game-build') {
164
+ return false;
165
+ }
166
+ const text = String(prompt || '').trim();
167
+ if (text.length > 700 || text.length < 12) {
168
+ return false;
169
+ }
170
+ return true;
171
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Shared prompt intent detection for CLI routing, V3 hints, and rescue gating.
3
+ * Keep aligned with V3-Code-Agent/agent.py _classify_request heuristics.
4
+ */
5
+ /** Strip CLI-appended platform / mode shaping so it cannot flip quality profiles. */
6
+ export declare function stripExecutionShaping(prompt: string): string;
7
+ export declare function hasBuildVerb(prompt: string): boolean;
8
+ export declare function hasArtifactNoun(prompt: string): boolean;
9
+ export declare function hasGameIntent(prompt: string): boolean;
10
+ export declare function isContinueOrRetryPrompt(prompt: string): boolean;
11
+ export declare function hasWebPageIntent(prompt: string): boolean;
12
+ export declare function isTrivialHtmlPageRequest(prompt: string): boolean;
13
+ export declare function hasCloneBuildIntent(prompt: string): boolean;
14
+ export declare function hasExplicitWriteIntent(prompt: string): boolean;
15
+ export declare function taskRequiresWorkspaceChanges(prompt: string): boolean;
16
+ export declare function hasAnalyzeAndBuildIntent(prompt: string): boolean;
17
+ export declare function inferAgentTaskType(prompt: string): string;
18
+ export declare function shouldSkipAnalysisRescue(liveOutcome: {
19
+ tasksTotal?: number;
20
+ changedFileCount?: number;
21
+ requiresWorkspaceChanges?: boolean;
22
+ }): boolean;
23
+ export declare function isPlannerBuildTask(agentTaskType: string, prompt: string): boolean;
24
+ export declare function resolvePlannerAgentTimeoutMs(baseTimeoutMs: number, agentTaskType: string, prompt: string): number;
25
+ /** V3 workflow mode: suppress write path when the user did not ask for file changes. */
26
+ export declare function resolveWorkflowType(agentTaskType: string, prompt: string): 'analysis_only' | 'full';
27
+ export declare function buildExecutionHints(agentTaskType: string, prompt: string, options?: {
28
+ fastPath?: string;
29
+ }): {
30
+ task_kind: string;
31
+ requires_file_changes: boolean | undefined;
32
+ classify_from: string;
33
+ workflow_type: 'analysis_only' | 'full';
34
+ fast_path?: string;
35
+ };
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Shared prompt intent detection for CLI routing, V3 hints, and rescue gating.
3
+ * Keep aligned with V3-Code-Agent/agent.py _classify_request heuristics.
4
+ */
5
+ const CLI_SHAPING_BLOCK = /(?:^|\n\n)Platform:\s*(?:Windows|macOS|Linux)\.[\s\S]*?(?=\n\n[A-Z][^\n]{0,40}:|\s*$)/i;
6
+ const READONLY_SHAPING_BLOCK = /(?:^|\n\n)(?:Read-only analysis mode is active\.|Diagnostic mode is active\.)[\s\S]*?(?=\n\n[A-Z][^\n]{0,40}:|\s*$)/i;
7
+ const BUILD_VERBS = /\b(build|create|make|implement|complete|fix|repair|edit|modify|write|generate|add|finish|scaffold|develop|update|change|refactor|let's|let us|we need|we should|erstelle|erstellen|schreib|schreibe|bearbeite)\b/i;
8
+ const ARTIFACT_NOUNS = /\b(file|files|project|game|spiel|app|website|html5|frontend|component|feature|code|workspace|repo|clone|replica|remake|page|site|canvas|sprite|level|levels|enemy|enemies|character|npc|world|scene|script|module|api|database|config|landing|dashboard|platformer|arcade|playable|collectible|scoreboard|leaderboard|pitfall|pacman|rogue|html|css|javascript|typescript)\b/i;
9
+ /** Canvas/playable games only — bare "html5" or "website" must NOT match. */
10
+ const GAME_INTENT = /\b(game|spiel|playable|html5\s+(?:game|canvas)|html5\s+game|canvas\s+game|game\s+canvas|arcade|platformer|side[- ]?scroller|pitfall|pac[- ]?man|tetris|snake|breakout|pong|roguelike|metroidvania|tower\s+defense|clone\s+of\s+(?:a\s+)?(?:game|pitfall|pac|mario|zelda|arcade)|gameplay|collectible|boss\s+fight|level\s+design|sprite\s+sheet|wild\s+(?:boar|pig|forest)|forest\s+pig)\b/i;
11
+ const WEB_PAGE_INTENT = /\b(website|web\s*site|webpage|web\s*page|landing\s+page|home\s*page|index\.html|html\s+page|static\s+page|single\s+page|popup|alert|modal|hello\s+world)\b/i;
12
+ const CLONE_BUILD = /\bclone\s+of\b|\b(build|create|make)\s+(?:a|an|the|us|me)?\s*(?:clone|replica|remake|port)\b/i;
13
+ const READ_ONLY_INTENT = /\b(analy[sz]e|analyse|analysis|audit|review|inspect|proof|understand|summari[sz]e|scan|read[\s-]?only|where we left|what is|how does|show me|tell me|identify\s+gaps?|gap\s+analysis|production\s+blockers?)\b/i;
14
+ const EXPLICIT_WRITE_VERBS = /\b(implement|write|edit|modify|update|fix|repair|create|build|generate|scaffold|deploy|refactor|develop|integrate)\b/i;
15
+ const TRIVIAL_HTML_PAGE = /\b(hello\s*world|simple\s+(?:html|web|page|site)|html5\s+website|html\s+5\s+website|popup|alert\s*\(|show\s+a\s+popup|one\s+page|single\s+html)\b/i;
16
+ /** Strip CLI-appended platform / mode shaping so it cannot flip quality profiles. */
17
+ export function stripExecutionShaping(prompt) {
18
+ return String(prompt || '')
19
+ .replace(CLI_SHAPING_BLOCK, '')
20
+ .replace(READONLY_SHAPING_BLOCK, '')
21
+ .trim();
22
+ }
23
+ export function hasBuildVerb(prompt) {
24
+ return BUILD_VERBS.test(stripExecutionShaping(prompt));
25
+ }
26
+ export function hasArtifactNoun(prompt) {
27
+ return ARTIFACT_NOUNS.test(stripExecutionShaping(prompt));
28
+ }
29
+ /** Game clones require a game target — generic "clone of X" must not imply game-build. */
30
+ const GAME_CLONE_TARGET = /\bclone\s+of\s+(?:a\s+)?(?:the\s+)?(?:game|pitfall|pac[- ]?man|mario|zelda|tetris|snake|arcade|spiel)\b/i;
31
+ export function hasGameIntent(prompt) {
32
+ const text = stripExecutionShaping(prompt);
33
+ if (GAME_INTENT.test(text)) {
34
+ return true;
35
+ }
36
+ if (GAME_CLONE_TARGET.test(text)) {
37
+ return true;
38
+ }
39
+ if (CLONE_BUILD.test(text) && /\b(game|spiel|pitfall|pacman|arcade|platformer|playable|canvas)\b/i.test(text)) {
40
+ return true;
41
+ }
42
+ return false;
43
+ }
44
+ export function isContinueOrRetryPrompt(prompt) {
45
+ const text = stripExecutionShaping(prompt).toLowerCase();
46
+ return /\b(continue\s+(?:the\s+)?(?:previous|last|current)\s+(?:agent\s+)?run|\/retry|\/continue|resume\s+(?:the\s+)?(?:failed|unfinished)|planner\s+produced\s+\d+\s+execution\s+tasks)\b/i.test(text);
47
+ }
48
+ export function hasWebPageIntent(prompt) {
49
+ const text = stripExecutionShaping(prompt);
50
+ return WEB_PAGE_INTENT.test(text) || /\bhtml5\s+website\b/i.test(text);
51
+ }
52
+ export function isTrivialHtmlPageRequest(prompt) {
53
+ const text = stripExecutionShaping(prompt);
54
+ if (isContinueOrRetryPrompt(prompt)) {
55
+ return false;
56
+ }
57
+ if (!hasBuildVerb(prompt) && !/\b(write|create|make|show)\b/i.test(text)) {
58
+ return false;
59
+ }
60
+ if (hasGameIntent(prompt)) {
61
+ return false;
62
+ }
63
+ if (text.length > 420) {
64
+ return false;
65
+ }
66
+ if (/\b(multi[- ]?page|full\s+saas|dashboard|e[\s-]?commerce|auth|database|backend|api|microservice)\b/i.test(text)) {
67
+ return false;
68
+ }
69
+ return TRIVIAL_HTML_PAGE.test(text)
70
+ || (hasWebPageIntent(prompt) && /\b(html5|html5\s+website|website|popup|hello\s*world)\b/i.test(text));
71
+ }
72
+ export function hasCloneBuildIntent(prompt) {
73
+ return CLONE_BUILD.test(stripExecutionShaping(prompt)) && hasBuildVerb(prompt);
74
+ }
75
+ export function hasExplicitWriteIntent(prompt) {
76
+ const text = stripExecutionShaping(prompt);
77
+ return (BUILD_VERBS.test(text) && ARTIFACT_NOUNS.test(text))
78
+ || hasCloneBuildIntent(prompt)
79
+ || (BUILD_VERBS.test(text) && hasGameIntent(prompt));
80
+ }
81
+ export function taskRequiresWorkspaceChanges(prompt) {
82
+ const text = String(prompt || '').trim();
83
+ const shaped = stripExecutionShaping(text);
84
+ const readOnlyIntent = READ_ONLY_INTENT.test(shaped);
85
+ const explicitWriteIntent = hasExplicitWriteIntent(text);
86
+ if (readOnlyIntent && !explicitWriteIntent) {
87
+ return false;
88
+ }
89
+ return explicitWriteIntent;
90
+ }
91
+ export function hasAnalyzeAndBuildIntent(prompt) {
92
+ const text = stripExecutionShaping(prompt);
93
+ return /\b(analy[sz]e|analyse)\b/i.test(text)
94
+ && /\b(complete|finish|fix|implement|build|repair|missing\s+elements?)\b/i.test(text)
95
+ && hasExplicitWriteIntent(prompt);
96
+ }
97
+ export function inferAgentTaskType(prompt) {
98
+ const text = stripExecutionShaping(prompt);
99
+ if (hasAnalyzeAndBuildIntent(prompt)) {
100
+ if (hasGameIntent(prompt)) {
101
+ return 'game-build';
102
+ }
103
+ if (hasWebPageIntent(prompt)) {
104
+ return 'web-build';
105
+ }
106
+ return 'implementation';
107
+ }
108
+ if (/\b(inspect|analyze|analyse|audit|review|find|diagnose|debug|trace|compare|diff|check|investigate)\b/i.test(text)
109
+ && !hasExplicitWriteIntent(prompt)) {
110
+ return 'debugging';
111
+ }
112
+ if (/\b(analy[sz]e|analyse|audit|review|inspect|identify\s+gaps?|gap\s+analysis|production\s+blockers?|read[\s-]?only)\b/i.test(text)
113
+ && !hasExplicitWriteIntent(prompt)) {
114
+ return 'analysis';
115
+ }
116
+ if (hasBuildVerb(prompt) && !/\b(analy[sz]e|analyse|audit|review|inspect)\b/i.test(text)) {
117
+ if (hasGameIntent(prompt)) {
118
+ return 'game-build';
119
+ }
120
+ if (hasWebPageIntent(prompt)) {
121
+ return 'web-build';
122
+ }
123
+ return 'implementation';
124
+ }
125
+ return 'analysis';
126
+ }
127
+ export function shouldSkipAnalysisRescue(liveOutcome) {
128
+ if (liveOutcome.requiresWorkspaceChanges) {
129
+ return true;
130
+ }
131
+ if ((liveOutcome.tasksTotal || 0) > 0) {
132
+ return true;
133
+ }
134
+ if ((liveOutcome.changedFileCount || 0) > 0) {
135
+ return true;
136
+ }
137
+ return false;
138
+ }
139
+ export function isPlannerBuildTask(agentTaskType, prompt) {
140
+ const kind = String(agentTaskType || '').toLowerCase();
141
+ if (isTrivialHtmlPageRequest(prompt)) {
142
+ return false;
143
+ }
144
+ if (/^(game-build|web-build|implementation|build|repair|refactor-build)$/.test(kind)) {
145
+ return true;
146
+ }
147
+ return hasExplicitWriteIntent(prompt) && !READ_ONLY_INTENT.test(stripExecutionShaping(prompt));
148
+ }
149
+ export function resolvePlannerAgentTimeoutMs(baseTimeoutMs, agentTaskType, prompt) {
150
+ if (!isPlannerBuildTask(agentTaskType, prompt)) {
151
+ return baseTimeoutMs;
152
+ }
153
+ const raw = process.env.VIGTHORIA_AGENT_PLANNER_TIMEOUT_MS || process.env.V3_AGENT_PLANNER_TIMEOUT_MS || '900000';
154
+ const extended = Number.parseInt(raw, 10);
155
+ const floor = Number.isFinite(extended) && extended > 0 ? extended : 900000;
156
+ return Math.max(baseTimeoutMs, floor);
157
+ }
158
+ /** V3 workflow mode: suppress write path when the user did not ask for file changes. */
159
+ export function resolveWorkflowType(agentTaskType, prompt) {
160
+ if (hasAnalyzeAndBuildIntent(prompt)) {
161
+ return 'full';
162
+ }
163
+ if (!taskRequiresWorkspaceChanges(prompt)) {
164
+ return 'analysis_only';
165
+ }
166
+ const kind = String(agentTaskType || '').toLowerCase();
167
+ if (kind === 'analysis' || kind === 'debugging' || kind === 'verification') {
168
+ return 'analysis_only';
169
+ }
170
+ return 'full';
171
+ }
172
+ export function buildExecutionHints(agentTaskType, prompt, options = {}) {
173
+ const workflowType = resolveWorkflowType(agentTaskType, prompt);
174
+ const kind = String(agentTaskType || 'general').toLowerCase();
175
+ const hints = {
176
+ task_kind: kind,
177
+ requires_file_changes: workflowType === 'analysis_only' ? false : undefined,
178
+ classify_from: String(prompt || ''),
179
+ workflow_type: workflowType,
180
+ };
181
+ if (options.fastPath) {
182
+ hints.fast_path = options.fastPath;
183
+ }
184
+ else if (isTrivialHtmlPageRequest(prompt)) {
185
+ hints.fast_path = 'template-instant';
186
+ }
187
+ return hints;
188
+ }