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/commands/chat.d.ts +10 -0
- package/dist/commands/chat.js +242 -20
- package/dist/utils/agentRoute.d.ts +17 -0
- package/dist/utils/agentRoute.js +137 -0
- package/dist/utils/api.d.ts +2 -1
- package/dist/utils/api.js +71 -73
- package/dist/utils/fastAgentRouter.d.ts +34 -0
- package/dist/utils/fastAgentRouter.js +171 -0
- package/dist/utils/requestIntent.d.ts +35 -0
- package/dist/utils/requestIntent.js +188 -0
- package/dist/utils/templateInstantPath.d.ts +18 -0
- package/dist/utils/templateInstantPath.js +125 -0
- package/dist/utils/v3-workspace-path.d.ts +7 -0
- package/dist/utils/v3-workspace-path.js +85 -0
- package/dist/utils/workspace-stream.js +9 -2
- package/package.json +2 -1
package/dist/commands/chat.d.ts
CHANGED
|
@@ -28,6 +28,7 @@ export declare class ChatCommand {
|
|
|
28
28
|
private tools;
|
|
29
29
|
private sessionManager;
|
|
30
30
|
private projectMemory;
|
|
31
|
+
private workspaceBrain;
|
|
31
32
|
private currentSession;
|
|
32
33
|
private agentMode;
|
|
33
34
|
private currentProjectPath;
|
|
@@ -51,6 +52,7 @@ export declare class ChatCommand {
|
|
|
51
52
|
private v3StreamedAnswerDisplayed;
|
|
52
53
|
private v3SeenToolCalls;
|
|
53
54
|
private v3SeenToolResults;
|
|
55
|
+
private lastAgentRoute;
|
|
54
56
|
private lastAgentRunOutcome;
|
|
55
57
|
private isJwtExpirationError;
|
|
56
58
|
private isNetworkError;
|
|
@@ -103,7 +105,11 @@ export declare class ChatCommand {
|
|
|
103
105
|
private buildTaskShapingInstructions;
|
|
104
106
|
private buildExecutionPrompt;
|
|
105
107
|
private isProjectBrainRuntimeDisabled;
|
|
108
|
+
private getWorkspaceBrainService;
|
|
109
|
+
private canPromptForWorkspaceIndex;
|
|
110
|
+
private promptWorkspaceIndex;
|
|
106
111
|
private buildProjectBrainRuntimeContext;
|
|
112
|
+
private buildWorkspaceBrainRuntimeContext;
|
|
107
113
|
private rememberBrainEvent;
|
|
108
114
|
private getPromptRuntimeContext;
|
|
109
115
|
private v3IterationCount;
|
|
@@ -168,6 +174,10 @@ export declare class ChatCommand {
|
|
|
168
174
|
private persistLocalLoopOutcome;
|
|
169
175
|
private runLocalAgentLoop;
|
|
170
176
|
private primeBypassedTargetFileContext;
|
|
177
|
+
/**
|
|
178
|
+
* Sub-second path for trivial HTML/hello-world: Template Service → index.html (no V3 planner).
|
|
179
|
+
*/
|
|
180
|
+
private tryTemplateInstantPath;
|
|
171
181
|
private tryDirectSingleFileFlow;
|
|
172
182
|
private isConfirmationFollowUp;
|
|
173
183
|
private taskRequiresWorkspaceChanges;
|
package/dist/commands/chat.js
CHANGED
|
@@ -11,11 +11,15 @@ import { BridgeClient, getBridgeClient } from '../utils/bridge-client.js';
|
|
|
11
11
|
import { WorkspaceWatcher } from '../utils/workspace-stream.js';
|
|
12
12
|
import { TaskDisplay } from '../utils/task-display.js';
|
|
13
13
|
import { ProjectMemoryService } from '../utils/project-memory.js';
|
|
14
|
+
import { WorkspaceBrainService } from '../utils/workspace-brain-service.js';
|
|
14
15
|
import { buildPersonaOverlay, normalizePersonaMode } from '../utils/persona.js';
|
|
15
16
|
import { runAgentSessionMenu, shouldShowAgentSessionMenu } from './agent-session-menu.js';
|
|
16
17
|
import { createLiveOutcome, evaluateExecutorSuccess, handleTaskEvent, isSubstantiveAgentAnswer, isToolEvidenceStubAnswer, normalizeAgentAnswerContent, noteAnalysisToolUse, } from '../utils/agentRunOutcome.js';
|
|
17
18
|
import { looksLikeMarkdownReport, renderMarkdownToTerminal, summarizeMarkdownReport, } from '../utils/terminalMarkdown.js';
|
|
18
19
|
import { emitDeckEvent, isDeckModeEnabled } from '../utils/deckEvents.js';
|
|
20
|
+
import { inferAgentTaskType as sharedInferAgentTaskType, isTrivialHtmlPageRequest, resolvePlannerAgentTimeoutMs, resolveWorkflowType, shouldSkipAnalysisRescue, taskRequiresWorkspaceChanges as promptRequiresWorkspaceChanges, buildExecutionHints, } from '../utils/requestIntent.js';
|
|
21
|
+
import { resolveAgentRoute } from '../utils/agentRoute.js';
|
|
22
|
+
import { runTemplateInstantPath } from '../utils/templateInstantPath.js';
|
|
19
23
|
const DEFAULT_V3_AGENT_TIMEOUT_MS = (() => {
|
|
20
24
|
const rawValue = process.env.VIGTHORIA_AGENT_TIMEOUT_MS || process.env.V3_AGENT_TIMEOUT_MS;
|
|
21
25
|
if (!rawValue) {
|
|
@@ -48,6 +52,7 @@ export class ChatCommand {
|
|
|
48
52
|
tools = null;
|
|
49
53
|
sessionManager;
|
|
50
54
|
projectMemory = null;
|
|
55
|
+
workspaceBrain = null;
|
|
51
56
|
currentSession = null;
|
|
52
57
|
agentMode = false;
|
|
53
58
|
currentProjectPath = process.cwd();
|
|
@@ -72,6 +77,7 @@ export class ChatCommand {
|
|
|
72
77
|
v3StreamedAnswerDisplayed = false;
|
|
73
78
|
v3SeenToolCalls = new Set();
|
|
74
79
|
v3SeenToolResults = new Set();
|
|
80
|
+
lastAgentRoute = null;
|
|
75
81
|
// Last completed Agent run — used by /retry, /continue, and the final summary block.
|
|
76
82
|
lastAgentRunOutcome = null;
|
|
77
83
|
isJwtExpirationError(error) {
|
|
@@ -305,6 +311,16 @@ export class ChatCommand {
|
|
|
305
311
|
routeReason: 'explicit-model-selection',
|
|
306
312
|
};
|
|
307
313
|
}
|
|
314
|
+
if (this.lastAgentRoute?.path === 'template-instant') {
|
|
315
|
+
return {
|
|
316
|
+
selectedModel: 'agent',
|
|
317
|
+
explicitModel: false,
|
|
318
|
+
heavyTask,
|
|
319
|
+
cloudEligible,
|
|
320
|
+
cloudSelected: false,
|
|
321
|
+
routeReason: `template-instant [${this.lastAgentRoute.source}]`,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
308
324
|
if (requiresV3Workflow) {
|
|
309
325
|
return {
|
|
310
326
|
selectedModel: 'agent',
|
|
@@ -315,6 +331,16 @@ export class ChatCommand {
|
|
|
315
331
|
routeReason: 'v3-required-frontend-task',
|
|
316
332
|
};
|
|
317
333
|
}
|
|
334
|
+
if (this.lastAgentRoute?.source === 'balanced-4b' || this.lastAgentRoute?.source === 'merged') {
|
|
335
|
+
return {
|
|
336
|
+
selectedModel: 'agent',
|
|
337
|
+
explicitModel: false,
|
|
338
|
+
heavyTask,
|
|
339
|
+
cloudEligible,
|
|
340
|
+
cloudSelected: false,
|
|
341
|
+
routeReason: `balanced-4b-router → ${this.lastAgentRoute.taskKind}`,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
318
344
|
if (this.config.shouldUseCloudForHeavyTask(prompt)) {
|
|
319
345
|
return {
|
|
320
346
|
selectedModel: 'cloud',
|
|
@@ -551,14 +577,7 @@ export class ChatCommand {
|
|
|
551
577
|
return 'debugging';
|
|
552
578
|
if (this.isAnalysisLookupPrompt(prompt))
|
|
553
579
|
return 'analysis';
|
|
554
|
-
|
|
555
|
-
return 'analysis';
|
|
556
|
-
}
|
|
557
|
-
if (/\b(implement|build|create|write|fix|repair|scaffold|deploy|add feature)\b/i.test(prompt)
|
|
558
|
-
&& !/\b(analy[sz]e|analyse|audit|review|inspect)\b/i.test(prompt)) {
|
|
559
|
-
return /\b(game|html5|pacman|rogue|playable)\b/i.test(prompt) ? 'game-build' : 'implementation';
|
|
560
|
-
}
|
|
561
|
-
return 'analysis';
|
|
580
|
+
return sharedInferAgentTaskType(prompt);
|
|
562
581
|
}
|
|
563
582
|
buildTaskShapingInstructions(prompt) {
|
|
564
583
|
const instructions = [];
|
|
@@ -594,6 +613,45 @@ export class ChatCommand {
|
|
|
594
613
|
isProjectBrainRuntimeDisabled() {
|
|
595
614
|
return /^(1|true|yes)$/i.test(String(process.env.VIGTHORIA_NO_BRAIN || process.env.VIGTHORIA_BRAIN_DISABLED || ''));
|
|
596
615
|
}
|
|
616
|
+
getWorkspaceBrainService() {
|
|
617
|
+
if (this.isProjectBrainRuntimeDisabled()) {
|
|
618
|
+
return null;
|
|
619
|
+
}
|
|
620
|
+
try {
|
|
621
|
+
if (!this.workspaceBrain) {
|
|
622
|
+
this.workspaceBrain = new WorkspaceBrainService({
|
|
623
|
+
workspacePath: this.currentProjectPath,
|
|
624
|
+
apiBase: this.config.get('apiUrl'),
|
|
625
|
+
getAuthToken: () => this.config.get('authToken'),
|
|
626
|
+
autoIndex: false,
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
return this.workspaceBrain;
|
|
630
|
+
}
|
|
631
|
+
catch {
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
canPromptForWorkspaceIndex() {
|
|
636
|
+
if (this.jsonOutput || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
return !/^(1|true|yes)$/i.test(String(process.env.VIGTHORIA_NO_INDEX_PROMPT || ''));
|
|
640
|
+
}
|
|
641
|
+
async promptWorkspaceIndex(fileCount, workspaceName) {
|
|
642
|
+
if (!this.canPromptForWorkspaceIndex()) {
|
|
643
|
+
return 'later';
|
|
644
|
+
}
|
|
645
|
+
const answer = await new Promise((resolve) => {
|
|
646
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
647
|
+
const prompt = `Index "${workspaceName}" for Vigthoria Brain (${fileCount} files) to improve large-context tasks? [y/N] `;
|
|
648
|
+
rl.question(chalk.cyan(prompt), (value) => {
|
|
649
|
+
rl.close();
|
|
650
|
+
resolve(value);
|
|
651
|
+
});
|
|
652
|
+
});
|
|
653
|
+
return /^(y|yes)$/i.test(answer.trim()) ? 'index_now' : 'later';
|
|
654
|
+
}
|
|
597
655
|
buildProjectBrainRuntimeContext(prompt, source) {
|
|
598
656
|
if (this.isProjectBrainRuntimeDisabled()) {
|
|
599
657
|
return undefined;
|
|
@@ -620,6 +678,42 @@ export class ChatCommand {
|
|
|
620
678
|
return undefined;
|
|
621
679
|
}
|
|
622
680
|
}
|
|
681
|
+
async buildWorkspaceBrainRuntimeContext(prompt) {
|
|
682
|
+
const service = this.getWorkspaceBrainService();
|
|
683
|
+
if (!service) {
|
|
684
|
+
return {};
|
|
685
|
+
}
|
|
686
|
+
try {
|
|
687
|
+
const result = await service.ensureIndexed({
|
|
688
|
+
promptIfMissing: this.canPromptForWorkspaceIndex(),
|
|
689
|
+
askToIndex: (fileCount, workspaceName) => this.promptWorkspaceIndex(fileCount, workspaceName),
|
|
690
|
+
});
|
|
691
|
+
const status = service.getStatus();
|
|
692
|
+
const context = {
|
|
693
|
+
workspaceBrain: {
|
|
694
|
+
schema: 'vigthoria.workspace-brain.v1',
|
|
695
|
+
indexed: result.indexed,
|
|
696
|
+
prompted: result.prompted || null,
|
|
697
|
+
indexedFileCount: status.indexedFileCount || result.fileCount,
|
|
698
|
+
totalChunks: status.totalChunks || result.chunkCount,
|
|
699
|
+
indexedAt: status.meta?.indexedAt || null,
|
|
700
|
+
indexHash: status.meta?.indexHash || null,
|
|
701
|
+
},
|
|
702
|
+
};
|
|
703
|
+
const codebaseContext = service.buildCodebaseContext(prompt);
|
|
704
|
+
if (codebaseContext) {
|
|
705
|
+
context.codebaseContext = codebaseContext;
|
|
706
|
+
}
|
|
707
|
+
const accountBrainContext = await service.fetchAccountBrainContext().catch(() => '');
|
|
708
|
+
if (accountBrainContext) {
|
|
709
|
+
context.accountBrainContext = accountBrainContext;
|
|
710
|
+
}
|
|
711
|
+
return context;
|
|
712
|
+
}
|
|
713
|
+
catch {
|
|
714
|
+
return {};
|
|
715
|
+
}
|
|
716
|
+
}
|
|
623
717
|
rememberBrainEvent(type, text, mode) {
|
|
624
718
|
if (this.isProjectBrainRuntimeDisabled()) {
|
|
625
719
|
return;
|
|
@@ -641,6 +735,19 @@ export class ChatCommand {
|
|
|
641
735
|
const brainContext = this.buildProjectBrainRuntimeContext(prompt, this.operatorMode ? 'vigthoria-cli.operator' : this.agentMode ? 'vigthoria-cli.agent' : 'vigthoria-cli.chat');
|
|
642
736
|
if (brainContext) {
|
|
643
737
|
runtimeContext.vigthoriaBrain = brainContext;
|
|
738
|
+
if (typeof brainContext.context === 'string' && brainContext.context.trim()) {
|
|
739
|
+
runtimeContext.projectMemory = brainContext.context;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
const workspaceBrainContext = await this.buildWorkspaceBrainRuntimeContext(prompt);
|
|
743
|
+
if (workspaceBrainContext.workspaceBrain) {
|
|
744
|
+
runtimeContext.workspaceBrain = workspaceBrainContext.workspaceBrain;
|
|
745
|
+
}
|
|
746
|
+
if (typeof workspaceBrainContext.codebaseContext === 'string' && workspaceBrainContext.codebaseContext.trim()) {
|
|
747
|
+
runtimeContext.codebaseContext = workspaceBrainContext.codebaseContext;
|
|
748
|
+
}
|
|
749
|
+
if (typeof workspaceBrainContext.accountBrainContext === 'string' && workspaceBrainContext.accountBrainContext.trim()) {
|
|
750
|
+
runtimeContext.accountBrainContext = workspaceBrainContext.accountBrainContext;
|
|
644
751
|
}
|
|
645
752
|
if (!this.isBrowserTaskPrompt(prompt)) {
|
|
646
753
|
return runtimeContext;
|
|
@@ -2057,7 +2164,20 @@ export class ChatCommand {
|
|
|
2057
2164
|
if (!this.tools) {
|
|
2058
2165
|
throw new Error('Agent tools are not initialized.');
|
|
2059
2166
|
}
|
|
2167
|
+
this.lastAgentRoute = await resolveAgentRoute(this.api, prompt);
|
|
2168
|
+
if (!this.jsonOutput) {
|
|
2169
|
+
const r = this.lastAgentRoute;
|
|
2170
|
+
const routerNote = r.routerLatencyMs
|
|
2171
|
+
? chalk.gray(` (${r.routerModel || 'balanced-4b'} ${r.routerLatencyMs}ms)`)
|
|
2172
|
+
: '';
|
|
2173
|
+
this.logger.debug(`Agent route: ${r.path} / ${r.taskKind} [${r.source}]${routerNote} — ${r.reason}`);
|
|
2174
|
+
}
|
|
2060
2175
|
const requiresV3Workflow = this.shouldRequireV3AgentWorkflow(prompt);
|
|
2176
|
+
const handledByTemplateInstant = await this.tryTemplateInstantPath(prompt);
|
|
2177
|
+
if (handledByTemplateInstant) {
|
|
2178
|
+
this.saveSession();
|
|
2179
|
+
return;
|
|
2180
|
+
}
|
|
2061
2181
|
const handledByDirectFileFlow = await this.tryDirectSingleFileFlow(prompt);
|
|
2062
2182
|
if (handledByDirectFileFlow) {
|
|
2063
2183
|
this.saveSession();
|
|
@@ -2397,6 +2517,99 @@ export class ChatCommand {
|
|
|
2397
2517
|
this.messages.push({ role: 'system', content: readSummary });
|
|
2398
2518
|
}
|
|
2399
2519
|
}
|
|
2520
|
+
/**
|
|
2521
|
+
* Sub-second path for trivial HTML/hello-world: Template Service → index.html (no V3 planner).
|
|
2522
|
+
*/
|
|
2523
|
+
async tryTemplateInstantPath(prompt) {
|
|
2524
|
+
const route = this.lastAgentRoute;
|
|
2525
|
+
if (route?.path !== 'template-instant' && !isTrivialHtmlPageRequest(prompt)) {
|
|
2526
|
+
return false;
|
|
2527
|
+
}
|
|
2528
|
+
let workspacePath = this.currentProjectPath;
|
|
2529
|
+
try {
|
|
2530
|
+
const fromPrompt = this.resolvePromptWorkspacePath(prompt, this.currentProjectPath);
|
|
2531
|
+
if (fromPrompt)
|
|
2532
|
+
workspacePath = fromPrompt;
|
|
2533
|
+
}
|
|
2534
|
+
catch {
|
|
2535
|
+
// use default workspace
|
|
2536
|
+
}
|
|
2537
|
+
if (!this.jsonOutput) {
|
|
2538
|
+
console.log();
|
|
2539
|
+
console.log(chalk.gray('━━━ ROUTING DECISION ━━━'));
|
|
2540
|
+
const r = this.lastAgentRoute;
|
|
2541
|
+
console.log(chalk.gray(`Reason: ${r?.reason || 'template-instant (trivial HTML page)'}`));
|
|
2542
|
+
console.log(chalk.gray(`Path: template-instant [${r?.source || 'regex'}]`));
|
|
2543
|
+
console.log(chalk.gray('Model: template-service'));
|
|
2544
|
+
console.log(chalk.gray('Cloud Eligible: false'));
|
|
2545
|
+
console.log(chalk.gray('Cloud Selected: false'));
|
|
2546
|
+
console.log(chalk.gray('━'.repeat(30)));
|
|
2547
|
+
console.log();
|
|
2548
|
+
console.log(chalk.cyan('⚡ Template instant path — matching template and writing index.html...'));
|
|
2549
|
+
}
|
|
2550
|
+
this.messages.push({ role: 'user', content: prompt });
|
|
2551
|
+
const mustComplete = route?.path === 'template-instant';
|
|
2552
|
+
let result = await runTemplateInstantPath(this.api, prompt, workspacePath, {
|
|
2553
|
+
allowFallback: true,
|
|
2554
|
+
forceMinimalFallback: route?.path === 'template-instant',
|
|
2555
|
+
});
|
|
2556
|
+
if (!result.ok && mustComplete) {
|
|
2557
|
+
result = await runTemplateInstantPath(this.api, prompt, workspacePath, {
|
|
2558
|
+
allowFallback: true,
|
|
2559
|
+
forceMinimalFallback: true,
|
|
2560
|
+
});
|
|
2561
|
+
}
|
|
2562
|
+
if (!result.ok) {
|
|
2563
|
+
if (!this.jsonOutput) {
|
|
2564
|
+
console.log(chalk.yellow(`Template instant path skipped: ${result.error || 'no match'}`));
|
|
2565
|
+
}
|
|
2566
|
+
return false;
|
|
2567
|
+
}
|
|
2568
|
+
const summary = [
|
|
2569
|
+
`Created ${result.entryPath || 'index.html'} via template-instant`,
|
|
2570
|
+
result.templateName ? `(${result.templateName})` : '',
|
|
2571
|
+
result.processingMs ? `in ${Math.round(result.processingMs)}ms` : '',
|
|
2572
|
+
result.usedFallback ? '(minimal fallback)' : '',
|
|
2573
|
+
].filter(Boolean).join(' ');
|
|
2574
|
+
this.messages.push({ role: 'assistant', content: summary });
|
|
2575
|
+
const liveOutcome = createLiveOutcome();
|
|
2576
|
+
liveOutcome.requiresWorkspaceChanges = true;
|
|
2577
|
+
liveOutcome.workspaceHasOutput = true;
|
|
2578
|
+
liveOutcome.changedFileCount = 1;
|
|
2579
|
+
liveOutcome.tasksTotal = 1;
|
|
2580
|
+
liveOutcome.tasksSucceeded = 1;
|
|
2581
|
+
liveOutcome.answerContent = summary;
|
|
2582
|
+
const evaluation = evaluateExecutorSuccess(liveOutcome);
|
|
2583
|
+
this.lastAgentRunOutcome = {
|
|
2584
|
+
prompt,
|
|
2585
|
+
taskId: null,
|
|
2586
|
+
contextId: null,
|
|
2587
|
+
tasksSucceeded: 1,
|
|
2588
|
+
tasksTotal: 1,
|
|
2589
|
+
failedTaskIds: [],
|
|
2590
|
+
unfinishedTaskIds: [],
|
|
2591
|
+
qualityScore: null,
|
|
2592
|
+
qualityMissing: [],
|
|
2593
|
+
qualityBlockers: [],
|
|
2594
|
+
hasOutput: true,
|
|
2595
|
+
answerContent: summary,
|
|
2596
|
+
selfHealStatus: 'skipped',
|
|
2597
|
+
selfHealTool: null,
|
|
2598
|
+
plannerError: null,
|
|
2599
|
+
executorError: null,
|
|
2600
|
+
clientToolErrors: [],
|
|
2601
|
+
transportErrors: [],
|
|
2602
|
+
workspacePath: workspacePath || null,
|
|
2603
|
+
workspaceSyncIssue: null,
|
|
2604
|
+
finishedAt: Date.now(),
|
|
2605
|
+
};
|
|
2606
|
+
if (!this.jsonOutput) {
|
|
2607
|
+
console.log(chalk.green(`✓ ${summary}`));
|
|
2608
|
+
console.log(chalk.gray(' Run: vigthoria preview'));
|
|
2609
|
+
this.printAgentRunSummary(this.lastAgentRunOutcome, evaluation, 1);
|
|
2610
|
+
}
|
|
2611
|
+
return true;
|
|
2612
|
+
}
|
|
2400
2613
|
async tryDirectSingleFileFlow(prompt) {
|
|
2401
2614
|
if (!this.directPromptMode || !this.tools) {
|
|
2402
2615
|
return false;
|
|
@@ -2516,13 +2729,7 @@ export class ChatCommand {
|
|
|
2516
2729
|
return /^(ja|ja bitte|ja bitte mach das|mach das|bitte mach das|genau|ok|okay|yes|yes please|please do|do it|go ahead|continue|proceed|make it so)$/.test(normalized);
|
|
2517
2730
|
}
|
|
2518
2731
|
taskRequiresWorkspaceChanges(prompt) {
|
|
2519
|
-
|
|
2520
|
-
const readOnlyIntent = /\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.test(text);
|
|
2521
|
-
const explicitWriteIntent = /\b(build|create|make|implement|complete|fix|repair|edit|modify|write|generate|add|finish|scaffold|develop|update|change|refactor)\b/i.test(text);
|
|
2522
|
-
if (readOnlyIntent && !explicitWriteIntent) {
|
|
2523
|
-
return false;
|
|
2524
|
-
}
|
|
2525
|
-
return explicitWriteIntent && /\b(file|project|game|app|website|html5|frontend|component|feature|code|workspace|repo)\b/i.test(text);
|
|
2732
|
+
return promptRequiresWorkspaceChanges(prompt);
|
|
2526
2733
|
}
|
|
2527
2734
|
getPreviousActionablePrompt() {
|
|
2528
2735
|
if (this.lastActionableUserInput && !this.isConfirmationFollowUp(this.lastActionableUserInput)) {
|
|
@@ -2663,7 +2870,11 @@ export class ChatCommand {
|
|
|
2663
2870
|
}
|
|
2664
2871
|
};
|
|
2665
2872
|
const executionPrompt = this.buildExecutionPrompt(contextualPrompt);
|
|
2666
|
-
const agentTaskType = this.inferAgentTaskType(prompt);
|
|
2873
|
+
const agentTaskType = this.lastAgentRoute?.taskKind || this.inferAgentTaskType(prompt);
|
|
2874
|
+
const workflowType = resolveWorkflowType(agentTaskType, prompt);
|
|
2875
|
+
const executionHints = buildExecutionHints(agentTaskType, prompt, {
|
|
2876
|
+
fastPath: this.lastAgentRoute?.fastPath,
|
|
2877
|
+
});
|
|
2667
2878
|
const workspaceContext = {
|
|
2668
2879
|
workspacePath: workspacePath,
|
|
2669
2880
|
projectPath: workspacePath,
|
|
@@ -2699,10 +2910,14 @@ export class ChatCommand {
|
|
|
2699
2910
|
workspace: { path: workspacePath },
|
|
2700
2911
|
...workspaceContext,
|
|
2701
2912
|
agentTaskType,
|
|
2913
|
+
workflowType,
|
|
2914
|
+
executionHints,
|
|
2915
|
+
fastPath: this.lastAgentRoute?.fastPath,
|
|
2916
|
+
agentRoute: this.lastAgentRoute,
|
|
2702
2917
|
executionSurface: 'cli',
|
|
2703
2918
|
clientSurface: 'cli',
|
|
2704
2919
|
localMachineCapable: true,
|
|
2705
|
-
agentTimeoutMs: DEFAULT_V3_AGENT_TIMEOUT_MS,
|
|
2920
|
+
agentTimeoutMs: resolvePlannerAgentTimeoutMs(DEFAULT_V3_AGENT_TIMEOUT_MS, agentTaskType, prompt),
|
|
2706
2921
|
agentIdleTimeoutMs: DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS,
|
|
2707
2922
|
agentSoftTimeoutMs: DEFAULT_V3_AGENT_SOFT_TIMEOUT_MS,
|
|
2708
2923
|
model: routingPolicy.selectedModel,
|
|
@@ -2795,7 +3010,7 @@ export class ChatCommand {
|
|
|
2795
3010
|
const previewGate = (response.metadata?.previewGate || null);
|
|
2796
3011
|
const workspaceHasOutput = this.api.hasAgentWorkspaceOutput(workspaceContext);
|
|
2797
3012
|
const changedFileCount = response.changedFiles ? Object.keys(response.changedFiles).length : 0;
|
|
2798
|
-
const requiresWorkspaceChanges =
|
|
3013
|
+
const requiresWorkspaceChanges = promptRequiresWorkspaceChanges(prompt);
|
|
2799
3014
|
const answerContent = normalizeAgentAnswerContent(response.content, this.v3StreamedAnswerBuffer);
|
|
2800
3015
|
liveOutcome.changedFileCount = changedFileCount;
|
|
2801
3016
|
liveOutcome.requiresWorkspaceChanges = requiresWorkspaceChanges;
|
|
@@ -2874,7 +3089,11 @@ export class ChatCommand {
|
|
|
2874
3089
|
if (isToolEvidenceStubAnswer(finalAnswer)) {
|
|
2875
3090
|
finalAnswer = '';
|
|
2876
3091
|
}
|
|
2877
|
-
const needsAnalysisRescue = !
|
|
3092
|
+
const needsAnalysisRescue = !shouldSkipAnalysisRescue({
|
|
3093
|
+
tasksTotal: liveOutcome.tasksTotal,
|
|
3094
|
+
changedFileCount,
|
|
3095
|
+
requiresWorkspaceChanges,
|
|
3096
|
+
}) && !this.jsonOutput
|
|
2878
3097
|
&& (!executorSucceeded || !isSubstantiveAgentAnswer(finalAnswer));
|
|
2879
3098
|
if (needsAnalysisRescue) {
|
|
2880
3099
|
const rescued = await this.tryAnalysisLocalRescue(prompt, workspacePath);
|
|
@@ -3146,13 +3365,16 @@ export class ChatCommand {
|
|
|
3146
3365
|
this.v3LastActivity = Date.now();
|
|
3147
3366
|
this.v3StreamingStarted = false;
|
|
3148
3367
|
try {
|
|
3368
|
+
const retryTaskType = this.inferAgentTaskType(rawPrompt);
|
|
3149
3369
|
const retryResponse = await this.api.runV3AgentWorkflow(executionPrompt, {
|
|
3150
3370
|
workspace: { path: this.currentProjectPath },
|
|
3151
3371
|
...workspaceContext,
|
|
3152
3372
|
executionSurface: 'cli',
|
|
3153
3373
|
clientSurface: 'cli',
|
|
3154
3374
|
localMachineCapable: true,
|
|
3155
|
-
|
|
3375
|
+
agentTaskType: retryTaskType,
|
|
3376
|
+
workflowType: resolveWorkflowType(retryTaskType, rawPrompt),
|
|
3377
|
+
agentTimeoutMs: resolvePlannerAgentTimeoutMs(DEFAULT_V3_AGENT_TIMEOUT_MS, retryTaskType, rawPrompt),
|
|
3156
3378
|
agentIdleTimeoutMs: DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS,
|
|
3157
3379
|
agentSoftTimeoutMs: DEFAULT_V3_AGENT_SOFT_TIMEOUT_MS,
|
|
3158
3380
|
model: routingPolicy.selectedModel,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified agent routing: regex (instant) → optional Balanced 4B → V3 execution hints.
|
|
3
|
+
*/
|
|
4
|
+
import type { APIClient } from './api.js';
|
|
5
|
+
import { type AgentRoutePath } from './fastAgentRouter.js';
|
|
6
|
+
export type { AgentRoutePath };
|
|
7
|
+
export interface AgentRouteDecision {
|
|
8
|
+
path: AgentRoutePath;
|
|
9
|
+
taskKind: string;
|
|
10
|
+
fastPath?: string;
|
|
11
|
+
confidence: number;
|
|
12
|
+
reason: string;
|
|
13
|
+
source: 'regex-trivial' | 'regex' | 'balanced-4b' | 'merged';
|
|
14
|
+
routerLatencyMs?: number;
|
|
15
|
+
routerModel?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function resolveAgentRoute(api: APIClient, prompt: string): Promise<AgentRouteDecision>;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified agent routing: regex (instant) → optional Balanced 4B → V3 execution hints.
|
|
3
|
+
*/
|
|
4
|
+
import { inferAgentTaskType, isTrivialHtmlPageRequest, isContinueOrRetryPrompt, hasGameIntent, hasWebPageIntent, taskRequiresWorkspaceChanges, stripExecutionShaping, } from './requestIntent.js';
|
|
5
|
+
import { isFastInferenceRouterAvailable, routeWithBalanced4b, shouldUseBalancedRouter, } from './fastAgentRouter.js';
|
|
6
|
+
function normalizeTaskKind(value, fallback) {
|
|
7
|
+
const kind = String(value || fallback).trim().toLowerCase();
|
|
8
|
+
const allowed = new Set([
|
|
9
|
+
'web-build', 'game-build', 'implementation', 'analysis', 'debugging', 'verification', 'build', 'repair',
|
|
10
|
+
]);
|
|
11
|
+
if (allowed.has(kind)) {
|
|
12
|
+
return kind === 'build' ? 'implementation' : kind;
|
|
13
|
+
}
|
|
14
|
+
return fallback;
|
|
15
|
+
}
|
|
16
|
+
function mergeLlmRoute(regexTask, prompt, parsed) {
|
|
17
|
+
const llmPath = String(parsed.path || '').toLowerCase();
|
|
18
|
+
const llmTask = normalizeTaskKind(parsed.task_kind || parsed.taskKind, regexTask);
|
|
19
|
+
const llmFast = parsed.fast_path ?? parsed.fastPath ?? undefined;
|
|
20
|
+
const confidence = Number(parsed.confidence) || 0.75;
|
|
21
|
+
const reason = String(parsed.reason || 'balanced-4b router');
|
|
22
|
+
// Regex game/web signals override wrong LLM labels (hello-world → v3-agent bug).
|
|
23
|
+
if (isTrivialHtmlPageRequest(prompt) || (hasWebPageIntent(prompt) && !hasGameIntent(prompt) && llmTask === 'game-build')) {
|
|
24
|
+
return {
|
|
25
|
+
path: 'template-instant',
|
|
26
|
+
taskKind: 'web-build',
|
|
27
|
+
fastPath: 'template-instant',
|
|
28
|
+
confidence: Math.max(confidence, 0.92),
|
|
29
|
+
reason: 'regex web-page override',
|
|
30
|
+
source: 'merged',
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (llmPath === 'template-instant' || llmPath === 'html-template' || llmFast === 'template-instant') {
|
|
34
|
+
if (hasWebPageIntent(prompt) || isTrivialHtmlPageRequest(prompt)) {
|
|
35
|
+
return {
|
|
36
|
+
path: 'template-instant',
|
|
37
|
+
taskKind: 'web-build',
|
|
38
|
+
fastPath: 'template-instant',
|
|
39
|
+
confidence,
|
|
40
|
+
reason,
|
|
41
|
+
source: 'merged',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (llmPath === 'analysis-only' || llmTask === 'analysis' || llmTask === 'debugging') {
|
|
46
|
+
if (!taskRequiresWorkspaceChanges(prompt)) {
|
|
47
|
+
return {
|
|
48
|
+
path: 'analysis-only',
|
|
49
|
+
taskKind: llmTask === 'debugging' ? 'debugging' : 'analysis',
|
|
50
|
+
confidence,
|
|
51
|
+
reason,
|
|
52
|
+
source: 'balanced-4b',
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (hasGameIntent(prompt) || llmTask === 'game-build') {
|
|
57
|
+
return {
|
|
58
|
+
path: 'v3-agent',
|
|
59
|
+
taskKind: 'game-build',
|
|
60
|
+
confidence,
|
|
61
|
+
reason,
|
|
62
|
+
source: 'merged',
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const taskKind = (regexTask === 'web-build' || regexTask === 'game-build') ? regexTask : (llmTask || regexTask);
|
|
66
|
+
return {
|
|
67
|
+
path: 'v3-agent',
|
|
68
|
+
taskKind,
|
|
69
|
+
fastPath: typeof llmFast === 'string' ? llmFast : undefined,
|
|
70
|
+
confidence,
|
|
71
|
+
reason,
|
|
72
|
+
source: 'balanced-4b',
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export async function resolveAgentRoute(api, prompt) {
|
|
76
|
+
const stripped = stripExecutionShaping(prompt);
|
|
77
|
+
if (isContinueOrRetryPrompt(stripped)) {
|
|
78
|
+
const regexTask = inferAgentTaskType(stripped);
|
|
79
|
+
return {
|
|
80
|
+
path: 'v3-agent',
|
|
81
|
+
taskKind: regexTask === 'analysis' ? 'implementation' : regexTask,
|
|
82
|
+
confidence: 0.9,
|
|
83
|
+
reason: 'continue/retry follow-up (regex)',
|
|
84
|
+
source: 'regex',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (isTrivialHtmlPageRequest(stripped)) {
|
|
88
|
+
return {
|
|
89
|
+
path: 'template-instant',
|
|
90
|
+
taskKind: 'web-build',
|
|
91
|
+
fastPath: 'template-instant',
|
|
92
|
+
confidence: 1,
|
|
93
|
+
reason: 'trivial html page (regex)',
|
|
94
|
+
source: 'regex-trivial',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const regexTask = inferAgentTaskType(stripped);
|
|
98
|
+
if (!taskRequiresWorkspaceChanges(stripped) && (regexTask === 'analysis' || regexTask === 'debugging')) {
|
|
99
|
+
return {
|
|
100
|
+
path: 'analysis-only',
|
|
101
|
+
taskKind: regexTask,
|
|
102
|
+
confidence: 0.95,
|
|
103
|
+
reason: 'read-only analysis (regex)',
|
|
104
|
+
source: 'regex',
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
if (hasGameIntent(stripped) && regexTask === 'game-build') {
|
|
108
|
+
return {
|
|
109
|
+
path: 'v3-agent',
|
|
110
|
+
taskKind: 'game-build',
|
|
111
|
+
confidence: 0.95,
|
|
112
|
+
reason: 'game build (regex)',
|
|
113
|
+
source: 'regex',
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const selfHosted = api.getSelfHostedModelsApiUrl();
|
|
117
|
+
const tryLlm = shouldUseBalancedRouter(stripped, regexTask)
|
|
118
|
+
&& await isFastInferenceRouterAvailable(selfHosted);
|
|
119
|
+
if (tryLlm) {
|
|
120
|
+
const llm = await routeWithBalanced4b(stripped, selfHosted);
|
|
121
|
+
if (llm.ok && llm.parsed) {
|
|
122
|
+
const merged = mergeLlmRoute(regexTask, stripped, llm.parsed);
|
|
123
|
+
return {
|
|
124
|
+
...merged,
|
|
125
|
+
routerLatencyMs: llm.latencyMs,
|
|
126
|
+
routerModel: llm.model,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
path: 'v3-agent',
|
|
132
|
+
taskKind: regexTask,
|
|
133
|
+
confidence: 0.8,
|
|
134
|
+
reason: 'default v3 agent (regex)',
|
|
135
|
+
source: 'regex',
|
|
136
|
+
};
|
|
137
|
+
}
|
package/dist/utils/api.d.ts
CHANGED
|
@@ -237,7 +237,8 @@ export declare class APIClient {
|
|
|
237
237
|
* on Windows / Node 25+.
|
|
238
238
|
*/
|
|
239
239
|
destroy(): void;
|
|
240
|
-
|
|
240
|
+
/** Exposed for Balanced 4B agent routing (local inference :8016). */
|
|
241
|
+
getSelfHostedModelsApiUrl(): string | null;
|
|
241
242
|
login(email: string, password: string): Promise<boolean>;
|
|
242
243
|
loginWithToken(token: string): Promise<boolean>;
|
|
243
244
|
private extractUserProfile;
|