vigthoria-cli 1.13.26 → 1.13.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/chat.js +73 -26
- package/dist/commands/config.js +4 -4
- package/dist/commands/fork.d.ts +3 -2
- package/dist/commands/fork.js +124 -123
- package/dist/commands/game.d.ts +8 -0
- package/dist/commands/game.js +113 -9
- package/dist/commands/history.d.ts +0 -1
- package/dist/commands/history.js +8 -22
- package/dist/commands/hub.d.ts +20 -0
- package/dist/commands/hub.js +17 -3
- package/dist/commands/preview.js +7 -2
- package/dist/commands/product-run-registration.js +1 -1
- package/dist/commands/replay.d.ts +0 -1
- package/dist/commands/replay.js +10 -19
- package/dist/commands/repo.js +16 -4
- package/dist/commands/update-registration.js +2 -2
- package/dist/commands/workflow.d.ts +4 -0
- package/dist/commands/workflow.js +27 -0
- package/dist/index.js +6 -4
- package/dist/utils/agentRunOutcome.d.ts +7 -0
- package/dist/utils/agentRunOutcome.js +13 -0
- package/dist/utils/api.d.ts +20 -5
- package/dist/utils/api.js +428 -43
- package/dist/utils/command-policy.js +1 -1
- package/dist/utils/config.d.ts +2 -0
- package/dist/utils/config.js +8 -3
- package/dist/utils/frontend-preview-service.d.ts +1 -0
- package/dist/utils/frontend-preview-service.js +54 -5
- package/dist/utils/model-governance.js +23 -14
- package/dist/utils/model-transport-service.js +1 -1
- package/dist/utils/network-policy.js +15 -3
- package/dist/utils/operator-client.js +23 -4
- package/dist/utils/post-write-validator.js +7 -3
- package/dist/utils/preview-screenshot-adapter.d.ts +16 -41
- package/dist/utils/preview-screenshot-adapter.js +273 -64
- package/dist/utils/runtime-capability.d.ts +7 -0
- package/dist/utils/runtime-capability.js +11 -0
- package/dist/utils/runtime-temp.d.ts +5 -2
- package/dist/utils/runtime-temp.js +125 -29
- package/dist/utils/tools.js +1 -1
- package/dist/utils/v3-stream-events.js +10 -2
- package/dist/utils/v3-workspace-service.d.ts +1 -0
- package/dist/utils/v3-workspace-service.js +38 -1
- package/dist/utils/vigflow-client.d.ts +9 -0
- package/dist/utils/vigflow-client.js +48 -2
- package/dist/utils/workspace-reference.d.ts +8 -0
- package/dist/utils/workspace-reference.js +21 -0
- package/package.json +4 -6
- package/scripts/release/LOCAL_MACHINE_USER_VERIFICATION.md +2 -2
- package/scripts/release/validate-live-service-gates.sh +3 -3
- package/scripts/release/validate-no-go-gates.sh +2 -0
package/dist/utils/api.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Connects to coder.vigthoria.io API endpoints
|
|
4
4
|
*/
|
|
5
5
|
import axios from 'axios';
|
|
6
|
-
import { randomUUID } from 'crypto';
|
|
6
|
+
import { createHash, randomUUID } from 'crypto';
|
|
7
7
|
import fs from 'fs';
|
|
8
8
|
import https from 'https';
|
|
9
9
|
import net from 'net';
|
|
@@ -22,22 +22,24 @@ import { buildClientManifest } from './clientManifest.js';
|
|
|
22
22
|
import { assertTrustedEndpoint, guardedFetch, installAxiosNetworkPolicy, installGlobalFetchPolicy } from './network-policy.js';
|
|
23
23
|
import { assertSafeRelativePath, resolveWorkspacePath } from './workspace-boundary.js';
|
|
24
24
|
import { parseSupportedProcess } from './process-policy.js';
|
|
25
|
-
import {
|
|
25
|
+
import { SystemBrowserScreenshotAdapter } from './preview-screenshot-adapter.js';
|
|
26
26
|
import { normalizeSubscriptionResponse } from './subscription-policy.js';
|
|
27
27
|
import { ModelGovernance } from './model-governance.js';
|
|
28
28
|
import { OperatorClient, OperatorClientError } from './operator-client.js';
|
|
29
29
|
import { CodeOperationsService } from './code-operations-service.js';
|
|
30
30
|
import { McpContextClient } from './mcp-context-client.js';
|
|
31
|
-
import { FrontendPreviewService } from './frontend-preview-service.js';
|
|
31
|
+
import { FrontendPreviewService, isConstrainedStaticFrontendRequest, } from './frontend-preview-service.js';
|
|
32
32
|
import { V3AgentClient } from './v3-agent-client.js';
|
|
33
33
|
import { V3WorkspaceService } from './v3-workspace-service.js';
|
|
34
34
|
import { CapabilityHealthService, } from './capability-health-service.js';
|
|
35
35
|
import { ModelTransportService } from './model-transport-service.js';
|
|
36
36
|
import { VigFlowClient, } from './vigflow-client.js';
|
|
37
37
|
import { MutationJournal, finalizeMutationTransaction, } from './mutation-journal.js';
|
|
38
|
-
import { isSensitivePath, redactSensitiveText, safeChildProcessEnv, scanOutboundContext, } from './secret-policy.js';
|
|
38
|
+
import { containsHighConfidenceSecret, isSensitivePath, redactSensitiveText, safeChildProcessEnv, scanOutboundContext, } from './secret-policy.js';
|
|
39
|
+
import { buildLocalWorkspaceReference } from './workspace-reference.js';
|
|
39
40
|
export const VIGTHORIA_HUB_CREDITS_URL = 'https://hub.vigthoria.io/credits';
|
|
40
41
|
export const VIGTHORIA_SERVER_TEMPORARILY_UNAVAILABLE_MESSAGE = 'Vigthoria Server is temporarily not available. Please try again later. If the issue persists, please contact support.';
|
|
42
|
+
const MAX_V3_CLIENT_MUTATION_BYTES = 512 * 1024;
|
|
41
43
|
export class CLIError extends Error {
|
|
42
44
|
category;
|
|
43
45
|
statusCode;
|
|
@@ -424,7 +426,7 @@ export class APIClient {
|
|
|
424
426
|
extractExpectedFiles: (message, context) => this.extractExpectedWorkspaceFiles(message, context),
|
|
425
427
|
isAnalysisOnlyTask: (message, context) => this.isAnalysisOnlyTask(message, context),
|
|
426
428
|
sanitizeError: sanitizeUserFacingErrorText,
|
|
427
|
-
screenshotAdapter: dependencies.screenshotAdapter || new
|
|
429
|
+
screenshotAdapter: dependencies.screenshotAdapter || new SystemBrowserScreenshotAdapter(),
|
|
428
430
|
});
|
|
429
431
|
const vigFlowTransport = axios.create({ timeout: 30_000 });
|
|
430
432
|
installAxiosNetworkPolicy(vigFlowTransport, 'vigflow');
|
|
@@ -802,6 +804,9 @@ export class APIClient {
|
|
|
802
804
|
getVigFlowExecutionStatus(executionId) {
|
|
803
805
|
return this.vigFlowClient.executionStatus(executionId);
|
|
804
806
|
}
|
|
807
|
+
deleteVigFlowWorkflow(selector) {
|
|
808
|
+
return this.vigFlowClient.deleteWorkflow(selector);
|
|
809
|
+
}
|
|
805
810
|
async getV3AgentHeaders() {
|
|
806
811
|
return this.v3AgentClient.headers();
|
|
807
812
|
}
|
|
@@ -873,20 +878,12 @@ export class APIClient {
|
|
|
873
878
|
const resolvedContext = this.ensureExecutionContext(context);
|
|
874
879
|
const localWorkspacePath = this.resolveAgentTargetPath(resolvedContext);
|
|
875
880
|
const serverWorkspacePath = this.resolveServerBindableWorkspacePath(resolvedContext);
|
|
876
|
-
const promptFocus = String(resolvedContext.rawPrompt || resolvedContext.contextualPrompt || resolvedContext.prompt || '');
|
|
877
|
-
const localWorkspaceSummary = this.buildLocalWorkspaceSummary(localWorkspacePath, promptFocus);
|
|
878
|
-
if (resolvedContext.workspaceFilesOutOfBand === true && localWorkspaceSummary?.workspaceFiles) {
|
|
879
|
-
localWorkspaceSummary.workspaceHydration = {
|
|
880
|
-
delivery: 'out-of-band',
|
|
881
|
-
fileCount: Object.keys(localWorkspaceSummary.workspaceFiles).length,
|
|
882
|
-
complete: true,
|
|
883
|
-
};
|
|
884
|
-
delete localWorkspaceSummary.workspaceFiles;
|
|
885
|
-
}
|
|
886
881
|
const requestedModel = String(resolvedContext.model || resolvedContext.requestedModel || 'agent');
|
|
887
882
|
const resolvedModel = this.resolvePermittedModelId(requestedModel);
|
|
888
883
|
const localWorkspaceName = this.getDisplayWorkspaceName(localWorkspacePath);
|
|
889
|
-
const localWorkspaceRef =
|
|
884
|
+
const localWorkspaceRef = localWorkspacePath
|
|
885
|
+
? buildLocalWorkspaceReference(localWorkspacePath, String(this.config.get('userId') || this.config.get('email') || ''))
|
|
886
|
+
: null;
|
|
890
887
|
const executionSurface = String(resolvedContext.executionSurface || resolvedContext.clientSurface || 'cli');
|
|
891
888
|
const localMachineCapable = resolvedContext.localMachineCapable !== false;
|
|
892
889
|
const clientToolExecution = resolvedContext.clientToolExecution === false
|
|
@@ -895,15 +892,34 @@ export class APIClient {
|
|
|
895
892
|
|| (!serverWorkspacePath
|
|
896
893
|
&& localMachineCapable
|
|
897
894
|
&& ['cli', 'fork', 'local-ide', 'desktop', 'local'].includes(executionSurface)));
|
|
895
|
+
const promptFocus = String(resolvedContext.rawPrompt || resolvedContext.contextualPrompt || resolvedContext.prompt || '');
|
|
896
|
+
const localWorkspaceSummary = this.buildLocalWorkspaceSummary(localWorkspacePath, promptFocus, !clientToolExecution);
|
|
897
|
+
if (clientToolExecution && localWorkspaceSummary) {
|
|
898
|
+
localWorkspaceSummary.workspaceHydration = {
|
|
899
|
+
delivery: 'client-tool-bridge',
|
|
900
|
+
fileCount: Number(localWorkspaceSummary.fileCount || 0),
|
|
901
|
+
complete: false,
|
|
902
|
+
authoritativeLocation: 'client',
|
|
903
|
+
};
|
|
904
|
+
delete localWorkspaceSummary.workspaceFiles;
|
|
905
|
+
}
|
|
906
|
+
else if (resolvedContext.workspaceFilesOutOfBand === true && localWorkspaceSummary?.workspaceFiles) {
|
|
907
|
+
localWorkspaceSummary.workspaceHydration = {
|
|
908
|
+
delivery: 'out-of-band',
|
|
909
|
+
fileCount: Object.keys(localWorkspaceSummary.workspaceFiles).length,
|
|
910
|
+
complete: true,
|
|
911
|
+
authoritativeLocation: 'server',
|
|
912
|
+
};
|
|
913
|
+
delete localWorkspaceSummary.workspaceFiles;
|
|
914
|
+
}
|
|
898
915
|
const publicRuntimeEnvironment = this.buildPublicRuntimeEnvironment(resolvedContext.agentRuntime, {
|
|
899
916
|
localWorkspacePath,
|
|
900
917
|
serverWorkspacePath,
|
|
901
918
|
});
|
|
902
|
-
//
|
|
903
|
-
//
|
|
904
|
-
// files are provided inline in localWorkspaceSummary.workspaceFiles.
|
|
919
|
+
// A local CLI/Workbench workspace remains authoritative on that machine.
|
|
920
|
+
// Only server-owned/background runs may hydrate a bounded server copy.
|
|
905
921
|
const effectiveWorkspacePath = serverWorkspacePath || null;
|
|
906
|
-
const needsHydration = !serverWorkspacePath && !!localWorkspacePath;
|
|
922
|
+
const needsHydration = !clientToolExecution && !serverWorkspacePath && !!localWorkspacePath;
|
|
907
923
|
const agentTaskType = resolvedContext.agentTaskType || 'general';
|
|
908
924
|
const rawPrompt = resolvedContext.rawPrompt || resolvedContext.prompt || '';
|
|
909
925
|
const executionHints = (resolvedContext.executionHints && typeof resolvedContext.executionHints === 'object')
|
|
@@ -957,15 +973,13 @@ export class APIClient {
|
|
|
957
973
|
projectPath: effectiveWorkspacePath,
|
|
958
974
|
targetPath: effectiveWorkspacePath,
|
|
959
975
|
// Never send a client-local absolute path to the V3 model boundary.
|
|
960
|
-
// The server only
|
|
961
|
-
//
|
|
976
|
+
// The server only receives a stable label. Local bridge runs retrieve
|
|
977
|
+
// file contents on demand through authenticated client tool requests.
|
|
962
978
|
localWorkspacePath: serverWorkspacePath ? serverWorkspacePath : localWorkspaceRef,
|
|
963
979
|
localWorkspaceRef,
|
|
964
980
|
localWorkspaceName,
|
|
965
981
|
localWorkspaceSummary,
|
|
966
|
-
//
|
|
967
|
-
// accessible — it must hydrate a temp directory from the provided
|
|
968
|
-
// workspaceFiles before the agent starts using tools.
|
|
982
|
+
// This is true only for explicit server-owned/background execution.
|
|
969
983
|
workspaceHydrationRequired: needsHydration,
|
|
970
984
|
contextId: resolvedContext.contextId,
|
|
971
985
|
traceId: resolvedContext.traceId,
|
|
@@ -1892,7 +1906,7 @@ menu {
|
|
|
1892
1906
|
cwd: serverBindableWorkspace ? paths.serverWorkspacePath || null : (localName ? `vigthoria://local-workspace/${localName}` : null),
|
|
1893
1907
|
};
|
|
1894
1908
|
}
|
|
1895
|
-
buildLocalWorkspaceSummary(rootPath, requestFocus = '') {
|
|
1909
|
+
buildLocalWorkspaceSummary(rootPath, requestFocus = '', includeWorkspaceFiles = true) {
|
|
1896
1910
|
if (!rootPath || !fs.existsSync(rootPath)) {
|
|
1897
1911
|
return null;
|
|
1898
1912
|
}
|
|
@@ -1978,9 +1992,12 @@ menu {
|
|
|
1978
1992
|
if (fs.existsSync(readmePath)) {
|
|
1979
1993
|
summary.readmeExcerpt = fs.readFileSync(readmePath, 'utf8').slice(0, 2500);
|
|
1980
1994
|
}
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1995
|
+
if (includeWorkspaceFiles) {
|
|
1996
|
+
// Explicit server-owned/background execution may hydrate a bounded
|
|
1997
|
+
// copy. Interactive CLI/Workbench runs retrieve files on demand via
|
|
1998
|
+
// the local tool bridge and never upload a workspace mirror.
|
|
1999
|
+
summary.workspaceFiles = this.collectWorkspaceFileContents(rootPath, orderedPaths);
|
|
2000
|
+
}
|
|
1984
2001
|
return summary;
|
|
1985
2002
|
}
|
|
1986
2003
|
catch (error) {
|
|
@@ -1996,7 +2013,15 @@ menu {
|
|
|
1996
2013
|
const resolvedContext = this.ensureExecutionContext(context);
|
|
1997
2014
|
const localWorkspacePath = this.resolveAgentTargetPath(resolvedContext);
|
|
1998
2015
|
const serverWorkspacePath = this.resolveServerBindableWorkspacePath(resolvedContext);
|
|
1999
|
-
|
|
2016
|
+
const executionSurface = String(resolvedContext.executionSurface || resolvedContext.clientSurface || 'cli');
|
|
2017
|
+
const localMachineCapable = resolvedContext.localMachineCapable !== false;
|
|
2018
|
+
const clientToolExecution = resolvedContext.clientToolExecution === false
|
|
2019
|
+
? false
|
|
2020
|
+
: (resolvedContext.clientToolExecution === true
|
|
2021
|
+
|| (!serverWorkspacePath
|
|
2022
|
+
&& localMachineCapable
|
|
2023
|
+
&& ['cli', 'fork', 'local-ide', 'desktop', 'local'].includes(executionSurface)));
|
|
2024
|
+
if (clientToolExecution || serverWorkspacePath || !localWorkspacePath)
|
|
2000
2025
|
return undefined;
|
|
2001
2026
|
const promptFocus = String(resolvedContext.rawPrompt || resolvedContext.contextualPrompt || resolvedContext.prompt || '');
|
|
2002
2027
|
const summary = this.buildLocalWorkspaceSummary(localWorkspacePath, promptFocus);
|
|
@@ -2031,6 +2056,29 @@ menu {
|
|
|
2031
2056
|
resolveV3ClientToolPath(rootPath, rawPath, sourceRoots = []) {
|
|
2032
2057
|
return this.v3WorkspaceService.resolveClientToolPath(rootPath, rawPath, sourceRoots);
|
|
2033
2058
|
}
|
|
2059
|
+
describeMissingV3ClientToolPath(rootPath, relativePath) {
|
|
2060
|
+
const normalized = String(relativePath || '').replace(/\\/g, '/');
|
|
2061
|
+
const requestedDirectory = path.posix.dirname(normalized);
|
|
2062
|
+
const requestedExtension = path.posix.extname(normalized).toLowerCase();
|
|
2063
|
+
let candidates = [];
|
|
2064
|
+
try {
|
|
2065
|
+
const paths = this.getAgentWorkspaceSnapshot(rootPath).paths;
|
|
2066
|
+
const sameDirectory = paths.filter((entry) => path.posix.dirname(entry) === requestedDirectory);
|
|
2067
|
+
const sameExtension = paths.filter((entry) => (requestedExtension && path.posix.extname(entry).toLowerCase() === requestedExtension));
|
|
2068
|
+
candidates = Array.from(new Set([
|
|
2069
|
+
...sameDirectory,
|
|
2070
|
+
...sameExtension,
|
|
2071
|
+
...paths,
|
|
2072
|
+
])).slice(0, 16);
|
|
2073
|
+
}
|
|
2074
|
+
catch {
|
|
2075
|
+
// The primary not-found error remains actionable even if inventory fails.
|
|
2076
|
+
}
|
|
2077
|
+
const inventory = candidates.length > 0
|
|
2078
|
+
? ` Existing workspace files include: ${candidates.join(', ')}.`
|
|
2079
|
+
: '';
|
|
2080
|
+
return `File not found in the local workspace: ${normalized}.${inventory} Use list_directory or glob and retry with an existing workspace-relative path.`;
|
|
2081
|
+
}
|
|
2034
2082
|
recordV3ClientToolMutation(event, context, result) {
|
|
2035
2083
|
if (!result.success) {
|
|
2036
2084
|
return;
|
|
@@ -2065,6 +2113,23 @@ menu {
|
|
|
2065
2113
|
}
|
|
2066
2114
|
}
|
|
2067
2115
|
}
|
|
2116
|
+
buildV3ClientToolMutation(relativePath, content) {
|
|
2117
|
+
if (isSensitivePath(relativePath)) {
|
|
2118
|
+
return { error: 'Sensitive paths cannot be synchronized to the Agent service.' };
|
|
2119
|
+
}
|
|
2120
|
+
const byteLength = Buffer.byteLength(content, 'utf8');
|
|
2121
|
+
if (byteLength > MAX_V3_CLIENT_MUTATION_BYTES) {
|
|
2122
|
+
return { error: `Client mutation exceeds the ${MAX_V3_CLIENT_MUTATION_BYTES}-byte synchronization limit.` };
|
|
2123
|
+
}
|
|
2124
|
+
if (containsHighConfidenceSecret(content)) {
|
|
2125
|
+
return { error: 'Client mutation contains credential-like material and was not synchronized.' };
|
|
2126
|
+
}
|
|
2127
|
+
return {
|
|
2128
|
+
path: relativePath,
|
|
2129
|
+
content,
|
|
2130
|
+
sha256: createHash('sha256').update(content, 'utf8').digest('hex'),
|
|
2131
|
+
};
|
|
2132
|
+
}
|
|
2068
2133
|
async executeV3ClientToolRequest(event, context = {}) {
|
|
2069
2134
|
const rootPath = this.resolveAgentTargetPath(context);
|
|
2070
2135
|
if (!rootPath) {
|
|
@@ -2084,13 +2149,121 @@ menu {
|
|
|
2084
2149
|
if (!target) {
|
|
2085
2150
|
return { success: false, output: '', error: 'Tool path is outside the workspace.' };
|
|
2086
2151
|
}
|
|
2152
|
+
const existingPathTools = new Set([
|
|
2153
|
+
'read_file', 'edit_file', 'list_directory', 'glob', 'search_files', 'grep',
|
|
2154
|
+
'syntax_check', 'preview_check', 'runtime_check',
|
|
2155
|
+
]);
|
|
2156
|
+
if (existingPathTools.has(name) && !fs.existsSync(target.absolutePath)) {
|
|
2157
|
+
return {
|
|
2158
|
+
success: false,
|
|
2159
|
+
output: '',
|
|
2160
|
+
error: this.describeMissingV3ClientToolPath(rootPath, target.relativePath),
|
|
2161
|
+
};
|
|
2162
|
+
}
|
|
2087
2163
|
try {
|
|
2164
|
+
if (name === 'preview_check') {
|
|
2165
|
+
const entryPath = fs.statSync(target.absolutePath).isDirectory()
|
|
2166
|
+
? path.join(target.absolutePath, 'index.html')
|
|
2167
|
+
: target.absolutePath;
|
|
2168
|
+
if (!fs.existsSync(entryPath) || !fs.statSync(entryPath).isFile()) {
|
|
2169
|
+
return { success: false, output: '', error: 'Preview entry file not found.' };
|
|
2170
|
+
}
|
|
2171
|
+
const htmlBytes = fs.statSync(entryPath).size;
|
|
2172
|
+
if (htmlBytes > 2 * 1024 * 1024) {
|
|
2173
|
+
return { success: false, output: '', error: 'Preview entry exceeds the 2 MiB local validation limit.' };
|
|
2174
|
+
}
|
|
2175
|
+
const html = fs.readFileSync(entryPath, 'utf8');
|
|
2176
|
+
const expectedText = String(args.expect_text || '').trim();
|
|
2177
|
+
if (expectedText && !html.includes(expectedText)) {
|
|
2178
|
+
return { success: false, output: '', error: `Expected text was not found in ${path.basename(entryPath)}.` };
|
|
2179
|
+
}
|
|
2180
|
+
const missingAssets = [];
|
|
2181
|
+
const checkedAssets = [];
|
|
2182
|
+
const assetPattern = /<(?:script|link|img|source|video|audio)\b[^>]+(?:src|href)=["']([^"']+)["']/gi;
|
|
2183
|
+
let match;
|
|
2184
|
+
while ((match = assetPattern.exec(html)) !== null && checkedAssets.length < 200) {
|
|
2185
|
+
const rawAsset = String(match[1] || '').trim();
|
|
2186
|
+
if (!rawAsset || /^(?:https?:|data:|blob:|mailto:|tel:|javascript:|#)/i.test(rawAsset))
|
|
2187
|
+
continue;
|
|
2188
|
+
let decoded;
|
|
2189
|
+
try {
|
|
2190
|
+
decoded = decodeURIComponent(rawAsset.split(/[?#]/, 1)[0]);
|
|
2191
|
+
}
|
|
2192
|
+
catch {
|
|
2193
|
+
return { success: false, output: '', error: 'Preview contains a malformed local asset URL.' };
|
|
2194
|
+
}
|
|
2195
|
+
const entryRelative = path.relative(rootPath, entryPath).replace(/\\/g, '/');
|
|
2196
|
+
const entryDirectory = path.posix.dirname(entryRelative);
|
|
2197
|
+
const assetRelative = decoded.startsWith('/')
|
|
2198
|
+
? decoded.replace(/^\/+/, '')
|
|
2199
|
+
: path.posix.normalize(path.posix.join(entryDirectory, decoded));
|
|
2200
|
+
let assetAbsolute;
|
|
2201
|
+
try {
|
|
2202
|
+
assetAbsolute = resolveWorkspacePath(rootPath, assetRelative);
|
|
2203
|
+
}
|
|
2204
|
+
catch {
|
|
2205
|
+
return { success: false, output: '', error: 'Preview asset path escapes the local workspace.' };
|
|
2206
|
+
}
|
|
2207
|
+
checkedAssets.push(assetRelative);
|
|
2208
|
+
if (!fs.existsSync(assetAbsolute) || !fs.statSync(assetAbsolute).isFile())
|
|
2209
|
+
missingAssets.push(assetRelative);
|
|
2210
|
+
}
|
|
2211
|
+
if (missingAssets.length > 0) {
|
|
2212
|
+
return {
|
|
2213
|
+
success: false,
|
|
2214
|
+
output: '',
|
|
2215
|
+
error: `Missing local preview assets: ${missingAssets.slice(0, 12).join(', ')}`,
|
|
2216
|
+
};
|
|
2217
|
+
}
|
|
2218
|
+
return {
|
|
2219
|
+
success: true,
|
|
2220
|
+
output: JSON.stringify({
|
|
2221
|
+
entry: path.relative(rootPath, entryPath).replace(/\\/g, '/'),
|
|
2222
|
+
checkedAssets: checkedAssets.length,
|
|
2223
|
+
expectedTextMatched: Boolean(expectedText),
|
|
2224
|
+
}),
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
if (name === 'runtime_check') {
|
|
2228
|
+
const previewResult = await this.executeV3ClientToolRequest({ ...event, name: 'preview_check', arguments: args }, context);
|
|
2229
|
+
if (!previewResult.success)
|
|
2230
|
+
return previewResult;
|
|
2231
|
+
const relativeTarget = target.relativePath && target.relativePath !== '.' ? target.relativePath : 'index.html';
|
|
2232
|
+
const gate = await this.frontendPreviewService.runTemplateServicePreviewGate(`Verify the interactive frontend runtime at ${relativeTarget}.`, {
|
|
2233
|
+
...context,
|
|
2234
|
+
targetPath: rootPath,
|
|
2235
|
+
workspacePath: rootPath,
|
|
2236
|
+
projectPath: rootPath,
|
|
2237
|
+
rawPrompt: `Verify the interactive frontend runtime at ${relativeTarget}.`,
|
|
2238
|
+
forceFrontendPreview: true,
|
|
2239
|
+
localScreenshotProof: true,
|
|
2240
|
+
requireScreenshot: true,
|
|
2241
|
+
});
|
|
2242
|
+
if (!gate.passed || gate.artifacts?.screenshotCaptured !== true) {
|
|
2243
|
+
return {
|
|
2244
|
+
success: false,
|
|
2245
|
+
output: '',
|
|
2246
|
+
error: gate.error || gate.artifacts?.screenshotError || 'Local browser runtime proof failed closed.',
|
|
2247
|
+
};
|
|
2248
|
+
}
|
|
2249
|
+
return {
|
|
2250
|
+
success: true,
|
|
2251
|
+
output: JSON.stringify({
|
|
2252
|
+
entry: gate.entryPath || relativeTarget,
|
|
2253
|
+
screenshotCaptured: true,
|
|
2254
|
+
proof: 'local-system-browser',
|
|
2255
|
+
}),
|
|
2256
|
+
};
|
|
2257
|
+
}
|
|
2088
2258
|
if (name === 'write_file') {
|
|
2089
2259
|
if (typeof args.content !== 'string')
|
|
2090
2260
|
return { success: false, output: '', error: 'write_file requires string content.' };
|
|
2261
|
+
const mutation = this.buildV3ClientToolMutation(target.relativePath, args.content);
|
|
2262
|
+
if ('error' in mutation)
|
|
2263
|
+
return { success: false, output: '', error: mutation.error };
|
|
2091
2264
|
fs.mkdirSync(path.dirname(target.absolutePath), { recursive: true });
|
|
2092
2265
|
fs.writeFileSync(target.absolutePath, args.content, 'utf8');
|
|
2093
|
-
const result = { success: true, output: `Updated ${target.relativePath}
|
|
2266
|
+
const result = { success: true, output: `Updated ${target.relativePath}`, mutation };
|
|
2094
2267
|
this.recordV3ClientToolMutation(event, context, result);
|
|
2095
2268
|
return result;
|
|
2096
2269
|
}
|
|
@@ -2101,10 +2274,31 @@ menu {
|
|
|
2101
2274
|
return { success: false, output: '', error: 'edit_file requires old_string.' };
|
|
2102
2275
|
const existing = fs.readFileSync(target.absolutePath, 'utf8');
|
|
2103
2276
|
const count = existing.split(oldString).length - 1;
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2277
|
+
const replaceAll = args.replace_all === true;
|
|
2278
|
+
if ((!replaceAll && count !== 1) || (replaceAll && count < 1)) {
|
|
2279
|
+
return {
|
|
2280
|
+
success: false,
|
|
2281
|
+
output: '',
|
|
2282
|
+
error: replaceAll
|
|
2283
|
+
? `old_string found ${count} times; expected at least once.`
|
|
2284
|
+
: `old_string found ${count} times; expected exactly once.`,
|
|
2285
|
+
};
|
|
2286
|
+
}
|
|
2287
|
+
if (replaceAll && (oldString.includes('\n') || newString.includes('\n') || oldString.length > 120 || count > 1000)) {
|
|
2288
|
+
return {
|
|
2289
|
+
success: false,
|
|
2290
|
+
output: '',
|
|
2291
|
+
error: 'replace_all is restricted to short, single-line token migrations with at most 1000 matches.',
|
|
2292
|
+
};
|
|
2293
|
+
}
|
|
2294
|
+
const nextContent = replaceAll
|
|
2295
|
+
? existing.split(oldString).join(newString)
|
|
2296
|
+
: existing.replace(oldString, newString);
|
|
2297
|
+
const mutation = this.buildV3ClientToolMutation(target.relativePath, nextContent);
|
|
2298
|
+
if ('error' in mutation)
|
|
2299
|
+
return { success: false, output: '', error: mutation.error };
|
|
2300
|
+
fs.writeFileSync(target.absolutePath, nextContent, 'utf8');
|
|
2301
|
+
const result = { success: true, output: `Edited ${target.relativePath}`, mutation };
|
|
2108
2302
|
this.recordV3ClientToolMutation(event, context, result);
|
|
2109
2303
|
return result;
|
|
2110
2304
|
}
|
|
@@ -2354,6 +2548,19 @@ menu {
|
|
|
2354
2548
|
normalizeAgentWorkspaceRelativePath(rawPath, rootPath) {
|
|
2355
2549
|
return this.v3WorkspaceService.normalizeRelativePath(rawPath, rootPath);
|
|
2356
2550
|
}
|
|
2551
|
+
isExplicitSingleFileFrontendRequest(prompt, expectedFiles, rootPath) {
|
|
2552
|
+
const normalizedFiles = Array.from(new Set(expectedFiles
|
|
2553
|
+
.map((filePath) => this.normalizeAgentWorkspaceRelativePath(filePath, rootPath))
|
|
2554
|
+
.filter((filePath) => /\.html?$/i.test(filePath))));
|
|
2555
|
+
if (normalizedFiles.length !== 1) {
|
|
2556
|
+
return false;
|
|
2557
|
+
}
|
|
2558
|
+
const request = String(prompt || '').toLowerCase();
|
|
2559
|
+
const explicitOneFileLanguage = /\b(?:single|one)[ -]?file\b/i.test(request)
|
|
2560
|
+
|| /\b(?:in\s+)?(?:exactly|only)\s+[`'\"]?[a-z0-9_./-]+\.html?\b/i.test(request)
|
|
2561
|
+
|| /\b[a-z0-9_./-]+\.html?\b[^.\n]{0,100}\b(?:only|alone)\b/i.test(request);
|
|
2562
|
+
return explicitOneFileLanguage;
|
|
2563
|
+
}
|
|
2357
2564
|
async ensureAgentFrontendPolish(message = '', context = {}) {
|
|
2358
2565
|
const rootPath = this.resolveAgentTargetPath(context);
|
|
2359
2566
|
if (!rootPath || !fs.existsSync(rootPath)) {
|
|
@@ -2361,16 +2568,66 @@ menu {
|
|
|
2361
2568
|
}
|
|
2362
2569
|
const prompt = String(message || '');
|
|
2363
2570
|
const expectedFiles = this.extractExpectedWorkspaceFiles(message, context);
|
|
2364
|
-
const looksLikeFrontendTask = /(premium|polished|landing|site|page|dashboard|saas|frontend|ui|pricing|showcase)/i.test(prompt)
|
|
2571
|
+
const looksLikeFrontendTask = /(premium|polished|landing|site|page|dashboard|saas|frontend|ui|pricing|showcase|responsive|interactive|game|canvas)/i.test(prompt)
|
|
2365
2572
|
|| expectedFiles.some((filePath) => /\.(html|css|js)$/i.test(filePath));
|
|
2366
2573
|
if (!looksLikeFrontendTask) {
|
|
2367
2574
|
return;
|
|
2368
2575
|
}
|
|
2369
|
-
const
|
|
2370
|
-
|
|
2576
|
+
const expectedHtmlPaths = expectedFiles
|
|
2577
|
+
.map((filePath) => this.normalizeAgentWorkspaceRelativePath(filePath, rootPath))
|
|
2578
|
+
.filter((filePath) => /\.html?$/i.test(filePath));
|
|
2579
|
+
const selectedHtmlRelativePath = [
|
|
2580
|
+
...expectedHtmlPaths,
|
|
2581
|
+
'index.html',
|
|
2582
|
+
].find((filePath) => {
|
|
2583
|
+
try {
|
|
2584
|
+
return Boolean(filePath) && fs.existsSync(resolveWorkspacePath(rootPath, filePath));
|
|
2585
|
+
}
|
|
2586
|
+
catch {
|
|
2587
|
+
return false;
|
|
2588
|
+
}
|
|
2589
|
+
});
|
|
2590
|
+
if (!selectedHtmlRelativePath) {
|
|
2591
|
+
return;
|
|
2592
|
+
}
|
|
2593
|
+
const htmlPath = resolveWorkspacePath(rootPath, selectedHtmlRelativePath);
|
|
2594
|
+
let html = fs.readFileSync(htmlPath, 'utf8');
|
|
2595
|
+
// The Template Service correctly fails motion-bearing frontend artifacts
|
|
2596
|
+
// without a reduced-motion path. V3 may produce a valid single-file app
|
|
2597
|
+
// whose only motion is inline CSS, so enforce this small accessibility
|
|
2598
|
+
// contract on the actual requested HTML entry before preview validation.
|
|
2599
|
+
// This remains inside the surrounding mutation journal and is rolled back
|
|
2600
|
+
// with every other client-side mutation when preview later fails.
|
|
2601
|
+
const usesMotion = /@keyframes|\banimation(?:-name)?\s*:|\btransition(?:-property)?\s*:/i.test(html);
|
|
2602
|
+
if (usesMotion && !/prefers-reduced-motion/i.test(html)) {
|
|
2603
|
+
const reducedMotionCss = `
|
|
2604
|
+
|
|
2605
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2606
|
+
html { scroll-behavior: auto !important; }
|
|
2607
|
+
*, *::before, *::after {
|
|
2608
|
+
animation-duration: 0.01ms !important;
|
|
2609
|
+
animation-iteration-count: 1 !important;
|
|
2610
|
+
transition-duration: 0.01ms !important;
|
|
2611
|
+
}
|
|
2612
|
+
}`;
|
|
2613
|
+
if (/<\/style>/i.test(html)) {
|
|
2614
|
+
html = html.replace(/<\/style>/i, `${reducedMotionCss}\n</style>`);
|
|
2615
|
+
}
|
|
2616
|
+
else if (/<\/head>/i.test(html)) {
|
|
2617
|
+
html = html.replace(/<\/head>/i, `<style>${reducedMotionCss}\n</style>\n</head>`);
|
|
2618
|
+
}
|
|
2619
|
+
fs.writeFileSync(htmlPath, html, 'utf8');
|
|
2620
|
+
}
|
|
2621
|
+
if (isConstrainedStaticFrontendRequest(prompt)
|
|
2622
|
+
|| this.isExplicitSingleFileFrontendRequest(prompt, expectedFiles, rootPath)) {
|
|
2623
|
+
return;
|
|
2624
|
+
}
|
|
2625
|
+
// The broader legacy polish recovery owns only the conventional root
|
|
2626
|
+
// index entry. Arbitrary explicitly named applications retain their
|
|
2627
|
+
// architecture; only the accessibility contract above is normalized.
|
|
2628
|
+
if (selectedHtmlRelativePath !== 'index.html') {
|
|
2371
2629
|
return;
|
|
2372
2630
|
}
|
|
2373
|
-
const html = fs.readFileSync(htmlPath, 'utf8');
|
|
2374
2631
|
const ensuredAssets = this.ensureReferencedFrontendAssets(rootPath, html, prompt, 'Vigthoria CLI');
|
|
2375
2632
|
const cssPath = ensuredAssets.cssPath;
|
|
2376
2633
|
const jsPath = ensuredAssets.jsPath;
|
|
@@ -2380,6 +2637,24 @@ menu {
|
|
|
2380
2637
|
}
|
|
2381
2638
|
let css = fs.readFileSync(cssPath, 'utf8');
|
|
2382
2639
|
let js = fs.existsSync(jsPath) ? fs.readFileSync(jsPath, 'utf8') : '';
|
|
2640
|
+
// Production preview evaluates the linked stylesheet as part of the final
|
|
2641
|
+
// artifact. The earlier inline-only guard cannot see animation and
|
|
2642
|
+
// transition rules that live in that stylesheet, which allowed the Agent's
|
|
2643
|
+
// client-side runtime check to pass and the final Template Service gate to
|
|
2644
|
+
// reject the same workspace. Normalize the two deterministic production
|
|
2645
|
+
// contracts before any of the legacy early-return paths below.
|
|
2646
|
+
const linkedFrontend = `${html}\n${css}`;
|
|
2647
|
+
if (!/\*\s*,\s*\*::before\s*,\s*\*::after\s*\{[^}]*box-sizing\s*:\s*border-box/i.test(linkedFrontend)) {
|
|
2648
|
+
css = `*, *::before, *::after {\n box-sizing: border-box;\n}\n\n${css}`;
|
|
2649
|
+
}
|
|
2650
|
+
const linkedFrontendWithReset = `${html}\n${css}`;
|
|
2651
|
+
const linkedStylesUseMotion = /@keyframes|\banimation(?:-name)?\s*:|\btransition(?:-property)?\s*:/i.test(linkedFrontendWithReset);
|
|
2652
|
+
if (linkedStylesUseMotion && !/prefers-reduced-motion/i.test(linkedFrontendWithReset)) {
|
|
2653
|
+
css = `${css.trimEnd()}\n\n/* Vigthoria CLI Reduced Motion Baseline */\n@media (prefers-reduced-motion: reduce) {\n html { scroll-behavior: auto !important; }\n *, *::before, *::after {\n animation-duration: 0.01ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: 0.01ms !important;\n }\n}\n`;
|
|
2654
|
+
}
|
|
2655
|
+
if (css !== fs.readFileSync(cssPath, 'utf8')) {
|
|
2656
|
+
fs.writeFileSync(cssPath, `${css.trimEnd()}\n`, 'utf8');
|
|
2657
|
+
}
|
|
2383
2658
|
const keyframesBlocks = Array.from(js.matchAll(/@keyframes[\s\S]*?\n\}/g)).map((match) => match[0]);
|
|
2384
2659
|
if (keyframesBlocks.length > 0) {
|
|
2385
2660
|
const migrated = keyframesBlocks.filter((block) => !css.includes(block));
|
|
@@ -2457,7 +2732,15 @@ menu {
|
|
|
2457
2732
|
const cssMarker = '/* Vigthoria CLI Motion Enhancement */';
|
|
2458
2733
|
const jsMarker = '/* Vigthoria CLI Motion Enhancement */';
|
|
2459
2734
|
if (!css.includes(cssMarker)) {
|
|
2460
|
-
|
|
2735
|
+
// This enhancement introduces motion after the earlier linked-asset
|
|
2736
|
+
// normalization pass. Add the accessibility counterpart in the same
|
|
2737
|
+
// atomic append so the final Template Service gate never observes
|
|
2738
|
+
// CLI-created animation without a reduced-motion contract.
|
|
2739
|
+
const currentCss = fs.readFileSync(cssPath, 'utf8');
|
|
2740
|
+
const reducedMotionBaseline = /prefers-reduced-motion/i.test(currentCss)
|
|
2741
|
+
? ''
|
|
2742
|
+
: `\n\n/* Vigthoria CLI Reduced Motion Baseline */\n@media (prefers-reduced-motion: reduce) {\n html { scroll-behavior: auto !important; }\n *, *::before, *::after {\n animation-duration: 0.01ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: 0.01ms !important;\n }\n}`;
|
|
2743
|
+
fs.appendFileSync(cssPath, `\n\n${cssMarker}\n.hero, section {\n opacity: 0;\n transform: translateY(24px);\n animation: vigCliFadeIn 0.8s ease forwards;\n}\n\nsection {\n animation-delay: 0.12s;\n}\n\nbutton, .cta, a {\n transition: transform 0.25s ease, opacity 0.25s ease;\n}\n\nbutton:hover, .cta:hover, a:hover {\n transform: translateY(-2px);\n}\n\n.motion-reveal {\n opacity: 0;\n transform: translateY(24px);\n transition: opacity 0.7s ease, transform 0.7s ease;\n}\n\n.motion-reveal.is-visible {\n opacity: 1;\n transform: translateY(0);\n}\n\n@keyframes vigCliFadeIn {\n from {\n opacity: 0;\n transform: translateY(24px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}${reducedMotionBaseline}\n`, 'utf8');
|
|
2461
2744
|
}
|
|
2462
2745
|
if (!js.includes(jsMarker)) {
|
|
2463
2746
|
fs.appendFileSync(jsPath, `\n\n${jsMarker}\ndocument.addEventListener('DOMContentLoaded', () => {\n const revealTargets = document.querySelectorAll('section, .hero, .project-grid > *, .journal-preview > *');\n if (typeof IntersectionObserver !== 'function') {\n revealTargets.forEach((element) => element.classList.add('is-visible'));\n return;\n }\n\n const observer = new IntersectionObserver((entries) => {\n entries.forEach((entry) => {\n if (entry.isIntersecting) {\n entry.target.classList.add('is-visible');\n observer.unobserve(entry.target);\n }\n });\n }, { threshold: 0.16 });\n\n revealTargets.forEach((element, index) => {\n element.classList.add('motion-reveal');\n element.style.transitionDelay = String(Math.min(index * 60, 320)) + 'ms';\n observer.observe(element);\n });\n});\n`, 'utf8');
|
|
@@ -3395,6 +3678,23 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3395
3678
|
}, mutationJournal);
|
|
3396
3679
|
}
|
|
3397
3680
|
catch (error) {
|
|
3681
|
+
if (error?.code === 'AGENT_PLAN_INVALID') {
|
|
3682
|
+
if (mutationJournal?.hasMutations()) {
|
|
3683
|
+
const mutation = mutationJournal.rollback();
|
|
3684
|
+
mutationJournal = null;
|
|
3685
|
+
error.operationId = mutation.operationId;
|
|
3686
|
+
error.mutation = mutation;
|
|
3687
|
+
error.partialMutation = mutation.partialMutation;
|
|
3688
|
+
}
|
|
3689
|
+
else {
|
|
3690
|
+
error.partialMutation = false;
|
|
3691
|
+
}
|
|
3692
|
+
this.activeV3StreamedFiles = null;
|
|
3693
|
+
// A malformed graph is deterministic and backend-independent.
|
|
3694
|
+
// Never retry it against another service or erase its typed code
|
|
3695
|
+
// while folding endpoint diagnostics into a string.
|
|
3696
|
+
throw error;
|
|
3697
|
+
}
|
|
3398
3698
|
if (error && error.name === 'AbortError' && error.partialData && this.hasAgentWorkspaceOutput(executionContext)) {
|
|
3399
3699
|
this.recoverAgentWorkspaceFiles(executionContext, error.partialData.files || {}, expectedFiles);
|
|
3400
3700
|
await this.waitForAgentWorkspaceSettle(executionContext, { expectedFiles });
|
|
@@ -3607,14 +3907,50 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3607
3907
|
? requestedOperatorTimeoutMs
|
|
3608
3908
|
: 0;
|
|
3609
3909
|
const workspacePath = executionContext.workspacePath || executionContext.projectPath || executionContext.targetPath || process.cwd();
|
|
3610
|
-
const
|
|
3910
|
+
const serverWorkspacePath = this.resolveServerBindableWorkspacePath(executionContext);
|
|
3911
|
+
const clientWorkspaceAuthoritative = Boolean(workspacePath
|
|
3912
|
+
&& !serverWorkspacePath
|
|
3913
|
+
&& executionContext.localMachineCapable !== false);
|
|
3914
|
+
let workspaceSummary = this.buildLocalWorkspaceSummary(workspacePath, message, !clientWorkspaceAuthoritative);
|
|
3611
3915
|
const operatorWorkspaceAlias = `vigthoria://workspace/${encodeURIComponent(path.basename(workspacePath))}`;
|
|
3612
3916
|
const requestedModel = String(executionContext.model || 'code');
|
|
3613
3917
|
const resolvedModel = this.resolveModelId(requestedModel);
|
|
3614
3918
|
try {
|
|
3615
|
-
|
|
3919
|
+
let groundedAnalysis = null;
|
|
3920
|
+
let operatorExecutionContext = executionContext;
|
|
3921
|
+
if (clientWorkspaceAuthoritative) {
|
|
3922
|
+
groundedAnalysis = await this.runV3AgentWorkflow([
|
|
3923
|
+
'Inspect the client workspace through the authenticated local tool bridge.',
|
|
3924
|
+
'Produce a grounded, read-only technical report for the BMAD Operator.',
|
|
3925
|
+
'Do not modify files. Identify relevant files, constraints, risks, and exact evidence for this request:',
|
|
3926
|
+
message,
|
|
3927
|
+
].join('\n'), {
|
|
3928
|
+
...executionContext,
|
|
3929
|
+
rawPrompt: message,
|
|
3930
|
+
contextualPrompt: message,
|
|
3931
|
+
agentTaskType: 'analysis',
|
|
3932
|
+
workflowType: 'analysis_only',
|
|
3933
|
+
clientToolExecution: true,
|
|
3934
|
+
localMachineCapable: true,
|
|
3935
|
+
});
|
|
3936
|
+
const groundingReport = String(groundedAnalysis.content || '').trim();
|
|
3937
|
+
if (!groundingReport || groundedAnalysis.partial === true) {
|
|
3938
|
+
throw new CLIError('Operator could not obtain a complete grounded report from the client workspace bridge.', 'model_backend');
|
|
3939
|
+
}
|
|
3940
|
+
workspaceSummary = workspaceSummary || { name: path.basename(workspacePath), files: [] };
|
|
3941
|
+
workspaceSummary.workspaceAuthority = 'client-tool-bridge';
|
|
3942
|
+
workspaceSummary.groundedBy = 'v3-client-tools';
|
|
3943
|
+
delete workspaceSummary.workspaceFiles;
|
|
3944
|
+
operatorExecutionContext = {
|
|
3945
|
+
...executionContext,
|
|
3946
|
+
workspaceAuthority: 'client-tool-bridge',
|
|
3947
|
+
clientGroundingReport: groundingReport.slice(0, 64 * 1024),
|
|
3948
|
+
workflowType: executionContext.workflowType === 'full' ? 'planning_only' : executionContext.workflowType,
|
|
3949
|
+
};
|
|
3950
|
+
}
|
|
3951
|
+
const operatorResult = await this.operatorClient.run({
|
|
3616
3952
|
message,
|
|
3617
|
-
executionContext,
|
|
3953
|
+
executionContext: operatorExecutionContext,
|
|
3618
3954
|
workspaceAlias: operatorWorkspaceAlias,
|
|
3619
3955
|
workspaceSummary,
|
|
3620
3956
|
requestedModel,
|
|
@@ -3622,6 +3958,54 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3622
3958
|
timeoutMs,
|
|
3623
3959
|
onStreamEvent: typeof context.onStreamEvent === 'function' ? context.onStreamEvent : undefined,
|
|
3624
3960
|
});
|
|
3961
|
+
if (!clientWorkspaceAuthoritative || executionContext.workflowType !== 'full') {
|
|
3962
|
+
return {
|
|
3963
|
+
...operatorResult,
|
|
3964
|
+
metadata: {
|
|
3965
|
+
...(operatorResult.metadata || {}),
|
|
3966
|
+
...(clientWorkspaceAuthoritative ? {
|
|
3967
|
+
workspaceAuthority: 'client-tool-bridge',
|
|
3968
|
+
groundingTaskId: groundedAnalysis?.taskId || null,
|
|
3969
|
+
} : {}),
|
|
3970
|
+
},
|
|
3971
|
+
};
|
|
3972
|
+
}
|
|
3973
|
+
const plan = String(operatorResult.content || '').trim();
|
|
3974
|
+
if (!plan) {
|
|
3975
|
+
throw new CLIError('BMAD Operator returned no governed implementation plan.', 'model_backend');
|
|
3976
|
+
}
|
|
3977
|
+
const implementation = await this.runV3AgentWorkflow([
|
|
3978
|
+
message,
|
|
3979
|
+
'',
|
|
3980
|
+
'Execute this governed BMAD plan against the authoritative client workspace using client tools:',
|
|
3981
|
+
plan.slice(0, 64 * 1024),
|
|
3982
|
+
].join('\n'), {
|
|
3983
|
+
...executionContext,
|
|
3984
|
+
rawPrompt: message,
|
|
3985
|
+
contextualPrompt: message,
|
|
3986
|
+
workflowType: 'full',
|
|
3987
|
+
clientToolExecution: true,
|
|
3988
|
+
localMachineCapable: true,
|
|
3989
|
+
});
|
|
3990
|
+
if (implementation.partial === true) {
|
|
3991
|
+
throw new CLIError('Local implementation ended partially and was not reported as Operator success.', 'model_backend');
|
|
3992
|
+
}
|
|
3993
|
+
return {
|
|
3994
|
+
content: implementation.content,
|
|
3995
|
+
workflowId: operatorResult.workflowId,
|
|
3996
|
+
contextId: implementation.contextId || operatorResult.contextId,
|
|
3997
|
+
backendUrl: operatorResult.backendUrl,
|
|
3998
|
+
savedWorkflow: operatorResult.savedWorkflow,
|
|
3999
|
+
changedFiles: implementation.changedFiles,
|
|
4000
|
+
metadata: {
|
|
4001
|
+
...(operatorResult.metadata || {}),
|
|
4002
|
+
workspaceAuthority: 'client-tool-bridge',
|
|
4003
|
+
groundingTaskId: groundedAnalysis?.taskId || null,
|
|
4004
|
+
implementationTaskId: implementation.taskId,
|
|
4005
|
+
implementationBackendUrl: implementation.backendUrl,
|
|
4006
|
+
bmadPlan: true,
|
|
4007
|
+
},
|
|
4008
|
+
};
|
|
3625
4009
|
}
|
|
3626
4010
|
catch (error) {
|
|
3627
4011
|
if (error instanceof OperatorClientError) {
|
|
@@ -3747,6 +4131,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3747
4131
|
success: result.success === true,
|
|
3748
4132
|
output: String(result.output || ''),
|
|
3749
4133
|
error: String(result.error || ''),
|
|
4134
|
+
mutation: result.mutation || null,
|
|
3750
4135
|
});
|
|
3751
4136
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
3752
4137
|
const response = await this.fetch(endpoint, {
|