vigthoria-cli 1.11.28 → 1.11.31
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 +0 -1
- package/dist/commands/chat.js +88 -40
- package/dist/utils/agentRunOutcome.js +17 -0
- package/dist/utils/api.d.ts +1 -0
- package/dist/utils/api.js +74 -9
- package/dist/utils/requestIntent.d.ts +15 -1
- package/dist/utils/requestIntent.js +146 -10
- package/dist/utils/v3-stream-events.d.ts +5 -0
- package/dist/utils/v3-stream-events.js +41 -0
- package/dist/utils/v3-workspace-path.js +47 -1
- package/package.json +3 -2
- package/scripts/release/validate-no-go-gates.sh +17 -3
package/dist/commands/chat.d.ts
CHANGED
|
@@ -185,7 +185,6 @@ export declare class ChatCommand {
|
|
|
185
185
|
*/
|
|
186
186
|
private tryTemplateInstantPath;
|
|
187
187
|
private tryDirectSingleFileFlow;
|
|
188
|
-
private isConfirmationFollowUp;
|
|
189
188
|
private taskRequiresWorkspaceChanges;
|
|
190
189
|
private getPreviousActionablePrompt;
|
|
191
190
|
private buildContextualAgentPrompt;
|
package/dist/commands/chat.js
CHANGED
|
@@ -17,9 +17,10 @@ import { runAgentSessionMenu, shouldShowAgentSessionMenu } from './agent-session
|
|
|
17
17
|
import { createLiveOutcome, evaluateExecutorSuccess, handleTaskEvent, isSubstantiveAgentAnswer, isToolEvidenceStubAnswer, normalizeAgentAnswerContent, noteAnalysisToolUse, } from '../utils/agentRunOutcome.js';
|
|
18
18
|
import { looksLikeMarkdownReport, renderMarkdownToTerminal, summarizeMarkdownReport, } from '../utils/terminalMarkdown.js';
|
|
19
19
|
import { emitDeckEvent, isDeckModeEnabled } from '../utils/deckEvents.js';
|
|
20
|
-
import { inferAgentTaskType as sharedInferAgentTaskType, isTrivialHtmlPageRequest, resolvePlannerAgentTimeoutMs, resolveWorkflowType, shouldSkipAnalysisRescue, taskRequiresWorkspaceChanges as promptRequiresWorkspaceChanges, buildExecutionHints, isAgentContinuePrompt, isAgentRetryPrompt, isBuiltContinuePrompt, isBuiltRetryPrompt, } from '../utils/requestIntent.js';
|
|
20
|
+
import { inferAgentTaskType as sharedInferAgentTaskType, inferAgentTaskTypeWithContext, isTrivialHtmlPageRequest, resolvePlannerAgentTimeoutMs, resolveWorkflowType, shouldSkipAnalysisRescue, taskRequiresWorkspaceChanges as promptRequiresWorkspaceChanges, taskRequiresWorkspaceChangesWithContext, buildExecutionHints, isAgentContinuePrompt, isAgentRetryPrompt, isBuiltContinuePrompt, isBuiltRetryPrompt, isBuiltWriteConfirmationPrompt, isConfirmationFollowUp, isWritePermissionGrant, } from '../utils/requestIntent.js';
|
|
21
21
|
import { resolveAgentRoute } from '../utils/agentRoute.js';
|
|
22
22
|
import { runTemplateInstantPath } from '../utils/templateInstantPath.js';
|
|
23
|
+
import { isV3StreamKeepaliveEvent } from '../utils/v3-stream-events.js';
|
|
23
24
|
const DEFAULT_V3_AGENT_TIMEOUT_MS = (() => {
|
|
24
25
|
const rawValue = process.env.VIGTHORIA_AGENT_TIMEOUT_MS || process.env.V3_AGENT_TIMEOUT_MS;
|
|
25
26
|
if (!rawValue) {
|
|
@@ -979,6 +980,9 @@ export class ChatCommand {
|
|
|
979
980
|
return;
|
|
980
981
|
}
|
|
981
982
|
this.v3LastActivity = Date.now();
|
|
983
|
+
if (isV3StreamKeepaliveEvent(event)) {
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
982
986
|
if (event.type === 'tool_call') {
|
|
983
987
|
const eventKey = this.toolEventKey(event);
|
|
984
988
|
if (this.v3SeenToolCalls.has(eventKey)) {
|
|
@@ -1138,11 +1142,6 @@ export class ChatCommand {
|
|
|
1138
1142
|
const quality = this.sanitizeServerPath(plan.quality_profile || '');
|
|
1139
1143
|
const status = typeof plan.status === 'string' ? plan.status : '';
|
|
1140
1144
|
const summary = typeof plan.summary === 'string' ? this.sanitizeServerPath(plan.summary) : '';
|
|
1141
|
-
if (status === 'planning') {
|
|
1142
|
-
const elapsed = Number.isFinite(Number(plan.elapsed_seconds)) ? Number(plan.elapsed_seconds) : 0;
|
|
1143
|
-
spinner.text = elapsed > 0 ? `Planning... (${elapsed}s elapsed)` : 'Planning...';
|
|
1144
|
-
return;
|
|
1145
|
-
}
|
|
1146
1145
|
if (spinner.isSpinning)
|
|
1147
1146
|
spinner.stop();
|
|
1148
1147
|
process.stdout.write(chalk.cyan(` [Plan] `) + this.fitTerminalText(`Task: ${planKind || 'analyzing'}${quality ? ` (${quality})` : ''}`, 9) + '\n');
|
|
@@ -2044,7 +2043,22 @@ export class ChatCommand {
|
|
|
2044
2043
|
}
|
|
2045
2044
|
async runSimplePrompt(prompt) {
|
|
2046
2045
|
if (!this.directPromptMode && !this.operatorMode) {
|
|
2047
|
-
const
|
|
2046
|
+
const isWriteFollowUp = isConfirmationFollowUp(prompt) || isWritePermissionGrant(prompt);
|
|
2047
|
+
if (isWriteFollowUp && (this.lastActionableUserInput || this.getPreviousActionablePrompt())) {
|
|
2048
|
+
if (!this.tools) {
|
|
2049
|
+
throw new Error('Agent tools are not initialized.');
|
|
2050
|
+
}
|
|
2051
|
+
this.agentMode = true;
|
|
2052
|
+
this.syncInteractiveModeModel('agent');
|
|
2053
|
+
if (this.currentSession) {
|
|
2054
|
+
this.currentSession.agentMode = true;
|
|
2055
|
+
this.currentSession.model = this.currentModel;
|
|
2056
|
+
}
|
|
2057
|
+
console.log(chalk.yellow('Write confirmation detected — switching to Agent mode with full file access.'));
|
|
2058
|
+
await this.runAgentTurn(prompt);
|
|
2059
|
+
return;
|
|
2060
|
+
}
|
|
2061
|
+
const promptToRun = isConfirmationFollowUp(prompt) && this.lastActionableUserInput && this.isRepoGroundedPrompt(this.lastActionableUserInput)
|
|
2048
2062
|
? this.lastActionableUserInput
|
|
2049
2063
|
: prompt;
|
|
2050
2064
|
if (this.isRepoGroundedPrompt(promptToRun) || /\b(build|implement|complete|fix|create|make|edit|write|change|finish)\b/i.test(promptToRun)) {
|
|
@@ -2215,7 +2229,7 @@ export class ChatCommand {
|
|
|
2215
2229
|
await this.runLocalAgentLoop(resolvedPrompt);
|
|
2216
2230
|
}
|
|
2217
2231
|
resolveAgentTurnPrompt(prompt) {
|
|
2218
|
-
if (isBuiltContinuePrompt(prompt) || isBuiltRetryPrompt(prompt)) {
|
|
2232
|
+
if (isBuiltContinuePrompt(prompt) || isBuiltRetryPrompt(prompt) || isBuiltWriteConfirmationPrompt(prompt)) {
|
|
2219
2233
|
return prompt;
|
|
2220
2234
|
}
|
|
2221
2235
|
if (isAgentContinuePrompt(prompt)) {
|
|
@@ -2236,6 +2250,15 @@ export class ChatCommand {
|
|
|
2236
2250
|
return followUp;
|
|
2237
2251
|
}
|
|
2238
2252
|
}
|
|
2253
|
+
if ((isConfirmationFollowUp(prompt) || isWritePermissionGrant(prompt)) && !isAgentContinuePrompt(prompt)) {
|
|
2254
|
+
const expanded = this.buildContextualAgentPrompt(prompt);
|
|
2255
|
+
if (expanded !== prompt) {
|
|
2256
|
+
if (!this.jsonOutput) {
|
|
2257
|
+
console.log(chalk.cyan('↪ Treating your message as write confirmation — applying the previous task with full write access.'));
|
|
2258
|
+
}
|
|
2259
|
+
return expanded;
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2239
2262
|
return prompt;
|
|
2240
2263
|
}
|
|
2241
2264
|
shouldPreserveBuildOutcomeForContinue(runPrompt, prior) {
|
|
@@ -2822,15 +2845,11 @@ export class ChatCommand {
|
|
|
2822
2845
|
}
|
|
2823
2846
|
return true;
|
|
2824
2847
|
}
|
|
2825
|
-
isConfirmationFollowUp(prompt) {
|
|
2826
|
-
const normalized = prompt.trim().toLowerCase().replace(/[.!?]+$/g, '').replace(/\s+/g, ' ');
|
|
2827
|
-
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);
|
|
2828
|
-
}
|
|
2829
2848
|
taskRequiresWorkspaceChanges(prompt) {
|
|
2830
2849
|
return promptRequiresWorkspaceChanges(prompt);
|
|
2831
2850
|
}
|
|
2832
2851
|
getPreviousActionablePrompt() {
|
|
2833
|
-
if (this.lastActionableUserInput && !
|
|
2852
|
+
if (this.lastActionableUserInput && !isConfirmationFollowUp(this.lastActionableUserInput)) {
|
|
2834
2853
|
return this.lastActionableUserInput;
|
|
2835
2854
|
}
|
|
2836
2855
|
for (let i = this.messages.length - 1; i >= 0; i -= 1) {
|
|
@@ -2838,7 +2857,9 @@ export class ChatCommand {
|
|
|
2838
2857
|
if (message.role !== 'user')
|
|
2839
2858
|
continue;
|
|
2840
2859
|
const content = (message.content || '').trim();
|
|
2841
|
-
if (!content ||
|
|
2860
|
+
if (!content || isConfirmationFollowUp(content) || isWritePermissionGrant(content))
|
|
2861
|
+
continue;
|
|
2862
|
+
if (isBuiltWriteConfirmationPrompt(content) || isBuiltContinuePrompt(content) || isBuiltRetryPrompt(content))
|
|
2842
2863
|
continue;
|
|
2843
2864
|
return content
|
|
2844
2865
|
.replace(/\n\nProject root:[\s\S]*$/i, '')
|
|
@@ -2848,7 +2869,7 @@ export class ChatCommand {
|
|
|
2848
2869
|
return '';
|
|
2849
2870
|
}
|
|
2850
2871
|
buildContextualAgentPrompt(prompt) {
|
|
2851
|
-
if (!
|
|
2872
|
+
if (!isConfirmationFollowUp(prompt) && !isWritePermissionGrant(prompt)) {
|
|
2852
2873
|
return prompt;
|
|
2853
2874
|
}
|
|
2854
2875
|
const previousPrompt = this.getPreviousActionablePrompt();
|
|
@@ -2856,13 +2877,14 @@ export class ChatCommand {
|
|
|
2856
2877
|
return prompt;
|
|
2857
2878
|
}
|
|
2858
2879
|
return [
|
|
2859
|
-
'The user confirmed
|
|
2880
|
+
'The user confirmed they want file changes applied. Execute the original task with full write access.',
|
|
2860
2881
|
'',
|
|
2861
2882
|
'Original task:',
|
|
2862
2883
|
previousPrompt,
|
|
2863
2884
|
'',
|
|
2864
|
-
`
|
|
2885
|
+
`User confirmation: ${prompt}`,
|
|
2865
2886
|
'',
|
|
2887
|
+
'Apply the requested file changes now. Do not stop at analysis or ask for permission again.',
|
|
2866
2888
|
'Do not reinterpret this confirmation as a new website, landing page, template, or index.html task.',
|
|
2867
2889
|
].join('\n');
|
|
2868
2890
|
}
|
|
@@ -2880,7 +2902,7 @@ export class ChatCommand {
|
|
|
2880
2902
|
}
|
|
2881
2903
|
const workspacePath = promptWorkspacePath || this.currentProjectPath;
|
|
2882
2904
|
const contextualPrompt = this.buildContextualAgentPrompt(prompt);
|
|
2883
|
-
if (contextualPrompt === prompt && !
|
|
2905
|
+
if (contextualPrompt === prompt && !isConfirmationFollowUp(prompt) && !isWritePermissionGrant(prompt)) {
|
|
2884
2906
|
this.lastActionableUserInput = prompt;
|
|
2885
2907
|
}
|
|
2886
2908
|
this.messages.push({ role: 'user', content: contextualPrompt });
|
|
@@ -2970,11 +2992,31 @@ export class ChatCommand {
|
|
|
2970
2992
|
}
|
|
2971
2993
|
};
|
|
2972
2994
|
const executionPrompt = this.buildExecutionPrompt(contextualPrompt);
|
|
2973
|
-
const
|
|
2974
|
-
const
|
|
2975
|
-
const
|
|
2995
|
+
const priorPrompt = this.getPreviousActionablePrompt();
|
|
2996
|
+
const intentContext = { priorPrompt: priorPrompt || null };
|
|
2997
|
+
const classificationPrompt = contextualPrompt;
|
|
2998
|
+
const agentTaskType = this.lastAgentRoute?.taskKind
|
|
2999
|
+
|| inferAgentTaskTypeWithContext(classificationPrompt, intentContext);
|
|
3000
|
+
const workflowType = resolveWorkflowType(agentTaskType, classificationPrompt, intentContext);
|
|
3001
|
+
const executionHints = buildExecutionHints(agentTaskType, classificationPrompt, {
|
|
2976
3002
|
fastPath: this.lastAgentRoute?.fastPath,
|
|
3003
|
+
priorPrompt: priorPrompt || null,
|
|
2977
3004
|
});
|
|
3005
|
+
const priorOutcome = this.lastAgentRunOutcome;
|
|
3006
|
+
const resumeTaskIds = priorOutcome
|
|
3007
|
+
? [...new Set([...(priorOutcome.failedTaskIds || []), ...(priorOutcome.unfinishedTaskIds || [])])]
|
|
3008
|
+
: [];
|
|
3009
|
+
const shouldResumePlan = isAgentContinuePrompt(contextualPrompt)
|
|
3010
|
+
|| isAgentRetryPrompt(contextualPrompt)
|
|
3011
|
+
|| isBuiltContinuePrompt(contextualPrompt)
|
|
3012
|
+
|| isBuiltRetryPrompt(contextualPrompt);
|
|
3013
|
+
const agentExecutionHints = shouldResumePlan
|
|
3014
|
+
? {
|
|
3015
|
+
...executionHints,
|
|
3016
|
+
resume_existing_plan: true,
|
|
3017
|
+
...(resumeTaskIds.length > 0 ? { remaining_task_ids: resumeTaskIds } : {}),
|
|
3018
|
+
}
|
|
3019
|
+
: executionHints;
|
|
2978
3020
|
const workspaceContext = {
|
|
2979
3021
|
workspacePath: workspacePath,
|
|
2980
3022
|
projectPath: workspacePath,
|
|
@@ -3011,7 +3053,7 @@ export class ChatCommand {
|
|
|
3011
3053
|
...workspaceContext,
|
|
3012
3054
|
agentTaskType,
|
|
3013
3055
|
workflowType,
|
|
3014
|
-
executionHints,
|
|
3056
|
+
executionHints: agentExecutionHints,
|
|
3015
3057
|
fastPath: this.lastAgentRoute?.fastPath,
|
|
3016
3058
|
agentRoute: this.lastAgentRoute,
|
|
3017
3059
|
executionSurface: 'cli',
|
|
@@ -3029,20 +3071,15 @@ export class ChatCommand {
|
|
|
3029
3071
|
history: this.getMessagesForModel(),
|
|
3030
3072
|
...runtimeContext,
|
|
3031
3073
|
onStreamEvent: (event) => {
|
|
3074
|
+
if (isV3StreamKeepaliveEvent(event)) {
|
|
3075
|
+
return;
|
|
3076
|
+
}
|
|
3032
3077
|
if (event.type === 'plan') {
|
|
3033
|
-
|
|
3034
|
-
const
|
|
3035
|
-
if (
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
}
|
|
3039
|
-
else {
|
|
3040
|
-
taskDisplay.complete(0);
|
|
3041
|
-
const tasks = plan?.tasks;
|
|
3042
|
-
if (Array.isArray(tasks) && tasks.length > 0) {
|
|
3043
|
-
taskDisplay.start(1);
|
|
3044
|
-
liveOutcome.tasksTotal = tasks.length;
|
|
3045
|
-
}
|
|
3078
|
+
taskDisplay.complete(0);
|
|
3079
|
+
const tasks = event?.plan?.tasks;
|
|
3080
|
+
if (Array.isArray(tasks) && tasks.length > 0) {
|
|
3081
|
+
taskDisplay.start(1);
|
|
3082
|
+
liveOutcome.tasksTotal = tasks.length;
|
|
3046
3083
|
}
|
|
3047
3084
|
}
|
|
3048
3085
|
else if (event.type === 'executor_start') {
|
|
@@ -3085,10 +3122,19 @@ export class ChatCommand {
|
|
|
3085
3122
|
}
|
|
3086
3123
|
else if (event.type === 'error') {
|
|
3087
3124
|
const msg = typeof event.message === 'string' ? event.message : '';
|
|
3088
|
-
|
|
3125
|
+
parsePlannerSummary(msg);
|
|
3126
|
+
const plannerSummary = /Planner-Executor completed/i.test(msg);
|
|
3127
|
+
const allTasksSucceeded = liveOutcome.tasksTotal > 0
|
|
3128
|
+
&& liveOutcome.tasksSucceeded >= liveOutcome.tasksTotal
|
|
3129
|
+
&& liveOutcome.failedTaskIds.size === 0;
|
|
3130
|
+
if (plannerSummary) {
|
|
3131
|
+
if (!allTasksSucceeded && !liveOutcome.plannerError) {
|
|
3132
|
+
liveOutcome.plannerError = msg;
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
else if (/plan|planner|dependency graph/i.test(msg)) {
|
|
3089
3136
|
if (!liveOutcome.plannerError)
|
|
3090
3137
|
liveOutcome.plannerError = msg;
|
|
3091
|
-
parsePlannerSummary(msg);
|
|
3092
3138
|
}
|
|
3093
3139
|
else if (/executor|task failed|iteration/i.test(msg)) {
|
|
3094
3140
|
if (!liveOutcome.executorError)
|
|
@@ -3110,7 +3156,7 @@ export class ChatCommand {
|
|
|
3110
3156
|
const previewGate = (response.metadata?.previewGate || null);
|
|
3111
3157
|
const workspaceHasOutput = this.api.hasAgentWorkspaceOutput(workspaceContext);
|
|
3112
3158
|
const changedFileCount = response.changedFiles ? Object.keys(response.changedFiles).length : 0;
|
|
3113
|
-
const requiresWorkspaceChanges =
|
|
3159
|
+
const requiresWorkspaceChanges = taskRequiresWorkspaceChangesWithContext(contextualPrompt, intentContext);
|
|
3114
3160
|
const answerContent = normalizeAgentAnswerContent(response.content, this.v3StreamedAnswerBuffer);
|
|
3115
3161
|
liveOutcome.changedFileCount = changedFileCount;
|
|
3116
3162
|
liveOutcome.requiresWorkspaceChanges = requiresWorkspaceChanges;
|
|
@@ -3472,7 +3518,9 @@ export class ChatCommand {
|
|
|
3472
3518
|
this.v3StreamingStarted = false;
|
|
3473
3519
|
this.v3StartSeen = false;
|
|
3474
3520
|
try {
|
|
3475
|
-
const
|
|
3521
|
+
const retryPriorPrompt = this.getPreviousActionablePrompt();
|
|
3522
|
+
const retryContext = { priorPrompt: retryPriorPrompt || null };
|
|
3523
|
+
const retryTaskType = inferAgentTaskTypeWithContext(rawPrompt, retryContext);
|
|
3476
3524
|
const retryResponse = await this.api.runV3AgentWorkflow(executionPrompt, {
|
|
3477
3525
|
workspace: { path: this.currentProjectPath },
|
|
3478
3526
|
...workspaceContext,
|
|
@@ -3480,7 +3528,7 @@ export class ChatCommand {
|
|
|
3480
3528
|
clientSurface: 'cli',
|
|
3481
3529
|
localMachineCapable: true,
|
|
3482
3530
|
agentTaskType: retryTaskType,
|
|
3483
|
-
workflowType: resolveWorkflowType(retryTaskType, rawPrompt),
|
|
3531
|
+
workflowType: resolveWorkflowType(retryTaskType, rawPrompt, retryContext),
|
|
3484
3532
|
agentTimeoutMs: resolvePlannerAgentTimeoutMs(DEFAULT_V3_AGENT_TIMEOUT_MS, retryTaskType, rawPrompt),
|
|
3485
3533
|
agentIdleTimeoutMs: DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS,
|
|
3486
3534
|
agentSoftTimeoutMs: DEFAULT_V3_AGENT_SOFT_TIMEOUT_MS,
|
|
@@ -206,6 +206,23 @@ export function evaluateExecutorSuccess(liveOutcome) {
|
|
|
206
206
|
const allTasksPassed = liveOutcome.tasksSucceeded === liveOutcome.tasksTotal;
|
|
207
207
|
const partialTasksPassed = liveOutcome.tasksSucceeded > 0;
|
|
208
208
|
const failedCount = liveOutcome.failedTaskIds.size;
|
|
209
|
+
if (allTasksPassed && failedCount === 0 && !liveOutcome.streamAborted) {
|
|
210
|
+
const qualityOnlyPlannerNote = Boolean(liveOutcome.plannerError)
|
|
211
|
+
&& /Planner-Executor completed:\s*\d+\s*\/\s*\d+\s+tasks succeeded/i.test(String(liveOutcome.plannerError));
|
|
212
|
+
const recoveredAfterTransientErrors = liveOutcome.executorFailed
|
|
213
|
+
|| Boolean(liveOutcome.executorError)
|
|
214
|
+
|| qualityOnlyPlannerNote;
|
|
215
|
+
if (!liveOutcome.plannerError || qualityOnlyPlannerNote || recoveredAfterTransientErrors) {
|
|
216
|
+
const qualityNote = qualityOnlyPlannerNote || /Quality (?:audit|blockers)/i.test(String(liveOutcome.plannerError || ''));
|
|
217
|
+
return {
|
|
218
|
+
executorSucceeded: true,
|
|
219
|
+
statusHeadline: qualityNote
|
|
220
|
+
? `✓ Tasks complete (${liveOutcome.tasksSucceeded}/${liveOutcome.tasksTotal}) — quality audit below target`
|
|
221
|
+
: `✓ Agent run finished (${liveOutcome.tasksSucceeded}/${liveOutcome.tasksTotal} tasks completed)`,
|
|
222
|
+
uiTheme: qualityNote ? 'warning' : 'success',
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
209
226
|
if (allTasksPassed && !hasFatalError) {
|
|
210
227
|
return {
|
|
211
228
|
executorSucceeded: true,
|
package/dist/utils/api.d.ts
CHANGED
|
@@ -272,6 +272,7 @@ export declare class APIClient {
|
|
|
272
272
|
*/
|
|
273
273
|
isAnalysisOnlyTask(message?: string, context?: Record<string, any>): boolean;
|
|
274
274
|
private normalizeWorkspaceRelativePath;
|
|
275
|
+
private collectAgentStateSyncPaths;
|
|
275
276
|
private listFrontendWorkspaceFiles;
|
|
276
277
|
private chooseFrontendPreviewEntry;
|
|
277
278
|
private extractLinkedFrontendAssets;
|
package/dist/utils/api.js
CHANGED
|
@@ -14,6 +14,7 @@ import { getChangedFiles } from './workspace-cache.js';
|
|
|
14
14
|
import { isSubstantiveAgentAnswer, isToolEvidenceStubAnswer } from './agentRunOutcome.js';
|
|
15
15
|
import { joinV3WorkspacePath, normalizeV3WorkspaceRelativePath } from './v3-workspace-path.js';
|
|
16
16
|
import { buildExecutionHints, isPlannerBuildTask, resolvePlannerAgentTimeoutMs } from './requestIntent.js';
|
|
17
|
+
import { isV3StreamKeepaliveEvent } from './v3-stream-events.js';
|
|
17
18
|
export const VIGTHORIA_HUB_CREDITS_URL = 'https://hub.vigthoria.io/credits';
|
|
18
19
|
export const VIGTHORIA_SERVER_TEMPORARILY_UNAVAILABLE_MESSAGE = 'Vigthoria Server is temporarily not available. Please try again later. If the issue persists, please contact support.';
|
|
19
20
|
export class CLIError extends Error {
|
|
@@ -732,6 +733,30 @@ export class APIClient {
|
|
|
732
733
|
normalizeWorkspaceRelativePath(filePath) {
|
|
733
734
|
return String(filePath || '').trim().replace(/\\/g, '/').replace(/^\.\//, '');
|
|
734
735
|
}
|
|
736
|
+
collectAgentStateSyncPaths(rootPath) {
|
|
737
|
+
const paths = [];
|
|
738
|
+
const progressPath = path.join(rootPath, '.vigthoria', 'agent-state', 'progress.json');
|
|
739
|
+
if (!fs.existsSync(progressPath)) {
|
|
740
|
+
return paths;
|
|
741
|
+
}
|
|
742
|
+
paths.push('.vigthoria/agent-state/progress.json');
|
|
743
|
+
const tasksDir = path.join(rootPath, '.vigthoria', 'agent-state', 'tasks');
|
|
744
|
+
if (!fs.existsSync(tasksDir)) {
|
|
745
|
+
return paths;
|
|
746
|
+
}
|
|
747
|
+
try {
|
|
748
|
+
for (const entry of fs.readdirSync(tasksDir, { withFileTypes: true })) {
|
|
749
|
+
if (!entry.isFile() || !entry.name.endsWith('.json')) {
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
paths.push(`.vigthoria/agent-state/tasks/${entry.name}`);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
catch {
|
|
756
|
+
// Ignore unreadable task snapshots.
|
|
757
|
+
}
|
|
758
|
+
return paths;
|
|
759
|
+
}
|
|
735
760
|
listFrontendWorkspaceFiles(rootPath) {
|
|
736
761
|
const results = [];
|
|
737
762
|
const stack = [rootPath];
|
|
@@ -2401,7 +2426,8 @@ menu {
|
|
|
2401
2426
|
};
|
|
2402
2427
|
const snapshot = this.getAgentWorkspaceSnapshot(rootPath);
|
|
2403
2428
|
const candidatePaths = snapshot.paths.filter((entry) => !/(^|[\/\\])\.vigthoria([\/\\]|$)/.test(entry));
|
|
2404
|
-
|
|
2429
|
+
const agentStatePaths = this.collectAgentStateSyncPaths(rootPath);
|
|
2430
|
+
let orderedPaths = [...agentStatePaths, ...candidatePaths];
|
|
2405
2431
|
try {
|
|
2406
2432
|
const ranked = buildSemanticContext(rootPath, requestFocus, 60).topFiles.map((file) => file.path);
|
|
2407
2433
|
const changes = getChangedFiles(rootPath, candidatePaths);
|
|
@@ -2478,7 +2504,9 @@ menu {
|
|
|
2478
2504
|
continue;
|
|
2479
2505
|
if (/(^|[\/\\])\.(git|hg)([\/\\]|$)/.test(relativePath))
|
|
2480
2506
|
continue;
|
|
2481
|
-
|
|
2507
|
+
const normalizedPath = relativePath.replace(/\\/g, '/');
|
|
2508
|
+
const isAgentStateSync = /^\.vigthoria\/agent-state\//i.test(normalizedPath);
|
|
2509
|
+
if (!isAgentStateSync && /(^|[\/\\])\.vigthoria([\/\\]|$)/.test(relativePath))
|
|
2482
2510
|
continue;
|
|
2483
2511
|
const absolutePath = path.join(rootPath, relativePath);
|
|
2484
2512
|
try {
|
|
@@ -2646,6 +2674,18 @@ menu {
|
|
|
2646
2674
|
}
|
|
2647
2675
|
return;
|
|
2648
2676
|
}
|
|
2677
|
+
if (event.type === 'workspace_snapshot' && event.files && typeof event.files === 'object') {
|
|
2678
|
+
for (const [rawPath, content] of Object.entries(event.files)) {
|
|
2679
|
+
if (typeof content !== 'string') {
|
|
2680
|
+
continue;
|
|
2681
|
+
}
|
|
2682
|
+
const filePath = this.normalizeAgentWorkspaceRelativePath(rawPath, serverRoot || undefined);
|
|
2683
|
+
if (filePath) {
|
|
2684
|
+
streamedFiles[filePath] = content;
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
return;
|
|
2688
|
+
}
|
|
2649
2689
|
// Only count confirmed mutations — not workspace snapshots (hydrated baseline)
|
|
2650
2690
|
// or tool_call previews that may still fail client-side / read-only guards.
|
|
2651
2691
|
if (event.type === 'tool_result' && event.success) {
|
|
@@ -2694,9 +2734,17 @@ menu {
|
|
|
2694
2734
|
}
|
|
2695
2735
|
return false;
|
|
2696
2736
|
}
|
|
2697
|
-
resolveV3ClientToolPath(rootPath, rawPath) {
|
|
2698
|
-
const
|
|
2699
|
-
|
|
2737
|
+
resolveV3ClientToolPath(rootPath, rawPath, sourceRoots = []) {
|
|
2738
|
+
const raw = String(rawPath || '.').trim();
|
|
2739
|
+
const roots = [rootPath, ...sourceRoots.filter(Boolean)];
|
|
2740
|
+
let relativePath = '';
|
|
2741
|
+
for (const sourceRoot of roots) {
|
|
2742
|
+
relativePath = this.normalizeAgentWorkspaceRelativePath(raw, sourceRoot);
|
|
2743
|
+
if (relativePath || raw === '.' || raw === '') {
|
|
2744
|
+
break;
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
if (!relativePath && raw !== '.' && raw !== '') {
|
|
2700
2748
|
return null;
|
|
2701
2749
|
}
|
|
2702
2750
|
const absolutePath = relativePath ? joinV3WorkspacePath(rootPath, relativePath) : path.resolve(rootPath);
|
|
@@ -2747,7 +2795,8 @@ menu {
|
|
|
2747
2795
|
}
|
|
2748
2796
|
const name = String(event.name || event.tool || '').trim();
|
|
2749
2797
|
const args = event.arguments || {};
|
|
2750
|
-
const
|
|
2798
|
+
const serverRoot = String(context.__v3ServerWorkspaceRoot || '').trim();
|
|
2799
|
+
const target = this.resolveV3ClientToolPath(rootPath, args.path || args.cwd || '.', serverRoot ? [serverRoot] : []);
|
|
2751
2800
|
if (!target) {
|
|
2752
2801
|
return { success: false, output: '', error: 'Tool path is outside the workspace.' };
|
|
2753
2802
|
}
|
|
@@ -2831,6 +2880,14 @@ menu {
|
|
|
2831
2880
|
error: `File not found: ${target.relativePath}. Write the file before running syntax_check.`,
|
|
2832
2881
|
};
|
|
2833
2882
|
}
|
|
2883
|
+
const stat = fs.statSync(target.absolutePath);
|
|
2884
|
+
if (stat.isDirectory()) {
|
|
2885
|
+
return {
|
|
2886
|
+
success: false,
|
|
2887
|
+
output: '',
|
|
2888
|
+
error: `Expected a file, but path is a directory: ${target.relativePath}`,
|
|
2889
|
+
};
|
|
2890
|
+
}
|
|
2834
2891
|
const ext = path.extname(target.absolutePath).toLowerCase();
|
|
2835
2892
|
if (ext === '.json') {
|
|
2836
2893
|
JSON.parse(fs.readFileSync(target.absolutePath, 'utf8'));
|
|
@@ -2851,12 +2908,19 @@ menu {
|
|
|
2851
2908
|
return { success: true, output: `Syntax check passed: ${target.relativePath}` };
|
|
2852
2909
|
}
|
|
2853
2910
|
if (name === 'run_command') {
|
|
2854
|
-
|
|
2911
|
+
let command = String(args.command || '').trim();
|
|
2855
2912
|
if (!command)
|
|
2856
2913
|
return { success: false, output: '', error: 'run_command requires command.' };
|
|
2914
|
+
if (process.platform === 'win32' && /^ls(\s|$)/i.test(command)) {
|
|
2915
|
+
command = command.replace(/^ls\b/i, 'dir');
|
|
2916
|
+
}
|
|
2857
2917
|
const { exec } = await import('child_process');
|
|
2858
2918
|
return await new Promise((resolve) => {
|
|
2859
|
-
exec(command, {
|
|
2919
|
+
exec(command, {
|
|
2920
|
+
cwd: target.absolutePath,
|
|
2921
|
+
timeout: Number(args.timeout || 30000),
|
|
2922
|
+
shell: process.platform === 'win32' ? 'cmd.exe' : undefined,
|
|
2923
|
+
}, (error, stdout, stderr) => {
|
|
2860
2924
|
resolve({
|
|
2861
2925
|
success: !error,
|
|
2862
2926
|
output: String(stdout || stderr || '').slice(0, 12000),
|
|
@@ -3688,6 +3752,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3688
3752
|
}
|
|
3689
3753
|
if (!serverWorkspaceRoot && event.type === 'context' && typeof event.workspace_root === 'string' && event.workspace_root.trim()) {
|
|
3690
3754
|
serverWorkspaceRoot = event.workspace_root.trim();
|
|
3755
|
+
context.__v3ServerWorkspaceRoot = serverWorkspaceRoot;
|
|
3691
3756
|
}
|
|
3692
3757
|
if (event.type === 'client_tool_request') {
|
|
3693
3758
|
const toolTask = this.handleV3ClientToolRequest(event, context, response.url).catch((error) => {
|
|
@@ -3721,7 +3786,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3721
3786
|
}
|
|
3722
3787
|
}
|
|
3723
3788
|
}
|
|
3724
|
-
if (typeof context.onStreamEvent === 'function') {
|
|
3789
|
+
if (typeof context.onStreamEvent === 'function' && !isV3StreamKeepaliveEvent(userEvent)) {
|
|
3725
3790
|
try {
|
|
3726
3791
|
context.onStreamEvent(userEvent);
|
|
3727
3792
|
}
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
* Shared prompt intent detection for CLI routing, V3 hints, and rescue gating.
|
|
3
3
|
* Keep aligned with V3-Code-Agent/agent.py _classify_request heuristics.
|
|
4
4
|
*/
|
|
5
|
+
export type IntentContext = {
|
|
6
|
+
priorPrompt?: string | null;
|
|
7
|
+
};
|
|
5
8
|
/** Strip CLI-appended platform / mode shaping so it cannot flip quality profiles. */
|
|
6
9
|
export declare function stripExecutionShaping(prompt: string): string;
|
|
7
10
|
export declare function hasBuildVerb(prompt: string): boolean;
|
|
@@ -17,10 +20,20 @@ export declare function isContinueOrRetryPrompt(prompt: string): boolean;
|
|
|
17
20
|
export declare function hasWebPageIntent(prompt: string): boolean;
|
|
18
21
|
export declare function isTrivialHtmlPageRequest(prompt: string): boolean;
|
|
19
22
|
export declare function hasCloneBuildIntent(prompt: string): boolean;
|
|
23
|
+
export declare function isWritePermissionGrant(prompt: string): boolean;
|
|
24
|
+
/** Short or extended confirmations that should chain to the prior actionable request. */
|
|
25
|
+
export declare function isConfirmationFollowUp(prompt: string): boolean;
|
|
26
|
+
/** True when the CLI expanded a write-confirmation follow-up for V3. */
|
|
27
|
+
export declare function isBuiltWriteConfirmationPrompt(prompt: string): boolean;
|
|
28
|
+
export declare function hasFixOrCorrectIntent(prompt: string): boolean;
|
|
29
|
+
export declare function isReadOnlyCheckIntent(prompt: string): boolean;
|
|
20
30
|
export declare function hasExplicitWriteIntent(prompt: string): boolean;
|
|
21
31
|
export declare function taskRequiresWorkspaceChanges(prompt: string): boolean;
|
|
32
|
+
export declare function resolveClassificationPrompt(prompt: string, context?: IntentContext): string;
|
|
33
|
+
export declare function taskRequiresWorkspaceChangesWithContext(prompt: string, context?: IntentContext): boolean;
|
|
22
34
|
export declare function hasAnalyzeAndBuildIntent(prompt: string): boolean;
|
|
23
35
|
export declare function inferAgentTaskType(prompt: string): string;
|
|
36
|
+
export declare function inferAgentTaskTypeWithContext(prompt: string, context?: IntentContext): string;
|
|
24
37
|
export declare function shouldSkipAnalysisRescue(liveOutcome: {
|
|
25
38
|
tasksTotal?: number;
|
|
26
39
|
changedFileCount?: number;
|
|
@@ -29,9 +42,10 @@ export declare function shouldSkipAnalysisRescue(liveOutcome: {
|
|
|
29
42
|
export declare function isPlannerBuildTask(agentTaskType: string, prompt: string): boolean;
|
|
30
43
|
export declare function resolvePlannerAgentTimeoutMs(baseTimeoutMs: number, agentTaskType: string, prompt: string): number;
|
|
31
44
|
/** V3 workflow mode: suppress write path when the user did not ask for file changes. */
|
|
32
|
-
export declare function resolveWorkflowType(agentTaskType: string, prompt: string): 'analysis_only' | 'full';
|
|
45
|
+
export declare function resolveWorkflowType(agentTaskType: string, prompt: string, context?: IntentContext): 'analysis_only' | 'full';
|
|
33
46
|
export declare function buildExecutionHints(agentTaskType: string, prompt: string, options?: {
|
|
34
47
|
fastPath?: string;
|
|
48
|
+
priorPrompt?: string | null;
|
|
35
49
|
}): {
|
|
36
50
|
task_kind: string;
|
|
37
51
|
requires_file_changes: boolean | undefined;
|
|
@@ -11,8 +11,14 @@ const GAME_INTENT = /\b(game|spiel|playable|html5\s+(?:game|canvas)|html5\s+game
|
|
|
11
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
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
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;
|
|
14
|
+
const EXPLICIT_WRITE_VERBS = /\b(implement|write|edit|modify|update|fix|repair|create|build|generate|scaffold|deploy|refactor|develop|integrate|correct|apply|patch)\b/i;
|
|
15
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
|
+
/** User explicitly grants write permission after an analysis or confirmation gate. */
|
|
17
|
+
const WRITE_PERMISSION_GRANT = /\b(?:hereby\s+confirm|i\s+confirm|confirm(?:ed)?\s+that|give\s+(?:you|permission)|granting\s+permission|allow\s+(?:you|me\s+to)|you\s+(?:may|can|have\s+permission\s+to)|permission\s+to|authorized?\s+to|approve(?:d)?\s+(?:the\s+)?(?:changes?|writes?|edits?)|go\s+ahead\s+and(?:\s+write|\s+apply|\s+make|\s+do)?|do\s+all\s+the\s+changes|apply\s+(?:the\s+)?(?:changes?|fixes?|fix)|proceed\s+with\s+(?:the\s+)?(?:changes?|fixes?|implementation|writes?)|make\s+(?:the\s+)?changes|please\s+write)\b/i;
|
|
18
|
+
/** Fix/correct/repair syntax or errors — write path without requiring artifact nouns. */
|
|
19
|
+
const FIX_CORRECT_INTENT = /\b(correct|fix|repair|resolve|patch|address|remedy)\b[\s\S]{0,48}\b(syntax|errors?|bugs?|issues?|mistakes?|typos?)\b|\b(syntax|errors?|bugs?|issues?)\b[\s\S]{0,48}\b(correct|fix|repair|resolve|patch)\b/i;
|
|
20
|
+
/** Read-only audit: check/review/find issues without an paired fix/implement verb nearby. */
|
|
21
|
+
const READ_ONLY_CHECK = /\b(check|verify|audit|inspect|review|investigate|look\s+for|find|scan|validate|examine)\b[\s\S]{0,60}\b(syntax|errors?|bugs?|issues?|missing|gaps?|functionality)\b/i;
|
|
16
22
|
/** Strip CLI-appended platform / mode shaping so it cannot flip quality profiles. */
|
|
17
23
|
export function stripExecutionShaping(prompt) {
|
|
18
24
|
return String(prompt || '')
|
|
@@ -120,8 +126,57 @@ export function isTrivialHtmlPageRequest(prompt) {
|
|
|
120
126
|
export function hasCloneBuildIntent(prompt) {
|
|
121
127
|
return CLONE_BUILD.test(stripExecutionShaping(prompt)) && hasBuildVerb(prompt);
|
|
122
128
|
}
|
|
129
|
+
export function isWritePermissionGrant(prompt) {
|
|
130
|
+
const text = stripExecutionShaping(prompt);
|
|
131
|
+
return WRITE_PERMISSION_GRANT.test(text)
|
|
132
|
+
|| /\ballow\s+you\s+to\s+do\s+all\s+the\s+changes\b/i.test(text)
|
|
133
|
+
|| /\b(?:i\s+)?(?:allow|permit)\s+you\s+to\s+write\b/i.test(text);
|
|
134
|
+
}
|
|
135
|
+
/** Short or extended confirmations that should chain to the prior actionable request. */
|
|
136
|
+
export function isConfirmationFollowUp(prompt) {
|
|
137
|
+
const normalized = stripExecutionShaping(prompt).trim().toLowerCase().replace(/[.!?]+$/g, '').replace(/\s+/g, ' ');
|
|
138
|
+
if (!normalized) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
if (/^(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)) {
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
if (/^(please\s+)?(?:do\s+it|make\s+(?:the\s+)?changes|apply\s+(?:them|the\s+fixes|changes|the\s+fix)|fix\s+(?:them|it|now)|go\s+ahead)$/.test(normalized)) {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
return isWritePermissionGrant(prompt);
|
|
148
|
+
}
|
|
149
|
+
/** True when the CLI expanded a write-confirmation follow-up for V3. */
|
|
150
|
+
export function isBuiltWriteConfirmationPrompt(prompt) {
|
|
151
|
+
return /^The user confirmed they want file changes applied/i.test(stripExecutionShaping(prompt));
|
|
152
|
+
}
|
|
153
|
+
export function hasFixOrCorrectIntent(prompt) {
|
|
154
|
+
return FIX_CORRECT_INTENT.test(stripExecutionShaping(prompt));
|
|
155
|
+
}
|
|
156
|
+
export function isReadOnlyCheckIntent(prompt) {
|
|
157
|
+
const text = stripExecutionShaping(prompt);
|
|
158
|
+
if (!READ_ONLY_CHECK.test(text)) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
if (hasFixOrCorrectIntent(prompt) || isWritePermissionGrant(prompt)) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
if (/\b(?:fix|correct|repair|implement|apply|write|change|update)\b/i.test(text)) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
123
169
|
export function hasExplicitWriteIntent(prompt) {
|
|
124
170
|
const text = stripExecutionShaping(prompt);
|
|
171
|
+
if (isWritePermissionGrant(prompt)) {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
if (hasFixOrCorrectIntent(prompt)) {
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
if (isReadOnlyCheckIntent(prompt)) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
125
180
|
return (BUILD_VERBS.test(text) && ARTIFACT_NOUNS.test(text))
|
|
126
181
|
|| hasCloneBuildIntent(prompt)
|
|
127
182
|
|| (BUILD_VERBS.test(text) && hasGameIntent(prompt));
|
|
@@ -129,13 +184,41 @@ export function hasExplicitWriteIntent(prompt) {
|
|
|
129
184
|
export function taskRequiresWorkspaceChanges(prompt) {
|
|
130
185
|
const text = String(prompt || '').trim();
|
|
131
186
|
const shaped = stripExecutionShaping(text);
|
|
132
|
-
const readOnlyIntent = READ_ONLY_INTENT.test(shaped);
|
|
187
|
+
const readOnlyIntent = READ_ONLY_INTENT.test(shaped) || isReadOnlyCheckIntent(text);
|
|
133
188
|
const explicitWriteIntent = hasExplicitWriteIntent(text);
|
|
134
189
|
if (readOnlyIntent && !explicitWriteIntent) {
|
|
135
190
|
return false;
|
|
136
191
|
}
|
|
137
192
|
return explicitWriteIntent;
|
|
138
193
|
}
|
|
194
|
+
export function resolveClassificationPrompt(prompt, context = {}) {
|
|
195
|
+
if (isBuiltContinuePrompt(prompt) || isBuiltRetryPrompt(prompt) || isBuiltWriteConfirmationPrompt(prompt)) {
|
|
196
|
+
return prompt;
|
|
197
|
+
}
|
|
198
|
+
const prior = String(context.priorPrompt || '').trim();
|
|
199
|
+
if (prior && (isConfirmationFollowUp(prompt) || isWritePermissionGrant(prompt))) {
|
|
200
|
+
return `${prompt}\n\nPrior actionable request:\n${prior}`;
|
|
201
|
+
}
|
|
202
|
+
return prompt;
|
|
203
|
+
}
|
|
204
|
+
export function taskRequiresWorkspaceChangesWithContext(prompt, context = {}) {
|
|
205
|
+
if (isBuiltWriteConfirmationPrompt(prompt) || isWritePermissionGrant(prompt)) {
|
|
206
|
+
const prior = String(context.priorPrompt || '').trim();
|
|
207
|
+
if (prior) {
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
return isWritePermissionGrant(prompt);
|
|
211
|
+
}
|
|
212
|
+
const effective = resolveClassificationPrompt(prompt, context);
|
|
213
|
+
if (taskRequiresWorkspaceChanges(effective)) {
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
const prior = String(context.priorPrompt || '').trim();
|
|
217
|
+
if (prior && isConfirmationFollowUp(prompt)) {
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
139
222
|
export function hasAnalyzeAndBuildIntent(prompt) {
|
|
140
223
|
const text = stripExecutionShaping(prompt);
|
|
141
224
|
return /\b(analy[sz]e|analyse)\b/i.test(text)
|
|
@@ -144,6 +227,21 @@ export function hasAnalyzeAndBuildIntent(prompt) {
|
|
|
144
227
|
}
|
|
145
228
|
export function inferAgentTaskType(prompt) {
|
|
146
229
|
const text = stripExecutionShaping(prompt);
|
|
230
|
+
if (isBuiltWriteConfirmationPrompt(prompt)) {
|
|
231
|
+
if (hasGameIntent(prompt)) {
|
|
232
|
+
return 'game-build';
|
|
233
|
+
}
|
|
234
|
+
if (hasFixOrCorrectIntent(prompt) || /\bsyntax\b/i.test(text)) {
|
|
235
|
+
return 'repair';
|
|
236
|
+
}
|
|
237
|
+
return 'implementation';
|
|
238
|
+
}
|
|
239
|
+
if (hasFixOrCorrectIntent(prompt)) {
|
|
240
|
+
if (hasGameIntent(prompt)) {
|
|
241
|
+
return 'game-build';
|
|
242
|
+
}
|
|
243
|
+
return 'repair';
|
|
244
|
+
}
|
|
147
245
|
if (hasAnalyzeAndBuildIntent(prompt)) {
|
|
148
246
|
if (hasGameIntent(prompt)) {
|
|
149
247
|
return 'game-build';
|
|
@@ -153,7 +251,10 @@ export function inferAgentTaskType(prompt) {
|
|
|
153
251
|
}
|
|
154
252
|
return 'implementation';
|
|
155
253
|
}
|
|
156
|
-
if (
|
|
254
|
+
if (isReadOnlyCheckIntent(prompt)) {
|
|
255
|
+
return 'debugging';
|
|
256
|
+
}
|
|
257
|
+
if (/\b(inspect|analyze|analyse|audit|review|find|diagnose|debug|trace|compare|diff|investigate)\b/i.test(text)
|
|
157
258
|
&& !hasExplicitWriteIntent(prompt)) {
|
|
158
259
|
return 'debugging';
|
|
159
260
|
}
|
|
@@ -170,7 +271,31 @@ export function inferAgentTaskType(prompt) {
|
|
|
170
271
|
}
|
|
171
272
|
return 'implementation';
|
|
172
273
|
}
|
|
173
|
-
|
|
274
|
+
if (hasExplicitWriteIntent(prompt)) {
|
|
275
|
+
return 'implementation';
|
|
276
|
+
}
|
|
277
|
+
return 'general';
|
|
278
|
+
}
|
|
279
|
+
export function inferAgentTaskTypeWithContext(prompt, context = {}) {
|
|
280
|
+
const prior = String(context.priorPrompt || '').trim();
|
|
281
|
+
if ((isConfirmationFollowUp(prompt) || isWritePermissionGrant(prompt) || isBuiltWriteConfirmationPrompt(prompt)) && prior) {
|
|
282
|
+
const priorType = inferAgentTaskType(prior);
|
|
283
|
+
if (isWritePermissionGrant(prompt) || isBuiltWriteConfirmationPrompt(prompt)) {
|
|
284
|
+
if (priorType === 'analysis' || priorType === 'debugging' || priorType === 'general') {
|
|
285
|
+
if (hasFixOrCorrectIntent(prior) || /\bsyntax\b/i.test(prior)) {
|
|
286
|
+
return 'repair';
|
|
287
|
+
}
|
|
288
|
+
if (hasGameIntent(prior)) {
|
|
289
|
+
return 'game-build';
|
|
290
|
+
}
|
|
291
|
+
return 'implementation';
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (priorType !== 'general') {
|
|
295
|
+
return priorType;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return inferAgentTaskType(resolveClassificationPrompt(prompt, context));
|
|
174
299
|
}
|
|
175
300
|
export function shouldSkipAnalysisRescue(liveOutcome) {
|
|
176
301
|
if (liveOutcome.requiresWorkspaceChanges) {
|
|
@@ -209,25 +334,36 @@ export function resolvePlannerAgentTimeoutMs(baseTimeoutMs, agentTaskType, promp
|
|
|
209
334
|
return Math.max(baseTimeoutMs, extended);
|
|
210
335
|
}
|
|
211
336
|
/** V3 workflow mode: suppress write path when the user did not ask for file changes. */
|
|
212
|
-
export function resolveWorkflowType(agentTaskType, prompt) {
|
|
337
|
+
export function resolveWorkflowType(agentTaskType, prompt, context = {}) {
|
|
338
|
+
if (isBuiltWriteConfirmationPrompt(prompt) || isWritePermissionGrant(prompt)) {
|
|
339
|
+
return 'full';
|
|
340
|
+
}
|
|
213
341
|
if (hasAnalyzeAndBuildIntent(prompt)) {
|
|
214
342
|
return 'full';
|
|
215
343
|
}
|
|
216
344
|
if (isBuiltContinuePrompt(prompt) || isBuiltRetryPrompt(prompt)) {
|
|
217
|
-
return
|
|
345
|
+
return taskRequiresWorkspaceChangesWithContext(prompt, context) ? 'full' : 'analysis_only';
|
|
346
|
+
}
|
|
347
|
+
const prior = String(context.priorPrompt || '').trim();
|
|
348
|
+
if (prior && isConfirmationFollowUp(prompt)) {
|
|
349
|
+
return 'full';
|
|
218
350
|
}
|
|
219
|
-
if (!
|
|
351
|
+
if (!taskRequiresWorkspaceChangesWithContext(prompt, context)) {
|
|
220
352
|
return 'analysis_only';
|
|
221
353
|
}
|
|
222
354
|
const kind = String(agentTaskType || '').toLowerCase();
|
|
223
|
-
if (kind === 'analysis' || kind === 'debugging' || kind === 'verification')
|
|
355
|
+
if ((kind === 'analysis' || kind === 'debugging' || kind === 'verification')
|
|
356
|
+
&& !hasFixOrCorrectIntent(prompt)
|
|
357
|
+
&& !isWritePermissionGrant(prompt)) {
|
|
224
358
|
return 'analysis_only';
|
|
225
359
|
}
|
|
226
360
|
return 'full';
|
|
227
361
|
}
|
|
228
362
|
export function buildExecutionHints(agentTaskType, prompt, options = {}) {
|
|
229
|
-
const
|
|
230
|
-
const
|
|
363
|
+
const context = { priorPrompt: options.priorPrompt ?? null };
|
|
364
|
+
const effectiveTaskType = inferAgentTaskTypeWithContext(prompt, context);
|
|
365
|
+
const workflowType = resolveWorkflowType(effectiveTaskType, prompt, context);
|
|
366
|
+
const kind = String(effectiveTaskType || agentTaskType || 'general').toLowerCase();
|
|
231
367
|
const hints = {
|
|
232
368
|
task_kind: kind,
|
|
233
369
|
requires_file_changes: workflowType === 'analysis_only' ? false : undefined,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detect V3 agent SSE keepalive/heartbeat events that exist only to keep
|
|
3
|
+
* connections alive — not substantive work the CLI should surface as progress.
|
|
4
|
+
*/
|
|
5
|
+
const PLANNING_KEEPALIVE = /^Planning in progress\.\.\.\s*\(\d+s(?:\s+elapsed)?\)$/i;
|
|
6
|
+
const PARALLEL_KEEPALIVE = /^Parallel tasks in progress\.\.\.\s*\(\d+s(?:\s+elapsed)?\)$/i;
|
|
7
|
+
const GENERIC_ELAPSED_KEEPALIVE = /\bin progress\b.*\(\d+s(?:\s+elapsed)?\)$/i;
|
|
8
|
+
export function isV3StreamKeepaliveEvent(event) {
|
|
9
|
+
if (!event || typeof event !== 'object') {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
const record = event;
|
|
13
|
+
const type = typeof record.type === 'string' ? record.type : '';
|
|
14
|
+
if (type === 'heartbeat') {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
if (type === 'progress' || type === 'status') {
|
|
18
|
+
const content = String(record.content || record.message || record.status || '').trim();
|
|
19
|
+
if (!content) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
if (PLANNING_KEEPALIVE.test(content) || PARALLEL_KEEPALIVE.test(content)) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
if (GENERIC_ELAPSED_KEEPALIVE.test(content)) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (type === 'plan') {
|
|
30
|
+
const plan = record.plan;
|
|
31
|
+
if (plan && typeof plan === 'object') {
|
|
32
|
+
const planRecord = plan;
|
|
33
|
+
const status = typeof planRecord.status === 'string' ? planRecord.status : '';
|
|
34
|
+
const tasks = planRecord.tasks;
|
|
35
|
+
if (status === 'planning' && !Array.isArray(tasks)) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
@@ -42,6 +42,36 @@ export function normalizeV3WorkspaceRelativePath(rawPath, rootPath) {
|
|
|
42
42
|
}
|
|
43
43
|
return null;
|
|
44
44
|
};
|
|
45
|
+
const stripScrubbedPlaceholder = (value) => {
|
|
46
|
+
const candidate = String(value || '').replace(/^\/+/, '');
|
|
47
|
+
const placeholderPatterns = [
|
|
48
|
+
[/^(?:var\/)?www\/\[internal\](?:\/(.*))?$/i, 1],
|
|
49
|
+
[/^\[internal\](?:\/(.*))?$/i, 1],
|
|
50
|
+
[/^\[Vigthoria service\](?:\/(.*))?$/i, 1],
|
|
51
|
+
[/^\[Vigthoria Agent\](?:\/(.*))?$/i, 1],
|
|
52
|
+
[/^\[Vigthoria storage\](?:\/(.*))?$/i, 1],
|
|
53
|
+
[/^\[temp\](?:\/(.*))?$/i, 1],
|
|
54
|
+
[/^\[home\](?:\/(.*))?$/i, 1],
|
|
55
|
+
[/^\[workspace\](?:\/(.*))?$/i, 1],
|
|
56
|
+
[/^(?:var\/)?www\/\[Vigthoria service\](?:\/(.*))?$/i, 1],
|
|
57
|
+
];
|
|
58
|
+
for (const [pattern, groupIndex] of placeholderPatterns) {
|
|
59
|
+
const match = candidate.match(pattern);
|
|
60
|
+
if (match) {
|
|
61
|
+
return safeRelative(match[groupIndex] || '');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const bracketSplit = candidate.split(/\[(?:internal|Vigthoria service|Vigthoria Agent|temp|home|workspace)\]/i);
|
|
65
|
+
if (bracketSplit.length > 1) {
|
|
66
|
+
const tail = bracketSplit[bracketSplit.length - 1].replace(/^\/+/, '');
|
|
67
|
+
return safeRelative(tail);
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
};
|
|
71
|
+
const scrubbed = stripScrubbedPlaceholder(input);
|
|
72
|
+
if (scrubbed !== null) {
|
|
73
|
+
return scrubbed;
|
|
74
|
+
}
|
|
45
75
|
const decoded = decodeBoundaryUri(input);
|
|
46
76
|
if (decoded !== null) {
|
|
47
77
|
return decoded;
|
|
@@ -72,6 +102,18 @@ export function normalizeV3WorkspaceRelativePath(rawPath, rootPath) {
|
|
|
72
102
|
}
|
|
73
103
|
const normalizedRoot = String(rootPath || '').trim().replace(/\\/g, '/').replace(/\/+$/g, '');
|
|
74
104
|
if (normalizedRoot) {
|
|
105
|
+
if (/^[a-zA-Z]:\//.test(input)) {
|
|
106
|
+
const winRoot = normalizedRoot;
|
|
107
|
+
const inputLower = input.toLowerCase();
|
|
108
|
+
const winRootLower = winRoot.toLowerCase();
|
|
109
|
+
if (inputLower === winRootLower || inputLower === `${winRootLower}/`) {
|
|
110
|
+
return '';
|
|
111
|
+
}
|
|
112
|
+
if (inputLower.startsWith(`${winRootLower}/`)) {
|
|
113
|
+
return safeRelative(input.slice(winRoot.length + 1));
|
|
114
|
+
}
|
|
115
|
+
return '';
|
|
116
|
+
}
|
|
75
117
|
const rootNoLeadingSlash = normalizedRoot.replace(/^\//, '');
|
|
76
118
|
const rootBase = path.posix.basename(normalizedRoot);
|
|
77
119
|
const prefixes = [
|
|
@@ -90,7 +132,11 @@ export function normalizeV3WorkspaceRelativePath(rawPath, rootPath) {
|
|
|
90
132
|
return safeRelative(input.slice(embeddedIndex + embeddedRoot.length));
|
|
91
133
|
}
|
|
92
134
|
}
|
|
93
|
-
|
|
135
|
+
const relative = safeRelative(input);
|
|
136
|
+
if (relative === 'workspace') {
|
|
137
|
+
return '';
|
|
138
|
+
}
|
|
139
|
+
return relative;
|
|
94
140
|
}
|
|
95
141
|
/** Join workspace root + relative path without path.resolve URI corruption on Windows. */
|
|
96
142
|
export function joinV3WorkspacePath(rootPath, relativePath) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vigthoria-cli",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.31",
|
|
4
4
|
"description": "Vigthoria Coder CLI - AI-powered terminal coding assistant",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -68,7 +68,8 @@
|
|
|
68
68
|
"test:model:governance": "npm run build && node scripts/test-model-governance-no-agent.js",
|
|
69
69
|
"validate:no-go": "bash scripts/release/validate-no-go-gates.sh",
|
|
70
70
|
"test:legion:billing:e2e": "npm run build && node scripts/test-legion-godmode-billing-e2e.js",
|
|
71
|
-
"test:windows:v3-sync": "npm run build && node scripts/test-windows-v3-sync-recovery.js"
|
|
71
|
+
"test:windows:v3-sync": "npm run build && node scripts/test-windows-v3-sync-recovery.js",
|
|
72
|
+
"test:v3-workspace-path": "npm run build && node scripts/test-v3-workspace-path.js"
|
|
72
73
|
},
|
|
73
74
|
"keywords": [
|
|
74
75
|
"ai",
|
|
@@ -97,9 +97,23 @@ python3 - << 'PY'
|
|
|
97
97
|
import json
|
|
98
98
|
ids={m.get('id','') for m in json.load(open('/tmp/vig-models.json')).get('data',[])}
|
|
99
99
|
if ids:
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
has_router = any(
|
|
101
|
+
m in ids for m in (
|
|
102
|
+
'vigthoria-balanced-4b', 'vigthoria-balanced-4b:latest',
|
|
103
|
+
'vigthoria-v3-balanced-4b', 'vigthoria-v3-balanced-4b:latest',
|
|
104
|
+
'vigthoria-fast-9b', 'vigthoria-fast-9b:latest',
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
has_creative = any(
|
|
108
|
+
m in ids for m in (
|
|
109
|
+
'vigthoria-creative-9b-v4', 'vigthoria-creative-9b-v4:latest',
|
|
110
|
+
'vigthoria-fast-9b', 'vigthoria-fast-9b:latest',
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
has_code = any('vigthoria-v3-code-35b' in i for i in ids)
|
|
114
|
+
assert has_router, f'missing router model in inventory: {sorted(ids)}'
|
|
115
|
+
assert has_creative, f'missing creative/fast model in inventory: {sorted(ids)}'
|
|
116
|
+
assert has_code, f'missing code model in inventory: {sorted(ids)}'
|
|
103
117
|
else:
|
|
104
118
|
print('[warn] model inventory empty on this host; route proof above is authoritative')
|
|
105
119
|
print('[pass] model inventory gates')
|