vigthoria-cli 1.13.20 → 1.13.22
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/completions/_vigthoria +49 -0
- package/completions/vigthoria.bash +6 -0
- package/completions/vigthoria.fish +41 -0
- package/dist/commands/auth.d.ts +1 -0
- package/dist/commands/auth.js +63 -128
- package/dist/commands/background.d.ts +0 -2
- package/dist/commands/background.js +26 -34
- package/dist/commands/bridge.js +45 -41
- package/dist/commands/cancel.d.ts +1 -0
- package/dist/commands/cancel.js +22 -16
- package/dist/commands/chat.d.ts +3 -42
- package/dist/commands/chat.js +378 -1123
- package/dist/commands/coding-registration.d.ts +4 -0
- package/dist/commands/coding-registration.js +24 -0
- package/dist/commands/config.js +23 -29
- package/dist/commands/conversation-registration.d.ts +4 -0
- package/dist/commands/conversation-registration.js +122 -0
- package/dist/commands/deploy.d.ts +8 -2
- package/dist/commands/deploy.js +92 -90
- package/dist/commands/device.d.ts +1 -2
- package/dist/commands/device.js +37 -21
- package/dist/commands/doctor-registration.d.ts +4 -0
- package/dist/commands/doctor-registration.js +200 -0
- package/dist/commands/edit.js +22 -33
- package/dist/commands/explain.js +7 -10
- package/dist/commands/fork.d.ts +1 -2
- package/dist/commands/fork.js +72 -22
- package/dist/commands/game.d.ts +1 -1
- package/dist/commands/game.js +17 -6
- package/dist/commands/generate.js +7 -9
- package/dist/commands/history.js +11 -7
- package/dist/commands/hub.d.ts +2 -2
- package/dist/commands/hub.js +34 -35
- package/dist/commands/legion.d.ts +0 -1
- package/dist/commands/legion.js +62 -79
- package/dist/commands/music.d.ts +1 -2
- package/dist/commands/music.js +62 -27
- package/dist/commands/platform-registration.d.ts +4 -0
- package/dist/commands/platform-registration.js +45 -0
- package/dist/commands/preview.d.ts +0 -1
- package/dist/commands/preview.js +82 -35
- package/dist/commands/product-run-registration.d.ts +4 -0
- package/dist/commands/product-run-registration.js +490 -0
- package/dist/commands/replay.d.ts +1 -2
- package/dist/commands/replay.js +15 -11
- package/dist/commands/repo.d.ts +14 -7
- package/dist/commands/repo.js +245 -196
- package/dist/commands/review.js +9 -12
- package/dist/commands/security.d.ts +1 -2
- package/dist/commands/security.js +12 -6
- package/dist/commands/update-registration.d.ts +3 -0
- package/dist/commands/update-registration.js +338 -0
- package/dist/commands/v4-registration.d.ts +4 -0
- package/dist/commands/v4-registration.js +24 -0
- package/dist/commands/v4.d.ts +1 -1
- package/dist/commands/v4.js +46 -8
- package/dist/commands/wallet.d.ts +1 -3
- package/dist/commands/wallet.js +18 -45
- package/dist/commands/workflow.d.ts +2 -0
- package/dist/commands/workflow.js +52 -39
- package/dist/index.js +122 -1589
- package/dist/utils/agent-stream-state.d.ts +27 -0
- package/dist/utils/agent-stream-state.js +93 -0
- package/dist/utils/agentRoute.d.ts +2 -1
- package/dist/utils/agentRoute.js +22 -9
- package/dist/utils/agentRunOutcome.d.ts +16 -0
- package/dist/utils/agentRunOutcome.js +37 -2
- package/dist/utils/api-client-factory.d.ts +8 -0
- package/dist/utils/api-client-factory.js +8 -0
- package/dist/utils/api.d.ts +44 -266
- package/dist/utils/api.js +647 -3180
- package/dist/utils/auth-session.d.ts +41 -0
- package/dist/utils/auth-session.js +224 -0
- package/dist/utils/brain-hub-client.d.ts +0 -4
- package/dist/utils/brain-hub-client.js +6 -3
- package/dist/utils/bridge-client.js +2 -0
- package/dist/utils/capability-health-service.d.ts +48 -0
- package/dist/utils/capability-health-service.js +64 -0
- package/dist/utils/chat-prompt-policy.d.ts +18 -0
- package/dist/utils/chat-prompt-policy.js +61 -0
- package/dist/utils/chat-tool-call-parser.d.ts +11 -0
- package/dist/utils/chat-tool-call-parser.js +71 -0
- package/dist/utils/cli-state.d.ts +22 -4
- package/dist/utils/cli-state.js +105 -47
- package/dist/utils/code-operations-service.d.ts +104 -0
- package/dist/utils/code-operations-service.js +837 -0
- package/dist/utils/codebase-indexer.d.ts +1 -0
- package/dist/utils/codebase-indexer.js +28 -8
- package/dist/utils/command-contract.d.ts +61 -0
- package/dist/utils/command-contract.js +149 -0
- package/dist/utils/command-policy.d.ts +10 -0
- package/dist/utils/command-policy.js +54 -0
- package/dist/utils/config.d.ts +13 -5
- package/dist/utils/config.js +220 -94
- package/dist/utils/context-ranker.js +11 -2
- package/dist/utils/context-state.d.ts +39 -0
- package/dist/utils/context-state.js +52 -0
- package/dist/utils/deckEvents.d.ts +0 -5
- package/dist/utils/deckEvents.js +3 -2
- package/dist/utils/desktop-bridge-client.js +2 -0
- package/dist/utils/direct-output-policy.d.ts +3 -0
- package/dist/utils/direct-output-policy.js +76 -0
- package/dist/utils/durable-json.d.ts +57 -0
- package/dist/utils/durable-json.js +319 -0
- package/dist/utils/fastAgentRouter.d.ts +5 -6
- package/dist/utils/fastAgentRouter.js +17 -25
- package/dist/utils/files.js +21 -13
- package/dist/utils/frontend-preview-service.d.ts +76 -0
- package/dist/utils/frontend-preview-service.js +482 -0
- package/dist/utils/goaEvents.d.ts +0 -4
- package/dist/utils/goaEvents.js +5 -2
- package/dist/utils/logger.d.ts +2 -1
- package/dist/utils/logger.js +27 -20
- package/dist/utils/mcp-context-client.d.ts +24 -0
- package/dist/utils/mcp-context-client.js +58 -0
- package/dist/utils/model-governance.d.ts +18 -0
- package/dist/utils/model-governance.js +75 -0
- package/dist/utils/model-transport-service.d.ts +44 -0
- package/dist/utils/model-transport-service.js +111 -0
- package/dist/utils/mutation-journal.d.ts +42 -0
- package/dist/utils/mutation-journal.js +387 -0
- package/dist/utils/network-policy.d.ts +24 -0
- package/dist/utils/network-policy.js +202 -0
- package/dist/utils/operator-client.d.ts +45 -0
- package/dist/utils/operator-client.js +159 -0
- package/dist/utils/post-write-validator.js +7 -9
- package/dist/utils/preview-screenshot-adapter.d.ts +13 -0
- package/dist/utils/preview-screenshot-adapter.js +36 -0
- package/dist/utils/process-policy.d.ts +23 -0
- package/dist/utils/process-policy.js +284 -0
- package/dist/utils/project-memory.d.ts +13 -3
- package/dist/utils/project-memory.js +70 -17
- package/dist/utils/prompt-workspace-resolver.d.ts +14 -0
- package/dist/utils/prompt-workspace-resolver.js +64 -0
- package/dist/utils/release-install.d.ts +11 -0
- package/dist/utils/release-install.js +155 -0
- package/dist/utils/release-policy.d.ts +64 -0
- package/dist/utils/release-policy.js +293 -0
- package/dist/utils/requestIntent.js +22 -3
- package/dist/utils/runtime-capability.d.ts +8 -0
- package/dist/utils/runtime-capability.js +16 -0
- package/dist/utils/secret-policy.d.ts +20 -0
- package/dist/utils/secret-policy.js +160 -0
- package/dist/utils/session.d.ts +17 -3
- package/dist/utils/session.js +354 -110
- package/dist/utils/tool-approval-service.d.ts +17 -0
- package/dist/utils/tool-approval-service.js +78 -0
- package/dist/utils/tool-capability-providers.d.ts +57 -0
- package/dist/utils/tool-capability-providers.js +57 -0
- package/dist/utils/tools.d.ts +2 -23
- package/dist/utils/tools.js +216 -508
- package/dist/utils/update-policy.d.ts +18 -0
- package/dist/utils/update-policy.js +137 -0
- package/dist/utils/v3-agent-client.d.ts +40 -0
- package/dist/utils/v3-agent-client.js +165 -0
- package/dist/utils/v3-workspace-service.d.ts +28 -0
- package/dist/utils/v3-workspace-service.js +279 -0
- package/dist/utils/vigflow-client.d.ts +95 -0
- package/dist/utils/vigflow-client.js +105 -0
- package/dist/utils/workspace-boundary.d.ts +45 -0
- package/dist/utils/workspace-boundary.js +377 -0
- package/dist/utils/workspace-brain-service.d.ts +2 -0
- package/dist/utils/workspace-brain-service.js +11 -1
- package/dist/utils/workspace-cache.js +37 -15
- package/dist/utils/workspace-stream.js +35 -10
- package/install.ps1 +85 -142
- package/install.sh +75 -80
- package/package.json +43 -9
- package/release-policy.json +43 -0
- package/scripts/release/LOCAL_MACHINE_USER_VERIFICATION.md +1 -1
- package/scripts/release/generate-completions.mjs +24 -0
- package/scripts/release/install-release.mjs +10 -0
- package/scripts/release/publish-cli-release.mjs +116 -0
- package/scripts/release/publish-cli-release.sh +2 -70
- package/scripts/release/resolve-release-manifest.mjs +26 -0
- package/scripts/release/test-balanced-model-live.sh +42 -0
- package/scripts/release/validate-live-service-gates.sh +90 -0
- package/scripts/release/validate-no-go-gates.sh +135 -174
- package/scripts/release/verify-runtime-consistency.mjs +4 -1
- package/dist/commands/index.d.ts +0 -12
- package/dist/commands/index.js +0 -194
- package/dist/commands/update.d.ts +0 -9
- package/dist/commands/update.js +0 -301
- package/dist/utils/templateInstantPath.d.ts +0 -18
- package/dist/utils/templateInstantPath.js +0 -228
package/dist/commands/chat.js
CHANGED
|
@@ -5,26 +5,34 @@ import * as path from 'path';
|
|
|
5
5
|
import * as readline from 'readline';
|
|
6
6
|
import { randomUUID } from 'crypto';
|
|
7
7
|
import { createSpinner } from '../utils/logger.js';
|
|
8
|
-
import {
|
|
8
|
+
import { CLIError, classifyError, formatCLIError, sanitizeUserFacingErrorText, sanitizeUserFacingPathText, propagateError, VIGTHORIA_SERVER_TEMPORARILY_UNAVAILABLE_MESSAGE } from '../utils/api.js';
|
|
9
|
+
import { createAPIClient } from '../utils/api-client-factory.js';
|
|
9
10
|
import { AgenticTools, robustifyStreamResponse } from '../utils/tools.js';
|
|
10
|
-
import { SessionManager } from '../utils/session.js';
|
|
11
|
+
import { SessionManager, assertAgentStatusTransition } from '../utils/session.js';
|
|
11
12
|
import { BridgeClient, getBridgeClient } from '../utils/bridge-client.js';
|
|
12
13
|
import { WorkspaceWatcher } from '../utils/workspace-stream.js';
|
|
13
14
|
import { TaskDisplay } from '../utils/task-display.js';
|
|
14
15
|
import { ProjectMemoryService } from '../utils/project-memory.js';
|
|
15
16
|
import { WorkspaceBrainService } from '../utils/workspace-brain-service.js';
|
|
17
|
+
import { assembleContextState, contextLayer } from '../utils/context-state.js';
|
|
16
18
|
import { buildPersonaOverlay, normalizePersonaMode } from '../utils/persona.js';
|
|
17
19
|
import { runAgentSessionMenu, shouldShowAgentSessionMenu } from './agent-session-menu.js';
|
|
18
20
|
import { V4Command } from './v4.js';
|
|
19
21
|
import { renderDynamicHelp } from '../utils/command-menu.js';
|
|
20
|
-
import {
|
|
22
|
+
import { resolvePromptWorkspace } from '../utils/prompt-workspace-resolver.js';
|
|
23
|
+
import { isDirectModeFollowUpQuestion, sanitizeDirectModeOutput, stripHiddenThoughtBlocks } from '../utils/direct-output-policy.js';
|
|
24
|
+
import { createLiveOutcome, evaluateExecutorSuccess, handleRunCompleteEvent, handleTaskEvent, isSubstantiveAgentAnswer, isToolEvidenceStubAnswer, normalizeAgentAnswerContent, noteAnalysisToolUse, } from '../utils/agentRunOutcome.js';
|
|
21
25
|
import { looksLikeMarkdownReport, renderMarkdownToTerminal, summarizeMarkdownReport, } from '../utils/terminalMarkdown.js';
|
|
22
26
|
import { emitDeckEvent, isDeckModeEnabled } from '../utils/deckEvents.js';
|
|
23
27
|
import { formatGoaSystemGrounding, splitGoaContextFromInput, } from '../utils/goaEvents.js';
|
|
24
|
-
import {
|
|
28
|
+
import { inferAgentTaskTypeWithContext, resolvePlannerAgentTimeoutMs, resolveWorkflowType, shouldSkipAnalysisRescue, taskRequiresWorkspaceChanges as promptRequiresWorkspaceChanges, taskRequiresWorkspaceChangesWithContext, buildExecutionHints, isAgentContinuePrompt, isAgentRetryPrompt, isBuiltContinuePrompt, isBuiltRetryPrompt, isBuiltWriteConfirmationPrompt, isConfirmationFollowUp, isWritePermissionGrant, } from '../utils/requestIntent.js';
|
|
25
29
|
import { resolveAgentRoute } from '../utils/agentRoute.js';
|
|
26
|
-
import { runTemplateInstantPath } from '../utils/templateInstantPath.js';
|
|
27
30
|
import { isV3StreamKeepaliveEvent } from '../utils/v3-stream-events.js';
|
|
31
|
+
import { resolveAutoApproval } from '../utils/process-policy.js';
|
|
32
|
+
import { ChatToolCallParser } from '../utils/chat-tool-call-parser.js';
|
|
33
|
+
import { ChatPromptPolicy } from '../utils/chat-prompt-policy.js';
|
|
34
|
+
import { AgentStreamState } from '../utils/agent-stream-state.js';
|
|
35
|
+
import { CliCommandError, commandFailure, commandNameFromArgv, failureEnvelope, normalizeCommandError, successEnvelope, } from '../utils/command-contract.js';
|
|
28
36
|
// Stream health policy (corrected 2026-07-04 after TestFarm incident with
|
|
29
37
|
// Gideon Lenz / C:\vigthoria\Apps\monopoly): agentic tasks have no
|
|
30
38
|
// predictable duration, so there is intentionally NO overall wall-clock
|
|
@@ -63,7 +71,46 @@ const DEFAULT_V3_AGENT_SOFT_TIMEOUT_MS = (() => {
|
|
|
63
71
|
})();
|
|
64
72
|
/** Block-write agent JSON to VIGTHORIA_AGENT_OUTPUT (Windows KVM fsync path). */
|
|
65
73
|
function emitAgentJsonOutput(payload) {
|
|
66
|
-
const
|
|
74
|
+
const command = commandNameFromArgv(process.argv);
|
|
75
|
+
const record = payload && typeof payload === 'object' && !Array.isArray(payload)
|
|
76
|
+
? { ...payload }
|
|
77
|
+
: { value: payload };
|
|
78
|
+
const succeeded = record.success !== false;
|
|
79
|
+
delete record.success;
|
|
80
|
+
let envelope;
|
|
81
|
+
if (succeeded) {
|
|
82
|
+
const metadata = record.metadata && typeof record.metadata === 'object' && !Array.isArray(record.metadata)
|
|
83
|
+
? record.metadata
|
|
84
|
+
: {};
|
|
85
|
+
const warnings = Array.isArray(record.warnings)
|
|
86
|
+
? record.warnings.filter((warning) => typeof warning === 'string')
|
|
87
|
+
: [];
|
|
88
|
+
delete record.metadata;
|
|
89
|
+
delete record.warnings;
|
|
90
|
+
envelope = successEnvelope(command, record, metadata, warnings);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
const message = typeof record.error === 'string' && record.error.trim()
|
|
94
|
+
? record.error
|
|
95
|
+
: 'Agent execution failed.';
|
|
96
|
+
const category = record.errorCategory === 'auth'
|
|
97
|
+
? 'authentication'
|
|
98
|
+
: record.errorCategory === 'network' || record.errorCategory === 'timeout'
|
|
99
|
+
? 'network'
|
|
100
|
+
: 'execution';
|
|
101
|
+
const partialMutation = record.partialMutation === true || record.partialMutation === 'unknown' || record.partial === true
|
|
102
|
+
? 'unknown'
|
|
103
|
+
: false;
|
|
104
|
+
delete record.error;
|
|
105
|
+
delete record.errorCategory;
|
|
106
|
+
envelope = failureEnvelope(command, normalizeCommandError(new CliCommandError(message, {
|
|
107
|
+
code: command === 'operator' ? 'OPERATOR_EXECUTION_FAILED' : command === 'agent' ? 'AGENT_EXECUTION_FAILED' : 'CHAT_EXECUTION_FAILED',
|
|
108
|
+
category: partialMutation === 'unknown' ? 'partial_mutation' : category,
|
|
109
|
+
partialMutation,
|
|
110
|
+
details: record,
|
|
111
|
+
})));
|
|
112
|
+
}
|
|
113
|
+
const text = `${JSON.stringify(envelope, null, 2)}\n`;
|
|
67
114
|
console.log(text.trimEnd());
|
|
68
115
|
const outPath = process.env.VIGTHORIA_AGENT_OUTPUT?.trim();
|
|
69
116
|
if (!outPath) {
|
|
@@ -100,6 +147,8 @@ export class ChatCommand {
|
|
|
100
147
|
modelExplicitlySelected = false;
|
|
101
148
|
autoApprove = false;
|
|
102
149
|
personaOverride = null;
|
|
150
|
+
toolCallParser = new ChatToolCallParser();
|
|
151
|
+
promptPolicy;
|
|
103
152
|
// Phase 5: Agent quality gate — track tool usage for evidence thresholds
|
|
104
153
|
agentToolEvidence = { discovery: 0, mutation: 0, searchFailed: 0 };
|
|
105
154
|
operatorMode = false;
|
|
@@ -109,12 +158,7 @@ export class ChatCommand {
|
|
|
109
158
|
modelGovernanceFallback = null;
|
|
110
159
|
retryPromptSignature = null;
|
|
111
160
|
retryPromptStreak = 0;
|
|
112
|
-
|
|
113
|
-
v3StreamedAnswerBuffer = '';
|
|
114
|
-
v3StreamedAnswerDisplayed = false;
|
|
115
|
-
v3SeenToolCalls = new Set();
|
|
116
|
-
v3SeenToolResults = new Set();
|
|
117
|
-
v3StartSeen = false;
|
|
161
|
+
streamState;
|
|
118
162
|
lastAgentRoute = null;
|
|
119
163
|
pendingGoaContext = null;
|
|
120
164
|
// Last completed Agent run — used by /retry, /continue, and the final summary block.
|
|
@@ -373,16 +417,6 @@ export class ChatCommand {
|
|
|
373
417
|
routeReason: 'dispatcher-eligible-agent-default',
|
|
374
418
|
};
|
|
375
419
|
}
|
|
376
|
-
if (this.lastAgentRoute?.path === 'template-instant') {
|
|
377
|
-
return {
|
|
378
|
-
selectedModel: 'agent',
|
|
379
|
-
explicitModel: false,
|
|
380
|
-
heavyTask,
|
|
381
|
-
cloudEligible,
|
|
382
|
-
cloudSelected: false,
|
|
383
|
-
routeReason: `template-instant [${this.lastAgentRoute.source}]`,
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
420
|
if (requiresV3Workflow) {
|
|
387
421
|
return {
|
|
388
422
|
selectedModel: 'agent',
|
|
@@ -457,131 +491,37 @@ export class ChatCommand {
|
|
|
457
491
|
return buildPersonaOverlay(this.getActivePersonaMode(), this.getLastUserPrompt());
|
|
458
492
|
}
|
|
459
493
|
isDiagnosticPrompt(prompt) {
|
|
460
|
-
|
|
461
|
-
return false;
|
|
462
|
-
return /(startup|start up|won'?t start|doesn'?t start|crash|crashes|error|errors|failing|fails|issue|issues|bug|bugs|diagnos|debug|runtime|log|logs|exception|traceback|stack trace|yaml|blocking|blocker)/i.test(prompt);
|
|
494
|
+
return this.promptPolicy.isDiagnostic(prompt);
|
|
463
495
|
}
|
|
464
496
|
/**
|
|
465
497
|
* Returns true when the prompt is a simple lookup / analysis / read-only
|
|
466
498
|
* question — these should use analysis_only workflow, not full_autonomy.
|
|
467
499
|
*/
|
|
468
|
-
isAnalysisLookupPrompt(prompt) {
|
|
469
|
-
if (promptRequiresWorkspaceChanges(prompt))
|
|
470
|
-
return false;
|
|
471
|
-
const trimmed = prompt.trim();
|
|
472
|
-
if (/^(what|which|where|how many|who|find|list|show|check|inspect|analyze|analyse|audit|explain|describe|summarize|summarise|review|overview|count|read|look at|tell me|locate|search for|does .* exist)/i.test(trimmed)) {
|
|
473
|
-
return true;
|
|
474
|
-
}
|
|
475
|
-
// Mid-conversation accountability / lookup questions (Gideon regression)
|
|
476
|
-
return /\b(where\s+(?:is|are)|what\s+(?:is|are|have|did|was|were)|how\s+(?:do|does|is|are)|what\s+(?:have\s+)?you\s+(?:done|changed|built)|what\s+(?:is|was)\s+(?:your|the)\s+answer)\b/i.test(trimmed);
|
|
477
|
-
}
|
|
478
|
-
extractExplicitLocalPath(prompt) {
|
|
479
|
-
// Try to extract Windows paths (C:\ D:\ etc.)
|
|
480
|
-
// Match drive letter followed by colon and path
|
|
481
|
-
const windowsMatch = prompt.match(/([A-Za-z]:[\\\/][^\s<>"|?*]*)/);
|
|
482
|
-
if (windowsMatch) {
|
|
483
|
-
const candidatePath = windowsMatch[1].replace(/\\/g, '\\').trim();
|
|
484
|
-
try {
|
|
485
|
-
const resolved = require('path').resolve(candidatePath);
|
|
486
|
-
const fs = require('fs');
|
|
487
|
-
if (fs.existsSync(resolved)) {
|
|
488
|
-
return resolved;
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
catch {
|
|
492
|
-
// Path resolution failed
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
// Try to extract Unix paths: / followed by any characters until we hit quotes, angle brackets, pipes, or sentence boundaries
|
|
496
|
-
// Supports spaces, special chars, camelCase, etc. (anything a filesystem path might have)
|
|
497
|
-
if (!prompt.includes('http://') && !prompt.includes('https://') && !prompt.includes('ftp://')) {
|
|
498
|
-
// Match / and everything after it until we hit delimiters or common sentence words
|
|
499
|
-
const unixMatch = prompt.match(/(\/(?:[^\s<>"|?*]|\s+(?![^\s<>"|?*]))*)/);
|
|
500
|
-
if (unixMatch) {
|
|
501
|
-
let candidatePath = unixMatch[1].trim();
|
|
502
|
-
// Remove trailing common English words that aren't part of path
|
|
503
|
-
candidatePath = candidatePath.replace(/\s+(and|or|at|in|the|to|for|with|from|by|on)$/i, '').trim();
|
|
504
|
-
// Remove trailing punctuation
|
|
505
|
-
candidatePath = candidatePath.replace(/[.,;!?:)\]]*$/, '').trim();
|
|
506
|
-
if (candidatePath.length > 1) {
|
|
507
|
-
try {
|
|
508
|
-
const resolved = require('path').resolve(candidatePath);
|
|
509
|
-
const fs = require('fs');
|
|
510
|
-
if (fs.existsSync(resolved)) {
|
|
511
|
-
return resolved;
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
catch {
|
|
515
|
-
// Path resolution failed
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
return null;
|
|
521
|
-
}
|
|
522
|
-
isUnscopedPromptPathOverrideAllowed() {
|
|
523
|
-
return /^(1|true|yes)$/i.test(String(process.env.VIGTHORIA_ALLOW_UNSCOPED_PROMPT_PATHS || ''));
|
|
524
|
-
}
|
|
525
|
-
isPathWithinRoot(candidatePath, rootPath) {
|
|
526
|
-
const candidate = path.resolve(candidatePath);
|
|
527
|
-
const root = path.resolve(rootPath);
|
|
528
|
-
const rel = path.relative(root, candidate);
|
|
529
|
-
return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
|
|
530
|
-
}
|
|
531
|
-
getPromptPathAllowedRoots(baseWorkspace) {
|
|
532
|
-
const roots = new Set();
|
|
533
|
-
const addRoot = (rawValue) => {
|
|
534
|
-
const value = String(rawValue || '').trim();
|
|
535
|
-
if (!value)
|
|
536
|
-
return;
|
|
537
|
-
const resolved = path.resolve(value);
|
|
538
|
-
try {
|
|
539
|
-
if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {
|
|
540
|
-
roots.add(resolved);
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
catch {
|
|
544
|
-
// ignore invalid or unreadable roots
|
|
545
|
-
}
|
|
546
|
-
};
|
|
547
|
-
addRoot(baseWorkspace);
|
|
548
|
-
addRoot(this.currentProjectPath);
|
|
549
|
-
addRoot(process.cwd());
|
|
550
|
-
addRoot(this.config.get('project')?.rootPath || null);
|
|
551
|
-
const envRootsRaw = String(process.env.VIGTHORIA_ALLOWED_WORKSPACE_ROOTS || '').trim();
|
|
552
|
-
if (envRootsRaw) {
|
|
553
|
-
for (const entry of envRootsRaw.split(path.delimiter)) {
|
|
554
|
-
addRoot(entry);
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
return Array.from(roots);
|
|
558
|
-
}
|
|
559
500
|
resolvePromptWorkspacePath(prompt, baseWorkspace) {
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
const displayRoots = allowedRoots.map((root) => root.replace(/\\/g, '/')).join(', ');
|
|
501
|
+
const envRoots = String(process.env.VIGTHORIA_ALLOWED_WORKSPACE_ROOTS || '')
|
|
502
|
+
.split(path.delimiter)
|
|
503
|
+
.map((entry) => entry.trim())
|
|
504
|
+
.filter(Boolean);
|
|
505
|
+
const result = resolvePromptWorkspace(prompt, {
|
|
506
|
+
baseWorkspace,
|
|
507
|
+
currentProjectPath: this.currentProjectPath,
|
|
508
|
+
cwd: process.cwd(),
|
|
509
|
+
configuredProjectRoot: this.config.get('project')?.rootPath || null,
|
|
510
|
+
additionalRoots: envRoots,
|
|
511
|
+
allowUnscoped: /^(1|true|yes)$/i.test(String(process.env.VIGTHORIA_ALLOW_UNSCOPED_PROMPT_PATHS || '')),
|
|
512
|
+
});
|
|
513
|
+
if (result.rejectedPath && !this.jsonOutput) {
|
|
514
|
+
console.log(chalk.yellow(`Ignoring path outside allowed workspace roots: ${result.rejectedPath}`));
|
|
515
|
+
if (result.allowedRoots.length > 0) {
|
|
516
|
+
const displayRoots = result.allowedRoots.map((root) => root.replace(/\\/g, '/')).join(', ');
|
|
577
517
|
console.log(chalk.gray(`Allowed roots: ${displayRoots}`));
|
|
578
518
|
}
|
|
579
519
|
console.log(chalk.gray('To allow unrestricted prompt path overrides, set VIGTHORIA_ALLOW_UNSCOPED_PROMPT_PATHS=1.'));
|
|
580
520
|
}
|
|
581
|
-
return
|
|
521
|
+
return result.resolvedPath;
|
|
582
522
|
}
|
|
583
523
|
isBrowserTaskPrompt(prompt) {
|
|
584
|
-
return
|
|
524
|
+
return this.promptPolicy.isBrowserTask(prompt);
|
|
585
525
|
}
|
|
586
526
|
/**
|
|
587
527
|
* Returns true when a prompt can be answered directly without the full
|
|
@@ -590,28 +530,7 @@ export class ChatCommand {
|
|
|
590
530
|
* file scanning, or infrastructure changes.
|
|
591
531
|
*/
|
|
592
532
|
isOperatorDirectAnswerable(prompt) {
|
|
593
|
-
|
|
594
|
-
// Infrastructure status pings are never repo-grounded analysis.
|
|
595
|
-
if (/^(status(\s+check)?|health(\s+check)?|ping)$/i.test(trimmed)) {
|
|
596
|
-
return true;
|
|
597
|
-
}
|
|
598
|
-
// ── Exclusion guard: any prompt that references code artefacts,
|
|
599
|
-
// file paths, or analysis verbs MUST go through a tool-backed path
|
|
600
|
-
// (BMAD or agent loop), never the toolless direct-answer shortcut.
|
|
601
|
-
const repoGrounded = /\b(src\/|\.js\b|\.ts\b|\.py\b|\.jsx\b|\.tsx\b|\.css\b|\.html\b|\.json\b|\.yaml\b|\.yml\b)/i.test(trimmed)
|
|
602
|
-
|| /\b(file|folder|directory|module|class|function|method|variable|handler|listener|binding|conflict|bug|issue|error)\b/i.test(trimmed)
|
|
603
|
-
|| /\b(inspect|analyze|analyse|audit|review|find|diagnose|debug|trace|compare|diff|check|investigate)\b/i.test(trimmed)
|
|
604
|
-
|| /\b(Camera|InputManager|keydown|KeyS|KeyA|KeyW|stopPropagation|addEventListener|handleKeyDown)\b/.test(trimmed)
|
|
605
|
-
|| /\/[a-zA-Z]/.test(trimmed) // slash-prefixed paths
|
|
606
|
-
|| /[A-Z][a-z]+\.[a-z_][a-zA-Z]+/.test(trimmed) // ClassName.methodName
|
|
607
|
-
|| /\b(key\s*code|keydown|keyup|key\s*bind|event\s*listener|event\s*handler|addEventListener|handleKey|onKey)\b/i.test(trimmed);
|
|
608
|
-
if (repoGrounded)
|
|
609
|
-
return false;
|
|
610
|
-
// Only truly trivial prompts are direct-answerable: short echo/smoke
|
|
611
|
-
// prompts and factual questions with no code or repo reference.
|
|
612
|
-
const isTrivialEcho = trimmed.length < 120
|
|
613
|
-
&& /^(reply with|say|echo|respond with|return|ping|hello|hi|test|status|what is \d|how much is)\b/i.test(trimmed);
|
|
614
|
-
return isTrivialEcho;
|
|
533
|
+
return this.promptPolicy.isOperatorDirectAnswerable(prompt);
|
|
615
534
|
}
|
|
616
535
|
/**
|
|
617
536
|
* Returns true when the prompt references code artefacts, file paths,
|
|
@@ -620,51 +539,10 @@ export class ChatCommand {
|
|
|
620
539
|
* instead of the toolless BMAD/direct-answer path.
|
|
621
540
|
*/
|
|
622
541
|
isRepoGroundedPrompt(prompt) {
|
|
623
|
-
|
|
624
|
-
return /\b(src\/|\.js\b|\.ts\b|\.py\b|\.jsx\b|\.tsx\b|\.css\b|\.html\b|\.json\b)/i.test(trimmed)
|
|
625
|
-
|| /\b(file|folder|module|class|function|method|handler|listener|binding|conflict|bug|error)\b/i.test(trimmed)
|
|
626
|
-
|| /\b(inspect|analyze|analyse|audit|review|find|diagnose|debug|trace|compare|diff|check|investigate)\b/i.test(trimmed)
|
|
627
|
-
|| /\/[a-zA-Z]/.test(trimmed)
|
|
628
|
-
|| /[A-Z][a-z]+\.[a-z_][a-zA-Z]+/.test(trimmed) // ClassName.methodName patterns
|
|
629
|
-
|| /\b(key\s*code|keydown|keyup|key\s*bind|event\s*listener|event\s*handler|addEventListener|handleKey|onKey)\b/i.test(trimmed);
|
|
630
|
-
}
|
|
631
|
-
inferAgentTaskType(prompt) {
|
|
632
|
-
if (this.isDiagnosticPrompt(prompt))
|
|
633
|
-
return 'debugging';
|
|
634
|
-
if (this.isAnalysisLookupPrompt(prompt))
|
|
635
|
-
return 'analysis';
|
|
636
|
-
return sharedInferAgentTaskType(prompt);
|
|
637
|
-
}
|
|
638
|
-
buildTaskShapingInstructions(prompt) {
|
|
639
|
-
const instructions = [];
|
|
640
|
-
const runtime = this.getRuntimeEnvironmentContext();
|
|
641
|
-
// Platform-aware routing hints
|
|
642
|
-
if (runtime.platform === 'windows') {
|
|
643
|
-
instructions.push('Platform: Windows. Use list_dir, glob, read_file, and the grep tool for searching.', 'The grep tool handles Windows automatically — do not use bash to call grep, findstr, or Select-String manually.', 'Do not use bash for Unix commands (cat, head, tail, awk, sed, wc).', 'Use read_file to inspect file contents instead of shell commands.', 'All file paths use forward slashes internally.');
|
|
644
|
-
}
|
|
645
|
-
else if (runtime.platform === 'macos') {
|
|
646
|
-
instructions.push('Platform: macOS. Prefer list_dir, grep, and read_file tools for deterministic results.', 'Use workspace-relative paths and verify file existence before claiming a file is missing.');
|
|
647
|
-
}
|
|
648
|
-
else if (runtime.platform === 'linux') {
|
|
649
|
-
instructions.push('Platform: Linux. Prefer list_dir, grep, and read_file for grounded repository inspection.', 'Use exact file paths from tool output when reporting findings or fixes.');
|
|
650
|
-
}
|
|
651
|
-
if (this.isAnalysisLookupPrompt(prompt)) {
|
|
652
|
-
instructions.push('Read-only analysis mode is active.', 'Inspect the workspace with tools and produce a grounded report of findings, gaps, and blockers.', 'Do not create, modify, or delete files unless the user explicitly asked for changes.');
|
|
653
|
-
}
|
|
654
|
-
if (this.isDiagnosticPrompt(prompt)) {
|
|
655
|
-
instructions.push('Diagnostic mode is active.', 'Treat this as a debugging task, not a generic code review or feature request.', 'Start with concrete evidence: logs, runtime errors, config, launch files, and exact symbol references.', 'If log files exist, inspect them before proposing fixes.', 'Do not claim a file, definition, asset, or symbol is missing until you verify that with tools.', 'If a prior diagnosis mentioned a missing symbol or YAML entry, re-check the actual files before repeating it.', 'Prefer grep plus read_file around the exact references involved in the failure.', 'Separate your reasoning into: Evidence, Confirmed Cause, and Remaining Hypotheses.', 'Do not suggest speculative fixes when the current evidence contradicts them.', 'CRITICAL GROUNDING RULE: Every key name, variable name, symbol, or identifier you mention in your final answer MUST appear verbatim in the tool output you received. If a key/symbol does NOT appear in tool output, you MUST NOT mention it as involved in any conflict or issue.', 'CROSS-FILE ATTRIBUTION: When reporting conflicts between two files, a key/symbol is conflicting ONLY if it appears in BOTH files. Read each file carefully and list only the exact keys that appear in the relevant handler/function of EACH file. Do not assume that because one file handles a key, the other file does too.', 'When reporting conflicts between files, cite the exact file name, line number, and the exact string/key from the tool output. Do not paraphrase or substitute key names.', 'Before concluding, re-check: (1) does every key/symbol in my answer actually appear in the evidence I gathered? (2) for each claimed conflict, did I verify the key appears in BOTH files? If not, correct your answer.');
|
|
656
|
-
}
|
|
657
|
-
if (this.isBrowserTaskPrompt(prompt)) {
|
|
658
|
-
instructions.push('Browser-debug mode is active.', 'Prefer concrete browser evidence such as console errors, network failures, DOM state, and websocket behavior.', 'Use the DevTools Bridge only when the user has explicitly enabled it for this run; otherwise ask for permission before requiring local browser tooling.');
|
|
659
|
-
}
|
|
660
|
-
if (instructions.length === 0) {
|
|
661
|
-
return '';
|
|
662
|
-
}
|
|
663
|
-
return instructions.join('\n');
|
|
542
|
+
return this.promptPolicy.isRepoGrounded(prompt);
|
|
664
543
|
}
|
|
665
544
|
buildExecutionPrompt(prompt) {
|
|
666
|
-
|
|
667
|
-
return shaping ? `${prompt}\n\n${shaping}` : prompt;
|
|
545
|
+
return this.promptPolicy.buildExecutionPrompt(prompt);
|
|
668
546
|
}
|
|
669
547
|
isProjectBrainRuntimeDisabled() {
|
|
670
548
|
return /^(1|true|yes)$/i.test(String(process.env.VIGTHORIA_NO_BRAIN || process.env.VIGTHORIA_BRAIN_DISABLED || ''));
|
|
@@ -689,7 +567,11 @@ export class ChatCommand {
|
|
|
689
567
|
}
|
|
690
568
|
}
|
|
691
569
|
canPromptForWorkspaceIndex() {
|
|
692
|
-
|
|
570
|
+
// A direct `--prompt` invocation is a bounded automation surface even when
|
|
571
|
+
// wrapped in `script`/ConPTY and therefore appears to have a TTY. Likewise,
|
|
572
|
+
// --auto-approve cannot answer an unrelated optional indexing question.
|
|
573
|
+
// Never block these modes on an interactive Brain opt-in prompt.
|
|
574
|
+
if (this.jsonOutput || this.directPromptMode || this.autoApprove || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
693
575
|
return false;
|
|
694
576
|
}
|
|
695
577
|
return !/^(1|true|yes)$/i.test(String(process.env.VIGTHORIA_NO_INDEX_PROMPT || ''));
|
|
@@ -789,22 +671,40 @@ export class ChatCommand {
|
|
|
789
671
|
agentRuntime: this.getRuntimeEnvironmentContext(),
|
|
790
672
|
};
|
|
791
673
|
const brainContext = this.buildProjectBrainRuntimeContext(prompt, this.operatorMode ? 'vigthoria-cli.operator' : this.agentMode ? 'vigthoria-cli.agent' : 'vigthoria-cli.chat');
|
|
792
|
-
if (brainContext) {
|
|
793
|
-
runtimeContext.vigthoriaBrain = brainContext;
|
|
794
|
-
if (typeof brainContext.context === 'string' && brainContext.context.trim()) {
|
|
795
|
-
runtimeContext.projectMemory = brainContext.context;
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
674
|
const workspaceBrainContext = await this.buildWorkspaceBrainRuntimeContext(prompt);
|
|
799
675
|
if (workspaceBrainContext.workspaceBrain) {
|
|
800
676
|
runtimeContext.workspaceBrain = workspaceBrainContext.workspaceBrain;
|
|
801
677
|
}
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
678
|
+
const contextState = assembleContextState({
|
|
679
|
+
...(this.workflowTarget ? { 'workflow-memory': { metadata: { workflowTarget: this.workflowTarget } } } : {}),
|
|
680
|
+
'session-memory': { content: this.sessionManager.buildMemoryContext(this.currentSession) },
|
|
681
|
+
'project-memory': {
|
|
682
|
+
content: typeof brainContext?.context === 'string' ? brainContext.context : '',
|
|
683
|
+
metadata: brainContext ? { ...brainContext, context: undefined } : undefined,
|
|
684
|
+
},
|
|
685
|
+
'codebase-index': {
|
|
686
|
+
content: typeof workspaceBrainContext.codebaseContext === 'string' ? workspaceBrainContext.codebaseContext : '',
|
|
687
|
+
metadata: workspaceBrainContext.workspaceBrain,
|
|
688
|
+
},
|
|
689
|
+
'account-brain': {
|
|
690
|
+
content: typeof workspaceBrainContext.accountBrainContext === 'string' ? workspaceBrainContext.accountBrainContext : '',
|
|
691
|
+
},
|
|
692
|
+
'workspace-hashes': { metadata: { role: 'change-priority-only', semanticAuthority: false } },
|
|
693
|
+
});
|
|
694
|
+
runtimeContext.contextState = contextState;
|
|
695
|
+
// Explicit server-compatibility adapters. These values are derived from
|
|
696
|
+
// contextState and have no independent persistence owner.
|
|
697
|
+
const projectLayer = contextLayer(contextState, 'project-memory');
|
|
698
|
+
const codebaseLayer = contextLayer(contextState, 'codebase-index');
|
|
699
|
+
const accountLayer = contextLayer(contextState, 'account-brain');
|
|
700
|
+
if (projectLayer) {
|
|
701
|
+
runtimeContext.projectMemory = projectLayer.content || '';
|
|
702
|
+
runtimeContext.vigthoriaBrain = { ...(brainContext || {}), context: projectLayer.content || '' };
|
|
703
|
+
}
|
|
704
|
+
if (codebaseLayer?.content)
|
|
705
|
+
runtimeContext.codebaseContext = codebaseLayer.content;
|
|
706
|
+
if (accountLayer?.content)
|
|
707
|
+
runtimeContext.accountBrainContext = accountLayer.content;
|
|
808
708
|
if (!this.isBrowserTaskPrompt(prompt)) {
|
|
809
709
|
return runtimeContext;
|
|
810
710
|
}
|
|
@@ -836,12 +736,6 @@ export class ChatCommand {
|
|
|
836
736
|
devtoolsBridgeEndpoint: bridgeStatus.endpoint,
|
|
837
737
|
};
|
|
838
738
|
}
|
|
839
|
-
v3IterationCount = 0;
|
|
840
|
-
v3ToolCallCount = 0;
|
|
841
|
-
v3LastActivity = Date.now();
|
|
842
|
-
v3IdleWatchInterval = null;
|
|
843
|
-
v3IdleNoticeShown = false;
|
|
844
|
-
v3StreamingStarted = false;
|
|
845
739
|
/**
|
|
846
740
|
* Strip server-internal path prefixes from tool output strings.
|
|
847
741
|
* Prevents exposing paths like /var/www/V3-Code-Agent/... to end users.
|
|
@@ -849,56 +743,13 @@ export class ChatCommand {
|
|
|
849
743
|
sanitizeServerPath(text) {
|
|
850
744
|
if (!text)
|
|
851
745
|
return text;
|
|
852
|
-
return sanitizeUserFacingPathText(
|
|
853
|
-
}
|
|
854
|
-
stripHiddenThoughtBlocks(text) {
|
|
855
|
-
if (!text)
|
|
856
|
-
return text;
|
|
857
|
-
return text
|
|
858
|
-
.replace(/<\|mask_start\|>[\s\S]*?<\|mask_end\|>/g, '')
|
|
859
|
-
.replace(/<think>[\s\S]*?<\/redacted_thinking>/gi, '')
|
|
860
|
-
.replace(/<thinking>[\s\S]*?<\/thinking>/gi, '')
|
|
861
|
-
.replace(/<\/redacted_thinking>/gi, '')
|
|
862
|
-
.replace(/<\/thinking>/gi, '')
|
|
863
|
-
.replace(/<\|(?:mask_start|mask_end)\|>/g, '')
|
|
864
|
-
.trim();
|
|
746
|
+
return sanitizeUserFacingPathText(stripHiddenThoughtBlocks(text));
|
|
865
747
|
}
|
|
866
748
|
startV3IdleWatch(spinner) {
|
|
867
|
-
this.
|
|
868
|
-
this.v3IdleNoticeShown = false;
|
|
869
|
-
if (this.jsonOutput || !spinner) {
|
|
870
|
-
return;
|
|
871
|
-
}
|
|
872
|
-
this.v3IdleWatchInterval = setInterval(() => {
|
|
873
|
-
const idleMs = Date.now() - this.v3LastActivity;
|
|
874
|
-
if (idleMs < 15_000) {
|
|
875
|
-
// Activity resumed — re-arm the notice so a later stall is reported too.
|
|
876
|
-
this.v3IdleNoticeShown = false;
|
|
877
|
-
return;
|
|
878
|
-
}
|
|
879
|
-
if (this.v3IdleNoticeShown) {
|
|
880
|
-
return;
|
|
881
|
-
}
|
|
882
|
-
this.v3IdleNoticeShown = true;
|
|
883
|
-
if (spinner.isSpinning) {
|
|
884
|
-
spinner.stop();
|
|
885
|
-
}
|
|
886
|
-
const seconds = Math.round(idleMs / 1000);
|
|
887
|
-
const idleTimeoutSec = Math.round(DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS / 1000);
|
|
888
|
-
process.stderr.write(chalk.yellow(` [Wait] Model still working (${seconds}s) — this can legitimately take a while for large tasks. ` +
|
|
889
|
-
(idleTimeoutSec > 0
|
|
890
|
-
? `Only complete silence (~${idleTimeoutSec}s with zero bytes received) will trigger an auto-recover/retry — active progress is never interrupted.\n`
|
|
891
|
-
: `\n`)));
|
|
892
|
-
spinner.start();
|
|
893
|
-
spinner.text = 'Waiting for model response...';
|
|
894
|
-
}, 5_000);
|
|
749
|
+
this.streamState.startIdleWatch(spinner);
|
|
895
750
|
}
|
|
896
751
|
stopV3IdleWatch() {
|
|
897
|
-
|
|
898
|
-
clearInterval(this.v3IdleWatchInterval);
|
|
899
|
-
this.v3IdleWatchInterval = null;
|
|
900
|
-
}
|
|
901
|
-
this.v3IdleNoticeShown = false;
|
|
752
|
+
this.streamState.stopIdleWatch();
|
|
902
753
|
}
|
|
903
754
|
describeV3AgentTool(toolName) {
|
|
904
755
|
const normalized = String(toolName || '').toLowerCase();
|
|
@@ -965,7 +816,7 @@ export class ChatCommand {
|
|
|
965
816
|
? (event.body ?? event.stream ?? event.response ?? event)
|
|
966
817
|
: event;
|
|
967
818
|
for await (const chunk of robustifyStreamResponse(source)) {
|
|
968
|
-
this.
|
|
819
|
+
this.streamState.noteActivity();
|
|
969
820
|
if (chunk.type === 'error') {
|
|
970
821
|
if (spinner.isSpinning)
|
|
971
822
|
spinner.stop();
|
|
@@ -992,8 +843,8 @@ export class ChatCommand {
|
|
|
992
843
|
if (!safeText) {
|
|
993
844
|
return;
|
|
994
845
|
}
|
|
995
|
-
if (!this.
|
|
996
|
-
this.
|
|
846
|
+
if (!this.streamState.streamingStarted) {
|
|
847
|
+
this.streamState.streamingStarted = true;
|
|
997
848
|
spinner.stop();
|
|
998
849
|
if (!this.directPromptMode) {
|
|
999
850
|
console.log();
|
|
@@ -1002,27 +853,27 @@ export class ChatCommand {
|
|
|
1002
853
|
else {
|
|
1003
854
|
spinner.stop();
|
|
1004
855
|
}
|
|
1005
|
-
this.
|
|
1006
|
-
spinner.text = looksLikeMarkdownReport(this.
|
|
856
|
+
this.streamState.streamedAnswerBuffer += safeText;
|
|
857
|
+
spinner.text = looksLikeMarkdownReport(this.streamState.streamedAnswerBuffer)
|
|
1007
858
|
? 'Writing analysis report...'
|
|
1008
859
|
: 'Writing response...';
|
|
1009
860
|
}
|
|
1010
861
|
displayV3StreamedAnswer() {
|
|
1011
|
-
if (this.jsonOutput || this.
|
|
862
|
+
if (this.jsonOutput || this.streamState.streamedAnswerDisplayed) {
|
|
1012
863
|
return;
|
|
1013
864
|
}
|
|
1014
|
-
const content = this.
|
|
865
|
+
const content = this.streamState.streamedAnswerBuffer.trim();
|
|
1015
866
|
if (!isSubstantiveAgentAnswer(content)) {
|
|
1016
867
|
return;
|
|
1017
868
|
}
|
|
1018
869
|
emitDeckEvent({ type: 'report_ready', markdown: content });
|
|
1019
870
|
if (isDeckModeEnabled()) {
|
|
1020
|
-
this.
|
|
871
|
+
this.streamState.streamedAnswerDisplayed = true;
|
|
1021
872
|
return;
|
|
1022
873
|
}
|
|
1023
874
|
console.log('');
|
|
1024
875
|
console.log(renderMarkdownToTerminal(content, this.getTerminalContentWidth()));
|
|
1025
|
-
this.
|
|
876
|
+
this.streamState.streamedAnswerDisplayed = true;
|
|
1026
877
|
}
|
|
1027
878
|
renderAgentAnswerForTerminal(content) {
|
|
1028
879
|
const normalized = String(content || '').trim();
|
|
@@ -1035,37 +886,7 @@ export class ChatCommand {
|
|
|
1035
886
|
return Math.max(60, Math.min(cols - 2, 100));
|
|
1036
887
|
}
|
|
1037
888
|
sanitizeV3VisibleStreamText(text) {
|
|
1038
|
-
|
|
1039
|
-
if (!output)
|
|
1040
|
-
return '';
|
|
1041
|
-
output = output.replace(/<\/think>/gi, '</thinking>');
|
|
1042
|
-
output = output.replace(/<think>/gi, '<thinking>');
|
|
1043
|
-
output = output.replace(/<\/redacted_thinking>/gi, '</thinking>');
|
|
1044
|
-
if (this.v3SuppressThinkingStream) {
|
|
1045
|
-
const closeIdx = output.search(/<\/thinking>/i);
|
|
1046
|
-
if (closeIdx < 0) {
|
|
1047
|
-
return '';
|
|
1048
|
-
}
|
|
1049
|
-
output = output.slice(closeIdx).replace(/^<\/thinking>/i, '');
|
|
1050
|
-
this.v3SuppressThinkingStream = false;
|
|
1051
|
-
}
|
|
1052
|
-
output = output.replace(/<thinking>[\s\S]*?<\/thinking>/gi, '');
|
|
1053
|
-
if (/<thinking>/i.test(output)) {
|
|
1054
|
-
output = output.replace(/<thinking>[\s\S]*$/i, '');
|
|
1055
|
-
this.v3SuppressThinkingStream = true;
|
|
1056
|
-
}
|
|
1057
|
-
output = output
|
|
1058
|
-
.replace(/```json\s*\[\s*\{[\s\S]*?"tool"[\s\S]*?\}\s*\]\s*```/gi, '')
|
|
1059
|
-
.replace(/```json\s*\{[\s\S]*?"tool"[\s\S]*?\}\s*```/gi, '')
|
|
1060
|
-
.replace(/^\s*(?:json\s*)?\[\s*\{[\s\S]*?"tool"[\s\S]*$/gim, '')
|
|
1061
|
-
.replace(/^\s*(?:list_dir|read_file|write_file|edit_file|glob|grep|bash)\s*$/gim, '')
|
|
1062
|
-
.replace(/<tool_call>[\s\S]*?<\/tool_call>/gi, '')
|
|
1063
|
-
.replace(/<tool_code>[\s\S]*?<\/tool_code>/gi, '')
|
|
1064
|
-
.replace(/<read_file>\s*<path>[\s\S]*?<\/path>\s*<\/read_file>/gi, '')
|
|
1065
|
-
.replace(/<(?:grep|list_directory|search_files|glob|tool_result)\b[\s\S]*?<\/(?:grep|list_directory|search_files|glob|tool_result)>/gi, '')
|
|
1066
|
-
.replace(/bootstrap discovery completed\s*[—-]\s*inspect the most relevant files next\.?/gi, '')
|
|
1067
|
-
.replace(/\n{3,}/g, '\n\n');
|
|
1068
|
-
return output;
|
|
889
|
+
return this.streamState.sanitizeVisibleText(text);
|
|
1069
890
|
}
|
|
1070
891
|
writeAgentActivityLine(text) {
|
|
1071
892
|
if (this.jsonOutput)
|
|
@@ -1085,29 +906,29 @@ export class ChatCommand {
|
|
|
1085
906
|
if (!event || typeof event !== 'object') {
|
|
1086
907
|
return;
|
|
1087
908
|
}
|
|
1088
|
-
this.
|
|
909
|
+
this.streamState.noteActivity();
|
|
1089
910
|
if (isV3StreamKeepaliveEvent(event)) {
|
|
1090
911
|
return;
|
|
1091
912
|
}
|
|
1092
913
|
if (event.type === 'tool_call') {
|
|
1093
914
|
const eventKey = this.toolEventKey(event);
|
|
1094
|
-
if (this.
|
|
915
|
+
if (this.streamState.seenToolCalls.has(eventKey)) {
|
|
1095
916
|
return;
|
|
1096
917
|
}
|
|
1097
|
-
this.
|
|
1098
|
-
this.
|
|
918
|
+
this.streamState.seenToolCalls.add(eventKey);
|
|
919
|
+
this.streamState.toolCallCount += 1;
|
|
1099
920
|
const toolName = event.name || event.tool || event.tool_name || '';
|
|
1100
921
|
emitDeckEvent({
|
|
1101
922
|
type: 'tool_start',
|
|
1102
923
|
tool: toolName,
|
|
1103
|
-
index: this.
|
|
924
|
+
index: this.streamState.toolCallCount,
|
|
1104
925
|
target: event.arguments?.path || event.arguments?.file_path || event.arguments?.pattern || '',
|
|
1105
926
|
});
|
|
1106
927
|
const toolDesc = this.describeV3AgentTool(event.tool || event.name || event.tool_name);
|
|
1107
928
|
const toolTarget = event.arguments?.path || event.arguments?.file_path || event.arguments?.pattern || '';
|
|
1108
929
|
const sanitizedTarget = this.sanitizeServerPath(String(toolTarget));
|
|
1109
930
|
const shortTarget = sanitizedTarget ? ` -> ${sanitizedTarget.replace(/\\/g, '/').split('/').slice(-2).join('/')}` : '';
|
|
1110
|
-
const stepLabel = chalk.cyan(` [${this.
|
|
931
|
+
const stepLabel = chalk.cyan(` [${this.streamState.toolCallCount}]`) + ` ${toolDesc}${shortTarget}`;
|
|
1111
932
|
if (spinner.isSpinning)
|
|
1112
933
|
spinner.stop();
|
|
1113
934
|
this.writeAgentActivityLine(this.fitTerminalText(stepLabel) + '\n');
|
|
@@ -1130,18 +951,18 @@ export class ChatCommand {
|
|
|
1130
951
|
}
|
|
1131
952
|
if (event.type === 'tool_result') {
|
|
1132
953
|
const eventKey = this.toolEventKey(event);
|
|
1133
|
-
if (eventKey && this.
|
|
954
|
+
if (eventKey && this.streamState.seenToolResults.has(eventKey)) {
|
|
1134
955
|
return;
|
|
1135
956
|
}
|
|
1136
957
|
if (eventKey)
|
|
1137
|
-
this.
|
|
958
|
+
this.streamState.seenToolResults.add(eventKey);
|
|
1138
959
|
const success = event.success !== false;
|
|
1139
960
|
const toolName = event.name || event.tool || '';
|
|
1140
961
|
emitDeckEvent({
|
|
1141
962
|
type: 'tool_end',
|
|
1142
963
|
tool: toolName,
|
|
1143
964
|
success,
|
|
1144
|
-
index: this.
|
|
965
|
+
index: this.streamState.toolCallCount,
|
|
1145
966
|
});
|
|
1146
967
|
const indicator = success ? chalk.green(' ✓') : chalk.red(' ✗');
|
|
1147
968
|
if (spinner.isSpinning)
|
|
@@ -1207,7 +1028,7 @@ export class ChatCommand {
|
|
|
1207
1028
|
return;
|
|
1208
1029
|
}
|
|
1209
1030
|
if (event.type === 'thinking') {
|
|
1210
|
-
this.
|
|
1031
|
+
this.streamState.iterationCount += 1;
|
|
1211
1032
|
const iterText = this.sanitizeServerPath(event.content || '');
|
|
1212
1033
|
const isGenericStep = !iterText
|
|
1213
1034
|
|| /read-only analysis turn|run at least one discovery tool/i.test(iterText)
|
|
@@ -1226,7 +1047,7 @@ export class ChatCommand {
|
|
|
1226
1047
|
? (event.delta?.text || '')
|
|
1227
1048
|
: (event.content || '');
|
|
1228
1049
|
if (text) {
|
|
1229
|
-
this.
|
|
1050
|
+
this.streamState.noteActivity();
|
|
1230
1051
|
if (event.type === 'message' && /^\[Context\]/i.test(String(text).trim())) {
|
|
1231
1052
|
if (spinner.isSpinning)
|
|
1232
1053
|
spinner.stop();
|
|
@@ -1243,16 +1064,16 @@ export class ChatCommand {
|
|
|
1243
1064
|
}
|
|
1244
1065
|
if (event.type === 'complete') {
|
|
1245
1066
|
const elapsed = event.elapsed || '';
|
|
1246
|
-
const iters = event.iterations || this.
|
|
1247
|
-
const tools = event.tool_calls || this.
|
|
1067
|
+
const iters = event.iterations || this.streamState.iterationCount;
|
|
1068
|
+
const tools = event.tool_calls || this.streamState.toolCallCount;
|
|
1248
1069
|
if (spinner.isSpinning)
|
|
1249
1070
|
spinner.stop();
|
|
1250
1071
|
this.displayV3StreamedAnswer();
|
|
1251
1072
|
emitDeckEvent({
|
|
1252
1073
|
type: 'run_complete',
|
|
1253
1074
|
success: true,
|
|
1254
|
-
tools: event.tool_calls || this.
|
|
1255
|
-
iterations: event.iterations || this.
|
|
1075
|
+
tools: event.tool_calls || this.streamState.toolCallCount,
|
|
1076
|
+
iterations: event.iterations || this.streamState.iterationCount,
|
|
1256
1077
|
elapsed: event.elapsed || '',
|
|
1257
1078
|
});
|
|
1258
1079
|
let statLine = `${iters} iterations, ${tools} tool calls`;
|
|
@@ -1268,10 +1089,11 @@ export class ChatCommand {
|
|
|
1268
1089
|
process.stdout.write(chalk.green(`\n✓ Complete`) + ` - ${this.fitTerminalText(statLine, 14)}\n`);
|
|
1269
1090
|
// Show seal quality score if available
|
|
1270
1091
|
if (event.seal_score && typeof event.seal_score.overall === 'number') {
|
|
1271
|
-
const
|
|
1092
|
+
const rawScore = event.seal_score.overall;
|
|
1093
|
+
const score = rawScore <= 10 ? rawScore * 10 : rawScore;
|
|
1272
1094
|
const tier = event.seal_score.tier || '';
|
|
1273
|
-
const scoreColor = score >=
|
|
1274
|
-
process.stdout.write(chalk.cyan(' [Quality] ') + scoreColor(`${score}/
|
|
1095
|
+
const scoreColor = score >= 70 ? chalk.green : score >= 50 ? chalk.yellow : chalk.red;
|
|
1096
|
+
process.stdout.write(chalk.cyan(' [Quality] ') + scoreColor(`${score}/100`) + (tier ? chalk.gray(` (${tier})`) : '') + '\n');
|
|
1275
1097
|
}
|
|
1276
1098
|
return;
|
|
1277
1099
|
}
|
|
@@ -1426,11 +1248,11 @@ export class ChatCommand {
|
|
|
1426
1248
|
return;
|
|
1427
1249
|
}
|
|
1428
1250
|
if (event.type === 'start') {
|
|
1429
|
-
if (this.
|
|
1251
|
+
if (this.streamState.startSeen) {
|
|
1430
1252
|
spinner.text = 'Working...';
|
|
1431
1253
|
return;
|
|
1432
1254
|
}
|
|
1433
|
-
this.
|
|
1255
|
+
this.streamState.startSeen = true;
|
|
1434
1256
|
if (spinner.isSpinning)
|
|
1435
1257
|
spinner.stop();
|
|
1436
1258
|
process.stderr.write(chalk.cyan(' [Start] ') + 'Agent initialized\n');
|
|
@@ -1504,20 +1326,20 @@ export class ChatCommand {
|
|
|
1504
1326
|
this.config = config;
|
|
1505
1327
|
this.logger = logger;
|
|
1506
1328
|
this.commandProgram = commandProgram;
|
|
1507
|
-
this.api =
|
|
1329
|
+
this.api = createAPIClient(config, logger);
|
|
1330
|
+
this.promptPolicy = new ChatPromptPolicy(() => this.getRuntimeEnvironmentContext());
|
|
1331
|
+
this.streamState = new AgentStreamState({
|
|
1332
|
+
isJsonOutput: () => this.jsonOutput,
|
|
1333
|
+
idleTimeoutMs: () => DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS,
|
|
1334
|
+
});
|
|
1508
1335
|
this.sessionManager = new SessionManager();
|
|
1509
1336
|
}
|
|
1510
1337
|
async run(options) {
|
|
1511
1338
|
const hasRuntimeToken = Boolean(process.env.VIGTHORIA_TOKEN || process.env.VIGTHORIA_AUTH_TOKEN);
|
|
1512
1339
|
if (!this.config.isAuthenticated() && !hasRuntimeToken) {
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
}
|
|
1517
|
-
else {
|
|
1518
|
-
this.logger.error('Not authenticated. Run: vigthoria login');
|
|
1519
|
-
}
|
|
1520
|
-
return;
|
|
1340
|
+
throw new CliCommandError('Not authenticated. Run: vigthoria login', {
|
|
1341
|
+
code: 'AUTH_REQUIRED', category: 'authentication',
|
|
1342
|
+
});
|
|
1521
1343
|
}
|
|
1522
1344
|
this.agentMode = options.agent === true;
|
|
1523
1345
|
this.operatorMode = options.operator === true;
|
|
@@ -1526,7 +1348,7 @@ export class ChatCommand {
|
|
|
1526
1348
|
: null;
|
|
1527
1349
|
this.savePlanToVigFlow = options.savePlan === true;
|
|
1528
1350
|
this.jsonOutput = options.json === true;
|
|
1529
|
-
this.autoApprove = options.autoApprove
|
|
1351
|
+
this.autoApprove = resolveAutoApproval(options.autoApprove, this.jsonOutput);
|
|
1530
1352
|
this.personaOverride = options.grant === true ? 'wiener_grant' : null;
|
|
1531
1353
|
this.modelExplicitlySelected = this.isHardExplicitAgentModelSelection(options);
|
|
1532
1354
|
this.currentModel = this.resolveInitialModel(options);
|
|
@@ -1582,19 +1404,23 @@ export class ChatCommand {
|
|
|
1582
1404
|
this.saveSession();
|
|
1583
1405
|
}
|
|
1584
1406
|
this.operatorMode = false;
|
|
1585
|
-
|
|
1586
|
-
return;
|
|
1407
|
+
throw new CliCommandError(this.operatorAccessMessage(), { code: 'OPERATOR_ACCESS_REQUIRED', category: 'authorization' });
|
|
1587
1408
|
}
|
|
1588
1409
|
if (options.retry || options.continue) {
|
|
1589
1410
|
if (!this.agentMode) {
|
|
1590
|
-
throw new
|
|
1411
|
+
throw new CliCommandError('--retry and --continue require agent mode.', { code: 'AGENT_MODE_REQUIRED', category: 'usage' });
|
|
1591
1412
|
}
|
|
1592
1413
|
const followUp = options.retry ? this.buildRetryPrompt() : this.buildContinuePrompt();
|
|
1593
1414
|
if (!followUp) {
|
|
1594
|
-
throw new
|
|
1415
|
+
throw new CliCommandError('No previous agent run to retry or continue. Run an agent prompt first with the same --project, or use --resume.', {
|
|
1416
|
+
code: 'AGENT_RUN_NOT_FOUND',
|
|
1417
|
+
category: 'usage',
|
|
1418
|
+
});
|
|
1595
1419
|
}
|
|
1596
1420
|
this.directPromptMode = true;
|
|
1597
1421
|
await this.runAgentTurn(followUp);
|
|
1422
|
+
this.workspaceBrain?.destroy();
|
|
1423
|
+
this.api.destroy();
|
|
1598
1424
|
return;
|
|
1599
1425
|
}
|
|
1600
1426
|
if (options.prompt) {
|
|
@@ -1606,35 +1432,34 @@ export class ChatCommand {
|
|
|
1606
1432
|
const parsed = Number.parseInt(rawValue, 10);
|
|
1607
1433
|
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
|
1608
1434
|
})();
|
|
1609
|
-
let
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
}, bridgePromptTimeoutMs)
|
|
1623
|
-
: null;
|
|
1624
|
-
await this.handleDirectPrompt(options.prompt);
|
|
1625
|
-
if (timeoutId)
|
|
1626
|
-
clearTimeout(timeoutId);
|
|
1627
|
-
if (!timedOut) {
|
|
1628
|
-
const bridge = getBridgeClient();
|
|
1629
|
-
if (bridge) {
|
|
1630
|
-
bridge.emitEnd({ reason: 'prompt-complete' });
|
|
1631
|
-
bridge.destroy();
|
|
1435
|
+
let timeoutId = null;
|
|
1436
|
+
try {
|
|
1437
|
+
const promptTask = this.handleDirectPrompt(options.prompt);
|
|
1438
|
+
if (options.bridge && bridgePromptTimeoutMs > 0) {
|
|
1439
|
+
await Promise.race([
|
|
1440
|
+
promptTask,
|
|
1441
|
+
new Promise((_resolve, reject) => {
|
|
1442
|
+
timeoutId = setTimeout(() => reject(new CliCommandError(`Bridge prompt timed out after ${Math.round(bridgePromptTimeoutMs / 1000)} seconds.`, { code: 'BRIDGE_PROMPT_TIMEOUT', category: 'network' })), bridgePromptTimeoutMs);
|
|
1443
|
+
}),
|
|
1444
|
+
]);
|
|
1445
|
+
}
|
|
1446
|
+
else {
|
|
1447
|
+
await promptTask;
|
|
1632
1448
|
}
|
|
1449
|
+
getBridgeClient()?.emitEnd({ reason: 'prompt-complete' });
|
|
1633
1450
|
}
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1451
|
+
catch (error) {
|
|
1452
|
+
getBridgeClient()?.emitEnd({ reason: 'prompt-failed' });
|
|
1453
|
+
throw error;
|
|
1454
|
+
}
|
|
1455
|
+
finally {
|
|
1456
|
+
if (timeoutId)
|
|
1457
|
+
clearTimeout(timeoutId);
|
|
1458
|
+
getBridgeClient()?.destroy();
|
|
1459
|
+
this.workspaceBrain?.destroy();
|
|
1460
|
+
this.api.destroy();
|
|
1461
|
+
}
|
|
1462
|
+
return;
|
|
1638
1463
|
}
|
|
1639
1464
|
await this.startInteractiveChat();
|
|
1640
1465
|
const bridge = getBridgeClient();
|
|
@@ -1642,6 +1467,8 @@ export class ChatCommand {
|
|
|
1642
1467
|
bridge.emitEnd({ reason: 'interactive-exit' });
|
|
1643
1468
|
bridge.destroy();
|
|
1644
1469
|
}
|
|
1470
|
+
this.workspaceBrain?.destroy();
|
|
1471
|
+
this.api.destroy();
|
|
1645
1472
|
}
|
|
1646
1473
|
/** Handle an inbound admin command from the Commando Bridge. */
|
|
1647
1474
|
handleAdminCommand(cmd) {
|
|
@@ -1661,7 +1488,9 @@ export class ChatCommand {
|
|
|
1661
1488
|
if (!this.jsonOutput)
|
|
1662
1489
|
console.log(chalk.red(`[bridge] Abort requested by admin`));
|
|
1663
1490
|
getBridgeClient()?.emitEnd({ reason: 'admin-abort' });
|
|
1664
|
-
process.
|
|
1491
|
+
process.exitCode = 130;
|
|
1492
|
+
this.workspaceBrain?.destroy();
|
|
1493
|
+
this.api.destroy();
|
|
1665
1494
|
break;
|
|
1666
1495
|
default:
|
|
1667
1496
|
getBridgeClient()?.emitError({ message: `Unknown admin command: ${cmd.action}` });
|
|
@@ -1936,8 +1765,7 @@ export class ChatCommand {
|
|
|
1936
1765
|
}
|
|
1937
1766
|
if (this.operatorMode) {
|
|
1938
1767
|
if (!this.hasOperatorAccess()) {
|
|
1939
|
-
|
|
1940
|
-
return;
|
|
1768
|
+
throw new CliCommandError(this.operatorAccessMessage(), { code: 'OPERATOR_ACCESS_REQUIRED', category: 'authorization' });
|
|
1941
1769
|
}
|
|
1942
1770
|
await this.runOperatorTurn(effectivePrompt);
|
|
1943
1771
|
return;
|
|
@@ -2017,13 +1845,13 @@ export class ChatCommand {
|
|
|
2017
1845
|
spinner.stop();
|
|
2018
1846
|
}
|
|
2019
1847
|
if (this.jsonOutput) {
|
|
2020
|
-
|
|
1848
|
+
emitAgentJsonOutput({
|
|
2021
1849
|
success: true,
|
|
2022
1850
|
workflow: resolvedWorkflow,
|
|
2023
1851
|
mode: invocationMode,
|
|
2024
1852
|
execution,
|
|
2025
1853
|
content,
|
|
2026
|
-
}
|
|
1854
|
+
});
|
|
2027
1855
|
return;
|
|
2028
1856
|
}
|
|
2029
1857
|
this.logger.success(`Workflow target ${resolvedWorkflow.name} ${execution.status}`);
|
|
@@ -2044,8 +1872,7 @@ export class ChatCommand {
|
|
|
2044
1872
|
}
|
|
2045
1873
|
async runOperatorTurn(prompt) {
|
|
2046
1874
|
if (!this.hasOperatorAccess()) {
|
|
2047
|
-
|
|
2048
|
-
return;
|
|
1875
|
+
throw new CliCommandError(this.operatorAccessMessage(), { code: 'OPERATOR_ACCESS_REQUIRED', category: 'authorization' });
|
|
2049
1876
|
}
|
|
2050
1877
|
// ── Fast-path: direct-answerable prompts skip the full BMAD workflow ──
|
|
2051
1878
|
// The BMAD orchestrator scans the entire workspace (can be 1000+ files)
|
|
@@ -2105,7 +1932,7 @@ export class ChatCommand {
|
|
|
2105
1932
|
throw new CLIError('Operator workflow returned a non-actionable acknowledgement instead of a grounded result.', 'model_backend');
|
|
2106
1933
|
}
|
|
2107
1934
|
if (this.jsonOutput) {
|
|
2108
|
-
|
|
1935
|
+
emitAgentJsonOutput({
|
|
2109
1936
|
success: true,
|
|
2110
1937
|
mode: 'operator',
|
|
2111
1938
|
workflowId: response.workflowId,
|
|
@@ -2113,7 +1940,7 @@ export class ChatCommand {
|
|
|
2113
1940
|
contextId: response.contextId || null,
|
|
2114
1941
|
content: response.content || 'Operator workflow completed.',
|
|
2115
1942
|
metadata: response.metadata || {},
|
|
2116
|
-
}
|
|
1943
|
+
});
|
|
2117
1944
|
}
|
|
2118
1945
|
else {
|
|
2119
1946
|
console.log(response.content || 'Operator workflow completed.');
|
|
@@ -2131,23 +1958,8 @@ export class ChatCommand {
|
|
|
2131
1958
|
}
|
|
2132
1959
|
const cliErr = error instanceof CLIError ? error : classifyError(error);
|
|
2133
1960
|
const errorMsg = formatCLIError(cliErr);
|
|
2134
|
-
if (!this.jsonOutput) {
|
|
2135
|
-
this.logger.error('Operator workflow failed');
|
|
2136
|
-
}
|
|
2137
1961
|
this.rememberBrainEvent('issue', `GoA operator workflow failed: ${errorMsg}`, 'operator');
|
|
2138
|
-
|
|
2139
|
-
process.exitCode = 1;
|
|
2140
|
-
console.log(JSON.stringify({
|
|
2141
|
-
success: false,
|
|
2142
|
-
mode: 'operator',
|
|
2143
|
-
content: '',
|
|
2144
|
-
error: errorMsg,
|
|
2145
|
-
errorCategory: cliErr.category,
|
|
2146
|
-
}, null, 2));
|
|
2147
|
-
}
|
|
2148
|
-
else {
|
|
2149
|
-
this.logger.error(errorMsg);
|
|
2150
|
-
}
|
|
1962
|
+
throw commandFailure(error, { code: 'OPERATOR_EXECUTION_FAILED', message: errorMsg });
|
|
2151
1963
|
}
|
|
2152
1964
|
}
|
|
2153
1965
|
/**
|
|
@@ -2193,12 +2005,12 @@ export class ChatCommand {
|
|
|
2193
2005
|
throw new CLIError('Operator returned a non-actionable acknowledgement instead of a grounded result.', 'model_backend');
|
|
2194
2006
|
}
|
|
2195
2007
|
if (this.jsonOutput) {
|
|
2196
|
-
|
|
2008
|
+
emitAgentJsonOutput({
|
|
2197
2009
|
success: true,
|
|
2198
2010
|
mode: 'operator',
|
|
2199
2011
|
content,
|
|
2200
2012
|
metadata: { source: 'operator-direct', mode: 'operator' },
|
|
2201
|
-
}
|
|
2013
|
+
});
|
|
2202
2014
|
}
|
|
2203
2015
|
else {
|
|
2204
2016
|
console.log(content);
|
|
@@ -2212,20 +2024,7 @@ export class ChatCommand {
|
|
|
2212
2024
|
spinner.stop();
|
|
2213
2025
|
const cliErr = error instanceof CLIError ? error : classifyError(error);
|
|
2214
2026
|
const errorMsg = formatCLIError(cliErr);
|
|
2215
|
-
|
|
2216
|
-
process.exitCode = 1;
|
|
2217
|
-
console.log(JSON.stringify({
|
|
2218
|
-
success: false,
|
|
2219
|
-
mode: 'operator',
|
|
2220
|
-
content: '',
|
|
2221
|
-
error: errorMsg,
|
|
2222
|
-
errorCategory: cliErr.category,
|
|
2223
|
-
}, null, 2));
|
|
2224
|
-
}
|
|
2225
|
-
else {
|
|
2226
|
-
this.logger.error('Operator direct answer failed');
|
|
2227
|
-
this.logger.error(errorMsg);
|
|
2228
|
-
}
|
|
2027
|
+
throw commandFailure(error, { code: 'OPERATOR_DIRECT_FAILED', message: errorMsg });
|
|
2229
2028
|
}
|
|
2230
2029
|
}
|
|
2231
2030
|
async runSimplePrompt(prompt) {
|
|
@@ -2324,20 +2123,20 @@ export class ChatCommand {
|
|
|
2324
2123
|
const response = await this.callApi('Send chat message', () => this.api.chat(this.getMessagesForModel(), this.currentModel));
|
|
2325
2124
|
if (spinner)
|
|
2326
2125
|
spinner.stop();
|
|
2327
|
-
const finalText =
|
|
2126
|
+
const finalText = sanitizeDirectModeOutput(stripHiddenThoughtBlocks(response.message || '')).trim();
|
|
2328
2127
|
const effectiveModel = String(response.model || this.currentModel);
|
|
2329
2128
|
const metadata = this.modelGovernanceFallback
|
|
2330
2129
|
? { modelFallback: this.modelGovernanceFallback }
|
|
2331
2130
|
: undefined;
|
|
2332
2131
|
if (this.jsonOutput) {
|
|
2333
|
-
|
|
2132
|
+
emitAgentJsonOutput({
|
|
2334
2133
|
success: true,
|
|
2335
2134
|
mode: 'chat',
|
|
2336
2135
|
model: effectiveModel,
|
|
2337
2136
|
requestedModel: this.modelGovernanceFallback?.requestedModel || this.currentModel,
|
|
2338
2137
|
metadata,
|
|
2339
2138
|
content: finalText || 'The model returned an empty response. Try rephrasing or use --agent for grounded file analysis.',
|
|
2340
|
-
}
|
|
2139
|
+
});
|
|
2341
2140
|
}
|
|
2342
2141
|
else if (finalText) {
|
|
2343
2142
|
console.log(finalText);
|
|
@@ -2353,20 +2152,7 @@ export class ChatCommand {
|
|
|
2353
2152
|
spinner.stop();
|
|
2354
2153
|
const cliErr = error instanceof CLIError ? error : classifyError(error);
|
|
2355
2154
|
const errorMsg = formatCLIError(cliErr);
|
|
2356
|
-
|
|
2357
|
-
process.exitCode = 1;
|
|
2358
|
-
console.log(JSON.stringify({
|
|
2359
|
-
success: false,
|
|
2360
|
-
mode: 'chat',
|
|
2361
|
-
model: this.currentModel,
|
|
2362
|
-
content: '',
|
|
2363
|
-
error: errorMsg,
|
|
2364
|
-
errorCategory: cliErr.category,
|
|
2365
|
-
}, null, 2));
|
|
2366
|
-
}
|
|
2367
|
-
else {
|
|
2368
|
-
this.logger.error(errorMsg);
|
|
2369
|
-
}
|
|
2155
|
+
throw commandFailure(error, { code: 'CHAT_EXECUTION_FAILED', message: errorMsg });
|
|
2370
2156
|
}
|
|
2371
2157
|
}
|
|
2372
2158
|
async runAgentTurn(prompt) {
|
|
@@ -2406,10 +2192,9 @@ export class ChatCommand {
|
|
|
2406
2192
|
return;
|
|
2407
2193
|
}
|
|
2408
2194
|
const errorMessage = 'The V3 planner/executor workflow is unavailable. Refusing to fall back to legacy template, direct-file, or local write paths.';
|
|
2409
|
-
this.logger.error(errorMessage);
|
|
2410
2195
|
this.messages.push({ role: 'assistant', content: errorMessage });
|
|
2411
|
-
process.exitCode = 1;
|
|
2412
2196
|
this.saveSession();
|
|
2197
|
+
throw new CliCommandError(errorMessage, { code: 'AGENT_UNAVAILABLE', category: 'execution' });
|
|
2413
2198
|
}
|
|
2414
2199
|
resolveAgentTurnPrompt(prompt) {
|
|
2415
2200
|
if (isBuiltContinuePrompt(prompt) || isBuiltRetryPrompt(prompt) || isBuiltWriteConfirmationPrompt(prompt)) {
|
|
@@ -2581,33 +2366,38 @@ export class ChatCommand {
|
|
|
2581
2366
|
spinner.stop();
|
|
2582
2367
|
const evidenceSummary = this.synthesizeEvidenceFromHistory();
|
|
2583
2368
|
if (evidenceSummary) {
|
|
2584
|
-
const fallbackContent =
|
|
2369
|
+
const fallbackContent = sanitizeDirectModeOutput(evidenceSummary);
|
|
2585
2370
|
const liveOutcome = this.buildLocalLoopLiveOutcome(prompt);
|
|
2586
2371
|
const evaluation = evaluateExecutorSuccess(liveOutcome);
|
|
2587
2372
|
this.persistLocalLoopOutcome(prompt, evaluation, liveOutcome);
|
|
2588
2373
|
if (this.jsonOutput) {
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2374
|
+
if (evaluation.executorSucceeded)
|
|
2375
|
+
emitAgentJsonOutput({
|
|
2376
|
+
success: true,
|
|
2377
|
+
mode: 'agent',
|
|
2378
|
+
model: this.currentModel,
|
|
2379
|
+
partial: !evaluation.executorSucceeded,
|
|
2380
|
+
content: fallbackContent,
|
|
2381
|
+
statusHeadline: evaluation.statusHeadline,
|
|
2382
|
+
metadata: {
|
|
2383
|
+
executionPath: 'local-agent-loop',
|
|
2384
|
+
recovered: true,
|
|
2385
|
+
outcomeTruth: {
|
|
2386
|
+
executorSucceeded: evaluation.executorSucceeded,
|
|
2387
|
+
uiTheme: evaluation.uiTheme,
|
|
2388
|
+
},
|
|
2603
2389
|
},
|
|
2604
|
-
}
|
|
2605
|
-
}, null, 2));
|
|
2390
|
+
});
|
|
2606
2391
|
}
|
|
2607
2392
|
else {
|
|
2608
2393
|
console.log(fallbackContent);
|
|
2609
2394
|
}
|
|
2610
2395
|
this.saveSession();
|
|
2396
|
+
if (!evaluation.executorSucceeded) {
|
|
2397
|
+
throw new CliCommandError(evaluation.statusHeadline, {
|
|
2398
|
+
code: 'AGENT_INCOMPLETE', details: { content: fallbackContent, executionPath: 'local-agent-loop' },
|
|
2399
|
+
});
|
|
2400
|
+
}
|
|
2611
2401
|
return;
|
|
2612
2402
|
}
|
|
2613
2403
|
throw retryErr;
|
|
@@ -2619,23 +2409,7 @@ export class ChatCommand {
|
|
|
2619
2409
|
if (spinner)
|
|
2620
2410
|
spinner.stop();
|
|
2621
2411
|
this.rememberBrainEvent('issue', 'Agent API preflight failed: ' + formatted, 'agent');
|
|
2622
|
-
|
|
2623
|
-
process.exitCode = 1;
|
|
2624
|
-
console.log(JSON.stringify({
|
|
2625
|
-
success: false,
|
|
2626
|
-
mode: 'agent',
|
|
2627
|
-
model: this.currentModel,
|
|
2628
|
-
partial: false,
|
|
2629
|
-
content: '',
|
|
2630
|
-
error: formatted,
|
|
2631
|
-
errorCategory: cliErr.category,
|
|
2632
|
-
metadata: { executionPath: 'local-agent-loop' },
|
|
2633
|
-
}, null, 2));
|
|
2634
|
-
}
|
|
2635
|
-
else {
|
|
2636
|
-
this.logger.error(formatted);
|
|
2637
|
-
}
|
|
2638
|
-
return;
|
|
2412
|
+
throw commandFailure(firstErr, { code: 'AGENT_PREFLIGHT_FAILED', message: formatted });
|
|
2639
2413
|
}
|
|
2640
2414
|
}
|
|
2641
2415
|
if (spinner)
|
|
@@ -2657,8 +2431,8 @@ export class ChatCommand {
|
|
|
2657
2431
|
const isPolicyAck = /^(i will follow|i understand|i('ll| will) adhere|understood[.,!]|sure[.,!]|i('ll| will) use the tools|i('ll| will) proceed|let me know|provide your|waiting for)/i.test(visibleText.trim());
|
|
2658
2432
|
// Detect if the response is effectively empty or just a follow-up
|
|
2659
2433
|
// question after we strip tool output echoes.
|
|
2660
|
-
const sanitized =
|
|
2661
|
-
const isFollowUp =
|
|
2434
|
+
const sanitized = sanitizeDirectModeOutput(visibleText.trim());
|
|
2435
|
+
const isFollowUp = isDirectModeFollowUpQuestion(sanitized);
|
|
2662
2436
|
const isEmptyAfterSanitize = !sanitized || sanitized.length < 10;
|
|
2663
2437
|
// Detect resignation: model gives up saying files/things were "not found"
|
|
2664
2438
|
// without having tried list_dir to discover the correct path.
|
|
@@ -2701,29 +2475,34 @@ export class ChatCommand {
|
|
|
2701
2475
|
const evaluation = evaluateExecutorSuccess(liveOutcome);
|
|
2702
2476
|
this.persistLocalLoopOutcome(prompt, evaluation, liveOutcome);
|
|
2703
2477
|
if (this.jsonOutput) {
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2478
|
+
if (evaluation.executorSucceeded)
|
|
2479
|
+
emitAgentJsonOutput({
|
|
2480
|
+
success: true,
|
|
2481
|
+
mode: 'agent',
|
|
2482
|
+
model: this.currentModel,
|
|
2483
|
+
partial: !evaluation.executorSucceeded,
|
|
2484
|
+
content: finalContent,
|
|
2485
|
+
statusHeadline: evaluation.statusHeadline,
|
|
2486
|
+
metadata: {
|
|
2487
|
+
executionPath: 'local-agent-loop',
|
|
2488
|
+
outcomeTruth: {
|
|
2489
|
+
executorSucceeded: evaluation.executorSucceeded,
|
|
2490
|
+
uiTheme: evaluation.uiTheme,
|
|
2491
|
+
analysisToolsUsed: liveOutcome.analysisToolsUsed,
|
|
2492
|
+
changedFileCount: liveOutcome.changedFileCount,
|
|
2493
|
+
},
|
|
2719
2494
|
},
|
|
2720
|
-
}
|
|
2721
|
-
}, null, 2));
|
|
2495
|
+
});
|
|
2722
2496
|
}
|
|
2723
2497
|
else {
|
|
2724
2498
|
console.log(finalContent);
|
|
2725
2499
|
}
|
|
2726
2500
|
this.saveSession();
|
|
2501
|
+
if (!evaluation.executorSucceeded) {
|
|
2502
|
+
throw new CliCommandError(evaluation.statusHeadline, {
|
|
2503
|
+
code: 'AGENT_INCOMPLETE', details: { content: finalContent, executionPath: 'local-agent-loop' },
|
|
2504
|
+
});
|
|
2505
|
+
}
|
|
2727
2506
|
return;
|
|
2728
2507
|
}
|
|
2729
2508
|
await this.executeToolCalls(toolCalls);
|
|
@@ -2740,25 +2519,7 @@ export class ChatCommand {
|
|
|
2740
2519
|
const cliErr = error instanceof CLIError ? error : classifyError(error);
|
|
2741
2520
|
const errorMsg = formatCLIError(cliErr);
|
|
2742
2521
|
this.rememberBrainEvent('issue', `Agent turn failed: ${errorMsg}`, 'agent');
|
|
2743
|
-
|
|
2744
|
-
process.exitCode = 1;
|
|
2745
|
-
console.log(JSON.stringify({
|
|
2746
|
-
success: false,
|
|
2747
|
-
mode: 'agent',
|
|
2748
|
-
model: this.currentModel,
|
|
2749
|
-
partial: false,
|
|
2750
|
-
content: '',
|
|
2751
|
-
error: errorMsg,
|
|
2752
|
-
errorCategory: cliErr.category,
|
|
2753
|
-
metadata: {
|
|
2754
|
-
executionPath: 'local-agent-loop',
|
|
2755
|
-
},
|
|
2756
|
-
}, null, 2));
|
|
2757
|
-
}
|
|
2758
|
-
else {
|
|
2759
|
-
this.logger.error(errorMsg);
|
|
2760
|
-
}
|
|
2761
|
-
return;
|
|
2522
|
+
throw commandFailure(error, { code: 'AGENT_TURN_FAILED', message: errorMsg });
|
|
2762
2523
|
}
|
|
2763
2524
|
}
|
|
2764
2525
|
const liveOutcome = this.buildLocalLoopLiveOutcome(prompt);
|
|
@@ -2768,29 +2529,11 @@ export class ChatCommand {
|
|
|
2768
2529
|
executorError: 'Agent exhausted the maximum local tool loop turns before reaching a clean completion.',
|
|
2769
2530
|
});
|
|
2770
2531
|
this.persistLocalLoopOutcome(prompt, exhaustedEval, liveOutcome);
|
|
2771
|
-
if (this.jsonOutput) {
|
|
2772
|
-
process.exitCode = 1;
|
|
2773
|
-
console.log(JSON.stringify({
|
|
2774
|
-
success: false,
|
|
2775
|
-
mode: 'agent',
|
|
2776
|
-
model: this.currentModel,
|
|
2777
|
-
partial: true,
|
|
2778
|
-
content: 'Task complete.',
|
|
2779
|
-
error: 'Agent exhausted the maximum local tool loop turns before reaching a clean completion.',
|
|
2780
|
-
statusHeadline: exhaustedEval.statusHeadline,
|
|
2781
|
-
metadata: {
|
|
2782
|
-
executionPath: 'local-agent-loop',
|
|
2783
|
-
outcomeTruth: {
|
|
2784
|
-
executorSucceeded: false,
|
|
2785
|
-
uiTheme: exhaustedEval.uiTheme,
|
|
2786
|
-
},
|
|
2787
|
-
},
|
|
2788
|
-
}, null, 2));
|
|
2789
|
-
}
|
|
2790
|
-
else {
|
|
2791
|
-
console.log('Task complete.');
|
|
2792
|
-
}
|
|
2793
2532
|
this.saveSession();
|
|
2533
|
+
throw new CliCommandError('Agent exhausted the maximum local tool loop turns before reaching a clean completion.', {
|
|
2534
|
+
code: 'AGENT_TURN_LIMIT_EXHAUSTED',
|
|
2535
|
+
details: { statusHeadline: exhaustedEval.statusHeadline, executionPath: 'local-agent-loop' },
|
|
2536
|
+
});
|
|
2794
2537
|
}
|
|
2795
2538
|
async primeBypassedTargetFileContext(prompt) {
|
|
2796
2539
|
if (!this.directPromptMode || !this.tools) {
|
|
@@ -2821,235 +2564,6 @@ export class ChatCommand {
|
|
|
2821
2564
|
this.messages.push({ role: 'system', content: readSummary });
|
|
2822
2565
|
}
|
|
2823
2566
|
}
|
|
2824
|
-
/**
|
|
2825
|
-
* Sub-second path for trivial HTML/hello-world: Template Service → index.html (no V3 planner).
|
|
2826
|
-
*/
|
|
2827
|
-
async tryTemplateInstantPath(prompt) {
|
|
2828
|
-
const route = this.lastAgentRoute;
|
|
2829
|
-
if (route?.path !== 'template-instant' && !isTrivialHtmlPageRequest(prompt)) {
|
|
2830
|
-
return false;
|
|
2831
|
-
}
|
|
2832
|
-
let workspacePath = this.currentProjectPath;
|
|
2833
|
-
try {
|
|
2834
|
-
const fromPrompt = this.resolvePromptWorkspacePath(prompt, this.currentProjectPath);
|
|
2835
|
-
if (fromPrompt)
|
|
2836
|
-
workspacePath = fromPrompt;
|
|
2837
|
-
}
|
|
2838
|
-
catch {
|
|
2839
|
-
// use default workspace
|
|
2840
|
-
}
|
|
2841
|
-
if (!this.jsonOutput) {
|
|
2842
|
-
console.log();
|
|
2843
|
-
console.log(chalk.gray('━━━ ROUTING DECISION ━━━'));
|
|
2844
|
-
const r = this.lastAgentRoute;
|
|
2845
|
-
console.log(chalk.gray(`Reason: ${r?.reason || 'template-instant (trivial HTML page)'}`));
|
|
2846
|
-
console.log(chalk.gray(`Path: template-instant [${r?.source || 'regex'}]`));
|
|
2847
|
-
console.log(chalk.gray('Model: template-service'));
|
|
2848
|
-
console.log(chalk.gray('Cloud Eligible: false'));
|
|
2849
|
-
console.log(chalk.gray('Cloud Selected: false'));
|
|
2850
|
-
console.log(chalk.gray('━'.repeat(30)));
|
|
2851
|
-
console.log();
|
|
2852
|
-
console.log(chalk.cyan('⚡ Template instant path — matching template and writing index.html...'));
|
|
2853
|
-
}
|
|
2854
|
-
this.messages.push({ role: 'user', content: prompt });
|
|
2855
|
-
const mustComplete = route?.path === 'template-instant';
|
|
2856
|
-
let result = await runTemplateInstantPath(this.api, prompt, workspacePath, {
|
|
2857
|
-
allowFallback: true,
|
|
2858
|
-
});
|
|
2859
|
-
if (!result.ok && mustComplete) {
|
|
2860
|
-
result = await runTemplateInstantPath(this.api, prompt, workspacePath, {
|
|
2861
|
-
allowFallback: true,
|
|
2862
|
-
forceMinimalFallback: true,
|
|
2863
|
-
});
|
|
2864
|
-
}
|
|
2865
|
-
if (!result.ok) {
|
|
2866
|
-
if (!this.jsonOutput) {
|
|
2867
|
-
console.log(chalk.yellow(`Template instant path skipped: ${result.error || 'no match'}`));
|
|
2868
|
-
}
|
|
2869
|
-
return false;
|
|
2870
|
-
}
|
|
2871
|
-
const summary = [
|
|
2872
|
-
`Created ${result.entryPath || 'index.html'} via template-instant`,
|
|
2873
|
-
result.templateName ? `(${result.templateName})` : '',
|
|
2874
|
-
result.processingMs ? `in ${Math.round(result.processingMs)}ms` : '',
|
|
2875
|
-
result.usedFallback ? '(minimal fallback)' : '',
|
|
2876
|
-
].filter(Boolean).join(' ');
|
|
2877
|
-
this.messages.push({ role: 'assistant', content: summary });
|
|
2878
|
-
const liveOutcome = createLiveOutcome();
|
|
2879
|
-
liveOutcome.requiresWorkspaceChanges = true;
|
|
2880
|
-
liveOutcome.workspaceHasOutput = true;
|
|
2881
|
-
liveOutcome.changedFileCount = 1;
|
|
2882
|
-
liveOutcome.tasksTotal = 1;
|
|
2883
|
-
liveOutcome.tasksSucceeded = 1;
|
|
2884
|
-
liveOutcome.answerContent = summary;
|
|
2885
|
-
const evaluation = evaluateExecutorSuccess(liveOutcome);
|
|
2886
|
-
this.commitAgentRunOutcome({
|
|
2887
|
-
prompt,
|
|
2888
|
-
originalPrompt: null,
|
|
2889
|
-
taskId: null,
|
|
2890
|
-
contextId: null,
|
|
2891
|
-
tasksSucceeded: 1,
|
|
2892
|
-
tasksTotal: 1,
|
|
2893
|
-
failedTaskIds: [],
|
|
2894
|
-
unfinishedTaskIds: [],
|
|
2895
|
-
qualityScore: null,
|
|
2896
|
-
qualityMissing: [],
|
|
2897
|
-
qualityBlockers: [],
|
|
2898
|
-
hasOutput: true,
|
|
2899
|
-
answerContent: summary,
|
|
2900
|
-
selfHealStatus: 'skipped',
|
|
2901
|
-
selfHealTool: null,
|
|
2902
|
-
plannerError: null,
|
|
2903
|
-
executorError: null,
|
|
2904
|
-
clientToolErrors: [],
|
|
2905
|
-
transportErrors: [],
|
|
2906
|
-
workspacePath: workspacePath || null,
|
|
2907
|
-
workspaceSyncIssue: null,
|
|
2908
|
-
finishedAt: Date.now(),
|
|
2909
|
-
}, prompt);
|
|
2910
|
-
if (!this.jsonOutput) {
|
|
2911
|
-
console.log(chalk.green(`✓ ${summary}`));
|
|
2912
|
-
console.log(chalk.gray(' Run: vigthoria preview'));
|
|
2913
|
-
this.printAgentRunSummary(this.lastAgentRunOutcome, evaluation, 1);
|
|
2914
|
-
}
|
|
2915
|
-
else {
|
|
2916
|
-
const routingPolicy = this.resolveAgentExecutionPolicy(prompt);
|
|
2917
|
-
console.log(JSON.stringify({
|
|
2918
|
-
success: evaluation.executorSucceeded,
|
|
2919
|
-
mode: 'agent',
|
|
2920
|
-
model: routingPolicy.selectedModel,
|
|
2921
|
-
routingPolicy,
|
|
2922
|
-
taskId: null,
|
|
2923
|
-
contextId: null,
|
|
2924
|
-
partial: false,
|
|
2925
|
-
content: summary,
|
|
2926
|
-
statusHeadline: evaluation.statusHeadline,
|
|
2927
|
-
tasksSucceeded: 1,
|
|
2928
|
-
tasksTotal: 1,
|
|
2929
|
-
metadata: {
|
|
2930
|
-
source: 'template-instant',
|
|
2931
|
-
templateName: result.templateName,
|
|
2932
|
-
processingMs: result.processingMs,
|
|
2933
|
-
route: this.lastAgentRoute,
|
|
2934
|
-
},
|
|
2935
|
-
}, null, 2));
|
|
2936
|
-
}
|
|
2937
|
-
return true;
|
|
2938
|
-
}
|
|
2939
|
-
async tryDirectSingleFileFlow(prompt) {
|
|
2940
|
-
if (!this.directPromptMode || !this.tools) {
|
|
2941
|
-
return false;
|
|
2942
|
-
}
|
|
2943
|
-
const targetFile = this.inferTargetFileFromPrompt(prompt);
|
|
2944
|
-
if (!targetFile) {
|
|
2945
|
-
return false;
|
|
2946
|
-
}
|
|
2947
|
-
if (this.shouldBypassDirectSingleFileFlow(targetFile, prompt)) {
|
|
2948
|
-
return false;
|
|
2949
|
-
}
|
|
2950
|
-
const readCall = {
|
|
2951
|
-
tool: 'read_file',
|
|
2952
|
-
args: { path: targetFile },
|
|
2953
|
-
};
|
|
2954
|
-
if (!this.jsonOutput) {
|
|
2955
|
-
console.log(chalk.cyan(`⚙ Executing: ${readCall.tool}`));
|
|
2956
|
-
}
|
|
2957
|
-
const readResult = await this.tools.execute(readCall);
|
|
2958
|
-
const readSummary = this.formatToolResult(readCall, readResult);
|
|
2959
|
-
if (!this.jsonOutput) {
|
|
2960
|
-
console.log(readResult.success ? chalk.gray(readSummary) : chalk.red(readSummary));
|
|
2961
|
-
}
|
|
2962
|
-
this.messages.push({ role: 'system', content: readSummary });
|
|
2963
|
-
if (!readResult.success || !readResult.output) {
|
|
2964
|
-
return false;
|
|
2965
|
-
}
|
|
2966
|
-
let rewrittenContent = this.tryDeterministicSingleFileRewrite(prompt, targetFile, readResult.output);
|
|
2967
|
-
if (!rewrittenContent) {
|
|
2968
|
-
const rewriteMessages = [
|
|
2969
|
-
{
|
|
2970
|
-
role: 'system',
|
|
2971
|
-
content: [
|
|
2972
|
-
'You are repairing a single file for a CLI agent task.',
|
|
2973
|
-
`Return only the final contents for ${targetFile}.`,
|
|
2974
|
-
'Do not use Markdown fences.',
|
|
2975
|
-
'Do not add explanations before or after the file contents.',
|
|
2976
|
-
'Produce complete, runnable code or markup.',
|
|
2977
|
-
].join('\n'),
|
|
2978
|
-
},
|
|
2979
|
-
{
|
|
2980
|
-
role: 'user',
|
|
2981
|
-
content: [
|
|
2982
|
-
`Task: ${prompt}`,
|
|
2983
|
-
`Target file: ${targetFile}`,
|
|
2984
|
-
'Current file contents:',
|
|
2985
|
-
readResult.output,
|
|
2986
|
-
].join('\n\n'),
|
|
2987
|
-
},
|
|
2988
|
-
];
|
|
2989
|
-
const rewriteResponse = await this.callApi('Rewrite target file', () => this.api.chat(rewriteMessages, this.currentModel));
|
|
2990
|
-
rewrittenContent = this.extractFinalFileContent(rewriteResponse.message, targetFile);
|
|
2991
|
-
}
|
|
2992
|
-
if (!rewrittenContent) {
|
|
2993
|
-
return false;
|
|
2994
|
-
}
|
|
2995
|
-
const writeCall = {
|
|
2996
|
-
tool: 'write_file',
|
|
2997
|
-
args: {
|
|
2998
|
-
path: targetFile,
|
|
2999
|
-
content: rewrittenContent,
|
|
3000
|
-
},
|
|
3001
|
-
};
|
|
3002
|
-
if (!this.jsonOutput) {
|
|
3003
|
-
console.log(chalk.cyan(`⚙ Executing: ${writeCall.tool}`));
|
|
3004
|
-
}
|
|
3005
|
-
const writeResult = await this.tools.execute(writeCall);
|
|
3006
|
-
const writeSummary = this.formatToolResult(writeCall, writeResult);
|
|
3007
|
-
if (!this.jsonOutput) {
|
|
3008
|
-
console.log(writeResult.success ? chalk.gray(writeSummary) : chalk.red(writeSummary));
|
|
3009
|
-
}
|
|
3010
|
-
this.messages.push({ role: 'system', content: writeSummary });
|
|
3011
|
-
if (!writeResult.success) {
|
|
3012
|
-
return false;
|
|
3013
|
-
}
|
|
3014
|
-
const previewGate = await this.callApi('Run Template Service preview gate', () => this.api.runTemplateServicePreviewGate(prompt, {
|
|
3015
|
-
workspacePath: this.currentProjectPath,
|
|
3016
|
-
projectPath: this.currentProjectPath,
|
|
3017
|
-
targetPath: this.currentProjectPath,
|
|
3018
|
-
rawPrompt: prompt,
|
|
3019
|
-
executionSurface: 'cli',
|
|
3020
|
-
clientSurface: 'cli',
|
|
3021
|
-
}));
|
|
3022
|
-
const success = previewGate.required ? previewGate.passed === true : true;
|
|
3023
|
-
if (!success) {
|
|
3024
|
-
process.exitCode = 1;
|
|
3025
|
-
}
|
|
3026
|
-
if (this.jsonOutput) {
|
|
3027
|
-
console.log(JSON.stringify({
|
|
3028
|
-
success,
|
|
3029
|
-
mode: 'agent',
|
|
3030
|
-
model: this.currentModel,
|
|
3031
|
-
partial: false,
|
|
3032
|
-
content: `Updated ${targetFile}.`,
|
|
3033
|
-
metadata: {
|
|
3034
|
-
executionPath: 'direct-single-file',
|
|
3035
|
-
targetFile,
|
|
3036
|
-
previewGate,
|
|
3037
|
-
},
|
|
3038
|
-
}, null, 2));
|
|
3039
|
-
}
|
|
3040
|
-
else {
|
|
3041
|
-
console.log(`Updated ${targetFile}.`);
|
|
3042
|
-
if (previewGate.required) {
|
|
3043
|
-
if (previewGate.passed) {
|
|
3044
|
-
console.log(chalk.gray(`Template Service preview gate: passed via ${previewGate.backendUrl || 'unknown backend'}`));
|
|
3045
|
-
}
|
|
3046
|
-
else {
|
|
3047
|
-
console.log(chalk.yellow(`Template Service preview gate: failed${previewGate.error ? ` - ${previewGate.error}` : ''}`));
|
|
3048
|
-
}
|
|
3049
|
-
}
|
|
3050
|
-
}
|
|
3051
|
-
return true;
|
|
3052
|
-
}
|
|
3053
2567
|
taskRequiresWorkspaceChanges(prompt) {
|
|
3054
2568
|
return promptRequiresWorkspaceChanges(prompt);
|
|
3055
2569
|
}
|
|
@@ -3129,15 +2643,7 @@ export class ChatCommand {
|
|
|
3129
2643
|
console.log();
|
|
3130
2644
|
}
|
|
3131
2645
|
// Reset streaming counters for new workflow
|
|
3132
|
-
this.
|
|
3133
|
-
this.v3ToolCallCount = 0;
|
|
3134
|
-
this.v3LastActivity = Date.now();
|
|
3135
|
-
this.v3StreamingStarted = false;
|
|
3136
|
-
this.v3StreamedAnswerBuffer = '';
|
|
3137
|
-
this.v3StreamedAnswerDisplayed = false;
|
|
3138
|
-
this.v3StartSeen = false;
|
|
3139
|
-
this.v3SeenToolCalls.clear();
|
|
3140
|
-
this.v3SeenToolResults.clear();
|
|
2646
|
+
this.streamState.reset();
|
|
3141
2647
|
emitDeckEvent({ type: 'agent_start' });
|
|
3142
2648
|
const taskDisplay = new TaskDisplay(['Analyse workspace', 'Execute tasks', 'Validate output', 'Self-heal'], false);
|
|
3143
2649
|
taskDisplay.start(0);
|
|
@@ -3216,6 +2722,12 @@ export class ChatCommand {
|
|
|
3216
2722
|
|| isAgentRetryPrompt(contextualPrompt)
|
|
3217
2723
|
|| isBuiltContinuePrompt(contextualPrompt)
|
|
3218
2724
|
|| isBuiltRetryPrompt(contextualPrompt);
|
|
2725
|
+
const initialExecutionStatus = isAgentRetryPrompt(contextualPrompt)
|
|
2726
|
+
|| isBuiltRetryPrompt(contextualPrompt)
|
|
2727
|
+
? 'retry'
|
|
2728
|
+
: shouldResumePlan
|
|
2729
|
+
? 'continue'
|
|
2730
|
+
: 'submitted';
|
|
3219
2731
|
const agentExecutionHints = shouldResumePlan
|
|
3220
2732
|
? {
|
|
3221
2733
|
...executionHints,
|
|
@@ -3243,7 +2755,7 @@ export class ChatCommand {
|
|
|
3243
2755
|
userTier: String(this.config.get('subscription')?.plan || '').trim() || null,
|
|
3244
2756
|
taskType: agentTaskType,
|
|
3245
2757
|
workflowType,
|
|
3246
|
-
status:
|
|
2758
|
+
status: initialExecutionStatus,
|
|
3247
2759
|
submittedAt: new Date().toISOString(),
|
|
3248
2760
|
updatedAt: new Date().toISOString(),
|
|
3249
2761
|
failedTaskIds: resumeTaskIds,
|
|
@@ -3255,7 +2767,7 @@ export class ChatCommand {
|
|
|
3255
2767
|
if (this.shouldStartWorkspaceWatcher(workspacePath)) {
|
|
3256
2768
|
watcher = new WorkspaceWatcher({
|
|
3257
2769
|
workspaceRoot: workspacePath,
|
|
3258
|
-
onFileChange: (relativePath,
|
|
2770
|
+
onFileChange: (relativePath, _content, action) => {
|
|
3259
2771
|
this.logger.debug(`Local change detected: ${action} ${relativePath}`);
|
|
3260
2772
|
},
|
|
3261
2773
|
});
|
|
@@ -3329,10 +2841,7 @@ export class ChatCommand {
|
|
|
3329
2841
|
noteAnalysisToolUse(toolName, liveOutcome);
|
|
3330
2842
|
}
|
|
3331
2843
|
else if (event.type === 'complete') {
|
|
3332
|
-
|
|
3333
|
-
if (Number.isFinite(tt) && tt > 0) {
|
|
3334
|
-
liveOutcome.tasksTotal = tt;
|
|
3335
|
-
}
|
|
2844
|
+
handleRunCompleteEvent(event, liveOutcome);
|
|
3336
2845
|
if (Number(event.discovery_tools_used) > 0) {
|
|
3337
2846
|
liveOutcome.analysisToolsUsed = Math.max(liveOutcome.analysisToolsUsed, Number(event.discovery_tools_used));
|
|
3338
2847
|
}
|
|
@@ -3379,23 +2888,23 @@ export class ChatCommand {
|
|
|
3379
2888
|
if (spinner) {
|
|
3380
2889
|
spinner.stop();
|
|
3381
2890
|
}
|
|
3382
|
-
if (this.
|
|
2891
|
+
if (this.streamState.streamingStarted && !this.streamState.streamedAnswerDisplayed) {
|
|
3383
2892
|
this.displayV3StreamedAnswer();
|
|
3384
2893
|
}
|
|
3385
2894
|
const previewGate = (response.metadata?.previewGate || null);
|
|
3386
2895
|
const workspaceHasOutput = this.api.hasAgentWorkspaceOutput(workspaceContext);
|
|
3387
2896
|
const changedFileCount = response.changedFiles ? Object.keys(response.changedFiles).length : 0;
|
|
3388
2897
|
const requiresWorkspaceChanges = taskRequiresWorkspaceChangesWithContext(contextualPrompt, intentContext);
|
|
3389
|
-
const answerContent = normalizeAgentAnswerContent(response.content, this.
|
|
2898
|
+
const answerContent = normalizeAgentAnswerContent(response.content, this.streamState.streamedAnswerBuffer);
|
|
3390
2899
|
liveOutcome.changedFileCount = changedFileCount;
|
|
3391
2900
|
liveOutcome.requiresWorkspaceChanges = requiresWorkspaceChanges;
|
|
3392
2901
|
liveOutcome.workspaceHasOutput = requiresWorkspaceChanges ? workspaceHasOutput : false;
|
|
3393
2902
|
liveOutcome.answerContent = answerContent;
|
|
3394
2903
|
const success = previewGate?.required === true
|
|
3395
|
-
?
|
|
2904
|
+
? previewGate?.passed === true && previewGate?.skipped !== true
|
|
3396
2905
|
: true;
|
|
3397
2906
|
if (!success) {
|
|
3398
|
-
if (this.isLegacyAgentFallbackAllowed()) {
|
|
2907
|
+
if (previewGate?.required !== true && this.isLegacyAgentFallbackAllowed()) {
|
|
3399
2908
|
if (spinner) {
|
|
3400
2909
|
spinner.stop();
|
|
3401
2910
|
}
|
|
@@ -3408,25 +2917,16 @@ export class ChatCommand {
|
|
|
3408
2917
|
if (spinner) {
|
|
3409
2918
|
spinner.stop();
|
|
3410
2919
|
}
|
|
3411
|
-
this.
|
|
2920
|
+
if (!this.jsonOutput)
|
|
2921
|
+
this.logger.error(errorMessage);
|
|
3412
2922
|
this.messages.push({ role: 'assistant', content: errorMessage });
|
|
3413
|
-
if (this.jsonOutput) {
|
|
3414
|
-
process.exitCode = 1;
|
|
3415
|
-
console.log(JSON.stringify({
|
|
3416
|
-
success: false,
|
|
3417
|
-
mode: 'agent',
|
|
3418
|
-
model: routingPolicy.selectedModel,
|
|
3419
|
-
partial: false,
|
|
3420
|
-
content: '',
|
|
3421
|
-
error: errorMessage,
|
|
3422
|
-
metadata: { executionPath: 'v3-agent', previewGate },
|
|
3423
|
-
}, null, 2));
|
|
3424
|
-
}
|
|
3425
2923
|
watcher?.stop();
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
2924
|
+
throw new CliCommandError(errorMessage, {
|
|
2925
|
+
code: 'AGENT_PREVIEW_FAILED',
|
|
2926
|
+
category: previewGate?.partialMutation === false ? 'execution' : 'partial_mutation',
|
|
2927
|
+
partialMutation: previewGate?.partialMutation ?? 'unknown',
|
|
2928
|
+
details: { executionPath: 'v3-agent', previewGate },
|
|
2929
|
+
});
|
|
3430
2930
|
}
|
|
3431
2931
|
if (!this.jsonOutput && previewGate?.required) {
|
|
3432
2932
|
if (previewGate.passed) {
|
|
@@ -3459,7 +2959,10 @@ export class ChatCommand {
|
|
|
3459
2959
|
liveOutcome.answerContent = '';
|
|
3460
2960
|
}
|
|
3461
2961
|
const runEvaluation = evaluateExecutorSuccess(liveOutcome);
|
|
3462
|
-
let executorSucceeded = runEvaluation.executorSucceeded;
|
|
2962
|
+
let executorSucceeded = runEvaluation.executorSucceeded && success;
|
|
2963
|
+
if (!success && !liveOutcome.executorError) {
|
|
2964
|
+
liveOutcome.executorError = previewGate?.error || 'Template Service preview proof did not pass.';
|
|
2965
|
+
}
|
|
3463
2966
|
let finalAnswer = answerContent;
|
|
3464
2967
|
if (isToolEvidenceStubAnswer(finalAnswer)) {
|
|
3465
2968
|
finalAnswer = '';
|
|
@@ -3479,7 +2982,7 @@ export class ChatCommand {
|
|
|
3479
2982
|
executorSucceeded = false;
|
|
3480
2983
|
}
|
|
3481
2984
|
if (!this.jsonOutput && !this.directPromptMode) {
|
|
3482
|
-
if (this.
|
|
2985
|
+
if (this.streamState.streamedAnswerDisplayed) {
|
|
3483
2986
|
// Highlighted report already printed once above.
|
|
3484
2987
|
}
|
|
3485
2988
|
else if (isSubstantiveAgentAnswer(finalAnswer)) {
|
|
@@ -3575,9 +3078,6 @@ export class ChatCommand {
|
|
|
3575
3078
|
}
|
|
3576
3079
|
taskDisplay.finalize();
|
|
3577
3080
|
// ────────────────────────────────────────────────────────────────
|
|
3578
|
-
if (!executorSucceeded) {
|
|
3579
|
-
process.exitCode = 1;
|
|
3580
|
-
}
|
|
3581
3081
|
this.commitAgentRunOutcome({
|
|
3582
3082
|
prompt,
|
|
3583
3083
|
originalPrompt: null,
|
|
@@ -3618,10 +3118,24 @@ export class ChatCommand {
|
|
|
3618
3118
|
this.printAgentRunSummary(this.lastAgentRunOutcome, runEvaluation, changedFileCount);
|
|
3619
3119
|
}
|
|
3620
3120
|
}
|
|
3121
|
+
if (!executorSucceeded) {
|
|
3122
|
+
throw new CliCommandError(runEvaluation.statusHeadline, {
|
|
3123
|
+
code: 'AGENT_INCOMPLETE',
|
|
3124
|
+
category: response.partial === true || liveOutcome.tasksSucceeded > 0 ? 'partial_mutation' : 'execution',
|
|
3125
|
+
partialMutation: response.partial === true || liveOutcome.tasksSucceeded > 0 ? 'unknown' : false,
|
|
3126
|
+
details: {
|
|
3127
|
+
mode: 'agent',
|
|
3128
|
+
model: routingPolicy.selectedModel,
|
|
3129
|
+
taskId: response.taskId || null,
|
|
3130
|
+
contextId: response.contextId || null,
|
|
3131
|
+
statusHeadline: runEvaluation.statusHeadline,
|
|
3132
|
+
tasksSucceeded: liveOutcome.tasksSucceeded,
|
|
3133
|
+
tasksTotal: liveOutcome.tasksTotal,
|
|
3134
|
+
previewGate,
|
|
3135
|
+
},
|
|
3136
|
+
});
|
|
3137
|
+
}
|
|
3621
3138
|
if (this.jsonOutput) {
|
|
3622
|
-
if (!executorSucceeded) {
|
|
3623
|
-
process.exitCode = 1;
|
|
3624
|
-
}
|
|
3625
3139
|
emitAgentJsonOutput({
|
|
3626
3140
|
success: executorSucceeded,
|
|
3627
3141
|
mode: 'agent',
|
|
@@ -3654,6 +3168,11 @@ export class ChatCommand {
|
|
|
3654
3168
|
catch (error) {
|
|
3655
3169
|
this.stopV3IdleWatch();
|
|
3656
3170
|
watcher?.stop();
|
|
3171
|
+
if (error instanceof CliCommandError) {
|
|
3172
|
+
if (spinner)
|
|
3173
|
+
spinner.stop();
|
|
3174
|
+
throw error;
|
|
3175
|
+
}
|
|
3657
3176
|
if (!this.api.hasAgentWorkspaceOutput(workspaceContext)) {
|
|
3658
3177
|
const recovered = await this.tryRecoverV3ServiceAndRetry(executionPrompt, prompt, workspaceContext, routingPolicy, spinner, error);
|
|
3659
3178
|
if (recovered) {
|
|
@@ -3720,25 +3239,7 @@ export class ChatCommand {
|
|
|
3720
3239
|
unfinishedTaskIds: [...liveOutcome.unfinishedTaskIds],
|
|
3721
3240
|
error: liveOutcome.executorError || safeDetail || 'Connection aborted',
|
|
3722
3241
|
});
|
|
3723
|
-
if (!this.jsonOutput) {
|
|
3724
|
-
process.exitCode = 1;
|
|
3725
|
-
}
|
|
3726
|
-
if (this.jsonOutput) {
|
|
3727
|
-
process.exitCode = 1;
|
|
3728
|
-
console.log(JSON.stringify({
|
|
3729
|
-
success: false,
|
|
3730
|
-
mode: 'agent',
|
|
3731
|
-
model: routingPolicy.selectedModel,
|
|
3732
|
-
partial: liveOutcome.tasksSucceeded > 0 && liveOutcome.tasksTotal > 0,
|
|
3733
|
-
content: '',
|
|
3734
|
-
error: errorMessage,
|
|
3735
|
-
statusHeadline: evaluateExecutorSuccess(liveOutcome).statusHeadline,
|
|
3736
|
-
tasksSucceeded: liveOutcome.tasksSucceeded,
|
|
3737
|
-
tasksTotal: liveOutcome.tasksTotal,
|
|
3738
|
-
metadata: { executionPath: 'v3-agent' },
|
|
3739
|
-
}, null, 2));
|
|
3740
|
-
}
|
|
3741
|
-
else if (!this.directPromptMode) {
|
|
3242
|
+
if (!this.jsonOutput && !this.directPromptMode) {
|
|
3742
3243
|
const failedEval = evaluateExecutorSuccess({
|
|
3743
3244
|
...liveOutcome,
|
|
3744
3245
|
executorFailed: true,
|
|
@@ -3751,7 +3252,20 @@ export class ChatCommand {
|
|
|
3751
3252
|
this.printAgentRunSummary(this.lastAgentRunOutcome, failedEval, 0);
|
|
3752
3253
|
}
|
|
3753
3254
|
}
|
|
3754
|
-
|
|
3255
|
+
throw new CliCommandError(errorMessage, {
|
|
3256
|
+
code: 'AGENT_EXECUTION_FAILED',
|
|
3257
|
+
category: liveOutcome.tasksSucceeded > 0 ? 'partial_mutation' : 'execution',
|
|
3258
|
+
partialMutation: liveOutcome.tasksSucceeded > 0 ? 'unknown' : false,
|
|
3259
|
+
details: {
|
|
3260
|
+
executionPath: 'v3-agent',
|
|
3261
|
+
mode: 'agent',
|
|
3262
|
+
model: routingPolicy.selectedModel,
|
|
3263
|
+
tasksSucceeded: liveOutcome.tasksSucceeded,
|
|
3264
|
+
tasksTotal: liveOutcome.tasksTotal,
|
|
3265
|
+
statusHeadline: evaluateExecutorSuccess(liveOutcome).statusHeadline,
|
|
3266
|
+
},
|
|
3267
|
+
cause: error,
|
|
3268
|
+
});
|
|
3755
3269
|
}
|
|
3756
3270
|
}
|
|
3757
3271
|
async tryRecoverV3ServiceAndRetry(executionPrompt, rawPrompt, workspaceContext, routingPolicy, spinner, error) {
|
|
@@ -3765,12 +3279,13 @@ export class ChatCommand {
|
|
|
3765
3279
|
if (!this.jsonOutput) {
|
|
3766
3280
|
console.log(chalk.yellow(`V3 recovery: ${recovery.message} Retrying once...`));
|
|
3767
3281
|
}
|
|
3768
|
-
this.
|
|
3769
|
-
this.
|
|
3770
|
-
this.
|
|
3771
|
-
this.
|
|
3772
|
-
this.
|
|
3282
|
+
this.streamState.iterationCount = 0;
|
|
3283
|
+
this.streamState.toolCallCount = 0;
|
|
3284
|
+
this.streamState.noteActivity();
|
|
3285
|
+
this.streamState.streamingStarted = false;
|
|
3286
|
+
this.streamState.startSeen = false;
|
|
3773
3287
|
try {
|
|
3288
|
+
this.updateAgentExecutionCheckpoint({ status: 'running' });
|
|
3774
3289
|
const retryPriorPrompt = this.getPreviousActionablePrompt();
|
|
3775
3290
|
const retryContext = { priorPrompt: retryPriorPrompt || null };
|
|
3776
3291
|
const retryTaskType = inferAgentTaskTypeWithContext(rawPrompt, retryContext);
|
|
@@ -3797,27 +3312,40 @@ export class ChatCommand {
|
|
|
3797
3312
|
if (spinner && spinner.isSpinning) {
|
|
3798
3313
|
spinner.stop();
|
|
3799
3314
|
}
|
|
3800
|
-
if (this.
|
|
3315
|
+
if (this.streamState.streamingStarted) {
|
|
3801
3316
|
process.stdout.write('\n');
|
|
3802
3317
|
}
|
|
3318
|
+
if (retryResponse.partial === true) {
|
|
3319
|
+
this.updateAgentExecutionCheckpoint({
|
|
3320
|
+
status: 'failed',
|
|
3321
|
+
contextId: retryResponse.contextId || this.currentSession?.activeAgentRun?.contextId,
|
|
3322
|
+
error: 'Recovery run returned a partial result.',
|
|
3323
|
+
});
|
|
3324
|
+
return false;
|
|
3325
|
+
}
|
|
3803
3326
|
if (this.jsonOutput) {
|
|
3804
|
-
|
|
3327
|
+
emitAgentJsonOutput({
|
|
3805
3328
|
success: true,
|
|
3806
3329
|
mode: 'agent',
|
|
3807
3330
|
model: routingPolicy.selectedModel,
|
|
3808
3331
|
routingPolicy,
|
|
3809
3332
|
taskId: retryResponse.taskId || null,
|
|
3810
3333
|
contextId: retryResponse.contextId || null,
|
|
3811
|
-
partial:
|
|
3334
|
+
partial: false,
|
|
3812
3335
|
content: retryResponse.content || 'V3 agent workflow completed after recovery.',
|
|
3813
3336
|
metadata: { ...(retryResponse.metadata || {}), recoveryAttempted: true },
|
|
3814
|
-
}
|
|
3337
|
+
});
|
|
3815
3338
|
}
|
|
3816
|
-
else if (!this.
|
|
3339
|
+
else if (!this.streamState.streamingStarted && retryResponse.content) {
|
|
3817
3340
|
console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
|
|
3818
3341
|
console.log(retryResponse.content);
|
|
3819
3342
|
}
|
|
3820
3343
|
this.messages.push({ role: 'assistant', content: retryResponse.content || 'V3 agent workflow completed after recovery.' });
|
|
3344
|
+
this.updateAgentExecutionCheckpoint({
|
|
3345
|
+
status: 'completed',
|
|
3346
|
+
contextId: retryResponse.contextId || this.currentSession?.activeAgentRun?.contextId,
|
|
3347
|
+
error: null,
|
|
3348
|
+
});
|
|
3821
3349
|
return true;
|
|
3822
3350
|
}
|
|
3823
3351
|
catch {
|
|
@@ -4424,6 +3952,13 @@ export class ChatCommand {
|
|
|
4424
3952
|
}
|
|
4425
3953
|
inferTargetFilesFromPrompt(prompt) {
|
|
4426
3954
|
const candidates = [];
|
|
3955
|
+
const sourceExtensions = new Set([
|
|
3956
|
+
'.c', '.cc', '.cpp', '.cs', '.css', '.csv', '.go', '.graphql', '.h', '.hpp',
|
|
3957
|
+
'.htm', '.html', '.ini', '.java', '.js', '.json', '.jsx', '.less', '.md',
|
|
3958
|
+
'.mjs', '.php', '.prisma', '.py', '.rb', '.rs', '.sass', '.scss', '.sh',
|
|
3959
|
+
'.sql', '.svelte', '.svg', '.toml', '.ts', '.tsx', '.txt', '.vue', '.xml',
|
|
3960
|
+
'.yaml', '.yml',
|
|
3961
|
+
]);
|
|
4427
3962
|
const matches = Array.from(prompt.matchAll(/([A-Za-z0-9_./-]+\.[A-Za-z0-9_-]+)/g));
|
|
4428
3963
|
for (const match of matches) {
|
|
4429
3964
|
const candidate = match[1];
|
|
@@ -4431,7 +3966,15 @@ export class ChatCommand {
|
|
|
4431
3966
|
continue;
|
|
4432
3967
|
}
|
|
4433
3968
|
const resolved = path.resolve(this.currentProjectPath, candidate);
|
|
4434
|
-
|
|
3969
|
+
const extension = path.extname(candidate).toLowerCase();
|
|
3970
|
+
// Dotted API/property references (for example `element.style`) are not
|
|
3971
|
+
// workspace files. Accept a non-standard extension only when that exact
|
|
3972
|
+
// path already exists, so real uncommon project files remain addressable.
|
|
3973
|
+
if (!sourceExtensions.has(extension) && !fs.existsSync(resolved)) {
|
|
3974
|
+
continue;
|
|
3975
|
+
}
|
|
3976
|
+
const relative = path.relative(this.currentProjectPath, resolved);
|
|
3977
|
+
if (relative && !relative.startsWith('..') && !path.isAbsolute(relative)) {
|
|
4435
3978
|
if (!candidates.includes(candidate)) {
|
|
4436
3979
|
candidates.push(candidate);
|
|
4437
3980
|
}
|
|
@@ -4442,36 +3985,6 @@ export class ChatCommand {
|
|
|
4442
3985
|
inferTargetFileFromPrompt(prompt) {
|
|
4443
3986
|
return this.inferTargetFilesFromPrompt(prompt)[0] || null;
|
|
4444
3987
|
}
|
|
4445
|
-
workspaceContainsHtmlEntry(rootDir) {
|
|
4446
|
-
const stack = [rootDir];
|
|
4447
|
-
while (stack.length > 0) {
|
|
4448
|
-
const currentDir = stack.pop();
|
|
4449
|
-
if (!currentDir) {
|
|
4450
|
-
continue;
|
|
4451
|
-
}
|
|
4452
|
-
let entries = [];
|
|
4453
|
-
try {
|
|
4454
|
-
entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
4455
|
-
}
|
|
4456
|
-
catch {
|
|
4457
|
-
continue;
|
|
4458
|
-
}
|
|
4459
|
-
for (const entry of entries) {
|
|
4460
|
-
if (entry.name === 'node_modules' || entry.name === '.git') {
|
|
4461
|
-
continue;
|
|
4462
|
-
}
|
|
4463
|
-
const fullPath = path.join(currentDir, entry.name);
|
|
4464
|
-
if (entry.isDirectory()) {
|
|
4465
|
-
stack.push(fullPath);
|
|
4466
|
-
continue;
|
|
4467
|
-
}
|
|
4468
|
-
if (entry.isFile() && entry.name.toLowerCase().endsWith('.html')) {
|
|
4469
|
-
return true;
|
|
4470
|
-
}
|
|
4471
|
-
}
|
|
4472
|
-
}
|
|
4473
|
-
return false;
|
|
4474
|
-
}
|
|
4475
3988
|
shouldBypassDirectSingleFileFlow(targetFile, prompt) {
|
|
4476
3989
|
const referencedFiles = this.inferTargetFilesFromPrompt(prompt);
|
|
4477
3990
|
const editableFiles = referencedFiles.filter((filePath) => !this.isProtectedFileReferenceSafe(prompt, filePath));
|
|
@@ -4488,36 +4001,6 @@ export class ChatCommand {
|
|
|
4488
4001
|
return true;
|
|
4489
4002
|
return false;
|
|
4490
4003
|
}
|
|
4491
|
-
shouldPreferLocalAgentLoop(prompt) {
|
|
4492
|
-
if (this.shouldRequireV3AgentWorkflow(prompt)) {
|
|
4493
|
-
return false;
|
|
4494
|
-
}
|
|
4495
|
-
const forceV3 = /^(1|true|yes)$/i.test(String(process.env.VIGTHORIA_FORCE_V3_AGENT || ''));
|
|
4496
|
-
if (forceV3) {
|
|
4497
|
-
return false;
|
|
4498
|
-
}
|
|
4499
|
-
if (this.isBrowserTaskPrompt(prompt)) {
|
|
4500
|
-
return false;
|
|
4501
|
-
}
|
|
4502
|
-
// Product default: Agent mode routes through V3.
|
|
4503
|
-
// Local loop is opt-in for explicit offline/debug scenarios only.
|
|
4504
|
-
// Explicit local paths now influence project root resolution, not routing mode.
|
|
4505
|
-
const preferLocalOptIn = /^(1|true|yes)$/i.test(String(process.env.VIGTHORIA_PREFER_LOCAL_AGENT_LOOP || ''));
|
|
4506
|
-
if (!preferLocalOptIn) {
|
|
4507
|
-
return false;
|
|
4508
|
-
}
|
|
4509
|
-
const runtime = this.getRuntimeEnvironmentContext();
|
|
4510
|
-
if (this.directPromptMode && this.isRepoGroundedPrompt(prompt)) {
|
|
4511
|
-
return false;
|
|
4512
|
-
}
|
|
4513
|
-
if (!runtime.serverBindableWorkspace) {
|
|
4514
|
-
return true;
|
|
4515
|
-
}
|
|
4516
|
-
if (!this.directPromptMode) {
|
|
4517
|
-
return true;
|
|
4518
|
-
}
|
|
4519
|
-
return runtime.platform !== 'linux';
|
|
4520
|
-
}
|
|
4521
4004
|
getRuntimeEnvironmentContext() {
|
|
4522
4005
|
const osPlatform = os.platform();
|
|
4523
4006
|
const platform = osPlatform === 'win32'
|
|
@@ -4605,16 +4088,6 @@ export class ChatCommand {
|
|
|
4605
4088
|
];
|
|
4606
4089
|
return protectedPhrases.some((phrase) => normalizedPrompt.includes(phrase));
|
|
4607
4090
|
}
|
|
4608
|
-
isProtectedFileReference(prompt, filePath) {
|
|
4609
|
-
const escapedPath = filePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
4610
|
-
const protectedPatterns = [
|
|
4611
|
-
new RegExp(`do not modify\\s+[-\u001f\s\-\"][` + "'" + `]?${escapedPath}[` + "'" + `]?`, 'i'),
|
|
4612
|
-
new RegExp(`don't modify\\s+[-\u001f\s\-\"][` + "'" + `]?${escapedPath}[` + "'" + `]?`, 'i'),
|
|
4613
|
-
new RegExp(`leave\\s+[-\u001f\s\-\"][` + "'" + `]?${escapedPath}[` + "'" + `]?\\s+unchanged`, 'i'),
|
|
4614
|
-
new RegExp(`without modifying\\s+[-\u001f\s\-\"][` + "'" + `]?${escapedPath}[` + "'" + `]?`, 'i'),
|
|
4615
|
-
];
|
|
4616
|
-
return protectedPatterns.some((pattern) => pattern.test(prompt));
|
|
4617
|
-
}
|
|
4618
4091
|
buildContinuationPrompt() {
|
|
4619
4092
|
const { discovery, searchFailed } = this.agentToolEvidence;
|
|
4620
4093
|
const evidenceLines = [];
|
|
@@ -4708,109 +4181,10 @@ export class ChatCommand {
|
|
|
4708
4181
|
return lines.join('\n');
|
|
4709
4182
|
}
|
|
4710
4183
|
extractToolCalls(message) {
|
|
4711
|
-
|
|
4712
|
-
const seen = new Set();
|
|
4713
|
-
const addCall = (call) => {
|
|
4714
|
-
if (!call)
|
|
4715
|
-
return;
|
|
4716
|
-
const key = `${call.tool}::${JSON.stringify(call.args)}`;
|
|
4717
|
-
if (!seen.has(key)) {
|
|
4718
|
-
seen.add(key);
|
|
4719
|
-
calls.push(call);
|
|
4720
|
-
}
|
|
4721
|
-
};
|
|
4722
|
-
const wrapperRegex = /<tool_call>([\s\S]*?)<\/tool_call>/g;
|
|
4723
|
-
for (const match of message.matchAll(wrapperRegex)) {
|
|
4724
|
-
addCall(this.parseToolPayload(match[1] || ''));
|
|
4725
|
-
}
|
|
4726
|
-
for (const call of this.parseLegacyFunctionToolCalls(message)) {
|
|
4727
|
-
addCall(call);
|
|
4728
|
-
}
|
|
4729
|
-
return calls;
|
|
4730
|
-
}
|
|
4731
|
-
normalizeCliToolName(name) {
|
|
4732
|
-
const normalized = name.trim().toLowerCase();
|
|
4733
|
-
const aliases = {
|
|
4734
|
-
list_directory: 'list_dir',
|
|
4735
|
-
listdirectory: 'list_dir',
|
|
4736
|
-
ls: 'list_dir',
|
|
4737
|
-
dir: 'list_dir',
|
|
4738
|
-
readfile: 'read_file',
|
|
4739
|
-
writefile: 'write_file',
|
|
4740
|
-
editfile: 'edit_file',
|
|
4741
|
-
shell: 'bash',
|
|
4742
|
-
command: 'bash',
|
|
4743
|
-
run_command: 'bash',
|
|
4744
|
-
};
|
|
4745
|
-
return aliases[normalized] || normalized;
|
|
4746
|
-
}
|
|
4747
|
-
parseLegacyFunctionToolCalls(message) {
|
|
4748
|
-
const calls = [];
|
|
4749
|
-
const functionRegex = /<function\s*=\s*["']?([A-Za-z0-9_-]+)["']?>\s*([\s\S]*?)(?:<\/function>|$)/gi;
|
|
4750
|
-
for (const match of message.matchAll(functionRegex)) {
|
|
4751
|
-
const tool = this.normalizeCliToolName(match[1] || '');
|
|
4752
|
-
if (!tool)
|
|
4753
|
-
continue;
|
|
4754
|
-
const body = match[2] || '';
|
|
4755
|
-
const args = {};
|
|
4756
|
-
const parameterRegex = /<parameter\s*=\s*["']?([A-Za-z0-9_-]+)["']?>\s*([\s\S]*?)(?=<parameter\s*=|<\/function>|<\/parameter>|$)/gi;
|
|
4757
|
-
for (const paramMatch of body.matchAll(parameterRegex)) {
|
|
4758
|
-
const key = paramMatch[1] || '';
|
|
4759
|
-
if (!key)
|
|
4760
|
-
continue;
|
|
4761
|
-
args[key] = (paramMatch[2] || '').replace(/<\/parameter>\s*$/i, '').trim();
|
|
4762
|
-
}
|
|
4763
|
-
calls.push({ tool, args });
|
|
4764
|
-
}
|
|
4765
|
-
return calls;
|
|
4766
|
-
}
|
|
4767
|
-
parseToolPayload(payload) {
|
|
4768
|
-
const normalized = payload.trim();
|
|
4769
|
-
if (!normalized) {
|
|
4770
|
-
return null;
|
|
4771
|
-
}
|
|
4772
|
-
try {
|
|
4773
|
-
const parsed = JSON.parse(normalized);
|
|
4774
|
-
if (typeof parsed.tool !== 'string') {
|
|
4775
|
-
return null;
|
|
4776
|
-
}
|
|
4777
|
-
const args = {};
|
|
4778
|
-
if (parsed.args && typeof parsed.args === 'object') {
|
|
4779
|
-
for (const [key, value] of Object.entries(parsed.args)) {
|
|
4780
|
-
args[key] = typeof value === 'string' ? value : JSON.stringify(value);
|
|
4781
|
-
}
|
|
4782
|
-
}
|
|
4783
|
-
return { tool: this.normalizeCliToolName(parsed.tool), args };
|
|
4784
|
-
}
|
|
4785
|
-
catch {
|
|
4786
|
-
return null;
|
|
4787
|
-
}
|
|
4184
|
+
return this.toolCallParser.extract(message);
|
|
4788
4185
|
}
|
|
4789
4186
|
stripToolPayloads(message) {
|
|
4790
|
-
return message
|
|
4791
|
-
.replace(/<tool_call>[\s\S]*?<\/tool_call>/g, '')
|
|
4792
|
-
.replace(/<function\s*=\s*["']?[A-Za-z0-9_-]+["']?>[\s\S]*?(?:<\/function>|$)/gi, '')
|
|
4793
|
-
.trim();
|
|
4794
|
-
}
|
|
4795
|
-
extractFinalFileContent(message, targetFile) {
|
|
4796
|
-
const trimmed = message.trim();
|
|
4797
|
-
if (!trimmed) {
|
|
4798
|
-
return '';
|
|
4799
|
-
}
|
|
4800
|
-
const fencedMatch = trimmed.match(/```(?:[A-Za-z0-9_-]+)?\n([\s\S]*?)```/);
|
|
4801
|
-
if (fencedMatch && fencedMatch[1]) {
|
|
4802
|
-
return fencedMatch[1].trim();
|
|
4803
|
-
}
|
|
4804
|
-
const extension = path.extname(targetFile).toLowerCase();
|
|
4805
|
-
if (extension === '.html') {
|
|
4806
|
-
const htmlStart = trimmed.indexOf('<!DOCTYPE html>') >= 0
|
|
4807
|
-
? trimmed.indexOf('<!DOCTYPE html>')
|
|
4808
|
-
: trimmed.indexOf('<html');
|
|
4809
|
-
if (htmlStart >= 0) {
|
|
4810
|
-
return trimmed.slice(htmlStart).trim();
|
|
4811
|
-
}
|
|
4812
|
-
}
|
|
4813
|
-
return trimmed;
|
|
4187
|
+
return this.toolCallParser.strip(message);
|
|
4814
4188
|
}
|
|
4815
4189
|
/**
|
|
4816
4190
|
* Synthesize a best-effort answer from tool evidence already collected
|
|
@@ -4839,8 +4213,8 @@ export class ChatCommand {
|
|
|
4839
4213
|
resolveDirectModeCompletion(prompt, visibleText) {
|
|
4840
4214
|
// Sanitize first — strip tool output and echoed instructions before
|
|
4841
4215
|
// deciding if the model actually answered the question.
|
|
4842
|
-
const sanitized =
|
|
4843
|
-
if (sanitized && !
|
|
4216
|
+
const sanitized = sanitizeDirectModeOutput((visibleText || '').trim());
|
|
4217
|
+
if (sanitized && !isDirectModeFollowUpQuestion(sanitized)) {
|
|
4844
4218
|
return sanitized;
|
|
4845
4219
|
}
|
|
4846
4220
|
const fallback = this.buildLocalAnalysisFallback(prompt);
|
|
@@ -4849,95 +4223,6 @@ export class ChatCommand {
|
|
|
4849
4223
|
}
|
|
4850
4224
|
return sanitized || 'Task complete.';
|
|
4851
4225
|
}
|
|
4852
|
-
/**
|
|
4853
|
-
* Strip system-prompt echoes, tool execution headers, grounding-rule
|
|
4854
|
-
* parroting, recovery banners, and multi-line tool output blocks from
|
|
4855
|
-
* the model's final answer so that --json output contains only the
|
|
4856
|
-
* substantive answer.
|
|
4857
|
-
*/
|
|
4858
|
-
sanitizeDirectModeOutput(text) {
|
|
4859
|
-
let cleaned = this.stripHiddenThoughtBlocks(text);
|
|
4860
|
-
cleaned = cleaned
|
|
4861
|
-
.replace(/<\|mask_start\|>[\s\S]*?<\|mask_end\|>/g, '')
|
|
4862
|
-
.replace(/<think>[\s\S]*?<\/think>/gi, '')
|
|
4863
|
-
.replace(/<\|(?:mask_start|mask_end)\|>/g, '');
|
|
4864
|
-
// ── Phase 1: Strip entire tool-output blocks ──
|
|
4865
|
-
// Matches "Tool <name> succeeded/FAILED." through the next blank line,
|
|
4866
|
-
// next tool header, or end-of-string. The DOTALL-like [\s\S]*? is
|
|
4867
|
-
// terminated by whichever boundary comes first.
|
|
4868
|
-
cleaned = cleaned.replace(/Tool (?:read_file|grep|list_dir|glob|bash|write_file|edit_file|ssh_exec|task|multi_edit|codebase_search) (?:succeeded|FAILED)\.[\s\S]*?(?=\nTool |\n\n|$)/g, '');
|
|
4869
|
-
// ── Phase 2: Strip echoed system-prompt / grounding lines ──
|
|
4870
|
-
const contaminationPatterns = [
|
|
4871
|
-
/^\[Agent recovered from backend failure[^\]]*\]\s*/m,
|
|
4872
|
-
/^Evidence gathered before backend failure:?\s*/m,
|
|
4873
|
-
/^MANDATORY CROSS-FILE EVIDENCE[^\n]*\n?/m,
|
|
4874
|
-
/^CONFIRMED CONFLICTING keys[^\n]*/m,
|
|
4875
|
-
/^Keys found ONLY in [^\n]*/m,
|
|
4876
|
-
/^CONSTRAINT: Your answer MUST[^\n]*/m,
|
|
4877
|
-
/^GROUNDING CHECK:[^\n]*/m,
|
|
4878
|
-
/^VERIFICATION PROTOCOL[^\n]*/m,
|
|
4879
|
-
/^Quality gate:[^\n]*/m,
|
|
4880
|
-
/^Evidence collected:[^\n]*/m,
|
|
4881
|
-
/^Warning: \d+ search tool[^\n]*/m,
|
|
4882
|
-
/^Tool results received for direct mode[^\n]*/m,
|
|
4883
|
-
/^Step \d+ complete\. Task:[^\n]*/m,
|
|
4884
|
-
/^Original user request:[^\n]*/m,
|
|
4885
|
-
/^Project root boundary:[^\n]*/m,
|
|
4886
|
-
/^Do not declare success[^\n]*/m,
|
|
4887
|
-
/^Keep working from concrete[^\n]*/m,
|
|
4888
|
-
/^Continue with tool calls if more[^\n]*/m,
|
|
4889
|
-
/^IMPORTANT: Your response must be ONLY[^\n]*/m,
|
|
4890
|
-
/^Because this is a debugging[^\n]*/m,
|
|
4891
|
-
/^If a user is asking which file[^\n]*/m,
|
|
4892
|
-
/^If the request is already[^\n]*/m,
|
|
4893
|
-
/^If more work is required[^\n]*/m,
|
|
4894
|
-
/^Do not ask follow-up[^\n]*/m,
|
|
4895
|
-
/^CRITICAL GROUNDING RULE:[^\n]*/m,
|
|
4896
|
-
/^CROSS-FILE ATTRIBUTION:[^\n]*/m,
|
|
4897
|
-
/^EVIDENCE-GROUNDING RULE:[^\n]*/m,
|
|
4898
|
-
/^CROSS-FILE RULE:[^\n]*/m,
|
|
4899
|
-
/^OUTPUT DISCIPLINE:[^\n]*/m,
|
|
4900
|
-
/^Vigthoria CLI agent operating contract\.[^\n]*/m,
|
|
4901
|
-
/^You are operating inside the project root:[^\n]*/m,
|
|
4902
|
-
/^CRITICAL: Begin working on the user's task[^\n]*/m,
|
|
4903
|
-
/^File: \S+\s*$/m,
|
|
4904
|
-
/^Search status: \S+\s*$/m,
|
|
4905
|
-
/^Output:\s*$/m,
|
|
4906
|
-
/^\[\.\.\. ?truncated\]\s*$/m,
|
|
4907
|
-
/^---\s*$/m,
|
|
4908
|
-
];
|
|
4909
|
-
for (const pat of contaminationPatterns) {
|
|
4910
|
-
cleaned = cleaned.replace(pat, '');
|
|
4911
|
-
}
|
|
4912
|
-
// ── Phase 3: Paragraph-level filter ──
|
|
4913
|
-
// Split into paragraph blocks and discard any that are pure tool
|
|
4914
|
-
// output, file content dumps, or system-instruction echoes.
|
|
4915
|
-
const paragraphs = cleaned.split(/\n\n+/);
|
|
4916
|
-
const kept = paragraphs.filter(p => {
|
|
4917
|
-
const t = p.trim();
|
|
4918
|
-
if (!t)
|
|
4919
|
-
return false;
|
|
4920
|
-
// Discard lingering tool headers
|
|
4921
|
-
if (/^Tool (?:read_file|grep|list_dir|glob|bash|write_file) /i.test(t))
|
|
4922
|
-
return false;
|
|
4923
|
-
// Discard paragraphs that are mostly file-content dumps (many
|
|
4924
|
-
// lines of code with typical code tokens like { } ; = function class)
|
|
4925
|
-
const lines = t.split('\n');
|
|
4926
|
-
if (lines.length > 6) {
|
|
4927
|
-
const codeLines = lines.filter(l => /[{};=]|function |class |const |let |var |import |export |switch |case /.test(l));
|
|
4928
|
-
if (codeLines.length / lines.length > 0.4)
|
|
4929
|
-
return false;
|
|
4930
|
-
}
|
|
4931
|
-
return true;
|
|
4932
|
-
});
|
|
4933
|
-
cleaned = kept.join('\n\n');
|
|
4934
|
-
// Collapse multiple blank lines
|
|
4935
|
-
cleaned = cleaned.replace(/\n{3,}/g, '\n\n').trim();
|
|
4936
|
-
return cleaned || this.stripHiddenThoughtBlocks(text);
|
|
4937
|
-
}
|
|
4938
|
-
isDirectModeFollowUpQuestion(text) {
|
|
4939
|
-
return /^(would you like me|do you want me|which aspect|what aspect|can you clarify|could you clarify|should i focus on|i will follow|i understand|i('ll| will) adhere|provide your|waiting for)/i.test(text.trim());
|
|
4940
|
-
}
|
|
4941
4226
|
buildLocalAnalysisFallback(prompt) {
|
|
4942
4227
|
if (!/(analyse|analyze|audit|overview|inspect|review|summari[sz]e|actual state)/i.test(prompt)) {
|
|
4943
4228
|
return '';
|
|
@@ -4979,42 +4264,6 @@ export class ChatCommand {
|
|
|
4979
4264
|
return '';
|
|
4980
4265
|
}
|
|
4981
4266
|
}
|
|
4982
|
-
tryDeterministicSingleFileRewrite(prompt, targetFile, currentContent) {
|
|
4983
|
-
const extension = path.extname(targetFile).toLowerCase();
|
|
4984
|
-
if (extension !== '.html') {
|
|
4985
|
-
return null;
|
|
4986
|
-
}
|
|
4987
|
-
const exactText = this.extractExactTextRequirement(prompt);
|
|
4988
|
-
if (!exactText) {
|
|
4989
|
-
return null;
|
|
4990
|
-
}
|
|
4991
|
-
if (!/hero\s+(?:paragraph|copy)/i.test(prompt)) {
|
|
4992
|
-
return null;
|
|
4993
|
-
}
|
|
4994
|
-
const heroCopyPattern = /(<p[^>]*class=["'][^"']*hero-copy[^"']*["'][^>]*>)([\s\S]*?)(<\/p>)/i;
|
|
4995
|
-
if (heroCopyPattern.test(currentContent)) {
|
|
4996
|
-
const nextContent = currentContent.replace(heroCopyPattern, `$1${exactText}$3`);
|
|
4997
|
-
return nextContent !== currentContent ? nextContent : null;
|
|
4998
|
-
}
|
|
4999
|
-
const heroParagraphPattern = /(<main[^>]*class=["'][^"']*hero[^"']*["'][^>]*>[\s\S]*?<p[^>]*>)([\s\S]*?)(<\/p>)/i;
|
|
5000
|
-
if (heroParagraphPattern.test(currentContent)) {
|
|
5001
|
-
const nextContent = currentContent.replace(heroParagraphPattern, `$1${exactText}$3`);
|
|
5002
|
-
return nextContent !== currentContent ? nextContent : null;
|
|
5003
|
-
}
|
|
5004
|
-
return null;
|
|
5005
|
-
}
|
|
5006
|
-
extractExactTextRequirement(prompt) {
|
|
5007
|
-
const match = prompt.match(/reads exactly:\s*([\s\S]*?)(?:\s+Do not\b|\s+Do\b|$)/i);
|
|
5008
|
-
if (!match || !match[1]) {
|
|
5009
|
-
return null;
|
|
5010
|
-
}
|
|
5011
|
-
const normalized = match[1]
|
|
5012
|
-
.trim()
|
|
5013
|
-
.replace(/^[`"']+/, '')
|
|
5014
|
-
.replace(/[`"']+$/, '')
|
|
5015
|
-
.trim();
|
|
5016
|
-
return normalized || null;
|
|
5017
|
-
}
|
|
5018
4267
|
async executeToolCalls(toolCalls) {
|
|
5019
4268
|
if (!this.tools) {
|
|
5020
4269
|
throw new Error('Agent tools are not initialized.');
|
|
@@ -5134,6 +4383,9 @@ export class ChatCommand {
|
|
|
5134
4383
|
if (!this.currentSession?.activeAgentRun) {
|
|
5135
4384
|
return;
|
|
5136
4385
|
}
|
|
4386
|
+
if (update.status) {
|
|
4387
|
+
assertAgentStatusTransition(this.currentSession.activeAgentRun.status, update.status);
|
|
4388
|
+
}
|
|
5137
4389
|
this.currentSession.activeAgentRun = {
|
|
5138
4390
|
...this.currentSession.activeAgentRun,
|
|
5139
4391
|
...update,
|
|
@@ -5145,13 +4397,16 @@ export class ChatCommand {
|
|
|
5145
4397
|
if (this.autoApprove) {
|
|
5146
4398
|
return true;
|
|
5147
4399
|
}
|
|
4400
|
+
if (this.jsonOutput) {
|
|
4401
|
+
return false;
|
|
4402
|
+
}
|
|
5148
4403
|
const rl = readline.createInterface({
|
|
5149
4404
|
input: process.stdin,
|
|
5150
4405
|
output: process.stdout,
|
|
5151
4406
|
});
|
|
5152
4407
|
console.log(action);
|
|
5153
4408
|
const answer = await new Promise((resolve) => {
|
|
5154
|
-
rl.question(chalk.yellow('Approve? [y]es / [n]o / [a]
|
|
4409
|
+
rl.question(chalk.yellow('Approve? [y]es / [n]o / [a] exact operation this turn / [p]ersist exact operation: '), resolve);
|
|
5155
4410
|
});
|
|
5156
4411
|
rl.close();
|
|
5157
4412
|
const normalized = answer.trim().toLowerCase();
|