vigthoria-cli 1.8.15 → 1.9.2
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 +2 -6
- package/dist/commands/auth.d.ts +49 -21
- package/dist/commands/auth.js +385 -343
- package/dist/commands/chat.d.ts +10 -2
- package/dist/commands/chat.js +328 -93
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +40 -20
- package/dist/commands/index.d.ts +12 -0
- package/dist/commands/index.js +182 -0
- package/dist/commands/legion.d.ts +39 -0
- package/dist/commands/legion.js +999 -71
- package/dist/index.d.ts +3 -1
- package/dist/index.js +506 -28
- package/dist/utils/api.d.ts +74 -18
- package/dist/utils/api.js +701 -805
- package/dist/utils/config.js +9 -10
- package/dist/utils/context-ranker.d.ts +24 -0
- package/dist/utils/context-ranker.js +147 -0
- package/dist/utils/post-write-validator.d.ts +25 -0
- package/dist/utils/post-write-validator.js +138 -0
- package/dist/utils/session.d.ts +19 -0
- package/dist/utils/session.js +91 -6
- package/dist/utils/task-display.d.ts +31 -0
- package/dist/utils/task-display.js +115 -0
- package/dist/utils/tools.d.ts +15 -0
- package/dist/utils/tools.js +341 -58
- package/dist/utils/workspace-cache.d.ts +31 -0
- package/dist/utils/workspace-cache.js +96 -0
- package/package.json +7 -3
package/dist/commands/legion.js
CHANGED
|
@@ -9,12 +9,48 @@
|
|
|
9
9
|
* vigthoria legion --workers List available Legion workers
|
|
10
10
|
* vigthoria legion --status Show Legion infrastructure status
|
|
11
11
|
*/
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
+
}) : function(o, v) {
|
|
26
|
+
o["default"] = v;
|
|
27
|
+
});
|
|
28
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
29
|
+
var ownKeys = function(o) {
|
|
30
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
31
|
+
var ar = [];
|
|
32
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
33
|
+
return ar;
|
|
34
|
+
};
|
|
35
|
+
return ownKeys(o);
|
|
36
|
+
};
|
|
37
|
+
return function (mod) {
|
|
38
|
+
if (mod && mod.__esModule) return mod;
|
|
39
|
+
var result = {};
|
|
40
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
41
|
+
__setModuleDefault(result, mod);
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
})();
|
|
12
45
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
13
46
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
14
47
|
};
|
|
15
48
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
49
|
exports.LegionCommand = void 0;
|
|
17
50
|
const chalk_1 = __importDefault(require("chalk"));
|
|
51
|
+
const fs = __importStar(require("fs"));
|
|
52
|
+
const path = __importStar(require("path"));
|
|
53
|
+
const readline = __importStar(require("readline/promises"));
|
|
18
54
|
const logger_js_1 = require("../utils/logger.js");
|
|
19
55
|
const api_js_1 = require("../utils/api.js");
|
|
20
56
|
// Hyper Loop / Legion runs on the Vigthoria backend only. Local user installs
|
|
@@ -42,8 +78,30 @@ class LegionCommand {
|
|
|
42
78
|
this.config = config;
|
|
43
79
|
this.logger = logger;
|
|
44
80
|
}
|
|
81
|
+
getHyperloopUrls() {
|
|
82
|
+
const urls = new Set();
|
|
83
|
+
const configuredApiUrl = String(this.config.get('apiUrl') || '').trim().replace(/\/$/, '');
|
|
84
|
+
if (configuredApiUrl) {
|
|
85
|
+
urls.add(`${configuredApiUrl}/api/hyperloop`);
|
|
86
|
+
}
|
|
87
|
+
const envUrl = String(process.env.VIGTHORIA_HYPERLOOP_URL || '').trim().replace(/\/$/, '');
|
|
88
|
+
if (envUrl) {
|
|
89
|
+
urls.add(envUrl);
|
|
90
|
+
}
|
|
91
|
+
if ((0, api_js_1.isServerRuntime)()) {
|
|
92
|
+
for (const internal of buildServerHyperloopUrls()) {
|
|
93
|
+
urls.add(internal.replace(/\/$/, ''));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return Array.from(urls);
|
|
97
|
+
}
|
|
45
98
|
getHeaders() {
|
|
46
99
|
const headers = { 'Content-Type': 'application/json' };
|
|
100
|
+
const serviceKey = this.getLegionServiceKey();
|
|
101
|
+
if (serviceKey) {
|
|
102
|
+
headers['X-Service-Key'] = serviceKey;
|
|
103
|
+
return headers;
|
|
104
|
+
}
|
|
47
105
|
const token = this.config.get('authToken');
|
|
48
106
|
if (token) {
|
|
49
107
|
headers['Authorization'] = `Bearer ${token}`;
|
|
@@ -51,7 +109,46 @@ class LegionCommand {
|
|
|
51
109
|
}
|
|
52
110
|
return headers;
|
|
53
111
|
}
|
|
112
|
+
async readJsonResponse(response, context) {
|
|
113
|
+
try {
|
|
114
|
+
return await response.json();
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
(0, api_js_1.propagateError)({
|
|
118
|
+
...((err && typeof err === 'object') ? err : { message: String(err) }),
|
|
119
|
+
message: `${context} returned invalid JSON: ${err?.message || String(err)}`,
|
|
120
|
+
statusCode: response.status,
|
|
121
|
+
commandName: 'legion',
|
|
122
|
+
endpoint: response.url || context,
|
|
123
|
+
details: {
|
|
124
|
+
...((err?.details && typeof err.details === 'object') ? err.details : {}),
|
|
125
|
+
command: 'legion',
|
|
126
|
+
endpoint: response.url || context,
|
|
127
|
+
context,
|
|
128
|
+
status: response.status,
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
propagateLegionApiError(context, endpoint, err) {
|
|
134
|
+
const original = err && typeof err === 'object' ? err : { message: String(err) };
|
|
135
|
+
(0, api_js_1.propagateError)({
|
|
136
|
+
...original,
|
|
137
|
+
commandName: 'legion',
|
|
138
|
+
endpoint: original.endpoint || original?.config?.url || original?.details?.endpoint || endpoint || context,
|
|
139
|
+
details: {
|
|
140
|
+
...(original.details && typeof original.details === 'object' ? original.details : {}),
|
|
141
|
+
command: 'legion',
|
|
142
|
+
endpoint: original.endpoint || original?.config?.url || original?.details?.endpoint || endpoint || context,
|
|
143
|
+
context,
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
}
|
|
54
147
|
async run(request, options) {
|
|
148
|
+
if (options.godmode) {
|
|
149
|
+
await this.runGodmode(request, options);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
55
152
|
if (options.workers) {
|
|
56
153
|
await this.showWorkers();
|
|
57
154
|
return;
|
|
@@ -64,99 +161,922 @@ class LegionCommand {
|
|
|
64
161
|
console.log(chalk_1.default.yellow('Usage: vigthoria legion "<task description>"'));
|
|
65
162
|
console.log(chalk_1.default.gray(' --workers List available Legion workers'));
|
|
66
163
|
console.log(chalk_1.default.gray(' --status Show Legion infrastructure status'));
|
|
164
|
+
console.log(chalk_1.default.gray(' --godmode Run calculator + high-intelligence orchestration'));
|
|
67
165
|
return;
|
|
68
166
|
}
|
|
69
167
|
await this.planAndExecute(request, options);
|
|
70
168
|
}
|
|
71
|
-
async
|
|
72
|
-
|
|
73
|
-
|
|
169
|
+
async runGodmode(request, options) {
|
|
170
|
+
if (!request) {
|
|
171
|
+
console.log(chalk_1.default.yellow('Usage: vigthoria legion --godmode "<task description>"'));
|
|
172
|
+
console.log(chalk_1.default.gray(' --plan-only Run calculator only (no execution)'));
|
|
173
|
+
console.log(chalk_1.default.gray(' --approve Skip confirmation prompt and execute'));
|
|
174
|
+
console.log(chalk_1.default.gray(' --auto-charge Attempt direct VigCoin top-up when balance is low'));
|
|
175
|
+
console.log(chalk_1.default.gray(' --timeout <sec> Abort remote execution if no result within timeout (default: 120)'));
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const workspace = options.project || process.cwd();
|
|
179
|
+
const scan = this.scanProject(workspace);
|
|
180
|
+
const selectedModels = this.resolveModelProfiles(options.models);
|
|
181
|
+
const quote = this.buildRoleQuote(scan, selectedModels);
|
|
182
|
+
const billingQuote = this.buildBillingQuote(quote);
|
|
183
|
+
let billingGate = await this.evaluateBillingGate(billingQuote);
|
|
184
|
+
this.printGodmodeQuote(workspace, scan, quote, billingQuote, billingGate);
|
|
185
|
+
if (options.planOnly) {
|
|
186
|
+
console.log(chalk_1.default.green('Godmode calculator complete (plan-only).'));
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (!billingGate.canProceed) {
|
|
190
|
+
const resolved = await this.resolveBillingInsufficientFunds(billingQuote, billingGate, options);
|
|
191
|
+
if (!resolved) {
|
|
192
|
+
console.log(chalk_1.default.yellow('Godmode cancelled due to insufficient VigCoin balance.'));
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
billingGate = await this.evaluateBillingGate(billingQuote);
|
|
196
|
+
if (!billingGate.canProceed) {
|
|
197
|
+
this.printBillingGateSummary(billingQuote, billingGate);
|
|
198
|
+
console.log(chalk_1.default.red('Billing gate still blocked after charge attempt.'));
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const autoApprove = options.approve === true && options.noApprove !== true;
|
|
203
|
+
const approved = autoApprove ? true : await this.confirmExecution();
|
|
204
|
+
if (!approved) {
|
|
205
|
+
console.log(chalk_1.default.yellow('Godmode cancelled by user.'));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const charged = await this.collectExecutionCharge(billingQuote, billingGate);
|
|
209
|
+
if (!charged) {
|
|
210
|
+
console.log(chalk_1.default.yellow('Godmode cancelled because wallet charge was not completed.'));
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const enrichedRequest = [
|
|
214
|
+
'[GODMODE EXECUTION]',
|
|
74
215
|
request,
|
|
75
|
-
|
|
76
|
-
|
|
216
|
+
`Workspace: ${workspace}`,
|
|
217
|
+
`PhaseA: files=${scan.files}, lines=${scan.lines}, import_edges=${scan.importEdges}`,
|
|
218
|
+
'Role model assignment:',
|
|
219
|
+
...quote.map((q) => `- ${q.role}: ${q.model} (requested_model=${q.requestedModel})`),
|
|
220
|
+
`Billing: plan=${billingGate.plan}, master_admin_free=${billingGate.masterAdminFree ? 'true' : 'false'}, vigcoin_required=${billingQuote.vigcoinRequired.toFixed(3)}, vigcoin_rate_usd=${billingQuote.vigcoinRateUsd.toFixed(4)}, total_usd=${billingQuote.finalUsd.toFixed(4)}`,
|
|
221
|
+
'Required flow: Detective repro -> 6-role parallel attack -> Architect synthesis -> test loop until pass.',
|
|
222
|
+
].join('\n');
|
|
223
|
+
await this.planAndExecute(enrichedRequest, options, {
|
|
224
|
+
workspace,
|
|
225
|
+
originalRequest: request,
|
|
226
|
+
scan,
|
|
227
|
+
quote,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
scanProject(workspace) {
|
|
231
|
+
const files = [];
|
|
232
|
+
const stack = [workspace];
|
|
233
|
+
const ignored = new Set(['.git', 'node_modules', 'dist', 'build', 'coverage', '.next', '__pycache__', '.venv']);
|
|
234
|
+
const exts = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.py', '.go', '.rs', '.java']);
|
|
235
|
+
while (stack.length > 0 && files.length < 4000) {
|
|
236
|
+
const dir = stack.pop();
|
|
237
|
+
if (!dir)
|
|
238
|
+
continue;
|
|
239
|
+
let entries = [];
|
|
240
|
+
try {
|
|
241
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
for (const e of entries) {
|
|
247
|
+
if (ignored.has(e.name))
|
|
248
|
+
continue;
|
|
249
|
+
const full = path.join(dir, e.name);
|
|
250
|
+
if (e.isDirectory()) {
|
|
251
|
+
stack.push(full);
|
|
252
|
+
}
|
|
253
|
+
else if (e.isFile() && exts.has(path.extname(e.name).toLowerCase())) {
|
|
254
|
+
files.push(full);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
let lines = 0;
|
|
259
|
+
let importEdges = 0;
|
|
260
|
+
const topFiles = [];
|
|
261
|
+
for (const file of files) {
|
|
262
|
+
try {
|
|
263
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
264
|
+
const fileLines = content.split('\n').length;
|
|
265
|
+
const imports = (content.match(/^\s*(import\s+.+from\s+['"].+['"]|from\s+.+\s+import\s+.+|require\(.+\))/gm) || []).length;
|
|
266
|
+
lines += fileLines;
|
|
267
|
+
importEdges += imports;
|
|
268
|
+
topFiles.push({ file: path.relative(workspace, file).replace(/\\/g, '/'), lines: fileLines, imports });
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
topFiles.sort((a, b) => (b.lines + b.imports * 25) - (a.lines + a.imports * 25));
|
|
275
|
+
return { files: files.length, lines, importEdges, topFiles: topFiles.slice(0, 8) };
|
|
276
|
+
}
|
|
277
|
+
resolveModelProfiles(modelsArg) {
|
|
278
|
+
const catalog = [
|
|
279
|
+
{ id: 'openrouter:openai/gpt-5.5', requestedModel: 'gpt-5.5', provider: 'openrouter', estInputPer1M: 5.0, estOutputPer1M: 30.0, capability: { reasoning: 10, coding: 10, security: 9, speed: 6, synthesis: 10 } },
|
|
280
|
+
{ id: 'openrouter:anthropic/claude-opus-4.7', requestedModel: 'opus-4.7', provider: 'openrouter', estInputPer1M: 5.0, estOutputPer1M: 25.0, capability: { reasoning: 10, coding: 9, security: 10, speed: 6, synthesis: 10 } },
|
|
281
|
+
{ id: 'openrouter:openai/o3', requestedModel: 'o3', provider: 'openrouter', estInputPer1M: 2.0, estOutputPer1M: 8.0, capability: { reasoning: 10, coding: 9, security: 9, speed: 6, synthesis: 9 } },
|
|
282
|
+
{ id: 'openrouter:google/gemini-2.5-pro', requestedModel: 'gemini-2.5-pro', provider: 'openrouter', estInputPer1M: 1.25, estOutputPer1M: 10.0, capability: { reasoning: 9, coding: 9, security: 8, speed: 8, synthesis: 9 } },
|
|
283
|
+
{ id: 'openrouter:deepseek/deepseek-v4-pro', requestedModel: 'deepseek-v4-pro', provider: 'openrouter', estInputPer1M: 0.435, estOutputPer1M: 0.87, capability: { reasoning: 8, coding: 9, security: 8, speed: 10, synthesis: 8 } },
|
|
284
|
+
{ id: 'openrouter:moonshotai/kimi-k2.5', requestedModel: 'kimi-k2.5', provider: 'openrouter', estInputPer1M: 0.44, estOutputPer1M: 2.0, capability: { reasoning: 9, coding: 9, security: 8, speed: 8, synthesis: 9 } },
|
|
285
|
+
{ id: 'openrouter:deepseek/deepseek-r1', requestedModel: 'cloud-reason', provider: 'openrouter', estInputPer1M: 0.7, estOutputPer1M: 2.5, capability: { reasoning: 9, coding: 8, security: 8, speed: 7, synthesis: 8 } },
|
|
286
|
+
{ id: 'openrouter:deepseek/deepseek-chat', requestedModel: 'cloud-pro', provider: 'openrouter', estInputPer1M: 0.32, estOutputPer1M: 0.89, capability: { reasoning: 7, coding: 8, security: 7, speed: 10, synthesis: 7 } },
|
|
287
|
+
];
|
|
288
|
+
if (!modelsArg || !modelsArg.trim())
|
|
289
|
+
return catalog;
|
|
290
|
+
const allow = new Set(modelsArg.split(',').map((m) => m.trim()).filter(Boolean));
|
|
291
|
+
const filtered = catalog.filter((m) => (allow.has(m.id)
|
|
292
|
+
|| allow.has(m.id.replace('openrouter:', ''))
|
|
293
|
+
|| allow.has(m.requestedModel)));
|
|
294
|
+
return filtered.length > 0 ? filtered : catalog;
|
|
295
|
+
}
|
|
296
|
+
buildRoleQuote(scan, models) {
|
|
297
|
+
const roleWeights = {
|
|
298
|
+
detective: { reasoning: 10, coding: 8, security: 6, speed: 3, synthesis: 7 },
|
|
299
|
+
logic: { reasoning: 8, coding: 10, security: 3, speed: 6, synthesis: 6 },
|
|
300
|
+
security: { reasoning: 7, coding: 7, security: 10, speed: 5, synthesis: 6 },
|
|
301
|
+
performance: { reasoning: 9, coding: 9, security: 7, speed: 5, synthesis: 7 },
|
|
302
|
+
edge_case: { reasoning: 8, coding: 7, security: 7, speed: 7, synthesis: 5 },
|
|
303
|
+
integration: { reasoning: 8, coding: 8, security: 6, speed: 5, synthesis: 8 },
|
|
304
|
+
reviewer: { reasoning: 9, coding: 7, security: 7, speed: 6, synthesis: 8 },
|
|
305
|
+
architect: { reasoning: 10, coding: 9, security: 8, speed: 4, synthesis: 10 },
|
|
306
|
+
};
|
|
307
|
+
const complexity = Math.max(1, Math.ceil((scan.lines / 4000) + (scan.importEdges / 200)));
|
|
308
|
+
return Object.keys(roleWeights).map((role) => {
|
|
309
|
+
const w = roleWeights[role];
|
|
310
|
+
const preferredByRole = {
|
|
311
|
+
detective: 'gpt-5.5',
|
|
312
|
+
logic: 'o3',
|
|
313
|
+
security: 'opus-4.7',
|
|
314
|
+
performance: 'deepseek-v4-pro',
|
|
315
|
+
edge_case: 'gemini-2.5-pro',
|
|
316
|
+
integration: 'kimi-k2.5',
|
|
317
|
+
reviewer: 'opus-4.7',
|
|
318
|
+
architect: 'gpt-5.5',
|
|
319
|
+
};
|
|
320
|
+
let best = models.find((m) => m.requestedModel === preferredByRole[role]) || models[0];
|
|
321
|
+
if (!best)
|
|
322
|
+
best = models[0];
|
|
323
|
+
// Fallback scoring when preferred model is filtered out by --models.
|
|
324
|
+
if (!models.some((m) => m.requestedModel === preferredByRole[role])) {
|
|
325
|
+
let bestScore = -1;
|
|
326
|
+
for (const m of models) {
|
|
327
|
+
const score = m.capability.reasoning * w.reasoning + m.capability.coding * w.coding + m.capability.security * w.security + m.capability.speed * w.speed + m.capability.synthesis * w.synthesis;
|
|
328
|
+
const costPenalty = (m.estInputPer1M + m.estOutputPer1M) * 0.1;
|
|
329
|
+
const finalScore = score - costPenalty;
|
|
330
|
+
if (finalScore > bestScore) {
|
|
331
|
+
best = m;
|
|
332
|
+
bestScore = finalScore;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
const estInputTokens = 1200 * complexity;
|
|
337
|
+
const estOutputTokens = 1800 * complexity;
|
|
338
|
+
const estCostUsd = (estInputTokens / 1_000_000) * best.estInputPer1M + (estOutputTokens / 1_000_000) * best.estOutputPer1M;
|
|
339
|
+
return { role, model: best.id, requestedModel: best.requestedModel, estInputTokens, estOutputTokens, estCostUsd };
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
buildGodmodeExplicitSteps(execution) {
|
|
343
|
+
const { originalRequest, workspace, scan, quote } = execution;
|
|
344
|
+
const quoteByRole = new Map(quote.map((row) => [row.role, row]));
|
|
345
|
+
const topFiles = scan.topFiles.slice(0, 5).map((file) => `${file.file} (${file.lines} lines, ${file.imports} imports)`);
|
|
346
|
+
const roleSequence = [
|
|
347
|
+
{ role: 'detective', dependsOn: [] },
|
|
348
|
+
{ role: 'logic', dependsOn: ['detective'] },
|
|
349
|
+
{ role: 'security', dependsOn: ['detective'] },
|
|
350
|
+
{ role: 'performance', dependsOn: ['detective'] },
|
|
351
|
+
{ role: 'edge_case', dependsOn: ['detective'] },
|
|
352
|
+
{ role: 'integration', dependsOn: ['detective'] },
|
|
353
|
+
{ role: 'reviewer', dependsOn: ['detective'] },
|
|
354
|
+
{ role: 'architect', dependsOn: ['logic', 'security', 'performance', 'edge_case', 'integration', 'reviewer'] },
|
|
355
|
+
];
|
|
356
|
+
const roleIterationBudget = {
|
|
357
|
+
detective: 4,
|
|
358
|
+
logic: 5,
|
|
359
|
+
security: 5,
|
|
360
|
+
performance: 4,
|
|
361
|
+
edge_case: 4,
|
|
362
|
+
integration: 5,
|
|
363
|
+
reviewer: 5,
|
|
364
|
+
architect: 5,
|
|
365
|
+
};
|
|
366
|
+
const steps = roleSequence.map(({ role, dependsOn }) => {
|
|
367
|
+
const row = quoteByRole.get(role);
|
|
368
|
+
const requestedModel = row?.requestedModel || 'cloud-pro';
|
|
369
|
+
const model = row?.model || 'openrouter:deepseek/deepseek-chat';
|
|
370
|
+
const roleObjective = [
|
|
371
|
+
`[GODMODE:${role.toUpperCase()}]`,
|
|
372
|
+
originalRequest,
|
|
373
|
+
`Workspace: ${workspace}`,
|
|
374
|
+
`Execution contract: force requested_model=${requestedModel} via ${model}; do not substitute a local-only model.`,
|
|
375
|
+
`Project scan: files=${scan.files}, lines=${scan.lines}, import_edges=${scan.importEdges}`,
|
|
376
|
+
topFiles.length > 0 ? `Key files: ${topFiles.join('; ')}` : 'Key files: unavailable',
|
|
377
|
+
`Execution budget: at most ${roleIterationBudget[role] || 3} reasoning iterations; prioritize a one-pass final answer and avoid redundant full-repo rescans.`,
|
|
378
|
+
].join('\n');
|
|
379
|
+
return {
|
|
380
|
+
step_id: role,
|
|
381
|
+
worker_name: 'v3_agent_worker',
|
|
382
|
+
objective: roleObjective,
|
|
383
|
+
depends_on: dependsOn,
|
|
384
|
+
priority: role === 'architect' ? 2 : 3,
|
|
385
|
+
retry_policy: {
|
|
386
|
+
max_attempts: 1,
|
|
387
|
+
strategy: 'repair',
|
|
388
|
+
requires_validation_failure: false,
|
|
389
|
+
},
|
|
390
|
+
payload: {
|
|
391
|
+
role,
|
|
392
|
+
requested_model: requestedModel,
|
|
393
|
+
quoted_model: model,
|
|
394
|
+
workspace,
|
|
395
|
+
top_files: topFiles,
|
|
396
|
+
max_iterations: roleIterationBudget[role] || 4,
|
|
397
|
+
compact_context: true,
|
|
398
|
+
max_dependency_chars: role === 'architect' ? 18000 : 12000,
|
|
399
|
+
max_artifacts: role === 'architect' ? 24 : 16,
|
|
400
|
+
max_context_chars: role === 'architect' ? 240000 : 180000,
|
|
401
|
+
max_output_tokens: role === 'detective' ? 3600 : role === 'architect' ? 3400 : role === 'reviewer' ? 3000 : 2400,
|
|
402
|
+
request_timeout_seconds: role === 'detective' || role === 'architect' ? 1200 : 720,
|
|
403
|
+
},
|
|
404
|
+
};
|
|
405
|
+
});
|
|
406
|
+
steps.push({
|
|
407
|
+
step_id: 'testing',
|
|
408
|
+
worker_name: 'testing_worker',
|
|
409
|
+
objective: [
|
|
410
|
+
'[GODMODE:TESTING]',
|
|
411
|
+
originalRequest,
|
|
412
|
+
`Workspace: ${workspace}`,
|
|
413
|
+
'Validate the architect output, run the narrowest relevant checks, and report concrete failures if any remain.',
|
|
414
|
+
].join('\n'),
|
|
415
|
+
depends_on: ['architect'],
|
|
416
|
+
priority: 2,
|
|
417
|
+
retry_policy: {
|
|
418
|
+
max_attempts: 1,
|
|
419
|
+
strategy: 'repair',
|
|
420
|
+
requires_validation_failure: false,
|
|
77
421
|
},
|
|
78
|
-
|
|
422
|
+
payload: {
|
|
423
|
+
workspace,
|
|
424
|
+
validation_min_score: 80,
|
|
425
|
+
},
|
|
426
|
+
});
|
|
427
|
+
return steps;
|
|
428
|
+
}
|
|
429
|
+
buildBillingQuote(quote) {
|
|
430
|
+
const baseUsd = quote.reduce((sum, r) => sum + r.estCostUsd, 0);
|
|
431
|
+
const marginPctRaw = Number.parseFloat(String(process.env.VIGTHORIA_GODMODE_MARGIN_PCT || '10'));
|
|
432
|
+
const marginPct = Number.isFinite(marginPctRaw) ? Math.max(0, marginPctRaw) : 10;
|
|
433
|
+
const finalUsd = baseUsd * (1 + (marginPct / 100));
|
|
434
|
+
const vigcoinRateRaw = Number.parseFloat(String(process.env.VIGTHORIA_VIGCOIN_USD_RATE || '1'));
|
|
435
|
+
const vigcoinRateUsd = Number.isFinite(vigcoinRateRaw) && vigcoinRateRaw > 0 ? vigcoinRateRaw : 1;
|
|
436
|
+
const vigcoinRequired = finalUsd / vigcoinRateUsd;
|
|
437
|
+
return {
|
|
438
|
+
baseUsd,
|
|
439
|
+
marginPct,
|
|
440
|
+
finalUsd,
|
|
441
|
+
vigcoinRateUsd,
|
|
442
|
+
vigcoinRequired,
|
|
79
443
|
};
|
|
80
|
-
|
|
444
|
+
}
|
|
445
|
+
async evaluateBillingGate(billingQuote) {
|
|
446
|
+
const forcedPlan = String(process.env.VIGTHORIA_GODMODE_FORCE_PLAN || '').trim().toLowerCase();
|
|
447
|
+
// On-server invocations using a service key run as trusted infrastructure.
|
|
448
|
+
// No user wallet check is needed — cost is tracked at the service level.
|
|
449
|
+
const hasServiceKey = !!(process.env.HYPERLOOP_SERVICE_KEY || process.env.V3_SERVICE_KEY);
|
|
450
|
+
if (hasServiceKey) {
|
|
451
|
+
return {
|
|
452
|
+
plan: forcedPlan || this.config.getNormalizedPlan() || 'service',
|
|
453
|
+
masterAdminFree: true,
|
|
454
|
+
canProceed: true,
|
|
455
|
+
wallet: {
|
|
456
|
+
available: true,
|
|
457
|
+
vigcoinBalance: null,
|
|
458
|
+
source: 'service_key_access',
|
|
459
|
+
purchaseUrl: null,
|
|
460
|
+
},
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
const entitlement = await this.fetchGodmodeEntitlement();
|
|
464
|
+
const normalizedPlan = forcedPlan || entitlement.plan || this.config.getNormalizedPlan() || 'free';
|
|
465
|
+
const masterAdminFree = this.isMasterAdminFree(normalizedPlan, entitlement.masterAccess, entitlement.isMasterAdmin);
|
|
466
|
+
if (masterAdminFree) {
|
|
467
|
+
return {
|
|
468
|
+
plan: normalizedPlan,
|
|
469
|
+
masterAdminFree,
|
|
470
|
+
canProceed: true,
|
|
471
|
+
wallet: {
|
|
472
|
+
available: true,
|
|
473
|
+
vigcoinBalance: null,
|
|
474
|
+
source: 'master_admin_access',
|
|
475
|
+
purchaseUrl: null,
|
|
476
|
+
},
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
const wallet = await this.fetchWalletState();
|
|
480
|
+
if (!wallet.available || wallet.vigcoinBalance === null) {
|
|
481
|
+
return {
|
|
482
|
+
plan: normalizedPlan,
|
|
483
|
+
masterAdminFree,
|
|
484
|
+
canProceed: false,
|
|
485
|
+
wallet,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
return {
|
|
489
|
+
plan: normalizedPlan,
|
|
490
|
+
masterAdminFree,
|
|
491
|
+
canProceed: wallet.vigcoinBalance >= billingQuote.vigcoinRequired,
|
|
492
|
+
wallet,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
parseBooleanCandidate(value) {
|
|
496
|
+
if (typeof value === 'boolean') {
|
|
497
|
+
return value;
|
|
498
|
+
}
|
|
499
|
+
if (typeof value === 'number') {
|
|
500
|
+
if (value === 1)
|
|
501
|
+
return true;
|
|
502
|
+
if (value === 0)
|
|
503
|
+
return false;
|
|
504
|
+
return null;
|
|
505
|
+
}
|
|
506
|
+
if (typeof value === 'string') {
|
|
507
|
+
const v = value.trim().toLowerCase();
|
|
508
|
+
if (['1', 'true', 'yes', 'enabled', 'on'].includes(v))
|
|
509
|
+
return true;
|
|
510
|
+
if (['0', 'false', 'no', 'disabled', 'off'].includes(v))
|
|
511
|
+
return false;
|
|
512
|
+
}
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
async fetchGodmodeEntitlement() {
|
|
516
|
+
if (process.env.VIGTHORIA_GODMODE_FORCE_MASTER_ACCESS === '1') {
|
|
517
|
+
return { plan: this.config.getNormalizedPlan() || 'free', masterAccess: true, isMasterAdmin: true };
|
|
518
|
+
}
|
|
519
|
+
const baseUrl = this.getBillingBaseUrl();
|
|
520
|
+
const headers = this.getHeaders();
|
|
521
|
+
const endpoints = ['/api/user/subscription', '/api/user/info', '/api/user/profile'];
|
|
522
|
+
for (const endpoint of endpoints) {
|
|
81
523
|
try {
|
|
82
|
-
const response = await fetch(`${baseUrl}
|
|
83
|
-
method: '
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
signal: AbortSignal.timeout(300000),
|
|
524
|
+
const response = await fetch(`${baseUrl}${endpoint}`, {
|
|
525
|
+
method: 'GET',
|
|
526
|
+
signal: AbortSignal.timeout(12000),
|
|
527
|
+
headers,
|
|
87
528
|
});
|
|
88
|
-
if (!response.ok)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
529
|
+
if (!response.ok)
|
|
530
|
+
continue;
|
|
531
|
+
const payload = await response.json();
|
|
532
|
+
const data = (payload && typeof payload === 'object') ? payload : {};
|
|
533
|
+
const plan = String(data.subscription?.plan
|
|
534
|
+
|| data.subscription_plan
|
|
535
|
+
|| data.plan
|
|
536
|
+
|| data.user?.subscription_plan
|
|
537
|
+
|| data.user?.subscription?.plan
|
|
538
|
+
|| '').trim().toLowerCase() || null;
|
|
539
|
+
const accessCandidates = [
|
|
540
|
+
data.godmode_master_access,
|
|
541
|
+
data.godmodeMasterAccess,
|
|
542
|
+
data.subscription?.godmode_master_access,
|
|
543
|
+
data.subscription?.godmodeMasterAccess,
|
|
544
|
+
data.user?.godmode_master_access,
|
|
545
|
+
data.user?.godmodeMasterAccess,
|
|
546
|
+
];
|
|
547
|
+
const masterRoleCandidates = [
|
|
548
|
+
data.user?.isMasterAdmin,
|
|
549
|
+
data.user?.is_master_admin,
|
|
550
|
+
data.subscription?.isMasterAdmin,
|
|
551
|
+
data.subscription?.is_master_admin,
|
|
552
|
+
data.isMasterAdmin,
|
|
553
|
+
data.is_master_admin,
|
|
554
|
+
data.user?.admin_role === 'master_admin',
|
|
555
|
+
data.admin_role === 'master_admin',
|
|
556
|
+
];
|
|
557
|
+
const roleHit = masterRoleCandidates
|
|
558
|
+
.map((c) => this.parseBooleanCandidate(c))
|
|
559
|
+
.find((v) => v !== null) ?? false;
|
|
560
|
+
for (const c of accessCandidates) {
|
|
561
|
+
const parsed = this.parseBooleanCandidate(c);
|
|
562
|
+
if (parsed !== null) {
|
|
563
|
+
return { plan, masterAccess: parsed, isMasterAdmin: Boolean(roleHit) };
|
|
122
564
|
}
|
|
123
565
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
566
|
+
if (plan) {
|
|
567
|
+
return { plan, masterAccess: false, isMasterAdmin: Boolean(roleHit) };
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
catch (err) {
|
|
571
|
+
this.logger.warn(this.formatLegionError(`Godmode entitlement request ${endpoint}`, err));
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
return {
|
|
576
|
+
plan: this.config.getNormalizedPlan() || null,
|
|
577
|
+
masterAccess: false,
|
|
578
|
+
isMasterAdmin: false,
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
isMasterAdminFree(plan, masterAccess, isMasterAdmin) {
|
|
582
|
+
if (!masterAccess) {
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
if (isMasterAdmin) {
|
|
586
|
+
return true;
|
|
587
|
+
}
|
|
588
|
+
return plan === 'master_admin';
|
|
589
|
+
}
|
|
590
|
+
getBillingBaseUrl() {
|
|
591
|
+
const configured = String(this.config.get('apiUrl') || '').trim().replace(/\/$/, '');
|
|
592
|
+
if (configured) {
|
|
593
|
+
return configured;
|
|
594
|
+
}
|
|
595
|
+
return 'https://coder.vigthoria.io';
|
|
596
|
+
}
|
|
597
|
+
parseNumericCandidate(value) {
|
|
598
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
599
|
+
return value;
|
|
600
|
+
}
|
|
601
|
+
if (typeof value === 'string') {
|
|
602
|
+
const parsed = Number.parseFloat(value.trim());
|
|
603
|
+
if (Number.isFinite(parsed)) {
|
|
604
|
+
return parsed;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
extractVigcoinBalance(data) {
|
|
610
|
+
const candidates = [
|
|
611
|
+
data.vigcoinBalance,
|
|
612
|
+
data.vigcoin_balance,
|
|
613
|
+
data.balance,
|
|
614
|
+
data.credits,
|
|
615
|
+
data.credit_balance,
|
|
616
|
+
data.wallet?.vigcoinBalance,
|
|
617
|
+
data.wallet?.vigcoin_balance,
|
|
618
|
+
data.wallet?.balance,
|
|
619
|
+
data.wallet?.credits,
|
|
620
|
+
data.subscription?.vigcoinBalance,
|
|
621
|
+
data.subscription?.vigcoin_balance,
|
|
622
|
+
data.subscription?.credits,
|
|
623
|
+
data.user?.vigcoinBalance,
|
|
624
|
+
data.user?.vigcoin_balance,
|
|
625
|
+
data.user?.credits,
|
|
626
|
+
data.data?.vigcoinBalance,
|
|
627
|
+
data.data?.vigcoin_balance,
|
|
628
|
+
data.data?.balance,
|
|
629
|
+
data.data?.credits,
|
|
630
|
+
];
|
|
631
|
+
for (const candidate of candidates) {
|
|
632
|
+
const parsed = this.parseNumericCandidate(candidate);
|
|
633
|
+
if (parsed !== null) {
|
|
634
|
+
return parsed;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
639
|
+
getPurchaseUrlFromPayload(baseUrl, data) {
|
|
640
|
+
const raw = data.purchaseUrl || data.purchase_url || data.checkoutUrl || data.checkout_url || data.url || data.data?.checkoutUrl || data.data?.url;
|
|
641
|
+
if (typeof raw === 'string' && raw.trim()) {
|
|
642
|
+
if (/^https?:\/\//i.test(raw.trim())) {
|
|
643
|
+
return raw.trim();
|
|
644
|
+
}
|
|
645
|
+
return `${baseUrl}${raw.startsWith('/') ? '' : '/'}${raw.trim()}`;
|
|
646
|
+
}
|
|
647
|
+
return `${baseUrl}/billing`;
|
|
648
|
+
}
|
|
649
|
+
async fetchWalletState() {
|
|
650
|
+
const baseUrl = this.getBillingBaseUrl();
|
|
651
|
+
const headers = this.getHeaders();
|
|
652
|
+
const forcedLow = process.env.VIGTHORIA_GODMODE_FORCE_LOW_CREDIT === '1';
|
|
653
|
+
const forcedBalanceRaw = process.env.VIGTHORIA_GODMODE_FORCE_BALANCE;
|
|
654
|
+
if (forcedLow) {
|
|
655
|
+
return {
|
|
656
|
+
available: true,
|
|
657
|
+
vigcoinBalance: 0,
|
|
658
|
+
source: 'forced_low_credit',
|
|
659
|
+
purchaseUrl: `${baseUrl}/music/store#vigcoins`,
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
if (forcedBalanceRaw) {
|
|
663
|
+
const forced = this.parseNumericCandidate(forcedBalanceRaw);
|
|
664
|
+
if (forced !== null) {
|
|
665
|
+
return {
|
|
666
|
+
available: true,
|
|
667
|
+
vigcoinBalance: forced,
|
|
668
|
+
source: 'forced_balance',
|
|
669
|
+
purchaseUrl: `${baseUrl}/music/store#vigcoins`,
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
const endpoints = [
|
|
674
|
+
'/api/viagen6/vigcoin/balance',
|
|
675
|
+
'/api/user/subscription',
|
|
676
|
+
'/api/user/info',
|
|
677
|
+
'/api/user/profile',
|
|
678
|
+
'/api/wallet/balance',
|
|
679
|
+
'/api/billing/wallet',
|
|
680
|
+
'/api/billing/credits',
|
|
681
|
+
];
|
|
682
|
+
for (const endpoint of endpoints) {
|
|
683
|
+
try {
|
|
684
|
+
const response = await fetch(`${baseUrl}${endpoint}`, {
|
|
685
|
+
method: 'GET',
|
|
686
|
+
signal: AbortSignal.timeout(12000),
|
|
687
|
+
headers,
|
|
688
|
+
});
|
|
689
|
+
if (!response.ok) {
|
|
690
|
+
continue;
|
|
128
691
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
692
|
+
const payload = await this.readJsonResponse(response, `wallet balance request ${endpoint}`);
|
|
693
|
+
const payloadObj = (payload && typeof payload === 'object') ? payload : {};
|
|
694
|
+
const balance = this.extractVigcoinBalance(payloadObj);
|
|
695
|
+
if (balance === null) {
|
|
696
|
+
continue;
|
|
132
697
|
}
|
|
133
|
-
|
|
134
|
-
|
|
698
|
+
return {
|
|
699
|
+
available: true,
|
|
700
|
+
vigcoinBalance: balance,
|
|
701
|
+
source: endpoint,
|
|
702
|
+
purchaseUrl: this.getPurchaseUrlFromPayload(baseUrl, payloadObj) || `${baseUrl}/music/store#vigcoins`,
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
catch (err) {
|
|
706
|
+
this.logger.warn(this.formatLegionError(`wallet balance request ${endpoint}`, err));
|
|
707
|
+
continue;
|
|
135
708
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
709
|
+
}
|
|
710
|
+
return {
|
|
711
|
+
available: false,
|
|
712
|
+
vigcoinBalance: null,
|
|
713
|
+
source: null,
|
|
714
|
+
purchaseUrl: `${baseUrl}/music/store#vigcoins`,
|
|
715
|
+
error: 'Wallet endpoint unavailable from current gateway session',
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
async attemptDirectCharge(vigcoinNeeded) {
|
|
719
|
+
const baseUrl = this.getBillingBaseUrl();
|
|
720
|
+
const headers = this.getHeaders();
|
|
721
|
+
const amount = Math.max(1, Math.ceil(vigcoinNeeded));
|
|
722
|
+
const chargePayloads = [
|
|
723
|
+
{ endpoint: '/api/viagen6/vigcoin/charge', body: { amount, reason: 'godmode_legion' } },
|
|
724
|
+
{ endpoint: '/api/wallet/charge', body: { amount, currency: 'VIGCOIN', reason: 'godmode_legion' } },
|
|
725
|
+
{ endpoint: '/api/billing/topup', body: { vigcoin: amount, reason: 'godmode_legion' } },
|
|
726
|
+
];
|
|
727
|
+
for (const attempt of chargePayloads) {
|
|
728
|
+
try {
|
|
729
|
+
const response = await fetch(`${baseUrl}${attempt.endpoint}`, {
|
|
730
|
+
method: 'POST',
|
|
731
|
+
signal: AbortSignal.timeout(15000),
|
|
732
|
+
headers,
|
|
733
|
+
body: JSON.stringify(attempt.body),
|
|
734
|
+
});
|
|
735
|
+
const payload = await response.json().catch((err) => {
|
|
736
|
+
this.logger.warn(this.formatLegionError(`direct VigCoin charge response ${attempt.endpoint}`, err));
|
|
737
|
+
return {};
|
|
738
|
+
});
|
|
739
|
+
const payloadObj = (payload && typeof payload === 'object') ? payload : {};
|
|
740
|
+
const checkoutUrl = this.getPurchaseUrlFromPayload(baseUrl, payloadObj) || `${baseUrl}/music/store#vigcoins`;
|
|
741
|
+
if (!response.ok) {
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
const charged = payloadObj.charged === true || payloadObj.success === true || payloadObj.status === 'charged';
|
|
745
|
+
if (charged) {
|
|
746
|
+
return { ok: true, checkoutUrl: checkoutUrl || undefined, note: `Charge accepted by ${attempt.endpoint}` };
|
|
141
747
|
}
|
|
142
|
-
|
|
748
|
+
return {
|
|
749
|
+
ok: false,
|
|
750
|
+
checkoutUrl: checkoutUrl || undefined,
|
|
751
|
+
note: payloadObj.error || payloadObj.message || `Checkout required via ${attempt.endpoint}`,
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
catch (err) {
|
|
755
|
+
this.logger.warn(this.formatLegionError(`direct VigCoin charge request ${attempt.endpoint}`, err));
|
|
143
756
|
continue;
|
|
144
757
|
}
|
|
145
758
|
}
|
|
759
|
+
return {
|
|
760
|
+
ok: false,
|
|
761
|
+
checkoutUrl: `${baseUrl}/music/store#vigcoins`,
|
|
762
|
+
note: 'No direct charge endpoint accepted this request',
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
async collectExecutionCharge(billingQuote, gate) {
|
|
766
|
+
if (gate.masterAdminFree) {
|
|
767
|
+
return true;
|
|
768
|
+
}
|
|
769
|
+
const spinner = (0, logger_js_1.createSpinner)('Charging VigCoin wallet for Godmode execution...').start();
|
|
770
|
+
const result = await this.attemptDirectCharge(billingQuote.vigcoinRequired);
|
|
146
771
|
spinner.stop();
|
|
147
|
-
|
|
772
|
+
if (!result.ok) {
|
|
773
|
+
console.log(chalk_1.default.red(result.note || 'Wallet charge failed.'));
|
|
774
|
+
console.log(chalk_1.default.yellow(`Complete purchase first: ${result.checkoutUrl || `${this.getBillingBaseUrl()}/music/store#vigcoins`}`));
|
|
775
|
+
return false;
|
|
776
|
+
}
|
|
777
|
+
console.log(chalk_1.default.green('Wallet charged for Godmode execution.'));
|
|
778
|
+
return true;
|
|
779
|
+
}
|
|
780
|
+
async resolveBillingInsufficientFunds(billingQuote, gate, options) {
|
|
781
|
+
this.printBillingGateSummary(billingQuote, gate);
|
|
782
|
+
if (!gate.wallet.available) {
|
|
783
|
+
console.log(chalk_1.default.red('Unable to verify wallet balance from server. Execution is blocked.'));
|
|
784
|
+
if (gate.wallet.purchaseUrl) {
|
|
785
|
+
console.log(chalk_1.default.gray(`Open billing portal: ${gate.wallet.purchaseUrl}`));
|
|
786
|
+
}
|
|
787
|
+
return false;
|
|
788
|
+
}
|
|
789
|
+
if (options.autoCharge) {
|
|
790
|
+
const spinner = (0, logger_js_1.createSpinner)('Attempting direct wallet charge...').start();
|
|
791
|
+
const result = await this.attemptDirectCharge(billingQuote.vigcoinRequired - (gate.wallet.vigcoinBalance || 0));
|
|
792
|
+
spinner.stop();
|
|
793
|
+
if (result.ok) {
|
|
794
|
+
console.log(chalk_1.default.green('Direct charge succeeded. Re-checking wallet balance...'));
|
|
795
|
+
return true;
|
|
796
|
+
}
|
|
797
|
+
console.log(chalk_1.default.yellow(result.note || 'Direct charge did not complete.'));
|
|
798
|
+
console.log(chalk_1.default.yellow(`Complete purchase first: ${result.checkoutUrl || gate.wallet.purchaseUrl || `${this.getBillingBaseUrl()}/billing`}`));
|
|
799
|
+
return false;
|
|
800
|
+
}
|
|
801
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
802
|
+
console.log(chalk_1.default.yellow('Low balance detected in non-interactive mode. Re-run with --auto-charge or top up first.'));
|
|
803
|
+
if (gate.wallet.purchaseUrl) {
|
|
804
|
+
console.log(chalk_1.default.gray(`Billing portal: ${gate.wallet.purchaseUrl}`));
|
|
805
|
+
}
|
|
806
|
+
return false;
|
|
807
|
+
}
|
|
808
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
809
|
+
try {
|
|
810
|
+
const answer = (await rl.question('VigCoin low. Choose action: [c]harge now, [p]urchase first, [n] cancel: ')).trim().toLowerCase();
|
|
811
|
+
if (answer === 'c' || answer === 'charge') {
|
|
812
|
+
const spinner = (0, logger_js_1.createSpinner)('Attempting direct wallet charge...').start();
|
|
813
|
+
const result = await this.attemptDirectCharge(billingQuote.vigcoinRequired - (gate.wallet.vigcoinBalance || 0));
|
|
814
|
+
spinner.stop();
|
|
815
|
+
if (result.ok) {
|
|
816
|
+
console.log(chalk_1.default.green('Direct charge succeeded. Re-checking wallet balance...'));
|
|
817
|
+
return true;
|
|
818
|
+
}
|
|
819
|
+
console.log(chalk_1.default.yellow(result.note || 'Direct charge did not complete.'));
|
|
820
|
+
console.log(chalk_1.default.yellow(`Complete purchase first: ${result.checkoutUrl || gate.wallet.purchaseUrl || `${this.getBillingBaseUrl()}/billing`}`));
|
|
821
|
+
return false;
|
|
822
|
+
}
|
|
823
|
+
if (answer === 'p' || answer === 'purchase') {
|
|
824
|
+
console.log(chalk_1.default.yellow(`Purchase VigCoin first: ${gate.wallet.purchaseUrl || `${this.getBillingBaseUrl()}/billing`}`));
|
|
825
|
+
return false;
|
|
826
|
+
}
|
|
827
|
+
return false;
|
|
828
|
+
}
|
|
829
|
+
finally {
|
|
830
|
+
rl.close();
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
printBillingGateSummary(billingQuote, gate) {
|
|
834
|
+
console.log(chalk_1.default.white(' Billing gate:'));
|
|
835
|
+
console.log(chalk_1.default.gray(` Plan: ${gate.plan}`));
|
|
836
|
+
if (gate.masterAdminFree) {
|
|
837
|
+
console.log(chalk_1.default.green(' Free tier override applied (Master Admin).'));
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
console.log(chalk_1.default.gray(` Estimated total (USD): $${billingQuote.finalUsd.toFixed(4)}`));
|
|
841
|
+
console.log(chalk_1.default.gray(` VigCoin rate: 1 VIG = $${billingQuote.vigcoinRateUsd.toFixed(4)}`));
|
|
842
|
+
console.log(chalk_1.default.gray(` VigCoin required: ${billingQuote.vigcoinRequired.toFixed(3)}`));
|
|
843
|
+
if (gate.wallet.vigcoinBalance !== null) {
|
|
844
|
+
const color = gate.wallet.vigcoinBalance >= billingQuote.vigcoinRequired ? chalk_1.default.green : chalk_1.default.red;
|
|
845
|
+
console.log(chalk_1.default.gray(' Wallet balance: ') + color(gate.wallet.vigcoinBalance.toFixed(3)) + (gate.wallet.source ? chalk_1.default.gray(` (source: ${gate.wallet.source})`) : ''));
|
|
846
|
+
}
|
|
847
|
+
else {
|
|
848
|
+
console.log(chalk_1.default.red(` Wallet balance: unavailable${gate.wallet.error ? ` (${gate.wallet.error})` : ''}`));
|
|
849
|
+
}
|
|
850
|
+
if (gate.wallet.purchaseUrl) {
|
|
851
|
+
console.log(chalk_1.default.gray(` Purchase URL: ${gate.wallet.purchaseUrl}`));
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
printGodmodeQuote(workspace, scan, quote, billingQuote, gate) {
|
|
855
|
+
const totalCost = billingQuote.finalUsd;
|
|
856
|
+
console.log();
|
|
857
|
+
console.log(chalk_1.default.bold.white(` ${logger_js_1.CH.hLine.repeat(3)} Legion Godmode Calculator ${logger_js_1.CH.hLine.repeat(31)}`));
|
|
858
|
+
console.log();
|
|
859
|
+
console.log(chalk_1.default.gray(' Workspace: ') + chalk_1.default.white(workspace));
|
|
860
|
+
console.log(chalk_1.default.gray(' Files scanned: ') + chalk_1.default.white(String(scan.files)));
|
|
861
|
+
console.log(chalk_1.default.gray(' Lines scanned: ') + chalk_1.default.white(String(scan.lines)));
|
|
862
|
+
console.log(chalk_1.default.gray(' Dependency edges: ') + chalk_1.default.white(String(scan.importEdges)));
|
|
863
|
+
if (scan.topFiles.length > 0) {
|
|
864
|
+
console.log();
|
|
865
|
+
console.log(chalk_1.default.white(' Top context files:'));
|
|
866
|
+
for (const f of scan.topFiles) {
|
|
867
|
+
console.log(chalk_1.default.gray(` ${logger_js_1.CH.bullet} ${f.file} (${f.lines} lines, ${f.imports} imports)`));
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
console.log();
|
|
871
|
+
console.log(chalk_1.default.white(' Role assignment and estimated cost:'));
|
|
872
|
+
for (const row of quote) {
|
|
873
|
+
console.log(chalk_1.default.gray(` ${logger_js_1.CH.bullet} ${row.role.padEnd(11)} ${row.model} $${row.estCostUsd.toFixed(4)}`));
|
|
874
|
+
}
|
|
875
|
+
console.log();
|
|
876
|
+
console.log(chalk_1.default.yellow(` Estimated total: $${totalCost.toFixed(4)}`));
|
|
877
|
+
console.log(chalk_1.default.gray(' Flow: Estimate -> Isolation -> Parallel Attack -> Synthesis'));
|
|
878
|
+
console.log();
|
|
879
|
+
this.printBillingGateSummary(billingQuote, gate);
|
|
880
|
+
console.log();
|
|
881
|
+
}
|
|
882
|
+
async confirmExecution() {
|
|
883
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
884
|
+
console.log(chalk_1.default.yellow('Non-interactive terminal detected. Re-run with --approve to continue execution.'));
|
|
885
|
+
return false;
|
|
886
|
+
}
|
|
887
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
888
|
+
try {
|
|
889
|
+
const answer = (await rl.question('Proceed with Godmode execution? (y/N): ')).trim().toLowerCase();
|
|
890
|
+
return answer === 'y' || answer === 'yes';
|
|
891
|
+
}
|
|
892
|
+
finally {
|
|
893
|
+
rl.close();
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* SSE streaming URL for the Legion execution endpoint.
|
|
898
|
+
* Always hits Hyper Loop directly (port 8020) with the service key to avoid
|
|
899
|
+
* gateway JWT expiry killing long-running GodMode jobs.
|
|
900
|
+
*/
|
|
901
|
+
getLegionStreamUrl() {
|
|
902
|
+
const envOverride = String(process.env.VIGTHORIA_HYPERLOOP_URL || '').trim().replace(/\/$/, '');
|
|
903
|
+
if (envOverride)
|
|
904
|
+
return `${envOverride}/legion/stream`;
|
|
905
|
+
return 'http://localhost:8020/api/hyperloop/legion/stream';
|
|
906
|
+
}
|
|
907
|
+
getLegionServiceKey() {
|
|
908
|
+
// Service key lets on-server CLI calls bypass gateway JWT validation
|
|
909
|
+
return String(process.env.HYPERLOOP_SERVICE_KEY ||
|
|
910
|
+
process.env.V3_SERVICE_KEY ||
|
|
911
|
+
'');
|
|
912
|
+
}
|
|
913
|
+
async planAndExecute(request, options, godmodeExecution) {
|
|
914
|
+
const explicitSteps = godmodeExecution ? this.buildGodmodeExplicitSteps(godmodeExecution) : undefined;
|
|
915
|
+
const workspace = godmodeExecution?.workspace || options.project || process.cwd();
|
|
916
|
+
const body = {
|
|
917
|
+
request,
|
|
918
|
+
context: { workspace },
|
|
919
|
+
constraints: {
|
|
920
|
+
active_only: true,
|
|
921
|
+
execution_timeout_seconds: options.timeoutSec,
|
|
922
|
+
preferred_workers: explicitSteps ? undefined : ['v3_agent_worker'],
|
|
923
|
+
explicit_steps: explicitSteps,
|
|
924
|
+
},
|
|
925
|
+
};
|
|
926
|
+
const streamUrl = this.getLegionStreamUrl();
|
|
927
|
+
const serviceKey = this.getLegionServiceKey();
|
|
928
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
929
|
+
if (serviceKey) {
|
|
930
|
+
headers['X-Service-Key'] = serviceKey;
|
|
931
|
+
}
|
|
932
|
+
else {
|
|
933
|
+
// Fallback: pass user JWT (works if token is still valid)
|
|
934
|
+
const token = this.config.get('authToken');
|
|
935
|
+
if (token) {
|
|
936
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
937
|
+
headers['Cookie'] = `vigthoria-auth-token=${token}`;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
const spinner = (0, logger_js_1.createSpinner)('Connecting to Legion SSE stream...').start();
|
|
941
|
+
const startTime = Date.now();
|
|
942
|
+
let response;
|
|
943
|
+
try {
|
|
944
|
+
// No AbortSignal timeout — SSE keeps alive; server controls lifetime
|
|
945
|
+
response = await fetch(streamUrl, {
|
|
946
|
+
method: 'POST',
|
|
947
|
+
headers,
|
|
948
|
+
body: JSON.stringify(body),
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
catch (connErr) {
|
|
952
|
+
spinner.stop();
|
|
953
|
+
this.logger.error(`Cannot connect to Hyper Loop at ${streamUrl}: ${connErr?.message || connErr}`);
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
if (!response.ok) {
|
|
957
|
+
spinner.stop();
|
|
958
|
+
const errBody = await response.text().catch(() => '');
|
|
959
|
+
this.logger.error(`Legion stream ${response.status}: ${(0, api_js_1.describeUpstreamStatus)(response.status)} — ${errBody.slice(0, 200)}`);
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
if (!response.body) {
|
|
963
|
+
spinner.stop();
|
|
964
|
+
this.logger.error('Legion stream returned no response body');
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
// ── SSE consumer ─────────────────────────────────────────────
|
|
968
|
+
spinner.stop();
|
|
969
|
+
console.log();
|
|
970
|
+
console.log(chalk_1.default.bold.white(` ${logger_js_1.CH.hLine.repeat(3)} Legion Execution Report ${logger_js_1.CH.hLine.repeat(34)}`));
|
|
971
|
+
console.log();
|
|
972
|
+
const decoder = new TextDecoder();
|
|
973
|
+
let buffer = '';
|
|
974
|
+
let finalResult = null;
|
|
975
|
+
let stepsTotal = 0;
|
|
976
|
+
let stepsDone = 0;
|
|
977
|
+
try {
|
|
978
|
+
const reader = response.body.getReader();
|
|
979
|
+
while (true) {
|
|
980
|
+
const { done, value } = await reader.read();
|
|
981
|
+
if (done)
|
|
982
|
+
break;
|
|
983
|
+
buffer += decoder.decode(value, { stream: true });
|
|
984
|
+
const lines = buffer.split('\n');
|
|
985
|
+
buffer = lines.pop() ?? ''; // keep incomplete last line
|
|
986
|
+
for (const line of lines) {
|
|
987
|
+
const trimmed = line.trim();
|
|
988
|
+
if (!trimmed || trimmed.startsWith(':'))
|
|
989
|
+
continue; // keep-alive or comment
|
|
990
|
+
if (!trimmed.startsWith('data:'))
|
|
991
|
+
continue;
|
|
992
|
+
const jsonStr = trimmed.slice(5).trim();
|
|
993
|
+
let evt;
|
|
994
|
+
try {
|
|
995
|
+
evt = JSON.parse(jsonStr);
|
|
996
|
+
}
|
|
997
|
+
catch {
|
|
998
|
+
continue;
|
|
999
|
+
}
|
|
1000
|
+
switch (evt.event) {
|
|
1001
|
+
case 'plan':
|
|
1002
|
+
stepsTotal = evt.steps_total || 0;
|
|
1003
|
+
console.log(chalk_1.default.gray(` Planned workers: ${stepsTotal} steps queued`));
|
|
1004
|
+
console.log();
|
|
1005
|
+
break;
|
|
1006
|
+
case 'batch_start':
|
|
1007
|
+
console.log(chalk_1.default.gray(` ▶ Running: ${evt.workers.join(', ')} (${evt.steps_done}/${evt.steps_total})`));
|
|
1008
|
+
break;
|
|
1009
|
+
case 'step_complete': {
|
|
1010
|
+
stepsDone = Number(evt.steps_done) || 0;
|
|
1011
|
+
const icon = evt.status === 'completed' ? chalk_1.default.green(logger_js_1.CH.success) : chalk_1.default.red(logger_js_1.CH.error);
|
|
1012
|
+
const summary = evt.summary ? chalk_1.default.gray(` — ${String(evt.summary).slice(0, 120)}`) : '';
|
|
1013
|
+
console.log(` ${icon} ${chalk_1.default.white(String(evt.step_id))} ${chalk_1.default.gray('[' + evt.worker + ']')}${summary}`);
|
|
1014
|
+
break;
|
|
1015
|
+
}
|
|
1016
|
+
case 'complete':
|
|
1017
|
+
finalResult = evt.result || null;
|
|
1018
|
+
if (evt.status === 'completed') {
|
|
1019
|
+
console.log();
|
|
1020
|
+
console.log(chalk_1.default.green(` ${logger_js_1.CH.success} Legion completed successfully`));
|
|
1021
|
+
}
|
|
1022
|
+
else if (evt.status === 'failed') {
|
|
1023
|
+
console.log();
|
|
1024
|
+
console.log(chalk_1.default.red(` ${logger_js_1.CH.error} Legion execution failed`));
|
|
1025
|
+
if (evt.error)
|
|
1026
|
+
console.log(chalk_1.default.red(` Error: ${String(evt.error).slice(0, 300)}`));
|
|
1027
|
+
}
|
|
1028
|
+
break;
|
|
1029
|
+
case 'error':
|
|
1030
|
+
console.log(chalk_1.default.red(` ${logger_js_1.CH.error} Stream error: ${String(evt.error).slice(0, 300)}`));
|
|
1031
|
+
break;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
catch (streamErr) {
|
|
1037
|
+
this.logger.error(`Legion stream read error: ${streamErr?.message || streamErr}`);
|
|
1038
|
+
}
|
|
1039
|
+
const elapsedSec = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
1040
|
+
if (stepsTotal > 0 && stepsDone < stepsTotal) {
|
|
1041
|
+
console.log(chalk_1.default.yellow(` ${logger_js_1.CH.warn} Legion stream ended after ${stepsDone}/${stepsTotal} steps`));
|
|
1042
|
+
}
|
|
1043
|
+
console.log();
|
|
1044
|
+
console.log(chalk_1.default.gray(` Time: ${elapsedSec}s`));
|
|
1045
|
+
// Show final output from architect/last step
|
|
1046
|
+
if (finalResult) {
|
|
1047
|
+
const lastStepResult = finalResult.final_output;
|
|
1048
|
+
const summary = lastStepResult?.result?.summary || lastStepResult?.summary || '';
|
|
1049
|
+
if (summary) {
|
|
1050
|
+
console.log();
|
|
1051
|
+
console.log(chalk_1.default.white(' Final output:'));
|
|
1052
|
+
console.log(chalk_1.default.gray(` ${String(summary).slice(0, 600)}`));
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
console.log();
|
|
1056
|
+
}
|
|
1057
|
+
formatLegionError(context, err) {
|
|
1058
|
+
const message = err?.message || String(err || 'Unknown error');
|
|
1059
|
+
const cause = err?.cause?.message ? ` (${err.cause.message})` : '';
|
|
1060
|
+
return `${context} failed: ${message}${cause}`;
|
|
148
1061
|
}
|
|
149
1062
|
async showWorkers() {
|
|
150
1063
|
const spinner = (0, logger_js_1.createSpinner)('Fetching Legion worker catalog...').start();
|
|
151
|
-
|
|
1064
|
+
let lastError = null;
|
|
1065
|
+
for (const baseUrl of this.getHyperloopUrls()) {
|
|
152
1066
|
try {
|
|
153
1067
|
const response = await fetch(`${baseUrl}/legion/workers`, {
|
|
154
1068
|
signal: AbortSignal.timeout(10000),
|
|
155
1069
|
headers: this.getHeaders(),
|
|
156
1070
|
});
|
|
157
|
-
if (!response.ok)
|
|
1071
|
+
if (!response.ok) {
|
|
1072
|
+
const errBody = await response.text().catch((err) => {
|
|
1073
|
+
this.logger.warn(this.formatLegionError(`worker catalog error body at ${baseUrl}`, err));
|
|
1074
|
+
return '';
|
|
1075
|
+
});
|
|
1076
|
+
lastError = `Legion worker catalog request at ${baseUrl} failed: ${response.status} ${(0, api_js_1.describeUpstreamStatus)(response.status)}${errBody ? ` — ${errBody.slice(0, 200)}` : ''}`;
|
|
158
1077
|
continue;
|
|
159
|
-
|
|
1078
|
+
}
|
|
1079
|
+
const data = await this.readJsonResponse(response, `worker catalog request at ${baseUrl}`);
|
|
160
1080
|
spinner.stop();
|
|
161
1081
|
console.log();
|
|
162
1082
|
console.log(chalk_1.default.bold.white(` ${logger_js_1.CH.hLine.repeat(3)} Legion Worker Catalog ${logger_js_1.CH.hLine.repeat(37)}`));
|
|
@@ -186,24 +1106,28 @@ class LegionCommand {
|
|
|
186
1106
|
console.log();
|
|
187
1107
|
return;
|
|
188
1108
|
}
|
|
189
|
-
catch {
|
|
1109
|
+
catch (err) {
|
|
1110
|
+
lastError = this.formatLegionError(`worker catalog request at ${baseUrl}`, err);
|
|
190
1111
|
continue;
|
|
191
1112
|
}
|
|
192
1113
|
}
|
|
193
1114
|
spinner.stop();
|
|
194
|
-
this.logger.error('Could not reach Hyper Loop. Is vigthoria-hyper-loop running?');
|
|
1115
|
+
this.logger.error(lastError || 'Could not reach Hyper Loop. Is vigthoria-hyper-loop running?');
|
|
195
1116
|
}
|
|
196
1117
|
async showStatus() {
|
|
197
1118
|
const spinner = (0, logger_js_1.createSpinner)('Checking Legion infrastructure...').start();
|
|
198
|
-
|
|
1119
|
+
let lastError = null;
|
|
1120
|
+
for (const baseUrl of this.getHyperloopUrls()) {
|
|
199
1121
|
try {
|
|
200
1122
|
const response = await fetch(`${baseUrl}/status`, {
|
|
201
1123
|
signal: AbortSignal.timeout(10000),
|
|
202
1124
|
headers: this.getHeaders(),
|
|
203
1125
|
});
|
|
204
|
-
if (!response.ok)
|
|
1126
|
+
if (!response.ok) {
|
|
1127
|
+
lastError = `Legion status check at ${baseUrl} failed: ${response.status} ${(0, api_js_1.describeUpstreamStatus)(response.status)}`;
|
|
205
1128
|
continue;
|
|
206
|
-
|
|
1129
|
+
}
|
|
1130
|
+
const data = await this.readJsonResponse(response, `status check at ${baseUrl}`);
|
|
207
1131
|
spinner.stop();
|
|
208
1132
|
console.log();
|
|
209
1133
|
console.log(chalk_1.default.bold.white(` ${logger_js_1.CH.hLine.repeat(3)} Legion Infrastructure ${logger_js_1.CH.hLine.repeat(37)}`));
|
|
@@ -225,13 +1149,17 @@ class LegionCommand {
|
|
|
225
1149
|
console.log();
|
|
226
1150
|
return;
|
|
227
1151
|
}
|
|
228
|
-
catch {
|
|
1152
|
+
catch (err) {
|
|
1153
|
+
lastError = this.formatLegionError(`status check at ${baseUrl}`, err);
|
|
229
1154
|
continue;
|
|
230
1155
|
}
|
|
231
1156
|
}
|
|
232
1157
|
spinner.stop();
|
|
233
1158
|
console.log();
|
|
234
1159
|
console.log(chalk_1.default.gray(' Hyper Loop: ') + chalk_1.default.red('offline'));
|
|
1160
|
+
if (lastError) {
|
|
1161
|
+
this.logger.error(lastError);
|
|
1162
|
+
}
|
|
235
1163
|
console.log();
|
|
236
1164
|
}
|
|
237
1165
|
}
|