vigthoria-cli 1.13.34 → 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 +19 -4
- package/dist/utils/api.js +322 -550
- package/dist/utils/frontend-preview-service.d.ts +10 -1
- package/dist/utils/frontend-preview-service.js +162 -17
- 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 +81 -0
- package/dist/utils/preview-screenshot-adapter.js +615 -38
- 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 +13 -24
- 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({
|
|
@@ -2243,7 +1948,7 @@ menu {
|
|
|
2243
1948
|
localScreenshotProof: true,
|
|
2244
1949
|
functionalRuntimeProof: true,
|
|
2245
1950
|
requireScreenshot: true,
|
|
2246
|
-
runtimeInteractionProof: this.
|
|
1951
|
+
runtimeInteractionProof: this.buildLoopbackRuntimeInteraction(runtimePrompt, args),
|
|
2247
1952
|
});
|
|
2248
1953
|
if (!gate.passed || gate.artifacts?.screenshotCaptured !== true) {
|
|
2249
1954
|
return {
|
|
@@ -2257,14 +1962,35 @@ menu {
|
|
|
2257
1962
|
output: JSON.stringify({
|
|
2258
1963
|
entry: gate.entryPath || relativeTarget,
|
|
2259
1964
|
screenshotCaptured: true,
|
|
2260
|
-
proof: '
|
|
1965
|
+
proof: 'loopback-http-browser-interaction',
|
|
2261
1966
|
runtimeInteraction: gate.artifacts?.runtimeInteraction || null,
|
|
2262
1967
|
}),
|
|
2263
1968
|
};
|
|
2264
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
|
+
}
|
|
2265
1987
|
if (name === 'write_file') {
|
|
2266
1988
|
if (typeof args.content !== 'string')
|
|
2267
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
|
+
}
|
|
2268
1994
|
const mutation = this.buildV3ClientToolMutation(target.relativePath, args.content);
|
|
2269
1995
|
if ('error' in mutation)
|
|
2270
1996
|
return { success: false, output: '', error: mutation.error };
|
|
@@ -2301,6 +2027,10 @@ menu {
|
|
|
2301
2027
|
const nextContent = replaceAll
|
|
2302
2028
|
? existing.split(oldString).join(newString)
|
|
2303
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
|
+
}
|
|
2304
2034
|
const mutation = this.buildV3ClientToolMutation(target.relativePath, nextContent);
|
|
2305
2035
|
if ('error' in mutation)
|
|
2306
2036
|
return { success: false, output: '', error: mutation.error };
|
|
@@ -2424,15 +2154,11 @@ menu {
|
|
|
2424
2154
|
return { success: true, output: `Syntax check passed: ${target.relativePath}` };
|
|
2425
2155
|
}
|
|
2426
2156
|
if (ext === '.js' || ext === '.mjs' || ext === '.cjs') {
|
|
2427
|
-
const
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
catch (error) {
|
|
2433
|
-
const stderr = error?.stderr?.toString?.() || error?.message || String(error);
|
|
2434
|
-
return { success: false, output: '', error: stderr.trim() || `Syntax check failed: ${target.relativePath}` };
|
|
2435
|
-
}
|
|
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}` };
|
|
2436
2162
|
}
|
|
2437
2163
|
fs.readFileSync(target.absolutePath, 'utf8');
|
|
2438
2164
|
return { success: true, output: `Syntax check passed: ${target.relativePath}` };
|
|
@@ -2576,7 +2302,7 @@ menu {
|
|
|
2576
2302
|
].map((value) => String(value || '').trim().toLowerCase());
|
|
2577
2303
|
return taskKinds.includes('repair');
|
|
2578
2304
|
}
|
|
2579
|
-
|
|
2305
|
+
buildLoopbackRuntimeInteraction(prompt, args = {}) {
|
|
2580
2306
|
const request = String(prompt || '');
|
|
2581
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);
|
|
2582
2308
|
const rawKey = String(keyMatch?.[1] || '').replace(/\s+/g, '');
|
|
@@ -2589,15 +2315,31 @@ menu {
|
|
|
2589
2315
|
? (keyAliases[rawKey.toLowerCase()] || rawKey)
|
|
2590
2316
|
: undefined;
|
|
2591
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));
|
|
2592
2328
|
return {
|
|
2593
|
-
...(key ? { key } : {}),
|
|
2329
|
+
...(steps.length > 0 ? { steps } : (key ? { key } : {})),
|
|
2594
2330
|
...(expectText ? { expectText } : {}),
|
|
2595
|
-
requireStateChange: Boolean(key),
|
|
2331
|
+
requireStateChange: steps.length === 0 && Boolean(key),
|
|
2596
2332
|
waitMs: 1_750,
|
|
2333
|
+
timeoutMs: requestedTimeoutSeconds * 1_000,
|
|
2597
2334
|
};
|
|
2598
2335
|
}
|
|
2599
2336
|
buildAgentFinalPreviewContext(message, context = {}) {
|
|
2600
|
-
|
|
2337
|
+
const classificationContext = {
|
|
2338
|
+
...context,
|
|
2339
|
+
forceFrontendPreview: false,
|
|
2340
|
+
};
|
|
2341
|
+
if (!this.isFocusedAgentRepair(context)
|
|
2342
|
+
|| !this.frontendPreviewService.isFrontendTask(message, classificationContext)) {
|
|
2601
2343
|
return context;
|
|
2602
2344
|
}
|
|
2603
2345
|
return {
|
|
@@ -2607,10 +2349,16 @@ menu {
|
|
|
2607
2349
|
localScreenshotProof: true,
|
|
2608
2350
|
functionalRuntimeProof: true,
|
|
2609
2351
|
requireScreenshot: true,
|
|
2610
|
-
runtimeInteractionProof: this.
|
|
2352
|
+
runtimeInteractionProof: this.buildLoopbackRuntimeInteraction(String(context.rawPrompt || message || '')),
|
|
2611
2353
|
};
|
|
2612
2354
|
}
|
|
2613
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
|
+
}
|
|
2614
2362
|
const rootPath = this.resolveAgentTargetPath(context);
|
|
2615
2363
|
if (!rootPath || !fs.existsSync(rootPath)) {
|
|
2616
2364
|
return;
|
|
@@ -3252,7 +3000,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3252
3000
|
const proofSucceeded = previewGate?.required === true
|
|
3253
3001
|
? previewGate.passed === true && previewGate.skipped !== true
|
|
3254
3002
|
: true;
|
|
3255
|
-
|
|
3003
|
+
try {
|
|
3004
|
+
mutation = finalizeMutationTransaction(journal, proofSucceeded);
|
|
3005
|
+
}
|
|
3006
|
+
finally {
|
|
3007
|
+
this.releaseMutationTransaction(journal);
|
|
3008
|
+
}
|
|
3256
3009
|
if (!proofSucceeded)
|
|
3257
3010
|
changedFiles = {};
|
|
3258
3011
|
}
|
|
@@ -3488,7 +3241,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3488
3241
|
}
|
|
3489
3242
|
}
|
|
3490
3243
|
}
|
|
3491
|
-
|
|
3244
|
+
const userVisibleEvent = userEvent?.user_visible !== false
|
|
3245
|
+
&& String(userEvent?.visibility || '').toLowerCase() !== 'internal';
|
|
3246
|
+
if (typeof context.onStreamEvent === 'function'
|
|
3247
|
+
&& !isV3StreamKeepaliveEvent(userEvent)
|
|
3248
|
+
&& userVisibleEvent) {
|
|
3492
3249
|
try {
|
|
3493
3250
|
context.onStreamEvent(userEvent);
|
|
3494
3251
|
}
|
|
@@ -3510,21 +3267,24 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3510
3267
|
checkpointed_task_id: event.task_id,
|
|
3511
3268
|
};
|
|
3512
3269
|
}
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
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
|
+
};
|
|
3528
3288
|
}
|
|
3529
3289
|
if (event.type === 'complete' || event.type === 'message') {
|
|
3530
3290
|
final = event;
|
|
@@ -3555,6 +3315,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3555
3315
|
let mutationJournal = null;
|
|
3556
3316
|
if (mutationRoot && fs.existsSync(mutationRoot) && fs.statSync(mutationRoot).isDirectory()) {
|
|
3557
3317
|
mutationJournal = new MutationJournal(mutationRoot, executionContext.operationId);
|
|
3318
|
+
this.trackMutationTransaction(mutationJournal);
|
|
3558
3319
|
executionContext.operationId = mutationJournal.operationId;
|
|
3559
3320
|
executionContext.__mutationJournal = mutationJournal;
|
|
3560
3321
|
}
|
|
@@ -3611,20 +3372,30 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3611
3372
|
const requestBody = buildRequestBody(contextIdOverride);
|
|
3612
3373
|
const controller = new AbortController();
|
|
3613
3374
|
const timeoutId = timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : null;
|
|
3375
|
+
let runRequestDispatched = false;
|
|
3376
|
+
let runResponseReceived = false;
|
|
3377
|
+
let runResponseAccepted = false;
|
|
3614
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;
|
|
3615
3385
|
const response = await this.executeV3AgentRunRequest(baseUrl, requestBody, requestExecutionContext, controller.signal);
|
|
3386
|
+
runResponseReceived = true;
|
|
3616
3387
|
if (!response.ok) {
|
|
3617
3388
|
const errorText = await response.text().catch(() => '');
|
|
3618
3389
|
const sanitized = sanitizeUserFacingErrorText(errorText).slice(0, 200);
|
|
3619
|
-
const isContextCollision = response.status === 409 && /already in progress/i.test(errorText);
|
|
3620
3390
|
const isTransientWorkspaceHydration = /remote workspace is empty|workspace sync failure/i.test(errorText);
|
|
3621
|
-
if (
|
|
3391
|
+
if (isTransientWorkspaceHydration && contextRetry === 0) {
|
|
3622
3392
|
contextIdOverride = `${requestExecutionContext.contextId || 'vig'}-retry-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
3623
3393
|
this.logger?.debug?.(`V3 transient agent error on ${baseUrl}; retrying with fresh context id.`);
|
|
3624
3394
|
continue;
|
|
3625
3395
|
}
|
|
3626
3396
|
throw new Error(`V3 agent ${response.status}: ${sanitized}`);
|
|
3627
3397
|
}
|
|
3398
|
+
runResponseAccepted = true;
|
|
3628
3399
|
const data = await this.collectV3AgentStream(response, requestExecutionContext);
|
|
3629
3400
|
// Auto-continuation: if the agent checkpointed (budget exceeded), continue automatically
|
|
3630
3401
|
if (data.checkpointed && data.checkpointed_task_id) {
|
|
@@ -3699,7 +3470,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3699
3470
|
// valid output. A terminal planner/executor error fails proof and
|
|
3700
3471
|
// causes the operation journal to roll back streamed mutations.
|
|
3701
3472
|
const analysisOnly = this.isAnalysisOnlyTask(message, executionContext);
|
|
3702
|
-
const
|
|
3473
|
+
const frontendTask = this.frontendPreviewService.isFrontendTask(message, {
|
|
3474
|
+
...executionContext,
|
|
3475
|
+
forceFrontendPreview: false,
|
|
3476
|
+
});
|
|
3477
|
+
const failedPreviewGate = analysisOnly || !frontendTask
|
|
3703
3478
|
? { required: false, passed: true, skipped: false }
|
|
3704
3479
|
: {
|
|
3705
3480
|
required: true,
|
|
@@ -3736,8 +3511,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3736
3511
|
}
|
|
3737
3512
|
catch (error) {
|
|
3738
3513
|
if (error?.code === 'AGENT_PLAN_INVALID') {
|
|
3739
|
-
if (mutationJournal
|
|
3740
|
-
const mutation =
|
|
3514
|
+
if (mutationJournal) {
|
|
3515
|
+
const mutation = this.rollbackMutationTransaction(mutationJournal);
|
|
3741
3516
|
mutationJournal = null;
|
|
3742
3517
|
error.operationId = mutation.operationId;
|
|
3743
3518
|
error.mutation = mutation;
|
|
@@ -3767,7 +3542,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3767
3542
|
}, mutationJournal);
|
|
3768
3543
|
}
|
|
3769
3544
|
if (mutationJournal?.hasMutations()) {
|
|
3770
|
-
const mutation =
|
|
3545
|
+
const mutation = this.rollbackMutationTransaction(mutationJournal);
|
|
3771
3546
|
mutationJournal = null;
|
|
3772
3547
|
this.activeV3StreamedFiles = null;
|
|
3773
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.`);
|
|
@@ -3776,6 +3551,22 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3776
3551
|
ambiguousError.partialMutation = mutation.partialMutation;
|
|
3777
3552
|
throw ambiguousError;
|
|
3778
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
|
+
}
|
|
3779
3570
|
errors.push(`${baseUrl}: ${error?.message || String(error)}`);
|
|
3780
3571
|
}
|
|
3781
3572
|
finally {
|
|
@@ -3816,27 +3607,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3816
3607
|
: '';
|
|
3817
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.`);
|
|
3818
3609
|
}
|
|
3819
|
-
if (preferLocalV3
|
|
3820
|
-
&& !this.hasAgentWorkspaceOutput(executionContext)
|
|
3821
|
-
&& /(saas|dashboard|analytics|billing|team management|activity feed)/i.test(message)) {
|
|
3822
|
-
const appName = this.materializeEmergencySaaSWorkspace(message, executionContext);
|
|
3823
|
-
if (appName) {
|
|
3824
|
-
await this.waitForAgentWorkspaceSettle(executionContext, { expectedFiles: ['index.html', 'styles.css', 'scripts.js'] });
|
|
3825
|
-
await this.ensureAgentFrontendPolish(message, executionContext);
|
|
3826
|
-
const previewGate = await this.runTemplateServicePreviewGate(message, this.buildAgentFinalPreviewContext(message, executionContext));
|
|
3827
|
-
return this.finalizeV3AgentWorkflowResponse({}, {
|
|
3828
|
-
content: `Recovered a local SaaS workspace scaffold for ${appName} after repeated V3 materialization failures.`,
|
|
3829
|
-
taskId: null,
|
|
3830
|
-
contextId: executionContext.contextId || null,
|
|
3831
|
-
backendUrl: 'local-emergency-scaffold',
|
|
3832
|
-
partial: true,
|
|
3833
|
-
metadata: { source: 'v3-agent-emergency-scaffold', mode: 'agent', previewGate, emergencyScaffold: true },
|
|
3834
|
-
}, mutationJournal);
|
|
3835
|
-
}
|
|
3836
|
-
}
|
|
3837
3610
|
const finalError = new Error(errors.join(' | '));
|
|
3838
3611
|
if (mutationJournal) {
|
|
3839
|
-
const mutation =
|
|
3612
|
+
const mutation = this.rollbackMutationTransaction(mutationJournal);
|
|
3840
3613
|
mutationJournal = null;
|
|
3841
3614
|
finalError.operationId = mutation.operationId;
|
|
3842
3615
|
finalError.mutation = mutation;
|
|
@@ -4181,13 +3954,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
4181
3954
|
const headers = await this.getV3AgentHeaders();
|
|
4182
3955
|
const endpoint = this.isDirectV3AgentBaseUrl(baseUrl)
|
|
4183
3956
|
? `${baseUrl}/api/agent/client-tool-result`
|
|
4184
|
-
: `${baseUrl}/api/v3-agent/client-tool-result`;
|
|
3957
|
+
: `${baseUrl}/api/v3-agent-cli/client-tool-result`;
|
|
4185
3958
|
const body = JSON.stringify({
|
|
4186
3959
|
context_id: trimmedContextId,
|
|
4187
3960
|
call_id: trimmedCallId,
|
|
4188
3961
|
success: result.success === true,
|
|
4189
|
-
output: String(result.output || ''),
|
|
4190
|
-
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),
|
|
4191
3964
|
mutation: result.mutation || null,
|
|
4192
3965
|
});
|
|
4193
3966
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
@@ -4311,12 +4084,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
4311
4084
|
}
|
|
4312
4085
|
async getV3AgentHealth() {
|
|
4313
4086
|
const baseUrl = this.getV3AgentBaseUrls()[0];
|
|
4314
|
-
//
|
|
4315
|
-
//
|
|
4087
|
+
// Direct requests use the isolated CLI runtime on 8032; authenticated
|
|
4088
|
+
// gateway requests use the named CLI lane.
|
|
4316
4089
|
const candidates = [
|
|
4317
|
-
`${baseUrl}/api/v3-agent/health`,
|
|
4090
|
+
`${baseUrl}${this.isDirectV3AgentBaseUrl(baseUrl) ? '/health' : '/api/v3-agent-cli/health'}`,
|
|
4318
4091
|
`${baseUrl}/api/health`,
|
|
4319
|
-
`${baseUrl}/health`,
|
|
4320
4092
|
];
|
|
4321
4093
|
const headers = await this.getV3AgentHeaders();
|
|
4322
4094
|
for (const endpoint of candidates) {
|