vigthoria-cli 1.11.27 → 1.11.29

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.
@@ -171,6 +171,11 @@ export declare class ChatCommand {
171
171
  private runOperatorDirectAnswer;
172
172
  private runSimplePrompt;
173
173
  private runAgentTurn;
174
+ private resolveAgentTurnPrompt;
175
+ private shouldPreserveBuildOutcomeForContinue;
176
+ private resolveOriginalPrompt;
177
+ private commitAgentRunOutcome;
178
+ private printResumeContinuationHint;
174
179
  private buildLocalLoopLiveOutcome;
175
180
  private persistLocalLoopOutcome;
176
181
  private runLocalAgentLoop;
@@ -180,7 +185,6 @@ export declare class ChatCommand {
180
185
  */
181
186
  private tryTemplateInstantPath;
182
187
  private tryDirectSingleFileFlow;
183
- private isConfirmationFollowUp;
184
188
  private taskRequiresWorkspaceChanges;
185
189
  private getPreviousActionablePrompt;
186
190
  private buildContextualAgentPrompt;
@@ -17,7 +17,7 @@ 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, } 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
23
  const DEFAULT_V3_AGENT_TIMEOUT_MS = (() => {
@@ -1405,6 +1405,9 @@ export class ChatCommand {
1405
1405
  this.directToolContinuationCount = 0;
1406
1406
  this.tools = new AgenticTools(this.logger, this.currentProjectPath, async (action) => this.requestPermission(action), this.autoApprove);
1407
1407
  this.initializeSession(options.resume === true || options.retry === true || options.continue === true);
1408
+ if (options.resume && this.lastAgentRunOutcome && !this.jsonOutput && !options.prompt) {
1409
+ this.printResumeContinuationHint();
1410
+ }
1408
1411
  // ── Commando Bridge: connect if --bridge was specified ──────────
1409
1412
  if (options.bridge) {
1410
1413
  const bridgeClient = new BridgeClient({
@@ -1677,7 +1680,11 @@ export class ChatCommand {
1677
1680
  this.operatorMode = this.currentSession.operatorMode || this.operatorMode;
1678
1681
  this.currentModel = this.currentSession.model || this.currentModel;
1679
1682
  if (this.currentSession.lastAgentRunOutcome) {
1680
- this.lastAgentRunOutcome = this.currentSession.lastAgentRunOutcome;
1683
+ const saved = this.currentSession.lastAgentRunOutcome;
1684
+ this.lastAgentRunOutcome = {
1685
+ ...saved,
1686
+ originalPrompt: saved.originalPrompt ?? null,
1687
+ };
1681
1688
  }
1682
1689
  return;
1683
1690
  }
@@ -2037,7 +2044,22 @@ export class ChatCommand {
2037
2044
  }
2038
2045
  async runSimplePrompt(prompt) {
2039
2046
  if (!this.directPromptMode && !this.operatorMode) {
2040
- const promptToRun = this.isConfirmationFollowUp(prompt) && this.lastActionableUserInput && this.isRepoGroundedPrompt(this.lastActionableUserInput)
2047
+ const isWriteFollowUp = isConfirmationFollowUp(prompt) || isWritePermissionGrant(prompt);
2048
+ if (isWriteFollowUp && (this.lastActionableUserInput || this.getPreviousActionablePrompt())) {
2049
+ if (!this.tools) {
2050
+ throw new Error('Agent tools are not initialized.');
2051
+ }
2052
+ this.agentMode = true;
2053
+ this.syncInteractiveModeModel('agent');
2054
+ if (this.currentSession) {
2055
+ this.currentSession.agentMode = true;
2056
+ this.currentSession.model = this.currentModel;
2057
+ }
2058
+ console.log(chalk.yellow('Write confirmation detected — switching to Agent mode with full file access.'));
2059
+ await this.runAgentTurn(prompt);
2060
+ return;
2061
+ }
2062
+ const promptToRun = isConfirmationFollowUp(prompt) && this.lastActionableUserInput && this.isRepoGroundedPrompt(this.lastActionableUserInput)
2041
2063
  ? this.lastActionableUserInput
2042
2064
  : prompt;
2043
2065
  if (this.isRepoGroundedPrompt(promptToRun) || /\b(build|implement|complete|fix|create|make|edit|write|change|finish)\b/i.test(promptToRun)) {
@@ -2165,7 +2187,8 @@ export class ChatCommand {
2165
2187
  if (!this.tools) {
2166
2188
  throw new Error('Agent tools are not initialized.');
2167
2189
  }
2168
- this.lastAgentRoute = await resolveAgentRoute(this.api, prompt);
2190
+ const resolvedPrompt = this.resolveAgentTurnPrompt(prompt);
2191
+ this.lastAgentRoute = await resolveAgentRoute(this.api, resolvedPrompt);
2169
2192
  if (!this.jsonOutput) {
2170
2193
  const r = this.lastAgentRoute;
2171
2194
  const routerNote = r.routerLatencyMs
@@ -2173,13 +2196,13 @@ export class ChatCommand {
2173
2196
  : '';
2174
2197
  this.logger.debug(`Agent route: ${r.path} / ${r.taskKind} [${r.source}]${routerNote} — ${r.reason}`);
2175
2198
  }
2176
- const requiresV3Workflow = this.shouldRequireV3AgentWorkflow(prompt);
2177
- const handledByTemplateInstant = await this.tryTemplateInstantPath(prompt);
2199
+ const requiresV3Workflow = this.shouldRequireV3AgentWorkflow(resolvedPrompt);
2200
+ const handledByTemplateInstant = await this.tryTemplateInstantPath(resolvedPrompt);
2178
2201
  if (handledByTemplateInstant) {
2179
2202
  this.saveSession();
2180
2203
  return;
2181
2204
  }
2182
- const handledByDirectFileFlow = await this.tryDirectSingleFileFlow(prompt);
2205
+ const handledByDirectFileFlow = await this.tryDirectSingleFileFlow(resolvedPrompt);
2183
2206
  if (handledByDirectFileFlow) {
2184
2207
  this.saveSession();
2185
2208
  return;
@@ -2187,12 +2210,12 @@ export class ChatCommand {
2187
2210
  // Prime the message context with the target file when the direct-file flow was
2188
2211
  // bypassed (e.g. HTML files routed to V3) so the local agent loop has file
2189
2212
  // awareness and the ⚙ Executing: read_file banner is always emitted.
2190
- await this.primeBypassedTargetFileContext(prompt);
2191
- if (this.shouldPreferLocalAgentLoop(prompt)) {
2192
- await this.runLocalAgentLoop(prompt);
2213
+ await this.primeBypassedTargetFileContext(resolvedPrompt);
2214
+ if (this.shouldPreferLocalAgentLoop(resolvedPrompt)) {
2215
+ await this.runLocalAgentLoop(resolvedPrompt);
2193
2216
  return;
2194
2217
  }
2195
- const handledByV3Workflow = await this.tryV3AgentWorkflow(prompt);
2218
+ const handledByV3Workflow = await this.tryV3AgentWorkflow(resolvedPrompt);
2196
2219
  if (handledByV3Workflow) {
2197
2220
  this.saveSession();
2198
2221
  return;
@@ -2204,7 +2227,104 @@ export class ChatCommand {
2204
2227
  this.saveSession();
2205
2228
  return;
2206
2229
  }
2207
- await this.runLocalAgentLoop(prompt);
2230
+ await this.runLocalAgentLoop(resolvedPrompt);
2231
+ }
2232
+ resolveAgentTurnPrompt(prompt) {
2233
+ if (isBuiltContinuePrompt(prompt) || isBuiltRetryPrompt(prompt) || isBuiltWriteConfirmationPrompt(prompt)) {
2234
+ return prompt;
2235
+ }
2236
+ if (isAgentContinuePrompt(prompt)) {
2237
+ const followUp = this.buildContinuePrompt();
2238
+ if (followUp) {
2239
+ if (!this.jsonOutput) {
2240
+ console.log(chalk.cyan('↪ Treating your message as /continue — resuming the previous agent run.'));
2241
+ }
2242
+ return followUp;
2243
+ }
2244
+ }
2245
+ if (isAgentRetryPrompt(prompt)) {
2246
+ const followUp = this.buildRetryPrompt();
2247
+ if (followUp) {
2248
+ if (!this.jsonOutput) {
2249
+ console.log(chalk.cyan('↪ Treating your message as /retry — re-running failed tasks from the previous run.'));
2250
+ }
2251
+ return followUp;
2252
+ }
2253
+ }
2254
+ if ((isConfirmationFollowUp(prompt) || isWritePermissionGrant(prompt)) && !isAgentContinuePrompt(prompt)) {
2255
+ const expanded = this.buildContextualAgentPrompt(prompt);
2256
+ if (expanded !== prompt) {
2257
+ if (!this.jsonOutput) {
2258
+ console.log(chalk.cyan('↪ Treating your message as write confirmation — applying the previous task with full write access.'));
2259
+ }
2260
+ return expanded;
2261
+ }
2262
+ }
2263
+ return prompt;
2264
+ }
2265
+ shouldPreserveBuildOutcomeForContinue(runPrompt, prior) {
2266
+ if (this.taskRequiresWorkspaceChanges(runPrompt)) {
2267
+ return false;
2268
+ }
2269
+ if (isBuiltContinuePrompt(runPrompt) || isBuiltRetryPrompt(runPrompt)) {
2270
+ return false;
2271
+ }
2272
+ const chainPrompt = prior.originalPrompt || prior.prompt;
2273
+ if (!this.taskRequiresWorkspaceChanges(chainPrompt)) {
2274
+ return false;
2275
+ }
2276
+ return isAgentContinuePrompt(runPrompt);
2277
+ }
2278
+ resolveOriginalPrompt(runPrompt) {
2279
+ const prior = this.lastAgentRunOutcome;
2280
+ if (prior?.originalPrompt) {
2281
+ return prior.originalPrompt;
2282
+ }
2283
+ if (this.taskRequiresWorkspaceChanges(runPrompt) && !isBuiltContinuePrompt(runPrompt) && !isBuiltRetryPrompt(runPrompt)) {
2284
+ return runPrompt;
2285
+ }
2286
+ if (prior && this.taskRequiresWorkspaceChanges(prior.originalPrompt || prior.prompt)) {
2287
+ return prior.originalPrompt || prior.prompt;
2288
+ }
2289
+ return null;
2290
+ }
2291
+ commitAgentRunOutcome(outcome, runPrompt) {
2292
+ const prior = this.lastAgentRunOutcome;
2293
+ if (prior && this.shouldPreserveBuildOutcomeForContinue(runPrompt, prior)) {
2294
+ if (!this.jsonOutput) {
2295
+ console.log(chalk.yellow('↪ Keeping the previous build run state — use /continue to resume implementation.'));
2296
+ }
2297
+ return;
2298
+ }
2299
+ outcome.originalPrompt = this.resolveOriginalPrompt(runPrompt);
2300
+ this.lastAgentRunOutcome = outcome;
2301
+ }
2302
+ printResumeContinuationHint() {
2303
+ const outcome = this.lastAgentRunOutcome;
2304
+ if (!outcome) {
2305
+ return;
2306
+ }
2307
+ const sourcePrompt = outcome.originalPrompt || outcome.prompt;
2308
+ const isBuild = this.taskRequiresWorkspaceChanges(sourcePrompt);
2309
+ const openWork = outcome.failedTaskIds.length > 0
2310
+ || outcome.unfinishedTaskIds.length > 0
2311
+ || (outcome.tasksTotal > 0 && outcome.tasksSucceeded < outcome.tasksTotal);
2312
+ if (!isBuild && !openWork) {
2313
+ return;
2314
+ }
2315
+ console.log('');
2316
+ console.log(chalk.cyan('Prior agent run restored for this project.'));
2317
+ if (openWork) {
2318
+ const taskIds = [...new Set([...outcome.failedTaskIds, ...outcome.unfinishedTaskIds])].slice(0, 8);
2319
+ if (taskIds.length > 0) {
2320
+ console.log(chalk.gray(` Unfinished tasks: ${taskIds.join(', ')}`));
2321
+ }
2322
+ else {
2323
+ console.log(chalk.gray(` Progress: ${outcome.tasksSucceeded}/${outcome.tasksTotal} tasks completed`));
2324
+ }
2325
+ }
2326
+ console.log(chalk.gray(' Type ') + chalk.cyan('/continue') + chalk.gray(' to resume implementation, or ') + chalk.cyan('/retry') + chalk.gray(' for failed tasks only.'));
2327
+ console.log('');
2208
2328
  }
2209
2329
  buildLocalLoopLiveOutcome(prompt) {
2210
2330
  const liveOutcome = createLiveOutcome();
@@ -2220,8 +2340,9 @@ export class ChatCommand {
2220
2340
  return liveOutcome;
2221
2341
  }
2222
2342
  persistLocalLoopOutcome(prompt, evaluation, liveOutcome) {
2223
- this.lastAgentRunOutcome = {
2343
+ this.commitAgentRunOutcome({
2224
2344
  prompt,
2345
+ originalPrompt: null,
2225
2346
  taskId: null,
2226
2347
  contextId: null,
2227
2348
  tasksSucceeded: liveOutcome.tasksSucceeded,
@@ -2242,7 +2363,7 @@ export class ChatCommand {
2242
2363
  workspacePath: this.currentProjectPath || null,
2243
2364
  workspaceSyncIssue: null,
2244
2365
  finishedAt: Date.now(),
2245
- };
2366
+ }, prompt);
2246
2367
  }
2247
2368
  async runLocalAgentLoop(prompt) {
2248
2369
  if (!this.tools) {
@@ -2580,8 +2701,9 @@ export class ChatCommand {
2580
2701
  liveOutcome.tasksSucceeded = 1;
2581
2702
  liveOutcome.answerContent = summary;
2582
2703
  const evaluation = evaluateExecutorSuccess(liveOutcome);
2583
- this.lastAgentRunOutcome = {
2704
+ this.commitAgentRunOutcome({
2584
2705
  prompt,
2706
+ originalPrompt: null,
2585
2707
  taskId: null,
2586
2708
  contextId: null,
2587
2709
  tasksSucceeded: 1,
@@ -2602,7 +2724,7 @@ export class ChatCommand {
2602
2724
  workspacePath: workspacePath || null,
2603
2725
  workspaceSyncIssue: null,
2604
2726
  finishedAt: Date.now(),
2605
- };
2727
+ }, prompt);
2606
2728
  if (!this.jsonOutput) {
2607
2729
  console.log(chalk.green(`✓ ${summary}`));
2608
2730
  console.log(chalk.gray(' Run: vigthoria preview'));
@@ -2724,15 +2846,11 @@ export class ChatCommand {
2724
2846
  }
2725
2847
  return true;
2726
2848
  }
2727
- isConfirmationFollowUp(prompt) {
2728
- const normalized = prompt.trim().toLowerCase().replace(/[.!?]+$/g, '').replace(/\s+/g, ' ');
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);
2730
- }
2731
2849
  taskRequiresWorkspaceChanges(prompt) {
2732
2850
  return promptRequiresWorkspaceChanges(prompt);
2733
2851
  }
2734
2852
  getPreviousActionablePrompt() {
2735
- if (this.lastActionableUserInput && !this.isConfirmationFollowUp(this.lastActionableUserInput)) {
2853
+ if (this.lastActionableUserInput && !isConfirmationFollowUp(this.lastActionableUserInput)) {
2736
2854
  return this.lastActionableUserInput;
2737
2855
  }
2738
2856
  for (let i = this.messages.length - 1; i >= 0; i -= 1) {
@@ -2740,7 +2858,9 @@ export class ChatCommand {
2740
2858
  if (message.role !== 'user')
2741
2859
  continue;
2742
2860
  const content = (message.content || '').trim();
2743
- if (!content || this.isConfirmationFollowUp(content))
2861
+ if (!content || isConfirmationFollowUp(content) || isWritePermissionGrant(content))
2862
+ continue;
2863
+ if (isBuiltWriteConfirmationPrompt(content) || isBuiltContinuePrompt(content) || isBuiltRetryPrompt(content))
2744
2864
  continue;
2745
2865
  return content
2746
2866
  .replace(/\n\nProject root:[\s\S]*$/i, '')
@@ -2750,7 +2870,7 @@ export class ChatCommand {
2750
2870
  return '';
2751
2871
  }
2752
2872
  buildContextualAgentPrompt(prompt) {
2753
- if (!this.isConfirmationFollowUp(prompt)) {
2873
+ if (!isConfirmationFollowUp(prompt) && !isWritePermissionGrant(prompt)) {
2754
2874
  return prompt;
2755
2875
  }
2756
2876
  const previousPrompt = this.getPreviousActionablePrompt();
@@ -2758,13 +2878,14 @@ export class ChatCommand {
2758
2878
  return prompt;
2759
2879
  }
2760
2880
  return [
2761
- 'The user confirmed the previous agent task. Continue and execute that original task now.',
2881
+ 'The user confirmed they want file changes applied. Execute the original task with full write access.',
2762
2882
  '',
2763
2883
  'Original task:',
2764
2884
  previousPrompt,
2765
2885
  '',
2766
- `Latest confirmation: ${prompt}`,
2886
+ `User confirmation: ${prompt}`,
2767
2887
  '',
2888
+ 'Apply the requested file changes now. Do not stop at analysis or ask for permission again.',
2768
2889
  'Do not reinterpret this confirmation as a new website, landing page, template, or index.html task.',
2769
2890
  ].join('\n');
2770
2891
  }
@@ -2782,7 +2903,7 @@ export class ChatCommand {
2782
2903
  }
2783
2904
  const workspacePath = promptWorkspacePath || this.currentProjectPath;
2784
2905
  const contextualPrompt = this.buildContextualAgentPrompt(prompt);
2785
- if (contextualPrompt === prompt && !this.isConfirmationFollowUp(prompt)) {
2906
+ if (contextualPrompt === prompt && !isConfirmationFollowUp(prompt) && !isWritePermissionGrant(prompt)) {
2786
2907
  this.lastActionableUserInput = prompt;
2787
2908
  }
2788
2909
  this.messages.push({ role: 'user', content: contextualPrompt });
@@ -2872,10 +2993,15 @@ export class ChatCommand {
2872
2993
  }
2873
2994
  };
2874
2995
  const executionPrompt = this.buildExecutionPrompt(contextualPrompt);
2875
- const agentTaskType = this.lastAgentRoute?.taskKind || this.inferAgentTaskType(prompt);
2876
- const workflowType = resolveWorkflowType(agentTaskType, prompt);
2877
- const executionHints = buildExecutionHints(agentTaskType, prompt, {
2996
+ const priorPrompt = this.getPreviousActionablePrompt();
2997
+ const intentContext = { priorPrompt: priorPrompt || null };
2998
+ const classificationPrompt = contextualPrompt;
2999
+ const agentTaskType = this.lastAgentRoute?.taskKind
3000
+ || inferAgentTaskTypeWithContext(classificationPrompt, intentContext);
3001
+ const workflowType = resolveWorkflowType(agentTaskType, classificationPrompt, intentContext);
3002
+ const executionHints = buildExecutionHints(agentTaskType, classificationPrompt, {
2878
3003
  fastPath: this.lastAgentRoute?.fastPath,
3004
+ priorPrompt: priorPrompt || null,
2879
3005
  });
2880
3006
  const workspaceContext = {
2881
3007
  workspacePath: workspacePath,
@@ -3012,7 +3138,7 @@ export class ChatCommand {
3012
3138
  const previewGate = (response.metadata?.previewGate || null);
3013
3139
  const workspaceHasOutput = this.api.hasAgentWorkspaceOutput(workspaceContext);
3014
3140
  const changedFileCount = response.changedFiles ? Object.keys(response.changedFiles).length : 0;
3015
- const requiresWorkspaceChanges = promptRequiresWorkspaceChanges(prompt);
3141
+ const requiresWorkspaceChanges = taskRequiresWorkspaceChangesWithContext(contextualPrompt, intentContext);
3016
3142
  const answerContent = normalizeAgentAnswerContent(response.content, this.v3StreamedAnswerBuffer);
3017
3143
  liveOutcome.changedFileCount = changedFileCount;
3018
3144
  liveOutcome.requiresWorkspaceChanges = requiresWorkspaceChanges;
@@ -3198,8 +3324,9 @@ export class ChatCommand {
3198
3324
  if (!executorSucceeded) {
3199
3325
  process.exitCode = 1;
3200
3326
  }
3201
- this.lastAgentRunOutcome = {
3327
+ this.commitAgentRunOutcome({
3202
3328
  prompt,
3329
+ originalPrompt: null,
3203
3330
  taskId: response.taskId || null,
3204
3331
  contextId: response.contextId || null,
3205
3332
  tasksSucceeded: liveOutcome.tasksSucceeded,
@@ -3222,9 +3349,11 @@ export class ChatCommand {
3222
3349
  ? 'Local workspace has files but the agent did not confirm tracked changes.'
3223
3350
  : null,
3224
3351
  finishedAt: Date.now(),
3225
- };
3352
+ }, prompt);
3226
3353
  if (!this.jsonOutput && !this.directPromptMode) {
3227
- this.printAgentRunSummary(this.lastAgentRunOutcome, runEvaluation, changedFileCount);
3354
+ if (this.lastAgentRunOutcome) {
3355
+ this.printAgentRunSummary(this.lastAgentRunOutcome, runEvaluation, changedFileCount);
3356
+ }
3228
3357
  }
3229
3358
  if (this.jsonOutput) {
3230
3359
  if (!executorSucceeded) {
@@ -3296,8 +3425,9 @@ export class ChatCommand {
3296
3425
  taskDisplay.finalize();
3297
3426
  }
3298
3427
  catch (_) { /* render is best-effort */ }
3299
- this.lastAgentRunOutcome = {
3428
+ this.commitAgentRunOutcome({
3300
3429
  prompt,
3430
+ originalPrompt: null,
3301
3431
  taskId: null,
3302
3432
  contextId: null,
3303
3433
  tasksSucceeded: liveOutcome.tasksSucceeded,
@@ -3318,7 +3448,7 @@ export class ChatCommand {
3318
3448
  workspacePath: workspacePath || null,
3319
3449
  workspaceSyncIssue: null,
3320
3450
  finishedAt: Date.now(),
3321
- };
3451
+ }, prompt);
3322
3452
  if (!this.jsonOutput) {
3323
3453
  process.exitCode = 1;
3324
3454
  }
@@ -3346,7 +3476,9 @@ export class ChatCommand {
3346
3476
  requiresWorkspaceChanges: this.taskRequiresWorkspaceChanges(prompt),
3347
3477
  workspaceHasOutput: this.api.hasAgentWorkspaceOutput(workspaceContext),
3348
3478
  });
3349
- this.printAgentRunSummary(this.lastAgentRunOutcome, failedEval, 0);
3479
+ if (this.lastAgentRunOutcome) {
3480
+ this.printAgentRunSummary(this.lastAgentRunOutcome, failedEval, 0);
3481
+ }
3350
3482
  }
3351
3483
  return true;
3352
3484
  }
@@ -3368,7 +3500,9 @@ export class ChatCommand {
3368
3500
  this.v3StreamingStarted = false;
3369
3501
  this.v3StartSeen = false;
3370
3502
  try {
3371
- const retryTaskType = this.inferAgentTaskType(rawPrompt);
3503
+ const retryPriorPrompt = this.getPreviousActionablePrompt();
3504
+ const retryContext = { priorPrompt: retryPriorPrompt || null };
3505
+ const retryTaskType = inferAgentTaskTypeWithContext(rawPrompt, retryContext);
3372
3506
  const retryResponse = await this.api.runV3AgentWorkflow(executionPrompt, {
3373
3507
  workspace: { path: this.currentProjectPath },
3374
3508
  ...workspaceContext,
@@ -3376,7 +3510,7 @@ export class ChatCommand {
3376
3510
  clientSurface: 'cli',
3377
3511
  localMachineCapable: true,
3378
3512
  agentTaskType: retryTaskType,
3379
- workflowType: resolveWorkflowType(retryTaskType, rawPrompt),
3513
+ workflowType: resolveWorkflowType(retryTaskType, rawPrompt, retryContext),
3380
3514
  agentTimeoutMs: resolvePlannerAgentTimeoutMs(DEFAULT_V3_AGENT_TIMEOUT_MS, retryTaskType, rawPrompt),
3381
3515
  agentIdleTimeoutMs: DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS,
3382
3516
  agentSoftTimeoutMs: DEFAULT_V3_AGENT_SOFT_TIMEOUT_MS,
@@ -3797,6 +3931,7 @@ export class ChatCommand {
3797
3931
  const o = this.lastAgentRunOutcome;
3798
3932
  if (!o || !o.prompt)
3799
3933
  return null;
3934
+ const sourcePrompt = o.originalPrompt || o.prompt;
3800
3935
  const runtime = this.getRuntimeEnvironmentContext();
3801
3936
  const remaining = [...new Set([...o.failedTaskIds, ...o.unfinishedTaskIds])];
3802
3937
  const taskList = remaining.length > 0 ? remaining.join(', ') : '';
@@ -3811,9 +3946,9 @@ export class ChatCommand {
3811
3946
  ? '\nUse local workspace tools and local filesystem paths first; do not assume server-side workspace access.'
3812
3947
  : '';
3813
3948
  if (taskList) {
3814
- return `Resume the previous agent run. Re-execute only these tasks and make them pass: ${taskList}.${blockerLine}${missingLine}${envLine}${localPreferenceLine}\nOriginal request was: ${o.prompt}`;
3949
+ return `Resume the previous agent run. Re-execute only these tasks and make them pass: ${taskList}.${blockerLine}${missingLine}${envLine}${localPreferenceLine}\nOriginal request was: ${sourcePrompt}`;
3815
3950
  }
3816
- return `Retry the previous request and make sure it finishes successfully.${blockerLine}${missingLine}${envLine}${localPreferenceLine}\nOriginal request was: ${o.prompt}`;
3951
+ return `Retry the previous request and make sure it finishes successfully.${blockerLine}${missingLine}${envLine}${localPreferenceLine}\nOriginal request was: ${sourcePrompt}`;
3817
3952
  }
3818
3953
  computeRetryPromptSignature() {
3819
3954
  const o = this.lastAgentRunOutcome;
@@ -3831,9 +3966,10 @@ export class ChatCommand {
3831
3966
  const o = this.lastAgentRunOutcome;
3832
3967
  if (!o || !o.prompt)
3833
3968
  return null;
3969
+ const sourcePrompt = o.originalPrompt || o.prompt;
3834
3970
  const remaining = [...new Set([...o.failedTaskIds, ...o.unfinishedTaskIds])];
3835
3971
  const taskList = remaining.length > 0 ? `\nRemaining tasks to finish: ${remaining.join(', ')}.` : '';
3836
- const mustChangeFiles = this.taskRequiresWorkspaceChanges(o.prompt)
3972
+ const mustChangeFiles = this.taskRequiresWorkspaceChanges(sourcePrompt)
3837
3973
  ? '\nThis was a build/edit request. Do not stop after analysis or tool JSON; make real workspace file changes and verify them before finishing.'
3838
3974
  : '';
3839
3975
  const blockerLine = o.qualityBlockers.length > 0
@@ -3842,7 +3978,7 @@ export class ChatCommand {
3842
3978
  const missingLine = o.qualityMissing.length > 0
3843
3979
  ? `\nMissing pieces: ${o.qualityMissing.slice(0, 6).join(', ')}.`
3844
3980
  : '';
3845
- return `Continue the previous agent run from the current workspace state without re-doing already-completed work.${taskList}${mustChangeFiles}${blockerLine}${missingLine}\nOriginal request was: ${o.prompt}`;
3981
+ return `Continue the previous agent run from the current workspace state without re-doing already-completed work.${taskList}${mustChangeFiles}${blockerLine}${missingLine}\nOriginal request was: ${sourcePrompt}`;
3846
3982
  }
3847
3983
  /**
3848
3984
  * Re-print the last agent run summary, or guide the user when there isn't one.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Unified agent routing: regex (instant) → optional Balanced 4B → V3 execution hints.
3
3
  */
4
- import { inferAgentTaskType, isTrivialHtmlPageRequest, isContinueOrRetryPrompt, hasGameIntent, hasWebPageIntent, taskRequiresWorkspaceChanges, stripExecutionShaping, } from './requestIntent.js';
4
+ import { inferAgentTaskType, isTrivialHtmlPageRequest, isAgentContinuePrompt, isAgentRetryPrompt, hasGameIntent, hasWebPageIntent, taskRequiresWorkspaceChanges, stripExecutionShaping, } from './requestIntent.js';
5
5
  import { isFastInferenceRouterAvailable, routeWithBalanced4b, shouldUseBalancedRouter, } from './fastAgentRouter.js';
6
6
  function normalizeTaskKind(value, fallback) {
7
7
  const kind = String(value || fallback).trim().toLowerCase();
@@ -74,13 +74,23 @@ function mergeLlmRoute(regexTask, prompt, parsed) {
74
74
  }
75
75
  export async function resolveAgentRoute(api, prompt) {
76
76
  const stripped = stripExecutionShaping(prompt);
77
- if (isContinueOrRetryPrompt(stripped)) {
77
+ if (isAgentContinuePrompt(stripped)) {
78
78
  const regexTask = inferAgentTaskType(stripped);
79
79
  return {
80
80
  path: 'v3-agent',
81
81
  taskKind: regexTask === 'analysis' ? 'implementation' : regexTask,
82
- confidence: 0.9,
83
- reason: 'continue/retry follow-up (regex)',
82
+ confidence: 0.92,
83
+ reason: 'continue follow-up (regex)',
84
+ source: 'regex',
85
+ };
86
+ }
87
+ if (isAgentRetryPrompt(stripped)) {
88
+ const regexTask = inferAgentTaskType(stripped);
89
+ return {
90
+ path: 'v3-agent',
91
+ taskKind: regexTask === 'analysis' ? 'implementation' : regexTask,
92
+ confidence: 0.92,
93
+ reason: 'retry follow-up (regex)',
84
94
  source: 'regex',
85
95
  };
86
96
  }
package/dist/utils/api.js CHANGED
@@ -2646,48 +2646,18 @@ menu {
2646
2646
  }
2647
2647
  return;
2648
2648
  }
2649
- if (event.type === 'workspace_snapshot' && event.files && typeof event.files === 'object') {
2650
- for (const [rawPath, content] of Object.entries(event.files)) {
2651
- if (typeof content !== 'string') {
2652
- continue;
2653
- }
2654
- const filePath = this.normalizeAgentWorkspaceRelativePath(rawPath, serverRoot || undefined);
2655
- if (filePath) {
2656
- streamedFiles[filePath] = content;
2657
- }
2658
- }
2659
- return;
2660
- }
2661
- if (event.type !== 'tool_call') {
2662
- if (event.type === 'tool_result' && event.success) {
2663
- const name = String(event.name || event.tool || '').trim();
2664
- const args = event.arguments || {};
2665
- const targetPath = typeof event.target === 'string'
2666
- ? event.target
2667
- : (typeof args.path === 'string' ? args.path : '');
2668
- if ((name === 'write_file' || name === 'edit_file') && targetPath) {
2669
- const filePath = this.normalizeAgentWorkspaceRelativePath(targetPath, serverRoot || undefined);
2670
- if (filePath && typeof args.content === 'string') {
2671
- streamedFiles[filePath] = args.content;
2672
- }
2673
- }
2674
- }
2675
- return;
2676
- }
2677
- const args = event.arguments || {};
2678
- if ((event.name === 'write_file' || event.name === 'edit_file') && typeof args.path === 'string') {
2679
- const filePath = this.normalizeAgentWorkspaceRelativePath(args.path, serverRoot || undefined);
2680
- if (!filePath) {
2681
- return;
2682
- }
2683
- if (event.name === 'write_file' && typeof args.content === 'string') {
2684
- streamedFiles[filePath] = args.content;
2685
- return;
2686
- }
2687
- if (event.name === 'edit_file' && typeof args.old_string === 'string' && typeof args.new_string === 'string') {
2688
- const existing = streamedFiles[filePath];
2689
- if (typeof existing === 'string' && existing.includes(args.old_string)) {
2690
- streamedFiles[filePath] = existing.replace(args.old_string, args.new_string);
2649
+ // Only count confirmed mutations not workspace snapshots (hydrated baseline)
2650
+ // or tool_call previews that may still fail client-side / read-only guards.
2651
+ if (event.type === 'tool_result' && event.success) {
2652
+ const name = String(event.name || event.tool || '').trim();
2653
+ const args = event.arguments || {};
2654
+ const targetPath = typeof event.target === 'string'
2655
+ ? event.target
2656
+ : (typeof args.path === 'string' ? args.path : '');
2657
+ if ((name === 'write_file' || name === 'edit_file') && targetPath) {
2658
+ const filePath = this.normalizeAgentWorkspaceRelativePath(targetPath, serverRoot || undefined);
2659
+ if (filePath && typeof args.content === 'string') {
2660
+ streamedFiles[filePath] = args.content;
2691
2661
  }
2692
2662
  }
2693
2663
  }
@@ -2,19 +2,38 @@
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;
8
11
  export declare function hasArtifactNoun(prompt: string): boolean;
9
12
  export declare function hasGameIntent(prompt: string): boolean;
13
+ /** True when the CLI already expanded a /continue-style follow-up prompt. */
14
+ export declare function isBuiltContinuePrompt(prompt: string): boolean;
15
+ /** True when the CLI already expanded a /retry-style follow-up prompt. */
16
+ export declare function isBuiltRetryPrompt(prompt: string): boolean;
17
+ export declare function isAgentContinuePrompt(prompt: string): boolean;
18
+ export declare function isAgentRetryPrompt(prompt: string): boolean;
10
19
  export declare function isContinueOrRetryPrompt(prompt: string): boolean;
11
20
  export declare function hasWebPageIntent(prompt: string): boolean;
12
21
  export declare function isTrivialHtmlPageRequest(prompt: string): boolean;
13
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;
14
30
  export declare function hasExplicitWriteIntent(prompt: string): boolean;
15
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;
16
34
  export declare function hasAnalyzeAndBuildIntent(prompt: string): boolean;
17
35
  export declare function inferAgentTaskType(prompt: string): string;
36
+ export declare function inferAgentTaskTypeWithContext(prompt: string, context?: IntentContext): string;
18
37
  export declare function shouldSkipAnalysisRescue(liveOutcome: {
19
38
  tasksTotal?: number;
20
39
  changedFileCount?: number;
@@ -23,9 +42,10 @@ export declare function shouldSkipAnalysisRescue(liveOutcome: {
23
42
  export declare function isPlannerBuildTask(agentTaskType: string, prompt: string): boolean;
24
43
  export declare function resolvePlannerAgentTimeoutMs(baseTimeoutMs: number, agentTaskType: string, prompt: string): number;
25
44
  /** V3 workflow mode: suppress write path when the user did not ask for file changes. */
26
- export declare function resolveWorkflowType(agentTaskType: string, prompt: string): 'analysis_only' | 'full';
45
+ export declare function resolveWorkflowType(agentTaskType: string, prompt: string, context?: IntentContext): 'analysis_only' | 'full';
27
46
  export declare function buildExecutionHints(agentTaskType: string, prompt: string, options?: {
28
47
  fastPath?: string;
48
+ priorPrompt?: string | null;
29
49
  }): {
30
50
  task_kind: string;
31
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 || '')
@@ -41,9 +47,57 @@ export function hasGameIntent(prompt) {
41
47
  }
42
48
  return false;
43
49
  }
50
+ const CONTINUE_VERB = /\b(?:continue|contiune|continu|proceed|resume|keep\s+going|go\s+on|carry\s+on|pick\s+up\s+where)\b/i;
51
+ const CONTINUE_OBJECT = /\b(?:task|tasks|work|build|building|implementation|project|run|plan|planning|remaining|unfinished|missing|components?|next\s+steps?|previous|last|where\s+we\s+left|hyperloop|report)\b/i;
52
+ /** True when the CLI already expanded a /continue-style follow-up prompt. */
53
+ export function isBuiltContinuePrompt(prompt) {
54
+ return /^Continue the previous agent run/i.test(stripExecutionShaping(prompt));
55
+ }
56
+ /** True when the CLI already expanded a /retry-style follow-up prompt. */
57
+ export function isBuiltRetryPrompt(prompt) {
58
+ const text = stripExecutionShaping(prompt);
59
+ return /^(?:Resume the previous agent run|Retry the previous request)/i.test(text);
60
+ }
61
+ export function isAgentContinuePrompt(prompt) {
62
+ const text = stripExecutionShaping(prompt).trim();
63
+ if (!text) {
64
+ return false;
65
+ }
66
+ if (isBuiltContinuePrompt(text) || isBuiltRetryPrompt(text)) {
67
+ return false;
68
+ }
69
+ if (/^\/(?:continue|contiune|contiunue)$/i.test(text)) {
70
+ return true;
71
+ }
72
+ if (/\bcontinue\s+(?:the\s+)?(?:previous|last|current)\s+(?:agent\s+)?run\b/i.test(text)) {
73
+ return true;
74
+ }
75
+ if (/\bresume\s+(?:the\s+)?(?:failed|unfinished)\b/i.test(text)) {
76
+ return true;
77
+ }
78
+ if (/\bplanner\s+produced\s+\d+\s+execution\s+tasks\b/i.test(text)) {
79
+ return true;
80
+ }
81
+ if (CONTINUE_VERB.test(text) && CONTINUE_OBJECT.test(text)) {
82
+ return true;
83
+ }
84
+ if (/^(?:please\s+)?(?:continue|contiune|proceed)(?:[!?.]|$)/i.test(text)) {
85
+ return true;
86
+ }
87
+ return false;
88
+ }
89
+ export function isAgentRetryPrompt(prompt) {
90
+ const text = stripExecutionShaping(prompt).trim();
91
+ if (!text || isBuiltContinuePrompt(text) || isBuiltRetryPrompt(text)) {
92
+ return false;
93
+ }
94
+ if (/^\/retry$/i.test(text)) {
95
+ return true;
96
+ }
97
+ return /\b(?:retry|re-?try)\s+(?:the\s+)?(?:failed|last|previous|unfinished)\b/i.test(text);
98
+ }
44
99
  export function isContinueOrRetryPrompt(prompt) {
45
- const text = stripExecutionShaping(prompt).toLowerCase();
46
- return /\b(continue\s+(?:the\s+)?(?:previous|last|current)\s+(?:agent\s+)?run|\/retry|\/continue|resume\s+(?:the\s+)?(?:failed|unfinished)|planner\s+produced\s+\d+\s+execution\s+tasks)\b/i.test(text);
100
+ return isAgentContinuePrompt(prompt) || isAgentRetryPrompt(prompt);
47
101
  }
48
102
  export function hasWebPageIntent(prompt) {
49
103
  const text = stripExecutionShaping(prompt);
@@ -72,8 +126,57 @@ export function isTrivialHtmlPageRequest(prompt) {
72
126
  export function hasCloneBuildIntent(prompt) {
73
127
  return CLONE_BUILD.test(stripExecutionShaping(prompt)) && hasBuildVerb(prompt);
74
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
+ }
75
169
  export function hasExplicitWriteIntent(prompt) {
76
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
+ }
77
180
  return (BUILD_VERBS.test(text) && ARTIFACT_NOUNS.test(text))
78
181
  || hasCloneBuildIntent(prompt)
79
182
  || (BUILD_VERBS.test(text) && hasGameIntent(prompt));
@@ -81,13 +184,41 @@ export function hasExplicitWriteIntent(prompt) {
81
184
  export function taskRequiresWorkspaceChanges(prompt) {
82
185
  const text = String(prompt || '').trim();
83
186
  const shaped = stripExecutionShaping(text);
84
- const readOnlyIntent = READ_ONLY_INTENT.test(shaped);
187
+ const readOnlyIntent = READ_ONLY_INTENT.test(shaped) || isReadOnlyCheckIntent(text);
85
188
  const explicitWriteIntent = hasExplicitWriteIntent(text);
86
189
  if (readOnlyIntent && !explicitWriteIntent) {
87
190
  return false;
88
191
  }
89
192
  return explicitWriteIntent;
90
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
+ }
91
222
  export function hasAnalyzeAndBuildIntent(prompt) {
92
223
  const text = stripExecutionShaping(prompt);
93
224
  return /\b(analy[sz]e|analyse)\b/i.test(text)
@@ -96,6 +227,21 @@ export function hasAnalyzeAndBuildIntent(prompt) {
96
227
  }
97
228
  export function inferAgentTaskType(prompt) {
98
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
+ }
99
245
  if (hasAnalyzeAndBuildIntent(prompt)) {
100
246
  if (hasGameIntent(prompt)) {
101
247
  return 'game-build';
@@ -105,7 +251,10 @@ export function inferAgentTaskType(prompt) {
105
251
  }
106
252
  return 'implementation';
107
253
  }
108
- if (/\b(inspect|analyze|analyse|audit|review|find|diagnose|debug|trace|compare|diff|check|investigate)\b/i.test(text)
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)
109
258
  && !hasExplicitWriteIntent(prompt)) {
110
259
  return 'debugging';
111
260
  }
@@ -122,7 +271,31 @@ export function inferAgentTaskType(prompt) {
122
271
  }
123
272
  return 'implementation';
124
273
  }
125
- return 'analysis';
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));
126
299
  }
127
300
  export function shouldSkipAnalysisRescue(liveOutcome) {
128
301
  if (liveOutcome.requiresWorkspaceChanges) {
@@ -161,22 +334,36 @@ export function resolvePlannerAgentTimeoutMs(baseTimeoutMs, agentTaskType, promp
161
334
  return Math.max(baseTimeoutMs, extended);
162
335
  }
163
336
  /** V3 workflow mode: suppress write path when the user did not ask for file changes. */
164
- export function resolveWorkflowType(agentTaskType, prompt) {
337
+ export function resolveWorkflowType(agentTaskType, prompt, context = {}) {
338
+ if (isBuiltWriteConfirmationPrompt(prompt) || isWritePermissionGrant(prompt)) {
339
+ return 'full';
340
+ }
165
341
  if (hasAnalyzeAndBuildIntent(prompt)) {
166
342
  return 'full';
167
343
  }
168
- if (!taskRequiresWorkspaceChanges(prompt)) {
344
+ if (isBuiltContinuePrompt(prompt) || isBuiltRetryPrompt(prompt)) {
345
+ return taskRequiresWorkspaceChangesWithContext(prompt, context) ? 'full' : 'analysis_only';
346
+ }
347
+ const prior = String(context.priorPrompt || '').trim();
348
+ if (prior && isConfirmationFollowUp(prompt)) {
349
+ return 'full';
350
+ }
351
+ if (!taskRequiresWorkspaceChangesWithContext(prompt, context)) {
169
352
  return 'analysis_only';
170
353
  }
171
354
  const kind = String(agentTaskType || '').toLowerCase();
172
- if (kind === 'analysis' || kind === 'debugging' || kind === 'verification') {
355
+ if ((kind === 'analysis' || kind === 'debugging' || kind === 'verification')
356
+ && !hasFixOrCorrectIntent(prompt)
357
+ && !isWritePermissionGrant(prompt)) {
173
358
  return 'analysis_only';
174
359
  }
175
360
  return 'full';
176
361
  }
177
362
  export function buildExecutionHints(agentTaskType, prompt, options = {}) {
178
- const workflowType = resolveWorkflowType(agentTaskType, prompt);
179
- const kind = String(agentTaskType || 'general').toLowerCase();
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();
180
367
  const hints = {
181
368
  task_kind: kind,
182
369
  requires_file_changes: workflowType === 'analysis_only' ? false : undefined,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigthoria-cli",
3
- "version": "1.11.27",
3
+ "version": "1.11.29",
4
4
  "description": "Vigthoria Coder CLI - AI-powered terminal coding assistant",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -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
- assert ('vigthoria-balanced-4b' in ids) or ('vigthoria-balanced-4b:latest' in ids) or ('vigthoria-v3-balanced-4b' in ids) or ('vigthoria-v3-balanced-4b:latest' in ids)
101
- assert 'vigthoria-creative-9b-v4' in ids
102
- assert any('vigthoria-v3-code-35b' in i for i in ids)
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')