vigthoria-cli 1.11.12 → 1.11.17
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 +241 -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 +34 -0
- package/dist/utils/requestIntent.js +170 -0
- package/dist/utils/templateInstantPath.d.ts +18 -0
- package/dist/utils/templateInstantPath.js +121 -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,98 @@ 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
|
+
});
|
|
2555
|
+
if (!result.ok && mustComplete) {
|
|
2556
|
+
result = await runTemplateInstantPath(this.api, prompt, workspacePath, {
|
|
2557
|
+
allowFallback: true,
|
|
2558
|
+
forceMinimalFallback: true,
|
|
2559
|
+
});
|
|
2560
|
+
}
|
|
2561
|
+
if (!result.ok) {
|
|
2562
|
+
if (!this.jsonOutput) {
|
|
2563
|
+
console.log(chalk.yellow(`Template instant path skipped: ${result.error || 'no match'}`));
|
|
2564
|
+
}
|
|
2565
|
+
return false;
|
|
2566
|
+
}
|
|
2567
|
+
const summary = [
|
|
2568
|
+
`Created ${result.entryPath || 'index.html'} via template-instant`,
|
|
2569
|
+
result.templateName ? `(${result.templateName})` : '',
|
|
2570
|
+
result.processingMs ? `in ${Math.round(result.processingMs)}ms` : '',
|
|
2571
|
+
result.usedFallback ? '(minimal fallback)' : '',
|
|
2572
|
+
].filter(Boolean).join(' ');
|
|
2573
|
+
this.messages.push({ role: 'assistant', content: summary });
|
|
2574
|
+
const liveOutcome = createLiveOutcome();
|
|
2575
|
+
liveOutcome.requiresWorkspaceChanges = true;
|
|
2576
|
+
liveOutcome.workspaceHasOutput = true;
|
|
2577
|
+
liveOutcome.changedFileCount = 1;
|
|
2578
|
+
liveOutcome.tasksTotal = 1;
|
|
2579
|
+
liveOutcome.tasksSucceeded = 1;
|
|
2580
|
+
liveOutcome.answerContent = summary;
|
|
2581
|
+
const evaluation = evaluateExecutorSuccess(liveOutcome);
|
|
2582
|
+
this.lastAgentRunOutcome = {
|
|
2583
|
+
prompt,
|
|
2584
|
+
taskId: null,
|
|
2585
|
+
contextId: null,
|
|
2586
|
+
tasksSucceeded: 1,
|
|
2587
|
+
tasksTotal: 1,
|
|
2588
|
+
failedTaskIds: [],
|
|
2589
|
+
unfinishedTaskIds: [],
|
|
2590
|
+
qualityScore: null,
|
|
2591
|
+
qualityMissing: [],
|
|
2592
|
+
qualityBlockers: [],
|
|
2593
|
+
hasOutput: true,
|
|
2594
|
+
answerContent: summary,
|
|
2595
|
+
selfHealStatus: 'skipped',
|
|
2596
|
+
selfHealTool: null,
|
|
2597
|
+
plannerError: null,
|
|
2598
|
+
executorError: null,
|
|
2599
|
+
clientToolErrors: [],
|
|
2600
|
+
transportErrors: [],
|
|
2601
|
+
workspacePath: workspacePath || null,
|
|
2602
|
+
workspaceSyncIssue: null,
|
|
2603
|
+
finishedAt: Date.now(),
|
|
2604
|
+
};
|
|
2605
|
+
if (!this.jsonOutput) {
|
|
2606
|
+
console.log(chalk.green(`✓ ${summary}`));
|
|
2607
|
+
console.log(chalk.gray(' Run: vigthoria preview'));
|
|
2608
|
+
this.printAgentRunSummary(this.lastAgentRunOutcome, evaluation, 1);
|
|
2609
|
+
}
|
|
2610
|
+
return true;
|
|
2611
|
+
}
|
|
2400
2612
|
async tryDirectSingleFileFlow(prompt) {
|
|
2401
2613
|
if (!this.directPromptMode || !this.tools) {
|
|
2402
2614
|
return false;
|
|
@@ -2516,13 +2728,7 @@ export class ChatCommand {
|
|
|
2516
2728
|
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
2729
|
}
|
|
2518
2730
|
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);
|
|
2731
|
+
return promptRequiresWorkspaceChanges(prompt);
|
|
2526
2732
|
}
|
|
2527
2733
|
getPreviousActionablePrompt() {
|
|
2528
2734
|
if (this.lastActionableUserInput && !this.isConfirmationFollowUp(this.lastActionableUserInput)) {
|
|
@@ -2663,7 +2869,11 @@ export class ChatCommand {
|
|
|
2663
2869
|
}
|
|
2664
2870
|
};
|
|
2665
2871
|
const executionPrompt = this.buildExecutionPrompt(contextualPrompt);
|
|
2666
|
-
const agentTaskType = this.inferAgentTaskType(prompt);
|
|
2872
|
+
const agentTaskType = this.lastAgentRoute?.taskKind || this.inferAgentTaskType(prompt);
|
|
2873
|
+
const workflowType = resolveWorkflowType(agentTaskType, prompt);
|
|
2874
|
+
const executionHints = buildExecutionHints(agentTaskType, prompt, {
|
|
2875
|
+
fastPath: this.lastAgentRoute?.fastPath,
|
|
2876
|
+
});
|
|
2667
2877
|
const workspaceContext = {
|
|
2668
2878
|
workspacePath: workspacePath,
|
|
2669
2879
|
projectPath: workspacePath,
|
|
@@ -2699,10 +2909,14 @@ export class ChatCommand {
|
|
|
2699
2909
|
workspace: { path: workspacePath },
|
|
2700
2910
|
...workspaceContext,
|
|
2701
2911
|
agentTaskType,
|
|
2912
|
+
workflowType,
|
|
2913
|
+
executionHints,
|
|
2914
|
+
fastPath: this.lastAgentRoute?.fastPath,
|
|
2915
|
+
agentRoute: this.lastAgentRoute,
|
|
2702
2916
|
executionSurface: 'cli',
|
|
2703
2917
|
clientSurface: 'cli',
|
|
2704
2918
|
localMachineCapable: true,
|
|
2705
|
-
agentTimeoutMs: DEFAULT_V3_AGENT_TIMEOUT_MS,
|
|
2919
|
+
agentTimeoutMs: resolvePlannerAgentTimeoutMs(DEFAULT_V3_AGENT_TIMEOUT_MS, agentTaskType, prompt),
|
|
2706
2920
|
agentIdleTimeoutMs: DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS,
|
|
2707
2921
|
agentSoftTimeoutMs: DEFAULT_V3_AGENT_SOFT_TIMEOUT_MS,
|
|
2708
2922
|
model: routingPolicy.selectedModel,
|
|
@@ -2795,7 +3009,7 @@ export class ChatCommand {
|
|
|
2795
3009
|
const previewGate = (response.metadata?.previewGate || null);
|
|
2796
3010
|
const workspaceHasOutput = this.api.hasAgentWorkspaceOutput(workspaceContext);
|
|
2797
3011
|
const changedFileCount = response.changedFiles ? Object.keys(response.changedFiles).length : 0;
|
|
2798
|
-
const requiresWorkspaceChanges =
|
|
3012
|
+
const requiresWorkspaceChanges = promptRequiresWorkspaceChanges(prompt);
|
|
2799
3013
|
const answerContent = normalizeAgentAnswerContent(response.content, this.v3StreamedAnswerBuffer);
|
|
2800
3014
|
liveOutcome.changedFileCount = changedFileCount;
|
|
2801
3015
|
liveOutcome.requiresWorkspaceChanges = requiresWorkspaceChanges;
|
|
@@ -2874,7 +3088,11 @@ export class ChatCommand {
|
|
|
2874
3088
|
if (isToolEvidenceStubAnswer(finalAnswer)) {
|
|
2875
3089
|
finalAnswer = '';
|
|
2876
3090
|
}
|
|
2877
|
-
const needsAnalysisRescue = !
|
|
3091
|
+
const needsAnalysisRescue = !shouldSkipAnalysisRescue({
|
|
3092
|
+
tasksTotal: liveOutcome.tasksTotal,
|
|
3093
|
+
changedFileCount,
|
|
3094
|
+
requiresWorkspaceChanges,
|
|
3095
|
+
}) && !this.jsonOutput
|
|
2878
3096
|
&& (!executorSucceeded || !isSubstantiveAgentAnswer(finalAnswer));
|
|
2879
3097
|
if (needsAnalysisRescue) {
|
|
2880
3098
|
const rescued = await this.tryAnalysisLocalRescue(prompt, workspacePath);
|
|
@@ -3146,13 +3364,16 @@ export class ChatCommand {
|
|
|
3146
3364
|
this.v3LastActivity = Date.now();
|
|
3147
3365
|
this.v3StreamingStarted = false;
|
|
3148
3366
|
try {
|
|
3367
|
+
const retryTaskType = this.inferAgentTaskType(rawPrompt);
|
|
3149
3368
|
const retryResponse = await this.api.runV3AgentWorkflow(executionPrompt, {
|
|
3150
3369
|
workspace: { path: this.currentProjectPath },
|
|
3151
3370
|
...workspaceContext,
|
|
3152
3371
|
executionSurface: 'cli',
|
|
3153
3372
|
clientSurface: 'cli',
|
|
3154
3373
|
localMachineCapable: true,
|
|
3155
|
-
|
|
3374
|
+
agentTaskType: retryTaskType,
|
|
3375
|
+
workflowType: resolveWorkflowType(retryTaskType, rawPrompt),
|
|
3376
|
+
agentTimeoutMs: resolvePlannerAgentTimeoutMs(DEFAULT_V3_AGENT_TIMEOUT_MS, retryTaskType, rawPrompt),
|
|
3156
3377
|
agentIdleTimeoutMs: DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS,
|
|
3157
3378
|
agentSoftTimeoutMs: DEFAULT_V3_AGENT_SOFT_TIMEOUT_MS,
|
|
3158
3379
|
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;
|