vigthoria-cli 1.13.31 → 1.13.42
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/README.md +11 -56
- package/SECURITY_HARDENING.md +2 -4
- package/dist/commands/auth.js +16 -14
- package/dist/commands/cancel.d.ts +1 -0
- package/dist/commands/cancel.js +21 -4
- package/dist/commands/chat.d.ts +13 -0
- package/dist/commands/chat.js +170 -14
- package/dist/commands/fork.js +8 -3
- package/dist/commands/history.js +8 -3
- package/dist/commands/legion.js +1 -1
- package/dist/commands/replay.js +8 -3
- package/dist/commands/update-registration.js +63 -206
- package/dist/commands/v4-menu.d.ts +0 -1
- package/dist/commands/v4-menu.js +8 -6
- package/dist/commands/v4-registration.js +0 -2
- package/dist/commands/v4.js +9 -4
- package/dist/commands/wallet.d.ts +1 -1
- package/dist/commands/wallet.js +3 -3
- package/dist/index.js +2 -3
- package/dist/utils/agent-stream-state.d.ts +7 -0
- package/dist/utils/agent-stream-state.js +31 -0
- package/dist/utils/agentRunOutcome.js +5 -1
- package/dist/utils/api.d.ts +21 -3
- package/dist/utils/api.js +379 -550
- package/dist/utils/frontend-preview-service.d.ts +12 -2
- package/dist/utils/frontend-preview-service.js +214 -24
- package/dist/utils/javascript-syntax.d.ts +19 -0
- package/dist/utils/javascript-syntax.js +35 -0
- package/dist/utils/localTestMode.js +1 -1
- package/dist/utils/mutation-journal.d.ts +10 -0
- package/dist/utils/mutation-journal.js +104 -76
- package/dist/utils/network-policy.js +7 -10
- package/dist/utils/preview-screenshot-adapter.d.ts +111 -1
- package/dist/utils/preview-screenshot-adapter.js +990 -7
- package/dist/utils/release-install.d.ts +0 -1
- package/dist/utils/release-install.js +44 -7
- package/dist/utils/requestIntent.d.ts +1 -1
- package/dist/utils/requestIntent.js +17 -3
- package/dist/utils/tools.js +21 -15
- package/dist/utils/update-policy.d.ts +0 -4
- package/dist/utils/update-policy.js +5 -9
- package/dist/utils/v3-agent-client.js +25 -11
- package/install.ps1 +30 -50
- package/install.sh +12 -23
- package/package.json +4 -2
- package/release-policy.json +1 -5
- package/scripts/release/LOCAL_MACHINE_USER_VERIFICATION.md +6 -7
- package/scripts/release/install-release.mjs +5 -4
- package/scripts/release/publish-cli-release.mjs +21 -8
- package/scripts/release/test-balanced-model-live.sh +4 -1
- package/scripts/release/validate-live-service-gates.sh +27 -5
package/dist/utils/api.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import axios from 'axios';
|
|
6
6
|
import { createHash, randomUUID } from 'crypto';
|
|
7
|
+
import { execFileSync } from 'child_process';
|
|
7
8
|
import fs from 'fs';
|
|
8
9
|
import https from 'https';
|
|
9
10
|
import net from 'net';
|
|
@@ -22,7 +23,7 @@ import { buildClientManifest } from './clientManifest.js';
|
|
|
22
23
|
import { assertTrustedEndpoint, guardedFetch, installAxiosNetworkPolicy, installGlobalFetchPolicy } from './network-policy.js';
|
|
23
24
|
import { assertSafeRelativePath, resolveWorkspacePath } from './workspace-boundary.js';
|
|
24
25
|
import { parseSupportedProcess } from './process-policy.js';
|
|
25
|
-
import { SystemBrowserScreenshotAdapter } from './preview-screenshot-adapter.js';
|
|
26
|
+
import { SystemBrowserScreenshotAdapter, } from './preview-screenshot-adapter.js';
|
|
26
27
|
import { normalizeSubscriptionResponse } from './subscription-policy.js';
|
|
27
28
|
import { ModelGovernance } from './model-governance.js';
|
|
28
29
|
import { OperatorClient, OperatorClientError } from './operator-client.js';
|
|
@@ -37,9 +38,22 @@ import { VigFlowClient, } from './vigflow-client.js';
|
|
|
37
38
|
import { MutationJournal, finalizeMutationTransaction, } from './mutation-journal.js';
|
|
38
39
|
import { containsHighConfidenceSecret, isSensitivePath, redactSensitiveText, safeChildProcessEnv, scanOutboundContext, } from './secret-policy.js';
|
|
39
40
|
import { buildLocalWorkspaceReference } from './workspace-reference.js';
|
|
41
|
+
import { resolveJavaScriptSyntaxModes } from './javascript-syntax.js';
|
|
40
42
|
export const VIGTHORIA_HUB_CREDITS_URL = 'https://hub.vigthoria.io/credits';
|
|
41
43
|
export const VIGTHORIA_SERVER_TEMPORARILY_UNAVAILABLE_MESSAGE = 'Vigthoria Server is temporarily not available. Please try again later. If the issue persists, please contact support.';
|
|
44
|
+
export function resolveSelfHostedModelAuthToken(environment = process.env, userToken = '') {
|
|
45
|
+
const serviceToken = String(environment.VIGTHORIA_INFERENCE_SERVICE_TOKEN || '').trim();
|
|
46
|
+
return serviceToken || String(userToken || '').trim();
|
|
47
|
+
}
|
|
42
48
|
const MAX_V3_CLIENT_MUTATION_BYTES = 512 * 1024;
|
|
49
|
+
// These are the authoritative V3 request-schema ceilings. A local tool can
|
|
50
|
+
// produce a very long parser diagnostic (for example when a generated module
|
|
51
|
+
// contains one large unterminated line). Sending that diagnostic verbatim
|
|
52
|
+
// makes FastAPI reject the result with HTTP 422 while the server-side tool
|
|
53
|
+
// future continues waiting. Normalize before the first submission so every
|
|
54
|
+
// local outcome, including failure, is deliverable.
|
|
55
|
+
const MAX_V3_CLIENT_RESULT_OUTPUT_CHARS = 500_000;
|
|
56
|
+
const MAX_V3_CLIENT_RESULT_ERROR_CHARS = 10_000;
|
|
43
57
|
export class CLIError extends Error {
|
|
44
58
|
category;
|
|
45
59
|
statusCode;
|
|
@@ -152,7 +166,7 @@ export function sanitizeUserFacingPathText(input) {
|
|
|
152
166
|
[/\bvig-remote(?:-server)?-[A-Za-z0-9._-]+/gi, 'workspace'],
|
|
153
167
|
[/\bvig-fork-[A-Za-z0-9._-]+/gi, 'workspace'],
|
|
154
168
|
// ── V3 Code Agent installation tree and sibling services
|
|
155
|
-
[new RegExp(String.raw `/var/www/V3-Code-Agent(?:/${pathToken})?`, 'gi'), '[Vigthoria Agent]'],
|
|
169
|
+
[new RegExp(String.raw `/var/www/V3-Code-Agent(?:-(?:IDE|CLI|VIBE))?(?:/${pathToken})?`, 'gi'), '[Vigthoria Agent]'],
|
|
156
170
|
[new RegExp(String.raw `/var/www/(?:agent-vigthoria|vigthoria-model-router|vigthoria-devtools-bridge|vigthoria-hyper-loop|vigthoria-mcp-server|vigthoria-template-service|vigthoria-security-devops|vigthoria-coder|vigthoria-code|Vigthoria-Code-2|vigthoria-asset-storage|vigthoria-storage-service|vigthoria-hosted)(?:/${pathToken})?`, 'gi'), '[Vigthoria service]'],
|
|
157
171
|
// ── User workspace mount + long-term storage backing path
|
|
158
172
|
[new RegExp(String.raw `/var/www/(?:vigthoria-user-workspaces|\.vigthoria)(?:/${pathToken})?`, 'gi'), 'workspace'],
|
|
@@ -320,6 +334,13 @@ export class APIClient {
|
|
|
320
334
|
pendingV3ClientToolTasks = new Set();
|
|
321
335
|
/** Tracks files mutated via client_tool_request during an active V3 SSE stream. */
|
|
322
336
|
activeV3StreamedFiles = null;
|
|
337
|
+
/**
|
|
338
|
+
* Owns the currently open local workspace transaction for a foreground V3
|
|
339
|
+
* run. This must live on the API lifecycle rather than only in the async
|
|
340
|
+
* call stack: SIGINT/SIGTERM can otherwise terminate Node before the
|
|
341
|
+
* workflow catch block gets a chance to restore the workspace.
|
|
342
|
+
*/
|
|
343
|
+
activeMutationJournal = null;
|
|
323
344
|
lifecycleAbortController = new AbortController();
|
|
324
345
|
frontendPreviewService;
|
|
325
346
|
withLifecycleSignal(signal) {
|
|
@@ -421,6 +442,7 @@ export class APIClient {
|
|
|
421
442
|
this.frontendPreviewService = new FrontendPreviewService({
|
|
422
443
|
getBaseUrls: () => this.getTemplateServiceBaseUrls(),
|
|
423
444
|
getAccessToken: () => this.getAccessToken(),
|
|
445
|
+
getServiceKey: () => this.config.get('v3ServiceKey'),
|
|
424
446
|
fetch: (input, init) => guardedFetch(input, { ...init, signal: this.withLifecycleSignal(init.signal) }, { audience: 'template' }),
|
|
425
447
|
resolveTargetPath: (context) => this.resolveAgentTargetPath(context),
|
|
426
448
|
extractExpectedFiles: (message, context) => this.extractExpectedWorkspaceFiles(message, context),
|
|
@@ -510,7 +532,7 @@ export class APIClient {
|
|
|
510
532
|
return req;
|
|
511
533
|
});
|
|
512
534
|
this.selfHostedModelRouterClient?.interceptors.request.use((req) => {
|
|
513
|
-
const token = this.getAccessToken();
|
|
535
|
+
const token = resolveSelfHostedModelAuthToken(process.env, this.getAccessToken() || '');
|
|
514
536
|
const destination = resolveAxiosRequestUrl(req);
|
|
515
537
|
if (token) {
|
|
516
538
|
assertTrustedEndpoint(destination, { audience: 'models', credentialBearing: true });
|
|
@@ -519,9 +541,9 @@ export class APIClient {
|
|
|
519
541
|
return req;
|
|
520
542
|
});
|
|
521
543
|
// Add response interceptors for token refresh + structured errors
|
|
522
|
-
const createAuthRetryInterceptor = (client) => {
|
|
544
|
+
const createAuthRetryInterceptor = (client, refreshableCredential = true) => {
|
|
523
545
|
client.interceptors.response.use((res) => res, async (error) => {
|
|
524
|
-
if (error.response?.status === 401) {
|
|
546
|
+
if (error.response?.status === 401 && refreshableCredential) {
|
|
525
547
|
const refreshed = await this.refreshToken();
|
|
526
548
|
if (refreshed && error.config) {
|
|
527
549
|
return client.request(error.config);
|
|
@@ -534,7 +556,8 @@ export class APIClient {
|
|
|
534
556
|
createAuthRetryInterceptor(this.client);
|
|
535
557
|
createAuthRetryInterceptor(this.modelRouterClient);
|
|
536
558
|
if (this.selfHostedModelRouterClient) {
|
|
537
|
-
|
|
559
|
+
const usesServiceIdentity = Boolean(String(process.env.VIGTHORIA_INFERENCE_SERVICE_TOKEN || '').trim());
|
|
560
|
+
createAuthRetryInterceptor(this.selfHostedModelRouterClient, !usesServiceIdentity);
|
|
538
561
|
}
|
|
539
562
|
}
|
|
540
563
|
/**
|
|
@@ -544,6 +567,7 @@ export class APIClient {
|
|
|
544
567
|
* on Windows / Node 25+.
|
|
545
568
|
*/
|
|
546
569
|
destroy() {
|
|
570
|
+
const mutation = this.rollbackActiveMutationTransaction();
|
|
547
571
|
this.lifecycleAbortController.abort();
|
|
548
572
|
this.unsubscribeAuthInvalidation?.();
|
|
549
573
|
this.unsubscribeAuthInvalidation = null;
|
|
@@ -551,6 +575,45 @@ export class APIClient {
|
|
|
551
575
|
this._httpsAgent.destroy();
|
|
552
576
|
this._httpsAgent = null;
|
|
553
577
|
}
|
|
578
|
+
return mutation;
|
|
579
|
+
}
|
|
580
|
+
trackMutationTransaction(journal) {
|
|
581
|
+
if (this.activeMutationJournal && this.activeMutationJournal !== journal) {
|
|
582
|
+
throw new Error('A V3 workspace mutation transaction is already active.');
|
|
583
|
+
}
|
|
584
|
+
this.activeMutationJournal = journal;
|
|
585
|
+
}
|
|
586
|
+
releaseMutationTransaction(journal) {
|
|
587
|
+
if (this.activeMutationJournal === journal) {
|
|
588
|
+
this.activeMutationJournal = null;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
rollbackMutationTransaction(journal) {
|
|
592
|
+
try {
|
|
593
|
+
return journal.rollback();
|
|
594
|
+
}
|
|
595
|
+
finally {
|
|
596
|
+
this.releaseMutationTransaction(journal);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
rollbackActiveMutationTransaction() {
|
|
600
|
+
const journal = this.activeMutationJournal;
|
|
601
|
+
if (!journal)
|
|
602
|
+
return null;
|
|
603
|
+
try {
|
|
604
|
+
const mutation = this.rollbackMutationTransaction(journal);
|
|
605
|
+
if (mutation.partialMutation) {
|
|
606
|
+
this.logger.error(`Interrupted V3 operation ${mutation.operationId} left a partial workspace mutation: ${mutation.rollbackFailures.join('; ')}`);
|
|
607
|
+
}
|
|
608
|
+
else if (mutation.affectedPaths.length > 0) {
|
|
609
|
+
this.logger.warn(`Interrupted V3 operation ${mutation.operationId}; restored ${mutation.affectedPaths.length} workspace path(s).`);
|
|
610
|
+
}
|
|
611
|
+
return mutation;
|
|
612
|
+
}
|
|
613
|
+
catch (error) {
|
|
614
|
+
this.logger.error(`Interrupted V3 workspace rollback failed: ${error.message}`);
|
|
615
|
+
return null;
|
|
616
|
+
}
|
|
554
617
|
}
|
|
555
618
|
/** Exposed for Balanced 4B agent routing (local inference :8016). */
|
|
556
619
|
getSelfHostedModelsApiUrl() {
|
|
@@ -896,9 +959,11 @@ export class APIClient {
|
|
|
896
959
|
const localWorkspaceSummary = this.buildLocalWorkspaceSummary(localWorkspacePath, promptFocus, !clientToolExecution);
|
|
897
960
|
if (clientToolExecution && localWorkspaceSummary) {
|
|
898
961
|
localWorkspaceSummary.workspaceHydration = {
|
|
899
|
-
delivery:
|
|
962
|
+
delivery: resolvedContext.workspaceFilesOutOfBand === true
|
|
963
|
+
? 'client-tool-bridge+validation-mirror'
|
|
964
|
+
: 'client-tool-bridge',
|
|
900
965
|
fileCount: Number(localWorkspaceSummary.fileCount || 0),
|
|
901
|
-
complete:
|
|
966
|
+
complete: resolvedContext.workspaceFilesOutOfBand === true,
|
|
902
967
|
authoritativeLocation: 'client',
|
|
903
968
|
};
|
|
904
969
|
delete localWorkspaceSummary.workspaceFiles;
|
|
@@ -917,9 +982,11 @@ export class APIClient {
|
|
|
917
982
|
serverWorkspacePath,
|
|
918
983
|
});
|
|
919
984
|
// A local CLI/Workbench workspace remains authoritative on that machine.
|
|
920
|
-
//
|
|
985
|
+
// Its bounded server scratch copy is validation-only and is hydrated from
|
|
986
|
+
// the out-of-band request payload so established cross-file interfaces are
|
|
987
|
+
// available before the first delegated mutation is mirrored.
|
|
921
988
|
const effectiveWorkspacePath = serverWorkspacePath || null;
|
|
922
|
-
const needsHydration = !
|
|
989
|
+
const needsHydration = !serverWorkspacePath && !!localWorkspacePath;
|
|
923
990
|
const agentTaskType = resolvedContext.agentTaskType || 'general';
|
|
924
991
|
const rawPrompt = resolvedContext.rawPrompt || resolvedContext.prompt || '';
|
|
925
992
|
const executionHints = (resolvedContext.executionHints && typeof resolvedContext.executionHints === 'object')
|
|
@@ -973,13 +1040,15 @@ export class APIClient {
|
|
|
973
1040
|
projectPath: effectiveWorkspacePath,
|
|
974
1041
|
targetPath: effectiveWorkspacePath,
|
|
975
1042
|
// Never send a client-local absolute path to the V3 model boundary.
|
|
976
|
-
// The server only receives a stable label. Local bridge runs
|
|
977
|
-
//
|
|
1043
|
+
// The server only receives a stable label. Local bridge runs still
|
|
1044
|
+
// execute reads and writes on the client; the separate bounded file map
|
|
1045
|
+
// exists only to keep the server's validation mirror graph-complete.
|
|
978
1046
|
localWorkspacePath: serverWorkspacePath ? serverWorkspacePath : localWorkspaceRef,
|
|
979
1047
|
localWorkspaceRef,
|
|
980
1048
|
localWorkspaceName,
|
|
981
1049
|
localWorkspaceSummary,
|
|
982
|
-
//
|
|
1050
|
+
// A remote local-workspace run also requires its validation-only mirror
|
|
1051
|
+
// to be hydrated before execution starts.
|
|
983
1052
|
workspaceHydrationRequired: needsHydration,
|
|
984
1053
|
contextId: resolvedContext.contextId,
|
|
985
1054
|
traceId: resolvedContext.traceId,
|
|
@@ -1288,462 +1357,6 @@ export class APIClient {
|
|
|
1288
1357
|
contextState: resolvedContext.contextState || null,
|
|
1289
1358
|
});
|
|
1290
1359
|
}
|
|
1291
|
-
extractEmergencyAppName(message = '', fallback = 'Signal Desk') {
|
|
1292
|
-
const match = String(message || '').match(/called\s+([A-Z][A-Za-z0-9&\- ]{2,40})/i);
|
|
1293
|
-
return match?.[1]?.trim() || fallback;
|
|
1294
|
-
}
|
|
1295
|
-
materializeEmergencySaaSWorkspace(message = '', context = {}) {
|
|
1296
|
-
const rootPath = this.resolveAgentTargetPath(context);
|
|
1297
|
-
if (!rootPath) {
|
|
1298
|
-
return null;
|
|
1299
|
-
}
|
|
1300
|
-
fs.mkdirSync(rootPath, { recursive: true });
|
|
1301
|
-
const appName = this.extractEmergencyAppName(message);
|
|
1302
|
-
const html = `<!DOCTYPE html>
|
|
1303
|
-
<html lang="en">
|
|
1304
|
-
<head>
|
|
1305
|
-
<meta charset="UTF-8">
|
|
1306
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1307
|
-
<title>${appName}</title>
|
|
1308
|
-
<link rel="stylesheet" href="styles.css">
|
|
1309
|
-
</head>
|
|
1310
|
-
<body>
|
|
1311
|
-
<div class="app-shell">
|
|
1312
|
-
<aside class="sidebar">
|
|
1313
|
-
<div class="brand">${appName}</div>
|
|
1314
|
-
<button class="menu-toggle" id="menu-toggle" aria-label="Toggle navigation">Menu</button>
|
|
1315
|
-
<nav>
|
|
1316
|
-
<a href="#dashboard" class="nav-link active">Dashboard</a>
|
|
1317
|
-
<a href="#team" class="nav-link">Team</a>
|
|
1318
|
-
<a href="#billing" class="nav-link">Billing</a>
|
|
1319
|
-
<a href="#settings" class="nav-link">Settings</a>
|
|
1320
|
-
</nav>
|
|
1321
|
-
</aside>
|
|
1322
|
-
<main class="content">
|
|
1323
|
-
<section class="hero-card panel active-panel" id="dashboard">
|
|
1324
|
-
<div class="hero-copy">
|
|
1325
|
-
<p class="eyebrow">Dashboard</p>
|
|
1326
|
-
<h1>${appName} revenue command center</h1>
|
|
1327
|
-
<p>Track login activity, campaign velocity, billing state, and team performance from one responsive SaaS workspace.</p>
|
|
1328
|
-
</div>
|
|
1329
|
-
<form class="login-card">
|
|
1330
|
-
<h2>Login</h2>
|
|
1331
|
-
<label>Email<input type="email" placeholder="ops@${appName.toLowerCase().replace(/[^a-z0-9]+/g, '') || 'signaldesk'}.io"></label>
|
|
1332
|
-
<label>Password<input type="password" placeholder="Enter password"></label>
|
|
1333
|
-
<button type="submit">Enter dashboard</button>
|
|
1334
|
-
</form>
|
|
1335
|
-
</section>
|
|
1336
|
-
|
|
1337
|
-
<section class="stats-grid">
|
|
1338
|
-
<article class="stat-card"><span>MRR</span><strong>$284K</strong><em>+12.4%</em></article>
|
|
1339
|
-
<article class="stat-card"><span>Activation</span><strong>74%</strong><em>+6.1%</em></article>
|
|
1340
|
-
<article class="stat-card"><span>Team Seats</span><strong>128</strong><em>8 pending</em></article>
|
|
1341
|
-
<article class="stat-card"><span>Churn Risk</span><strong>2.1%</strong><em>Low</em></article>
|
|
1342
|
-
</section>
|
|
1343
|
-
|
|
1344
|
-
<section class="workspace-grid">
|
|
1345
|
-
<article class="panel chart-panel">
|
|
1346
|
-
<div class="panel-header">
|
|
1347
|
-
<h2>Analytics</h2>
|
|
1348
|
-
<button id="open-modal" type="button">Add campaign</button>
|
|
1349
|
-
</div>
|
|
1350
|
-
<div class="chart-bars" aria-label="Revenue chart">
|
|
1351
|
-
<div class="bar" style="--value: 52%"><span>Mon</span></div>
|
|
1352
|
-
<div class="bar" style="--value: 68%"><span>Tue</span></div>
|
|
1353
|
-
<div class="bar" style="--value: 74%"><span>Wed</span></div>
|
|
1354
|
-
<div class="bar" style="--value: 59%"><span>Thu</span></div>
|
|
1355
|
-
<div class="bar" style="--value: 88%"><span>Fri</span></div>
|
|
1356
|
-
</div>
|
|
1357
|
-
</article>
|
|
1358
|
-
|
|
1359
|
-
<article class="panel activity-panel">
|
|
1360
|
-
<div class="panel-header"><h2>Activity Feed</h2><span>Live</span></div>
|
|
1361
|
-
<ul class="activity-feed">
|
|
1362
|
-
<li><strong>Billing</strong><span>Enterprise invoice paid</span></li>
|
|
1363
|
-
<li><strong>Team</strong><span>New strategist invited to workspace</span></li>
|
|
1364
|
-
<li><strong>Dashboard</strong><span>KPI threshold updated for activation alerts</span></li>
|
|
1365
|
-
</ul>
|
|
1366
|
-
</article>
|
|
1367
|
-
|
|
1368
|
-
<article class="panel" id="team">
|
|
1369
|
-
<div class="panel-header"><h2>Team Management</h2><span>Owners and operators</span></div>
|
|
1370
|
-
<div class="team-list">
|
|
1371
|
-
<div><strong>Ana</strong><span>Growth lead</span></div>
|
|
1372
|
-
<div><strong>Marcus</strong><span>Billing admin</span></div>
|
|
1373
|
-
<div><strong>Lina</strong><span>Lifecycle analyst</span></div>
|
|
1374
|
-
</div>
|
|
1375
|
-
</article>
|
|
1376
|
-
|
|
1377
|
-
<article class="panel" id="billing">
|
|
1378
|
-
<div class="panel-header"><h2>Billing</h2><span>Current plan</span></div>
|
|
1379
|
-
<div class="billing-card">
|
|
1380
|
-
<strong>Scale Annual</strong>
|
|
1381
|
-
<p>Renews on 12 Oct with usage-based analytics overages.</p>
|
|
1382
|
-
<button type="button" class="secondary-action">Update payment method</button>
|
|
1383
|
-
</div>
|
|
1384
|
-
</article>
|
|
1385
|
-
|
|
1386
|
-
<article class="panel" id="settings">
|
|
1387
|
-
<div class="panel-header"><h2>Settings</h2><span>Automation and alerts</span></div>
|
|
1388
|
-
<form class="settings-form">
|
|
1389
|
-
<label>Alert threshold<input type="number" value="18"></label>
|
|
1390
|
-
<label>Weekly digest<select><option>Enabled</option><option>Paused</option></select></label>
|
|
1391
|
-
<button type="submit">Save settings</button>
|
|
1392
|
-
</form>
|
|
1393
|
-
</article>
|
|
1394
|
-
</section>
|
|
1395
|
-
</main>
|
|
1396
|
-
</div>
|
|
1397
|
-
|
|
1398
|
-
<dialog id="campaign-modal">
|
|
1399
|
-
<form method="dialog" class="modal-form">
|
|
1400
|
-
<h2>Launch campaign</h2>
|
|
1401
|
-
<label>Name<input type="text" placeholder="Retention push"></label>
|
|
1402
|
-
<label>Owner<input type="text" placeholder="Lina"></label>
|
|
1403
|
-
<menu>
|
|
1404
|
-
<button value="cancel">Cancel</button>
|
|
1405
|
-
<button value="confirm">Create</button>
|
|
1406
|
-
</menu>
|
|
1407
|
-
</form>
|
|
1408
|
-
</dialog>
|
|
1409
|
-
|
|
1410
|
-
<script src="scripts.js"></script>
|
|
1411
|
-
</body>
|
|
1412
|
-
</html>
|
|
1413
|
-
`;
|
|
1414
|
-
const css = `:root {
|
|
1415
|
-
--bg: #f2ede4;
|
|
1416
|
-
--ink: #18222f;
|
|
1417
|
-
--muted: #5c6674;
|
|
1418
|
-
--panel: rgba(255, 255, 255, 0.82);
|
|
1419
|
-
--line: rgba(24, 34, 47, 0.08);
|
|
1420
|
-
--accent: #b6542c;
|
|
1421
|
-
--accent-strong: #7f3417;
|
|
1422
|
-
--shadow: 0 24px 60px rgba(24, 34, 47, 0.12);
|
|
1423
|
-
}
|
|
1424
|
-
|
|
1425
|
-
* { box-sizing: border-box; }
|
|
1426
|
-
|
|
1427
|
-
body {
|
|
1428
|
-
margin: 0;
|
|
1429
|
-
font-family: "Georgia", "Times New Roman", serif;
|
|
1430
|
-
color: var(--ink);
|
|
1431
|
-
background:
|
|
1432
|
-
radial-gradient(circle at top left, rgba(182, 84, 44, 0.18), transparent 28%),
|
|
1433
|
-
radial-gradient(circle at bottom right, rgba(24, 34, 47, 0.14), transparent 30%),
|
|
1434
|
-
var(--bg);
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
.app-shell {
|
|
1438
|
-
min-height: 100vh;
|
|
1439
|
-
display: grid;
|
|
1440
|
-
grid-template-columns: 260px 1fr;
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
.sidebar {
|
|
1444
|
-
padding: 2rem 1.25rem;
|
|
1445
|
-
background: rgba(24, 34, 47, 0.94);
|
|
1446
|
-
color: #f7f2eb;
|
|
1447
|
-
position: sticky;
|
|
1448
|
-
top: 0;
|
|
1449
|
-
min-height: 100vh;
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
.brand {
|
|
1453
|
-
font-size: 1.6rem;
|
|
1454
|
-
font-weight: 700;
|
|
1455
|
-
margin-bottom: 1.5rem;
|
|
1456
|
-
}
|
|
1457
|
-
|
|
1458
|
-
.menu-toggle {
|
|
1459
|
-
display: none;
|
|
1460
|
-
margin-bottom: 1rem;
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
nav {
|
|
1464
|
-
display: grid;
|
|
1465
|
-
gap: 0.6rem;
|
|
1466
|
-
}
|
|
1467
|
-
|
|
1468
|
-
.nav-link {
|
|
1469
|
-
color: inherit;
|
|
1470
|
-
text-decoration: none;
|
|
1471
|
-
padding: 0.8rem 0.95rem;
|
|
1472
|
-
border-radius: 999px;
|
|
1473
|
-
transition: transform 0.25s ease, background-color 0.25s ease;
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1476
|
-
.nav-link:hover,
|
|
1477
|
-
.nav-link.active {
|
|
1478
|
-
background: rgba(255, 255, 255, 0.12);
|
|
1479
|
-
transform: translateX(4px);
|
|
1480
|
-
}
|
|
1481
|
-
|
|
1482
|
-
.content {
|
|
1483
|
-
padding: 2rem;
|
|
1484
|
-
}
|
|
1485
|
-
|
|
1486
|
-
.hero-card,
|
|
1487
|
-
.panel,
|
|
1488
|
-
.stat-card,
|
|
1489
|
-
.login-card,
|
|
1490
|
-
dialog {
|
|
1491
|
-
background: var(--panel);
|
|
1492
|
-
backdrop-filter: blur(16px);
|
|
1493
|
-
border: 1px solid var(--line);
|
|
1494
|
-
box-shadow: var(--shadow);
|
|
1495
|
-
}
|
|
1496
|
-
|
|
1497
|
-
.hero-card {
|
|
1498
|
-
display: grid;
|
|
1499
|
-
grid-template-columns: 1.3fr 0.9fr;
|
|
1500
|
-
gap: 1.5rem;
|
|
1501
|
-
border-radius: 32px;
|
|
1502
|
-
padding: 2rem;
|
|
1503
|
-
margin-bottom: 1.5rem;
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1506
|
-
.eyebrow {
|
|
1507
|
-
text-transform: uppercase;
|
|
1508
|
-
letter-spacing: 0.14em;
|
|
1509
|
-
color: var(--accent-strong);
|
|
1510
|
-
font-size: 0.78rem;
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
|
-
.hero-card h1,
|
|
1514
|
-
.panel h2,
|
|
1515
|
-
.login-card h2 {
|
|
1516
|
-
margin: 0 0 0.75rem;
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
|
-
.login-card,
|
|
1520
|
-
.panel,
|
|
1521
|
-
.stat-card {
|
|
1522
|
-
border-radius: 24px;
|
|
1523
|
-
}
|
|
1524
|
-
|
|
1525
|
-
.login-card,
|
|
1526
|
-
.settings-form,
|
|
1527
|
-
.modal-form {
|
|
1528
|
-
display: grid;
|
|
1529
|
-
gap: 0.85rem;
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
.stats-grid,
|
|
1533
|
-
.workspace-grid {
|
|
1534
|
-
display: grid;
|
|
1535
|
-
gap: 1rem;
|
|
1536
|
-
}
|
|
1537
|
-
|
|
1538
|
-
.stats-grid {
|
|
1539
|
-
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
1540
|
-
margin-bottom: 1rem;
|
|
1541
|
-
}
|
|
1542
|
-
|
|
1543
|
-
.workspace-grid {
|
|
1544
|
-
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1545
|
-
}
|
|
1546
|
-
|
|
1547
|
-
.stat-card,
|
|
1548
|
-
.panel {
|
|
1549
|
-
padding: 1.2rem;
|
|
1550
|
-
animation: riseIn 0.7s ease forwards;
|
|
1551
|
-
}
|
|
1552
|
-
|
|
1553
|
-
.stat-card span,
|
|
1554
|
-
.panel-header span,
|
|
1555
|
-
.activity-feed span,
|
|
1556
|
-
.team-list span,
|
|
1557
|
-
.billing-card p {
|
|
1558
|
-
color: var(--muted);
|
|
1559
|
-
}
|
|
1560
|
-
|
|
1561
|
-
.panel-header {
|
|
1562
|
-
display: flex;
|
|
1563
|
-
align-items: center;
|
|
1564
|
-
justify-content: space-between;
|
|
1565
|
-
gap: 1rem;
|
|
1566
|
-
margin-bottom: 1rem;
|
|
1567
|
-
}
|
|
1568
|
-
|
|
1569
|
-
.chart-bars {
|
|
1570
|
-
display: grid;
|
|
1571
|
-
grid-template-columns: repeat(5, minmax(0, 1fr));
|
|
1572
|
-
gap: 0.9rem;
|
|
1573
|
-
align-items: end;
|
|
1574
|
-
min-height: 220px;
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
|
-
.bar {
|
|
1578
|
-
position: relative;
|
|
1579
|
-
min-height: 180px;
|
|
1580
|
-
border-radius: 20px 20px 8px 8px;
|
|
1581
|
-
background: linear-gradient(180deg, rgba(182, 84, 44, 0.92), rgba(127, 52, 23, 0.68));
|
|
1582
|
-
transform-origin: bottom;
|
|
1583
|
-
transform: scaleY(calc(var(--value) / 100));
|
|
1584
|
-
transition: transform 0.6s ease;
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
.bar span {
|
|
1588
|
-
position: absolute;
|
|
1589
|
-
left: 50%;
|
|
1590
|
-
bottom: -1.6rem;
|
|
1591
|
-
transform: translateX(-50%);
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1594
|
-
.activity-feed,
|
|
1595
|
-
.team-list {
|
|
1596
|
-
display: grid;
|
|
1597
|
-
gap: 0.8rem;
|
|
1598
|
-
padding: 0;
|
|
1599
|
-
margin: 0;
|
|
1600
|
-
list-style: none;
|
|
1601
|
-
}
|
|
1602
|
-
|
|
1603
|
-
.activity-feed li,
|
|
1604
|
-
.team-list div,
|
|
1605
|
-
.billing-card {
|
|
1606
|
-
padding: 0.9rem 1rem;
|
|
1607
|
-
border-radius: 18px;
|
|
1608
|
-
background: rgba(255, 255, 255, 0.7);
|
|
1609
|
-
border: 1px solid var(--line);
|
|
1610
|
-
}
|
|
1611
|
-
|
|
1612
|
-
label {
|
|
1613
|
-
display: grid;
|
|
1614
|
-
gap: 0.35rem;
|
|
1615
|
-
font-size: 0.95rem;
|
|
1616
|
-
}
|
|
1617
|
-
|
|
1618
|
-
input,
|
|
1619
|
-
select,
|
|
1620
|
-
button {
|
|
1621
|
-
font: inherit;
|
|
1622
|
-
}
|
|
1623
|
-
|
|
1624
|
-
input,
|
|
1625
|
-
select {
|
|
1626
|
-
width: 100%;
|
|
1627
|
-
padding: 0.85rem 1rem;
|
|
1628
|
-
border-radius: 14px;
|
|
1629
|
-
border: 1px solid var(--line);
|
|
1630
|
-
background: rgba(255, 255, 255, 0.92);
|
|
1631
|
-
}
|
|
1632
|
-
|
|
1633
|
-
button {
|
|
1634
|
-
border: none;
|
|
1635
|
-
border-radius: 999px;
|
|
1636
|
-
padding: 0.85rem 1.2rem;
|
|
1637
|
-
background: var(--accent);
|
|
1638
|
-
color: #fff9f3;
|
|
1639
|
-
cursor: pointer;
|
|
1640
|
-
transition: transform 0.25s ease, background-color 0.25s ease;
|
|
1641
|
-
}
|
|
1642
|
-
|
|
1643
|
-
button:hover {
|
|
1644
|
-
background: var(--accent-strong);
|
|
1645
|
-
transform: translateY(-2px);
|
|
1646
|
-
}
|
|
1647
|
-
|
|
1648
|
-
.secondary-action,
|
|
1649
|
-
menu button:first-child {
|
|
1650
|
-
background: rgba(24, 34, 47, 0.12);
|
|
1651
|
-
color: var(--ink);
|
|
1652
|
-
}
|
|
1653
|
-
|
|
1654
|
-
dialog {
|
|
1655
|
-
border-radius: 28px;
|
|
1656
|
-
padding: 0;
|
|
1657
|
-
width: min(420px, calc(100% - 2rem));
|
|
1658
|
-
}
|
|
1659
|
-
|
|
1660
|
-
dialog::backdrop {
|
|
1661
|
-
background: rgba(24, 34, 47, 0.3);
|
|
1662
|
-
}
|
|
1663
|
-
|
|
1664
|
-
.modal-form {
|
|
1665
|
-
padding: 1.4rem;
|
|
1666
|
-
}
|
|
1667
|
-
|
|
1668
|
-
menu {
|
|
1669
|
-
display: flex;
|
|
1670
|
-
justify-content: flex-end;
|
|
1671
|
-
gap: 0.75rem;
|
|
1672
|
-
padding: 0;
|
|
1673
|
-
margin: 0.5rem 0 0;
|
|
1674
|
-
}
|
|
1675
|
-
|
|
1676
|
-
@keyframes riseIn {
|
|
1677
|
-
from {
|
|
1678
|
-
opacity: 0;
|
|
1679
|
-
transform: translateY(18px);
|
|
1680
|
-
}
|
|
1681
|
-
to {
|
|
1682
|
-
opacity: 1;
|
|
1683
|
-
transform: translateY(0);
|
|
1684
|
-
}
|
|
1685
|
-
}
|
|
1686
|
-
|
|
1687
|
-
@media (max-width: 980px) {
|
|
1688
|
-
.app-shell,
|
|
1689
|
-
.hero-card,
|
|
1690
|
-
.stats-grid,
|
|
1691
|
-
.workspace-grid {
|
|
1692
|
-
grid-template-columns: 1fr;
|
|
1693
|
-
}
|
|
1694
|
-
|
|
1695
|
-
.sidebar {
|
|
1696
|
-
position: static;
|
|
1697
|
-
min-height: auto;
|
|
1698
|
-
}
|
|
1699
|
-
|
|
1700
|
-
.menu-toggle {
|
|
1701
|
-
display: inline-flex;
|
|
1702
|
-
}
|
|
1703
|
-
|
|
1704
|
-
nav {
|
|
1705
|
-
display: none;
|
|
1706
|
-
}
|
|
1707
|
-
|
|
1708
|
-
nav.is-open {
|
|
1709
|
-
display: grid;
|
|
1710
|
-
}
|
|
1711
|
-
}
|
|
1712
|
-
`;
|
|
1713
|
-
const js = `document.addEventListener('DOMContentLoaded', () => {
|
|
1714
|
-
const menuToggle = document.getElementById('menu-toggle');
|
|
1715
|
-
const nav = document.querySelector('nav');
|
|
1716
|
-
const modal = document.getElementById('campaign-modal');
|
|
1717
|
-
const openModal = document.getElementById('open-modal');
|
|
1718
|
-
const navLinks = document.querySelectorAll('.nav-link');
|
|
1719
|
-
|
|
1720
|
-
menuToggle?.addEventListener('click', () => nav?.classList.toggle('is-open'));
|
|
1721
|
-
openModal?.addEventListener('click', () => modal?.showModal());
|
|
1722
|
-
modal?.addEventListener('close', () => document.body.classList.remove('modal-open'));
|
|
1723
|
-
|
|
1724
|
-
navLinks.forEach((link) => {
|
|
1725
|
-
link.addEventListener('click', (event) => {
|
|
1726
|
-
event.preventDefault();
|
|
1727
|
-
navLinks.forEach((entry) => entry.classList.remove('active'));
|
|
1728
|
-
link.classList.add('active');
|
|
1729
|
-
document.querySelector(link.getAttribute('href'))?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
1730
|
-
nav?.classList.remove('is-open');
|
|
1731
|
-
});
|
|
1732
|
-
});
|
|
1733
|
-
|
|
1734
|
-
document.querySelectorAll('.bar').forEach((bar, index) => {
|
|
1735
|
-
bar.animate([
|
|
1736
|
-
{ transform: 'scaleY(0.15)' },
|
|
1737
|
-
{ transform: getComputedStyle(bar).transform || 'scaleY(1)' }
|
|
1738
|
-
], { duration: 600 + index * 80, fill: 'forwards', easing: 'ease-out' });
|
|
1739
|
-
});
|
|
1740
|
-
});
|
|
1741
|
-
`;
|
|
1742
|
-
fs.writeFileSync(path.join(rootPath, 'index.html'), `${html.trimEnd()}\n`, 'utf8');
|
|
1743
|
-
fs.writeFileSync(path.join(rootPath, 'styles.css'), `${css.trimEnd()}\n`, 'utf8');
|
|
1744
|
-
fs.writeFileSync(path.join(rootPath, 'scripts.js'), `${js.trimEnd()}\n`, 'utf8');
|
|
1745
|
-
return appName;
|
|
1746
|
-
}
|
|
1747
1360
|
ensureExecutionContext(context = {}) {
|
|
1748
1361
|
const existingId = String(context.contextId || context.traceId || '').trim();
|
|
1749
1362
|
const contextId = existingId || `vig-${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
@@ -1993,9 +1606,9 @@ menu {
|
|
|
1993
1606
|
summary.readmeExcerpt = fs.readFileSync(readmePath, 'utf8').slice(0, 2500);
|
|
1994
1607
|
}
|
|
1995
1608
|
if (includeWorkspaceFiles) {
|
|
1996
|
-
//
|
|
1997
|
-
//
|
|
1998
|
-
//
|
|
1609
|
+
// The context-embedded form is reserved for server-owned/background
|
|
1610
|
+
// execution. Interactive CLI/Workbench hydration is sent separately
|
|
1611
|
+
// from model context and remains a bounded validation-only mirror.
|
|
1999
1612
|
summary.workspaceFiles = this.collectWorkspaceFileContents(rootPath, orderedPaths);
|
|
2000
1613
|
}
|
|
2001
1614
|
return summary;
|
|
@@ -2013,15 +1626,7 @@ menu {
|
|
|
2013
1626
|
const resolvedContext = this.ensureExecutionContext(context);
|
|
2014
1627
|
const localWorkspacePath = this.resolveAgentTargetPath(resolvedContext);
|
|
2015
1628
|
const serverWorkspacePath = this.resolveServerBindableWorkspacePath(resolvedContext);
|
|
2016
|
-
|
|
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)
|
|
1629
|
+
if (serverWorkspacePath || !localWorkspacePath)
|
|
2025
1630
|
return undefined;
|
|
2026
1631
|
const promptFocus = String(resolvedContext.rawPrompt || resolvedContext.contextualPrompt || resolvedContext.prompt || '');
|
|
2027
1632
|
const summary = this.buildLocalWorkspaceSummary(localWorkspacePath, promptFocus);
|
|
@@ -2084,7 +1689,7 @@ menu {
|
|
|
2084
1689
|
return;
|
|
2085
1690
|
}
|
|
2086
1691
|
const name = String(event.name || event.tool || '').trim();
|
|
2087
|
-
if (name !== 'write_file' && name !== 'edit_file') {
|
|
1692
|
+
if (name !== 'write_file' && name !== 'edit_file' && name !== 'delete_file') {
|
|
2088
1693
|
return;
|
|
2089
1694
|
}
|
|
2090
1695
|
const args = event.arguments || {};
|
|
@@ -2100,6 +1705,10 @@ menu {
|
|
|
2100
1705
|
if (!bucket) {
|
|
2101
1706
|
return;
|
|
2102
1707
|
}
|
|
1708
|
+
if (name === 'delete_file') {
|
|
1709
|
+
delete bucket[target.relativePath];
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
2103
1712
|
if (name === 'write_file' && typeof args.content === 'string') {
|
|
2104
1713
|
bucket[target.relativePath] = args.content;
|
|
2105
1714
|
return;
|
|
@@ -2130,6 +1739,71 @@ menu {
|
|
|
2130
1739
|
sha256: createHash('sha256').update(content, 'utf8').digest('hex'),
|
|
2131
1740
|
};
|
|
2132
1741
|
}
|
|
1742
|
+
validateV3ClientJavaScript(relativePath, source) {
|
|
1743
|
+
const extension = path.extname(relativePath).toLowerCase();
|
|
1744
|
+
if (!['.js', '.mjs', '.cjs'].includes(extension)) {
|
|
1745
|
+
return null;
|
|
1746
|
+
}
|
|
1747
|
+
let firstError = null;
|
|
1748
|
+
for (const mode of resolveJavaScriptSyntaxModes(source, relativePath)) {
|
|
1749
|
+
const argv = mode === 'module'
|
|
1750
|
+
? ['--input-type=module', '--check', '-']
|
|
1751
|
+
: ['--check', '-'];
|
|
1752
|
+
try {
|
|
1753
|
+
execFileSync(process.execPath, argv, {
|
|
1754
|
+
input: source,
|
|
1755
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1756
|
+
timeout: 10_000,
|
|
1757
|
+
windowsHide: true,
|
|
1758
|
+
env: safeChildProcessEnv(),
|
|
1759
|
+
});
|
|
1760
|
+
return null;
|
|
1761
|
+
}
|
|
1762
|
+
catch (error) {
|
|
1763
|
+
const detail = error?.stderr?.toString?.().trim() || error?.message || 'JavaScript syntax check failed.';
|
|
1764
|
+
firstError ||= this.compactV3ClientJavaScriptSyntaxError(relativePath, String(detail));
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
return firstError || 'JavaScript syntax check failed.';
|
|
1768
|
+
}
|
|
1769
|
+
compactV3ClientJavaScriptSyntaxError(relativePath, detail) {
|
|
1770
|
+
const normalized = String(detail || '').replaceAll('[stdin]', relativePath).replaceAll('\r\n', '\n');
|
|
1771
|
+
const lines = normalized.split('\n').map((line) => line.trim()).filter(Boolean);
|
|
1772
|
+
const escapedPath = relativePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1773
|
+
const location = lines.find((line) => new RegExp(`^${escapedPath}:\\d+(?::\\d+)?$`).test(line))
|
|
1774
|
+
|| lines.find((line) => /:\d+(?::\d+)?$/.test(line));
|
|
1775
|
+
const syntax = [...lines].reverse().find((line) => /^(?:[A-Za-z]*SyntaxError|SyntaxError):/i.test(line))
|
|
1776
|
+
|| [...lines].reverse().find((line) => /(?:unexpected|missing|unterminated|invalid)/i.test(line));
|
|
1777
|
+
if (location || syntax) {
|
|
1778
|
+
return [location, syntax].filter(Boolean).join(' ').slice(0, 900);
|
|
1779
|
+
}
|
|
1780
|
+
const nonStack = lines.find((line) => !/^at\s/.test(line) && !/^node:/.test(line));
|
|
1781
|
+
return (nonStack || 'JavaScript syntax check failed.').slice(0, 900);
|
|
1782
|
+
}
|
|
1783
|
+
isV3ReleaseVerificationRequest(event) {
|
|
1784
|
+
const taskId = String(event?.task_id || '').trim().toUpperCase();
|
|
1785
|
+
const phaseKind = String(event?.phase_kind || '').trim().toLowerCase();
|
|
1786
|
+
const taskKind = String(event?.task_kind || '').trim().toLowerCase();
|
|
1787
|
+
return taskId === 'TUX'
|
|
1788
|
+
|| taskId.startsWith('HEAL-FIX-')
|
|
1789
|
+
|| phaseKind === 'verify'
|
|
1790
|
+
|| phaseKind === 'verification'
|
|
1791
|
+
|| taskKind === 'verify'
|
|
1792
|
+
|| taskKind === 'verification';
|
|
1793
|
+
}
|
|
1794
|
+
describeV3AuthoritativePreviewFailure(gate) {
|
|
1795
|
+
const production = (gate.modes?.production || {});
|
|
1796
|
+
const summary = (gate.summary || {});
|
|
1797
|
+
const blockers = [
|
|
1798
|
+
...(Array.isArray(production.blockers) ? production.blockers : []),
|
|
1799
|
+
...(Array.isArray(summary.blockers) ? summary.blockers : []),
|
|
1800
|
+
].map((value) => String(value || '').trim()).filter(Boolean);
|
|
1801
|
+
const details = [...new Set([
|
|
1802
|
+
String(gate.error || '').trim(),
|
|
1803
|
+
...blockers,
|
|
1804
|
+
].filter(Boolean))];
|
|
1805
|
+
return `Authoritative Template Service production preview failed${details.length ? `: ${details.join('; ')}` : '.'}`;
|
|
1806
|
+
}
|
|
2133
1807
|
async executeV3ClientToolRequest(event, context = {}) {
|
|
2134
1808
|
const rootPath = this.resolveAgentTargetPath(context);
|
|
2135
1809
|
if (!rootPath) {
|
|
@@ -2150,7 +1824,7 @@ menu {
|
|
|
2150
1824
|
return { success: false, output: '', error: 'Tool path is outside the workspace.' };
|
|
2151
1825
|
}
|
|
2152
1826
|
const existingPathTools = new Set([
|
|
2153
|
-
'read_file', 'edit_file', 'list_directory', 'glob', 'search_files', 'grep',
|
|
1827
|
+
'read_file', 'edit_file', 'delete_file', 'list_directory', 'glob', 'search_files', 'grep',
|
|
2154
1828
|
'syntax_check', 'preview_check', 'runtime_check',
|
|
2155
1829
|
]);
|
|
2156
1830
|
if (existingPathTools.has(name) && !fs.existsSync(target.absolutePath)) {
|
|
@@ -2179,10 +1853,10 @@ menu {
|
|
|
2179
1853
|
}
|
|
2180
1854
|
const missingAssets = [];
|
|
2181
1855
|
const checkedAssets = [];
|
|
2182
|
-
const assetPattern = /<(?:script|link|img|source|video|audio)\b[^>]+(?:src|href)
|
|
1856
|
+
const assetPattern = /<(?:script|link|img|source|video|audio)\b[^>]+(?:src|href)\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'"`=<>]+))/gi;
|
|
2183
1857
|
let match;
|
|
2184
1858
|
while ((match = assetPattern.exec(html)) !== null && checkedAssets.length < 200) {
|
|
2185
|
-
const rawAsset = String(match[1] || '').trim();
|
|
1859
|
+
const rawAsset = String(match[1] || match[2] || match[3] || '').trim();
|
|
2186
1860
|
if (!rawAsset || /^(?:https?:|data:|blob:|mailto:|tel:|javascript:|#)/i.test(rawAsset))
|
|
2187
1861
|
continue;
|
|
2188
1862
|
let decoded;
|
|
@@ -2215,6 +1889,37 @@ menu {
|
|
|
2215
1889
|
error: `Missing local preview assets: ${missingAssets.slice(0, 12).join(', ')}`,
|
|
2216
1890
|
};
|
|
2217
1891
|
}
|
|
1892
|
+
if (this.isV3ReleaseVerificationRequest(event)) {
|
|
1893
|
+
const releasePrompt = String(context.rawPrompt || context.contextualPrompt || context.prompt
|
|
1894
|
+
|| 'Verify the complete responsive frontend application for production release.');
|
|
1895
|
+
const gate = await this.frontendPreviewService.runTemplateServicePreviewGate(releasePrompt, {
|
|
1896
|
+
...context,
|
|
1897
|
+
targetPath: rootPath,
|
|
1898
|
+
workspacePath: rootPath,
|
|
1899
|
+
projectPath: rootPath,
|
|
1900
|
+
rawPrompt: releasePrompt,
|
|
1901
|
+
forceFrontendPreview: true,
|
|
1902
|
+
functionalRuntimeProof: false,
|
|
1903
|
+
requireScreenshot: false,
|
|
1904
|
+
});
|
|
1905
|
+
if (!gate.passed || gate.skipped === true) {
|
|
1906
|
+
return {
|
|
1907
|
+
success: false,
|
|
1908
|
+
output: '',
|
|
1909
|
+
error: this.describeV3AuthoritativePreviewFailure(gate),
|
|
1910
|
+
};
|
|
1911
|
+
}
|
|
1912
|
+
return {
|
|
1913
|
+
success: true,
|
|
1914
|
+
output: JSON.stringify({
|
|
1915
|
+
entry: gate.entryPath || path.relative(rootPath, entryPath).replace(/\\/g, '/'),
|
|
1916
|
+
checkedAssets: checkedAssets.length,
|
|
1917
|
+
expectedTextMatched: Boolean(expectedText),
|
|
1918
|
+
authoritativeProductionPreview: true,
|
|
1919
|
+
qualityTier: gate.modes?.production?.qualityTier || null,
|
|
1920
|
+
}),
|
|
1921
|
+
};
|
|
1922
|
+
}
|
|
2218
1923
|
return {
|
|
2219
1924
|
success: true,
|
|
2220
1925
|
output: JSON.stringify({
|
|
@@ -2225,19 +1930,25 @@ menu {
|
|
|
2225
1930
|
};
|
|
2226
1931
|
}
|
|
2227
1932
|
if (name === 'runtime_check') {
|
|
2228
|
-
const
|
|
1933
|
+
const previewArgs = { ...args };
|
|
1934
|
+
delete previewArgs.expect_text;
|
|
1935
|
+
const previewResult = await this.executeV3ClientToolRequest({ ...event, name: 'preview_check', arguments: previewArgs }, context);
|
|
2229
1936
|
if (!previewResult.success)
|
|
2230
1937
|
return previewResult;
|
|
2231
1938
|
const relativeTarget = target.relativePath && target.relativePath !== '.' ? target.relativePath : 'index.html';
|
|
2232
|
-
const
|
|
1939
|
+
const runtimePrompt = String(context.rawPrompt || context.contextualPrompt || context.prompt
|
|
1940
|
+
|| `Verify the interactive frontend runtime at ${relativeTarget}.`);
|
|
1941
|
+
const gate = await this.frontendPreviewService.runTemplateServicePreviewGate(runtimePrompt, {
|
|
2233
1942
|
...context,
|
|
2234
1943
|
targetPath: rootPath,
|
|
2235
1944
|
workspacePath: rootPath,
|
|
2236
1945
|
projectPath: rootPath,
|
|
2237
|
-
rawPrompt:
|
|
1946
|
+
rawPrompt: runtimePrompt,
|
|
2238
1947
|
forceFrontendPreview: true,
|
|
2239
1948
|
localScreenshotProof: true,
|
|
1949
|
+
functionalRuntimeProof: true,
|
|
2240
1950
|
requireScreenshot: true,
|
|
1951
|
+
runtimeInteractionProof: this.buildLoopbackRuntimeInteraction(runtimePrompt, args),
|
|
2241
1952
|
});
|
|
2242
1953
|
if (!gate.passed || gate.artifacts?.screenshotCaptured !== true) {
|
|
2243
1954
|
return {
|
|
@@ -2251,13 +1962,35 @@ menu {
|
|
|
2251
1962
|
output: JSON.stringify({
|
|
2252
1963
|
entry: gate.entryPath || relativeTarget,
|
|
2253
1964
|
screenshotCaptured: true,
|
|
2254
|
-
proof: '
|
|
1965
|
+
proof: 'loopback-http-browser-interaction',
|
|
1966
|
+
runtimeInteraction: gate.artifacts?.runtimeInteraction || null,
|
|
2255
1967
|
}),
|
|
2256
1968
|
};
|
|
2257
1969
|
}
|
|
1970
|
+
if (name === 'delete_file') {
|
|
1971
|
+
const stat = fs.statSync(target.absolutePath);
|
|
1972
|
+
if (!stat.isFile()) {
|
|
1973
|
+
return {
|
|
1974
|
+
success: false,
|
|
1975
|
+
output: '',
|
|
1976
|
+
error: `delete_file only removes a single workspace file: ${target.relativePath}`,
|
|
1977
|
+
};
|
|
1978
|
+
}
|
|
1979
|
+
fs.unlinkSync(target.absolutePath);
|
|
1980
|
+
const result = {
|
|
1981
|
+
success: true,
|
|
1982
|
+
output: `Removed ${target.relativePath}`,
|
|
1983
|
+
};
|
|
1984
|
+
this.recordV3ClientToolMutation(event, context, result);
|
|
1985
|
+
return result;
|
|
1986
|
+
}
|
|
2258
1987
|
if (name === 'write_file') {
|
|
2259
1988
|
if (typeof args.content !== 'string')
|
|
2260
1989
|
return { success: false, output: '', error: 'write_file requires string content.' };
|
|
1990
|
+
const syntaxError = this.validateV3ClientJavaScript(target.relativePath, args.content);
|
|
1991
|
+
if (syntaxError) {
|
|
1992
|
+
return { success: false, output: '', error: `JavaScript mutation rejected before write: ${syntaxError}` };
|
|
1993
|
+
}
|
|
2261
1994
|
const mutation = this.buildV3ClientToolMutation(target.relativePath, args.content);
|
|
2262
1995
|
if ('error' in mutation)
|
|
2263
1996
|
return { success: false, output: '', error: mutation.error };
|
|
@@ -2294,6 +2027,10 @@ menu {
|
|
|
2294
2027
|
const nextContent = replaceAll
|
|
2295
2028
|
? existing.split(oldString).join(newString)
|
|
2296
2029
|
: existing.replace(oldString, newString);
|
|
2030
|
+
const syntaxError = this.validateV3ClientJavaScript(target.relativePath, nextContent);
|
|
2031
|
+
if (syntaxError) {
|
|
2032
|
+
return { success: false, output: '', error: `JavaScript mutation rejected before edit: ${syntaxError}` };
|
|
2033
|
+
}
|
|
2297
2034
|
const mutation = this.buildV3ClientToolMutation(target.relativePath, nextContent);
|
|
2298
2035
|
if ('error' in mutation)
|
|
2299
2036
|
return { success: false, output: '', error: mutation.error };
|
|
@@ -2417,15 +2154,11 @@ menu {
|
|
|
2417
2154
|
return { success: true, output: `Syntax check passed: ${target.relativePath}` };
|
|
2418
2155
|
}
|
|
2419
2156
|
if (ext === '.js' || ext === '.mjs' || ext === '.cjs') {
|
|
2420
|
-
const
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
catch (error) {
|
|
2426
|
-
const stderr = error?.stderr?.toString?.() || error?.message || String(error);
|
|
2427
|
-
return { success: false, output: '', error: stderr.trim() || `Syntax check failed: ${target.relativePath}` };
|
|
2428
|
-
}
|
|
2157
|
+
const source = fs.readFileSync(target.absolutePath, 'utf8');
|
|
2158
|
+
const syntaxError = this.validateV3ClientJavaScript(target.relativePath, source);
|
|
2159
|
+
return syntaxError
|
|
2160
|
+
? { success: false, output: '', error: syntaxError }
|
|
2161
|
+
: { success: true, output: `Syntax check passed: ${target.relativePath}` };
|
|
2429
2162
|
}
|
|
2430
2163
|
fs.readFileSync(target.absolutePath, 'utf8');
|
|
2431
2164
|
return { success: true, output: `Syntax check passed: ${target.relativePath}` };
|
|
@@ -2561,12 +2294,84 @@ menu {
|
|
|
2561
2294
|
|| /\b[a-z0-9_./-]+\.html?\b[^.\n]{0,100}\b(?:only|alone)\b/i.test(request);
|
|
2562
2295
|
return explicitOneFileLanguage;
|
|
2563
2296
|
}
|
|
2297
|
+
isFocusedAgentRepair(context = {}) {
|
|
2298
|
+
const taskKinds = [
|
|
2299
|
+
context.agentTaskType,
|
|
2300
|
+
context.executionHints?.task_kind,
|
|
2301
|
+
context.agentRoute?.taskKind,
|
|
2302
|
+
].map((value) => String(value || '').trim().toLowerCase());
|
|
2303
|
+
return taskKinds.includes('repair');
|
|
2304
|
+
}
|
|
2305
|
+
buildLoopbackRuntimeInteraction(prompt, args = {}) {
|
|
2306
|
+
const request = String(prompt || '');
|
|
2307
|
+
const keyMatch = request.match(/\b(?:press|hit|tap)\s+(?:the\s+)?(?:key\s+)?(space(?:bar)?|enter|return|esc(?:ape)?|arrow\s*(?:up|down|left|right)|[a-z0-9])\b/i);
|
|
2308
|
+
const rawKey = String(keyMatch?.[1] || '').replace(/\s+/g, '');
|
|
2309
|
+
const keyAliases = {
|
|
2310
|
+
return: 'Enter',
|
|
2311
|
+
esc: 'Escape',
|
|
2312
|
+
spacebar: 'Space',
|
|
2313
|
+
};
|
|
2314
|
+
const key = rawKey
|
|
2315
|
+
? (keyAliases[rawKey.toLowerCase()] || rawKey)
|
|
2316
|
+
: undefined;
|
|
2317
|
+
const expectText = String(args.expect_text || '').trim() || undefined;
|
|
2318
|
+
const supportedActions = new Set(['assert', 'click', 'fill', 'select', 'press', 'focus', 'wait', 'reload']);
|
|
2319
|
+
const steps = Array.isArray(args.steps)
|
|
2320
|
+
? args.steps.slice(0, 64).filter((step) => (Boolean(step) && typeof step === 'object' && !Array.isArray(step))).map((step) => ({
|
|
2321
|
+
...step,
|
|
2322
|
+
action: supportedActions.has(String(step.action || '').trim().toLowerCase())
|
|
2323
|
+
? String(step.action || '').trim().toLowerCase()
|
|
2324
|
+
: 'assert',
|
|
2325
|
+
}))
|
|
2326
|
+
: [];
|
|
2327
|
+
const requestedTimeoutSeconds = Math.max(1, Math.min(Number(args.timeout) || 30, 120));
|
|
2328
|
+
return {
|
|
2329
|
+
...(steps.length > 0 ? { steps } : (key ? { key } : {})),
|
|
2330
|
+
...(expectText ? { expectText } : {}),
|
|
2331
|
+
requireStateChange: steps.length === 0 && Boolean(key),
|
|
2332
|
+
waitMs: 1_750,
|
|
2333
|
+
timeoutMs: requestedTimeoutSeconds * 1_000,
|
|
2334
|
+
};
|
|
2335
|
+
}
|
|
2336
|
+
buildAgentFinalPreviewContext(message, context = {}) {
|
|
2337
|
+
const classificationContext = {
|
|
2338
|
+
...context,
|
|
2339
|
+
forceFrontendPreview: false,
|
|
2340
|
+
};
|
|
2341
|
+
if (!this.isFocusedAgentRepair(context)
|
|
2342
|
+
|| !this.frontendPreviewService.isFrontendTask(message, classificationContext)) {
|
|
2343
|
+
return context;
|
|
2344
|
+
}
|
|
2345
|
+
return {
|
|
2346
|
+
...context,
|
|
2347
|
+
rawPrompt: context.rawPrompt || message,
|
|
2348
|
+
forceFrontendPreview: true,
|
|
2349
|
+
localScreenshotProof: true,
|
|
2350
|
+
functionalRuntimeProof: true,
|
|
2351
|
+
requireScreenshot: true,
|
|
2352
|
+
runtimeInteractionProof: this.buildLoopbackRuntimeInteraction(String(context.rawPrompt || message || '')),
|
|
2353
|
+
};
|
|
2354
|
+
}
|
|
2564
2355
|
async ensureAgentFrontendPolish(message = '', context = {}) {
|
|
2356
|
+
// V3 owns application source. Legacy post-processing is retained only as
|
|
2357
|
+
// an explicit compatibility hook; normal Agent runs must never mutate a
|
|
2358
|
+
// successful or read-only result behind the model's back.
|
|
2359
|
+
if (context.allowLegacyClientFrontendPolish !== true) {
|
|
2360
|
+
return;
|
|
2361
|
+
}
|
|
2565
2362
|
const rootPath = this.resolveAgentTargetPath(context);
|
|
2566
2363
|
if (!rootPath || !fs.existsSync(rootPath)) {
|
|
2567
2364
|
return;
|
|
2568
2365
|
}
|
|
2569
2366
|
const prompt = String(message || '');
|
|
2367
|
+
// A focused repair owns only the evidence-proven source mutation. It is
|
|
2368
|
+
// not authorization for the CLI to add responsive CSS, motion helpers, or
|
|
2369
|
+
// fallback script files after the agent has finished. The final repair
|
|
2370
|
+
// gate below still captures a real browser screenshot, but it must remain
|
|
2371
|
+
// read-only with respect to the user's application files.
|
|
2372
|
+
if (this.isFocusedAgentRepair(context)) {
|
|
2373
|
+
return;
|
|
2374
|
+
}
|
|
2570
2375
|
const expectedFiles = this.extractExpectedWorkspaceFiles(message, context);
|
|
2571
2376
|
const looksLikeFrontendTask = /(premium|polished|landing|site|page|dashboard|saas|frontend|ui|pricing|showcase|responsive|interactive|game|canvas)/i.test(prompt)
|
|
2572
2377
|
|| expectedFiles.some((filePath) => /\.(html|css|js)$/i.test(filePath));
|
|
@@ -3195,7 +3000,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3195
3000
|
const proofSucceeded = previewGate?.required === true
|
|
3196
3001
|
? previewGate.passed === true && previewGate.skipped !== true
|
|
3197
3002
|
: true;
|
|
3198
|
-
|
|
3003
|
+
try {
|
|
3004
|
+
mutation = finalizeMutationTransaction(journal, proofSucceeded);
|
|
3005
|
+
}
|
|
3006
|
+
finally {
|
|
3007
|
+
this.releaseMutationTransaction(journal);
|
|
3008
|
+
}
|
|
3199
3009
|
if (!proofSucceeded)
|
|
3200
3010
|
changedFiles = {};
|
|
3201
3011
|
}
|
|
@@ -3431,7 +3241,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3431
3241
|
}
|
|
3432
3242
|
}
|
|
3433
3243
|
}
|
|
3434
|
-
|
|
3244
|
+
const userVisibleEvent = userEvent?.user_visible !== false
|
|
3245
|
+
&& String(userEvent?.visibility || '').toLowerCase() !== 'internal';
|
|
3246
|
+
if (typeof context.onStreamEvent === 'function'
|
|
3247
|
+
&& !isV3StreamKeepaliveEvent(userEvent)
|
|
3248
|
+
&& userVisibleEvent) {
|
|
3435
3249
|
try {
|
|
3436
3250
|
context.onStreamEvent(userEvent);
|
|
3437
3251
|
}
|
|
@@ -3453,21 +3267,24 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3453
3267
|
checkpointed_task_id: event.task_id,
|
|
3454
3268
|
};
|
|
3455
3269
|
}
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3270
|
+
// A parsed terminal SSE error is an authoritative server outcome,
|
|
3271
|
+
// including when an empty-workspace build failed before its first
|
|
3272
|
+
// accepted mutation. Return typed workflow data so the caller can
|
|
3273
|
+
// report the real executor failure. Throwing here made the outer
|
|
3274
|
+
// accepted-stream safety guard misclassify a known clean failure as
|
|
3275
|
+
// AGENT_OUTCOME_UNKNOWN and misleadingly recommend a resume.
|
|
3276
|
+
return {
|
|
3277
|
+
task_id: events.find((entry) => entry && entry.task_id)?.task_id || null,
|
|
3278
|
+
context_id: contextId,
|
|
3279
|
+
result: final || event,
|
|
3280
|
+
events,
|
|
3281
|
+
files: streamedFiles,
|
|
3282
|
+
partial: true,
|
|
3283
|
+
terminal_error: {
|
|
3284
|
+
message: event.message || 'V3 agent returned an error',
|
|
3285
|
+
code: event.code || event.error_code || null,
|
|
3286
|
+
},
|
|
3287
|
+
};
|
|
3471
3288
|
}
|
|
3472
3289
|
if (event.type === 'complete' || event.type === 'message') {
|
|
3473
3290
|
final = event;
|
|
@@ -3498,6 +3315,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3498
3315
|
let mutationJournal = null;
|
|
3499
3316
|
if (mutationRoot && fs.existsSync(mutationRoot) && fs.statSync(mutationRoot).isDirectory()) {
|
|
3500
3317
|
mutationJournal = new MutationJournal(mutationRoot, executionContext.operationId);
|
|
3318
|
+
this.trackMutationTransaction(mutationJournal);
|
|
3501
3319
|
executionContext.operationId = mutationJournal.operationId;
|
|
3502
3320
|
executionContext.__mutationJournal = mutationJournal;
|
|
3503
3321
|
}
|
|
@@ -3554,20 +3372,30 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3554
3372
|
const requestBody = buildRequestBody(contextIdOverride);
|
|
3555
3373
|
const controller = new AbortController();
|
|
3556
3374
|
const timeoutId = timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : null;
|
|
3375
|
+
let runRequestDispatched = false;
|
|
3376
|
+
let runResponseReceived = false;
|
|
3377
|
+
let runResponseAccepted = false;
|
|
3557
3378
|
try {
|
|
3379
|
+
// POST /run is a mutating operation. A transport failure after the
|
|
3380
|
+
// request leaves this process is ambiguous even when no streamed
|
|
3381
|
+
// file has reached the local mirror yet: the server can already be
|
|
3382
|
+
// planning or writing its managed workspace. Never turn that
|
|
3383
|
+
// ambiguity into a second whole-agent execution.
|
|
3384
|
+
runRequestDispatched = true;
|
|
3558
3385
|
const response = await this.executeV3AgentRunRequest(baseUrl, requestBody, requestExecutionContext, controller.signal);
|
|
3386
|
+
runResponseReceived = true;
|
|
3559
3387
|
if (!response.ok) {
|
|
3560
3388
|
const errorText = await response.text().catch(() => '');
|
|
3561
3389
|
const sanitized = sanitizeUserFacingErrorText(errorText).slice(0, 200);
|
|
3562
|
-
const isContextCollision = response.status === 409 && /already in progress/i.test(errorText);
|
|
3563
3390
|
const isTransientWorkspaceHydration = /remote workspace is empty|workspace sync failure/i.test(errorText);
|
|
3564
|
-
if (
|
|
3391
|
+
if (isTransientWorkspaceHydration && contextRetry === 0) {
|
|
3565
3392
|
contextIdOverride = `${requestExecutionContext.contextId || 'vig'}-retry-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
3566
3393
|
this.logger?.debug?.(`V3 transient agent error on ${baseUrl}; retrying with fresh context id.`);
|
|
3567
3394
|
continue;
|
|
3568
3395
|
}
|
|
3569
3396
|
throw new Error(`V3 agent ${response.status}: ${sanitized}`);
|
|
3570
3397
|
}
|
|
3398
|
+
runResponseAccepted = true;
|
|
3571
3399
|
const data = await this.collectV3AgentStream(response, requestExecutionContext);
|
|
3572
3400
|
// Auto-continuation: if the agent checkpointed (budget exceeded), continue automatically
|
|
3573
3401
|
if (data.checkpointed && data.checkpointed_task_id) {
|
|
@@ -3623,7 +3451,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3623
3451
|
this.recoverAgentWorkspaceFiles(executionContext, continuationData.files || {}, expectedFiles);
|
|
3624
3452
|
await this.waitForAgentWorkspaceSettle(executionContext, { expectedFiles });
|
|
3625
3453
|
await this.ensureAgentFrontendPolish(message, executionContext);
|
|
3626
|
-
const previewGate = await this.runTemplateServicePreviewGate(message, executionContext);
|
|
3454
|
+
const previewGate = await this.runTemplateServicePreviewGate(message, this.buildAgentFinalPreviewContext(message, executionContext));
|
|
3627
3455
|
const finalContextId = continuationData.context_id || data.context_id || response.headers.get('x-context-id') || requestExecutionContext.contextId || null;
|
|
3628
3456
|
return this.finalizeV3AgentWorkflowResponse(continuationData, {
|
|
3629
3457
|
content: this.formatV3AgentResponse(continuationData) || this.formatV3AgentResponse(data),
|
|
@@ -3642,7 +3470,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3642
3470
|
// valid output. A terminal planner/executor error fails proof and
|
|
3643
3471
|
// causes the operation journal to roll back streamed mutations.
|
|
3644
3472
|
const analysisOnly = this.isAnalysisOnlyTask(message, executionContext);
|
|
3645
|
-
const
|
|
3473
|
+
const frontendTask = this.frontendPreviewService.isFrontendTask(message, {
|
|
3474
|
+
...executionContext,
|
|
3475
|
+
forceFrontendPreview: false,
|
|
3476
|
+
});
|
|
3477
|
+
const failedPreviewGate = analysisOnly || !frontendTask
|
|
3646
3478
|
? { required: false, passed: true, skipped: false }
|
|
3647
3479
|
: {
|
|
3648
3480
|
required: true,
|
|
@@ -3668,7 +3500,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3668
3500
|
}
|
|
3669
3501
|
await this.waitForAgentWorkspaceSettle(executionContext, { expectedFiles });
|
|
3670
3502
|
await this.ensureAgentFrontendPolish(message, executionContext);
|
|
3671
|
-
const previewGate = await this.runTemplateServicePreviewGate(message, executionContext);
|
|
3503
|
+
const previewGate = await this.runTemplateServicePreviewGate(message, this.buildAgentFinalPreviewContext(message, executionContext));
|
|
3672
3504
|
return this.finalizeV3AgentWorkflowResponse(data, {
|
|
3673
3505
|
content: this.formatV3AgentResponse(data),
|
|
3674
3506
|
taskId: data.task_id || null,
|
|
@@ -3679,8 +3511,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3679
3511
|
}
|
|
3680
3512
|
catch (error) {
|
|
3681
3513
|
if (error?.code === 'AGENT_PLAN_INVALID') {
|
|
3682
|
-
if (mutationJournal
|
|
3683
|
-
const mutation =
|
|
3514
|
+
if (mutationJournal) {
|
|
3515
|
+
const mutation = this.rollbackMutationTransaction(mutationJournal);
|
|
3684
3516
|
mutationJournal = null;
|
|
3685
3517
|
error.operationId = mutation.operationId;
|
|
3686
3518
|
error.mutation = mutation;
|
|
@@ -3699,7 +3531,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3699
3531
|
this.recoverAgentWorkspaceFiles(executionContext, error.partialData.files || {}, expectedFiles);
|
|
3700
3532
|
await this.waitForAgentWorkspaceSettle(executionContext, { expectedFiles });
|
|
3701
3533
|
await this.ensureAgentFrontendPolish(message, executionContext);
|
|
3702
|
-
const previewGate = await this.runTemplateServicePreviewGate(message, executionContext);
|
|
3534
|
+
const previewGate = await this.runTemplateServicePreviewGate(message, this.buildAgentFinalPreviewContext(message, executionContext));
|
|
3703
3535
|
return this.finalizeV3AgentWorkflowResponse(error.partialData, {
|
|
3704
3536
|
content: this.formatV3AgentResponse(error.partialData) || 'V3 agent wrote workspace files before the request timed out waiting for a final summary.',
|
|
3705
3537
|
taskId: error.partialData.task_id || null,
|
|
@@ -3710,7 +3542,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3710
3542
|
}, mutationJournal);
|
|
3711
3543
|
}
|
|
3712
3544
|
if (mutationJournal?.hasMutations()) {
|
|
3713
|
-
const mutation =
|
|
3545
|
+
const mutation = this.rollbackMutationTransaction(mutationJournal);
|
|
3714
3546
|
mutationJournal = null;
|
|
3715
3547
|
this.activeV3StreamedFiles = null;
|
|
3716
3548
|
const ambiguousError = new Error(`V3 mutation outcome is ambiguous for operation ${mutation.operationId}; workspace rollback was attempted and no alternate backend retry was allowed.`);
|
|
@@ -3719,6 +3551,22 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3719
3551
|
ambiguousError.partialMutation = mutation.partialMutation;
|
|
3720
3552
|
throw ambiguousError;
|
|
3721
3553
|
}
|
|
3554
|
+
if (runResponseAccepted || (runRequestDispatched && !runResponseReceived)) {
|
|
3555
|
+
const mutation = mutationJournal
|
|
3556
|
+
? this.rollbackMutationTransaction(mutationJournal)
|
|
3557
|
+
: null;
|
|
3558
|
+
mutationJournal = null;
|
|
3559
|
+
this.activeV3StreamedFiles = null;
|
|
3560
|
+
const ambiguousError = new Error(`Agent connection was interrupted after operation ${executionContext.operationId || 'unknown'} started. `
|
|
3561
|
+
+ 'The task was not submitted again because that could duplicate changes. Use /continue to resume the saved run.');
|
|
3562
|
+
ambiguousError.name = 'AgentOutcomeUnknownError';
|
|
3563
|
+
ambiguousError.code = 'AGENT_OUTCOME_UNKNOWN';
|
|
3564
|
+
ambiguousError.operationId = executionContext.operationId || null;
|
|
3565
|
+
ambiguousError.contextId = requestBody.context_id || requestExecutionContext.contextId || null;
|
|
3566
|
+
ambiguousError.mutation = mutation || undefined;
|
|
3567
|
+
ambiguousError.partialMutation = mutation?.partialMutation || false;
|
|
3568
|
+
throw ambiguousError;
|
|
3569
|
+
}
|
|
3722
3570
|
errors.push(`${baseUrl}: ${error?.message || String(error)}`);
|
|
3723
3571
|
}
|
|
3724
3572
|
finally {
|
|
@@ -3759,27 +3607,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3759
3607
|
: '';
|
|
3760
3608
|
throw new Error(`V3 agent authentication failed at the V3 service layer while your gateway login token is still valid.${endpointSummary} Please retry shortly.`);
|
|
3761
3609
|
}
|
|
3762
|
-
if (preferLocalV3
|
|
3763
|
-
&& !this.hasAgentWorkspaceOutput(executionContext)
|
|
3764
|
-
&& /(saas|dashboard|analytics|billing|team management|activity feed)/i.test(message)) {
|
|
3765
|
-
const appName = this.materializeEmergencySaaSWorkspace(message, executionContext);
|
|
3766
|
-
if (appName) {
|
|
3767
|
-
await this.waitForAgentWorkspaceSettle(executionContext, { expectedFiles: ['index.html', 'styles.css', 'scripts.js'] });
|
|
3768
|
-
await this.ensureAgentFrontendPolish(message, executionContext);
|
|
3769
|
-
const previewGate = await this.runTemplateServicePreviewGate(message, executionContext);
|
|
3770
|
-
return this.finalizeV3AgentWorkflowResponse({}, {
|
|
3771
|
-
content: `Recovered a local SaaS workspace scaffold for ${appName} after repeated V3 materialization failures.`,
|
|
3772
|
-
taskId: null,
|
|
3773
|
-
contextId: executionContext.contextId || null,
|
|
3774
|
-
backendUrl: 'local-emergency-scaffold',
|
|
3775
|
-
partial: true,
|
|
3776
|
-
metadata: { source: 'v3-agent-emergency-scaffold', mode: 'agent', previewGate, emergencyScaffold: true },
|
|
3777
|
-
}, mutationJournal);
|
|
3778
|
-
}
|
|
3779
|
-
}
|
|
3780
3610
|
const finalError = new Error(errors.join(' | '));
|
|
3781
3611
|
if (mutationJournal) {
|
|
3782
|
-
const mutation =
|
|
3612
|
+
const mutation = this.rollbackMutationTransaction(mutationJournal);
|
|
3783
3613
|
mutationJournal = null;
|
|
3784
3614
|
finalError.operationId = mutation.operationId;
|
|
3785
3615
|
finalError.mutation = mutation;
|
|
@@ -4124,13 +3954,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
4124
3954
|
const headers = await this.getV3AgentHeaders();
|
|
4125
3955
|
const endpoint = this.isDirectV3AgentBaseUrl(baseUrl)
|
|
4126
3956
|
? `${baseUrl}/api/agent/client-tool-result`
|
|
4127
|
-
: `${baseUrl}/api/v3-agent/client-tool-result`;
|
|
3957
|
+
: `${baseUrl}/api/v3-agent-cli/client-tool-result`;
|
|
4128
3958
|
const body = JSON.stringify({
|
|
4129
3959
|
context_id: trimmedContextId,
|
|
4130
3960
|
call_id: trimmedCallId,
|
|
4131
3961
|
success: result.success === true,
|
|
4132
|
-
output: String(result.output || ''),
|
|
4133
|
-
error: String(result.error || ''),
|
|
3962
|
+
output: String(result.output || '').slice(0, MAX_V3_CLIENT_RESULT_OUTPUT_CHARS),
|
|
3963
|
+
error: String(result.error || '').slice(0, MAX_V3_CLIENT_RESULT_ERROR_CHARS),
|
|
4134
3964
|
mutation: result.mutation || null,
|
|
4135
3965
|
});
|
|
4136
3966
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
@@ -4254,12 +4084,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
4254
4084
|
}
|
|
4255
4085
|
async getV3AgentHealth() {
|
|
4256
4086
|
const baseUrl = this.getV3AgentBaseUrls()[0];
|
|
4257
|
-
//
|
|
4258
|
-
//
|
|
4087
|
+
// Direct requests use the isolated CLI runtime on 8032; authenticated
|
|
4088
|
+
// gateway requests use the named CLI lane.
|
|
4259
4089
|
const candidates = [
|
|
4260
|
-
`${baseUrl}/api/v3-agent/health`,
|
|
4090
|
+
`${baseUrl}${this.isDirectV3AgentBaseUrl(baseUrl) ? '/health' : '/api/v3-agent-cli/health'}`,
|
|
4261
4091
|
`${baseUrl}/api/health`,
|
|
4262
|
-
`${baseUrl}/health`,
|
|
4263
4092
|
];
|
|
4264
4093
|
const headers = await this.getV3AgentHeaders();
|
|
4265
4094
|
for (const endpoint of candidates) {
|