vigthoria-cli 1.13.18 → 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 +84 -135
- 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 +5 -42
- package/dist/commands/chat.js +462 -1122
- package/dist/commands/coding-registration.d.ts +4 -0
- package/dist/commands/coding-registration.js +24 -0
- package/dist/commands/config.js +32 -42
- 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 -1498
- 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 +45 -269
- package/dist/utils/api.js +664 -3210
- 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 +227 -99
- 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 +6 -7
- package/dist/utils/fastAgentRouter.js +21 -30
- 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 +34 -2
- 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 +86 -143
- package/install.sh +76 -81
- package/package.json +44 -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 -172
- 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
|
@@ -3,27 +3,36 @@ import * as fs from 'fs';
|
|
|
3
3
|
import * as os from 'os';
|
|
4
4
|
import * as path from 'path';
|
|
5
5
|
import * as readline from 'readline';
|
|
6
|
+
import { randomUUID } from 'crypto';
|
|
6
7
|
import { createSpinner } from '../utils/logger.js';
|
|
7
|
-
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';
|
|
8
10
|
import { AgenticTools, robustifyStreamResponse } from '../utils/tools.js';
|
|
9
|
-
import { SessionManager } from '../utils/session.js';
|
|
11
|
+
import { SessionManager, assertAgentStatusTransition } from '../utils/session.js';
|
|
10
12
|
import { BridgeClient, getBridgeClient } from '../utils/bridge-client.js';
|
|
11
13
|
import { WorkspaceWatcher } from '../utils/workspace-stream.js';
|
|
12
14
|
import { TaskDisplay } from '../utils/task-display.js';
|
|
13
15
|
import { ProjectMemoryService } from '../utils/project-memory.js';
|
|
14
16
|
import { WorkspaceBrainService } from '../utils/workspace-brain-service.js';
|
|
17
|
+
import { assembleContextState, contextLayer } from '../utils/context-state.js';
|
|
15
18
|
import { buildPersonaOverlay, normalizePersonaMode } from '../utils/persona.js';
|
|
16
19
|
import { runAgentSessionMenu, shouldShowAgentSessionMenu } from './agent-session-menu.js';
|
|
17
20
|
import { V4Command } from './v4.js';
|
|
18
21
|
import { renderDynamicHelp } from '../utils/command-menu.js';
|
|
19
|
-
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';
|
|
20
25
|
import { looksLikeMarkdownReport, renderMarkdownToTerminal, summarizeMarkdownReport, } from '../utils/terminalMarkdown.js';
|
|
21
26
|
import { emitDeckEvent, isDeckModeEnabled } from '../utils/deckEvents.js';
|
|
22
27
|
import { formatGoaSystemGrounding, splitGoaContextFromInput, } from '../utils/goaEvents.js';
|
|
23
|
-
import {
|
|
28
|
+
import { inferAgentTaskTypeWithContext, resolvePlannerAgentTimeoutMs, resolveWorkflowType, shouldSkipAnalysisRescue, taskRequiresWorkspaceChanges as promptRequiresWorkspaceChanges, taskRequiresWorkspaceChangesWithContext, buildExecutionHints, isAgentContinuePrompt, isAgentRetryPrompt, isBuiltContinuePrompt, isBuiltRetryPrompt, isBuiltWriteConfirmationPrompt, isConfirmationFollowUp, isWritePermissionGrant, } from '../utils/requestIntent.js';
|
|
24
29
|
import { resolveAgentRoute } from '../utils/agentRoute.js';
|
|
25
|
-
import { runTemplateInstantPath } from '../utils/templateInstantPath.js';
|
|
26
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';
|
|
27
36
|
// Stream health policy (corrected 2026-07-04 after TestFarm incident with
|
|
28
37
|
// Gideon Lenz / C:\vigthoria\Apps\monopoly): agentic tasks have no
|
|
29
38
|
// predictable duration, so there is intentionally NO overall wall-clock
|
|
@@ -62,7 +71,46 @@ const DEFAULT_V3_AGENT_SOFT_TIMEOUT_MS = (() => {
|
|
|
62
71
|
})();
|
|
63
72
|
/** Block-write agent JSON to VIGTHORIA_AGENT_OUTPUT (Windows KVM fsync path). */
|
|
64
73
|
function emitAgentJsonOutput(payload) {
|
|
65
|
-
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`;
|
|
66
114
|
console.log(text.trimEnd());
|
|
67
115
|
const outPath = process.env.VIGTHORIA_AGENT_OUTPUT?.trim();
|
|
68
116
|
if (!outPath) {
|
|
@@ -99,6 +147,8 @@ export class ChatCommand {
|
|
|
99
147
|
modelExplicitlySelected = false;
|
|
100
148
|
autoApprove = false;
|
|
101
149
|
personaOverride = null;
|
|
150
|
+
toolCallParser = new ChatToolCallParser();
|
|
151
|
+
promptPolicy;
|
|
102
152
|
// Phase 5: Agent quality gate — track tool usage for evidence thresholds
|
|
103
153
|
agentToolEvidence = { discovery: 0, mutation: 0, searchFailed: 0 };
|
|
104
154
|
operatorMode = false;
|
|
@@ -108,12 +158,7 @@ export class ChatCommand {
|
|
|
108
158
|
modelGovernanceFallback = null;
|
|
109
159
|
retryPromptSignature = null;
|
|
110
160
|
retryPromptStreak = 0;
|
|
111
|
-
|
|
112
|
-
v3StreamedAnswerBuffer = '';
|
|
113
|
-
v3StreamedAnswerDisplayed = false;
|
|
114
|
-
v3SeenToolCalls = new Set();
|
|
115
|
-
v3SeenToolResults = new Set();
|
|
116
|
-
v3StartSeen = false;
|
|
161
|
+
streamState;
|
|
117
162
|
lastAgentRoute = null;
|
|
118
163
|
pendingGoaContext = null;
|
|
119
164
|
// Last completed Agent run — used by /retry, /continue, and the final summary block.
|
|
@@ -372,16 +417,6 @@ export class ChatCommand {
|
|
|
372
417
|
routeReason: 'dispatcher-eligible-agent-default',
|
|
373
418
|
};
|
|
374
419
|
}
|
|
375
|
-
if (this.lastAgentRoute?.path === 'template-instant') {
|
|
376
|
-
return {
|
|
377
|
-
selectedModel: 'agent',
|
|
378
|
-
explicitModel: false,
|
|
379
|
-
heavyTask,
|
|
380
|
-
cloudEligible,
|
|
381
|
-
cloudSelected: false,
|
|
382
|
-
routeReason: `template-instant [${this.lastAgentRoute.source}]`,
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
420
|
if (requiresV3Workflow) {
|
|
386
421
|
return {
|
|
387
422
|
selectedModel: 'agent',
|
|
@@ -456,131 +491,37 @@ export class ChatCommand {
|
|
|
456
491
|
return buildPersonaOverlay(this.getActivePersonaMode(), this.getLastUserPrompt());
|
|
457
492
|
}
|
|
458
493
|
isDiagnosticPrompt(prompt) {
|
|
459
|
-
|
|
460
|
-
return false;
|
|
461
|
-
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);
|
|
462
495
|
}
|
|
463
496
|
/**
|
|
464
497
|
* Returns true when the prompt is a simple lookup / analysis / read-only
|
|
465
498
|
* question — these should use analysis_only workflow, not full_autonomy.
|
|
466
499
|
*/
|
|
467
|
-
isAnalysisLookupPrompt(prompt) {
|
|
468
|
-
if (promptRequiresWorkspaceChanges(prompt))
|
|
469
|
-
return false;
|
|
470
|
-
const trimmed = prompt.trim();
|
|
471
|
-
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)) {
|
|
472
|
-
return true;
|
|
473
|
-
}
|
|
474
|
-
// Mid-conversation accountability / lookup questions (Gideon regression)
|
|
475
|
-
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);
|
|
476
|
-
}
|
|
477
|
-
extractExplicitLocalPath(prompt) {
|
|
478
|
-
// Try to extract Windows paths (C:\ D:\ etc.)
|
|
479
|
-
// Match drive letter followed by colon and path
|
|
480
|
-
const windowsMatch = prompt.match(/([A-Za-z]:[\\\/][^\s<>"|?*]*)/);
|
|
481
|
-
if (windowsMatch) {
|
|
482
|
-
const candidatePath = windowsMatch[1].replace(/\\/g, '\\').trim();
|
|
483
|
-
try {
|
|
484
|
-
const resolved = require('path').resolve(candidatePath);
|
|
485
|
-
const fs = require('fs');
|
|
486
|
-
if (fs.existsSync(resolved)) {
|
|
487
|
-
return resolved;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
catch {
|
|
491
|
-
// Path resolution failed
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
// Try to extract Unix paths: / followed by any characters until we hit quotes, angle brackets, pipes, or sentence boundaries
|
|
495
|
-
// Supports spaces, special chars, camelCase, etc. (anything a filesystem path might have)
|
|
496
|
-
if (!prompt.includes('http://') && !prompt.includes('https://') && !prompt.includes('ftp://')) {
|
|
497
|
-
// Match / and everything after it until we hit delimiters or common sentence words
|
|
498
|
-
const unixMatch = prompt.match(/(\/(?:[^\s<>"|?*]|\s+(?![^\s<>"|?*]))*)/);
|
|
499
|
-
if (unixMatch) {
|
|
500
|
-
let candidatePath = unixMatch[1].trim();
|
|
501
|
-
// Remove trailing common English words that aren't part of path
|
|
502
|
-
candidatePath = candidatePath.replace(/\s+(and|or|at|in|the|to|for|with|from|by|on)$/i, '').trim();
|
|
503
|
-
// Remove trailing punctuation
|
|
504
|
-
candidatePath = candidatePath.replace(/[.,;!?:)\]]*$/, '').trim();
|
|
505
|
-
if (candidatePath.length > 1) {
|
|
506
|
-
try {
|
|
507
|
-
const resolved = require('path').resolve(candidatePath);
|
|
508
|
-
const fs = require('fs');
|
|
509
|
-
if (fs.existsSync(resolved)) {
|
|
510
|
-
return resolved;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
catch {
|
|
514
|
-
// Path resolution failed
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
return null;
|
|
520
|
-
}
|
|
521
|
-
isUnscopedPromptPathOverrideAllowed() {
|
|
522
|
-
return /^(1|true|yes)$/i.test(String(process.env.VIGTHORIA_ALLOW_UNSCOPED_PROMPT_PATHS || ''));
|
|
523
|
-
}
|
|
524
|
-
isPathWithinRoot(candidatePath, rootPath) {
|
|
525
|
-
const candidate = path.resolve(candidatePath);
|
|
526
|
-
const root = path.resolve(rootPath);
|
|
527
|
-
const rel = path.relative(root, candidate);
|
|
528
|
-
return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
|
|
529
|
-
}
|
|
530
|
-
getPromptPathAllowedRoots(baseWorkspace) {
|
|
531
|
-
const roots = new Set();
|
|
532
|
-
const addRoot = (rawValue) => {
|
|
533
|
-
const value = String(rawValue || '').trim();
|
|
534
|
-
if (!value)
|
|
535
|
-
return;
|
|
536
|
-
const resolved = path.resolve(value);
|
|
537
|
-
try {
|
|
538
|
-
if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {
|
|
539
|
-
roots.add(resolved);
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
catch {
|
|
543
|
-
// ignore invalid or unreadable roots
|
|
544
|
-
}
|
|
545
|
-
};
|
|
546
|
-
addRoot(baseWorkspace);
|
|
547
|
-
addRoot(this.currentProjectPath);
|
|
548
|
-
addRoot(process.cwd());
|
|
549
|
-
addRoot(this.config.get('project')?.rootPath || null);
|
|
550
|
-
const envRootsRaw = String(process.env.VIGTHORIA_ALLOWED_WORKSPACE_ROOTS || '').trim();
|
|
551
|
-
if (envRootsRaw) {
|
|
552
|
-
for (const entry of envRootsRaw.split(path.delimiter)) {
|
|
553
|
-
addRoot(entry);
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
return Array.from(roots);
|
|
557
|
-
}
|
|
558
500
|
resolvePromptWorkspacePath(prompt, baseWorkspace) {
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
const
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
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(', ');
|
|
576
517
|
console.log(chalk.gray(`Allowed roots: ${displayRoots}`));
|
|
577
518
|
}
|
|
578
519
|
console.log(chalk.gray('To allow unrestricted prompt path overrides, set VIGTHORIA_ALLOW_UNSCOPED_PROMPT_PATHS=1.'));
|
|
579
520
|
}
|
|
580
|
-
return
|
|
521
|
+
return result.resolvedPath;
|
|
581
522
|
}
|
|
582
523
|
isBrowserTaskPrompt(prompt) {
|
|
583
|
-
return
|
|
524
|
+
return this.promptPolicy.isBrowserTask(prompt);
|
|
584
525
|
}
|
|
585
526
|
/**
|
|
586
527
|
* Returns true when a prompt can be answered directly without the full
|
|
@@ -589,28 +530,7 @@ export class ChatCommand {
|
|
|
589
530
|
* file scanning, or infrastructure changes.
|
|
590
531
|
*/
|
|
591
532
|
isOperatorDirectAnswerable(prompt) {
|
|
592
|
-
|
|
593
|
-
// Infrastructure status pings are never repo-grounded analysis.
|
|
594
|
-
if (/^(status(\s+check)?|health(\s+check)?|ping)$/i.test(trimmed)) {
|
|
595
|
-
return true;
|
|
596
|
-
}
|
|
597
|
-
// ── Exclusion guard: any prompt that references code artefacts,
|
|
598
|
-
// file paths, or analysis verbs MUST go through a tool-backed path
|
|
599
|
-
// (BMAD or agent loop), never the toolless direct-answer shortcut.
|
|
600
|
-
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)
|
|
601
|
-
|| /\b(file|folder|directory|module|class|function|method|variable|handler|listener|binding|conflict|bug|issue|error)\b/i.test(trimmed)
|
|
602
|
-
|| /\b(inspect|analyze|analyse|audit|review|find|diagnose|debug|trace|compare|diff|check|investigate)\b/i.test(trimmed)
|
|
603
|
-
|| /\b(Camera|InputManager|keydown|KeyS|KeyA|KeyW|stopPropagation|addEventListener|handleKeyDown)\b/.test(trimmed)
|
|
604
|
-
|| /\/[a-zA-Z]/.test(trimmed) // slash-prefixed paths
|
|
605
|
-
|| /[A-Z][a-z]+\.[a-z_][a-zA-Z]+/.test(trimmed) // ClassName.methodName
|
|
606
|
-
|| /\b(key\s*code|keydown|keyup|key\s*bind|event\s*listener|event\s*handler|addEventListener|handleKey|onKey)\b/i.test(trimmed);
|
|
607
|
-
if (repoGrounded)
|
|
608
|
-
return false;
|
|
609
|
-
// Only truly trivial prompts are direct-answerable: short echo/smoke
|
|
610
|
-
// prompts and factual questions with no code or repo reference.
|
|
611
|
-
const isTrivialEcho = trimmed.length < 120
|
|
612
|
-
&& /^(reply with|say|echo|respond with|return|ping|hello|hi|test|status|what is \d|how much is)\b/i.test(trimmed);
|
|
613
|
-
return isTrivialEcho;
|
|
533
|
+
return this.promptPolicy.isOperatorDirectAnswerable(prompt);
|
|
614
534
|
}
|
|
615
535
|
/**
|
|
616
536
|
* Returns true when the prompt references code artefacts, file paths,
|
|
@@ -619,51 +539,10 @@ export class ChatCommand {
|
|
|
619
539
|
* instead of the toolless BMAD/direct-answer path.
|
|
620
540
|
*/
|
|
621
541
|
isRepoGroundedPrompt(prompt) {
|
|
622
|
-
|
|
623
|
-
return /\b(src\/|\.js\b|\.ts\b|\.py\b|\.jsx\b|\.tsx\b|\.css\b|\.html\b|\.json\b)/i.test(trimmed)
|
|
624
|
-
|| /\b(file|folder|module|class|function|method|handler|listener|binding|conflict|bug|error)\b/i.test(trimmed)
|
|
625
|
-
|| /\b(inspect|analyze|analyse|audit|review|find|diagnose|debug|trace|compare|diff|check|investigate)\b/i.test(trimmed)
|
|
626
|
-
|| /\/[a-zA-Z]/.test(trimmed)
|
|
627
|
-
|| /[A-Z][a-z]+\.[a-z_][a-zA-Z]+/.test(trimmed) // ClassName.methodName patterns
|
|
628
|
-
|| /\b(key\s*code|keydown|keyup|key\s*bind|event\s*listener|event\s*handler|addEventListener|handleKey|onKey)\b/i.test(trimmed);
|
|
629
|
-
}
|
|
630
|
-
inferAgentTaskType(prompt) {
|
|
631
|
-
if (this.isDiagnosticPrompt(prompt))
|
|
632
|
-
return 'debugging';
|
|
633
|
-
if (this.isAnalysisLookupPrompt(prompt))
|
|
634
|
-
return 'analysis';
|
|
635
|
-
return sharedInferAgentTaskType(prompt);
|
|
636
|
-
}
|
|
637
|
-
buildTaskShapingInstructions(prompt) {
|
|
638
|
-
const instructions = [];
|
|
639
|
-
const runtime = this.getRuntimeEnvironmentContext();
|
|
640
|
-
// Platform-aware routing hints
|
|
641
|
-
if (runtime.platform === 'windows') {
|
|
642
|
-
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.');
|
|
643
|
-
}
|
|
644
|
-
else if (runtime.platform === 'macos') {
|
|
645
|
-
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.');
|
|
646
|
-
}
|
|
647
|
-
else if (runtime.platform === 'linux') {
|
|
648
|
-
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.');
|
|
649
|
-
}
|
|
650
|
-
if (this.isAnalysisLookupPrompt(prompt)) {
|
|
651
|
-
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.');
|
|
652
|
-
}
|
|
653
|
-
if (this.isDiagnosticPrompt(prompt)) {
|
|
654
|
-
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.');
|
|
655
|
-
}
|
|
656
|
-
if (this.isBrowserTaskPrompt(prompt)) {
|
|
657
|
-
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.');
|
|
658
|
-
}
|
|
659
|
-
if (instructions.length === 0) {
|
|
660
|
-
return '';
|
|
661
|
-
}
|
|
662
|
-
return instructions.join('\n');
|
|
542
|
+
return this.promptPolicy.isRepoGrounded(prompt);
|
|
663
543
|
}
|
|
664
544
|
buildExecutionPrompt(prompt) {
|
|
665
|
-
|
|
666
|
-
return shaping ? `${prompt}\n\n${shaping}` : prompt;
|
|
545
|
+
return this.promptPolicy.buildExecutionPrompt(prompt);
|
|
667
546
|
}
|
|
668
547
|
isProjectBrainRuntimeDisabled() {
|
|
669
548
|
return /^(1|true|yes)$/i.test(String(process.env.VIGTHORIA_NO_BRAIN || process.env.VIGTHORIA_BRAIN_DISABLED || ''));
|
|
@@ -688,7 +567,11 @@ export class ChatCommand {
|
|
|
688
567
|
}
|
|
689
568
|
}
|
|
690
569
|
canPromptForWorkspaceIndex() {
|
|
691
|
-
|
|
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) {
|
|
692
575
|
return false;
|
|
693
576
|
}
|
|
694
577
|
return !/^(1|true|yes)$/i.test(String(process.env.VIGTHORIA_NO_INDEX_PROMPT || ''));
|
|
@@ -788,22 +671,40 @@ export class ChatCommand {
|
|
|
788
671
|
agentRuntime: this.getRuntimeEnvironmentContext(),
|
|
789
672
|
};
|
|
790
673
|
const brainContext = this.buildProjectBrainRuntimeContext(prompt, this.operatorMode ? 'vigthoria-cli.operator' : this.agentMode ? 'vigthoria-cli.agent' : 'vigthoria-cli.chat');
|
|
791
|
-
if (brainContext) {
|
|
792
|
-
runtimeContext.vigthoriaBrain = brainContext;
|
|
793
|
-
if (typeof brainContext.context === 'string' && brainContext.context.trim()) {
|
|
794
|
-
runtimeContext.projectMemory = brainContext.context;
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
674
|
const workspaceBrainContext = await this.buildWorkspaceBrainRuntimeContext(prompt);
|
|
798
675
|
if (workspaceBrainContext.workspaceBrain) {
|
|
799
676
|
runtimeContext.workspaceBrain = workspaceBrainContext.workspaceBrain;
|
|
800
677
|
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
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;
|
|
807
708
|
if (!this.isBrowserTaskPrompt(prompt)) {
|
|
808
709
|
return runtimeContext;
|
|
809
710
|
}
|
|
@@ -835,12 +736,6 @@ export class ChatCommand {
|
|
|
835
736
|
devtoolsBridgeEndpoint: bridgeStatus.endpoint,
|
|
836
737
|
};
|
|
837
738
|
}
|
|
838
|
-
v3IterationCount = 0;
|
|
839
|
-
v3ToolCallCount = 0;
|
|
840
|
-
v3LastActivity = Date.now();
|
|
841
|
-
v3IdleWatchInterval = null;
|
|
842
|
-
v3IdleNoticeShown = false;
|
|
843
|
-
v3StreamingStarted = false;
|
|
844
739
|
/**
|
|
845
740
|
* Strip server-internal path prefixes from tool output strings.
|
|
846
741
|
* Prevents exposing paths like /var/www/V3-Code-Agent/... to end users.
|
|
@@ -848,56 +743,13 @@ export class ChatCommand {
|
|
|
848
743
|
sanitizeServerPath(text) {
|
|
849
744
|
if (!text)
|
|
850
745
|
return text;
|
|
851
|
-
return sanitizeUserFacingPathText(
|
|
852
|
-
}
|
|
853
|
-
stripHiddenThoughtBlocks(text) {
|
|
854
|
-
if (!text)
|
|
855
|
-
return text;
|
|
856
|
-
return text
|
|
857
|
-
.replace(/<\|mask_start\|>[\s\S]*?<\|mask_end\|>/g, '')
|
|
858
|
-
.replace(/<think>[\s\S]*?<\/redacted_thinking>/gi, '')
|
|
859
|
-
.replace(/<thinking>[\s\S]*?<\/thinking>/gi, '')
|
|
860
|
-
.replace(/<\/redacted_thinking>/gi, '')
|
|
861
|
-
.replace(/<\/thinking>/gi, '')
|
|
862
|
-
.replace(/<\|(?:mask_start|mask_end)\|>/g, '')
|
|
863
|
-
.trim();
|
|
746
|
+
return sanitizeUserFacingPathText(stripHiddenThoughtBlocks(text));
|
|
864
747
|
}
|
|
865
748
|
startV3IdleWatch(spinner) {
|
|
866
|
-
this.
|
|
867
|
-
this.v3IdleNoticeShown = false;
|
|
868
|
-
if (this.jsonOutput || !spinner) {
|
|
869
|
-
return;
|
|
870
|
-
}
|
|
871
|
-
this.v3IdleWatchInterval = setInterval(() => {
|
|
872
|
-
const idleMs = Date.now() - this.v3LastActivity;
|
|
873
|
-
if (idleMs < 15_000) {
|
|
874
|
-
// Activity resumed — re-arm the notice so a later stall is reported too.
|
|
875
|
-
this.v3IdleNoticeShown = false;
|
|
876
|
-
return;
|
|
877
|
-
}
|
|
878
|
-
if (this.v3IdleNoticeShown) {
|
|
879
|
-
return;
|
|
880
|
-
}
|
|
881
|
-
this.v3IdleNoticeShown = true;
|
|
882
|
-
if (spinner.isSpinning) {
|
|
883
|
-
spinner.stop();
|
|
884
|
-
}
|
|
885
|
-
const seconds = Math.round(idleMs / 1000);
|
|
886
|
-
const idleTimeoutSec = Math.round(DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS / 1000);
|
|
887
|
-
process.stderr.write(chalk.yellow(` [Wait] Model still working (${seconds}s) — this can legitimately take a while for large tasks. ` +
|
|
888
|
-
(idleTimeoutSec > 0
|
|
889
|
-
? `Only complete silence (~${idleTimeoutSec}s with zero bytes received) will trigger an auto-recover/retry — active progress is never interrupted.\n`
|
|
890
|
-
: `\n`)));
|
|
891
|
-
spinner.start();
|
|
892
|
-
spinner.text = 'Waiting for model response...';
|
|
893
|
-
}, 5_000);
|
|
749
|
+
this.streamState.startIdleWatch(spinner);
|
|
894
750
|
}
|
|
895
751
|
stopV3IdleWatch() {
|
|
896
|
-
|
|
897
|
-
clearInterval(this.v3IdleWatchInterval);
|
|
898
|
-
this.v3IdleWatchInterval = null;
|
|
899
|
-
}
|
|
900
|
-
this.v3IdleNoticeShown = false;
|
|
752
|
+
this.streamState.stopIdleWatch();
|
|
901
753
|
}
|
|
902
754
|
describeV3AgentTool(toolName) {
|
|
903
755
|
const normalized = String(toolName || '').toLowerCase();
|
|
@@ -964,7 +816,7 @@ export class ChatCommand {
|
|
|
964
816
|
? (event.body ?? event.stream ?? event.response ?? event)
|
|
965
817
|
: event;
|
|
966
818
|
for await (const chunk of robustifyStreamResponse(source)) {
|
|
967
|
-
this.
|
|
819
|
+
this.streamState.noteActivity();
|
|
968
820
|
if (chunk.type === 'error') {
|
|
969
821
|
if (spinner.isSpinning)
|
|
970
822
|
spinner.stop();
|
|
@@ -991,8 +843,8 @@ export class ChatCommand {
|
|
|
991
843
|
if (!safeText) {
|
|
992
844
|
return;
|
|
993
845
|
}
|
|
994
|
-
if (!this.
|
|
995
|
-
this.
|
|
846
|
+
if (!this.streamState.streamingStarted) {
|
|
847
|
+
this.streamState.streamingStarted = true;
|
|
996
848
|
spinner.stop();
|
|
997
849
|
if (!this.directPromptMode) {
|
|
998
850
|
console.log();
|
|
@@ -1001,27 +853,27 @@ export class ChatCommand {
|
|
|
1001
853
|
else {
|
|
1002
854
|
spinner.stop();
|
|
1003
855
|
}
|
|
1004
|
-
this.
|
|
1005
|
-
spinner.text = looksLikeMarkdownReport(this.
|
|
856
|
+
this.streamState.streamedAnswerBuffer += safeText;
|
|
857
|
+
spinner.text = looksLikeMarkdownReport(this.streamState.streamedAnswerBuffer)
|
|
1006
858
|
? 'Writing analysis report...'
|
|
1007
859
|
: 'Writing response...';
|
|
1008
860
|
}
|
|
1009
861
|
displayV3StreamedAnswer() {
|
|
1010
|
-
if (this.jsonOutput || this.
|
|
862
|
+
if (this.jsonOutput || this.streamState.streamedAnswerDisplayed) {
|
|
1011
863
|
return;
|
|
1012
864
|
}
|
|
1013
|
-
const content = this.
|
|
865
|
+
const content = this.streamState.streamedAnswerBuffer.trim();
|
|
1014
866
|
if (!isSubstantiveAgentAnswer(content)) {
|
|
1015
867
|
return;
|
|
1016
868
|
}
|
|
1017
869
|
emitDeckEvent({ type: 'report_ready', markdown: content });
|
|
1018
870
|
if (isDeckModeEnabled()) {
|
|
1019
|
-
this.
|
|
871
|
+
this.streamState.streamedAnswerDisplayed = true;
|
|
1020
872
|
return;
|
|
1021
873
|
}
|
|
1022
874
|
console.log('');
|
|
1023
875
|
console.log(renderMarkdownToTerminal(content, this.getTerminalContentWidth()));
|
|
1024
|
-
this.
|
|
876
|
+
this.streamState.streamedAnswerDisplayed = true;
|
|
1025
877
|
}
|
|
1026
878
|
renderAgentAnswerForTerminal(content) {
|
|
1027
879
|
const normalized = String(content || '').trim();
|
|
@@ -1034,37 +886,7 @@ export class ChatCommand {
|
|
|
1034
886
|
return Math.max(60, Math.min(cols - 2, 100));
|
|
1035
887
|
}
|
|
1036
888
|
sanitizeV3VisibleStreamText(text) {
|
|
1037
|
-
|
|
1038
|
-
if (!output)
|
|
1039
|
-
return '';
|
|
1040
|
-
output = output.replace(/<\/think>/gi, '</thinking>');
|
|
1041
|
-
output = output.replace(/<think>/gi, '<thinking>');
|
|
1042
|
-
output = output.replace(/<\/redacted_thinking>/gi, '</thinking>');
|
|
1043
|
-
if (this.v3SuppressThinkingStream) {
|
|
1044
|
-
const closeIdx = output.search(/<\/thinking>/i);
|
|
1045
|
-
if (closeIdx < 0) {
|
|
1046
|
-
return '';
|
|
1047
|
-
}
|
|
1048
|
-
output = output.slice(closeIdx).replace(/^<\/thinking>/i, '');
|
|
1049
|
-
this.v3SuppressThinkingStream = false;
|
|
1050
|
-
}
|
|
1051
|
-
output = output.replace(/<thinking>[\s\S]*?<\/thinking>/gi, '');
|
|
1052
|
-
if (/<thinking>/i.test(output)) {
|
|
1053
|
-
output = output.replace(/<thinking>[\s\S]*$/i, '');
|
|
1054
|
-
this.v3SuppressThinkingStream = true;
|
|
1055
|
-
}
|
|
1056
|
-
output = output
|
|
1057
|
-
.replace(/```json\s*\[\s*\{[\s\S]*?"tool"[\s\S]*?\}\s*\]\s*```/gi, '')
|
|
1058
|
-
.replace(/```json\s*\{[\s\S]*?"tool"[\s\S]*?\}\s*```/gi, '')
|
|
1059
|
-
.replace(/^\s*(?:json\s*)?\[\s*\{[\s\S]*?"tool"[\s\S]*$/gim, '')
|
|
1060
|
-
.replace(/^\s*(?:list_dir|read_file|write_file|edit_file|glob|grep|bash)\s*$/gim, '')
|
|
1061
|
-
.replace(/<tool_call>[\s\S]*?<\/tool_call>/gi, '')
|
|
1062
|
-
.replace(/<tool_code>[\s\S]*?<\/tool_code>/gi, '')
|
|
1063
|
-
.replace(/<read_file>\s*<path>[\s\S]*?<\/path>\s*<\/read_file>/gi, '')
|
|
1064
|
-
.replace(/<(?:grep|list_directory|search_files|glob|tool_result)\b[\s\S]*?<\/(?:grep|list_directory|search_files|glob|tool_result)>/gi, '')
|
|
1065
|
-
.replace(/bootstrap discovery completed\s*[—-]\s*inspect the most relevant files next\.?/gi, '')
|
|
1066
|
-
.replace(/\n{3,}/g, '\n\n');
|
|
1067
|
-
return output;
|
|
889
|
+
return this.streamState.sanitizeVisibleText(text);
|
|
1068
890
|
}
|
|
1069
891
|
writeAgentActivityLine(text) {
|
|
1070
892
|
if (this.jsonOutput)
|
|
@@ -1084,29 +906,29 @@ export class ChatCommand {
|
|
|
1084
906
|
if (!event || typeof event !== 'object') {
|
|
1085
907
|
return;
|
|
1086
908
|
}
|
|
1087
|
-
this.
|
|
909
|
+
this.streamState.noteActivity();
|
|
1088
910
|
if (isV3StreamKeepaliveEvent(event)) {
|
|
1089
911
|
return;
|
|
1090
912
|
}
|
|
1091
913
|
if (event.type === 'tool_call') {
|
|
1092
914
|
const eventKey = this.toolEventKey(event);
|
|
1093
|
-
if (this.
|
|
915
|
+
if (this.streamState.seenToolCalls.has(eventKey)) {
|
|
1094
916
|
return;
|
|
1095
917
|
}
|
|
1096
|
-
this.
|
|
1097
|
-
this.
|
|
918
|
+
this.streamState.seenToolCalls.add(eventKey);
|
|
919
|
+
this.streamState.toolCallCount += 1;
|
|
1098
920
|
const toolName = event.name || event.tool || event.tool_name || '';
|
|
1099
921
|
emitDeckEvent({
|
|
1100
922
|
type: 'tool_start',
|
|
1101
923
|
tool: toolName,
|
|
1102
|
-
index: this.
|
|
924
|
+
index: this.streamState.toolCallCount,
|
|
1103
925
|
target: event.arguments?.path || event.arguments?.file_path || event.arguments?.pattern || '',
|
|
1104
926
|
});
|
|
1105
927
|
const toolDesc = this.describeV3AgentTool(event.tool || event.name || event.tool_name);
|
|
1106
928
|
const toolTarget = event.arguments?.path || event.arguments?.file_path || event.arguments?.pattern || '';
|
|
1107
929
|
const sanitizedTarget = this.sanitizeServerPath(String(toolTarget));
|
|
1108
930
|
const shortTarget = sanitizedTarget ? ` -> ${sanitizedTarget.replace(/\\/g, '/').split('/').slice(-2).join('/')}` : '';
|
|
1109
|
-
const stepLabel = chalk.cyan(` [${this.
|
|
931
|
+
const stepLabel = chalk.cyan(` [${this.streamState.toolCallCount}]`) + ` ${toolDesc}${shortTarget}`;
|
|
1110
932
|
if (spinner.isSpinning)
|
|
1111
933
|
spinner.stop();
|
|
1112
934
|
this.writeAgentActivityLine(this.fitTerminalText(stepLabel) + '\n');
|
|
@@ -1129,18 +951,18 @@ export class ChatCommand {
|
|
|
1129
951
|
}
|
|
1130
952
|
if (event.type === 'tool_result') {
|
|
1131
953
|
const eventKey = this.toolEventKey(event);
|
|
1132
|
-
if (eventKey && this.
|
|
954
|
+
if (eventKey && this.streamState.seenToolResults.has(eventKey)) {
|
|
1133
955
|
return;
|
|
1134
956
|
}
|
|
1135
957
|
if (eventKey)
|
|
1136
|
-
this.
|
|
958
|
+
this.streamState.seenToolResults.add(eventKey);
|
|
1137
959
|
const success = event.success !== false;
|
|
1138
960
|
const toolName = event.name || event.tool || '';
|
|
1139
961
|
emitDeckEvent({
|
|
1140
962
|
type: 'tool_end',
|
|
1141
963
|
tool: toolName,
|
|
1142
964
|
success,
|
|
1143
|
-
index: this.
|
|
965
|
+
index: this.streamState.toolCallCount,
|
|
1144
966
|
});
|
|
1145
967
|
const indicator = success ? chalk.green(' ✓') : chalk.red(' ✗');
|
|
1146
968
|
if (spinner.isSpinning)
|
|
@@ -1206,7 +1028,7 @@ export class ChatCommand {
|
|
|
1206
1028
|
return;
|
|
1207
1029
|
}
|
|
1208
1030
|
if (event.type === 'thinking') {
|
|
1209
|
-
this.
|
|
1031
|
+
this.streamState.iterationCount += 1;
|
|
1210
1032
|
const iterText = this.sanitizeServerPath(event.content || '');
|
|
1211
1033
|
const isGenericStep = !iterText
|
|
1212
1034
|
|| /read-only analysis turn|run at least one discovery tool/i.test(iterText)
|
|
@@ -1225,7 +1047,7 @@ export class ChatCommand {
|
|
|
1225
1047
|
? (event.delta?.text || '')
|
|
1226
1048
|
: (event.content || '');
|
|
1227
1049
|
if (text) {
|
|
1228
|
-
this.
|
|
1050
|
+
this.streamState.noteActivity();
|
|
1229
1051
|
if (event.type === 'message' && /^\[Context\]/i.test(String(text).trim())) {
|
|
1230
1052
|
if (spinner.isSpinning)
|
|
1231
1053
|
spinner.stop();
|
|
@@ -1242,16 +1064,16 @@ export class ChatCommand {
|
|
|
1242
1064
|
}
|
|
1243
1065
|
if (event.type === 'complete') {
|
|
1244
1066
|
const elapsed = event.elapsed || '';
|
|
1245
|
-
const iters = event.iterations || this.
|
|
1246
|
-
const tools = event.tool_calls || this.
|
|
1067
|
+
const iters = event.iterations || this.streamState.iterationCount;
|
|
1068
|
+
const tools = event.tool_calls || this.streamState.toolCallCount;
|
|
1247
1069
|
if (spinner.isSpinning)
|
|
1248
1070
|
spinner.stop();
|
|
1249
1071
|
this.displayV3StreamedAnswer();
|
|
1250
1072
|
emitDeckEvent({
|
|
1251
1073
|
type: 'run_complete',
|
|
1252
1074
|
success: true,
|
|
1253
|
-
tools: event.tool_calls || this.
|
|
1254
|
-
iterations: event.iterations || this.
|
|
1075
|
+
tools: event.tool_calls || this.streamState.toolCallCount,
|
|
1076
|
+
iterations: event.iterations || this.streamState.iterationCount,
|
|
1255
1077
|
elapsed: event.elapsed || '',
|
|
1256
1078
|
});
|
|
1257
1079
|
let statLine = `${iters} iterations, ${tools} tool calls`;
|
|
@@ -1267,10 +1089,11 @@ export class ChatCommand {
|
|
|
1267
1089
|
process.stdout.write(chalk.green(`\n✓ Complete`) + ` - ${this.fitTerminalText(statLine, 14)}\n`);
|
|
1268
1090
|
// Show seal quality score if available
|
|
1269
1091
|
if (event.seal_score && typeof event.seal_score.overall === 'number') {
|
|
1270
|
-
const
|
|
1092
|
+
const rawScore = event.seal_score.overall;
|
|
1093
|
+
const score = rawScore <= 10 ? rawScore * 10 : rawScore;
|
|
1271
1094
|
const tier = event.seal_score.tier || '';
|
|
1272
|
-
const scoreColor = score >=
|
|
1273
|
-
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');
|
|
1274
1097
|
}
|
|
1275
1098
|
return;
|
|
1276
1099
|
}
|
|
@@ -1425,11 +1248,11 @@ export class ChatCommand {
|
|
|
1425
1248
|
return;
|
|
1426
1249
|
}
|
|
1427
1250
|
if (event.type === 'start') {
|
|
1428
|
-
if (this.
|
|
1251
|
+
if (this.streamState.startSeen) {
|
|
1429
1252
|
spinner.text = 'Working...';
|
|
1430
1253
|
return;
|
|
1431
1254
|
}
|
|
1432
|
-
this.
|
|
1255
|
+
this.streamState.startSeen = true;
|
|
1433
1256
|
if (spinner.isSpinning)
|
|
1434
1257
|
spinner.stop();
|
|
1435
1258
|
process.stderr.write(chalk.cyan(' [Start] ') + 'Agent initialized\n');
|
|
@@ -1503,20 +1326,20 @@ export class ChatCommand {
|
|
|
1503
1326
|
this.config = config;
|
|
1504
1327
|
this.logger = logger;
|
|
1505
1328
|
this.commandProgram = commandProgram;
|
|
1506
|
-
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
|
+
});
|
|
1507
1335
|
this.sessionManager = new SessionManager();
|
|
1508
1336
|
}
|
|
1509
1337
|
async run(options) {
|
|
1510
1338
|
const hasRuntimeToken = Boolean(process.env.VIGTHORIA_TOKEN || process.env.VIGTHORIA_AUTH_TOKEN);
|
|
1511
1339
|
if (!this.config.isAuthenticated() && !hasRuntimeToken) {
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
}
|
|
1516
|
-
else {
|
|
1517
|
-
this.logger.error('Not authenticated. Run: vigthoria login');
|
|
1518
|
-
}
|
|
1519
|
-
return;
|
|
1340
|
+
throw new CliCommandError('Not authenticated. Run: vigthoria login', {
|
|
1341
|
+
code: 'AUTH_REQUIRED', category: 'authentication',
|
|
1342
|
+
});
|
|
1520
1343
|
}
|
|
1521
1344
|
this.agentMode = options.agent === true;
|
|
1522
1345
|
this.operatorMode = options.operator === true;
|
|
@@ -1525,7 +1348,7 @@ export class ChatCommand {
|
|
|
1525
1348
|
: null;
|
|
1526
1349
|
this.savePlanToVigFlow = options.savePlan === true;
|
|
1527
1350
|
this.jsonOutput = options.json === true;
|
|
1528
|
-
this.autoApprove = options.autoApprove
|
|
1351
|
+
this.autoApprove = resolveAutoApproval(options.autoApprove, this.jsonOutput);
|
|
1529
1352
|
this.personaOverride = options.grant === true ? 'wiener_grant' : null;
|
|
1530
1353
|
this.modelExplicitlySelected = this.isHardExplicitAgentModelSelection(options);
|
|
1531
1354
|
this.currentModel = this.resolveInitialModel(options);
|
|
@@ -1581,19 +1404,23 @@ export class ChatCommand {
|
|
|
1581
1404
|
this.saveSession();
|
|
1582
1405
|
}
|
|
1583
1406
|
this.operatorMode = false;
|
|
1584
|
-
|
|
1585
|
-
return;
|
|
1407
|
+
throw new CliCommandError(this.operatorAccessMessage(), { code: 'OPERATOR_ACCESS_REQUIRED', category: 'authorization' });
|
|
1586
1408
|
}
|
|
1587
1409
|
if (options.retry || options.continue) {
|
|
1588
1410
|
if (!this.agentMode) {
|
|
1589
|
-
throw new
|
|
1411
|
+
throw new CliCommandError('--retry and --continue require agent mode.', { code: 'AGENT_MODE_REQUIRED', category: 'usage' });
|
|
1590
1412
|
}
|
|
1591
1413
|
const followUp = options.retry ? this.buildRetryPrompt() : this.buildContinuePrompt();
|
|
1592
1414
|
if (!followUp) {
|
|
1593
|
-
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
|
+
});
|
|
1594
1419
|
}
|
|
1595
1420
|
this.directPromptMode = true;
|
|
1596
1421
|
await this.runAgentTurn(followUp);
|
|
1422
|
+
this.workspaceBrain?.destroy();
|
|
1423
|
+
this.api.destroy();
|
|
1597
1424
|
return;
|
|
1598
1425
|
}
|
|
1599
1426
|
if (options.prompt) {
|
|
@@ -1605,35 +1432,34 @@ export class ChatCommand {
|
|
|
1605
1432
|
const parsed = Number.parseInt(rawValue, 10);
|
|
1606
1433
|
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
|
1607
1434
|
})();
|
|
1608
|
-
let
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
}, bridgePromptTimeoutMs)
|
|
1622
|
-
: null;
|
|
1623
|
-
await this.handleDirectPrompt(options.prompt);
|
|
1624
|
-
if (timeoutId)
|
|
1625
|
-
clearTimeout(timeoutId);
|
|
1626
|
-
if (!timedOut) {
|
|
1627
|
-
const bridge = getBridgeClient();
|
|
1628
|
-
if (bridge) {
|
|
1629
|
-
bridge.emitEnd({ reason: 'prompt-complete' });
|
|
1630
|
-
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;
|
|
1631
1448
|
}
|
|
1449
|
+
getBridgeClient()?.emitEnd({ reason: 'prompt-complete' });
|
|
1632
1450
|
}
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
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;
|
|
1637
1463
|
}
|
|
1638
1464
|
await this.startInteractiveChat();
|
|
1639
1465
|
const bridge = getBridgeClient();
|
|
@@ -1641,6 +1467,8 @@ export class ChatCommand {
|
|
|
1641
1467
|
bridge.emitEnd({ reason: 'interactive-exit' });
|
|
1642
1468
|
bridge.destroy();
|
|
1643
1469
|
}
|
|
1470
|
+
this.workspaceBrain?.destroy();
|
|
1471
|
+
this.api.destroy();
|
|
1644
1472
|
}
|
|
1645
1473
|
/** Handle an inbound admin command from the Commando Bridge. */
|
|
1646
1474
|
handleAdminCommand(cmd) {
|
|
@@ -1660,7 +1488,9 @@ export class ChatCommand {
|
|
|
1660
1488
|
if (!this.jsonOutput)
|
|
1661
1489
|
console.log(chalk.red(`[bridge] Abort requested by admin`));
|
|
1662
1490
|
getBridgeClient()?.emitEnd({ reason: 'admin-abort' });
|
|
1663
|
-
process.
|
|
1491
|
+
process.exitCode = 130;
|
|
1492
|
+
this.workspaceBrain?.destroy();
|
|
1493
|
+
this.api.destroy();
|
|
1664
1494
|
break;
|
|
1665
1495
|
default:
|
|
1666
1496
|
getBridgeClient()?.emitError({ message: `Unknown admin command: ${cmd.action}` });
|
|
@@ -1838,6 +1668,35 @@ export class ChatCommand {
|
|
|
1838
1668
|
originalPrompt: saved.originalPrompt ?? null,
|
|
1839
1669
|
};
|
|
1840
1670
|
}
|
|
1671
|
+
else if (this.currentSession.activeAgentRun?.prompt) {
|
|
1672
|
+
const checkpoint = this.currentSession.activeAgentRun;
|
|
1673
|
+
this.lastAgentRunOutcome = {
|
|
1674
|
+
prompt: checkpoint.prompt,
|
|
1675
|
+
originalPrompt: checkpoint.originalPrompt || checkpoint.prompt,
|
|
1676
|
+
taskId: checkpoint.executionId,
|
|
1677
|
+
contextId: checkpoint.contextId,
|
|
1678
|
+
tasksSucceeded: 0,
|
|
1679
|
+
tasksTotal: 0,
|
|
1680
|
+
failedTaskIds: [...(checkpoint.failedTaskIds || [])],
|
|
1681
|
+
unfinishedTaskIds: checkpoint.unfinishedTaskIds?.length
|
|
1682
|
+
? [...checkpoint.unfinishedTaskIds]
|
|
1683
|
+
: ['resume-interrupted-agent-run'],
|
|
1684
|
+
qualityScore: null,
|
|
1685
|
+
qualityMissing: [],
|
|
1686
|
+
qualityBlockers: checkpoint.error ? [checkpoint.error] : [],
|
|
1687
|
+
hasOutput: false,
|
|
1688
|
+
answerContent: null,
|
|
1689
|
+
selfHealStatus: 'skipped',
|
|
1690
|
+
selfHealTool: null,
|
|
1691
|
+
plannerError: null,
|
|
1692
|
+
executorError: checkpoint.error || 'The previous client process ended before the agent run completed.',
|
|
1693
|
+
clientToolErrors: [],
|
|
1694
|
+
transportErrors: [],
|
|
1695
|
+
workspacePath: checkpoint.workspacePath || this.currentProjectPath,
|
|
1696
|
+
workspaceSyncIssue: null,
|
|
1697
|
+
finishedAt: Date.parse(checkpoint.updatedAt) || Date.now(),
|
|
1698
|
+
};
|
|
1699
|
+
}
|
|
1841
1700
|
return;
|
|
1842
1701
|
}
|
|
1843
1702
|
}
|
|
@@ -1906,8 +1765,7 @@ export class ChatCommand {
|
|
|
1906
1765
|
}
|
|
1907
1766
|
if (this.operatorMode) {
|
|
1908
1767
|
if (!this.hasOperatorAccess()) {
|
|
1909
|
-
|
|
1910
|
-
return;
|
|
1768
|
+
throw new CliCommandError(this.operatorAccessMessage(), { code: 'OPERATOR_ACCESS_REQUIRED', category: 'authorization' });
|
|
1911
1769
|
}
|
|
1912
1770
|
await this.runOperatorTurn(effectivePrompt);
|
|
1913
1771
|
return;
|
|
@@ -1987,13 +1845,13 @@ export class ChatCommand {
|
|
|
1987
1845
|
spinner.stop();
|
|
1988
1846
|
}
|
|
1989
1847
|
if (this.jsonOutput) {
|
|
1990
|
-
|
|
1848
|
+
emitAgentJsonOutput({
|
|
1991
1849
|
success: true,
|
|
1992
1850
|
workflow: resolvedWorkflow,
|
|
1993
1851
|
mode: invocationMode,
|
|
1994
1852
|
execution,
|
|
1995
1853
|
content,
|
|
1996
|
-
}
|
|
1854
|
+
});
|
|
1997
1855
|
return;
|
|
1998
1856
|
}
|
|
1999
1857
|
this.logger.success(`Workflow target ${resolvedWorkflow.name} ${execution.status}`);
|
|
@@ -2014,8 +1872,7 @@ export class ChatCommand {
|
|
|
2014
1872
|
}
|
|
2015
1873
|
async runOperatorTurn(prompt) {
|
|
2016
1874
|
if (!this.hasOperatorAccess()) {
|
|
2017
|
-
|
|
2018
|
-
return;
|
|
1875
|
+
throw new CliCommandError(this.operatorAccessMessage(), { code: 'OPERATOR_ACCESS_REQUIRED', category: 'authorization' });
|
|
2019
1876
|
}
|
|
2020
1877
|
// ── Fast-path: direct-answerable prompts skip the full BMAD workflow ──
|
|
2021
1878
|
// The BMAD orchestrator scans the entire workspace (can be 1000+ files)
|
|
@@ -2075,7 +1932,7 @@ export class ChatCommand {
|
|
|
2075
1932
|
throw new CLIError('Operator workflow returned a non-actionable acknowledgement instead of a grounded result.', 'model_backend');
|
|
2076
1933
|
}
|
|
2077
1934
|
if (this.jsonOutput) {
|
|
2078
|
-
|
|
1935
|
+
emitAgentJsonOutput({
|
|
2079
1936
|
success: true,
|
|
2080
1937
|
mode: 'operator',
|
|
2081
1938
|
workflowId: response.workflowId,
|
|
@@ -2083,7 +1940,7 @@ export class ChatCommand {
|
|
|
2083
1940
|
contextId: response.contextId || null,
|
|
2084
1941
|
content: response.content || 'Operator workflow completed.',
|
|
2085
1942
|
metadata: response.metadata || {},
|
|
2086
|
-
}
|
|
1943
|
+
});
|
|
2087
1944
|
}
|
|
2088
1945
|
else {
|
|
2089
1946
|
console.log(response.content || 'Operator workflow completed.');
|
|
@@ -2101,23 +1958,8 @@ export class ChatCommand {
|
|
|
2101
1958
|
}
|
|
2102
1959
|
const cliErr = error instanceof CLIError ? error : classifyError(error);
|
|
2103
1960
|
const errorMsg = formatCLIError(cliErr);
|
|
2104
|
-
if (!this.jsonOutput) {
|
|
2105
|
-
this.logger.error('Operator workflow failed');
|
|
2106
|
-
}
|
|
2107
1961
|
this.rememberBrainEvent('issue', `GoA operator workflow failed: ${errorMsg}`, 'operator');
|
|
2108
|
-
|
|
2109
|
-
process.exitCode = 1;
|
|
2110
|
-
console.log(JSON.stringify({
|
|
2111
|
-
success: false,
|
|
2112
|
-
mode: 'operator',
|
|
2113
|
-
content: '',
|
|
2114
|
-
error: errorMsg,
|
|
2115
|
-
errorCategory: cliErr.category,
|
|
2116
|
-
}, null, 2));
|
|
2117
|
-
}
|
|
2118
|
-
else {
|
|
2119
|
-
this.logger.error(errorMsg);
|
|
2120
|
-
}
|
|
1962
|
+
throw commandFailure(error, { code: 'OPERATOR_EXECUTION_FAILED', message: errorMsg });
|
|
2121
1963
|
}
|
|
2122
1964
|
}
|
|
2123
1965
|
/**
|
|
@@ -2163,12 +2005,12 @@ export class ChatCommand {
|
|
|
2163
2005
|
throw new CLIError('Operator returned a non-actionable acknowledgement instead of a grounded result.', 'model_backend');
|
|
2164
2006
|
}
|
|
2165
2007
|
if (this.jsonOutput) {
|
|
2166
|
-
|
|
2008
|
+
emitAgentJsonOutput({
|
|
2167
2009
|
success: true,
|
|
2168
2010
|
mode: 'operator',
|
|
2169
2011
|
content,
|
|
2170
2012
|
metadata: { source: 'operator-direct', mode: 'operator' },
|
|
2171
|
-
}
|
|
2013
|
+
});
|
|
2172
2014
|
}
|
|
2173
2015
|
else {
|
|
2174
2016
|
console.log(content);
|
|
@@ -2182,20 +2024,7 @@ export class ChatCommand {
|
|
|
2182
2024
|
spinner.stop();
|
|
2183
2025
|
const cliErr = error instanceof CLIError ? error : classifyError(error);
|
|
2184
2026
|
const errorMsg = formatCLIError(cliErr);
|
|
2185
|
-
|
|
2186
|
-
process.exitCode = 1;
|
|
2187
|
-
console.log(JSON.stringify({
|
|
2188
|
-
success: false,
|
|
2189
|
-
mode: 'operator',
|
|
2190
|
-
content: '',
|
|
2191
|
-
error: errorMsg,
|
|
2192
|
-
errorCategory: cliErr.category,
|
|
2193
|
-
}, null, 2));
|
|
2194
|
-
}
|
|
2195
|
-
else {
|
|
2196
|
-
this.logger.error('Operator direct answer failed');
|
|
2197
|
-
this.logger.error(errorMsg);
|
|
2198
|
-
}
|
|
2027
|
+
throw commandFailure(error, { code: 'OPERATOR_DIRECT_FAILED', message: errorMsg });
|
|
2199
2028
|
}
|
|
2200
2029
|
}
|
|
2201
2030
|
async runSimplePrompt(prompt) {
|
|
@@ -2294,20 +2123,20 @@ export class ChatCommand {
|
|
|
2294
2123
|
const response = await this.callApi('Send chat message', () => this.api.chat(this.getMessagesForModel(), this.currentModel));
|
|
2295
2124
|
if (spinner)
|
|
2296
2125
|
spinner.stop();
|
|
2297
|
-
const finalText =
|
|
2126
|
+
const finalText = sanitizeDirectModeOutput(stripHiddenThoughtBlocks(response.message || '')).trim();
|
|
2298
2127
|
const effectiveModel = String(response.model || this.currentModel);
|
|
2299
2128
|
const metadata = this.modelGovernanceFallback
|
|
2300
2129
|
? { modelFallback: this.modelGovernanceFallback }
|
|
2301
2130
|
: undefined;
|
|
2302
2131
|
if (this.jsonOutput) {
|
|
2303
|
-
|
|
2132
|
+
emitAgentJsonOutput({
|
|
2304
2133
|
success: true,
|
|
2305
2134
|
mode: 'chat',
|
|
2306
2135
|
model: effectiveModel,
|
|
2307
2136
|
requestedModel: this.modelGovernanceFallback?.requestedModel || this.currentModel,
|
|
2308
2137
|
metadata,
|
|
2309
2138
|
content: finalText || 'The model returned an empty response. Try rephrasing or use --agent for grounded file analysis.',
|
|
2310
|
-
}
|
|
2139
|
+
});
|
|
2311
2140
|
}
|
|
2312
2141
|
else if (finalText) {
|
|
2313
2142
|
console.log(finalText);
|
|
@@ -2323,20 +2152,7 @@ export class ChatCommand {
|
|
|
2323
2152
|
spinner.stop();
|
|
2324
2153
|
const cliErr = error instanceof CLIError ? error : classifyError(error);
|
|
2325
2154
|
const errorMsg = formatCLIError(cliErr);
|
|
2326
|
-
|
|
2327
|
-
process.exitCode = 1;
|
|
2328
|
-
console.log(JSON.stringify({
|
|
2329
|
-
success: false,
|
|
2330
|
-
mode: 'chat',
|
|
2331
|
-
model: this.currentModel,
|
|
2332
|
-
content: '',
|
|
2333
|
-
error: errorMsg,
|
|
2334
|
-
errorCategory: cliErr.category,
|
|
2335
|
-
}, null, 2));
|
|
2336
|
-
}
|
|
2337
|
-
else {
|
|
2338
|
-
this.logger.error(errorMsg);
|
|
2339
|
-
}
|
|
2155
|
+
throw commandFailure(error, { code: 'CHAT_EXECUTION_FAILED', message: errorMsg });
|
|
2340
2156
|
}
|
|
2341
2157
|
}
|
|
2342
2158
|
async runAgentTurn(prompt) {
|
|
@@ -2376,10 +2192,9 @@ export class ChatCommand {
|
|
|
2376
2192
|
return;
|
|
2377
2193
|
}
|
|
2378
2194
|
const errorMessage = 'The V3 planner/executor workflow is unavailable. Refusing to fall back to legacy template, direct-file, or local write paths.';
|
|
2379
|
-
this.logger.error(errorMessage);
|
|
2380
2195
|
this.messages.push({ role: 'assistant', content: errorMessage });
|
|
2381
|
-
process.exitCode = 1;
|
|
2382
2196
|
this.saveSession();
|
|
2197
|
+
throw new CliCommandError(errorMessage, { code: 'AGENT_UNAVAILABLE', category: 'execution' });
|
|
2383
2198
|
}
|
|
2384
2199
|
resolveAgentTurnPrompt(prompt) {
|
|
2385
2200
|
if (isBuiltContinuePrompt(prompt) || isBuiltRetryPrompt(prompt) || isBuiltWriteConfirmationPrompt(prompt)) {
|
|
@@ -2551,33 +2366,38 @@ export class ChatCommand {
|
|
|
2551
2366
|
spinner.stop();
|
|
2552
2367
|
const evidenceSummary = this.synthesizeEvidenceFromHistory();
|
|
2553
2368
|
if (evidenceSummary) {
|
|
2554
|
-
const fallbackContent =
|
|
2369
|
+
const fallbackContent = sanitizeDirectModeOutput(evidenceSummary);
|
|
2555
2370
|
const liveOutcome = this.buildLocalLoopLiveOutcome(prompt);
|
|
2556
2371
|
const evaluation = evaluateExecutorSuccess(liveOutcome);
|
|
2557
2372
|
this.persistLocalLoopOutcome(prompt, evaluation, liveOutcome);
|
|
2558
2373
|
if (this.jsonOutput) {
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
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
|
+
},
|
|
2573
2389
|
},
|
|
2574
|
-
}
|
|
2575
|
-
}, null, 2));
|
|
2390
|
+
});
|
|
2576
2391
|
}
|
|
2577
2392
|
else {
|
|
2578
2393
|
console.log(fallbackContent);
|
|
2579
2394
|
}
|
|
2580
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
|
+
}
|
|
2581
2401
|
return;
|
|
2582
2402
|
}
|
|
2583
2403
|
throw retryErr;
|
|
@@ -2589,23 +2409,7 @@ export class ChatCommand {
|
|
|
2589
2409
|
if (spinner)
|
|
2590
2410
|
spinner.stop();
|
|
2591
2411
|
this.rememberBrainEvent('issue', 'Agent API preflight failed: ' + formatted, 'agent');
|
|
2592
|
-
|
|
2593
|
-
process.exitCode = 1;
|
|
2594
|
-
console.log(JSON.stringify({
|
|
2595
|
-
success: false,
|
|
2596
|
-
mode: 'agent',
|
|
2597
|
-
model: this.currentModel,
|
|
2598
|
-
partial: false,
|
|
2599
|
-
content: '',
|
|
2600
|
-
error: formatted,
|
|
2601
|
-
errorCategory: cliErr.category,
|
|
2602
|
-
metadata: { executionPath: 'local-agent-loop' },
|
|
2603
|
-
}, null, 2));
|
|
2604
|
-
}
|
|
2605
|
-
else {
|
|
2606
|
-
this.logger.error(formatted);
|
|
2607
|
-
}
|
|
2608
|
-
return;
|
|
2412
|
+
throw commandFailure(firstErr, { code: 'AGENT_PREFLIGHT_FAILED', message: formatted });
|
|
2609
2413
|
}
|
|
2610
2414
|
}
|
|
2611
2415
|
if (spinner)
|
|
@@ -2627,8 +2431,8 @@ export class ChatCommand {
|
|
|
2627
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());
|
|
2628
2432
|
// Detect if the response is effectively empty or just a follow-up
|
|
2629
2433
|
// question after we strip tool output echoes.
|
|
2630
|
-
const sanitized =
|
|
2631
|
-
const isFollowUp =
|
|
2434
|
+
const sanitized = sanitizeDirectModeOutput(visibleText.trim());
|
|
2435
|
+
const isFollowUp = isDirectModeFollowUpQuestion(sanitized);
|
|
2632
2436
|
const isEmptyAfterSanitize = !sanitized || sanitized.length < 10;
|
|
2633
2437
|
// Detect resignation: model gives up saying files/things were "not found"
|
|
2634
2438
|
// without having tried list_dir to discover the correct path.
|
|
@@ -2671,29 +2475,34 @@ export class ChatCommand {
|
|
|
2671
2475
|
const evaluation = evaluateExecutorSuccess(liveOutcome);
|
|
2672
2476
|
this.persistLocalLoopOutcome(prompt, evaluation, liveOutcome);
|
|
2673
2477
|
if (this.jsonOutput) {
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
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
|
+
},
|
|
2689
2494
|
},
|
|
2690
|
-
}
|
|
2691
|
-
}, null, 2));
|
|
2495
|
+
});
|
|
2692
2496
|
}
|
|
2693
2497
|
else {
|
|
2694
2498
|
console.log(finalContent);
|
|
2695
2499
|
}
|
|
2696
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
|
+
}
|
|
2697
2506
|
return;
|
|
2698
2507
|
}
|
|
2699
2508
|
await this.executeToolCalls(toolCalls);
|
|
@@ -2710,25 +2519,7 @@ export class ChatCommand {
|
|
|
2710
2519
|
const cliErr = error instanceof CLIError ? error : classifyError(error);
|
|
2711
2520
|
const errorMsg = formatCLIError(cliErr);
|
|
2712
2521
|
this.rememberBrainEvent('issue', `Agent turn failed: ${errorMsg}`, 'agent');
|
|
2713
|
-
|
|
2714
|
-
process.exitCode = 1;
|
|
2715
|
-
console.log(JSON.stringify({
|
|
2716
|
-
success: false,
|
|
2717
|
-
mode: 'agent',
|
|
2718
|
-
model: this.currentModel,
|
|
2719
|
-
partial: false,
|
|
2720
|
-
content: '',
|
|
2721
|
-
error: errorMsg,
|
|
2722
|
-
errorCategory: cliErr.category,
|
|
2723
|
-
metadata: {
|
|
2724
|
-
executionPath: 'local-agent-loop',
|
|
2725
|
-
},
|
|
2726
|
-
}, null, 2));
|
|
2727
|
-
}
|
|
2728
|
-
else {
|
|
2729
|
-
this.logger.error(errorMsg);
|
|
2730
|
-
}
|
|
2731
|
-
return;
|
|
2522
|
+
throw commandFailure(error, { code: 'AGENT_TURN_FAILED', message: errorMsg });
|
|
2732
2523
|
}
|
|
2733
2524
|
}
|
|
2734
2525
|
const liveOutcome = this.buildLocalLoopLiveOutcome(prompt);
|
|
@@ -2738,29 +2529,11 @@ export class ChatCommand {
|
|
|
2738
2529
|
executorError: 'Agent exhausted the maximum local tool loop turns before reaching a clean completion.',
|
|
2739
2530
|
});
|
|
2740
2531
|
this.persistLocalLoopOutcome(prompt, exhaustedEval, liveOutcome);
|
|
2741
|
-
if (this.jsonOutput) {
|
|
2742
|
-
process.exitCode = 1;
|
|
2743
|
-
console.log(JSON.stringify({
|
|
2744
|
-
success: false,
|
|
2745
|
-
mode: 'agent',
|
|
2746
|
-
model: this.currentModel,
|
|
2747
|
-
partial: true,
|
|
2748
|
-
content: 'Task complete.',
|
|
2749
|
-
error: 'Agent exhausted the maximum local tool loop turns before reaching a clean completion.',
|
|
2750
|
-
statusHeadline: exhaustedEval.statusHeadline,
|
|
2751
|
-
metadata: {
|
|
2752
|
-
executionPath: 'local-agent-loop',
|
|
2753
|
-
outcomeTruth: {
|
|
2754
|
-
executorSucceeded: false,
|
|
2755
|
-
uiTheme: exhaustedEval.uiTheme,
|
|
2756
|
-
},
|
|
2757
|
-
},
|
|
2758
|
-
}, null, 2));
|
|
2759
|
-
}
|
|
2760
|
-
else {
|
|
2761
|
-
console.log('Task complete.');
|
|
2762
|
-
}
|
|
2763
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
|
+
});
|
|
2764
2537
|
}
|
|
2765
2538
|
async primeBypassedTargetFileContext(prompt) {
|
|
2766
2539
|
if (!this.directPromptMode || !this.tools) {
|
|
@@ -2791,235 +2564,6 @@ export class ChatCommand {
|
|
|
2791
2564
|
this.messages.push({ role: 'system', content: readSummary });
|
|
2792
2565
|
}
|
|
2793
2566
|
}
|
|
2794
|
-
/**
|
|
2795
|
-
* Sub-second path for trivial HTML/hello-world: Template Service → index.html (no V3 planner).
|
|
2796
|
-
*/
|
|
2797
|
-
async tryTemplateInstantPath(prompt) {
|
|
2798
|
-
const route = this.lastAgentRoute;
|
|
2799
|
-
if (route?.path !== 'template-instant' && !isTrivialHtmlPageRequest(prompt)) {
|
|
2800
|
-
return false;
|
|
2801
|
-
}
|
|
2802
|
-
let workspacePath = this.currentProjectPath;
|
|
2803
|
-
try {
|
|
2804
|
-
const fromPrompt = this.resolvePromptWorkspacePath(prompt, this.currentProjectPath);
|
|
2805
|
-
if (fromPrompt)
|
|
2806
|
-
workspacePath = fromPrompt;
|
|
2807
|
-
}
|
|
2808
|
-
catch {
|
|
2809
|
-
// use default workspace
|
|
2810
|
-
}
|
|
2811
|
-
if (!this.jsonOutput) {
|
|
2812
|
-
console.log();
|
|
2813
|
-
console.log(chalk.gray('━━━ ROUTING DECISION ━━━'));
|
|
2814
|
-
const r = this.lastAgentRoute;
|
|
2815
|
-
console.log(chalk.gray(`Reason: ${r?.reason || 'template-instant (trivial HTML page)'}`));
|
|
2816
|
-
console.log(chalk.gray(`Path: template-instant [${r?.source || 'regex'}]`));
|
|
2817
|
-
console.log(chalk.gray('Model: template-service'));
|
|
2818
|
-
console.log(chalk.gray('Cloud Eligible: false'));
|
|
2819
|
-
console.log(chalk.gray('Cloud Selected: false'));
|
|
2820
|
-
console.log(chalk.gray('━'.repeat(30)));
|
|
2821
|
-
console.log();
|
|
2822
|
-
console.log(chalk.cyan('⚡ Template instant path — matching template and writing index.html...'));
|
|
2823
|
-
}
|
|
2824
|
-
this.messages.push({ role: 'user', content: prompt });
|
|
2825
|
-
const mustComplete = route?.path === 'template-instant';
|
|
2826
|
-
let result = await runTemplateInstantPath(this.api, prompt, workspacePath, {
|
|
2827
|
-
allowFallback: true,
|
|
2828
|
-
});
|
|
2829
|
-
if (!result.ok && mustComplete) {
|
|
2830
|
-
result = await runTemplateInstantPath(this.api, prompt, workspacePath, {
|
|
2831
|
-
allowFallback: true,
|
|
2832
|
-
forceMinimalFallback: true,
|
|
2833
|
-
});
|
|
2834
|
-
}
|
|
2835
|
-
if (!result.ok) {
|
|
2836
|
-
if (!this.jsonOutput) {
|
|
2837
|
-
console.log(chalk.yellow(`Template instant path skipped: ${result.error || 'no match'}`));
|
|
2838
|
-
}
|
|
2839
|
-
return false;
|
|
2840
|
-
}
|
|
2841
|
-
const summary = [
|
|
2842
|
-
`Created ${result.entryPath || 'index.html'} via template-instant`,
|
|
2843
|
-
result.templateName ? `(${result.templateName})` : '',
|
|
2844
|
-
result.processingMs ? `in ${Math.round(result.processingMs)}ms` : '',
|
|
2845
|
-
result.usedFallback ? '(minimal fallback)' : '',
|
|
2846
|
-
].filter(Boolean).join(' ');
|
|
2847
|
-
this.messages.push({ role: 'assistant', content: summary });
|
|
2848
|
-
const liveOutcome = createLiveOutcome();
|
|
2849
|
-
liveOutcome.requiresWorkspaceChanges = true;
|
|
2850
|
-
liveOutcome.workspaceHasOutput = true;
|
|
2851
|
-
liveOutcome.changedFileCount = 1;
|
|
2852
|
-
liveOutcome.tasksTotal = 1;
|
|
2853
|
-
liveOutcome.tasksSucceeded = 1;
|
|
2854
|
-
liveOutcome.answerContent = summary;
|
|
2855
|
-
const evaluation = evaluateExecutorSuccess(liveOutcome);
|
|
2856
|
-
this.commitAgentRunOutcome({
|
|
2857
|
-
prompt,
|
|
2858
|
-
originalPrompt: null,
|
|
2859
|
-
taskId: null,
|
|
2860
|
-
contextId: null,
|
|
2861
|
-
tasksSucceeded: 1,
|
|
2862
|
-
tasksTotal: 1,
|
|
2863
|
-
failedTaskIds: [],
|
|
2864
|
-
unfinishedTaskIds: [],
|
|
2865
|
-
qualityScore: null,
|
|
2866
|
-
qualityMissing: [],
|
|
2867
|
-
qualityBlockers: [],
|
|
2868
|
-
hasOutput: true,
|
|
2869
|
-
answerContent: summary,
|
|
2870
|
-
selfHealStatus: 'skipped',
|
|
2871
|
-
selfHealTool: null,
|
|
2872
|
-
plannerError: null,
|
|
2873
|
-
executorError: null,
|
|
2874
|
-
clientToolErrors: [],
|
|
2875
|
-
transportErrors: [],
|
|
2876
|
-
workspacePath: workspacePath || null,
|
|
2877
|
-
workspaceSyncIssue: null,
|
|
2878
|
-
finishedAt: Date.now(),
|
|
2879
|
-
}, prompt);
|
|
2880
|
-
if (!this.jsonOutput) {
|
|
2881
|
-
console.log(chalk.green(`✓ ${summary}`));
|
|
2882
|
-
console.log(chalk.gray(' Run: vigthoria preview'));
|
|
2883
|
-
this.printAgentRunSummary(this.lastAgentRunOutcome, evaluation, 1);
|
|
2884
|
-
}
|
|
2885
|
-
else {
|
|
2886
|
-
const routingPolicy = this.resolveAgentExecutionPolicy(prompt);
|
|
2887
|
-
console.log(JSON.stringify({
|
|
2888
|
-
success: evaluation.executorSucceeded,
|
|
2889
|
-
mode: 'agent',
|
|
2890
|
-
model: routingPolicy.selectedModel,
|
|
2891
|
-
routingPolicy,
|
|
2892
|
-
taskId: null,
|
|
2893
|
-
contextId: null,
|
|
2894
|
-
partial: false,
|
|
2895
|
-
content: summary,
|
|
2896
|
-
statusHeadline: evaluation.statusHeadline,
|
|
2897
|
-
tasksSucceeded: 1,
|
|
2898
|
-
tasksTotal: 1,
|
|
2899
|
-
metadata: {
|
|
2900
|
-
source: 'template-instant',
|
|
2901
|
-
templateName: result.templateName,
|
|
2902
|
-
processingMs: result.processingMs,
|
|
2903
|
-
route: this.lastAgentRoute,
|
|
2904
|
-
},
|
|
2905
|
-
}, null, 2));
|
|
2906
|
-
}
|
|
2907
|
-
return true;
|
|
2908
|
-
}
|
|
2909
|
-
async tryDirectSingleFileFlow(prompt) {
|
|
2910
|
-
if (!this.directPromptMode || !this.tools) {
|
|
2911
|
-
return false;
|
|
2912
|
-
}
|
|
2913
|
-
const targetFile = this.inferTargetFileFromPrompt(prompt);
|
|
2914
|
-
if (!targetFile) {
|
|
2915
|
-
return false;
|
|
2916
|
-
}
|
|
2917
|
-
if (this.shouldBypassDirectSingleFileFlow(targetFile, prompt)) {
|
|
2918
|
-
return false;
|
|
2919
|
-
}
|
|
2920
|
-
const readCall = {
|
|
2921
|
-
tool: 'read_file',
|
|
2922
|
-
args: { path: targetFile },
|
|
2923
|
-
};
|
|
2924
|
-
if (!this.jsonOutput) {
|
|
2925
|
-
console.log(chalk.cyan(`⚙ Executing: ${readCall.tool}`));
|
|
2926
|
-
}
|
|
2927
|
-
const readResult = await this.tools.execute(readCall);
|
|
2928
|
-
const readSummary = this.formatToolResult(readCall, readResult);
|
|
2929
|
-
if (!this.jsonOutput) {
|
|
2930
|
-
console.log(readResult.success ? chalk.gray(readSummary) : chalk.red(readSummary));
|
|
2931
|
-
}
|
|
2932
|
-
this.messages.push({ role: 'system', content: readSummary });
|
|
2933
|
-
if (!readResult.success || !readResult.output) {
|
|
2934
|
-
return false;
|
|
2935
|
-
}
|
|
2936
|
-
let rewrittenContent = this.tryDeterministicSingleFileRewrite(prompt, targetFile, readResult.output);
|
|
2937
|
-
if (!rewrittenContent) {
|
|
2938
|
-
const rewriteMessages = [
|
|
2939
|
-
{
|
|
2940
|
-
role: 'system',
|
|
2941
|
-
content: [
|
|
2942
|
-
'You are repairing a single file for a CLI agent task.',
|
|
2943
|
-
`Return only the final contents for ${targetFile}.`,
|
|
2944
|
-
'Do not use Markdown fences.',
|
|
2945
|
-
'Do not add explanations before or after the file contents.',
|
|
2946
|
-
'Produce complete, runnable code or markup.',
|
|
2947
|
-
].join('\n'),
|
|
2948
|
-
},
|
|
2949
|
-
{
|
|
2950
|
-
role: 'user',
|
|
2951
|
-
content: [
|
|
2952
|
-
`Task: ${prompt}`,
|
|
2953
|
-
`Target file: ${targetFile}`,
|
|
2954
|
-
'Current file contents:',
|
|
2955
|
-
readResult.output,
|
|
2956
|
-
].join('\n\n'),
|
|
2957
|
-
},
|
|
2958
|
-
];
|
|
2959
|
-
const rewriteResponse = await this.callApi('Rewrite target file', () => this.api.chat(rewriteMessages, this.currentModel));
|
|
2960
|
-
rewrittenContent = this.extractFinalFileContent(rewriteResponse.message, targetFile);
|
|
2961
|
-
}
|
|
2962
|
-
if (!rewrittenContent) {
|
|
2963
|
-
return false;
|
|
2964
|
-
}
|
|
2965
|
-
const writeCall = {
|
|
2966
|
-
tool: 'write_file',
|
|
2967
|
-
args: {
|
|
2968
|
-
path: targetFile,
|
|
2969
|
-
content: rewrittenContent,
|
|
2970
|
-
},
|
|
2971
|
-
};
|
|
2972
|
-
if (!this.jsonOutput) {
|
|
2973
|
-
console.log(chalk.cyan(`⚙ Executing: ${writeCall.tool}`));
|
|
2974
|
-
}
|
|
2975
|
-
const writeResult = await this.tools.execute(writeCall);
|
|
2976
|
-
const writeSummary = this.formatToolResult(writeCall, writeResult);
|
|
2977
|
-
if (!this.jsonOutput) {
|
|
2978
|
-
console.log(writeResult.success ? chalk.gray(writeSummary) : chalk.red(writeSummary));
|
|
2979
|
-
}
|
|
2980
|
-
this.messages.push({ role: 'system', content: writeSummary });
|
|
2981
|
-
if (!writeResult.success) {
|
|
2982
|
-
return false;
|
|
2983
|
-
}
|
|
2984
|
-
const previewGate = await this.callApi('Run Template Service preview gate', () => this.api.runTemplateServicePreviewGate(prompt, {
|
|
2985
|
-
workspacePath: this.currentProjectPath,
|
|
2986
|
-
projectPath: this.currentProjectPath,
|
|
2987
|
-
targetPath: this.currentProjectPath,
|
|
2988
|
-
rawPrompt: prompt,
|
|
2989
|
-
executionSurface: 'cli',
|
|
2990
|
-
clientSurface: 'cli',
|
|
2991
|
-
}));
|
|
2992
|
-
const success = previewGate.required ? previewGate.passed === true : true;
|
|
2993
|
-
if (!success) {
|
|
2994
|
-
process.exitCode = 1;
|
|
2995
|
-
}
|
|
2996
|
-
if (this.jsonOutput) {
|
|
2997
|
-
console.log(JSON.stringify({
|
|
2998
|
-
success,
|
|
2999
|
-
mode: 'agent',
|
|
3000
|
-
model: this.currentModel,
|
|
3001
|
-
partial: false,
|
|
3002
|
-
content: `Updated ${targetFile}.`,
|
|
3003
|
-
metadata: {
|
|
3004
|
-
executionPath: 'direct-single-file',
|
|
3005
|
-
targetFile,
|
|
3006
|
-
previewGate,
|
|
3007
|
-
},
|
|
3008
|
-
}, null, 2));
|
|
3009
|
-
}
|
|
3010
|
-
else {
|
|
3011
|
-
console.log(`Updated ${targetFile}.`);
|
|
3012
|
-
if (previewGate.required) {
|
|
3013
|
-
if (previewGate.passed) {
|
|
3014
|
-
console.log(chalk.gray(`Template Service preview gate: passed via ${previewGate.backendUrl || 'unknown backend'}`));
|
|
3015
|
-
}
|
|
3016
|
-
else {
|
|
3017
|
-
console.log(chalk.yellow(`Template Service preview gate: failed${previewGate.error ? ` - ${previewGate.error}` : ''}`));
|
|
3018
|
-
}
|
|
3019
|
-
}
|
|
3020
|
-
}
|
|
3021
|
-
return true;
|
|
3022
|
-
}
|
|
3023
2567
|
taskRequiresWorkspaceChanges(prompt) {
|
|
3024
2568
|
return promptRequiresWorkspaceChanges(prompt);
|
|
3025
2569
|
}
|
|
@@ -3099,15 +2643,7 @@ export class ChatCommand {
|
|
|
3099
2643
|
console.log();
|
|
3100
2644
|
}
|
|
3101
2645
|
// Reset streaming counters for new workflow
|
|
3102
|
-
this.
|
|
3103
|
-
this.v3ToolCallCount = 0;
|
|
3104
|
-
this.v3LastActivity = Date.now();
|
|
3105
|
-
this.v3StreamingStarted = false;
|
|
3106
|
-
this.v3StreamedAnswerBuffer = '';
|
|
3107
|
-
this.v3StreamedAnswerDisplayed = false;
|
|
3108
|
-
this.v3StartSeen = false;
|
|
3109
|
-
this.v3SeenToolCalls.clear();
|
|
3110
|
-
this.v3SeenToolResults.clear();
|
|
2646
|
+
this.streamState.reset();
|
|
3111
2647
|
emitDeckEvent({ type: 'agent_start' });
|
|
3112
2648
|
const taskDisplay = new TaskDisplay(['Analyse workspace', 'Execute tasks', 'Validate output', 'Self-heal'], false);
|
|
3113
2649
|
taskDisplay.start(0);
|
|
@@ -3186,6 +2722,12 @@ export class ChatCommand {
|
|
|
3186
2722
|
|| isAgentRetryPrompt(contextualPrompt)
|
|
3187
2723
|
|| isBuiltContinuePrompt(contextualPrompt)
|
|
3188
2724
|
|| isBuiltRetryPrompt(contextualPrompt);
|
|
2725
|
+
const initialExecutionStatus = isAgentRetryPrompt(contextualPrompt)
|
|
2726
|
+
|| isBuiltRetryPrompt(contextualPrompt)
|
|
2727
|
+
? 'retry'
|
|
2728
|
+
: shouldResumePlan
|
|
2729
|
+
? 'continue'
|
|
2730
|
+
: 'submitted';
|
|
3189
2731
|
const agentExecutionHints = shouldResumePlan
|
|
3190
2732
|
? {
|
|
3191
2733
|
...executionHints,
|
|
@@ -3193,18 +2735,39 @@ export class ChatCommand {
|
|
|
3193
2735
|
...(resumeTaskIds.length > 0 ? { remaining_task_ids: resumeTaskIds } : {}),
|
|
3194
2736
|
}
|
|
3195
2737
|
: executionHints;
|
|
2738
|
+
const previousExecutionId = this.currentSession?.activeAgentRun?.executionId || null;
|
|
2739
|
+
const executionId = `cli-${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
3196
2740
|
const workspaceContext = {
|
|
3197
2741
|
workspacePath: workspacePath,
|
|
3198
2742
|
projectPath: workspacePath,
|
|
3199
2743
|
targetPath: workspacePath,
|
|
2744
|
+
contextId: executionId,
|
|
2745
|
+
executionId,
|
|
3200
2746
|
...runtimeContext,
|
|
3201
2747
|
};
|
|
2748
|
+
this.persistAgentExecutionCheckpoint({
|
|
2749
|
+
executionId,
|
|
2750
|
+
parentExecutionId: shouldResumePlan ? previousExecutionId : null,
|
|
2751
|
+
contextId: executionId,
|
|
2752
|
+
prompt,
|
|
2753
|
+
originalPrompt: priorOutcome?.originalPrompt || priorOutcome?.prompt || prompt,
|
|
2754
|
+
workspacePath,
|
|
2755
|
+
userTier: String(this.config.get('subscription')?.plan || '').trim() || null,
|
|
2756
|
+
taskType: agentTaskType,
|
|
2757
|
+
workflowType,
|
|
2758
|
+
status: initialExecutionStatus,
|
|
2759
|
+
submittedAt: new Date().toISOString(),
|
|
2760
|
+
updatedAt: new Date().toISOString(),
|
|
2761
|
+
failedTaskIds: resumeTaskIds,
|
|
2762
|
+
unfinishedTaskIds: resumeTaskIds.length ? resumeTaskIds : ['agent-run-in-progress'],
|
|
2763
|
+
error: null,
|
|
2764
|
+
});
|
|
3202
2765
|
// Start workspace watcher for bidirectional real-time sync
|
|
3203
2766
|
let watcher = null;
|
|
3204
2767
|
if (this.shouldStartWorkspaceWatcher(workspacePath)) {
|
|
3205
2768
|
watcher = new WorkspaceWatcher({
|
|
3206
2769
|
workspaceRoot: workspacePath,
|
|
3207
|
-
onFileChange: (relativePath,
|
|
2770
|
+
onFileChange: (relativePath, _content, action) => {
|
|
3208
2771
|
this.logger.debug(`Local change detected: ${action} ${relativePath}`);
|
|
3209
2772
|
},
|
|
3210
2773
|
});
|
|
@@ -3252,6 +2815,7 @@ export class ChatCommand {
|
|
|
3252
2815
|
return;
|
|
3253
2816
|
}
|
|
3254
2817
|
if (event.type === 'plan') {
|
|
2818
|
+
this.updateAgentExecutionCheckpoint({ status: 'running' });
|
|
3255
2819
|
taskDisplay.complete(0);
|
|
3256
2820
|
const tasks = event?.plan?.tasks;
|
|
3257
2821
|
if (Array.isArray(tasks) && tasks.length > 0) {
|
|
@@ -3277,10 +2841,7 @@ export class ChatCommand {
|
|
|
3277
2841
|
noteAnalysisToolUse(toolName, liveOutcome);
|
|
3278
2842
|
}
|
|
3279
2843
|
else if (event.type === 'complete') {
|
|
3280
|
-
|
|
3281
|
-
if (Number.isFinite(tt) && tt > 0) {
|
|
3282
|
-
liveOutcome.tasksTotal = tt;
|
|
3283
|
-
}
|
|
2844
|
+
handleRunCompleteEvent(event, liveOutcome);
|
|
3284
2845
|
if (Number(event.discovery_tools_used) > 0) {
|
|
3285
2846
|
liveOutcome.analysisToolsUsed = Math.max(liveOutcome.analysisToolsUsed, Number(event.discovery_tools_used));
|
|
3286
2847
|
}
|
|
@@ -3327,23 +2888,23 @@ export class ChatCommand {
|
|
|
3327
2888
|
if (spinner) {
|
|
3328
2889
|
spinner.stop();
|
|
3329
2890
|
}
|
|
3330
|
-
if (this.
|
|
2891
|
+
if (this.streamState.streamingStarted && !this.streamState.streamedAnswerDisplayed) {
|
|
3331
2892
|
this.displayV3StreamedAnswer();
|
|
3332
2893
|
}
|
|
3333
2894
|
const previewGate = (response.metadata?.previewGate || null);
|
|
3334
2895
|
const workspaceHasOutput = this.api.hasAgentWorkspaceOutput(workspaceContext);
|
|
3335
2896
|
const changedFileCount = response.changedFiles ? Object.keys(response.changedFiles).length : 0;
|
|
3336
2897
|
const requiresWorkspaceChanges = taskRequiresWorkspaceChangesWithContext(contextualPrompt, intentContext);
|
|
3337
|
-
const answerContent = normalizeAgentAnswerContent(response.content, this.
|
|
2898
|
+
const answerContent = normalizeAgentAnswerContent(response.content, this.streamState.streamedAnswerBuffer);
|
|
3338
2899
|
liveOutcome.changedFileCount = changedFileCount;
|
|
3339
2900
|
liveOutcome.requiresWorkspaceChanges = requiresWorkspaceChanges;
|
|
3340
2901
|
liveOutcome.workspaceHasOutput = requiresWorkspaceChanges ? workspaceHasOutput : false;
|
|
3341
2902
|
liveOutcome.answerContent = answerContent;
|
|
3342
2903
|
const success = previewGate?.required === true
|
|
3343
|
-
?
|
|
2904
|
+
? previewGate?.passed === true && previewGate?.skipped !== true
|
|
3344
2905
|
: true;
|
|
3345
2906
|
if (!success) {
|
|
3346
|
-
if (this.isLegacyAgentFallbackAllowed()) {
|
|
2907
|
+
if (previewGate?.required !== true && this.isLegacyAgentFallbackAllowed()) {
|
|
3347
2908
|
if (spinner) {
|
|
3348
2909
|
spinner.stop();
|
|
3349
2910
|
}
|
|
@@ -3356,25 +2917,16 @@ export class ChatCommand {
|
|
|
3356
2917
|
if (spinner) {
|
|
3357
2918
|
spinner.stop();
|
|
3358
2919
|
}
|
|
3359
|
-
this.
|
|
2920
|
+
if (!this.jsonOutput)
|
|
2921
|
+
this.logger.error(errorMessage);
|
|
3360
2922
|
this.messages.push({ role: 'assistant', content: errorMessage });
|
|
3361
|
-
if (this.jsonOutput) {
|
|
3362
|
-
process.exitCode = 1;
|
|
3363
|
-
console.log(JSON.stringify({
|
|
3364
|
-
success: false,
|
|
3365
|
-
mode: 'agent',
|
|
3366
|
-
model: routingPolicy.selectedModel,
|
|
3367
|
-
partial: false,
|
|
3368
|
-
content: '',
|
|
3369
|
-
error: errorMessage,
|
|
3370
|
-
metadata: { executionPath: 'v3-agent', previewGate },
|
|
3371
|
-
}, null, 2));
|
|
3372
|
-
}
|
|
3373
2923
|
watcher?.stop();
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
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
|
+
});
|
|
3378
2930
|
}
|
|
3379
2931
|
if (!this.jsonOutput && previewGate?.required) {
|
|
3380
2932
|
if (previewGate.passed) {
|
|
@@ -3407,7 +2959,10 @@ export class ChatCommand {
|
|
|
3407
2959
|
liveOutcome.answerContent = '';
|
|
3408
2960
|
}
|
|
3409
2961
|
const runEvaluation = evaluateExecutorSuccess(liveOutcome);
|
|
3410
|
-
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
|
+
}
|
|
3411
2966
|
let finalAnswer = answerContent;
|
|
3412
2967
|
if (isToolEvidenceStubAnswer(finalAnswer)) {
|
|
3413
2968
|
finalAnswer = '';
|
|
@@ -3427,7 +2982,7 @@ export class ChatCommand {
|
|
|
3427
2982
|
executorSucceeded = false;
|
|
3428
2983
|
}
|
|
3429
2984
|
if (!this.jsonOutput && !this.directPromptMode) {
|
|
3430
|
-
if (this.
|
|
2985
|
+
if (this.streamState.streamedAnswerDisplayed) {
|
|
3431
2986
|
// Highlighted report already printed once above.
|
|
3432
2987
|
}
|
|
3433
2988
|
else if (isSubstantiveAgentAnswer(finalAnswer)) {
|
|
@@ -3523,9 +3078,6 @@ export class ChatCommand {
|
|
|
3523
3078
|
}
|
|
3524
3079
|
taskDisplay.finalize();
|
|
3525
3080
|
// ────────────────────────────────────────────────────────────────
|
|
3526
|
-
if (!executorSucceeded) {
|
|
3527
|
-
process.exitCode = 1;
|
|
3528
|
-
}
|
|
3529
3081
|
this.commitAgentRunOutcome({
|
|
3530
3082
|
prompt,
|
|
3531
3083
|
originalPrompt: null,
|
|
@@ -3552,15 +3104,38 @@ export class ChatCommand {
|
|
|
3552
3104
|
: null,
|
|
3553
3105
|
finishedAt: Date.now(),
|
|
3554
3106
|
}, prompt);
|
|
3107
|
+
this.updateAgentExecutionCheckpoint({
|
|
3108
|
+
status: executorSucceeded ? 'completed' : 'failed',
|
|
3109
|
+
contextId: response.contextId || executionId,
|
|
3110
|
+
failedTaskIds: [...liveOutcome.failedTaskIds],
|
|
3111
|
+
unfinishedTaskIds: [...liveOutcome.unfinishedTaskIds],
|
|
3112
|
+
error: executorSucceeded
|
|
3113
|
+
? null
|
|
3114
|
+
: (liveOutcome.executorError || liveOutcome.plannerError || runEvaluation.statusHeadline),
|
|
3115
|
+
});
|
|
3555
3116
|
if (!this.jsonOutput && !this.directPromptMode) {
|
|
3556
3117
|
if (this.lastAgentRunOutcome) {
|
|
3557
3118
|
this.printAgentRunSummary(this.lastAgentRunOutcome, runEvaluation, changedFileCount);
|
|
3558
3119
|
}
|
|
3559
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
|
+
}
|
|
3560
3138
|
if (this.jsonOutput) {
|
|
3561
|
-
if (!executorSucceeded) {
|
|
3562
|
-
process.exitCode = 1;
|
|
3563
|
-
}
|
|
3564
3139
|
emitAgentJsonOutput({
|
|
3565
3140
|
success: executorSucceeded,
|
|
3566
3141
|
mode: 'agent',
|
|
@@ -3593,6 +3168,11 @@ export class ChatCommand {
|
|
|
3593
3168
|
catch (error) {
|
|
3594
3169
|
this.stopV3IdleWatch();
|
|
3595
3170
|
watcher?.stop();
|
|
3171
|
+
if (error instanceof CliCommandError) {
|
|
3172
|
+
if (spinner)
|
|
3173
|
+
spinner.stop();
|
|
3174
|
+
throw error;
|
|
3175
|
+
}
|
|
3596
3176
|
if (!this.api.hasAgentWorkspaceOutput(workspaceContext)) {
|
|
3597
3177
|
const recovered = await this.tryRecoverV3ServiceAndRetry(executionPrompt, prompt, workspaceContext, routingPolicy, spinner, error);
|
|
3598
3178
|
if (recovered) {
|
|
@@ -3653,25 +3233,13 @@ export class ChatCommand {
|
|
|
3653
3233
|
workspaceSyncIssue: null,
|
|
3654
3234
|
finishedAt: Date.now(),
|
|
3655
3235
|
}, prompt);
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
mode: 'agent',
|
|
3664
|
-
model: routingPolicy.selectedModel,
|
|
3665
|
-
partial: liveOutcome.tasksSucceeded > 0 && liveOutcome.tasksTotal > 0,
|
|
3666
|
-
content: '',
|
|
3667
|
-
error: errorMessage,
|
|
3668
|
-
statusHeadline: evaluateExecutorSuccess(liveOutcome).statusHeadline,
|
|
3669
|
-
tasksSucceeded: liveOutcome.tasksSucceeded,
|
|
3670
|
-
tasksTotal: liveOutcome.tasksTotal,
|
|
3671
|
-
metadata: { executionPath: 'v3-agent' },
|
|
3672
|
-
}, null, 2));
|
|
3673
|
-
}
|
|
3674
|
-
else if (!this.directPromptMode) {
|
|
3236
|
+
this.updateAgentExecutionCheckpoint({
|
|
3237
|
+
status: 'failed',
|
|
3238
|
+
failedTaskIds: [...liveOutcome.failedTaskIds],
|
|
3239
|
+
unfinishedTaskIds: [...liveOutcome.unfinishedTaskIds],
|
|
3240
|
+
error: liveOutcome.executorError || safeDetail || 'Connection aborted',
|
|
3241
|
+
});
|
|
3242
|
+
if (!this.jsonOutput && !this.directPromptMode) {
|
|
3675
3243
|
const failedEval = evaluateExecutorSuccess({
|
|
3676
3244
|
...liveOutcome,
|
|
3677
3245
|
executorFailed: true,
|
|
@@ -3684,7 +3252,20 @@ export class ChatCommand {
|
|
|
3684
3252
|
this.printAgentRunSummary(this.lastAgentRunOutcome, failedEval, 0);
|
|
3685
3253
|
}
|
|
3686
3254
|
}
|
|
3687
|
-
|
|
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
|
+
});
|
|
3688
3269
|
}
|
|
3689
3270
|
}
|
|
3690
3271
|
async tryRecoverV3ServiceAndRetry(executionPrompt, rawPrompt, workspaceContext, routingPolicy, spinner, error) {
|
|
@@ -3698,12 +3279,13 @@ export class ChatCommand {
|
|
|
3698
3279
|
if (!this.jsonOutput) {
|
|
3699
3280
|
console.log(chalk.yellow(`V3 recovery: ${recovery.message} Retrying once...`));
|
|
3700
3281
|
}
|
|
3701
|
-
this.
|
|
3702
|
-
this.
|
|
3703
|
-
this.
|
|
3704
|
-
this.
|
|
3705
|
-
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;
|
|
3706
3287
|
try {
|
|
3288
|
+
this.updateAgentExecutionCheckpoint({ status: 'running' });
|
|
3707
3289
|
const retryPriorPrompt = this.getPreviousActionablePrompt();
|
|
3708
3290
|
const retryContext = { priorPrompt: retryPriorPrompt || null };
|
|
3709
3291
|
const retryTaskType = inferAgentTaskTypeWithContext(rawPrompt, retryContext);
|
|
@@ -3730,27 +3312,40 @@ export class ChatCommand {
|
|
|
3730
3312
|
if (spinner && spinner.isSpinning) {
|
|
3731
3313
|
spinner.stop();
|
|
3732
3314
|
}
|
|
3733
|
-
if (this.
|
|
3315
|
+
if (this.streamState.streamingStarted) {
|
|
3734
3316
|
process.stdout.write('\n');
|
|
3735
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
|
+
}
|
|
3736
3326
|
if (this.jsonOutput) {
|
|
3737
|
-
|
|
3327
|
+
emitAgentJsonOutput({
|
|
3738
3328
|
success: true,
|
|
3739
3329
|
mode: 'agent',
|
|
3740
3330
|
model: routingPolicy.selectedModel,
|
|
3741
3331
|
routingPolicy,
|
|
3742
3332
|
taskId: retryResponse.taskId || null,
|
|
3743
3333
|
contextId: retryResponse.contextId || null,
|
|
3744
|
-
partial:
|
|
3334
|
+
partial: false,
|
|
3745
3335
|
content: retryResponse.content || 'V3 agent workflow completed after recovery.',
|
|
3746
3336
|
metadata: { ...(retryResponse.metadata || {}), recoveryAttempted: true },
|
|
3747
|
-
}
|
|
3337
|
+
});
|
|
3748
3338
|
}
|
|
3749
|
-
else if (!this.
|
|
3339
|
+
else if (!this.streamState.streamingStarted && retryResponse.content) {
|
|
3750
3340
|
console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
|
|
3751
3341
|
console.log(retryResponse.content);
|
|
3752
3342
|
}
|
|
3753
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
|
+
});
|
|
3754
3349
|
return true;
|
|
3755
3350
|
}
|
|
3756
3351
|
catch {
|
|
@@ -4357,6 +3952,13 @@ export class ChatCommand {
|
|
|
4357
3952
|
}
|
|
4358
3953
|
inferTargetFilesFromPrompt(prompt) {
|
|
4359
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
|
+
]);
|
|
4360
3962
|
const matches = Array.from(prompt.matchAll(/([A-Za-z0-9_./-]+\.[A-Za-z0-9_-]+)/g));
|
|
4361
3963
|
for (const match of matches) {
|
|
4362
3964
|
const candidate = match[1];
|
|
@@ -4364,7 +3966,15 @@ export class ChatCommand {
|
|
|
4364
3966
|
continue;
|
|
4365
3967
|
}
|
|
4366
3968
|
const resolved = path.resolve(this.currentProjectPath, candidate);
|
|
4367
|
-
|
|
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)) {
|
|
4368
3978
|
if (!candidates.includes(candidate)) {
|
|
4369
3979
|
candidates.push(candidate);
|
|
4370
3980
|
}
|
|
@@ -4375,36 +3985,6 @@ export class ChatCommand {
|
|
|
4375
3985
|
inferTargetFileFromPrompt(prompt) {
|
|
4376
3986
|
return this.inferTargetFilesFromPrompt(prompt)[0] || null;
|
|
4377
3987
|
}
|
|
4378
|
-
workspaceContainsHtmlEntry(rootDir) {
|
|
4379
|
-
const stack = [rootDir];
|
|
4380
|
-
while (stack.length > 0) {
|
|
4381
|
-
const currentDir = stack.pop();
|
|
4382
|
-
if (!currentDir) {
|
|
4383
|
-
continue;
|
|
4384
|
-
}
|
|
4385
|
-
let entries = [];
|
|
4386
|
-
try {
|
|
4387
|
-
entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
4388
|
-
}
|
|
4389
|
-
catch {
|
|
4390
|
-
continue;
|
|
4391
|
-
}
|
|
4392
|
-
for (const entry of entries) {
|
|
4393
|
-
if (entry.name === 'node_modules' || entry.name === '.git') {
|
|
4394
|
-
continue;
|
|
4395
|
-
}
|
|
4396
|
-
const fullPath = path.join(currentDir, entry.name);
|
|
4397
|
-
if (entry.isDirectory()) {
|
|
4398
|
-
stack.push(fullPath);
|
|
4399
|
-
continue;
|
|
4400
|
-
}
|
|
4401
|
-
if (entry.isFile() && entry.name.toLowerCase().endsWith('.html')) {
|
|
4402
|
-
return true;
|
|
4403
|
-
}
|
|
4404
|
-
}
|
|
4405
|
-
}
|
|
4406
|
-
return false;
|
|
4407
|
-
}
|
|
4408
3988
|
shouldBypassDirectSingleFileFlow(targetFile, prompt) {
|
|
4409
3989
|
const referencedFiles = this.inferTargetFilesFromPrompt(prompt);
|
|
4410
3990
|
const editableFiles = referencedFiles.filter((filePath) => !this.isProtectedFileReferenceSafe(prompt, filePath));
|
|
@@ -4421,36 +4001,6 @@ export class ChatCommand {
|
|
|
4421
4001
|
return true;
|
|
4422
4002
|
return false;
|
|
4423
4003
|
}
|
|
4424
|
-
shouldPreferLocalAgentLoop(prompt) {
|
|
4425
|
-
if (this.shouldRequireV3AgentWorkflow(prompt)) {
|
|
4426
|
-
return false;
|
|
4427
|
-
}
|
|
4428
|
-
const forceV3 = /^(1|true|yes)$/i.test(String(process.env.VIGTHORIA_FORCE_V3_AGENT || ''));
|
|
4429
|
-
if (forceV3) {
|
|
4430
|
-
return false;
|
|
4431
|
-
}
|
|
4432
|
-
if (this.isBrowserTaskPrompt(prompt)) {
|
|
4433
|
-
return false;
|
|
4434
|
-
}
|
|
4435
|
-
// Product default: Agent mode routes through V3.
|
|
4436
|
-
// Local loop is opt-in for explicit offline/debug scenarios only.
|
|
4437
|
-
// Explicit local paths now influence project root resolution, not routing mode.
|
|
4438
|
-
const preferLocalOptIn = /^(1|true|yes)$/i.test(String(process.env.VIGTHORIA_PREFER_LOCAL_AGENT_LOOP || ''));
|
|
4439
|
-
if (!preferLocalOptIn) {
|
|
4440
|
-
return false;
|
|
4441
|
-
}
|
|
4442
|
-
const runtime = this.getRuntimeEnvironmentContext();
|
|
4443
|
-
if (this.directPromptMode && this.isRepoGroundedPrompt(prompt)) {
|
|
4444
|
-
return false;
|
|
4445
|
-
}
|
|
4446
|
-
if (!runtime.serverBindableWorkspace) {
|
|
4447
|
-
return true;
|
|
4448
|
-
}
|
|
4449
|
-
if (!this.directPromptMode) {
|
|
4450
|
-
return true;
|
|
4451
|
-
}
|
|
4452
|
-
return runtime.platform !== 'linux';
|
|
4453
|
-
}
|
|
4454
4004
|
getRuntimeEnvironmentContext() {
|
|
4455
4005
|
const osPlatform = os.platform();
|
|
4456
4006
|
const platform = osPlatform === 'win32'
|
|
@@ -4538,16 +4088,6 @@ export class ChatCommand {
|
|
|
4538
4088
|
];
|
|
4539
4089
|
return protectedPhrases.some((phrase) => normalizedPrompt.includes(phrase));
|
|
4540
4090
|
}
|
|
4541
|
-
isProtectedFileReference(prompt, filePath) {
|
|
4542
|
-
const escapedPath = filePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
4543
|
-
const protectedPatterns = [
|
|
4544
|
-
new RegExp(`do not modify\\s+[-\u001f\s\-\"][` + "'" + `]?${escapedPath}[` + "'" + `]?`, 'i'),
|
|
4545
|
-
new RegExp(`don't modify\\s+[-\u001f\s\-\"][` + "'" + `]?${escapedPath}[` + "'" + `]?`, 'i'),
|
|
4546
|
-
new RegExp(`leave\\s+[-\u001f\s\-\"][` + "'" + `]?${escapedPath}[` + "'" + `]?\\s+unchanged`, 'i'),
|
|
4547
|
-
new RegExp(`without modifying\\s+[-\u001f\s\-\"][` + "'" + `]?${escapedPath}[` + "'" + `]?`, 'i'),
|
|
4548
|
-
];
|
|
4549
|
-
return protectedPatterns.some((pattern) => pattern.test(prompt));
|
|
4550
|
-
}
|
|
4551
4091
|
buildContinuationPrompt() {
|
|
4552
4092
|
const { discovery, searchFailed } = this.agentToolEvidence;
|
|
4553
4093
|
const evidenceLines = [];
|
|
@@ -4641,109 +4181,10 @@ export class ChatCommand {
|
|
|
4641
4181
|
return lines.join('\n');
|
|
4642
4182
|
}
|
|
4643
4183
|
extractToolCalls(message) {
|
|
4644
|
-
|
|
4645
|
-
const seen = new Set();
|
|
4646
|
-
const addCall = (call) => {
|
|
4647
|
-
if (!call)
|
|
4648
|
-
return;
|
|
4649
|
-
const key = `${call.tool}::${JSON.stringify(call.args)}`;
|
|
4650
|
-
if (!seen.has(key)) {
|
|
4651
|
-
seen.add(key);
|
|
4652
|
-
calls.push(call);
|
|
4653
|
-
}
|
|
4654
|
-
};
|
|
4655
|
-
const wrapperRegex = /<tool_call>([\s\S]*?)<\/tool_call>/g;
|
|
4656
|
-
for (const match of message.matchAll(wrapperRegex)) {
|
|
4657
|
-
addCall(this.parseToolPayload(match[1] || ''));
|
|
4658
|
-
}
|
|
4659
|
-
for (const call of this.parseLegacyFunctionToolCalls(message)) {
|
|
4660
|
-
addCall(call);
|
|
4661
|
-
}
|
|
4662
|
-
return calls;
|
|
4663
|
-
}
|
|
4664
|
-
normalizeCliToolName(name) {
|
|
4665
|
-
const normalized = name.trim().toLowerCase();
|
|
4666
|
-
const aliases = {
|
|
4667
|
-
list_directory: 'list_dir',
|
|
4668
|
-
listdirectory: 'list_dir',
|
|
4669
|
-
ls: 'list_dir',
|
|
4670
|
-
dir: 'list_dir',
|
|
4671
|
-
readfile: 'read_file',
|
|
4672
|
-
writefile: 'write_file',
|
|
4673
|
-
editfile: 'edit_file',
|
|
4674
|
-
shell: 'bash',
|
|
4675
|
-
command: 'bash',
|
|
4676
|
-
run_command: 'bash',
|
|
4677
|
-
};
|
|
4678
|
-
return aliases[normalized] || normalized;
|
|
4679
|
-
}
|
|
4680
|
-
parseLegacyFunctionToolCalls(message) {
|
|
4681
|
-
const calls = [];
|
|
4682
|
-
const functionRegex = /<function\s*=\s*["']?([A-Za-z0-9_-]+)["']?>\s*([\s\S]*?)(?:<\/function>|$)/gi;
|
|
4683
|
-
for (const match of message.matchAll(functionRegex)) {
|
|
4684
|
-
const tool = this.normalizeCliToolName(match[1] || '');
|
|
4685
|
-
if (!tool)
|
|
4686
|
-
continue;
|
|
4687
|
-
const body = match[2] || '';
|
|
4688
|
-
const args = {};
|
|
4689
|
-
const parameterRegex = /<parameter\s*=\s*["']?([A-Za-z0-9_-]+)["']?>\s*([\s\S]*?)(?=<parameter\s*=|<\/function>|<\/parameter>|$)/gi;
|
|
4690
|
-
for (const paramMatch of body.matchAll(parameterRegex)) {
|
|
4691
|
-
const key = paramMatch[1] || '';
|
|
4692
|
-
if (!key)
|
|
4693
|
-
continue;
|
|
4694
|
-
args[key] = (paramMatch[2] || '').replace(/<\/parameter>\s*$/i, '').trim();
|
|
4695
|
-
}
|
|
4696
|
-
calls.push({ tool, args });
|
|
4697
|
-
}
|
|
4698
|
-
return calls;
|
|
4699
|
-
}
|
|
4700
|
-
parseToolPayload(payload) {
|
|
4701
|
-
const normalized = payload.trim();
|
|
4702
|
-
if (!normalized) {
|
|
4703
|
-
return null;
|
|
4704
|
-
}
|
|
4705
|
-
try {
|
|
4706
|
-
const parsed = JSON.parse(normalized);
|
|
4707
|
-
if (typeof parsed.tool !== 'string') {
|
|
4708
|
-
return null;
|
|
4709
|
-
}
|
|
4710
|
-
const args = {};
|
|
4711
|
-
if (parsed.args && typeof parsed.args === 'object') {
|
|
4712
|
-
for (const [key, value] of Object.entries(parsed.args)) {
|
|
4713
|
-
args[key] = typeof value === 'string' ? value : JSON.stringify(value);
|
|
4714
|
-
}
|
|
4715
|
-
}
|
|
4716
|
-
return { tool: this.normalizeCliToolName(parsed.tool), args };
|
|
4717
|
-
}
|
|
4718
|
-
catch {
|
|
4719
|
-
return null;
|
|
4720
|
-
}
|
|
4184
|
+
return this.toolCallParser.extract(message);
|
|
4721
4185
|
}
|
|
4722
4186
|
stripToolPayloads(message) {
|
|
4723
|
-
return message
|
|
4724
|
-
.replace(/<tool_call>[\s\S]*?<\/tool_call>/g, '')
|
|
4725
|
-
.replace(/<function\s*=\s*["']?[A-Za-z0-9_-]+["']?>[\s\S]*?(?:<\/function>|$)/gi, '')
|
|
4726
|
-
.trim();
|
|
4727
|
-
}
|
|
4728
|
-
extractFinalFileContent(message, targetFile) {
|
|
4729
|
-
const trimmed = message.trim();
|
|
4730
|
-
if (!trimmed) {
|
|
4731
|
-
return '';
|
|
4732
|
-
}
|
|
4733
|
-
const fencedMatch = trimmed.match(/```(?:[A-Za-z0-9_-]+)?\n([\s\S]*?)```/);
|
|
4734
|
-
if (fencedMatch && fencedMatch[1]) {
|
|
4735
|
-
return fencedMatch[1].trim();
|
|
4736
|
-
}
|
|
4737
|
-
const extension = path.extname(targetFile).toLowerCase();
|
|
4738
|
-
if (extension === '.html') {
|
|
4739
|
-
const htmlStart = trimmed.indexOf('<!DOCTYPE html>') >= 0
|
|
4740
|
-
? trimmed.indexOf('<!DOCTYPE html>')
|
|
4741
|
-
: trimmed.indexOf('<html');
|
|
4742
|
-
if (htmlStart >= 0) {
|
|
4743
|
-
return trimmed.slice(htmlStart).trim();
|
|
4744
|
-
}
|
|
4745
|
-
}
|
|
4746
|
-
return trimmed;
|
|
4187
|
+
return this.toolCallParser.strip(message);
|
|
4747
4188
|
}
|
|
4748
4189
|
/**
|
|
4749
4190
|
* Synthesize a best-effort answer from tool evidence already collected
|
|
@@ -4772,8 +4213,8 @@ export class ChatCommand {
|
|
|
4772
4213
|
resolveDirectModeCompletion(prompt, visibleText) {
|
|
4773
4214
|
// Sanitize first — strip tool output and echoed instructions before
|
|
4774
4215
|
// deciding if the model actually answered the question.
|
|
4775
|
-
const sanitized =
|
|
4776
|
-
if (sanitized && !
|
|
4216
|
+
const sanitized = sanitizeDirectModeOutput((visibleText || '').trim());
|
|
4217
|
+
if (sanitized && !isDirectModeFollowUpQuestion(sanitized)) {
|
|
4777
4218
|
return sanitized;
|
|
4778
4219
|
}
|
|
4779
4220
|
const fallback = this.buildLocalAnalysisFallback(prompt);
|
|
@@ -4782,95 +4223,6 @@ export class ChatCommand {
|
|
|
4782
4223
|
}
|
|
4783
4224
|
return sanitized || 'Task complete.';
|
|
4784
4225
|
}
|
|
4785
|
-
/**
|
|
4786
|
-
* Strip system-prompt echoes, tool execution headers, grounding-rule
|
|
4787
|
-
* parroting, recovery banners, and multi-line tool output blocks from
|
|
4788
|
-
* the model's final answer so that --json output contains only the
|
|
4789
|
-
* substantive answer.
|
|
4790
|
-
*/
|
|
4791
|
-
sanitizeDirectModeOutput(text) {
|
|
4792
|
-
let cleaned = this.stripHiddenThoughtBlocks(text);
|
|
4793
|
-
cleaned = cleaned
|
|
4794
|
-
.replace(/<\|mask_start\|>[\s\S]*?<\|mask_end\|>/g, '')
|
|
4795
|
-
.replace(/<think>[\s\S]*?<\/think>/gi, '')
|
|
4796
|
-
.replace(/<\|(?:mask_start|mask_end)\|>/g, '');
|
|
4797
|
-
// ── Phase 1: Strip entire tool-output blocks ──
|
|
4798
|
-
// Matches "Tool <name> succeeded/FAILED." through the next blank line,
|
|
4799
|
-
// next tool header, or end-of-string. The DOTALL-like [\s\S]*? is
|
|
4800
|
-
// terminated by whichever boundary comes first.
|
|
4801
|
-
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, '');
|
|
4802
|
-
// ── Phase 2: Strip echoed system-prompt / grounding lines ──
|
|
4803
|
-
const contaminationPatterns = [
|
|
4804
|
-
/^\[Agent recovered from backend failure[^\]]*\]\s*/m,
|
|
4805
|
-
/^Evidence gathered before backend failure:?\s*/m,
|
|
4806
|
-
/^MANDATORY CROSS-FILE EVIDENCE[^\n]*\n?/m,
|
|
4807
|
-
/^CONFIRMED CONFLICTING keys[^\n]*/m,
|
|
4808
|
-
/^Keys found ONLY in [^\n]*/m,
|
|
4809
|
-
/^CONSTRAINT: Your answer MUST[^\n]*/m,
|
|
4810
|
-
/^GROUNDING CHECK:[^\n]*/m,
|
|
4811
|
-
/^VERIFICATION PROTOCOL[^\n]*/m,
|
|
4812
|
-
/^Quality gate:[^\n]*/m,
|
|
4813
|
-
/^Evidence collected:[^\n]*/m,
|
|
4814
|
-
/^Warning: \d+ search tool[^\n]*/m,
|
|
4815
|
-
/^Tool results received for direct mode[^\n]*/m,
|
|
4816
|
-
/^Step \d+ complete\. Task:[^\n]*/m,
|
|
4817
|
-
/^Original user request:[^\n]*/m,
|
|
4818
|
-
/^Project root boundary:[^\n]*/m,
|
|
4819
|
-
/^Do not declare success[^\n]*/m,
|
|
4820
|
-
/^Keep working from concrete[^\n]*/m,
|
|
4821
|
-
/^Continue with tool calls if more[^\n]*/m,
|
|
4822
|
-
/^IMPORTANT: Your response must be ONLY[^\n]*/m,
|
|
4823
|
-
/^Because this is a debugging[^\n]*/m,
|
|
4824
|
-
/^If a user is asking which file[^\n]*/m,
|
|
4825
|
-
/^If the request is already[^\n]*/m,
|
|
4826
|
-
/^If more work is required[^\n]*/m,
|
|
4827
|
-
/^Do not ask follow-up[^\n]*/m,
|
|
4828
|
-
/^CRITICAL GROUNDING RULE:[^\n]*/m,
|
|
4829
|
-
/^CROSS-FILE ATTRIBUTION:[^\n]*/m,
|
|
4830
|
-
/^EVIDENCE-GROUNDING RULE:[^\n]*/m,
|
|
4831
|
-
/^CROSS-FILE RULE:[^\n]*/m,
|
|
4832
|
-
/^OUTPUT DISCIPLINE:[^\n]*/m,
|
|
4833
|
-
/^Vigthoria CLI agent operating contract\.[^\n]*/m,
|
|
4834
|
-
/^You are operating inside the project root:[^\n]*/m,
|
|
4835
|
-
/^CRITICAL: Begin working on the user's task[^\n]*/m,
|
|
4836
|
-
/^File: \S+\s*$/m,
|
|
4837
|
-
/^Search status: \S+\s*$/m,
|
|
4838
|
-
/^Output:\s*$/m,
|
|
4839
|
-
/^\[\.\.\. ?truncated\]\s*$/m,
|
|
4840
|
-
/^---\s*$/m,
|
|
4841
|
-
];
|
|
4842
|
-
for (const pat of contaminationPatterns) {
|
|
4843
|
-
cleaned = cleaned.replace(pat, '');
|
|
4844
|
-
}
|
|
4845
|
-
// ── Phase 3: Paragraph-level filter ──
|
|
4846
|
-
// Split into paragraph blocks and discard any that are pure tool
|
|
4847
|
-
// output, file content dumps, or system-instruction echoes.
|
|
4848
|
-
const paragraphs = cleaned.split(/\n\n+/);
|
|
4849
|
-
const kept = paragraphs.filter(p => {
|
|
4850
|
-
const t = p.trim();
|
|
4851
|
-
if (!t)
|
|
4852
|
-
return false;
|
|
4853
|
-
// Discard lingering tool headers
|
|
4854
|
-
if (/^Tool (?:read_file|grep|list_dir|glob|bash|write_file) /i.test(t))
|
|
4855
|
-
return false;
|
|
4856
|
-
// Discard paragraphs that are mostly file-content dumps (many
|
|
4857
|
-
// lines of code with typical code tokens like { } ; = function class)
|
|
4858
|
-
const lines = t.split('\n');
|
|
4859
|
-
if (lines.length > 6) {
|
|
4860
|
-
const codeLines = lines.filter(l => /[{};=]|function |class |const |let |var |import |export |switch |case /.test(l));
|
|
4861
|
-
if (codeLines.length / lines.length > 0.4)
|
|
4862
|
-
return false;
|
|
4863
|
-
}
|
|
4864
|
-
return true;
|
|
4865
|
-
});
|
|
4866
|
-
cleaned = kept.join('\n\n');
|
|
4867
|
-
// Collapse multiple blank lines
|
|
4868
|
-
cleaned = cleaned.replace(/\n{3,}/g, '\n\n').trim();
|
|
4869
|
-
return cleaned || this.stripHiddenThoughtBlocks(text);
|
|
4870
|
-
}
|
|
4871
|
-
isDirectModeFollowUpQuestion(text) {
|
|
4872
|
-
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());
|
|
4873
|
-
}
|
|
4874
4226
|
buildLocalAnalysisFallback(prompt) {
|
|
4875
4227
|
if (!/(analyse|analyze|audit|overview|inspect|review|summari[sz]e|actual state)/i.test(prompt)) {
|
|
4876
4228
|
return '';
|
|
@@ -4912,42 +4264,6 @@ export class ChatCommand {
|
|
|
4912
4264
|
return '';
|
|
4913
4265
|
}
|
|
4914
4266
|
}
|
|
4915
|
-
tryDeterministicSingleFileRewrite(prompt, targetFile, currentContent) {
|
|
4916
|
-
const extension = path.extname(targetFile).toLowerCase();
|
|
4917
|
-
if (extension !== '.html') {
|
|
4918
|
-
return null;
|
|
4919
|
-
}
|
|
4920
|
-
const exactText = this.extractExactTextRequirement(prompt);
|
|
4921
|
-
if (!exactText) {
|
|
4922
|
-
return null;
|
|
4923
|
-
}
|
|
4924
|
-
if (!/hero\s+(?:paragraph|copy)/i.test(prompt)) {
|
|
4925
|
-
return null;
|
|
4926
|
-
}
|
|
4927
|
-
const heroCopyPattern = /(<p[^>]*class=["'][^"']*hero-copy[^"']*["'][^>]*>)([\s\S]*?)(<\/p>)/i;
|
|
4928
|
-
if (heroCopyPattern.test(currentContent)) {
|
|
4929
|
-
const nextContent = currentContent.replace(heroCopyPattern, `$1${exactText}$3`);
|
|
4930
|
-
return nextContent !== currentContent ? nextContent : null;
|
|
4931
|
-
}
|
|
4932
|
-
const heroParagraphPattern = /(<main[^>]*class=["'][^"']*hero[^"']*["'][^>]*>[\s\S]*?<p[^>]*>)([\s\S]*?)(<\/p>)/i;
|
|
4933
|
-
if (heroParagraphPattern.test(currentContent)) {
|
|
4934
|
-
const nextContent = currentContent.replace(heroParagraphPattern, `$1${exactText}$3`);
|
|
4935
|
-
return nextContent !== currentContent ? nextContent : null;
|
|
4936
|
-
}
|
|
4937
|
-
return null;
|
|
4938
|
-
}
|
|
4939
|
-
extractExactTextRequirement(prompt) {
|
|
4940
|
-
const match = prompt.match(/reads exactly:\s*([\s\S]*?)(?:\s+Do not\b|\s+Do\b|$)/i);
|
|
4941
|
-
if (!match || !match[1]) {
|
|
4942
|
-
return null;
|
|
4943
|
-
}
|
|
4944
|
-
const normalized = match[1]
|
|
4945
|
-
.trim()
|
|
4946
|
-
.replace(/^[`"']+/, '')
|
|
4947
|
-
.replace(/[`"']+$/, '')
|
|
4948
|
-
.trim();
|
|
4949
|
-
return normalized || null;
|
|
4950
|
-
}
|
|
4951
4267
|
async executeToolCalls(toolCalls) {
|
|
4952
4268
|
if (!this.tools) {
|
|
4953
4269
|
throw new Error('Agent tools are not initialized.');
|
|
@@ -5056,17 +4372,41 @@ export class ChatCommand {
|
|
|
5056
4372
|
sessionSummary: this.currentSession.memorySummary || '',
|
|
5057
4373
|
});
|
|
5058
4374
|
}
|
|
4375
|
+
persistAgentExecutionCheckpoint(checkpoint) {
|
|
4376
|
+
if (!this.currentSession) {
|
|
4377
|
+
this.currentSession = this.sessionManager.create(this.currentProjectPath, this.currentModel, this.agentMode, this.operatorMode);
|
|
4378
|
+
}
|
|
4379
|
+
this.currentSession.activeAgentRun = checkpoint;
|
|
4380
|
+
this.saveSession();
|
|
4381
|
+
}
|
|
4382
|
+
updateAgentExecutionCheckpoint(update) {
|
|
4383
|
+
if (!this.currentSession?.activeAgentRun) {
|
|
4384
|
+
return;
|
|
4385
|
+
}
|
|
4386
|
+
if (update.status) {
|
|
4387
|
+
assertAgentStatusTransition(this.currentSession.activeAgentRun.status, update.status);
|
|
4388
|
+
}
|
|
4389
|
+
this.currentSession.activeAgentRun = {
|
|
4390
|
+
...this.currentSession.activeAgentRun,
|
|
4391
|
+
...update,
|
|
4392
|
+
updatedAt: new Date().toISOString(),
|
|
4393
|
+
};
|
|
4394
|
+
this.saveSession();
|
|
4395
|
+
}
|
|
5059
4396
|
async requestPermission(action) {
|
|
5060
4397
|
if (this.autoApprove) {
|
|
5061
4398
|
return true;
|
|
5062
4399
|
}
|
|
4400
|
+
if (this.jsonOutput) {
|
|
4401
|
+
return false;
|
|
4402
|
+
}
|
|
5063
4403
|
const rl = readline.createInterface({
|
|
5064
4404
|
input: process.stdin,
|
|
5065
4405
|
output: process.stdout,
|
|
5066
4406
|
});
|
|
5067
4407
|
console.log(action);
|
|
5068
4408
|
const answer = await new Promise((resolve) => {
|
|
5069
|
-
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);
|
|
5070
4410
|
});
|
|
5071
4411
|
rl.close();
|
|
5072
4412
|
const normalized = answer.trim().toLowerCase();
|