vigthoria-cli 1.11.36 → 1.11.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/bridge.js +8 -2
- package/dist/commands/chat.d.ts +1 -0
- package/dist/commands/chat.js +99 -11
- package/dist/index.js +5 -0
- package/dist/utils/agentRoute.js +17 -0
- package/dist/utils/api.d.ts +1 -0
- package/dist/utils/api.js +154 -49
- package/dist/utils/clientManifest.d.ts +12 -0
- package/dist/utils/clientManifest.js +68 -0
- package/dist/utils/localTestMode.d.ts +9 -0
- package/dist/utils/localTestMode.js +42 -0
- package/dist/utils/session.d.ts +1 -0
- package/dist/utils/session.js +36 -3
- package/dist/utils/templateInstantPath.js +18 -0
- package/dist/utils/tools.js +20 -4
- package/dist/utils/v3-workspace-path.d.ts +2 -0
- package/dist/utils/v3-workspace-path.js +42 -2
- package/install.ps1 +1 -1
- package/install.sh +1 -1
- package/package.json +2 -1
- package/scripts/release/validate-no-go-gates.sh +4 -1
package/dist/commands/bridge.js
CHANGED
|
@@ -2,6 +2,12 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { createSpinner } from '../utils/logger.js';
|
|
3
3
|
import { APIClient } from '../utils/api.js';
|
|
4
4
|
import { getDesktopBridgeStatus } from '../utils/desktop-bridge-client.js';
|
|
5
|
+
function displayEndpoint(endpoint) {
|
|
6
|
+
if (/^(?:wss?|https?):\/\/(?:127\.0\.0\.1|localhost)(?::\d+)?/i.test(endpoint)) {
|
|
7
|
+
return 'local loopback endpoint';
|
|
8
|
+
}
|
|
9
|
+
return endpoint;
|
|
10
|
+
}
|
|
5
11
|
export class BridgeCommand {
|
|
6
12
|
api;
|
|
7
13
|
constructor(config, logger) {
|
|
@@ -18,7 +24,7 @@ export class BridgeCommand {
|
|
|
18
24
|
console.log(chalk.white('DevTools Bridge (browser, port 4016):'));
|
|
19
25
|
console.log(chalk.gray(' Status: ') + (devtoolsBridge.ok ? chalk.green('Reachable') : chalk.yellow('Not running')));
|
|
20
26
|
if (devtoolsBridge.ok) {
|
|
21
|
-
console.log(chalk.gray(' Endpoint: ') + devtoolsBridge.endpoint);
|
|
27
|
+
console.log(chalk.gray(' Endpoint: ') + displayEndpoint(devtoolsBridge.endpoint));
|
|
22
28
|
}
|
|
23
29
|
else {
|
|
24
30
|
const detail = String(devtoolsBridge.error || 'Connection refused').trim() || 'Connection refused';
|
|
@@ -31,7 +37,7 @@ export class BridgeCommand {
|
|
|
31
37
|
console.log(chalk.white('Desktop Bridge (Windows desktop, port 49160):'));
|
|
32
38
|
console.log(chalk.gray(' Status: ') + (desktopBridge.ok ? chalk.green('Reachable') : chalk.yellow('Not running')));
|
|
33
39
|
if (desktopBridge.ok) {
|
|
34
|
-
console.log(chalk.gray(' Endpoint: ') + desktopBridge.endpoint);
|
|
40
|
+
console.log(chalk.gray(' Endpoint: ') + displayEndpoint(desktopBridge.endpoint));
|
|
35
41
|
if (desktopBridge.service)
|
|
36
42
|
console.log(chalk.gray(' Service: ') + desktopBridge.service);
|
|
37
43
|
if (desktopBridge.version)
|
package/dist/commands/chat.d.ts
CHANGED
|
@@ -68,6 +68,7 @@ export declare class ChatCommand {
|
|
|
68
68
|
private hasOperatorAccess;
|
|
69
69
|
private operatorAccessMessage;
|
|
70
70
|
private getDefaultChatModel;
|
|
71
|
+
private isHardExplicitAgentModelSelection;
|
|
71
72
|
private resolveInitialModel;
|
|
72
73
|
private isLegacyAgentFallbackAllowed;
|
|
73
74
|
private shouldRescueFailedAnalysis;
|
package/dist/commands/chat.js
CHANGED
|
@@ -22,27 +22,54 @@ import { inferAgentTaskType as sharedInferAgentTaskType, inferAgentTaskTypeWithC
|
|
|
22
22
|
import { resolveAgentRoute } from '../utils/agentRoute.js';
|
|
23
23
|
import { runTemplateInstantPath } from '../utils/templateInstantPath.js';
|
|
24
24
|
import { isV3StreamKeepaliveEvent } from '../utils/v3-stream-events.js';
|
|
25
|
+
// Production safety net: previously these defaulted to 0 (disabled) unless an
|
|
26
|
+
// operator manually set an env var. Real end-user installs never set these,
|
|
27
|
+
// so a stalled/incomplete model stream (e.g. text ends after </think> with no
|
|
28
|
+
// tool_call event) would hang the CLI forever with only a cosmetic
|
|
29
|
+
// "[Wait] Model still working" notice and no actual abort. See TestFarm
|
|
30
|
+
// incident 2026-07-04 (Gideon Lenz / C:\vigthoria\Apps\monopoly).
|
|
31
|
+
// Set VIGTHORIA_AGENT_TIMEOUT_MS=0 / VIGTHORIA_AGENT_IDLE_TIMEOUT_MS=0 to
|
|
32
|
+
// explicitly restore the old unbounded behavior for debugging.
|
|
25
33
|
const DEFAULT_V3_AGENT_TIMEOUT_MS = (() => {
|
|
26
34
|
const rawValue = process.env.VIGTHORIA_AGENT_TIMEOUT_MS || process.env.V3_AGENT_TIMEOUT_MS;
|
|
27
35
|
if (!rawValue) {
|
|
28
|
-
return
|
|
36
|
+
return 1_200_000; // 20 min hard cap on the whole agent request
|
|
29
37
|
}
|
|
30
38
|
const parsed = Number.parseInt(rawValue, 10);
|
|
31
|
-
return Number.isFinite(parsed) && parsed >= 0 ? parsed :
|
|
39
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 1_200_000;
|
|
32
40
|
})();
|
|
33
41
|
const DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS = (() => {
|
|
34
42
|
const rawValue = process.env.VIGTHORIA_AGENT_IDLE_TIMEOUT_MS || process.env.V3_AGENT_IDLE_TIMEOUT_MS;
|
|
35
43
|
if (!rawValue) {
|
|
36
|
-
return
|
|
44
|
+
return 60_000; // 60s with zero new stream data => abort/retry
|
|
37
45
|
}
|
|
38
46
|
const parsed = Number.parseInt(rawValue, 10);
|
|
39
|
-
return Number.isFinite(parsed) && parsed >= 0 ? parsed :
|
|
47
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 60_000;
|
|
40
48
|
})();
|
|
41
49
|
const DEFAULT_V3_AGENT_SOFT_TIMEOUT_MS = (() => {
|
|
42
50
|
const rawValue = process.env.VIGTHORIA_AGENT_SOFT_TIMEOUT_MS || process.env.V3_AGENT_SOFT_TIMEOUT_MS || '300000';
|
|
43
51
|
const parsed = Number.parseInt(rawValue, 10);
|
|
44
52
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : 300000;
|
|
45
53
|
})();
|
|
54
|
+
/** Block-write agent JSON to VIGTHORIA_AGENT_OUTPUT (Windows KVM fsync path). */
|
|
55
|
+
function emitAgentJsonOutput(payload) {
|
|
56
|
+
const text = `${JSON.stringify(payload, null, 2)}\n`;
|
|
57
|
+
console.log(text.trimEnd());
|
|
58
|
+
const outPath = process.env.VIGTHORIA_AGENT_OUTPUT?.trim();
|
|
59
|
+
if (!outPath) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const dir = path.dirname(outPath);
|
|
63
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
64
|
+
const fd = fs.openSync(outPath, 'w');
|
|
65
|
+
try {
|
|
66
|
+
fs.writeSync(fd, text, undefined, 'utf8');
|
|
67
|
+
fs.fsyncSync(fd);
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
fs.closeSync(fd);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
46
73
|
export class ChatCommand {
|
|
47
74
|
config;
|
|
48
75
|
logger;
|
|
@@ -251,6 +278,19 @@ export class ChatCommand {
|
|
|
251
278
|
}
|
|
252
279
|
return preferredModel;
|
|
253
280
|
}
|
|
281
|
+
isHardExplicitAgentModelSelection(options) {
|
|
282
|
+
const requestedModel = String(options.model || '').trim().toLowerCase();
|
|
283
|
+
if (!requestedModel) {
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
if (/^(1|true|yes)$/i.test(String(process.env.VIGTHORIA_FORCE_EXPLICIT_AGENT_MODEL || ''))) {
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
if (options.agent === true || options.operator === true) {
|
|
290
|
+
return !['agent', 'code', 'balanced', 'vigthoria-agent', 'vigthoria-code'].includes(requestedModel);
|
|
291
|
+
}
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
254
294
|
resolveInitialModel(options) {
|
|
255
295
|
const requestedModel = String(options.model || '').trim();
|
|
256
296
|
if (requestedModel) {
|
|
@@ -312,6 +352,16 @@ export class ChatCommand {
|
|
|
312
352
|
routeReason: 'explicit-model-selection',
|
|
313
353
|
};
|
|
314
354
|
}
|
|
355
|
+
if (this.agentMode && ['agent', 'code', 'balanced', 'vigthoria-agent', 'vigthoria-code'].includes(String(this.currentModel || '').toLowerCase())) {
|
|
356
|
+
return {
|
|
357
|
+
selectedModel: 'agent',
|
|
358
|
+
explicitModel: false,
|
|
359
|
+
heavyTask,
|
|
360
|
+
cloudEligible,
|
|
361
|
+
cloudSelected: false,
|
|
362
|
+
routeReason: 'dispatcher-eligible-agent-default',
|
|
363
|
+
};
|
|
364
|
+
}
|
|
315
365
|
if (this.lastAgentRoute?.path === 'template-instant') {
|
|
316
366
|
return {
|
|
317
367
|
selectedModel: 'agent',
|
|
@@ -816,7 +866,12 @@ export class ChatCommand {
|
|
|
816
866
|
}
|
|
817
867
|
this.v3IdleWatchInterval = setInterval(() => {
|
|
818
868
|
const idleMs = Date.now() - this.v3LastActivity;
|
|
819
|
-
if (idleMs < 15_000
|
|
869
|
+
if (idleMs < 15_000) {
|
|
870
|
+
// Activity resumed — re-arm the notice so a later stall is reported too.
|
|
871
|
+
this.v3IdleNoticeShown = false;
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
if (this.v3IdleNoticeShown) {
|
|
820
875
|
return;
|
|
821
876
|
}
|
|
822
877
|
this.v3IdleNoticeShown = true;
|
|
@@ -824,7 +879,11 @@ export class ChatCommand {
|
|
|
824
879
|
spinner.stop();
|
|
825
880
|
}
|
|
826
881
|
const seconds = Math.round(idleMs / 1000);
|
|
827
|
-
|
|
882
|
+
const idleTimeoutSec = Math.round(DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS / 1000);
|
|
883
|
+
process.stderr.write(chalk.yellow(` [Wait] Model still working (${seconds}s) — large context or server retry may take a while. ` +
|
|
884
|
+
(idleTimeoutSec > 0
|
|
885
|
+
? `A hard timeout (~${idleTimeoutSec}s of silence) will auto-recover and offer /retry if the model stalls.\n`
|
|
886
|
+
: `\n`)));
|
|
828
887
|
spinner.start();
|
|
829
888
|
spinner.text = 'Waiting for model response...';
|
|
830
889
|
}, 5_000);
|
|
@@ -1406,7 +1465,7 @@ export class ChatCommand {
|
|
|
1406
1465
|
if (!this.config.isAuthenticated()) {
|
|
1407
1466
|
if (options.json) {
|
|
1408
1467
|
process.exitCode = 1;
|
|
1409
|
-
|
|
1468
|
+
emitAgentJsonOutput({ success: false, error: 'Not authenticated. Run: vigthoria login' });
|
|
1410
1469
|
}
|
|
1411
1470
|
else {
|
|
1412
1471
|
this.logger.error('Not authenticated. Run: vigthoria login');
|
|
@@ -1422,7 +1481,7 @@ export class ChatCommand {
|
|
|
1422
1481
|
this.jsonOutput = options.json === true;
|
|
1423
1482
|
this.autoApprove = options.autoApprove === true || this.jsonOutput;
|
|
1424
1483
|
this.personaOverride = options.grant === true ? 'wiener_grant' : null;
|
|
1425
|
-
this.modelExplicitlySelected =
|
|
1484
|
+
this.modelExplicitlySelected = this.isHardExplicitAgentModelSelection(options);
|
|
1426
1485
|
this.currentModel = this.resolveInitialModel(options);
|
|
1427
1486
|
this.applyNoAgentGovernance(String(options.model || this.currentModel || ''));
|
|
1428
1487
|
this.currentProjectPath = this.resolveProjectPath(options);
|
|
@@ -2788,6 +2847,28 @@ export class ChatCommand {
|
|
|
2788
2847
|
console.log(chalk.gray(' Run: vigthoria preview'));
|
|
2789
2848
|
this.printAgentRunSummary(this.lastAgentRunOutcome, evaluation, 1);
|
|
2790
2849
|
}
|
|
2850
|
+
else {
|
|
2851
|
+
const routingPolicy = this.resolveAgentExecutionPolicy(prompt);
|
|
2852
|
+
console.log(JSON.stringify({
|
|
2853
|
+
success: evaluation.executorSucceeded,
|
|
2854
|
+
mode: 'agent',
|
|
2855
|
+
model: routingPolicy.selectedModel,
|
|
2856
|
+
routingPolicy,
|
|
2857
|
+
taskId: null,
|
|
2858
|
+
contextId: null,
|
|
2859
|
+
partial: false,
|
|
2860
|
+
content: summary,
|
|
2861
|
+
statusHeadline: evaluation.statusHeadline,
|
|
2862
|
+
tasksSucceeded: 1,
|
|
2863
|
+
tasksTotal: 1,
|
|
2864
|
+
metadata: {
|
|
2865
|
+
source: 'template-instant',
|
|
2866
|
+
templateName: result.templateName,
|
|
2867
|
+
processingMs: result.processingMs,
|
|
2868
|
+
route: this.lastAgentRoute,
|
|
2869
|
+
},
|
|
2870
|
+
}, null, 2));
|
|
2871
|
+
}
|
|
2791
2872
|
return true;
|
|
2792
2873
|
}
|
|
2793
2874
|
async tryDirectSingleFileFlow(prompt) {
|
|
@@ -3328,7 +3409,8 @@ export class ChatCommand {
|
|
|
3328
3409
|
console.log(this.renderAgentAnswerForTerminal(finalAnswer));
|
|
3329
3410
|
}
|
|
3330
3411
|
}
|
|
3331
|
-
|
|
3412
|
+
const plannerFailedBeforeTasks = Boolean(liveOutcome.plannerError) && liveOutcome.tasksTotal === 0;
|
|
3413
|
+
if (!executorSucceeded && requiresWorkspaceChanges && changedFileCount === 0 && !liveOutcome.executorError && !plannerFailedBeforeTasks) {
|
|
3332
3414
|
const clientToolErrors = this.api.getClientToolErrors();
|
|
3333
3415
|
const transportErrors = this.api.getLastChatTransportErrors();
|
|
3334
3416
|
if (clientToolErrors.length > 0) {
|
|
@@ -3344,6 +3426,12 @@ export class ChatCommand {
|
|
|
3344
3426
|
liveOutcome.plannerError = transportErrors[0];
|
|
3345
3427
|
}
|
|
3346
3428
|
}
|
|
3429
|
+
if (plannerFailedBeforeTasks && !liveOutcome.executorError) {
|
|
3430
|
+
const transportErrors = this.api.getLastChatTransportErrors();
|
|
3431
|
+
if (transportErrors.length > 0) {
|
|
3432
|
+
liveOutcome.executorError = transportErrors[0];
|
|
3433
|
+
}
|
|
3434
|
+
}
|
|
3347
3435
|
if (executorSucceeded) {
|
|
3348
3436
|
taskDisplay.complete(1);
|
|
3349
3437
|
}
|
|
@@ -3437,7 +3525,7 @@ export class ChatCommand {
|
|
|
3437
3525
|
if (!executorSucceeded) {
|
|
3438
3526
|
process.exitCode = 1;
|
|
3439
3527
|
}
|
|
3440
|
-
|
|
3528
|
+
emitAgentJsonOutput({
|
|
3441
3529
|
success: executorSucceeded,
|
|
3442
3530
|
mode: 'agent',
|
|
3443
3531
|
model: routingPolicy.selectedModel,
|
|
@@ -3459,7 +3547,7 @@ export class ChatCommand {
|
|
|
3459
3547
|
},
|
|
3460
3548
|
previewGate,
|
|
3461
3549
|
},
|
|
3462
|
-
}
|
|
3550
|
+
});
|
|
3463
3551
|
}
|
|
3464
3552
|
this.messages.push({ role: 'assistant', content: finalAnswer || runEvaluation.statusHeadline });
|
|
3465
3553
|
this.stopV3IdleWatch();
|
package/dist/index.js
CHANGED
|
@@ -54,6 +54,8 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
54
54
|
const __dirname = path.dirname(__filename);
|
|
55
55
|
import { APIClient, sanitizeUserFacingErrorText } from './utils/api.js';
|
|
56
56
|
import { getCliStateFile, isOfflineMode, isSafeNpmPackageSpec, isUpdateCheckSuppressed, readCachedLatestVersion, readGatewayPreflightCache, writeCachedLatestVersion, writeGatewayPreflightCache, } from './utils/cli-state.js';
|
|
57
|
+
import { applyLocalTestfarmDefaults, isLocalTestfarmMode } from './utils/localTestMode.js';
|
|
58
|
+
applyLocalTestfarmDefaults();
|
|
57
59
|
function isApiError(error) {
|
|
58
60
|
return Boolean(error &&
|
|
59
61
|
typeof error === 'object' &&
|
|
@@ -398,6 +400,9 @@ function isAuthProtectedCommand(command) {
|
|
|
398
400
|
return protectedCommands.has(command);
|
|
399
401
|
}
|
|
400
402
|
async function enforceGatewayAuthSession(config, logger, jsonOutputRequested) {
|
|
403
|
+
if (isLocalTestfarmMode()) {
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
401
406
|
if (!config.isAuthenticated()) {
|
|
402
407
|
return true;
|
|
403
408
|
}
|
package/dist/utils/agentRoute.js
CHANGED
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { inferAgentTaskType, isTrivialHtmlPageRequest, isAgentContinuePrompt, isAgentRetryPrompt, hasGameIntent, hasWebPageIntent, taskRequiresWorkspaceChanges, stripExecutionShaping, } from './requestIntent.js';
|
|
5
5
|
import { isFastInferenceRouterAvailable, routeWithBalanced4b, shouldUseBalancedRouter, } from './fastAgentRouter.js';
|
|
6
|
+
function forceTemplateScaffold() {
|
|
7
|
+
const v = String(process.env.VIGTHORIA_FORCE_TEMPLATE_SCAFFOLD || '').trim().toLowerCase();
|
|
8
|
+
return v === '1' || v === 'true' || v === 'yes';
|
|
9
|
+
}
|
|
10
|
+
function wantsInstantTemplatePath(prompt) {
|
|
11
|
+
return /instant[- ]template|template[- ]instant|use the instant-template path/i.test(prompt);
|
|
12
|
+
}
|
|
6
13
|
function normalizeTaskKind(value, fallback) {
|
|
7
14
|
const kind = String(value || fallback).trim().toLowerCase();
|
|
8
15
|
const allowed = new Set([
|
|
@@ -115,6 +122,16 @@ export async function resolveAgentRoute(api, prompt) {
|
|
|
115
122
|
};
|
|
116
123
|
}
|
|
117
124
|
if (hasGameIntent(stripped) && regexTask === 'game-build') {
|
|
125
|
+
if (forceTemplateScaffold() || wantsInstantTemplatePath(stripped)) {
|
|
126
|
+
return {
|
|
127
|
+
path: 'template-instant',
|
|
128
|
+
taskKind: 'game-build',
|
|
129
|
+
fastPath: 'template-instant',
|
|
130
|
+
confidence: 0.98,
|
|
131
|
+
reason: 'express game template scaffold',
|
|
132
|
+
source: 'regex',
|
|
133
|
+
};
|
|
134
|
+
}
|
|
118
135
|
return {
|
|
119
136
|
path: 'v3-agent',
|
|
120
137
|
taskKind: 'game-build',
|
package/dist/utils/api.d.ts
CHANGED
|
@@ -256,6 +256,7 @@ export declare class APIClient {
|
|
|
256
256
|
valid: boolean;
|
|
257
257
|
error?: string;
|
|
258
258
|
}>;
|
|
259
|
+
private isDirectV3AgentBaseUrl;
|
|
259
260
|
getV3AgentBaseUrls(preferLocal?: boolean): string[];
|
|
260
261
|
getV3AgentRunUrl(baseUrl: string): string;
|
|
261
262
|
getV3AgentContinueUrl(baseUrl: string): string;
|
package/dist/utils/api.js
CHANGED
|
@@ -12,10 +12,12 @@ import WebSocket from 'ws';
|
|
|
12
12
|
import { buildSemanticContext } from './context-ranker.js';
|
|
13
13
|
import { getChangedFiles } from './workspace-cache.js';
|
|
14
14
|
import { isSubstantiveAgentAnswer, isToolEvidenceStubAnswer } from './agentRunOutcome.js';
|
|
15
|
-
import { joinV3WorkspacePath, normalizeV3WorkspaceRelativePath } from './v3-workspace-path.js';
|
|
15
|
+
import { isV3WorkspaceRootAliasPath, joinV3WorkspacePath, normalizeV3WorkspaceRelativePath } from './v3-workspace-path.js';
|
|
16
16
|
import { buildExecutionHints, isPlannerBuildTask, resolvePlannerAgentTimeoutMs } from './requestIntent.js';
|
|
17
17
|
import { isV3StreamKeepaliveEvent } from './v3-stream-events.js';
|
|
18
18
|
import { resolveV3ContextCharLimit, WORKSPACE_FILE_CHAR_CAP, } from './contextBudget.js';
|
|
19
|
+
import { isLocalTestfarmMode, fetchWithServiceTimeout } from './localTestMode.js';
|
|
20
|
+
import { buildClientManifest } from './clientManifest.js';
|
|
19
21
|
export const VIGTHORIA_HUB_CREDITS_URL = 'https://hub.vigthoria.io/credits';
|
|
20
22
|
export const VIGTHORIA_SERVER_TEMPORARILY_UNAVAILABLE_MESSAGE = 'Vigthoria Server is temporarily not available. Please try again later. If the issue persists, please contact support.';
|
|
21
23
|
export class CLIError extends Error {
|
|
@@ -258,21 +260,25 @@ export function propagateError(err) {
|
|
|
258
260
|
},
|
|
259
261
|
};
|
|
260
262
|
}
|
|
263
|
+
// Production safety net — see matching comment in src/commands/chat.ts.
|
|
264
|
+
// These previously defaulted to 0 (disabled), letting a stalled model stream
|
|
265
|
+
// hang the CLI forever with no abort. Set the env vars to 0 to restore the
|
|
266
|
+
// old unbounded behavior for debugging.
|
|
261
267
|
const DEFAULT_V3_AGENT_TIMEOUT_MS = (() => {
|
|
262
268
|
const rawValue = process.env.VIGTHORIA_AGENT_TIMEOUT_MS || process.env.V3_AGENT_TIMEOUT_MS;
|
|
263
269
|
if (!rawValue) {
|
|
264
|
-
return
|
|
270
|
+
return 1_200_000; // 20 min hard cap on the whole agent request
|
|
265
271
|
}
|
|
266
272
|
const parsed = Number.parseInt(rawValue, 10);
|
|
267
|
-
return Number.isFinite(parsed) && parsed >= 0 ? parsed :
|
|
273
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 1_200_000;
|
|
268
274
|
})();
|
|
269
275
|
const DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS = (() => {
|
|
270
276
|
const rawValue = process.env.VIGTHORIA_AGENT_IDLE_TIMEOUT_MS || process.env.V3_AGENT_IDLE_TIMEOUT_MS;
|
|
271
277
|
if (!rawValue) {
|
|
272
|
-
return
|
|
278
|
+
return 60_000; // 60s with zero new stream data => abort/retry
|
|
273
279
|
}
|
|
274
280
|
const parsed = Number.parseInt(rawValue, 10);
|
|
275
|
-
return Number.isFinite(parsed) && parsed >= 0 ? parsed :
|
|
281
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 60_000;
|
|
276
282
|
})();
|
|
277
283
|
const DEFAULT_OPERATOR_TIMEOUT_MS = (() => {
|
|
278
284
|
const rawValue = process.env.VIGTHORIA_OPERATOR_TIMEOUT_MS || process.env.OPERATOR_TIMEOUT_MS;
|
|
@@ -559,6 +565,16 @@ export class APIClient {
|
|
|
559
565
|
const enforceTokenShape = options.enforceTokenShape !== false;
|
|
560
566
|
const explicitEnvToken = Boolean(process.env.VIGTHORIA_TOKEN || process.env.VIGTHORIA_AUTH_TOKEN);
|
|
561
567
|
const token = this.getAccessToken();
|
|
568
|
+
if (isLocalTestfarmMode()) {
|
|
569
|
+
if (!token) {
|
|
570
|
+
return { valid: false, error: 'Local test mode requires VIGTHORIA_LOCAL_TEST_TOKEN or VIGTHORIA_AUTH_TOKEN' };
|
|
571
|
+
}
|
|
572
|
+
const expected = String(process.env.VIGTHORIA_LOCAL_TEST_TOKEN || '').trim();
|
|
573
|
+
if (expected && token !== expected) {
|
|
574
|
+
return { valid: false, error: 'VIGTHORIA_LOCAL_TEST_TOKEN mismatch' };
|
|
575
|
+
}
|
|
576
|
+
return { valid: true };
|
|
577
|
+
}
|
|
562
578
|
if (!token) {
|
|
563
579
|
return { valid: false, error: 'No auth token configured. Run: vigthoria login' };
|
|
564
580
|
}
|
|
@@ -617,8 +633,18 @@ export class APIClient {
|
|
|
617
633
|
// Both unreachable — don't assume the stored token is bad when running offline.
|
|
618
634
|
return { valid: true };
|
|
619
635
|
}
|
|
636
|
+
isDirectV3AgentBaseUrl(baseUrl) {
|
|
637
|
+
try {
|
|
638
|
+
const parsed = new URL(String(baseUrl));
|
|
639
|
+
return parsed.port === '8030' || parsed.port.endsWith('8030');
|
|
640
|
+
}
|
|
641
|
+
catch {
|
|
642
|
+
return /(?:127\.0\.0\.1|localhost|172\.19\.0\.1|host\.lan):(?:\d*8030|8030)/.test(String(baseUrl));
|
|
643
|
+
}
|
|
644
|
+
}
|
|
620
645
|
getV3AgentBaseUrls(preferLocal = false) {
|
|
621
646
|
const configuredApiUrl = String(this.config.get('apiUrl') || 'https://coder.vigthoria.io').replace(/\/$/, '');
|
|
647
|
+
const localTestMode = isLocalTestfarmMode();
|
|
622
648
|
const includeLoopbackV3Agent = process.env.VIGTHORIA_ALLOW_LOCAL_V3_AGENT === '1'
|
|
623
649
|
|| (preferLocal && isServerRuntime());
|
|
624
650
|
const localCandidates = [
|
|
@@ -626,30 +652,32 @@ export class APIClient {
|
|
|
626
652
|
process.env.V3_AGENT_URL,
|
|
627
653
|
...(includeLoopbackV3Agent ? ['http://127.0.0.1:8030'] : []),
|
|
628
654
|
].filter(Boolean).map((url) => String(url).replace(/\/$/, ''));
|
|
629
|
-
const remoteCandidates =
|
|
630
|
-
configuredApiUrl
|
|
631
|
-
'https://coder.vigthoria.io'
|
|
632
|
-
|
|
655
|
+
const remoteCandidates = localTestMode
|
|
656
|
+
? [configuredApiUrl]
|
|
657
|
+
: [configuredApiUrl, 'https://coder.vigthoria.io'];
|
|
658
|
+
const normalizedRemote = remoteCandidates
|
|
659
|
+
.filter(Boolean)
|
|
660
|
+
.map((url) => String(url).replace(/\/$/, ''));
|
|
633
661
|
const urls = preferLocal && localCandidates.length > 0
|
|
634
|
-
? [...localCandidates, ...
|
|
635
|
-
: [...
|
|
662
|
+
? [...localCandidates, ...normalizedRemote]
|
|
663
|
+
: [...normalizedRemote, ...localCandidates];
|
|
636
664
|
return [...new Set(urls)];
|
|
637
665
|
}
|
|
638
666
|
getV3AgentRunUrl(baseUrl) {
|
|
639
|
-
if (
|
|
667
|
+
if (this.isDirectV3AgentBaseUrl(baseUrl)) {
|
|
640
668
|
return `${baseUrl}/api/agent/run`;
|
|
641
669
|
}
|
|
642
670
|
return `${baseUrl}/api/v3-agent/run`;
|
|
643
671
|
}
|
|
644
672
|
getV3AgentContinueUrl(baseUrl) {
|
|
645
|
-
if (
|
|
673
|
+
if (this.isDirectV3AgentBaseUrl(baseUrl)) {
|
|
646
674
|
return `${baseUrl}/api/agent/continue`;
|
|
647
675
|
}
|
|
648
676
|
return `${baseUrl}/api/v3-agent/continue`;
|
|
649
677
|
}
|
|
650
678
|
getV3AgentBackgroundUrl(baseUrl, suffix = '') {
|
|
651
679
|
const cleanSuffix = suffix ? `/${suffix.replace(/^\/+/, '')}` : '';
|
|
652
|
-
if (
|
|
680
|
+
if (this.isDirectV3AgentBaseUrl(baseUrl)) {
|
|
653
681
|
return `${baseUrl}/api/agent/background${cleanSuffix}`;
|
|
654
682
|
}
|
|
655
683
|
return `${baseUrl}/api/v3-agent/background${cleanSuffix}`;
|
|
@@ -670,13 +698,20 @@ export class APIClient {
|
|
|
670
698
|
}
|
|
671
699
|
getMcpBaseUrls() {
|
|
672
700
|
const configuredApiUrl = String(this.config.get('apiUrl') || 'https://coder.vigthoria.io').replace(/\/$/, '');
|
|
673
|
-
const
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
701
|
+
const localTestMode = isLocalTestfarmMode();
|
|
702
|
+
const urls = localTestMode
|
|
703
|
+
? [
|
|
704
|
+
process.env.VIGTHORIA_MCP_URL,
|
|
705
|
+
process.env.MCP_SERVER_URL,
|
|
706
|
+
configuredApiUrl,
|
|
707
|
+
]
|
|
708
|
+
: [
|
|
709
|
+
process.env.VIGTHORIA_MCP_URL,
|
|
710
|
+
process.env.MCP_SERVER_URL,
|
|
711
|
+
'http://127.0.0.1:4008',
|
|
712
|
+
configuredApiUrl,
|
|
713
|
+
];
|
|
714
|
+
return [...new Set(urls.filter(Boolean).map((url) => String(url).replace(/\/$/, '')))];
|
|
680
715
|
}
|
|
681
716
|
getVigFlowBaseUrls() {
|
|
682
717
|
const configuredApiUrl = String(this.config.get('apiUrl') || 'https://coder.vigthoria.io').replace(/\/$/, '');
|
|
@@ -1165,44 +1200,52 @@ export class APIClient {
|
|
|
1165
1200
|
async runV3HealthCheck() {
|
|
1166
1201
|
const endpoints = this.getV3AgentBaseUrls(false);
|
|
1167
1202
|
const headers = await this.getV3AgentHeaders();
|
|
1203
|
+
const failures = [];
|
|
1168
1204
|
for (const baseUrl of endpoints) {
|
|
1169
1205
|
const runUrl = this.getV3AgentRunUrl(baseUrl);
|
|
1170
1206
|
// Use the lightweight GET /health endpoint — no LLM invocation, responds instantly.
|
|
1171
1207
|
// For local 8030 the path is /health directly; for remote it's /api/v3-agent/health via proxy.
|
|
1172
|
-
const healthUrl =
|
|
1208
|
+
const healthUrl = this.isDirectV3AgentBaseUrl(baseUrl)
|
|
1173
1209
|
? `${baseUrl}/health`
|
|
1174
1210
|
: `${baseUrl}/api/v3-agent/health`;
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1211
|
+
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
1212
|
+
try {
|
|
1213
|
+
const response = await fetch(healthUrl, {
|
|
1214
|
+
method: 'GET',
|
|
1215
|
+
headers,
|
|
1216
|
+
signal: AbortSignal.timeout(8000),
|
|
1217
|
+
});
|
|
1218
|
+
if (response.status === 401 || response.status === 403) {
|
|
1219
|
+
return {
|
|
1220
|
+
healthy: false,
|
|
1221
|
+
endpoint: runUrl,
|
|
1222
|
+
error: 'V3 service rejected authentication. Please re-login to refresh your token.',
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
// 200 OK or 405 Method Not Allowed both mean V3 is reachable.
|
|
1226
|
+
if (response.ok || response.status === 405) {
|
|
1227
|
+
return { healthy: true, endpoint: runUrl };
|
|
1228
|
+
}
|
|
1229
|
+
failures.push(`${healthUrl} attempt ${attempt}: HTTP ${response.status}`);
|
|
1230
|
+
}
|
|
1231
|
+
catch (error) {
|
|
1232
|
+
const reason = sanitizeUserFacingErrorText(error?.message || String(error)).slice(0, 160);
|
|
1233
|
+
failures.push(`${healthUrl} attempt ${attempt}: ${reason || 'network error'}`);
|
|
1187
1234
|
}
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
return { healthy: true, endpoint: runUrl };
|
|
1235
|
+
if (attempt < 3) {
|
|
1236
|
+
await new Promise((resolve) => setTimeout(resolve, 1000 * attempt));
|
|
1191
1237
|
}
|
|
1192
1238
|
}
|
|
1193
|
-
catch {
|
|
1194
|
-
continue;
|
|
1195
|
-
}
|
|
1196
1239
|
}
|
|
1197
1240
|
return {
|
|
1198
1241
|
healthy: false,
|
|
1199
1242
|
endpoint: endpoints[0] ? this.getV3AgentRunUrl(endpoints[0]) : 'unknown',
|
|
1200
|
-
error:
|
|
1243
|
+
error: `V3 service is not reachable during startup preflight. Checked ${endpoints.length} endpoint(s). ${failures.slice(-3).join(' | ')}`,
|
|
1201
1244
|
};
|
|
1202
1245
|
}
|
|
1203
1246
|
async runV3AgentAuthPreflight(baseUrl, body, executionContext) {
|
|
1204
1247
|
const endpoint = this.getV3AgentRunUrl(baseUrl);
|
|
1205
|
-
const healthEndpoint =
|
|
1248
|
+
const healthEndpoint = this.isDirectV3AgentBaseUrl(baseUrl)
|
|
1206
1249
|
? `${baseUrl}/health`
|
|
1207
1250
|
: `${baseUrl}/api/v3-agent/health`;
|
|
1208
1251
|
const headers = await this.getV3AgentHeaders();
|
|
@@ -1579,6 +1622,9 @@ export class APIClient {
|
|
|
1579
1622
|
codebaseContext: resolvedContext.codebaseContext || '',
|
|
1580
1623
|
accountBrainContext: resolvedContext.accountBrainContext || '',
|
|
1581
1624
|
projectMemory: resolvedContext.projectMemory || '',
|
|
1625
|
+
clientManifest: buildClientManifest(resolvedContext.clientManifest && typeof resolvedContext.clientManifest === 'object'
|
|
1626
|
+
? resolvedContext.clientManifest
|
|
1627
|
+
: {}),
|
|
1582
1628
|
};
|
|
1583
1629
|
return this.compactV3Context(payload);
|
|
1584
1630
|
}
|
|
@@ -2853,7 +2899,10 @@ menu {
|
|
|
2853
2899
|
}
|
|
2854
2900
|
}
|
|
2855
2901
|
if (!relativePath && raw !== '.' && raw !== '') {
|
|
2856
|
-
|
|
2902
|
+
const treatAsWorkspaceRoot = roots.some((sourceRoot) => isV3WorkspaceRootAliasPath(raw, sourceRoot));
|
|
2903
|
+
if (!treatAsWorkspaceRoot) {
|
|
2904
|
+
return null;
|
|
2905
|
+
}
|
|
2857
2906
|
}
|
|
2858
2907
|
const absolutePath = relativePath ? joinV3WorkspacePath(rootPath, relativePath) : path.resolve(rootPath);
|
|
2859
2908
|
const resolvedRoot = path.resolve(rootPath);
|
|
@@ -3776,6 +3825,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3776
3825
|
else {
|
|
3777
3826
|
chunk = await new Promise((resolve, reject) => {
|
|
3778
3827
|
let idleTimer = null;
|
|
3828
|
+
// Hard backstop for the reschedule branches below: even when the
|
|
3829
|
+
// workspace looks empty (nothing to recover yet) or client tools
|
|
3830
|
+
// are pending, we must not reschedule forever. Cap total
|
|
3831
|
+
// unproductive idle time at 10x the idle window (10 min at the
|
|
3832
|
+
// 60s default) before giving up with a clean timeout instead of
|
|
3833
|
+
// hanging the CLI indefinitely (TestFarm incident 2026-07-04).
|
|
3834
|
+
let idleRescheduleCount = 0;
|
|
3835
|
+
const maxIdleReschedules = 10;
|
|
3779
3836
|
const clearIdleTimer = () => {
|
|
3780
3837
|
if (idleTimer) {
|
|
3781
3838
|
clearTimeout(idleTimer);
|
|
@@ -3785,7 +3842,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3785
3842
|
const scheduleIdleTimer = () => {
|
|
3786
3843
|
clearIdleTimer();
|
|
3787
3844
|
idleTimer = setTimeout(() => {
|
|
3788
|
-
if (this.pendingV3ClientToolTasks.size > 0) {
|
|
3845
|
+
if (this.pendingV3ClientToolTasks.size > 0 && idleRescheduleCount < maxIdleReschedules) {
|
|
3846
|
+
idleRescheduleCount += 1;
|
|
3789
3847
|
scheduleIdleTimer();
|
|
3790
3848
|
return;
|
|
3791
3849
|
}
|
|
@@ -3803,6 +3861,22 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3803
3861
|
reject(stalledError);
|
|
3804
3862
|
return;
|
|
3805
3863
|
}
|
|
3864
|
+
if (idleRescheduleCount >= maxIdleReschedules) {
|
|
3865
|
+
const timeoutError = new Error(`V3 agent stream timed out after ${Math.round((idleTimeoutMs * (maxIdleReschedules + 1)) / 1000)}s with no data ` +
|
|
3866
|
+
'(model likely stopped after a thinking block without emitting a tool call or final answer).');
|
|
3867
|
+
timeoutError.name = 'AbortError';
|
|
3868
|
+
timeoutError.partialData = {
|
|
3869
|
+
task_id: events.find((event) => event && event.task_id)?.task_id || null,
|
|
3870
|
+
context_id: contextId,
|
|
3871
|
+
result: final,
|
|
3872
|
+
events,
|
|
3873
|
+
files: streamedFiles,
|
|
3874
|
+
};
|
|
3875
|
+
clearIdleTimer();
|
|
3876
|
+
reject(timeoutError);
|
|
3877
|
+
return;
|
|
3878
|
+
}
|
|
3879
|
+
idleRescheduleCount += 1;
|
|
3806
3880
|
scheduleIdleTimer();
|
|
3807
3881
|
}, idleTimeoutMs);
|
|
3808
3882
|
};
|
|
@@ -4382,6 +4456,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
4382
4456
|
// ground its analysis in real files rather than returning files_analyzed: 0.
|
|
4383
4457
|
const workspacePath = executionContext.workspacePath || executionContext.projectPath || executionContext.targetPath || process.cwd();
|
|
4384
4458
|
const workspaceSummary = this.buildLocalWorkspaceSummary(workspacePath);
|
|
4459
|
+
const requestedModel = String(executionContext.model || 'code');
|
|
4460
|
+
const resolvedModel = this.resolveModelId(requestedModel);
|
|
4385
4461
|
for (const baseUrl of this.getOperatorBaseUrls()) {
|
|
4386
4462
|
const controller = new AbortController();
|
|
4387
4463
|
const timeoutId = timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : null;
|
|
@@ -4401,6 +4477,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
4401
4477
|
prompt: message,
|
|
4402
4478
|
task: message,
|
|
4403
4479
|
raw_prompt: executionContext.rawPrompt || null,
|
|
4480
|
+
model: requestedModel,
|
|
4481
|
+
preferred_model: resolvedModel,
|
|
4404
4482
|
context_id: executionContext.contextId,
|
|
4405
4483
|
trace_id: executionContext.traceId,
|
|
4406
4484
|
mcp_context_id: executionContext.mcpContextId || null,
|
|
@@ -4408,7 +4486,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
4408
4486
|
workspace: { path: workspacePath },
|
|
4409
4487
|
workspace_path: workspacePath,
|
|
4410
4488
|
workspace_summary: workspaceSummary,
|
|
4411
|
-
model:
|
|
4489
|
+
model: resolvedModel,
|
|
4412
4490
|
history: executionContext.history || [],
|
|
4413
4491
|
executionSurface: executionContext.executionSurface || 'cli',
|
|
4414
4492
|
clientSurface: executionContext.clientSurface || 'cli',
|
|
@@ -5915,7 +5993,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
5915
5993
|
return;
|
|
5916
5994
|
}
|
|
5917
5995
|
const headers = await this.getV3AgentHeaders();
|
|
5918
|
-
const endpoint =
|
|
5996
|
+
const endpoint = this.isDirectV3AgentBaseUrl(baseUrl)
|
|
5919
5997
|
? `${baseUrl}/api/agent/client-tool-result`
|
|
5920
5998
|
: `${baseUrl}/api/v3-agent/client-tool-result`;
|
|
5921
5999
|
const body = JSON.stringify({
|
|
@@ -6132,7 +6210,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
6132
6210
|
const endpoint = process.env.VIGTHORIA_HYPERLOOP_URL || `${configuredApiUrl}/api/hyperloop/health`;
|
|
6133
6211
|
try {
|
|
6134
6212
|
const token = this.getAccessToken();
|
|
6135
|
-
const response = await
|
|
6213
|
+
const response = await fetchWithServiceTimeout(endpoint, {
|
|
6136
6214
|
method: 'GET',
|
|
6137
6215
|
headers: token ? { Authorization: `Bearer ${token}` } : undefined,
|
|
6138
6216
|
});
|
|
@@ -6164,7 +6242,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
6164
6242
|
const token = this.getAccessToken();
|
|
6165
6243
|
const projectPath = this.resolveAgentTargetPath(context);
|
|
6166
6244
|
try {
|
|
6167
|
-
const modulesResponse = await
|
|
6245
|
+
const modulesResponse = await fetchWithServiceTimeout(modulesEndpoint, {
|
|
6168
6246
|
method: 'GET',
|
|
6169
6247
|
headers: {
|
|
6170
6248
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
@@ -6178,7 +6256,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
6178
6256
|
const compactor = modules.find((entry) => entry && entry.name === 'repo_context_compactor');
|
|
6179
6257
|
let compactContextLength = 0;
|
|
6180
6258
|
try {
|
|
6181
|
-
const probeResponse = await
|
|
6259
|
+
const probeResponse = await fetchWithServiceTimeout(endpoint, {
|
|
6182
6260
|
method: 'POST',
|
|
6183
6261
|
headers: {
|
|
6184
6262
|
'Content-Type': 'application/json',
|
|
@@ -6260,6 +6338,33 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
6260
6338
|
const host = process.env.VIGTHORIA_DEVTOOLS_BRIDGE_HOST || '127.0.0.1';
|
|
6261
6339
|
const port = Number.parseInt(process.env.VIGTHORIA_DEVTOOLS_BRIDGE_PORT || '4016', 10);
|
|
6262
6340
|
const endpoint = `ws://${host}:${port}/ws`;
|
|
6341
|
+
const healthEndpoint = `http://${host}:${port}/health`;
|
|
6342
|
+
try {
|
|
6343
|
+
const controller = new AbortController();
|
|
6344
|
+
const timer = setTimeout(() => controller.abort(), 1500);
|
|
6345
|
+
const response = await fetch(healthEndpoint, {
|
|
6346
|
+
signal: controller.signal,
|
|
6347
|
+
headers: { Accept: 'application/json' },
|
|
6348
|
+
});
|
|
6349
|
+
clearTimeout(timer);
|
|
6350
|
+
if (response.ok) {
|
|
6351
|
+
return {
|
|
6352
|
+
name: 'DevTools Bridge',
|
|
6353
|
+
endpoint,
|
|
6354
|
+
ok: true,
|
|
6355
|
+
details: { host, port, healthEndpoint },
|
|
6356
|
+
};
|
|
6357
|
+
}
|
|
6358
|
+
return {
|
|
6359
|
+
name: 'DevTools Bridge',
|
|
6360
|
+
endpoint,
|
|
6361
|
+
ok: false,
|
|
6362
|
+
error: `HTTP ${response.status}`,
|
|
6363
|
+
};
|
|
6364
|
+
}
|
|
6365
|
+
catch {
|
|
6366
|
+
// Fall back to a raw TCP probe for older bridge builds without /health.
|
|
6367
|
+
}
|
|
6263
6368
|
return new Promise((resolve) => {
|
|
6264
6369
|
const socket = net.connect({ host, port, timeout: 1500 }, () => {
|
|
6265
6370
|
socket.end();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type ClientManifestPayload = {
|
|
2
|
+
product_id: string;
|
|
3
|
+
version: string;
|
|
4
|
+
os: string;
|
|
5
|
+
execution_mode: string;
|
|
6
|
+
features_allowed: string[];
|
|
7
|
+
};
|
|
8
|
+
/** Detect iSH/Blink-style iOS terminal sandboxes (override, UA, or Linux-on-iOS release string). */
|
|
9
|
+
export declare function isIOSEnvironment(): boolean;
|
|
10
|
+
/** Map Node process.platform/arch to dispatcher gate OS slug. */
|
|
11
|
+
export declare function resolveClientOsSlug(): string;
|
|
12
|
+
export declare function buildClientManifest(overrides?: Partial<ClientManifestPayload>): ClientManifestPayload;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = path.dirname(__filename);
|
|
7
|
+
const DEFAULT_FEATURES = ['template-instant', 'hyperloop-stream'];
|
|
8
|
+
function readPackageVersion() {
|
|
9
|
+
const candidates = [
|
|
10
|
+
path.join(__dirname, '..', '..', 'package.json'),
|
|
11
|
+
path.join(__dirname, '..', 'package.json'),
|
|
12
|
+
];
|
|
13
|
+
for (const candidate of candidates) {
|
|
14
|
+
try {
|
|
15
|
+
if (!fs.existsSync(candidate))
|
|
16
|
+
continue;
|
|
17
|
+
const parsed = JSON.parse(fs.readFileSync(candidate, 'utf8'));
|
|
18
|
+
if (parsed.version)
|
|
19
|
+
return String(parsed.version);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// try next candidate
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return '0.0.0';
|
|
26
|
+
}
|
|
27
|
+
/** Detect iSH/Blink-style iOS terminal sandboxes (override, UA, or Linux-on-iOS release string). */
|
|
28
|
+
export function isIOSEnvironment() {
|
|
29
|
+
const override = (process.env.VIGTHORIA_OS_OVERRIDE || '').trim().toLowerCase();
|
|
30
|
+
if (override === 'ios')
|
|
31
|
+
return true;
|
|
32
|
+
const userAgent = (process.env.VIGTHORIA_CLIENT_USER_AGENT || '').trim();
|
|
33
|
+
if (/ipad|iphone|ios/i.test(userAgent))
|
|
34
|
+
return true;
|
|
35
|
+
if (process.platform === 'linux') {
|
|
36
|
+
const release = os.release().toLowerCase();
|
|
37
|
+
if (/ipad|iphone|ios/.test(release))
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
/** Map Node process.platform/arch to dispatcher gate OS slug. */
|
|
43
|
+
export function resolveClientOsSlug() {
|
|
44
|
+
if (isIOSEnvironment()) {
|
|
45
|
+
return process.arch === 'arm64' ? 'ios-arm64' : 'ios-x64';
|
|
46
|
+
}
|
|
47
|
+
if (process.platform === 'win32')
|
|
48
|
+
return 'windows-x86_64';
|
|
49
|
+
if (process.platform === 'darwin') {
|
|
50
|
+
return process.arch === 'arm64' ? 'macos-arm64' : 'macos-x86_64';
|
|
51
|
+
}
|
|
52
|
+
if (process.platform === 'linux') {
|
|
53
|
+
return process.arch === 'arm64' ? 'linux-arm64' : 'linux-x86_64';
|
|
54
|
+
}
|
|
55
|
+
return `${process.platform}-${process.arch}`;
|
|
56
|
+
}
|
|
57
|
+
export function buildClientManifest(overrides = {}) {
|
|
58
|
+
const ios = isIOSEnvironment() && !overrides.os;
|
|
59
|
+
return {
|
|
60
|
+
product_id: overrides.product_id || 'vigthoria-cli',
|
|
61
|
+
version: overrides.version || readPackageVersion(),
|
|
62
|
+
os: overrides.os || resolveClientOsSlug(),
|
|
63
|
+
execution_mode: overrides.execution_mode || (ios ? 'mobile-terminal-sandbox' : 'local-user-machine'),
|
|
64
|
+
features_allowed: overrides.features_allowed?.length
|
|
65
|
+
? [...overrides.features_allowed]
|
|
66
|
+
: [...DEFAULT_FEATURES],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Testfarm / Admin Factory local E2E — bypass public coder.vigthoria.io gateway
|
|
3
|
+
* when VIGTHORIA_LOCAL_TEST_MODE=1 or VIGTHORIA_LOCAL_TEST_TOKEN is set.
|
|
4
|
+
*/
|
|
5
|
+
export declare function isLocalTestfarmMode(): boolean;
|
|
6
|
+
/** Apply once at CLI startup — idempotent via ??= */
|
|
7
|
+
export declare function applyLocalTestfarmDefaults(): void;
|
|
8
|
+
export declare function resolveHyperloopTimeoutMs(): number;
|
|
9
|
+
export declare function fetchWithServiceTimeout(url: string, init?: RequestInit): Promise<Response>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Testfarm / Admin Factory local E2E — bypass public coder.vigthoria.io gateway
|
|
3
|
+
* when VIGTHORIA_LOCAL_TEST_MODE=1 or VIGTHORIA_LOCAL_TEST_TOKEN is set.
|
|
4
|
+
*/
|
|
5
|
+
export function isLocalTestfarmMode() {
|
|
6
|
+
const flag = String(process.env.VIGTHORIA_LOCAL_TEST_MODE || '').trim().toLowerCase();
|
|
7
|
+
if (flag === '1' || flag === 'true' || flag === 'yes') {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
return Boolean(String(process.env.VIGTHORIA_LOCAL_TEST_TOKEN || '').trim());
|
|
11
|
+
}
|
|
12
|
+
/** Apply once at CLI startup — idempotent via ??= */
|
|
13
|
+
export function applyLocalTestfarmDefaults() {
|
|
14
|
+
if (!isLocalTestfarmMode()) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
process.env.VIGTHORIA_ALLOW_LOCAL_SERVICES ??= '1';
|
|
18
|
+
process.env.VIGTHORIA_ALLOW_LOCAL_V3_AGENT ??= '1';
|
|
19
|
+
process.env.VIGTHORIA_SELF_HOSTED_MODELS_API_URL ??= 'http://127.0.0.1:4009';
|
|
20
|
+
process.env.VIGTHORIA_HYPERLOOP_URL ??= 'http://127.0.0.1:8020';
|
|
21
|
+
process.env.VIGTHORIA_HYPERLOOP_EXECUTE_URL ??= 'http://127.0.0.1:8020/execute';
|
|
22
|
+
process.env.VIGTHORIA_HYPERLOOP_MODULES_URL ??= 'http://127.0.0.1:8020/modules';
|
|
23
|
+
process.env.VIGTHORIA_V3_AGENT_URL ??= 'http://127.0.0.1:8030';
|
|
24
|
+
process.env.VIGTHORIA_TEMPLATE_SERVICE_URL ??= 'http://127.0.0.1:4011';
|
|
25
|
+
process.env.VIGTHORIA_HYPERLOOP_TIMEOUT_MS ??= '180000';
|
|
26
|
+
const testToken = String(process.env.VIGTHORIA_LOCAL_TEST_TOKEN || '').trim();
|
|
27
|
+
if (testToken && !process.env.VIGTHORIA_AUTH_TOKEN && !process.env.VIGTHORIA_TOKEN) {
|
|
28
|
+
process.env.VIGTHORIA_AUTH_TOKEN = testToken;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function resolveHyperloopTimeoutMs() {
|
|
32
|
+
const raw = process.env.VIGTHORIA_HYPERLOOP_TIMEOUT_MS
|
|
33
|
+
|| process.env.VIGTHORIA_HTTP_TIMEOUT_MS
|
|
34
|
+
|| '120000';
|
|
35
|
+
const parsed = Number.parseInt(String(raw), 10);
|
|
36
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 120000;
|
|
37
|
+
}
|
|
38
|
+
export async function fetchWithServiceTimeout(url, init = {}) {
|
|
39
|
+
const ms = resolveHyperloopTimeoutMs();
|
|
40
|
+
const signal = init.signal ?? AbortSignal.timeout(ms);
|
|
41
|
+
return fetch(url, { ...init, signal });
|
|
42
|
+
}
|
package/dist/utils/session.d.ts
CHANGED
package/dist/utils/session.js
CHANGED
|
@@ -251,15 +251,48 @@ export class SessionManager {
|
|
|
251
251
|
return null;
|
|
252
252
|
}
|
|
253
253
|
}
|
|
254
|
+
normalizeProjectKey(projectPath) {
|
|
255
|
+
const raw = String(projectPath || '').trim();
|
|
256
|
+
if (!raw) {
|
|
257
|
+
return '';
|
|
258
|
+
}
|
|
259
|
+
// Normalize separators and collapse trailing slashes.
|
|
260
|
+
let normalized = raw.replace(/\\/g, '/').replace(/\/+$/g, '');
|
|
261
|
+
// Canonicalize obvious Windows drive prefix casing (C:/... === c:/...).
|
|
262
|
+
const driveMatch = normalized.match(/^([a-zA-Z]):\//);
|
|
263
|
+
if (driveMatch) {
|
|
264
|
+
normalized = `${driveMatch[1].toUpperCase()}:${normalized.slice(2)}`;
|
|
265
|
+
}
|
|
266
|
+
// Windows paths are case-insensitive in practice for this CLI matching purpose.
|
|
267
|
+
if (/^[a-zA-Z]:\//.test(normalized) || normalized.startsWith('//')) {
|
|
268
|
+
return normalized.toLowerCase();
|
|
269
|
+
}
|
|
270
|
+
return normalized;
|
|
271
|
+
}
|
|
254
272
|
/**
|
|
255
273
|
* Get the most recent session for a project
|
|
256
274
|
*/
|
|
257
275
|
getLatest(project) {
|
|
258
276
|
const sessions = this.list();
|
|
259
|
-
const
|
|
260
|
-
|
|
277
|
+
const projectKey = this.normalizeProjectKey(project);
|
|
278
|
+
const projectBase = path.basename(projectKey || '').toLowerCase();
|
|
279
|
+
const exactMatches = sessions
|
|
280
|
+
.filter((s) => this.normalizeProjectKey(s.project) === projectKey)
|
|
261
281
|
.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
|
|
262
|
-
|
|
282
|
+
if (exactMatches.length > 0) {
|
|
283
|
+
return this.load(exactMatches[0].id);
|
|
284
|
+
}
|
|
285
|
+
// Fallback: if old sessions stored inconsistent slash/case forms, allow
|
|
286
|
+
// latest same-folder match (e.g., C:\Proj and C:/proj) to preserve /retry UX.
|
|
287
|
+
if (projectBase) {
|
|
288
|
+
const softMatches = sessions
|
|
289
|
+
.filter((s) => path.basename(this.normalizeProjectKey(s.project) || '').toLowerCase() === projectBase)
|
|
290
|
+
.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
|
|
291
|
+
if (softMatches.length > 0) {
|
|
292
|
+
return this.load(softMatches[0].id);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return null;
|
|
263
296
|
}
|
|
264
297
|
/**
|
|
265
298
|
* List all sessions (metadata only) — corrupt or unreadable files are
|
|
@@ -170,6 +170,24 @@ export async function runTemplateInstantPath(api, vision, workspacePath, options
|
|
|
170
170
|
const target = path.join(workspacePath, entryPath);
|
|
171
171
|
const cleanVision = stripExecutionShaping(vision);
|
|
172
172
|
const preferMinimal = options.forceMinimalFallback === true;
|
|
173
|
+
const keepScaffold = String(process.env.VIGTHORIA_EXPRESS_KEEP_SCAFFOLD || process.env.CLI_EXPRESS_KEEP_SCAFFOLD || '').trim().toLowerCase();
|
|
174
|
+
if (['1', 'true', 'yes'].includes(keepScaffold)) {
|
|
175
|
+
try {
|
|
176
|
+
const stat = fs.statSync(target);
|
|
177
|
+
if (stat.isFile() && stat.size >= 8000) {
|
|
178
|
+
return {
|
|
179
|
+
ok: true,
|
|
180
|
+
entryPath,
|
|
181
|
+
templateName: 'express-preserved-scaffold',
|
|
182
|
+
confidence: 1,
|
|
183
|
+
processingMs: 0,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// fall through to normal template match
|
|
189
|
+
}
|
|
190
|
+
}
|
|
173
191
|
let html;
|
|
174
192
|
let usedFallback = false;
|
|
175
193
|
let match = {};
|
package/dist/utils/tools.js
CHANGED
|
@@ -2549,6 +2549,21 @@ export class AgenticTools {
|
|
|
2549
2549
|
if (!candidate) {
|
|
2550
2550
|
return candidate;
|
|
2551
2551
|
}
|
|
2552
|
+
const redactedWorkspaceAliases = [
|
|
2553
|
+
'www/[internal]/',
|
|
2554
|
+
'var/www/[internal]/',
|
|
2555
|
+
'/var/www/[internal]/',
|
|
2556
|
+
'[internal]/',
|
|
2557
|
+
];
|
|
2558
|
+
for (const alias of redactedWorkspaceAliases) {
|
|
2559
|
+
if (candidate.startsWith(alias)) {
|
|
2560
|
+
candidate = candidate.slice(alias.length);
|
|
2561
|
+
break;
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
if (candidate === 'www/[internal]' || candidate === 'var/www/[internal]' || candidate === '/var/www/[internal]' || candidate === '[internal]') {
|
|
2565
|
+
return '';
|
|
2566
|
+
}
|
|
2552
2567
|
const normalizedRootNoSlash = workspaceRootPosix.replace(/^\//, '');
|
|
2553
2568
|
const prefixes = [
|
|
2554
2569
|
`${workspaceRootPosix}/`,
|
|
@@ -2568,13 +2583,14 @@ export class AgenticTools {
|
|
|
2568
2583
|
}
|
|
2569
2584
|
return candidate.replace(/^\//, '');
|
|
2570
2585
|
};
|
|
2571
|
-
// Resolve the full path
|
|
2586
|
+
// Resolve the full path after removing workspace aliases emitted by remote models.
|
|
2587
|
+
const safeInput = stripWorkspacePrefix(normalizedInput);
|
|
2572
2588
|
let resolvedPath;
|
|
2573
|
-
if (path.isAbsolute(
|
|
2574
|
-
resolvedPath = path.normalize(
|
|
2589
|
+
if (path.isAbsolute(safeInput)) {
|
|
2590
|
+
resolvedPath = path.normalize(safeInput);
|
|
2575
2591
|
}
|
|
2576
2592
|
else {
|
|
2577
|
-
resolvedPath = path.normalize(path.join(this.cwd,
|
|
2593
|
+
resolvedPath = path.normalize(path.join(this.cwd, safeInput));
|
|
2578
2594
|
}
|
|
2579
2595
|
// SECURITY CHECK: Ensure path is within workspace
|
|
2580
2596
|
if (!resolvedPath.startsWith(workspaceRoot + path.sep) && resolvedPath !== workspaceRoot) {
|
|
@@ -5,3 +5,5 @@
|
|
|
5
5
|
export declare function normalizeV3WorkspaceRelativePath(rawPath: string, rootPath?: string): string;
|
|
6
6
|
/** Join workspace root + relative path without path.resolve URI corruption on Windows. */
|
|
7
7
|
export declare function joinV3WorkspacePath(rootPath: string, relativePath: string): string;
|
|
8
|
+
/** True when rawPath points to workspace root via aliases or scrubbed placeholders. */
|
|
9
|
+
export declare function isV3WorkspaceRootAliasPath(rawPath: string, rootPath?: string): boolean;
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
+
const WORKSPACE_PLACEHOLDER_SEGMENT = /\[(?:internal|Vigthoria service|Vigthoria Agent|Vigthoria storage|temp|home|workspace)\]/ig;
|
|
3
|
+
function stripInlineWorkspacePlaceholders(value) {
|
|
4
|
+
// Only strip placeholders when they were appended to a segment (e.g. pitfall[Vigthoria service]).
|
|
5
|
+
// Keep canonical scrubbed segments like /[internal]/ intact for dedicated mapping logic.
|
|
6
|
+
return String(value || '')
|
|
7
|
+
.replace(/(^|[^/])\[(?:internal|Vigthoria service|Vigthoria Agent|Vigthoria storage|temp|home|workspace)\](?=(?:\/|$))/ig, '$1')
|
|
8
|
+
.replace(/\/+$/g, '');
|
|
9
|
+
}
|
|
2
10
|
/**
|
|
3
11
|
* Normalize V3 SSE / tool paths to a safe workspace-relative POSIX path.
|
|
4
12
|
* Decodes vigthoria:// boundary URIs from server scrubbing before local writes.
|
|
@@ -8,6 +16,7 @@ export function normalizeV3WorkspaceRelativePath(rawPath, rootPath) {
|
|
|
8
16
|
if (!input) {
|
|
9
17
|
return '';
|
|
10
18
|
}
|
|
19
|
+
input = stripInlineWorkspacePlaceholders(input);
|
|
11
20
|
const safeRelative = (candidate) => {
|
|
12
21
|
const stripped = String(candidate || '').replace(/^\/+/, '');
|
|
13
22
|
if (!stripped) {
|
|
@@ -43,7 +52,7 @@ export function normalizeV3WorkspaceRelativePath(rawPath, rootPath) {
|
|
|
43
52
|
return null;
|
|
44
53
|
};
|
|
45
54
|
const stripScrubbedPlaceholder = (value) => {
|
|
46
|
-
let candidate = String(value || '').replace(/^\/+/, '');
|
|
55
|
+
let candidate = stripInlineWorkspacePlaceholders(String(value || '')).replace(/^\/+/, '');
|
|
47
56
|
// Hybrid paths models echo back after SSE scrubbing, e.g. C:/var/www/[internal]/game.js
|
|
48
57
|
const hybridWin = candidate.match(/^[a-zA-Z]:\/(?:var\/)?www\/\[internal\](?:\/(.*))?$/i);
|
|
49
58
|
if (hybridWin) {
|
|
@@ -70,7 +79,7 @@ export function normalizeV3WorkspaceRelativePath(rawPath, rootPath) {
|
|
|
70
79
|
return safeRelative(match[groupIndex] || '');
|
|
71
80
|
}
|
|
72
81
|
}
|
|
73
|
-
const bracketSplit = candidate.split(
|
|
82
|
+
const bracketSplit = candidate.split(WORKSPACE_PLACEHOLDER_SEGMENT);
|
|
74
83
|
if (bracketSplit.length > 1) {
|
|
75
84
|
const tail = bracketSplit[bracketSplit.length - 1].replace(/^\/+/, '');
|
|
76
85
|
return safeRelative(tail);
|
|
@@ -152,3 +161,34 @@ export function joinV3WorkspacePath(rootPath, relativePath) {
|
|
|
152
161
|
const segments = relativePath.split('/').filter((segment) => segment && segment !== '.');
|
|
153
162
|
return path.join(rootPath, ...segments);
|
|
154
163
|
}
|
|
164
|
+
/** True when rawPath points to workspace root via aliases or scrubbed placeholders. */
|
|
165
|
+
export function isV3WorkspaceRootAliasPath(rawPath, rootPath) {
|
|
166
|
+
const original = String(rawPath || '').trim();
|
|
167
|
+
if (!original || original === '.') {
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
const sanitized = stripInlineWorkspacePlaceholders(original).replace(/\\/g, '/').trim();
|
|
171
|
+
if (!sanitized || sanitized === '.' || sanitized === '/') {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
const candidate = sanitized.replace(/^\/+/, '');
|
|
175
|
+
if (!candidate) {
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
const lowered = candidate.toLowerCase();
|
|
179
|
+
if (lowered === 'workspace'
|
|
180
|
+
|| lowered === '[workspace]'
|
|
181
|
+
|| lowered === '[internal]'
|
|
182
|
+
|| lowered === 'www/[internal]'
|
|
183
|
+
|| lowered === 'var/www/[internal]'
|
|
184
|
+
|| lowered === '[vigthoria service]'
|
|
185
|
+
|| lowered === 'var/www/[vigthoria service]') {
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
const normalizedRoot = String(rootPath || '').trim().replace(/\\/g, '/').replace(/\/+$/g, '');
|
|
189
|
+
if (!normalizedRoot) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
const normalizedInput = sanitized.replace(/\/+$/g, '');
|
|
193
|
+
return normalizedInput.toLowerCase() === normalizedRoot.toLowerCase();
|
|
194
|
+
}
|
package/install.ps1
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
$ErrorActionPreference = "Stop"
|
|
6
6
|
|
|
7
7
|
# Configuration
|
|
8
|
-
$CLI_VERSION = "1.11.
|
|
8
|
+
$CLI_VERSION = "1.11.42"
|
|
9
9
|
$INSTALL_DIR = "$env:USERPROFILE\.vigthoria"
|
|
10
10
|
$NPM_PACKAGE = "vigthoria-cli"
|
|
11
11
|
$GIT_PACKAGE_URL = "git+https://market.vigthoria.io/vigthoria/vigthoria-cli.git"
|
package/install.sh
CHANGED
|
@@ -26,7 +26,7 @@ else
|
|
|
26
26
|
fi
|
|
27
27
|
|
|
28
28
|
# Configuration
|
|
29
|
-
CLI_VERSION="1.11.
|
|
29
|
+
CLI_VERSION="1.11.42"
|
|
30
30
|
INSTALL_DIR="$HOME/.vigthoria"
|
|
31
31
|
REPO_URL="https://market.vigthoria.io/vigthoria/vigthoria-cli"
|
|
32
32
|
GIT_PACKAGE_URL="git+https://market.vigthoria.io/vigthoria/vigthoria-cli.git"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vigthoria-cli",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.43",
|
|
4
4
|
"description": "Vigthoria Coder CLI - AI-powered terminal coding assistant",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
"test:legion:billing:e2e": "npm run build && node scripts/test-legion-godmode-billing-e2e.js",
|
|
71
71
|
"test:windows:v3-sync": "npm run build && node scripts/test-windows-v3-sync-recovery.js",
|
|
72
72
|
"test:v3-workspace-path": "npm run build && node scripts/test-v3-workspace-path.js",
|
|
73
|
+
"test:session:project-match": "npm run build && node scripts/test-session-project-match.mjs",
|
|
73
74
|
"test:v3-stream-mutation": "npm run build && node scripts/test-v3-stream-mutation.js",
|
|
74
75
|
"test:context:budget": "npm run build && node scripts/test-context-budget.js",
|
|
75
76
|
"test:pitfall:context": "npm run build && node scripts/test-pitfall-context-smoke.js"
|
|
@@ -122,7 +122,10 @@ PY
|
|
|
122
122
|
echo "[7.5 policy] secured hyperloop endpoint"
|
|
123
123
|
code=$(curl -s -o /dev/null -w "%{http_code}" https://coder.vigthoria.io/api/hyperloop/health)
|
|
124
124
|
echo "unauth_hyperloop_code=$code"
|
|
125
|
-
|
|
125
|
+
# 401 = correct JSON auth error for API callers. 302 (redirect to /login) is
|
|
126
|
+
# also acceptable defense-in-depth: it still requires auth, just via the
|
|
127
|
+
# browser-session redirect path some legacy middleware uses.
|
|
128
|
+
[[ "$code" == "401" || "$code" == "302" ]]
|
|
126
129
|
|
|
127
130
|
echo "[8.2] runtime symlink guard signals"
|
|
128
131
|
rg -n "realpathSync|results.length >= maxFiles|pattern = kw.length <= 4" dist/utils/context-ranker.js >/tmp/vig-ranker-signals.txt
|