@vtxmacro/cli 2026.10.5 → 2026.10.7
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 +10 -0
- package/bin/vtx-mcp.js +3268 -759
- package/bin/vtx-service-bootstrap.js +387 -379
- package/bin/vtx-service-launcher.js +55 -0
- package/bin/vtx.js +17927 -16686
- package/package.json +1 -1
package/bin/vtx-mcp.js
CHANGED
|
@@ -1,44 +1,118 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire as __vtxCreateRequire } from 'node:module'; const require = __vtxCreateRequire(import.meta.url);
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __esm = (fn, res, err) => function __init() {
|
|
6
|
+
if (err) throw err[0];
|
|
7
|
+
try {
|
|
8
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
9
|
+
} catch (e) {
|
|
10
|
+
throw err = [e], e;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
var __export = (target, all) => {
|
|
14
|
+
for (var name in all)
|
|
15
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
16
|
+
};
|
|
3
17
|
|
|
4
|
-
//
|
|
5
|
-
|
|
18
|
+
// <define:__VTX_EXO_POLICY__>
|
|
19
|
+
var init_define_VTX_EXO_POLICY = __esm({
|
|
20
|
+
"<define:__VTX_EXO_POLICY__>"() {
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// <define:__VTX_GROK_POLICY__>
|
|
25
|
+
var init_define_VTX_GROK_POLICY = __esm({
|
|
26
|
+
"<define:__VTX_GROK_POLICY__>"() {
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// <define:__VTX_PI_MODEL_POLICY__>
|
|
31
|
+
var init_define_VTX_PI_MODEL_POLICY = __esm({
|
|
32
|
+
"<define:__VTX_PI_MODEL_POLICY__>"() {
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// agent-cli-release.json
|
|
37
|
+
var agent_cli_release_default;
|
|
38
|
+
var init_agent_cli_release = __esm({
|
|
39
|
+
"agent-cli-release.json"() {
|
|
40
|
+
agent_cli_release_default = { package_name: "@vtxmacro/cli", package_version: "2026.10.7", codex_package_name: "@openai/codex", codex_version: "0.155.0", copilot_sdk_package_name: "@github/copilot-sdk", copilot_sdk_version: "1.0.0-beta.8", copilot_cli_package_name: "@github/copilot", copilot_cli_version: "1.0.80", deepseek_harness_version: "0.1.1-rc.2", deepseek_harness_packages: ["@deepseek-ai/dsh-agent", "@deepseek-ai/dsh-agent-instructions", "@deepseek-ai/dsh-agent-loop", "@deepseek-ai/dsh-agent-spine-demo", "@deepseek-ai/dsh-anonymous-user-id", "@deepseek-ai/dsh-atomic-write", "@deepseek-ai/dsh-attachment", "@deepseek-ai/dsh-brand", "@deepseek-ai/dsh-code-runtime", "@deepseek-ai/dsh-credentials", "@deepseek-ai/dsh-fs", "@deepseek-ai/dsh-goal", "@deepseek-ai/dsh-goal-round-driver", "@deepseek-ai/dsh-home-paths", "@deepseek-ai/dsh-invariants", "@deepseek-ai/dsh-jobs", "@deepseek-ai/dsh-jobs-local", "@deepseek-ai/dsh-launch-environment", "@deepseek-ai/dsh-llm", "@deepseek-ai/dsh-llm-deepseek", "@deepseek-ai/dsh-llm-retry", "@deepseek-ai/dsh-output-retention", "@deepseek-ai/dsh-sandbox", "@deepseek-ai/dsh-sandbox-policy", "@deepseek-ai/dsh-scope", "@deepseek-ai/dsh-session", "@deepseek-ai/dsh-session-checkpoint-policy", "@deepseek-ai/dsh-session-persistence", "@deepseek-ai/dsh-session-persistence-jsonl", "@deepseek-ai/dsh-session-projection", "@deepseek-ai/dsh-session-title", "@deepseek-ai/dsh-settings", "@deepseek-ai/dsh-shell", "@deepseek-ai/dsh-shell-env", "@deepseek-ai/dsh-skill", "@deepseek-ai/dsh-skill-filesystem", "@deepseek-ai/dsh-subprocess", "@deepseek-ai/dsh-system-prompt", "@deepseek-ai/dsh-timeout", "@deepseek-ai/dsh-tool-bash", "@deepseek-ai/dsh-tool-goal", "@deepseek-ai/dsh-tool-jobs", "@deepseek-ai/dsh-tool-skill", "@deepseek-ai/dsh-tools", "@deepseek-ai/dsh-typert-protocol", "@deepseek-ai/dsh-user-approval"], deepseek_cordis_package_name: "@deepseek-ai/cordis", deepseek_cordis_version: "4.0.1", deepseek_support_packages: { "@deepseek-ai/cordis-plugin-timer": "1.1.3" }, platforms: { "linux-x64": { package_name: "@openai/codex-linux-x64", target: "x86_64-unknown-linux-musl", binary_name: "codex", sha256: "660e159a49e823ac8e5986cb238f73158ce4b957d40d9292f8de90862644b501" }, "win32-x64": { package_name: "@openai/codex-win32-x64", target: "x86_64-pc-windows-msvc", binary_name: "codex.exe", sha256: "e4c11374bd9de8ad5c3b7617fd4654bb7839901edb0863f9930666863c7a021b" } }, pi_coding_agent_package_name: "@earendil-works/pi-coding-agent", pi_ai_package_name: "@earendil-works/pi-ai", pi_version: "0.85.1", pi_support_packages: ["@earendil-works/pi-agent-core", "@earendil-works/pi-telemetry", "@earendil-works/pi-tui", "@earendil-works/chord"], grok_version: "1.0.30", grok_platforms: { "linux-x64": { package_name: "@xai-official/grok-linux-x64", binary_name: "grok", sha256: "504dd6546ab991b75d36698242875ce461489cd1f8cd84285873cb55bd5c7d54" }, "win32-x64": { package_name: "@xai-official/grok-win32-x64", binary_name: "grok.exe", sha256: "48736d1a4342a801ea26d0ecde4c516b653f791399065adaa55973f83abd8867" } } };
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// lib/agent-core/config.ts
|
|
45
|
+
import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
46
|
+
import { dirname, join } from "node:path";
|
|
47
|
+
import { homedir } from "node:os";
|
|
48
|
+
function defaultBaseDir() {
|
|
49
|
+
return join(homedir(), ".vtx");
|
|
50
|
+
}
|
|
51
|
+
function resolveAgentCliConfig(env = process.env) {
|
|
52
|
+
const baseDir = String(env.VTX_HOME || "").trim() || defaultBaseDir();
|
|
53
|
+
const rawProfile = String(env.VTX_PROFILE_ID || "").trim();
|
|
54
|
+
const parsedProfile = rawProfile ? Number(rawProfile) : NaN;
|
|
55
|
+
const explicitStatePath = String(env.VTX_RUNTIME_STATE_PATH || "").trim();
|
|
56
|
+
return {
|
|
57
|
+
apiUrl: String(env.VTX_API_URL || "http://localhost:8000").replace(/\/+$/, ""),
|
|
58
|
+
tokenPath: String(env.VTX_TOKEN_PATH || "").trim() || join(baseDir, "token.json"),
|
|
59
|
+
statePath: explicitStatePath || join(baseDir, "runtime-state.json"),
|
|
60
|
+
runtimeStateDir: explicitStatePath ? null : baseDir,
|
|
61
|
+
runtimeDeviceId: String(env.VTX_RUNTIME_DEVICE_ID || "").trim() || null,
|
|
62
|
+
activeProfileId: Number.isFinite(parsedProfile) && parsedProfile > 0 ? parsedProfile : null,
|
|
63
|
+
outputJson: String(env.VTX_OUTPUT || "").trim().toLowerCase() === "json"
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
async function readStoredAgentAuth(path) {
|
|
67
|
+
try {
|
|
68
|
+
const raw = await readFile(path, "utf8");
|
|
69
|
+
if (!raw.trim()) {
|
|
70
|
+
throw new Error(`VTX token file is empty at ${path}. Run "vtx auth login" or remove the file and retry.`);
|
|
71
|
+
}
|
|
72
|
+
const parsed = JSON.parse(raw);
|
|
73
|
+
const token = String(parsed.token || "").trim();
|
|
74
|
+
return token ? { token } : null;
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (error.code === "ENOENT") {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
if (error instanceof SyntaxError) {
|
|
80
|
+
throw new Error(`VTX token file is not valid JSON at ${path}. Run "vtx auth login" or remove the file and retry.`);
|
|
81
|
+
}
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
var init_config = __esm({
|
|
86
|
+
"lib/agent-core/config.ts"() {
|
|
87
|
+
"use strict";
|
|
88
|
+
init_define_VTX_EXO_POLICY();
|
|
89
|
+
init_define_VTX_GROK_POLICY();
|
|
90
|
+
init_define_VTX_PI_MODEL_POLICY();
|
|
91
|
+
}
|
|
92
|
+
});
|
|
6
93
|
|
|
7
94
|
// lib/agent-core/types.ts
|
|
8
|
-
var AgentClientError
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
95
|
+
var AgentClientError;
|
|
96
|
+
var init_types = __esm({
|
|
97
|
+
"lib/agent-core/types.ts"() {
|
|
98
|
+
"use strict";
|
|
99
|
+
init_define_VTX_EXO_POLICY();
|
|
100
|
+
init_define_VTX_GROK_POLICY();
|
|
101
|
+
init_define_VTX_PI_MODEL_POLICY();
|
|
102
|
+
AgentClientError = class extends Error {
|
|
103
|
+
constructor(payload) {
|
|
104
|
+
super(payload.message);
|
|
105
|
+
this.name = "AgentClientError";
|
|
106
|
+
this.status = payload.status;
|
|
107
|
+
this.code = payload.code;
|
|
108
|
+
this.detail = payload.detail;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
15
111
|
}
|
|
16
|
-
};
|
|
112
|
+
});
|
|
17
113
|
|
|
18
114
|
// lib/agent-core/client.ts
|
|
19
|
-
|
|
20
|
-
"provider_key_slots",
|
|
21
|
-
"hyperliquid_signing_key",
|
|
22
|
-
"local_ai_api_key",
|
|
23
|
-
"openai_compatible_api_key",
|
|
24
|
-
"openai_api_key",
|
|
25
|
-
"anthropic_api_key",
|
|
26
|
-
"gemini_api_key",
|
|
27
|
-
"grok_api_key",
|
|
28
|
-
"groq_api_key",
|
|
29
|
-
"openrouter_api_key",
|
|
30
|
-
"venice_api_key",
|
|
31
|
-
"deepseek_api_key",
|
|
32
|
-
"mistral_api_key",
|
|
33
|
-
"together_api_key",
|
|
34
|
-
"fireworks_api_key",
|
|
35
|
-
"byteplus_api_key",
|
|
36
|
-
"cerebras_api_key",
|
|
37
|
-
"moonshot_api_key",
|
|
38
|
-
"alibaba_cloud_model_studio_api_key",
|
|
39
|
-
"amazon_bedrock_api_key",
|
|
40
|
-
"lightning_api_key"
|
|
41
|
-
]);
|
|
115
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
42
116
|
function normalizeApiUrl(value) {
|
|
43
117
|
const parsed = String(value || "").trim();
|
|
44
118
|
if (!parsed) {
|
|
@@ -84,706 +158,461 @@ function redactSecretResponse(value) {
|
|
|
84
158
|
}
|
|
85
159
|
return redacted;
|
|
86
160
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
161
|
+
function agentCliVersionHeaders() {
|
|
162
|
+
return { "X-VTX-CLI-Version": agent_cli_release_default.package_version };
|
|
163
|
+
}
|
|
164
|
+
function createAgentClient(config) {
|
|
165
|
+
return new VtxAgentClient(config);
|
|
166
|
+
}
|
|
167
|
+
var SECRET_RESPONSE_FIELDS, VtxAgentClient;
|
|
168
|
+
var init_client = __esm({
|
|
169
|
+
"lib/agent-core/client.ts"() {
|
|
170
|
+
"use strict";
|
|
171
|
+
init_define_VTX_EXO_POLICY();
|
|
172
|
+
init_define_VTX_GROK_POLICY();
|
|
173
|
+
init_define_VTX_PI_MODEL_POLICY();
|
|
174
|
+
init_agent_cli_release();
|
|
175
|
+
init_types();
|
|
176
|
+
SECRET_RESPONSE_FIELDS = /* @__PURE__ */ new Set([
|
|
177
|
+
"provider_key_slots",
|
|
178
|
+
"hyperliquid_signing_key",
|
|
179
|
+
"local_ai_api_key",
|
|
180
|
+
"openai_compatible_api_key",
|
|
181
|
+
"openai_api_key",
|
|
182
|
+
"anthropic_api_key",
|
|
183
|
+
"gemini_api_key",
|
|
184
|
+
"grok_api_key",
|
|
185
|
+
"groq_api_key",
|
|
186
|
+
"openrouter_api_key",
|
|
187
|
+
"venice_api_key",
|
|
188
|
+
"deepseek_api_key",
|
|
189
|
+
"mistral_api_key",
|
|
190
|
+
"together_api_key",
|
|
191
|
+
"fireworks_api_key",
|
|
192
|
+
"byteplus_api_key",
|
|
193
|
+
"cerebras_api_key",
|
|
194
|
+
"moonshot_api_key",
|
|
195
|
+
"alibaba_cloud_model_studio_api_key",
|
|
196
|
+
"amazon_bedrock_api_key",
|
|
197
|
+
"lightning_api_key"
|
|
198
|
+
]);
|
|
199
|
+
VtxAgentClient = class _VtxAgentClient {
|
|
200
|
+
constructor(config) {
|
|
201
|
+
this.apiUrl = normalizeApiUrl(config.apiUrl);
|
|
202
|
+
this.token = String(config.token || "").trim() || null;
|
|
203
|
+
this.activeProfileId = config.activeProfileId ?? null;
|
|
204
|
+
this.fetchImpl = config.fetchImpl ?? fetch;
|
|
205
|
+
}
|
|
206
|
+
/** How shared web runtime code (lib/api.ts) reaches the API as this client. */
|
|
207
|
+
apiTransport() {
|
|
208
|
+
const token = this.token;
|
|
209
|
+
return {
|
|
210
|
+
baseUrl: this.apiUrl,
|
|
211
|
+
authorization: () => token ? `Bearer ${token}` : null,
|
|
212
|
+
// The shared runtime's requests name this CLI's version, as the agent
|
|
213
|
+
// client's own requests do.
|
|
214
|
+
headers: agentCliVersionHeaders
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
async request(path, options = {}) {
|
|
218
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
219
|
+
const headers = {
|
|
220
|
+
Accept: "application/json",
|
|
221
|
+
...agentCliVersionHeaders()
|
|
222
|
+
};
|
|
223
|
+
if (this.token) {
|
|
224
|
+
headers.Authorization = `Bearer ${this.token}`;
|
|
225
|
+
}
|
|
226
|
+
const profileId = options.profileId ?? this.activeProfileId;
|
|
227
|
+
if (profileId != null) {
|
|
228
|
+
headers["x-profile-id"] = String(profileId);
|
|
229
|
+
}
|
|
230
|
+
if (options.idempotencyKey) {
|
|
231
|
+
headers["x-idempotency-key"] = options.idempotencyKey;
|
|
232
|
+
}
|
|
233
|
+
if (options.headers) {
|
|
234
|
+
for (const [key, value] of Object.entries(options.headers)) {
|
|
235
|
+
if (value) {
|
|
236
|
+
headers[key] = value;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
let body;
|
|
241
|
+
if (options.body !== void 0) {
|
|
242
|
+
headers["Content-Type"] = "application/json";
|
|
243
|
+
body = JSON.stringify(options.body);
|
|
244
|
+
}
|
|
245
|
+
const response = await this.fetchImpl(`${this.apiUrl}${normalizedPath}`, {
|
|
246
|
+
method: options.method ?? "GET",
|
|
247
|
+
headers,
|
|
248
|
+
body
|
|
249
|
+
});
|
|
250
|
+
if (!response.ok) {
|
|
251
|
+
throw await parseError(response, `VTX API request failed: ${response.status}`);
|
|
252
|
+
}
|
|
253
|
+
if (response.status === 204) {
|
|
254
|
+
return void 0;
|
|
255
|
+
}
|
|
256
|
+
return await response.json();
|
|
257
|
+
}
|
|
258
|
+
whoami() {
|
|
259
|
+
return this.request("/auth/agent-tokens/whoami");
|
|
260
|
+
}
|
|
261
|
+
listTokens() {
|
|
262
|
+
return this.request("/auth/agent-tokens");
|
|
263
|
+
}
|
|
264
|
+
createToken(payload) {
|
|
265
|
+
return this.request("/auth/agent-tokens", { method: "POST", body: payload });
|
|
266
|
+
}
|
|
267
|
+
startBrowserLogin(payload) {
|
|
268
|
+
return this.request("/auth/agent-tokens/login/start", {
|
|
269
|
+
method: "POST",
|
|
270
|
+
body: payload
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
pollBrowserLogin(loginCode, verifier) {
|
|
274
|
+
return this.request("/auth/agent-tokens/login/poll", {
|
|
275
|
+
method: "POST",
|
|
276
|
+
body: { login_code: loginCode, verifier }
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
revokeToken(tokenId, reason = "cli_revoked") {
|
|
280
|
+
return this.request(`/auth/agent-tokens/${tokenId}`, {
|
|
281
|
+
method: "DELETE",
|
|
282
|
+
body: { reason }
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
listProfiles() {
|
|
286
|
+
return this.request("/auth/me/profiles");
|
|
287
|
+
}
|
|
288
|
+
createProfile(profileId) {
|
|
289
|
+
return this.request("/auth/me/profiles", {
|
|
290
|
+
method: "POST",
|
|
291
|
+
profileId
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
updateProfile(profileId, payload) {
|
|
295
|
+
return this.request("/auth/me", {
|
|
296
|
+
method: "PATCH",
|
|
297
|
+
profileId,
|
|
298
|
+
body: payload
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
deleteProfile(profileId) {
|
|
302
|
+
return this.request(`/auth/me/profiles/${profileId}`, {
|
|
303
|
+
method: "DELETE"
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
setProfileExecutionMode(profileId, executionMode) {
|
|
307
|
+
return this.request("/preferences", {
|
|
308
|
+
method: "PUT",
|
|
309
|
+
profileId,
|
|
310
|
+
body: { preferences: { execution_mode: executionMode } }
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
updatePreferences(profileId, preferences) {
|
|
314
|
+
return this.request("/preferences", {
|
|
315
|
+
method: "PUT",
|
|
316
|
+
profileId,
|
|
317
|
+
body: { preferences }
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
getBillingStatus() {
|
|
321
|
+
return this.request("/billing/balance");
|
|
322
|
+
}
|
|
323
|
+
getBillingPrices(profileId) {
|
|
324
|
+
return this.request("/billing/prices", { profileId });
|
|
325
|
+
}
|
|
326
|
+
getRuntimeStatus(profileId) {
|
|
327
|
+
return this.request("/trading/ai/status", { profileId });
|
|
328
|
+
}
|
|
329
|
+
getRuntimeEvents(profileId, options = {}) {
|
|
330
|
+
const params = new URLSearchParams();
|
|
331
|
+
if (options.limit !== void 0) {
|
|
332
|
+
params.set("limit", String(options.limit));
|
|
333
|
+
}
|
|
334
|
+
if (options.eventType) {
|
|
335
|
+
params.set("event_type", options.eventType);
|
|
336
|
+
}
|
|
337
|
+
if (options.clientEventType) {
|
|
338
|
+
params.set("client_event_type", options.clientEventType);
|
|
339
|
+
}
|
|
340
|
+
const query = params.toString();
|
|
341
|
+
return this.request(`/trading/ai/runtime/events${query ? `?${query}` : ""}`, { profileId });
|
|
342
|
+
}
|
|
343
|
+
getRuntimeBootstrap(profileId, leaseToken) {
|
|
344
|
+
return this.request("/trading/ai/runtime/bootstrap", {
|
|
345
|
+
profileId,
|
|
346
|
+
headers: { "x-client-runtime-lease": leaseToken }
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
getLlmConfig(profileId) {
|
|
350
|
+
return this.request("/llm/config", { profileId });
|
|
351
|
+
}
|
|
352
|
+
heartbeatRuntime(profileId, payload, leaseToken) {
|
|
353
|
+
return this.request("/trading/ai/runtime/heartbeat", {
|
|
354
|
+
method: "POST",
|
|
355
|
+
profileId,
|
|
356
|
+
headers: { "x-client-runtime-lease": leaseToken },
|
|
357
|
+
body: payload
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
getTradeHistory(profileId) {
|
|
361
|
+
return this.request("/trading/ai/history", { profileId });
|
|
362
|
+
}
|
|
363
|
+
placeMarketOrder(profileId, payload) {
|
|
364
|
+
return this.request("/trading/market-order", {
|
|
365
|
+
method: "POST",
|
|
366
|
+
profileId,
|
|
367
|
+
idempotencyKey: randomUUID2(),
|
|
368
|
+
body: payload
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
placeLimitOrder(profileId, payload) {
|
|
372
|
+
return this.request("/trading/limit-order", {
|
|
373
|
+
method: "POST",
|
|
374
|
+
profileId,
|
|
375
|
+
idempotencyKey: randomUUID2(),
|
|
376
|
+
body: payload
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
cancelOrder(profileId, payload) {
|
|
380
|
+
return this.request("/trading/cancel-order", {
|
|
381
|
+
method: "POST",
|
|
382
|
+
profileId,
|
|
383
|
+
idempotencyKey: randomUUID2(),
|
|
384
|
+
body: payload
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
getSecretStatus(profileId) {
|
|
388
|
+
return this.request("/auth/me", { profileId });
|
|
389
|
+
}
|
|
390
|
+
async setRuntimeSecret(profileId, fieldName, value) {
|
|
391
|
+
const response = await this.request("/auth/me/client-runtime-secrets", {
|
|
392
|
+
method: "PUT",
|
|
393
|
+
profileId,
|
|
394
|
+
body: { [fieldName]: value }
|
|
395
|
+
});
|
|
396
|
+
return redactSecretResponse(response);
|
|
397
|
+
}
|
|
398
|
+
startBot(profileId, payload = {}) {
|
|
399
|
+
return this.request("/trading/ai/start", {
|
|
400
|
+
method: "POST",
|
|
401
|
+
profileId,
|
|
402
|
+
idempotencyKey: randomUUID2(),
|
|
403
|
+
body: payload
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
stopBot(profileId) {
|
|
407
|
+
return this.request("/trading/ai/stop", {
|
|
408
|
+
method: "POST",
|
|
409
|
+
profileId,
|
|
410
|
+
idempotencyKey: randomUUID2(),
|
|
411
|
+
body: {}
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
startAssistant(profileId) {
|
|
415
|
+
return this.request("/trading/ai/assistant/start", {
|
|
416
|
+
method: "POST",
|
|
417
|
+
profileId,
|
|
418
|
+
idempotencyKey: randomUUID2(),
|
|
419
|
+
body: {}
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
stopAssistant(profileId) {
|
|
423
|
+
return this.request("/trading/ai/assistant/stop", {
|
|
424
|
+
method: "POST",
|
|
425
|
+
profileId,
|
|
426
|
+
idempotencyKey: randomUUID2(),
|
|
427
|
+
body: {}
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
startRuntime(profileId, payload) {
|
|
431
|
+
return this.request("/trading/ai/runtime/session/start", {
|
|
432
|
+
method: "POST",
|
|
433
|
+
profileId,
|
|
434
|
+
idempotencyKey: randomUUID2(),
|
|
435
|
+
body: payload
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
stopRuntime(profileId, payload, leaseToken) {
|
|
439
|
+
const headersLease = String(leaseToken || "").trim();
|
|
440
|
+
const previousFetch = this.fetchImpl;
|
|
441
|
+
if (!headersLease) {
|
|
442
|
+
return this.request("/trading/ai/runtime/session/stop", {
|
|
443
|
+
method: "POST",
|
|
444
|
+
profileId,
|
|
445
|
+
idempotencyKey: randomUUID2(),
|
|
446
|
+
body: payload
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
const withLeaseFetch = async (input, init) => {
|
|
450
|
+
const headers = new Headers(init?.headers);
|
|
451
|
+
headers.set("x-client-runtime-lease", headersLease);
|
|
452
|
+
return previousFetch(input, { ...init, headers });
|
|
453
|
+
};
|
|
454
|
+
return new _VtxAgentClient({
|
|
455
|
+
apiUrl: this.apiUrl,
|
|
456
|
+
token: this.token,
|
|
457
|
+
activeProfileId: this.activeProfileId,
|
|
458
|
+
fetchImpl: withLeaseFetch
|
|
459
|
+
}).request("/trading/ai/runtime/session/stop", {
|
|
460
|
+
method: "POST",
|
|
461
|
+
profileId,
|
|
462
|
+
idempotencyKey: randomUUID2(),
|
|
463
|
+
body: payload
|
|
464
|
+
});
|
|
465
|
+
}
|
|
100
466
|
};
|
|
101
467
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
headers[key] = value;
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
// lib/runtime/hyperliquid-market-symbol.ts
|
|
471
|
+
var configuredCaseSensitiveSymbols, canonicalBaseSymbol, normalizeHyperliquidMarketSymbol;
|
|
472
|
+
var init_hyperliquid_market_symbol = __esm({
|
|
473
|
+
"lib/runtime/hyperliquid-market-symbol.ts"() {
|
|
474
|
+
"use strict";
|
|
475
|
+
init_define_VTX_EXO_POLICY();
|
|
476
|
+
init_define_VTX_GROK_POLICY();
|
|
477
|
+
init_define_VTX_PI_MODEL_POLICY();
|
|
478
|
+
configuredCaseSensitiveSymbols = [];
|
|
479
|
+
canonicalBaseSymbol = (base) => {
|
|
480
|
+
const trimmed = base.trim();
|
|
481
|
+
if (!trimmed) return "";
|
|
482
|
+
const upper = trimmed.toUpperCase();
|
|
483
|
+
for (const candidate of configuredCaseSensitiveSymbols) {
|
|
484
|
+
if (candidate.toUpperCase() === upper) {
|
|
485
|
+
return candidate;
|
|
121
486
|
}
|
|
122
487
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
throw await parseError(response, `VTX API request failed: ${response.status}`);
|
|
136
|
-
}
|
|
137
|
-
if (response.status === 204) {
|
|
138
|
-
return void 0;
|
|
139
|
-
}
|
|
140
|
-
return await response.json();
|
|
141
|
-
}
|
|
142
|
-
whoami() {
|
|
143
|
-
return this.request("/auth/agent-tokens/whoami");
|
|
144
|
-
}
|
|
145
|
-
listTokens() {
|
|
146
|
-
return this.request("/auth/agent-tokens");
|
|
147
|
-
}
|
|
148
|
-
createToken(payload) {
|
|
149
|
-
return this.request("/auth/agent-tokens", { method: "POST", body: payload });
|
|
150
|
-
}
|
|
151
|
-
startBrowserLogin(payload) {
|
|
152
|
-
return this.request("/auth/agent-tokens/login/start", {
|
|
153
|
-
method: "POST",
|
|
154
|
-
body: payload
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
pollBrowserLogin(loginCode, verifier) {
|
|
158
|
-
return this.request("/auth/agent-tokens/login/poll", {
|
|
159
|
-
method: "POST",
|
|
160
|
-
body: { login_code: loginCode, verifier }
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
revokeToken(tokenId, reason = "cli_revoked") {
|
|
164
|
-
return this.request(`/auth/agent-tokens/${tokenId}`, {
|
|
165
|
-
method: "DELETE",
|
|
166
|
-
body: { reason }
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
listProfiles() {
|
|
170
|
-
return this.request("/auth/me/profiles");
|
|
171
|
-
}
|
|
172
|
-
createProfile(profileId) {
|
|
173
|
-
return this.request("/auth/me/profiles", {
|
|
174
|
-
method: "POST",
|
|
175
|
-
profileId
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
updateProfile(profileId, payload) {
|
|
179
|
-
return this.request("/auth/me", {
|
|
180
|
-
method: "PATCH",
|
|
181
|
-
profileId,
|
|
182
|
-
body: payload
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
deleteProfile(profileId) {
|
|
186
|
-
return this.request(`/auth/me/profiles/${profileId}`, {
|
|
187
|
-
method: "DELETE"
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
setProfileExecutionMode(profileId, executionMode) {
|
|
191
|
-
return this.request("/preferences", {
|
|
192
|
-
method: "PUT",
|
|
193
|
-
profileId,
|
|
194
|
-
body: { preferences: { execution_mode: executionMode } }
|
|
195
|
-
});
|
|
488
|
+
return upper;
|
|
489
|
+
};
|
|
490
|
+
normalizeHyperliquidMarketSymbol = (value) => {
|
|
491
|
+
const raw = String(value ?? "").trim();
|
|
492
|
+
if (!raw) return "";
|
|
493
|
+
if (!raw.includes(":")) return canonicalBaseSymbol(raw);
|
|
494
|
+
const [dex, ...rest] = raw.split(":");
|
|
495
|
+
const base = rest.join(":").trim();
|
|
496
|
+
const normalizedDex = dex.trim().toLowerCase();
|
|
497
|
+
if (!normalizedDex || !base) return "";
|
|
498
|
+
return `${normalizedDex}:${canonicalBaseSymbol(base)}`;
|
|
499
|
+
};
|
|
196
500
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
// lib/agent-mcp/server.ts
|
|
504
|
+
function requirePositiveNumber(input, key) {
|
|
505
|
+
const value = Number(input[key]);
|
|
506
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
507
|
+
throw new Error(`${key} must be a positive number`);
|
|
203
508
|
}
|
|
204
|
-
|
|
205
|
-
|
|
509
|
+
return value;
|
|
510
|
+
}
|
|
511
|
+
function optionalPositiveNumber(input, key) {
|
|
512
|
+
if (input[key] === void 0 || input[key] === null || input[key] === "") {
|
|
513
|
+
return void 0;
|
|
206
514
|
}
|
|
207
|
-
|
|
208
|
-
|
|
515
|
+
return requirePositiveNumber(input, key);
|
|
516
|
+
}
|
|
517
|
+
function optionalString(input, key) {
|
|
518
|
+
const value = String(input[key] || "").trim();
|
|
519
|
+
return value || null;
|
|
520
|
+
}
|
|
521
|
+
function botStartPayload(input) {
|
|
522
|
+
const payload = {};
|
|
523
|
+
for (const key of ["symbol", "timeframe", "size"]) {
|
|
524
|
+
const value = optionalString(input, key);
|
|
525
|
+
if (value) {
|
|
526
|
+
payload[key] = value;
|
|
527
|
+
}
|
|
209
528
|
}
|
|
210
|
-
|
|
211
|
-
|
|
529
|
+
const leverage = optionalPositiveNumber(input, "leverage");
|
|
530
|
+
if (leverage !== void 0) {
|
|
531
|
+
payload.leverage = leverage;
|
|
212
532
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
params.set("limit", String(options.limit));
|
|
217
|
-
}
|
|
218
|
-
if (options.eventType) {
|
|
219
|
-
params.set("event_type", options.eventType);
|
|
220
|
-
}
|
|
221
|
-
if (options.clientEventType) {
|
|
222
|
-
params.set("client_event_type", options.clientEventType);
|
|
223
|
-
}
|
|
224
|
-
const query = params.toString();
|
|
225
|
-
return this.request(`/trading/ai/runtime/events${query ? `?${query}` : ""}`, { profileId });
|
|
533
|
+
const interval = optionalPositiveNumber(input, "interval");
|
|
534
|
+
if (interval !== void 0) {
|
|
535
|
+
payload.interval = interval;
|
|
226
536
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
537
|
+
return payload;
|
|
538
|
+
}
|
|
539
|
+
function botConfigurePreferences(input) {
|
|
540
|
+
const preferences = {};
|
|
541
|
+
const trade = {};
|
|
542
|
+
const symbol = optionalString(input, "symbol");
|
|
543
|
+
if (symbol) {
|
|
544
|
+
const normalizedSymbol = normalizeHyperliquidMarketSymbol(symbol);
|
|
545
|
+
preferences.trading_symbols = [normalizedSymbol];
|
|
546
|
+
trade.tradingSymbol = normalizedSymbol;
|
|
232
547
|
}
|
|
233
|
-
|
|
234
|
-
|
|
548
|
+
const timeframe = optionalString(input, "timeframe");
|
|
549
|
+
if (timeframe) {
|
|
550
|
+
preferences.trading_timeframes = [timeframe.trim()];
|
|
551
|
+
trade.chartInterval = timeframe.trim();
|
|
235
552
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
headers: { "x-client-runtime-lease": leaseToken },
|
|
241
|
-
body: payload
|
|
242
|
-
});
|
|
553
|
+
const size = optionalString(input, "size");
|
|
554
|
+
if (size) {
|
|
555
|
+
preferences.ai_trade_amount = Number(size);
|
|
556
|
+
trade.orderSizeUSDC = size;
|
|
243
557
|
}
|
|
244
|
-
|
|
245
|
-
|
|
558
|
+
const leverage = optionalPositiveNumber(input, "leverage");
|
|
559
|
+
if (leverage !== void 0) {
|
|
560
|
+
trade.leverage = leverage;
|
|
246
561
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
profileId,
|
|
251
|
-
idempotencyKey: randomUUID(),
|
|
252
|
-
body: payload
|
|
253
|
-
});
|
|
562
|
+
const interval = optionalPositiveNumber(input, "interval");
|
|
563
|
+
if (interval !== void 0) {
|
|
564
|
+
preferences.trading_interval = interval;
|
|
254
565
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
method: "POST",
|
|
258
|
-
profileId,
|
|
259
|
-
idempotencyKey: randomUUID(),
|
|
260
|
-
body: payload
|
|
261
|
-
});
|
|
566
|
+
if (Object.keys(trade).length > 0) {
|
|
567
|
+
preferences.trade = trade;
|
|
262
568
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
method: "POST",
|
|
266
|
-
profileId,
|
|
267
|
-
idempotencyKey: randomUUID(),
|
|
268
|
-
body: payload
|
|
269
|
-
});
|
|
569
|
+
if (Object.keys(preferences).length === 0) {
|
|
570
|
+
throw new Error("At least one bot configuration field is required");
|
|
270
571
|
}
|
|
271
|
-
|
|
272
|
-
|
|
572
|
+
return preferences;
|
|
573
|
+
}
|
|
574
|
+
function requireOrderSide(input) {
|
|
575
|
+
const side = optionalString(input, "side");
|
|
576
|
+
if (side !== "buy" && side !== "sell") {
|
|
577
|
+
throw new Error("side must be buy or sell");
|
|
273
578
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
return redactSecretResponse(response);
|
|
281
|
-
}
|
|
282
|
-
startBot(profileId, payload = {}) {
|
|
283
|
-
return this.request("/trading/ai/start", {
|
|
284
|
-
method: "POST",
|
|
285
|
-
profileId,
|
|
286
|
-
idempotencyKey: randomUUID(),
|
|
287
|
-
body: payload
|
|
288
|
-
});
|
|
579
|
+
return side;
|
|
580
|
+
}
|
|
581
|
+
function requireSymbol(input) {
|
|
582
|
+
const symbol = optionalString(input, "symbol");
|
|
583
|
+
if (!symbol) {
|
|
584
|
+
throw new Error("symbol is required");
|
|
289
585
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
586
|
+
return normalizeHyperliquidMarketSymbol(symbol);
|
|
587
|
+
}
|
|
588
|
+
function marketOrderPayload(input) {
|
|
589
|
+
const payload = {
|
|
590
|
+
symbol: requireSymbol(input),
|
|
591
|
+
side: requireOrderSide(input),
|
|
592
|
+
size: requirePositiveNumber(input, "size")
|
|
593
|
+
};
|
|
594
|
+
const slippage = optionalPositiveNumber(input, "slippage");
|
|
595
|
+
if (slippage !== void 0) {
|
|
596
|
+
payload.slippage = slippage;
|
|
297
597
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
profileId,
|
|
302
|
-
idempotencyKey: randomUUID(),
|
|
303
|
-
body: {}
|
|
304
|
-
});
|
|
598
|
+
const estimatedPrice = optionalPositiveNumber(input, "estimatedPrice");
|
|
599
|
+
if (estimatedPrice !== void 0) {
|
|
600
|
+
payload.estimated_price = estimatedPrice;
|
|
305
601
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
profileId,
|
|
310
|
-
idempotencyKey: randomUUID(),
|
|
311
|
-
body: {}
|
|
312
|
-
});
|
|
602
|
+
const estimatedPriceTimestampMs = optionalPositiveNumber(input, "estimatedPriceTimestampMs");
|
|
603
|
+
if (estimatedPriceTimestampMs !== void 0) {
|
|
604
|
+
payload.estimated_price_timestamp_ms = estimatedPriceTimestampMs;
|
|
313
605
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
method: "POST",
|
|
317
|
-
profileId,
|
|
318
|
-
idempotencyKey: randomUUID(),
|
|
319
|
-
body: payload
|
|
320
|
-
});
|
|
606
|
+
if (input.reduceOnly === true) {
|
|
607
|
+
payload.reduce_only = true;
|
|
321
608
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
if (!headersLease) {
|
|
326
|
-
return this.request("/trading/ai/runtime/session/stop", {
|
|
327
|
-
method: "POST",
|
|
328
|
-
profileId,
|
|
329
|
-
idempotencyKey: randomUUID(),
|
|
330
|
-
body: payload
|
|
331
|
-
});
|
|
609
|
+
if (input.protection !== void 0) {
|
|
610
|
+
if (input.protection === null || typeof input.protection !== "object" || Array.isArray(input.protection)) {
|
|
611
|
+
throw new Error("protection must be an object with take_profit and/or stop_loss");
|
|
332
612
|
}
|
|
333
|
-
|
|
334
|
-
const headers = new Headers(init?.headers);
|
|
335
|
-
headers.set("x-client-runtime-lease", headersLease);
|
|
336
|
-
return previousFetch(input, { ...init, headers });
|
|
337
|
-
};
|
|
338
|
-
return new _VtxAgentClient({
|
|
339
|
-
apiUrl: this.apiUrl,
|
|
340
|
-
token: this.token,
|
|
341
|
-
activeProfileId: this.activeProfileId,
|
|
342
|
-
fetchImpl: withLeaseFetch
|
|
343
|
-
}).request("/trading/ai/runtime/session/stop", {
|
|
344
|
-
method: "POST",
|
|
345
|
-
profileId,
|
|
346
|
-
idempotencyKey: randomUUID(),
|
|
347
|
-
body: payload
|
|
348
|
-
});
|
|
613
|
+
payload.protection = input.protection;
|
|
349
614
|
}
|
|
350
|
-
|
|
351
|
-
function createAgentClient(config) {
|
|
352
|
-
return new VtxAgentClient(config);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// lib/agent-core/config.ts
|
|
356
|
-
import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
357
|
-
import { dirname, join } from "node:path";
|
|
358
|
-
import { homedir } from "node:os";
|
|
359
|
-
function defaultBaseDir() {
|
|
360
|
-
return join(homedir(), ".vtx");
|
|
361
|
-
}
|
|
362
|
-
function resolveAgentCliConfig(env = process.env) {
|
|
363
|
-
const baseDir = String(env.VTX_HOME || "").trim() || defaultBaseDir();
|
|
364
|
-
const rawProfile = String(env.VTX_PROFILE_ID || "").trim();
|
|
365
|
-
const parsedProfile = rawProfile ? Number(rawProfile) : NaN;
|
|
366
|
-
const explicitStatePath = String(env.VTX_RUNTIME_STATE_PATH || "").trim();
|
|
367
|
-
return {
|
|
368
|
-
apiUrl: String(env.VTX_API_URL || "http://localhost:8000").replace(/\/+$/, ""),
|
|
369
|
-
tokenPath: String(env.VTX_TOKEN_PATH || "").trim() || join(baseDir, "token.json"),
|
|
370
|
-
statePath: explicitStatePath || join(baseDir, "runtime-state.json"),
|
|
371
|
-
runtimeStateDir: explicitStatePath ? null : baseDir,
|
|
372
|
-
runtimeDeviceId: String(env.VTX_RUNTIME_DEVICE_ID || "").trim() || null,
|
|
373
|
-
activeProfileId: Number.isFinite(parsedProfile) && parsedProfile > 0 ? parsedProfile : null,
|
|
374
|
-
outputJson: String(env.VTX_OUTPUT || "").trim().toLowerCase() === "json"
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
async function readStoredAgentAuth(path) {
|
|
378
|
-
try {
|
|
379
|
-
const raw = await readFile(path, "utf8");
|
|
380
|
-
if (!raw.trim()) {
|
|
381
|
-
throw new Error(`VTX token file is empty at ${path}. Run "vtx auth login" or remove the file and retry.`);
|
|
382
|
-
}
|
|
383
|
-
const parsed = JSON.parse(raw);
|
|
384
|
-
const token = String(parsed.token || "").trim();
|
|
385
|
-
return token ? { token } : null;
|
|
386
|
-
} catch (error) {
|
|
387
|
-
if (error.code === "ENOENT") {
|
|
388
|
-
return null;
|
|
389
|
-
}
|
|
390
|
-
if (error instanceof SyntaxError) {
|
|
391
|
-
throw new Error(`VTX token file is not valid JSON at ${path}. Run "vtx auth login" or remove the file and retry.`);
|
|
392
|
-
}
|
|
393
|
-
throw error;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// lib/runtime/hyperliquid-market-symbol.ts
|
|
398
|
-
var configuredCaseSensitiveSymbols = [];
|
|
399
|
-
var canonicalBaseSymbol = (base) => {
|
|
400
|
-
const trimmed = base.trim();
|
|
401
|
-
if (!trimmed) return "";
|
|
402
|
-
const upper = trimmed.toUpperCase();
|
|
403
|
-
for (const candidate of configuredCaseSensitiveSymbols) {
|
|
404
|
-
if (candidate.toUpperCase() === upper) {
|
|
405
|
-
return candidate;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
return upper;
|
|
409
|
-
};
|
|
410
|
-
var normalizeHyperliquidMarketSymbol = (value) => {
|
|
411
|
-
const raw = String(value ?? "").trim();
|
|
412
|
-
if (!raw) return "";
|
|
413
|
-
if (!raw.includes(":")) return canonicalBaseSymbol(raw);
|
|
414
|
-
const [dex, ...rest] = raw.split(":");
|
|
415
|
-
const base = rest.join(":").trim();
|
|
416
|
-
const normalizedDex = dex.trim().toLowerCase();
|
|
417
|
-
if (!normalizedDex || !base) return "";
|
|
418
|
-
return `${normalizedDex}:${canonicalBaseSymbol(base)}`;
|
|
419
|
-
};
|
|
420
|
-
|
|
421
|
-
// lib/agent-mcp/server.ts
|
|
422
|
-
var VTX_MCP_TOOLS = [
|
|
423
|
-
{
|
|
424
|
-
name: "vtx_whoami",
|
|
425
|
-
description: "Inspect the authenticated VTX user and agent-token scopes.",
|
|
426
|
-
inputSchema: { type: "object", properties: {} }
|
|
427
|
-
},
|
|
428
|
-
{
|
|
429
|
-
name: "vtx_profiles_list",
|
|
430
|
-
description: "List profiles owned by the authenticated VTX user.",
|
|
431
|
-
inputSchema: { type: "object", properties: {} }
|
|
432
|
-
},
|
|
433
|
-
{
|
|
434
|
-
name: "vtx_profiles_create",
|
|
435
|
-
description: "Create a profile for the authenticated VTX user. Requires profile:write scope.",
|
|
436
|
-
inputSchema: {
|
|
437
|
-
type: "object",
|
|
438
|
-
properties: { sourceProfileId: { type: "number" } }
|
|
439
|
-
}
|
|
440
|
-
},
|
|
441
|
-
{
|
|
442
|
-
name: "vtx_profiles_update",
|
|
443
|
-
description: "Update the selected profile handle for the authenticated VTX user. Requires profile:write scope.",
|
|
444
|
-
inputSchema: {
|
|
445
|
-
type: "object",
|
|
446
|
-
required: ["profileId", "name"],
|
|
447
|
-
properties: { profileId: { type: "number" }, name: { type: "string" } }
|
|
448
|
-
}
|
|
449
|
-
},
|
|
450
|
-
{
|
|
451
|
-
name: "vtx_profiles_delete",
|
|
452
|
-
description: "Delete a profile owned by the authenticated VTX user. Requires profile:write scope.",
|
|
453
|
-
inputSchema: {
|
|
454
|
-
type: "object",
|
|
455
|
-
required: ["profileId"],
|
|
456
|
-
properties: { profileId: { type: "number" } }
|
|
457
|
-
}
|
|
458
|
-
},
|
|
459
|
-
{
|
|
460
|
-
name: "vtx_profiles_set_mode",
|
|
461
|
-
description: "Set a profile execution mode. Client Mode runs locally with no VTX platform fee; Server Mode uses normal VTX infrastructure and fees.",
|
|
462
|
-
inputSchema: {
|
|
463
|
-
type: "object",
|
|
464
|
-
required: ["profileId", "mode"],
|
|
465
|
-
properties: { profileId: { type: "number" }, mode: { type: "string", enum: ["client", "server"] } }
|
|
466
|
-
}
|
|
467
|
-
},
|
|
468
|
-
{
|
|
469
|
-
name: "vtx_bots_start",
|
|
470
|
-
description: "Start the Server Mode AI Trader for a profile through the normal backend path. Requires bot:control scope and normal Server Mode billing/caps.",
|
|
471
|
-
inputSchema: {
|
|
472
|
-
type: "object",
|
|
473
|
-
required: ["profileId"],
|
|
474
|
-
properties: {
|
|
475
|
-
profileId: { type: "number" },
|
|
476
|
-
symbol: { type: "string" },
|
|
477
|
-
timeframe: { type: "string" },
|
|
478
|
-
size: { type: "string" },
|
|
479
|
-
leverage: { type: "number" },
|
|
480
|
-
interval: { type: "number" }
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
},
|
|
484
|
-
{
|
|
485
|
-
name: "vtx_bots_configure",
|
|
486
|
-
description: "Configure AI Trader profile preferences through the normal preferences API before starting a bot. Requires profile:write scope.",
|
|
487
|
-
inputSchema: {
|
|
488
|
-
type: "object",
|
|
489
|
-
required: ["profileId"],
|
|
490
|
-
properties: {
|
|
491
|
-
profileId: { type: "number" },
|
|
492
|
-
symbol: { type: "string" },
|
|
493
|
-
timeframe: { type: "string" },
|
|
494
|
-
size: { type: "string" },
|
|
495
|
-
leverage: { type: "number" },
|
|
496
|
-
interval: { type: "number" }
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
},
|
|
500
|
-
{
|
|
501
|
-
name: "vtx_bots_stop",
|
|
502
|
-
description: "Stop the Server Mode AI Trader for a profile through the normal backend path. Requires bot:control scope.",
|
|
503
|
-
inputSchema: {
|
|
504
|
-
type: "object",
|
|
505
|
-
required: ["profileId"],
|
|
506
|
-
properties: { profileId: { type: "number" } }
|
|
507
|
-
}
|
|
508
|
-
},
|
|
509
|
-
{
|
|
510
|
-
name: "vtx_bots_status",
|
|
511
|
-
description: "Read bot/runtime status for a profile.",
|
|
512
|
-
inputSchema: {
|
|
513
|
-
type: "object",
|
|
514
|
-
required: ["profileId"],
|
|
515
|
-
properties: { profileId: { type: "number" } }
|
|
516
|
-
}
|
|
517
|
-
},
|
|
518
|
-
{
|
|
519
|
-
name: "vtx_bots_logs",
|
|
520
|
-
description: "Read AI Trader decision history/logs for a profile without executing trades.",
|
|
521
|
-
inputSchema: {
|
|
522
|
-
type: "object",
|
|
523
|
-
required: ["profileId"],
|
|
524
|
-
properties: { profileId: { type: "number" } }
|
|
525
|
-
}
|
|
526
|
-
},
|
|
527
|
-
{
|
|
528
|
-
name: "vtx_runtime_status",
|
|
529
|
-
description: "Read Client Mode runtime status for a profile. Client Mode has no VTX platform fee; Server Mode bots use normal fees.",
|
|
530
|
-
inputSchema: {
|
|
531
|
-
type: "object",
|
|
532
|
-
required: ["profileId"],
|
|
533
|
-
properties: { profileId: { type: "number" } }
|
|
534
|
-
}
|
|
535
|
-
},
|
|
536
|
-
{
|
|
537
|
-
name: "vtx_runtime_events",
|
|
538
|
-
description: "Read Client Mode runtime events for a profile. This is status/history visibility and does not execute local runtime work.",
|
|
539
|
-
inputSchema: {
|
|
540
|
-
type: "object",
|
|
541
|
-
required: ["profileId"],
|
|
542
|
-
properties: {
|
|
543
|
-
profileId: { type: "number" },
|
|
544
|
-
limit: { type: "number" },
|
|
545
|
-
eventType: { type: "string" },
|
|
546
|
-
clientEventType: { type: "string" }
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
},
|
|
550
|
-
{
|
|
551
|
-
name: "vtx_trade_market_order",
|
|
552
|
-
description: "Submit a market order for a profile through the normal Trade page backend path. Requires trading:execute scope and uses the same risk checks, rate limits, and idempotency behavior as a user-submitted order.",
|
|
553
|
-
inputSchema: {
|
|
554
|
-
type: "object",
|
|
555
|
-
required: ["profileId", "symbol", "side", "size"],
|
|
556
|
-
properties: {
|
|
557
|
-
profileId: { type: "number" },
|
|
558
|
-
symbol: { type: "string" },
|
|
559
|
-
side: { type: "string", enum: ["buy", "sell"] },
|
|
560
|
-
size: { type: "number" },
|
|
561
|
-
slippage: { type: "number" },
|
|
562
|
-
estimatedPrice: { type: "number" },
|
|
563
|
-
estimatedPriceTimestampMs: { type: "number" },
|
|
564
|
-
reduceOnly: { type: "boolean" },
|
|
565
|
-
protection: {
|
|
566
|
-
type: "object",
|
|
567
|
-
description: 'Optional fixed TP/SL like the AI page Auto SL/TP: take_profit and/or stop_loss, each { mode: "percent" | "usdc", value }. Placed as reduce-only market triggers from the average fill; not allowed with reduceOnly; no trailing stops.',
|
|
568
|
-
properties: {
|
|
569
|
-
take_profit: { type: "object", required: ["mode", "value"], properties: { mode: { type: "string", enum: ["percent", "usdc"] }, value: { type: "number" } } },
|
|
570
|
-
stop_loss: { type: "object", required: ["mode", "value"], properties: { mode: { type: "string", enum: ["percent", "usdc"] }, value: { type: "number" }, stop_type: { type: "string", enum: ["fixed"] } } }
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
},
|
|
576
|
-
{
|
|
577
|
-
name: "vtx_trade_limit_order",
|
|
578
|
-
description: "Submit a limit order for a profile through the normal Trade page backend path. Requires trading:execute scope and uses the same risk checks, rate limits, and idempotency behavior as a user-submitted order.",
|
|
579
|
-
inputSchema: {
|
|
580
|
-
type: "object",
|
|
581
|
-
required: ["profileId", "symbol", "side", "size", "limitPrice"],
|
|
582
|
-
properties: {
|
|
583
|
-
profileId: { type: "number" },
|
|
584
|
-
symbol: { type: "string" },
|
|
585
|
-
side: { type: "string", enum: ["buy", "sell"] },
|
|
586
|
-
size: { type: "number" },
|
|
587
|
-
limitPrice: { type: "number" },
|
|
588
|
-
reduceOnly: { type: "boolean" }
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
},
|
|
592
|
-
{
|
|
593
|
-
name: "vtx_trade_cancel_order",
|
|
594
|
-
description: "Cancel an open order for a profile through the normal Trade page backend path. Requires trading:execute scope.",
|
|
595
|
-
inputSchema: {
|
|
596
|
-
type: "object",
|
|
597
|
-
required: ["profileId", "symbol", "oid"],
|
|
598
|
-
properties: {
|
|
599
|
-
profileId: { type: "number" },
|
|
600
|
-
symbol: { type: "string" },
|
|
601
|
-
oid: { type: "number" }
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
},
|
|
605
|
-
{
|
|
606
|
-
name: "vtx_tokens_list",
|
|
607
|
-
description: "List agent tokens without returning raw token values.",
|
|
608
|
-
inputSchema: { type: "object", properties: {} }
|
|
609
|
-
},
|
|
610
|
-
{
|
|
611
|
-
name: "vtx_billing_status",
|
|
612
|
-
description: "Read VTX billing balance for the authenticated user.",
|
|
613
|
-
inputSchema: { type: "object", properties: {} }
|
|
614
|
-
},
|
|
615
|
-
{
|
|
616
|
-
name: "vtx_billing_prices",
|
|
617
|
-
description: "Read VTX pricing visibility, including Server Mode trader call fee and model pricing metadata.",
|
|
618
|
-
inputSchema: {
|
|
619
|
-
type: "object",
|
|
620
|
-
properties: { profileId: { type: "number" } }
|
|
621
|
-
}
|
|
622
|
-
},
|
|
623
|
-
{
|
|
624
|
-
name: "vtx_ai_config",
|
|
625
|
-
description: "Read the shared AI/runtime configuration contract used by the web app, CLI, MCP, and Client Mode runtime.",
|
|
626
|
-
inputSchema: {
|
|
627
|
-
type: "object",
|
|
628
|
-
properties: { profileId: { type: "number" } }
|
|
629
|
-
}
|
|
630
|
-
},
|
|
631
|
-
{
|
|
632
|
-
name: "vtx_secrets_status",
|
|
633
|
-
description: "Read secret readiness and masked previews for a profile. Raw stored secret values are not returned.",
|
|
634
|
-
inputSchema: {
|
|
635
|
-
type: "object",
|
|
636
|
-
required: ["profileId"],
|
|
637
|
-
properties: { profileId: { type: "number" } }
|
|
638
|
-
}
|
|
639
|
-
},
|
|
640
|
-
{
|
|
641
|
-
name: "vtx_secrets_set",
|
|
642
|
-
description: "Write or rotate one runtime secret for a profile. The response redacts raw secret values.",
|
|
643
|
-
inputSchema: {
|
|
644
|
-
type: "object",
|
|
645
|
-
required: ["profileId", "field", "value"],
|
|
646
|
-
properties: {
|
|
647
|
-
profileId: { type: "number" },
|
|
648
|
-
field: { type: "string" },
|
|
649
|
-
value: { type: "string" }
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
];
|
|
654
|
-
var WRITABLE_SECRET_FIELDS = /* @__PURE__ */ new Set([
|
|
655
|
-
"hyperliquid_signing_key",
|
|
656
|
-
"local_ai_api_key",
|
|
657
|
-
"openai_compatible_api_key",
|
|
658
|
-
"openai_api_key",
|
|
659
|
-
"anthropic_api_key",
|
|
660
|
-
"gemini_api_key",
|
|
661
|
-
"grok_api_key",
|
|
662
|
-
"groq_api_key",
|
|
663
|
-
"openrouter_api_key",
|
|
664
|
-
"venice_api_key",
|
|
665
|
-
"deepseek_api_key",
|
|
666
|
-
"mistral_api_key",
|
|
667
|
-
"together_api_key",
|
|
668
|
-
"fireworks_api_key",
|
|
669
|
-
"byteplus_api_key",
|
|
670
|
-
"cerebras_api_key",
|
|
671
|
-
"moonshot_api_key",
|
|
672
|
-
"alibaba_cloud_model_studio_api_key",
|
|
673
|
-
"lightning_api_key"
|
|
674
|
-
]);
|
|
675
|
-
function requirePositiveNumber(input, key) {
|
|
676
|
-
const value = Number(input[key]);
|
|
677
|
-
if (!Number.isFinite(value) || value <= 0) {
|
|
678
|
-
throw new Error(`${key} must be a positive number`);
|
|
679
|
-
}
|
|
680
|
-
return value;
|
|
681
|
-
}
|
|
682
|
-
function optionalPositiveNumber(input, key) {
|
|
683
|
-
if (input[key] === void 0 || input[key] === null || input[key] === "") {
|
|
684
|
-
return void 0;
|
|
685
|
-
}
|
|
686
|
-
return requirePositiveNumber(input, key);
|
|
687
|
-
}
|
|
688
|
-
function optionalString(input, key) {
|
|
689
|
-
const value = String(input[key] || "").trim();
|
|
690
|
-
return value || null;
|
|
691
|
-
}
|
|
692
|
-
function botStartPayload(input) {
|
|
693
|
-
const payload = {};
|
|
694
|
-
for (const key of ["symbol", "timeframe", "size"]) {
|
|
695
|
-
const value = optionalString(input, key);
|
|
696
|
-
if (value) {
|
|
697
|
-
payload[key] = value;
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
const leverage = optionalPositiveNumber(input, "leverage");
|
|
701
|
-
if (leverage !== void 0) {
|
|
702
|
-
payload.leverage = leverage;
|
|
703
|
-
}
|
|
704
|
-
const interval = optionalPositiveNumber(input, "interval");
|
|
705
|
-
if (interval !== void 0) {
|
|
706
|
-
payload.interval = interval;
|
|
707
|
-
}
|
|
708
|
-
return payload;
|
|
709
|
-
}
|
|
710
|
-
function botConfigurePreferences(input) {
|
|
711
|
-
const preferences = {};
|
|
712
|
-
const trade = {};
|
|
713
|
-
const symbol = optionalString(input, "symbol");
|
|
714
|
-
if (symbol) {
|
|
715
|
-
const normalizedSymbol = normalizeHyperliquidMarketSymbol(symbol);
|
|
716
|
-
preferences.trading_symbols = [normalizedSymbol];
|
|
717
|
-
trade.tradingSymbol = normalizedSymbol;
|
|
718
|
-
}
|
|
719
|
-
const timeframe = optionalString(input, "timeframe");
|
|
720
|
-
if (timeframe) {
|
|
721
|
-
preferences.trading_timeframes = [timeframe.trim()];
|
|
722
|
-
trade.chartInterval = timeframe.trim();
|
|
723
|
-
}
|
|
724
|
-
const size = optionalString(input, "size");
|
|
725
|
-
if (size) {
|
|
726
|
-
preferences.ai_trade_amount = Number(size);
|
|
727
|
-
trade.orderSizeUSDC = size;
|
|
728
|
-
}
|
|
729
|
-
const leverage = optionalPositiveNumber(input, "leverage");
|
|
730
|
-
if (leverage !== void 0) {
|
|
731
|
-
trade.leverage = leverage;
|
|
732
|
-
}
|
|
733
|
-
const interval = optionalPositiveNumber(input, "interval");
|
|
734
|
-
if (interval !== void 0) {
|
|
735
|
-
preferences.trading_interval = interval;
|
|
736
|
-
}
|
|
737
|
-
if (Object.keys(trade).length > 0) {
|
|
738
|
-
preferences.trade = trade;
|
|
739
|
-
}
|
|
740
|
-
if (Object.keys(preferences).length === 0) {
|
|
741
|
-
throw new Error("At least one bot configuration field is required");
|
|
742
|
-
}
|
|
743
|
-
return preferences;
|
|
744
|
-
}
|
|
745
|
-
function requireOrderSide(input) {
|
|
746
|
-
const side = optionalString(input, "side");
|
|
747
|
-
if (side !== "buy" && side !== "sell") {
|
|
748
|
-
throw new Error("side must be buy or sell");
|
|
749
|
-
}
|
|
750
|
-
return side;
|
|
751
|
-
}
|
|
752
|
-
function requireSymbol(input) {
|
|
753
|
-
const symbol = optionalString(input, "symbol");
|
|
754
|
-
if (!symbol) {
|
|
755
|
-
throw new Error("symbol is required");
|
|
756
|
-
}
|
|
757
|
-
return normalizeHyperliquidMarketSymbol(symbol);
|
|
758
|
-
}
|
|
759
|
-
function marketOrderPayload(input) {
|
|
760
|
-
const payload = {
|
|
761
|
-
symbol: requireSymbol(input),
|
|
762
|
-
side: requireOrderSide(input),
|
|
763
|
-
size: requirePositiveNumber(input, "size")
|
|
764
|
-
};
|
|
765
|
-
const slippage = optionalPositiveNumber(input, "slippage");
|
|
766
|
-
if (slippage !== void 0) {
|
|
767
|
-
payload.slippage = slippage;
|
|
768
|
-
}
|
|
769
|
-
const estimatedPrice = optionalPositiveNumber(input, "estimatedPrice");
|
|
770
|
-
if (estimatedPrice !== void 0) {
|
|
771
|
-
payload.estimated_price = estimatedPrice;
|
|
772
|
-
}
|
|
773
|
-
const estimatedPriceTimestampMs = optionalPositiveNumber(input, "estimatedPriceTimestampMs");
|
|
774
|
-
if (estimatedPriceTimestampMs !== void 0) {
|
|
775
|
-
payload.estimated_price_timestamp_ms = estimatedPriceTimestampMs;
|
|
776
|
-
}
|
|
777
|
-
if (input.reduceOnly === true) {
|
|
778
|
-
payload.reduce_only = true;
|
|
779
|
-
}
|
|
780
|
-
if (input.protection !== void 0) {
|
|
781
|
-
if (input.protection === null || typeof input.protection !== "object" || Array.isArray(input.protection)) {
|
|
782
|
-
throw new Error("protection must be an object with take_profit and/or stop_loss");
|
|
783
|
-
}
|
|
784
|
-
payload.protection = input.protection;
|
|
785
|
-
}
|
|
786
|
-
return payload;
|
|
615
|
+
return payload;
|
|
787
616
|
}
|
|
788
617
|
function limitOrderPayload(input) {
|
|
789
618
|
return {
|
|
@@ -904,57 +733,327 @@ async function callVtxMcpTool(client, name, input = {}) {
|
|
|
904
733
|
}
|
|
905
734
|
throw new Error(`Unknown VTX MCP tool: ${name}`);
|
|
906
735
|
}
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
}
|
|
922
|
-
function stringifyToolResult(result) {
|
|
923
|
-
if (typeof result === "string") {
|
|
924
|
-
return result;
|
|
925
|
-
}
|
|
926
|
-
return JSON.stringify(result, null, 2);
|
|
927
|
-
}
|
|
928
|
-
async function handleMcpJsonRpcRequest(request, clientFactory = createDefaultMcpClient) {
|
|
929
|
-
const id = requestId(request);
|
|
930
|
-
const method = String(request.method || "").trim();
|
|
931
|
-
if (!method) {
|
|
932
|
-
return jsonRpcError(id, -32600, "Invalid JSON-RPC request.");
|
|
933
|
-
}
|
|
934
|
-
if (method === "initialize") {
|
|
935
|
-
return jsonRpcResult(id, {
|
|
936
|
-
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
937
|
-
capabilities: {
|
|
938
|
-
tools: {}
|
|
736
|
+
var VTX_MCP_TOOLS, WRITABLE_SECRET_FIELDS;
|
|
737
|
+
var init_server = __esm({
|
|
738
|
+
"lib/agent-mcp/server.ts"() {
|
|
739
|
+
"use strict";
|
|
740
|
+
init_define_VTX_EXO_POLICY();
|
|
741
|
+
init_define_VTX_GROK_POLICY();
|
|
742
|
+
init_define_VTX_PI_MODEL_POLICY();
|
|
743
|
+
init_client();
|
|
744
|
+
init_config();
|
|
745
|
+
init_hyperliquid_market_symbol();
|
|
746
|
+
VTX_MCP_TOOLS = [
|
|
747
|
+
{
|
|
748
|
+
name: "vtx_whoami",
|
|
749
|
+
description: "Inspect the authenticated VTX user and agent-token scopes.",
|
|
750
|
+
inputSchema: { type: "object", properties: {} }
|
|
939
751
|
},
|
|
940
|
-
|
|
941
|
-
name: "
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
752
|
+
{
|
|
753
|
+
name: "vtx_profiles_list",
|
|
754
|
+
description: "List profiles owned by the authenticated VTX user.",
|
|
755
|
+
inputSchema: { type: "object", properties: {} }
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
name: "vtx_profiles_create",
|
|
759
|
+
description: "Create a profile for the authenticated VTX user. Requires profile:write scope.",
|
|
760
|
+
inputSchema: {
|
|
761
|
+
type: "object",
|
|
762
|
+
properties: { sourceProfileId: { type: "number" } }
|
|
763
|
+
}
|
|
764
|
+
},
|
|
765
|
+
{
|
|
766
|
+
name: "vtx_profiles_update",
|
|
767
|
+
description: "Update the selected profile handle for the authenticated VTX user. Requires profile:write scope.",
|
|
768
|
+
inputSchema: {
|
|
769
|
+
type: "object",
|
|
770
|
+
required: ["profileId", "name"],
|
|
771
|
+
properties: { profileId: { type: "number" }, name: { type: "string" } }
|
|
772
|
+
}
|
|
773
|
+
},
|
|
774
|
+
{
|
|
775
|
+
name: "vtx_profiles_delete",
|
|
776
|
+
description: "Delete a profile owned by the authenticated VTX user. Requires profile:write scope.",
|
|
777
|
+
inputSchema: {
|
|
778
|
+
type: "object",
|
|
779
|
+
required: ["profileId"],
|
|
780
|
+
properties: { profileId: { type: "number" } }
|
|
781
|
+
}
|
|
782
|
+
},
|
|
783
|
+
{
|
|
784
|
+
name: "vtx_profiles_set_mode",
|
|
785
|
+
description: "Set a profile execution mode. Client Mode runs locally with no VTX platform fee; Server Mode uses normal VTX infrastructure and fees.",
|
|
786
|
+
inputSchema: {
|
|
787
|
+
type: "object",
|
|
788
|
+
required: ["profileId", "mode"],
|
|
789
|
+
properties: { profileId: { type: "number" }, mode: { type: "string", enum: ["client", "server"] } }
|
|
790
|
+
}
|
|
791
|
+
},
|
|
792
|
+
{
|
|
793
|
+
name: "vtx_bots_start",
|
|
794
|
+
description: "Start the Server Mode AI Trader for a profile through the normal backend path. Requires bot:control scope and normal Server Mode billing/caps.",
|
|
795
|
+
inputSchema: {
|
|
796
|
+
type: "object",
|
|
797
|
+
required: ["profileId"],
|
|
798
|
+
properties: {
|
|
799
|
+
profileId: { type: "number" },
|
|
800
|
+
symbol: { type: "string" },
|
|
801
|
+
timeframe: { type: "string" },
|
|
802
|
+
size: { type: "string" },
|
|
803
|
+
leverage: { type: "number" },
|
|
804
|
+
interval: { type: "number" }
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
},
|
|
808
|
+
{
|
|
809
|
+
name: "vtx_bots_configure",
|
|
810
|
+
description: "Configure AI Trader profile preferences through the normal preferences API before starting a bot. Requires profile:write scope.",
|
|
811
|
+
inputSchema: {
|
|
812
|
+
type: "object",
|
|
813
|
+
required: ["profileId"],
|
|
814
|
+
properties: {
|
|
815
|
+
profileId: { type: "number" },
|
|
816
|
+
symbol: { type: "string" },
|
|
817
|
+
timeframe: { type: "string" },
|
|
818
|
+
size: { type: "string" },
|
|
819
|
+
leverage: { type: "number" },
|
|
820
|
+
interval: { type: "number" }
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
},
|
|
824
|
+
{
|
|
825
|
+
name: "vtx_bots_stop",
|
|
826
|
+
description: "Stop the Server Mode AI Trader for a profile through the normal backend path. Requires bot:control scope.",
|
|
827
|
+
inputSchema: {
|
|
828
|
+
type: "object",
|
|
829
|
+
required: ["profileId"],
|
|
830
|
+
properties: { profileId: { type: "number" } }
|
|
831
|
+
}
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
name: "vtx_bots_status",
|
|
835
|
+
description: "Read bot/runtime status for a profile.",
|
|
836
|
+
inputSchema: {
|
|
837
|
+
type: "object",
|
|
838
|
+
required: ["profileId"],
|
|
839
|
+
properties: { profileId: { type: "number" } }
|
|
840
|
+
}
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
name: "vtx_bots_logs",
|
|
844
|
+
description: "Read AI Trader decision history/logs for a profile without executing trades.",
|
|
845
|
+
inputSchema: {
|
|
846
|
+
type: "object",
|
|
847
|
+
required: ["profileId"],
|
|
848
|
+
properties: { profileId: { type: "number" } }
|
|
849
|
+
}
|
|
850
|
+
},
|
|
851
|
+
{
|
|
852
|
+
name: "vtx_runtime_status",
|
|
853
|
+
description: "Read Client Mode runtime status for a profile. Client Mode has no VTX platform fee; Server Mode bots use normal fees.",
|
|
854
|
+
inputSchema: {
|
|
855
|
+
type: "object",
|
|
856
|
+
required: ["profileId"],
|
|
857
|
+
properties: { profileId: { type: "number" } }
|
|
858
|
+
}
|
|
859
|
+
},
|
|
860
|
+
{
|
|
861
|
+
name: "vtx_runtime_events",
|
|
862
|
+
description: "Read Client Mode runtime events for a profile. This is status/history visibility and does not execute local runtime work.",
|
|
863
|
+
inputSchema: {
|
|
864
|
+
type: "object",
|
|
865
|
+
required: ["profileId"],
|
|
866
|
+
properties: {
|
|
867
|
+
profileId: { type: "number" },
|
|
868
|
+
limit: { type: "number" },
|
|
869
|
+
eventType: { type: "string" },
|
|
870
|
+
clientEventType: { type: "string" }
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
},
|
|
874
|
+
{
|
|
875
|
+
name: "vtx_trade_market_order",
|
|
876
|
+
description: "Submit a market order for a profile through the normal Trade page backend path. Requires trading:execute scope and uses the same risk checks, rate limits, and idempotency behavior as a user-submitted order.",
|
|
877
|
+
inputSchema: {
|
|
878
|
+
type: "object",
|
|
879
|
+
required: ["profileId", "symbol", "side", "size"],
|
|
880
|
+
properties: {
|
|
881
|
+
profileId: { type: "number" },
|
|
882
|
+
symbol: { type: "string" },
|
|
883
|
+
side: { type: "string", enum: ["buy", "sell"] },
|
|
884
|
+
size: { type: "number" },
|
|
885
|
+
slippage: { type: "number" },
|
|
886
|
+
estimatedPrice: { type: "number" },
|
|
887
|
+
estimatedPriceTimestampMs: { type: "number" },
|
|
888
|
+
reduceOnly: { type: "boolean" },
|
|
889
|
+
protection: {
|
|
890
|
+
type: "object",
|
|
891
|
+
description: 'Optional fixed TP/SL like the AI page Auto SL/TP: take_profit and/or stop_loss, each { mode: "percent" | "usdc", value }. Placed as reduce-only market triggers from the average fill; not allowed with reduceOnly; no trailing stops.',
|
|
892
|
+
properties: {
|
|
893
|
+
take_profit: { type: "object", required: ["mode", "value"], properties: { mode: { type: "string", enum: ["percent", "usdc"] }, value: { type: "number" } } },
|
|
894
|
+
stop_loss: { type: "object", required: ["mode", "value"], properties: { mode: { type: "string", enum: ["percent", "usdc"] }, value: { type: "number" }, stop_type: { type: "string", enum: ["fixed"] } } }
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
},
|
|
900
|
+
{
|
|
901
|
+
name: "vtx_trade_limit_order",
|
|
902
|
+
description: "Submit a limit order for a profile through the normal Trade page backend path. Requires trading:execute scope and uses the same risk checks, rate limits, and idempotency behavior as a user-submitted order.",
|
|
903
|
+
inputSchema: {
|
|
904
|
+
type: "object",
|
|
905
|
+
required: ["profileId", "symbol", "side", "size", "limitPrice"],
|
|
906
|
+
properties: {
|
|
907
|
+
profileId: { type: "number" },
|
|
908
|
+
symbol: { type: "string" },
|
|
909
|
+
side: { type: "string", enum: ["buy", "sell"] },
|
|
910
|
+
size: { type: "number" },
|
|
911
|
+
limitPrice: { type: "number" },
|
|
912
|
+
reduceOnly: { type: "boolean" }
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
},
|
|
916
|
+
{
|
|
917
|
+
name: "vtx_trade_cancel_order",
|
|
918
|
+
description: "Cancel an open order for a profile through the normal Trade page backend path. Requires trading:execute scope.",
|
|
919
|
+
inputSchema: {
|
|
920
|
+
type: "object",
|
|
921
|
+
required: ["profileId", "symbol", "oid"],
|
|
922
|
+
properties: {
|
|
923
|
+
profileId: { type: "number" },
|
|
924
|
+
symbol: { type: "string" },
|
|
925
|
+
oid: { type: "number" }
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
},
|
|
929
|
+
{
|
|
930
|
+
name: "vtx_tokens_list",
|
|
931
|
+
description: "List agent tokens without returning raw token values.",
|
|
932
|
+
inputSchema: { type: "object", properties: {} }
|
|
933
|
+
},
|
|
934
|
+
{
|
|
935
|
+
name: "vtx_billing_status",
|
|
936
|
+
description: "Read VTX billing balance for the authenticated user.",
|
|
937
|
+
inputSchema: { type: "object", properties: {} }
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
name: "vtx_billing_prices",
|
|
941
|
+
description: "Read VTX pricing visibility, including Server Mode trader call fee and model pricing metadata.",
|
|
942
|
+
inputSchema: {
|
|
943
|
+
type: "object",
|
|
944
|
+
properties: { profileId: { type: "number" } }
|
|
945
|
+
}
|
|
946
|
+
},
|
|
947
|
+
{
|
|
948
|
+
name: "vtx_ai_config",
|
|
949
|
+
description: "Read the shared AI/runtime configuration contract used by the web app, CLI, MCP, and Client Mode runtime.",
|
|
950
|
+
inputSchema: {
|
|
951
|
+
type: "object",
|
|
952
|
+
properties: { profileId: { type: "number" } }
|
|
953
|
+
}
|
|
954
|
+
},
|
|
955
|
+
{
|
|
956
|
+
name: "vtx_secrets_status",
|
|
957
|
+
description: "Read secret readiness and masked previews for a profile. Raw stored secret values are not returned.",
|
|
958
|
+
inputSchema: {
|
|
959
|
+
type: "object",
|
|
960
|
+
required: ["profileId"],
|
|
961
|
+
properties: { profileId: { type: "number" } }
|
|
962
|
+
}
|
|
963
|
+
},
|
|
964
|
+
{
|
|
965
|
+
name: "vtx_secrets_set",
|
|
966
|
+
description: "Write or rotate one runtime secret for a profile. The response redacts raw secret values.",
|
|
967
|
+
inputSchema: {
|
|
968
|
+
type: "object",
|
|
969
|
+
required: ["profileId", "field", "value"],
|
|
970
|
+
properties: {
|
|
971
|
+
profileId: { type: "number" },
|
|
972
|
+
field: { type: "string" },
|
|
973
|
+
value: { type: "string" }
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
];
|
|
978
|
+
WRITABLE_SECRET_FIELDS = /* @__PURE__ */ new Set([
|
|
979
|
+
"hyperliquid_signing_key",
|
|
980
|
+
"local_ai_api_key",
|
|
981
|
+
"openai_compatible_api_key",
|
|
982
|
+
"openai_api_key",
|
|
983
|
+
"anthropic_api_key",
|
|
984
|
+
"gemini_api_key",
|
|
985
|
+
"grok_api_key",
|
|
986
|
+
"groq_api_key",
|
|
987
|
+
"openrouter_api_key",
|
|
988
|
+
"venice_api_key",
|
|
989
|
+
"deepseek_api_key",
|
|
990
|
+
"mistral_api_key",
|
|
991
|
+
"together_api_key",
|
|
992
|
+
"fireworks_api_key",
|
|
993
|
+
"byteplus_api_key",
|
|
994
|
+
"cerebras_api_key",
|
|
995
|
+
"moonshot_api_key",
|
|
996
|
+
"alibaba_cloud_model_studio_api_key",
|
|
997
|
+
"lightning_api_key"
|
|
998
|
+
]);
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
|
|
1002
|
+
// lib/agent-mcp/stdio.ts
|
|
1003
|
+
var stdio_exports = {};
|
|
1004
|
+
__export(stdio_exports, {
|
|
1005
|
+
encodeMcpFrame: () => encodeMcpFrame,
|
|
1006
|
+
handleMcpJsonRpcRequest: () => handleMcpJsonRpcRequest,
|
|
1007
|
+
runVtxMcpStdioServer: () => runVtxMcpStdioServer
|
|
1008
|
+
});
|
|
1009
|
+
function jsonRpcResult(id, result) {
|
|
1010
|
+
return { jsonrpc: "2.0", id, result };
|
|
1011
|
+
}
|
|
1012
|
+
function jsonRpcError(id, code, message, data) {
|
|
1013
|
+
return { jsonrpc: "2.0", id, error: { code, message, data } };
|
|
1014
|
+
}
|
|
1015
|
+
function requestId(request) {
|
|
1016
|
+
return request.id === void 0 ? null : request.id;
|
|
1017
|
+
}
|
|
1018
|
+
function isNotification(request) {
|
|
1019
|
+
return request.id === void 0;
|
|
1020
|
+
}
|
|
1021
|
+
function stringifyToolResult(result) {
|
|
1022
|
+
if (typeof result === "string") {
|
|
1023
|
+
return result;
|
|
1024
|
+
}
|
|
1025
|
+
return JSON.stringify(result, null, 2);
|
|
1026
|
+
}
|
|
1027
|
+
async function handleMcpJsonRpcRequest(request, clientFactory = createDefaultMcpClient) {
|
|
1028
|
+
const id = requestId(request);
|
|
1029
|
+
const method = String(request.method || "").trim();
|
|
1030
|
+
if (!method) {
|
|
1031
|
+
return jsonRpcError(id, -32600, "Invalid JSON-RPC request.");
|
|
1032
|
+
}
|
|
1033
|
+
if (method === "initialize") {
|
|
1034
|
+
return jsonRpcResult(id, {
|
|
1035
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
1036
|
+
capabilities: {
|
|
1037
|
+
tools: {}
|
|
1038
|
+
},
|
|
1039
|
+
serverInfo: {
|
|
1040
|
+
name: "vtx-macro",
|
|
1041
|
+
version: agent_cli_release_default.package_version
|
|
1042
|
+
}
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
if (method === "notifications/initialized") {
|
|
1046
|
+
return null;
|
|
1047
|
+
}
|
|
1048
|
+
if (method === "ping") {
|
|
1049
|
+
return jsonRpcResult(id, {});
|
|
1050
|
+
}
|
|
1051
|
+
if (method === "tools/list") {
|
|
1052
|
+
return jsonRpcResult(id, { tools: VTX_MCP_TOOLS });
|
|
1053
|
+
}
|
|
1054
|
+
if (method === "tools/call") {
|
|
1055
|
+
const params = request.params || {};
|
|
1056
|
+
const name = String(params.name || "").trim();
|
|
958
1057
|
const args = params.arguments && typeof params.arguments === "object" ? params.arguments : {};
|
|
959
1058
|
if (!name) {
|
|
960
1059
|
return jsonRpcError(id, -32602, "Tool name is required.");
|
|
@@ -1075,6 +1174,2416 @@ async function runVtxMcpStdioServer(input = process.stdin, output = process.stdo
|
|
|
1075
1174
|
}
|
|
1076
1175
|
}
|
|
1077
1176
|
}
|
|
1177
|
+
var MCP_PROTOCOL_VERSION;
|
|
1178
|
+
var init_stdio = __esm({
|
|
1179
|
+
"lib/agent-mcp/stdio.ts"() {
|
|
1180
|
+
"use strict";
|
|
1181
|
+
init_define_VTX_EXO_POLICY();
|
|
1182
|
+
init_define_VTX_GROK_POLICY();
|
|
1183
|
+
init_define_VTX_PI_MODEL_POLICY();
|
|
1184
|
+
init_agent_cli_release();
|
|
1185
|
+
init_server();
|
|
1186
|
+
MCP_PROTOCOL_VERSION = "2024-11-05";
|
|
1187
|
+
}
|
|
1188
|
+
});
|
|
1078
1189
|
|
|
1079
1190
|
// bin/vtx-mcp.ts
|
|
1080
|
-
|
|
1191
|
+
init_define_VTX_EXO_POLICY();
|
|
1192
|
+
init_define_VTX_GROK_POLICY();
|
|
1193
|
+
init_define_VTX_PI_MODEL_POLICY();
|
|
1194
|
+
|
|
1195
|
+
// lib/cli-release/updater.ts
|
|
1196
|
+
init_define_VTX_EXO_POLICY();
|
|
1197
|
+
init_define_VTX_GROK_POLICY();
|
|
1198
|
+
init_define_VTX_PI_MODEL_POLICY();
|
|
1199
|
+
init_agent_cli_release();
|
|
1200
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
1201
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
1202
|
+
import { access, appendFile, chmod, lstat as lstat2, mkdir as mkdir3, readFile as readFile3, readdir as readdir3, realpath as realpath2, rename as rename2, rm as rm3 } from "node:fs/promises";
|
|
1203
|
+
import { constants as osConstants, homedir as homedir3 } from "node:os";
|
|
1204
|
+
import { dirname as dirname3, isAbsolute as isAbsolute2, join as join3, relative as relative2, resolve as resolve2, win32 as windowsPath2 } from "node:path";
|
|
1205
|
+
|
|
1206
|
+
// lib/runtime-policy.generated.json
|
|
1207
|
+
var runtime_policy_generated_default = {
|
|
1208
|
+
ui: {
|
|
1209
|
+
performance_defaults: {
|
|
1210
|
+
default_timeframe: "30d"
|
|
1211
|
+
},
|
|
1212
|
+
execution_metadata_retry: {
|
|
1213
|
+
max_attempts: 3,
|
|
1214
|
+
base_delay_milliseconds: 2e3,
|
|
1215
|
+
max_delay_milliseconds: 3e4
|
|
1216
|
+
},
|
|
1217
|
+
auth_bootstrap_retry: {
|
|
1218
|
+
max_attempts: 3,
|
|
1219
|
+
jitter_milliseconds: 750,
|
|
1220
|
+
base_delay_milliseconds: 500,
|
|
1221
|
+
min_delay_milliseconds: 250,
|
|
1222
|
+
max_delay_milliseconds: 1e4
|
|
1223
|
+
},
|
|
1224
|
+
trade_refresh_policy: {
|
|
1225
|
+
server_config_stream_timeout_ms: 1e4,
|
|
1226
|
+
config_retry_interval_ms: 5e3,
|
|
1227
|
+
copy_trading_status_poll_ms: 5e3,
|
|
1228
|
+
post_execution_refresh_delay_ms: 3e3,
|
|
1229
|
+
account_read_backoff_min_ms: 2e3,
|
|
1230
|
+
account_read_backoff_max_ms: 6e4
|
|
1231
|
+
},
|
|
1232
|
+
deployment_reload_observability: {
|
|
1233
|
+
bootstrap_max_error_events_per_page: 4,
|
|
1234
|
+
bootstrap_max_profiles: 10
|
|
1235
|
+
}
|
|
1236
|
+
},
|
|
1237
|
+
ai_dashboard: {
|
|
1238
|
+
market_news_bounds: {
|
|
1239
|
+
max_items_min: 1,
|
|
1240
|
+
max_items_max: 100,
|
|
1241
|
+
lookback_hours_min: 1,
|
|
1242
|
+
lookback_hours_max: 168,
|
|
1243
|
+
max_items_steps: [
|
|
1244
|
+
1,
|
|
1245
|
+
2,
|
|
1246
|
+
3,
|
|
1247
|
+
4,
|
|
1248
|
+
5,
|
|
1249
|
+
6,
|
|
1250
|
+
7,
|
|
1251
|
+
8,
|
|
1252
|
+
9,
|
|
1253
|
+
10,
|
|
1254
|
+
15,
|
|
1255
|
+
20,
|
|
1256
|
+
25,
|
|
1257
|
+
30,
|
|
1258
|
+
40,
|
|
1259
|
+
50,
|
|
1260
|
+
60,
|
|
1261
|
+
75,
|
|
1262
|
+
90,
|
|
1263
|
+
100
|
|
1264
|
+
],
|
|
1265
|
+
lookback_hours_steps: [
|
|
1266
|
+
1,
|
|
1267
|
+
2,
|
|
1268
|
+
4,
|
|
1269
|
+
8,
|
|
1270
|
+
12,
|
|
1271
|
+
24,
|
|
1272
|
+
48,
|
|
1273
|
+
72,
|
|
1274
|
+
168
|
|
1275
|
+
]
|
|
1276
|
+
}
|
|
1277
|
+
},
|
|
1278
|
+
trading: {
|
|
1279
|
+
engine: {
|
|
1280
|
+
client_runtime: {
|
|
1281
|
+
reconcile: {
|
|
1282
|
+
tick_interval_seconds: 30,
|
|
1283
|
+
wake_jitter_max_ms: 1e3,
|
|
1284
|
+
network_backoff_max_seconds: 300,
|
|
1285
|
+
steal_ack_timeout_ms: 5e3,
|
|
1286
|
+
explicit_claim_max_waits: 3,
|
|
1287
|
+
local_ai_preflight_timeout_ms: 3e3
|
|
1288
|
+
},
|
|
1289
|
+
observability: {
|
|
1290
|
+
deployment_reload_stall_warning_ms: 15e3
|
|
1291
|
+
},
|
|
1292
|
+
protective_actions: {
|
|
1293
|
+
adoption_request_timeout_milliseconds: 2e4,
|
|
1294
|
+
idle_recheck_seconds: 60,
|
|
1295
|
+
attention_refresh_seconds: 60,
|
|
1296
|
+
evidence_max_items: 32,
|
|
1297
|
+
evidence_shared_read_seconds: 10
|
|
1298
|
+
},
|
|
1299
|
+
history_evidence: {
|
|
1300
|
+
device_rounds_per_beat: 6,
|
|
1301
|
+
device_retry_seconds: 60
|
|
1302
|
+
},
|
|
1303
|
+
panel_reads: {
|
|
1304
|
+
device_open_orders_reuse_seconds: 2
|
|
1305
|
+
},
|
|
1306
|
+
account_stream: {
|
|
1307
|
+
fill_forward_interval_ms: 1e3,
|
|
1308
|
+
fill_forward_max_pending: 500
|
|
1309
|
+
},
|
|
1310
|
+
hyperliquid_snapshot: {
|
|
1311
|
+
max_fills_per_execution: 20,
|
|
1312
|
+
max_fill_payload_bytes: 32768
|
|
1313
|
+
}
|
|
1314
|
+
},
|
|
1315
|
+
server_runtime: {
|
|
1316
|
+
prompt_rescue: {
|
|
1317
|
+
request_ttl_seconds: 20,
|
|
1318
|
+
snapshot_max_age_seconds: 5,
|
|
1319
|
+
snapshot_component_max_skew_seconds: 2,
|
|
1320
|
+
client_claim_http_timeout_milliseconds: 5e3,
|
|
1321
|
+
client_claim_retry_delay_milliseconds: 1e3,
|
|
1322
|
+
client_failure_report_timeout_milliseconds: 2e3,
|
|
1323
|
+
client_presence_heartbeat_interval_milliseconds: 3e4,
|
|
1324
|
+
client_presence_http_timeout_milliseconds: 5e3
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
},
|
|
1328
|
+
history_reads: {
|
|
1329
|
+
max_requests_per_read: 64,
|
|
1330
|
+
history_start_ms: 16409952e5,
|
|
1331
|
+
device_read_deadline_seconds: 180,
|
|
1332
|
+
device_recent_window_hours: 24
|
|
1333
|
+
}
|
|
1334
|
+
},
|
|
1335
|
+
agent_cli: {
|
|
1336
|
+
update_check_interval_ms: 144e5,
|
|
1337
|
+
update_check_timeout_ms: 5e3,
|
|
1338
|
+
stage_timeout_ms: 6e5,
|
|
1339
|
+
startup_ready_timeout_ms: 12e4
|
|
1340
|
+
},
|
|
1341
|
+
leaderboard: {
|
|
1342
|
+
device_portfolio: {
|
|
1343
|
+
device_retry_seconds: 300
|
|
1344
|
+
}
|
|
1345
|
+
},
|
|
1346
|
+
external_inference: {
|
|
1347
|
+
service: {
|
|
1348
|
+
update_check_interval_ms: 3e5,
|
|
1349
|
+
update_registry_timeout_ms: 15e3,
|
|
1350
|
+
bootstrap_readiness_timeout_ms: 6e5,
|
|
1351
|
+
bootstrap_stop_timeout_ms: 1e4,
|
|
1352
|
+
startup_max_attempts: 40,
|
|
1353
|
+
startup_poll_interval_ms: 250,
|
|
1354
|
+
restart_stable_uptime_ms: 6e4,
|
|
1355
|
+
restart_backoff_base_ms: 1e3,
|
|
1356
|
+
restart_backoff_max_ms: 3e4,
|
|
1357
|
+
restart_exponent_max: 5
|
|
1358
|
+
},
|
|
1359
|
+
claim_diagnostics: {
|
|
1360
|
+
event_loop_sample_interval_ms: 100
|
|
1361
|
+
}
|
|
1362
|
+
},
|
|
1363
|
+
desktop: {
|
|
1364
|
+
runtime_watchdog: {
|
|
1365
|
+
ping_interval_milliseconds: 15e3,
|
|
1366
|
+
telemetry_interval_milliseconds: 3e5
|
|
1367
|
+
},
|
|
1368
|
+
updates: {
|
|
1369
|
+
auto_check_interval_seconds: 21600
|
|
1370
|
+
}
|
|
1371
|
+
},
|
|
1372
|
+
screener: {
|
|
1373
|
+
resource_scheduler: {
|
|
1374
|
+
client_lock_ttl_milliseconds: 12e4,
|
|
1375
|
+
client_poll_interval_milliseconds: 250
|
|
1376
|
+
}
|
|
1377
|
+
},
|
|
1378
|
+
deployment: {
|
|
1379
|
+
cutover_buffer: {
|
|
1380
|
+
target_timeout_ms: 3e3,
|
|
1381
|
+
retired_timeout_ms: 45e3,
|
|
1382
|
+
max_body_bytes: 33554432,
|
|
1383
|
+
max_inflight: 16,
|
|
1384
|
+
max_queue: 64
|
|
1385
|
+
}
|
|
1386
|
+
},
|
|
1387
|
+
exchange: {
|
|
1388
|
+
hyperliquid: {
|
|
1389
|
+
client_public_read_budget: {
|
|
1390
|
+
max_weight: 1200,
|
|
1391
|
+
window_seconds: 60,
|
|
1392
|
+
ui_max_weight_percent: 75,
|
|
1393
|
+
background_max_weight_percent: 50,
|
|
1394
|
+
cooldown_seconds: 60,
|
|
1395
|
+
metadata_ttl_seconds: 300,
|
|
1396
|
+
user_fees_ttl_seconds: 300,
|
|
1397
|
+
account_mode_ttl_seconds: 30,
|
|
1398
|
+
default_request_weight: 20,
|
|
1399
|
+
cache_max_entries: 512
|
|
1400
|
+
},
|
|
1401
|
+
read_budget: {
|
|
1402
|
+
weights: {
|
|
1403
|
+
exchange: 1,
|
|
1404
|
+
exchangeStatus: 2,
|
|
1405
|
+
clearinghouseState: 2,
|
|
1406
|
+
spotClearinghouseState: 2,
|
|
1407
|
+
l2Book: 2,
|
|
1408
|
+
allMids: 2,
|
|
1409
|
+
userFills: 120,
|
|
1410
|
+
userFillsByTime: 120,
|
|
1411
|
+
historicalOrders: 120,
|
|
1412
|
+
twapHistory: 120,
|
|
1413
|
+
userTwapSliceFills: 120,
|
|
1414
|
+
userFunding: 45,
|
|
1415
|
+
userNonFundingLedgerUpdates: 45,
|
|
1416
|
+
candleSnapshot: 104,
|
|
1417
|
+
userRole: 60,
|
|
1418
|
+
userAbstraction: 20,
|
|
1419
|
+
userDexAbstraction: 20,
|
|
1420
|
+
activeAssetData: 20,
|
|
1421
|
+
openOrders: 20,
|
|
1422
|
+
orderStatus: 2,
|
|
1423
|
+
portfolio: 20,
|
|
1424
|
+
userFees: 20,
|
|
1425
|
+
metaAndAssetCtxs: 20,
|
|
1426
|
+
meta: 20,
|
|
1427
|
+
perpDexs: 20,
|
|
1428
|
+
spotMeta: 20,
|
|
1429
|
+
approvedAgents: 20,
|
|
1430
|
+
authorizedAgents: 20,
|
|
1431
|
+
agents: 20,
|
|
1432
|
+
apiWallets: 20
|
|
1433
|
+
},
|
|
1434
|
+
exchange_action_batch_size: 40
|
|
1435
|
+
},
|
|
1436
|
+
public_recovery: {
|
|
1437
|
+
fallback_initial_seconds: 2,
|
|
1438
|
+
fallback_max_seconds: 60,
|
|
1439
|
+
permit_lease_seconds: 60,
|
|
1440
|
+
recovery_initial_concurrency: 1,
|
|
1441
|
+
recovery_max_concurrency: 8,
|
|
1442
|
+
recovery_successes_per_step: 4,
|
|
1443
|
+
recovery_min_spacing_ms: 100,
|
|
1444
|
+
queue_poll_ms: 50,
|
|
1445
|
+
recovery_queue_wait_seconds: 10,
|
|
1446
|
+
recovery_quiet_seconds: 30
|
|
1447
|
+
},
|
|
1448
|
+
http: {
|
|
1449
|
+
exchange: {
|
|
1450
|
+
request_timeout_seconds: 10,
|
|
1451
|
+
pool_timeout_seconds: 10
|
|
1452
|
+
},
|
|
1453
|
+
public_critical: {
|
|
1454
|
+
request_timeout_seconds: 10,
|
|
1455
|
+
pool_timeout_seconds: 5
|
|
1456
|
+
}
|
|
1457
|
+
},
|
|
1458
|
+
public_request_priority: {
|
|
1459
|
+
trading_reduce: 1,
|
|
1460
|
+
trading_reconcile: 2,
|
|
1461
|
+
trading_execute: 3,
|
|
1462
|
+
trading_analysis: 4,
|
|
1463
|
+
ui: 5,
|
|
1464
|
+
background: 6
|
|
1465
|
+
},
|
|
1466
|
+
account_stream_backoff: {
|
|
1467
|
+
initial_seconds: 1,
|
|
1468
|
+
max_seconds: 15,
|
|
1469
|
+
multiplier: 2,
|
|
1470
|
+
jitter_seconds: 2,
|
|
1471
|
+
reset_after_seconds: 60
|
|
1472
|
+
},
|
|
1473
|
+
websocket: {
|
|
1474
|
+
ping_interval_seconds: 20,
|
|
1475
|
+
account_open_timeout_seconds: 10
|
|
1476
|
+
}
|
|
1477
|
+
},
|
|
1478
|
+
cache: {
|
|
1479
|
+
order_history_ttl_seconds: 30,
|
|
1480
|
+
twap_history_ttl_seconds: 30,
|
|
1481
|
+
twap_fills_ttl_seconds: 30,
|
|
1482
|
+
funding_history_ttl_seconds: 300
|
|
1483
|
+
}
|
|
1484
|
+
},
|
|
1485
|
+
data_sync: {
|
|
1486
|
+
trade_chain_history: {
|
|
1487
|
+
range_page_limit: 500
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
};
|
|
1491
|
+
|
|
1492
|
+
// lib/cli-release/updater.ts
|
|
1493
|
+
init_config();
|
|
1494
|
+
|
|
1495
|
+
// lib/inference-host/config.ts
|
|
1496
|
+
init_define_VTX_EXO_POLICY();
|
|
1497
|
+
init_define_VTX_GROK_POLICY();
|
|
1498
|
+
init_define_VTX_PI_MODEL_POLICY();
|
|
1499
|
+
import { randomBytes } from "node:crypto";
|
|
1500
|
+
import { spawn } from "node:child_process";
|
|
1501
|
+
import { constants } from "node:fs";
|
|
1502
|
+
import { lstat, mkdir as mkdir2, open, readdir as readdir2, readFile as readFile2, realpath, rename, rm as rm2 } from "node:fs/promises";
|
|
1503
|
+
import { homedir as homedir2 } from "node:os";
|
|
1504
|
+
import { basename, dirname as dirname2, isAbsolute, join as join2, relative, resolve, win32 as windowsPath } from "node:path";
|
|
1505
|
+
|
|
1506
|
+
// node_modules/smol-toml/dist/index.js
|
|
1507
|
+
init_define_VTX_EXO_POLICY();
|
|
1508
|
+
init_define_VTX_GROK_POLICY();
|
|
1509
|
+
init_define_VTX_PI_MODEL_POLICY();
|
|
1510
|
+
|
|
1511
|
+
// node_modules/smol-toml/dist/parse.js
|
|
1512
|
+
init_define_VTX_EXO_POLICY();
|
|
1513
|
+
init_define_VTX_GROK_POLICY();
|
|
1514
|
+
init_define_VTX_PI_MODEL_POLICY();
|
|
1515
|
+
|
|
1516
|
+
// node_modules/smol-toml/dist/struct.js
|
|
1517
|
+
init_define_VTX_EXO_POLICY();
|
|
1518
|
+
init_define_VTX_GROK_POLICY();
|
|
1519
|
+
init_define_VTX_PI_MODEL_POLICY();
|
|
1520
|
+
|
|
1521
|
+
// node_modules/smol-toml/dist/primitive.js
|
|
1522
|
+
init_define_VTX_EXO_POLICY();
|
|
1523
|
+
init_define_VTX_GROK_POLICY();
|
|
1524
|
+
init_define_VTX_PI_MODEL_POLICY();
|
|
1525
|
+
|
|
1526
|
+
// node_modules/smol-toml/dist/date.js
|
|
1527
|
+
init_define_VTX_EXO_POLICY();
|
|
1528
|
+
init_define_VTX_GROK_POLICY();
|
|
1529
|
+
init_define_VTX_PI_MODEL_POLICY();
|
|
1530
|
+
|
|
1531
|
+
// node_modules/smol-toml/dist/error.js
|
|
1532
|
+
init_define_VTX_EXO_POLICY();
|
|
1533
|
+
init_define_VTX_GROK_POLICY();
|
|
1534
|
+
init_define_VTX_PI_MODEL_POLICY();
|
|
1535
|
+
|
|
1536
|
+
// node_modules/smol-toml/dist/util.js
|
|
1537
|
+
init_define_VTX_EXO_POLICY();
|
|
1538
|
+
init_define_VTX_GROK_POLICY();
|
|
1539
|
+
init_define_VTX_PI_MODEL_POLICY();
|
|
1540
|
+
|
|
1541
|
+
// node_modules/smol-toml/dist/extract.js
|
|
1542
|
+
init_define_VTX_EXO_POLICY();
|
|
1543
|
+
init_define_VTX_GROK_POLICY();
|
|
1544
|
+
init_define_VTX_PI_MODEL_POLICY();
|
|
1545
|
+
|
|
1546
|
+
// node_modules/smol-toml/dist/stringify.js
|
|
1547
|
+
init_define_VTX_EXO_POLICY();
|
|
1548
|
+
init_define_VTX_GROK_POLICY();
|
|
1549
|
+
init_define_VTX_PI_MODEL_POLICY();
|
|
1550
|
+
|
|
1551
|
+
// lib/inference-host/mcp-transport-config.ts
|
|
1552
|
+
init_define_VTX_EXO_POLICY();
|
|
1553
|
+
init_define_VTX_GROK_POLICY();
|
|
1554
|
+
init_define_VTX_PI_MODEL_POLICY();
|
|
1555
|
+
|
|
1556
|
+
// lib/inference-host/config.ts
|
|
1557
|
+
var InferenceHostProcessLockBusyError = class extends Error {
|
|
1558
|
+
constructor() {
|
|
1559
|
+
super("Another inference host process already owns this local host.");
|
|
1560
|
+
this.name = "InferenceHostProcessLockBusyError";
|
|
1561
|
+
}
|
|
1562
|
+
};
|
|
1563
|
+
var MAX_INFERENCE_PRIVATE_FILE_BYTES = 16 * 1024 * 1024;
|
|
1564
|
+
var WINDOWS_PRIVATE_ACL_BROKER_SCRIPT = `$ErrorActionPreference='Stop'
|
|
1565
|
+
$ProgressPreference='SilentlyContinue'
|
|
1566
|
+
while(($line=[Console]::In.ReadLine()) -ne $null) {
|
|
1567
|
+
$requestId=$null
|
|
1568
|
+
$failureStage='request_validation'
|
|
1569
|
+
try {
|
|
1570
|
+
if($line.Length -lt 2 -or $line.Length -gt 65536) { throw 'invalid request size' }
|
|
1571
|
+
$request=$line | ConvertFrom-Json
|
|
1572
|
+
$names=@($request.PSObject.Properties.Name | Sort-Object)
|
|
1573
|
+
if(($names -join ',') -ne 'id,kind,mode,path_b64') { throw 'invalid request shape' }
|
|
1574
|
+
if($request.id -isnot [long] -and $request.id -isnot [int]) { throw 'invalid request id' }
|
|
1575
|
+
$requestId=[long]$request.id
|
|
1576
|
+
if($requestId -lt 1) { throw 'invalid request id' }
|
|
1577
|
+
$kind=[string]$request.kind
|
|
1578
|
+
$mode=[string]$request.mode
|
|
1579
|
+
if($kind -ne 'directory' -and $kind -ne 'file') { throw 'invalid path kind' }
|
|
1580
|
+
if($mode -ne 'harden' -and $mode -ne 'verify' -and $mode -ne 'verify-owner') { throw 'invalid ACL mode' }
|
|
1581
|
+
$path=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String([string]$request.path_b64))
|
|
1582
|
+
if([string]::IsNullOrWhiteSpace($path) -or $path.Length -gt 32767 -or $path.Contains([char]0)) { throw 'invalid path' }
|
|
1583
|
+
$failureStage='path_lookup'
|
|
1584
|
+
if(-not (Test-Path -LiteralPath $path)) {
|
|
1585
|
+
[Console]::Out.WriteLine((@{id=$requestId;status='missing'} | ConvertTo-Json -Compress))
|
|
1586
|
+
continue
|
|
1587
|
+
}
|
|
1588
|
+
$failureStage='principal_resolution'
|
|
1589
|
+
$currentIdentity=[Security.Principal.WindowsIdentity]::GetCurrent()
|
|
1590
|
+
$current=$currentIdentity.User
|
|
1591
|
+
$system=New-Object Security.Principal.SecurityIdentifier('S-1-5-18')
|
|
1592
|
+
$admins=New-Object Security.Principal.SecurityIdentifier('S-1-5-32-544')
|
|
1593
|
+
$currentIsAdmin=(New-Object Security.Principal.WindowsPrincipal($currentIdentity)).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
|
1594
|
+
$allowed=@($current.Value,$system.Value,$admins.Value)
|
|
1595
|
+
if($mode -eq 'harden') {
|
|
1596
|
+
$failureStage='acl_read'
|
|
1597
|
+
$acl=Get-Acl -LiteralPath $path
|
|
1598
|
+
$originalAccess=$acl.GetSecurityDescriptorSddlForm([Security.AccessControl.AccessControlSections]::Access)
|
|
1599
|
+
$failureStage='owner_validation'
|
|
1600
|
+
$existingOwner=(New-Object Security.Principal.NTAccount($acl.Owner)).Translate([Security.Principal.SecurityIdentifier]).Value
|
|
1601
|
+
if($existingOwner -ne $current.Value -and ($existingOwner -ne $admins.Value -or -not $currentIsAdmin)) { throw 'private ACL owner is invalid' }
|
|
1602
|
+
$failureStage='acl_mutation'
|
|
1603
|
+
$acl.SetAccessRuleProtection($true,$false)
|
|
1604
|
+
foreach($existingRule in @($acl.Access)) { [void]$acl.RemoveAccessRuleSpecific($existingRule) }
|
|
1605
|
+
$inherit=if($kind -eq 'directory') {
|
|
1606
|
+
[Security.AccessControl.InheritanceFlags]'ContainerInherit,ObjectInherit'
|
|
1607
|
+
} else { [Security.AccessControl.InheritanceFlags]::None }
|
|
1608
|
+
foreach($sid in @($current,$system,$admins)) {
|
|
1609
|
+
$rule=New-Object Security.AccessControl.FileSystemAccessRule(
|
|
1610
|
+
$sid,
|
|
1611
|
+
[Security.AccessControl.FileSystemRights]::FullControl,
|
|
1612
|
+
$inherit,
|
|
1613
|
+
[Security.AccessControl.PropagationFlags]::None,
|
|
1614
|
+
[Security.AccessControl.AccessControlType]::Allow
|
|
1615
|
+
)
|
|
1616
|
+
[void]$acl.AddAccessRule($rule)
|
|
1617
|
+
}
|
|
1618
|
+
$failureStage='acl_write'
|
|
1619
|
+
if($originalAccess -ne $acl.GetSecurityDescriptorSddlForm([Security.AccessControl.AccessControlSections]::Access)) {
|
|
1620
|
+
(Get-Item -LiteralPath $path).SetAccessControl($acl)
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
$failureStage='acl_readback'
|
|
1624
|
+
$actual=Get-Acl -LiteralPath $path
|
|
1625
|
+
$failureStage='acl_verification'
|
|
1626
|
+
$owner=(New-Object Security.Principal.NTAccount($actual.Owner)).Translate([Security.Principal.SecurityIdentifier]).Value
|
|
1627
|
+
if($owner -ne $current.Value -and ($owner -ne $admins.Value -or -not $currentIsAdmin)) { throw 'private ACL owner is invalid' }
|
|
1628
|
+
if($mode -ne 'verify-owner') {
|
|
1629
|
+
if(-not $actual.AreAccessRulesProtected) { throw 'private ACL inheritance is invalid' }
|
|
1630
|
+
$userFull=$false
|
|
1631
|
+
foreach($rule in $actual.Access) {
|
|
1632
|
+
$sid=$rule.IdentityReference.Translate([Security.Principal.SecurityIdentifier]).Value
|
|
1633
|
+
if($rule.IsInherited -or $allowed -notcontains $sid -or $rule.AccessControlType -ne [Security.AccessControl.AccessControlType]::Allow) {
|
|
1634
|
+
throw 'private ACL contains an unauthorized access rule'
|
|
1635
|
+
}
|
|
1636
|
+
if($sid -eq $current.Value -and (($rule.FileSystemRights -band [Security.AccessControl.FileSystemRights]::FullControl) -eq [Security.AccessControl.FileSystemRights]::FullControl)) { $userFull=$true }
|
|
1637
|
+
}
|
|
1638
|
+
if(-not $userFull) { throw 'private ACL does not grant the current user full control' }
|
|
1639
|
+
}
|
|
1640
|
+
[Console]::Out.WriteLine((@{id=$requestId;status='ok'} | ConvertTo-Json -Compress))
|
|
1641
|
+
} catch {
|
|
1642
|
+
[Console]::Out.WriteLine((@{id=$requestId;status='error';error_code=$failureStage} | ConvertTo-Json -Compress))
|
|
1643
|
+
}
|
|
1644
|
+
}`;
|
|
1645
|
+
var WINDOWS_IDENTITY_COMMAND_TIMEOUT_MS = 15e3;
|
|
1646
|
+
var WINDOWS_PRIVATE_ACL_BROKER_STARTUP_TIMEOUT_MS = 9e4;
|
|
1647
|
+
var SMALL_IDENTITY_COMMAND_TIMEOUT_MS = 5e3;
|
|
1648
|
+
var windowsPowerShellEnvironment = (extra = {}) => {
|
|
1649
|
+
const sanitizedExtra = { ...extra };
|
|
1650
|
+
delete sanitizedExtra.PSModulePath;
|
|
1651
|
+
const windowsRoot = process.env.SystemRoot ?? process.env.WINDIR;
|
|
1652
|
+
return {
|
|
1653
|
+
...sanitizedExtra,
|
|
1654
|
+
NODE_ENV: process.env.NODE_ENV,
|
|
1655
|
+
SystemRoot: process.env.SystemRoot,
|
|
1656
|
+
WINDIR: process.env.WINDIR,
|
|
1657
|
+
ComSpec: process.env.ComSpec,
|
|
1658
|
+
PATHEXT: process.env.PATHEXT,
|
|
1659
|
+
PSModulePath: windowsRoot ? windowsPath.join(windowsRoot, "System32", "WindowsPowerShell", "v1.0", "Modules") : void 0,
|
|
1660
|
+
TEMP: process.env.TEMP,
|
|
1661
|
+
TMP: process.env.TMP
|
|
1662
|
+
};
|
|
1663
|
+
};
|
|
1664
|
+
var windowsPrivateAclBrokerInvocation = () => ({
|
|
1665
|
+
args: [
|
|
1666
|
+
"-NoLogo",
|
|
1667
|
+
"-NoProfile",
|
|
1668
|
+
"-NonInteractive",
|
|
1669
|
+
"-EncodedCommand",
|
|
1670
|
+
Buffer.from(WINDOWS_PRIVATE_ACL_BROKER_SCRIPT, "utf16le").toString("base64")
|
|
1671
|
+
],
|
|
1672
|
+
env: windowsPowerShellEnvironment()
|
|
1673
|
+
});
|
|
1674
|
+
var WindowsPrivateAclBroker = class {
|
|
1675
|
+
constructor(dependencies = {}) {
|
|
1676
|
+
this.child = null;
|
|
1677
|
+
this.requestSequence = 0;
|
|
1678
|
+
this.queue = Promise.resolve();
|
|
1679
|
+
this.pending = null;
|
|
1680
|
+
this.outputBuffer = "";
|
|
1681
|
+
this.idleTimer = null;
|
|
1682
|
+
this.run = async (path, kind, mode) => {
|
|
1683
|
+
const operation = this.queue.then(
|
|
1684
|
+
() => this.dispatch(path, kind, mode),
|
|
1685
|
+
() => this.dispatch(path, kind, mode)
|
|
1686
|
+
);
|
|
1687
|
+
this.queue = operation.then(() => void 0, () => void 0);
|
|
1688
|
+
await operation;
|
|
1689
|
+
};
|
|
1690
|
+
this.requestTimeoutMs = dependencies.requestTimeoutMs ?? 15e3;
|
|
1691
|
+
this.startupRequestTimeoutMs = dependencies.startupRequestTimeoutMs ?? WINDOWS_PRIVATE_ACL_BROKER_STARTUP_TIMEOUT_MS;
|
|
1692
|
+
this.idleShutdownMs = dependencies.idleShutdownMs ?? 3e4;
|
|
1693
|
+
this.spawnProcess = dependencies.spawnProcess ?? (() => {
|
|
1694
|
+
const invocation = windowsPrivateAclBrokerInvocation();
|
|
1695
|
+
return spawn("powershell.exe", [...invocation.args], {
|
|
1696
|
+
env: invocation.env,
|
|
1697
|
+
windowsHide: true,
|
|
1698
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1699
|
+
});
|
|
1700
|
+
});
|
|
1701
|
+
}
|
|
1702
|
+
scheduleIdleShutdown() {
|
|
1703
|
+
if (this.idleTimer) clearTimeout(this.idleTimer);
|
|
1704
|
+
this.idleTimer = setTimeout(() => {
|
|
1705
|
+
this.idleTimer = null;
|
|
1706
|
+
const child = this.child;
|
|
1707
|
+
if (!child || this.pending) return;
|
|
1708
|
+
this.child = null;
|
|
1709
|
+
this.outputBuffer = "";
|
|
1710
|
+
child.stdin.end();
|
|
1711
|
+
const force = setTimeout(() => child.kill("SIGKILL"), 1e3);
|
|
1712
|
+
force.unref();
|
|
1713
|
+
child.once("exit", () => clearTimeout(force));
|
|
1714
|
+
}, this.idleShutdownMs);
|
|
1715
|
+
this.idleTimer.unref();
|
|
1716
|
+
}
|
|
1717
|
+
failChild(child, error) {
|
|
1718
|
+
if (this.child !== child) return;
|
|
1719
|
+
this.child = null;
|
|
1720
|
+
this.outputBuffer = "";
|
|
1721
|
+
if (this.idleTimer) {
|
|
1722
|
+
clearTimeout(this.idleTimer);
|
|
1723
|
+
this.idleTimer = null;
|
|
1724
|
+
}
|
|
1725
|
+
const pending = this.pending;
|
|
1726
|
+
this.pending = null;
|
|
1727
|
+
if (pending) {
|
|
1728
|
+
clearTimeout(pending.timer);
|
|
1729
|
+
pending.reject(error);
|
|
1730
|
+
}
|
|
1731
|
+
child.kill("SIGKILL");
|
|
1732
|
+
}
|
|
1733
|
+
acceptLine(child, line) {
|
|
1734
|
+
const pending = this.pending;
|
|
1735
|
+
if (!pending || !line || line.length > 4096) {
|
|
1736
|
+
this.failChild(child, new Error("Windows private ACL broker returned malformed output."));
|
|
1737
|
+
return;
|
|
1738
|
+
}
|
|
1739
|
+
let response;
|
|
1740
|
+
try {
|
|
1741
|
+
const parsed = JSON.parse(line);
|
|
1742
|
+
const status = String(parsed.status);
|
|
1743
|
+
const expectedKeys = status === "error" ? "error_code\0id\0status" : "id\0status";
|
|
1744
|
+
if (Object.keys(parsed).sort().join("\0") !== expectedKeys || parsed.id !== pending.id || !["ok", "missing", "error"].includes(status) || status === "error" && ![
|
|
1745
|
+
"request_validation",
|
|
1746
|
+
"path_lookup",
|
|
1747
|
+
"principal_resolution",
|
|
1748
|
+
"acl_read",
|
|
1749
|
+
"owner_validation",
|
|
1750
|
+
"acl_mutation",
|
|
1751
|
+
"acl_write",
|
|
1752
|
+
"acl_readback",
|
|
1753
|
+
"acl_verification"
|
|
1754
|
+
].includes(String(parsed.error_code))) throw new Error("invalid response");
|
|
1755
|
+
response = parsed;
|
|
1756
|
+
} catch {
|
|
1757
|
+
this.failChild(child, new Error("Windows private ACL broker returned malformed output."));
|
|
1758
|
+
return;
|
|
1759
|
+
}
|
|
1760
|
+
this.pending = null;
|
|
1761
|
+
clearTimeout(pending.timer);
|
|
1762
|
+
if (response.status === "ok") {
|
|
1763
|
+
pending.resolve();
|
|
1764
|
+
} else if (response.status === "missing") {
|
|
1765
|
+
const error = new Error("Windows private path no longer exists.");
|
|
1766
|
+
error.code = "ENOENT";
|
|
1767
|
+
pending.reject(error);
|
|
1768
|
+
} else {
|
|
1769
|
+
pending.reject(new Error(
|
|
1770
|
+
`Windows private ACL ${pending.mode} failed (${response.error_code}).`
|
|
1771
|
+
));
|
|
1772
|
+
}
|
|
1773
|
+
this.scheduleIdleShutdown();
|
|
1774
|
+
}
|
|
1775
|
+
ensureChild() {
|
|
1776
|
+
if (this.child && this.child.exitCode === null && !this.child.killed) {
|
|
1777
|
+
return { child: this.child, startedNow: false };
|
|
1778
|
+
}
|
|
1779
|
+
const child = this.spawnProcess();
|
|
1780
|
+
this.child = child;
|
|
1781
|
+
this.outputBuffer = "";
|
|
1782
|
+
child.stdout.setEncoding("utf8");
|
|
1783
|
+
child.stdout.on("data", (chunk) => {
|
|
1784
|
+
if (this.child !== child) return;
|
|
1785
|
+
this.outputBuffer += chunk;
|
|
1786
|
+
if (this.outputBuffer.length > 65536) {
|
|
1787
|
+
this.failChild(child, new Error("Windows private ACL broker exceeded its output limit."));
|
|
1788
|
+
return;
|
|
1789
|
+
}
|
|
1790
|
+
while (this.child === child) {
|
|
1791
|
+
const newline = this.outputBuffer.indexOf("\n");
|
|
1792
|
+
if (newline < 0) break;
|
|
1793
|
+
const line = this.outputBuffer.slice(0, newline).trim();
|
|
1794
|
+
this.outputBuffer = this.outputBuffer.slice(newline + 1);
|
|
1795
|
+
this.acceptLine(child, line);
|
|
1796
|
+
}
|
|
1797
|
+
});
|
|
1798
|
+
child.stderr.on("data", () => void 0);
|
|
1799
|
+
child.once("error", () => {
|
|
1800
|
+
this.failChild(child, new Error("Windows private ACL broker failed to start."));
|
|
1801
|
+
});
|
|
1802
|
+
child.once("exit", () => {
|
|
1803
|
+
this.failChild(child, new Error("Windows private ACL broker exited before confirmation."));
|
|
1804
|
+
});
|
|
1805
|
+
child.unref();
|
|
1806
|
+
for (const stream of [child.stdin, child.stdout, child.stderr]) {
|
|
1807
|
+
stream.unref?.();
|
|
1808
|
+
}
|
|
1809
|
+
return { child, startedNow: true };
|
|
1810
|
+
}
|
|
1811
|
+
async dispatch(path, kind, mode) {
|
|
1812
|
+
if (this.idleTimer) {
|
|
1813
|
+
clearTimeout(this.idleTimer);
|
|
1814
|
+
this.idleTimer = null;
|
|
1815
|
+
}
|
|
1816
|
+
const id = ++this.requestSequence;
|
|
1817
|
+
const request = `${JSON.stringify({
|
|
1818
|
+
id,
|
|
1819
|
+
kind,
|
|
1820
|
+
mode,
|
|
1821
|
+
path_b64: Buffer.from(path, "utf8").toString("base64")
|
|
1822
|
+
})}
|
|
1823
|
+
`;
|
|
1824
|
+
if (Buffer.byteLength(request, "utf8") > 65536) {
|
|
1825
|
+
throw new Error("Windows private ACL broker request exceeds its input limit.");
|
|
1826
|
+
}
|
|
1827
|
+
const { child, startedNow } = this.ensureChild();
|
|
1828
|
+
const timeoutMs = startedNow ? this.startupRequestTimeoutMs : this.requestTimeoutMs;
|
|
1829
|
+
await new Promise((resolvePromise, reject) => {
|
|
1830
|
+
const timer = setTimeout(() => {
|
|
1831
|
+
this.failChild(child, new Error("Windows private ACL broker request timed out."));
|
|
1832
|
+
}, timeoutMs);
|
|
1833
|
+
this.pending = { id, mode, resolve: resolvePromise, reject, timer };
|
|
1834
|
+
child.stdin.write(request, "utf8", (error) => {
|
|
1835
|
+
if (error) {
|
|
1836
|
+
this.failChild(child, new Error("Windows private ACL broker request failed."));
|
|
1837
|
+
}
|
|
1838
|
+
});
|
|
1839
|
+
});
|
|
1840
|
+
}
|
|
1841
|
+
close() {
|
|
1842
|
+
if (this.idleTimer) clearTimeout(this.idleTimer);
|
|
1843
|
+
this.idleTimer = null;
|
|
1844
|
+
const child = this.child;
|
|
1845
|
+
this.child = null;
|
|
1846
|
+
this.outputBuffer = "";
|
|
1847
|
+
if (child) child.stdin.end();
|
|
1848
|
+
}
|
|
1849
|
+
};
|
|
1850
|
+
var defaultWindowsPrivateAclBroker = new WindowsPrivateAclBroker();
|
|
1851
|
+
var runWindowsPrivateAcl = async (path, kind, mode) => await defaultWindowsPrivateAclBroker.run(path, kind, mode);
|
|
1852
|
+
var windowsAclCache = /* @__PURE__ */ new Map();
|
|
1853
|
+
var windowsAclIdentity = async (path) => {
|
|
1854
|
+
const metadata = await lstat(path, { bigint: true });
|
|
1855
|
+
return {
|
|
1856
|
+
dev: metadata.dev,
|
|
1857
|
+
ino: metadata.ino,
|
|
1858
|
+
ctimeNs: metadata.ctimeNs,
|
|
1859
|
+
mtimeNs: metadata.mtimeNs,
|
|
1860
|
+
mode: metadata.mode,
|
|
1861
|
+
size: metadata.size
|
|
1862
|
+
};
|
|
1863
|
+
};
|
|
1864
|
+
var aclCacheKey = (path, kind, mode) => `${kind}:${mode}:${resolve(path)}`;
|
|
1865
|
+
var secureWindowsPrivatePath = async (path, kind, mode, platform = process.platform, runner = runWindowsPrivateAcl) => {
|
|
1866
|
+
if (platform !== "win32") return;
|
|
1867
|
+
const key = aclCacheKey(path, kind, mode);
|
|
1868
|
+
const before = await windowsAclIdentity(path);
|
|
1869
|
+
const cached = windowsAclCache.get(key);
|
|
1870
|
+
if (cached && cached.dev === before.dev && cached.ino === before.ino && cached.ctimeNs === before.ctimeNs && cached.mtimeNs === before.mtimeNs && cached.mode === before.mode && cached.size === before.size) return;
|
|
1871
|
+
await runner(path, kind, mode);
|
|
1872
|
+
windowsAclCache.set(key, await windowsAclIdentity(path));
|
|
1873
|
+
};
|
|
1874
|
+
var invalidateWindowsAclCache = (path) => {
|
|
1875
|
+
for (const kind of ["file", "directory"]) {
|
|
1876
|
+
for (const mode of ["harden", "verify", "verify-owner"]) {
|
|
1877
|
+
windowsAclCache.delete(aclCacheKey(path, kind, mode));
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
};
|
|
1881
|
+
var rebindHardenedWindowsAclAfterRename = async (source, destination, kind, expected) => {
|
|
1882
|
+
const resolvedSource = resolve(source);
|
|
1883
|
+
const resolvedDestination = resolve(destination);
|
|
1884
|
+
if (resolvedSource === resolvedDestination || dirname2(resolvedSource) !== dirname2(resolvedDestination)) {
|
|
1885
|
+
throw new Error("Windows private ACL cache rename boundary is invalid.");
|
|
1886
|
+
}
|
|
1887
|
+
const published = await windowsAclIdentity(destination);
|
|
1888
|
+
if (published.dev !== expected.dev || published.ino !== expected.ino || published.mode !== expected.mode || published.mtimeNs !== expected.mtimeNs || published.size !== expected.size) {
|
|
1889
|
+
invalidateWindowsAclCache(source);
|
|
1890
|
+
invalidateWindowsAclCache(destination);
|
|
1891
|
+
throw new Error("Windows private file identity changed during atomic publication.");
|
|
1892
|
+
}
|
|
1893
|
+
invalidateWindowsAclCache(source);
|
|
1894
|
+
invalidateWindowsAclCache(destination);
|
|
1895
|
+
for (const mode of ["harden", "verify", "verify-owner"]) {
|
|
1896
|
+
windowsAclCache.set(aclCacheKey(destination, kind, mode), published);
|
|
1897
|
+
}
|
|
1898
|
+
};
|
|
1899
|
+
var rebindHardenedWindowsDirectoryAfterOwnedMutation = async (path, expected) => {
|
|
1900
|
+
const current = await windowsAclIdentity(path);
|
|
1901
|
+
if (current.dev !== expected.dev || current.ino !== expected.ino || current.mode !== expected.mode) {
|
|
1902
|
+
invalidateWindowsAclCache(path);
|
|
1903
|
+
throw new Error("Windows private directory identity changed during atomic publication.");
|
|
1904
|
+
}
|
|
1905
|
+
invalidateWindowsAclCache(path);
|
|
1906
|
+
for (const mode of ["harden", "verify", "verify-owner"]) {
|
|
1907
|
+
windowsAclCache.set(aclCacheKey(path, "directory", mode), current);
|
|
1908
|
+
}
|
|
1909
|
+
};
|
|
1910
|
+
var isDefaultWindowsPrivatePath = (path, root = resolve(homedir2(), ".vtx", "inference-host")) => {
|
|
1911
|
+
const child = relative(root, resolve(path));
|
|
1912
|
+
return child === "" || !child.startsWith("..") && !child.includes(":");
|
|
1913
|
+
};
|
|
1914
|
+
var secureExistingWindowsPath = async (path, kind, platform = process.platform, runner = runWindowsPrivateAcl, defaultRoot = resolve(homedir2(), ".vtx", "inference-host")) => {
|
|
1915
|
+
if (platform !== "win32") return;
|
|
1916
|
+
if (!isDefaultWindowsPrivatePath(path, defaultRoot)) {
|
|
1917
|
+
await secureWindowsPrivatePath(path, kind, "verify", platform, runner);
|
|
1918
|
+
return;
|
|
1919
|
+
}
|
|
1920
|
+
await secureWindowsPrivatePath(path, kind, "harden", platform, runner);
|
|
1921
|
+
};
|
|
1922
|
+
var linuxProcessIdentity = async (pid) => {
|
|
1923
|
+
try {
|
|
1924
|
+
const [bootId, statLine] = await Promise.all([
|
|
1925
|
+
readFile2("/proc/sys/kernel/random/boot_id", "utf8"),
|
|
1926
|
+
readFile2(`/proc/${pid}/stat`, "utf8")
|
|
1927
|
+
]);
|
|
1928
|
+
const fields = statLine.slice(statLine.lastIndexOf(")") + 2).trim().split(/\s+/u);
|
|
1929
|
+
const startTicks = fields[19];
|
|
1930
|
+
return startTicks ? `linux:${bootId.trim()}:${startTicks}` : null;
|
|
1931
|
+
} catch {
|
|
1932
|
+
return null;
|
|
1933
|
+
}
|
|
1934
|
+
};
|
|
1935
|
+
var windowsProcessIdentityInvocation = (pid) => {
|
|
1936
|
+
const script = `$ErrorActionPreference='Stop';$p=[Diagnostics.Process]::GetProcessById([int]$env:VTX_PROCESS_PID);[Console]::Out.Write($p.StartTime.ToUniversalTime().ToFileTimeUtc())`;
|
|
1937
|
+
return {
|
|
1938
|
+
args: [
|
|
1939
|
+
"-NoLogo",
|
|
1940
|
+
"-NoProfile",
|
|
1941
|
+
"-NonInteractive",
|
|
1942
|
+
"-EncodedCommand",
|
|
1943
|
+
Buffer.from(script, "utf16le").toString("base64")
|
|
1944
|
+
],
|
|
1945
|
+
env: windowsPowerShellEnvironment({ VTX_PROCESS_PID: String(pid) }),
|
|
1946
|
+
timeoutMs: WINDOWS_IDENTITY_COMMAND_TIMEOUT_MS
|
|
1947
|
+
};
|
|
1948
|
+
};
|
|
1949
|
+
var windowsBootIdentityInvocation = () => {
|
|
1950
|
+
const script = `$ErrorActionPreference='Stop';$s=[System.Management.ManagementObjectSearcher]::new('SELECT LastBootUpTime FROM Win32_OperatingSystem');try{$c=$s.Get();$e=$c.GetEnumerator();if(-not $e.MoveNext()){throw 'boot identity unavailable'};$raw=[string]$e.Current['LastBootUpTime'];$v=[System.Management.ManagementDateTimeConverter]::ToDateTime($raw).ToUniversalTime().ToFileTimeUtc();[Console]::Out.Write($v)}finally{if($c){$c.Dispose()};$s.Dispose()}`;
|
|
1951
|
+
return {
|
|
1952
|
+
args: [
|
|
1953
|
+
"-NoLogo",
|
|
1954
|
+
"-NoProfile",
|
|
1955
|
+
"-NonInteractive",
|
|
1956
|
+
"-EncodedCommand",
|
|
1957
|
+
Buffer.from(script, "utf16le").toString("base64")
|
|
1958
|
+
],
|
|
1959
|
+
env: windowsPowerShellEnvironment(),
|
|
1960
|
+
timeoutMs: WINDOWS_IDENTITY_COMMAND_TIMEOUT_MS
|
|
1961
|
+
};
|
|
1962
|
+
};
|
|
1963
|
+
var windowsProcessIdentity = async (pid) => await new Promise((resolvePromise) => {
|
|
1964
|
+
const invocation = windowsProcessIdentityInvocation(pid);
|
|
1965
|
+
const child = spawn("powershell.exe", [...invocation.args], {
|
|
1966
|
+
windowsHide: true,
|
|
1967
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1968
|
+
env: invocation.env
|
|
1969
|
+
});
|
|
1970
|
+
const output = [];
|
|
1971
|
+
let settled = false;
|
|
1972
|
+
const settle = (value) => {
|
|
1973
|
+
if (settled) return;
|
|
1974
|
+
settled = true;
|
|
1975
|
+
clearTimeout(timer);
|
|
1976
|
+
resolvePromise(value);
|
|
1977
|
+
};
|
|
1978
|
+
const timer = setTimeout(() => {
|
|
1979
|
+
child.kill("SIGKILL");
|
|
1980
|
+
settle(null);
|
|
1981
|
+
}, invocation.timeoutMs);
|
|
1982
|
+
child.stdout.on("data", (chunk) => {
|
|
1983
|
+
if (output.reduce((total, item) => total + item.byteLength, 0) + Buffer.byteLength(chunk) > 1024) {
|
|
1984
|
+
child.kill("SIGKILL");
|
|
1985
|
+
settle(null);
|
|
1986
|
+
return;
|
|
1987
|
+
}
|
|
1988
|
+
output.push(Buffer.from(chunk));
|
|
1989
|
+
});
|
|
1990
|
+
child.once("error", () => settle(null));
|
|
1991
|
+
child.once("exit", (code) => {
|
|
1992
|
+
const started = Buffer.concat(output).toString("utf8").trim();
|
|
1993
|
+
settle(code === 0 && /^\d+$/u.test(started) ? `win32:${started}` : null);
|
|
1994
|
+
});
|
|
1995
|
+
});
|
|
1996
|
+
var darwinProcessIdentity = async (pid) => await new Promise((resolvePromise) => {
|
|
1997
|
+
const child = spawn("ps", ["-p", String(pid), "-o", "lstart="], {
|
|
1998
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1999
|
+
});
|
|
2000
|
+
const output = [];
|
|
2001
|
+
let settled = false;
|
|
2002
|
+
const settle = (value) => {
|
|
2003
|
+
if (settled) return;
|
|
2004
|
+
settled = true;
|
|
2005
|
+
clearTimeout(timer);
|
|
2006
|
+
resolvePromise(value);
|
|
2007
|
+
};
|
|
2008
|
+
const timer = setTimeout(() => {
|
|
2009
|
+
child.kill("SIGKILL");
|
|
2010
|
+
settle(null);
|
|
2011
|
+
}, 5e3);
|
|
2012
|
+
child.stdout.on("data", (chunk) => output.push(Buffer.from(chunk)));
|
|
2013
|
+
child.once("error", () => settle(null));
|
|
2014
|
+
child.once("exit", (code) => {
|
|
2015
|
+
const started = Date.parse(Buffer.concat(output).toString("utf8").trim());
|
|
2016
|
+
settle(code === 0 && Number.isFinite(started) ? `darwin:${Math.floor(started / 1e3)}` : null);
|
|
2017
|
+
});
|
|
2018
|
+
});
|
|
2019
|
+
var runSmallIdentityCommand = async (command, args, env, timeoutMs = SMALL_IDENTITY_COMMAND_TIMEOUT_MS) => await new Promise((resolvePromise) => {
|
|
2020
|
+
const child = spawn(command, [...args], {
|
|
2021
|
+
windowsHide: true,
|
|
2022
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2023
|
+
env
|
|
2024
|
+
});
|
|
2025
|
+
const output = [];
|
|
2026
|
+
let bytes = 0;
|
|
2027
|
+
let settled = false;
|
|
2028
|
+
const settle = (value) => {
|
|
2029
|
+
if (settled) return;
|
|
2030
|
+
settled = true;
|
|
2031
|
+
clearTimeout(timer);
|
|
2032
|
+
resolvePromise(value);
|
|
2033
|
+
};
|
|
2034
|
+
const timer = setTimeout(() => {
|
|
2035
|
+
child.kill("SIGKILL");
|
|
2036
|
+
settle(null);
|
|
2037
|
+
}, timeoutMs);
|
|
2038
|
+
child.stdout.on("data", (chunk) => {
|
|
2039
|
+
bytes += Buffer.byteLength(chunk);
|
|
2040
|
+
if (bytes > 4096) {
|
|
2041
|
+
child.kill("SIGKILL");
|
|
2042
|
+
settle(null);
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
output.push(Buffer.from(chunk));
|
|
2046
|
+
});
|
|
2047
|
+
child.once("error", () => settle(null));
|
|
2048
|
+
child.once("exit", (code) => settle(
|
|
2049
|
+
code === 0 ? Buffer.concat(output).toString("utf8").trim() || null : null
|
|
2050
|
+
));
|
|
2051
|
+
});
|
|
2052
|
+
var cachedSystemBootIdentity = null;
|
|
2053
|
+
var readInferenceSystemBootIdentity = async (platform = process.platform) => {
|
|
2054
|
+
if (platform === process.platform && cachedSystemBootIdentity) {
|
|
2055
|
+
return await cachedSystemBootIdentity;
|
|
2056
|
+
}
|
|
2057
|
+
const read = async () => {
|
|
2058
|
+
let identity = null;
|
|
2059
|
+
if (platform === "linux") {
|
|
2060
|
+
try {
|
|
2061
|
+
identity = (await readFile2("/proc/sys/kernel/random/boot_id", "utf8")).trim();
|
|
2062
|
+
} catch {
|
|
2063
|
+
identity = null;
|
|
2064
|
+
}
|
|
2065
|
+
} else if (platform === "win32") {
|
|
2066
|
+
const invocation = windowsBootIdentityInvocation();
|
|
2067
|
+
identity = await runSmallIdentityCommand(
|
|
2068
|
+
"powershell.exe",
|
|
2069
|
+
invocation.args,
|
|
2070
|
+
invocation.env,
|
|
2071
|
+
invocation.timeoutMs
|
|
2072
|
+
);
|
|
2073
|
+
} else if (platform === "darwin") {
|
|
2074
|
+
identity = await runSmallIdentityCommand("sysctl", ["-n", "kern.boottime"], void 0);
|
|
2075
|
+
}
|
|
2076
|
+
if (!identity || /[\r\n\0]/u.test(identity)) {
|
|
2077
|
+
throw new Error("Inference host could not establish the current system boot identity.");
|
|
2078
|
+
}
|
|
2079
|
+
return `${platform}:${identity}`;
|
|
2080
|
+
};
|
|
2081
|
+
const pending = read();
|
|
2082
|
+
if (platform === process.platform) cachedSystemBootIdentity = pending;
|
|
2083
|
+
try {
|
|
2084
|
+
return await pending;
|
|
2085
|
+
} catch (error) {
|
|
2086
|
+
if (platform === process.platform) cachedSystemBootIdentity = null;
|
|
2087
|
+
throw error;
|
|
2088
|
+
}
|
|
2089
|
+
};
|
|
2090
|
+
var defaultProcessIdentity = async (pid) => {
|
|
2091
|
+
if (process.platform === "linux") return await linuxProcessIdentity(pid);
|
|
2092
|
+
if (process.platform === "win32") return await windowsProcessIdentity(pid);
|
|
2093
|
+
if (process.platform === "darwin") return await darwinProcessIdentity(pid);
|
|
2094
|
+
return null;
|
|
2095
|
+
};
|
|
2096
|
+
var cachedCurrentProcessIdentity = null;
|
|
2097
|
+
var defaultCurrentProcessIdentity = async () => {
|
|
2098
|
+
if (!cachedCurrentProcessIdentity) {
|
|
2099
|
+
cachedCurrentProcessIdentity = (async () => {
|
|
2100
|
+
const identity = await defaultProcessIdentity(process.pid);
|
|
2101
|
+
if (!identity) throw new Error("Inference host could not establish its process-start identity.");
|
|
2102
|
+
return identity;
|
|
2103
|
+
})();
|
|
2104
|
+
}
|
|
2105
|
+
const pending = cachedCurrentProcessIdentity;
|
|
2106
|
+
try {
|
|
2107
|
+
return await pending;
|
|
2108
|
+
} catch (error) {
|
|
2109
|
+
if (cachedCurrentProcessIdentity === pending) cachedCurrentProcessIdentity = null;
|
|
2110
|
+
throw error;
|
|
2111
|
+
}
|
|
2112
|
+
};
|
|
2113
|
+
var processLockObservationCache = /* @__PURE__ */ new Map();
|
|
2114
|
+
var inferenceProcessIdentitiesMatch = (stored, observed) => stored === observed;
|
|
2115
|
+
function inferenceBaseDir(env) {
|
|
2116
|
+
const configured = String(env.VTX_INFERENCE_HOST_HOME || "").trim();
|
|
2117
|
+
return configured || join2(homedir2(), ".vtx", "inference-host");
|
|
2118
|
+
}
|
|
2119
|
+
var resolveInferenceHostApiUrl = (env) => String(env.VTX_API_URL || "https://api.vtxmacro.com").replace(/\/+$/, "");
|
|
2120
|
+
var inferenceHostSupervisorServiceManifestPath = (env) => `${join2(inferenceBaseDir(env), "state.json")}.service.json`;
|
|
2121
|
+
async function ensureInferencePrivateDirectory(path, dependencies = {}) {
|
|
2122
|
+
const platform = dependencies.platform ?? process.platform;
|
|
2123
|
+
const runner = dependencies.runner ?? runWindowsPrivateAcl;
|
|
2124
|
+
const defaultRoot = dependencies.defaultRoot ?? resolve(homedir2(), ".vtx", "inference-host");
|
|
2125
|
+
let created = false;
|
|
2126
|
+
try {
|
|
2127
|
+
const before = await lstat(path);
|
|
2128
|
+
if (before.isSymbolicLink() || !before.isDirectory()) {
|
|
2129
|
+
throw new Error("Inference host state directory is unsafe.");
|
|
2130
|
+
}
|
|
2131
|
+
} catch (error) {
|
|
2132
|
+
if (error.code !== "ENOENT") throw error;
|
|
2133
|
+
await mkdir2(path, { recursive: true, mode: 448 });
|
|
2134
|
+
created = true;
|
|
2135
|
+
}
|
|
2136
|
+
const after = await lstat(path);
|
|
2137
|
+
if (after.isSymbolicLink() || !after.isDirectory()) {
|
|
2138
|
+
throw new Error("Inference host state directory is unsafe.");
|
|
2139
|
+
}
|
|
2140
|
+
if (platform === "win32") {
|
|
2141
|
+
if (created) {
|
|
2142
|
+
await secureWindowsPrivatePath(path, "directory", "harden", platform, runner);
|
|
2143
|
+
} else {
|
|
2144
|
+
await secureExistingWindowsPath(
|
|
2145
|
+
path,
|
|
2146
|
+
"directory",
|
|
2147
|
+
platform,
|
|
2148
|
+
runner,
|
|
2149
|
+
defaultRoot
|
|
2150
|
+
);
|
|
2151
|
+
}
|
|
2152
|
+
return;
|
|
2153
|
+
}
|
|
2154
|
+
if ((after.mode & 63) !== 0) {
|
|
2155
|
+
throw new Error("Inference host state directory must have mode 0700.");
|
|
2156
|
+
}
|
|
2157
|
+
if (typeof process.getuid === "function" && after.uid !== process.getuid()) {
|
|
2158
|
+
throw new Error("Inference host state directory must be owned by the current user.");
|
|
2159
|
+
}
|
|
2160
|
+
const canonical = await realpath(path);
|
|
2161
|
+
if (canonical !== resolve(path)) {
|
|
2162
|
+
throw new Error("Inference host state directory may not traverse symbolic links.");
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
async function syncInferenceDirectory(path, platform = process.platform) {
|
|
2166
|
+
if (platform === "win32") return;
|
|
2167
|
+
const handle = await open(path, constants.O_RDONLY | constants.O_DIRECTORY);
|
|
2168
|
+
try {
|
|
2169
|
+
await handle.sync();
|
|
2170
|
+
} finally {
|
|
2171
|
+
await handle.close();
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
var WINDOWS_PRIVATE_FILE_REPLACE_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
2175
|
+
"EACCES",
|
|
2176
|
+
"EBUSY",
|
|
2177
|
+
"EEXIST",
|
|
2178
|
+
"ENOTEMPTY",
|
|
2179
|
+
"EPERM"
|
|
2180
|
+
]);
|
|
2181
|
+
async function replaceAtomicInferencePrivateFile(temporaryPath, destinationPath, options = {}) {
|
|
2182
|
+
const platform = options.platform ?? process.platform;
|
|
2183
|
+
const renameFile = options.renameFile ?? rename;
|
|
2184
|
+
const sleep = options.sleep ?? ((milliseconds) => new Promise((resolve3) => setTimeout(resolve3, milliseconds)));
|
|
2185
|
+
const maxAttempts = Math.max(1, options.maxAttempts ?? 40);
|
|
2186
|
+
const retryDelayMs = Math.max(0, options.retryDelayMs ?? 25);
|
|
2187
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
2188
|
+
try {
|
|
2189
|
+
await renameFile(temporaryPath, destinationPath);
|
|
2190
|
+
return;
|
|
2191
|
+
} catch (error) {
|
|
2192
|
+
const code = String(error.code ?? "");
|
|
2193
|
+
if (platform !== "win32" || !WINDOWS_PRIVATE_FILE_REPLACE_ERROR_CODES.has(code) || attempt === maxAttempts - 1) {
|
|
2194
|
+
throw error;
|
|
2195
|
+
}
|
|
2196
|
+
await sleep(retryDelayMs);
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
async function writeAtomicInferencePrivateFile(path, contents, dependencies = {}) {
|
|
2201
|
+
const platform = dependencies.platform ?? process.platform;
|
|
2202
|
+
const runner = dependencies.runner ?? runWindowsPrivateAcl;
|
|
2203
|
+
const defaultRoot = dependencies.defaultRoot ?? resolve(homedir2(), ".vtx", "inference-host");
|
|
2204
|
+
const directory = dirname2(path);
|
|
2205
|
+
await ensureInferencePrivateDirectory(directory, dependencies);
|
|
2206
|
+
const hardenedDirectoryIdentity = platform === "win32" && isDefaultWindowsPrivatePath(directory, defaultRoot) ? windowsAclCache.get(aclCacheKey(directory, "directory", "harden")) : void 0;
|
|
2207
|
+
if (platform === "win32" && isDefaultWindowsPrivatePath(directory, defaultRoot) && !hardenedDirectoryIdentity) {
|
|
2208
|
+
throw new Error("Windows private directory ACL hardening was not durably verified.");
|
|
2209
|
+
}
|
|
2210
|
+
const temporary = join2(
|
|
2211
|
+
directory,
|
|
2212
|
+
`.${process.pid}.${randomBytes(12).toString("hex")}.tmp`
|
|
2213
|
+
);
|
|
2214
|
+
const handle = await open(
|
|
2215
|
+
temporary,
|
|
2216
|
+
constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW,
|
|
2217
|
+
384
|
|
2218
|
+
);
|
|
2219
|
+
try {
|
|
2220
|
+
await handle.writeFile(contents, "utf8");
|
|
2221
|
+
await handle.sync();
|
|
2222
|
+
} catch (error) {
|
|
2223
|
+
await handle.close().catch(() => void 0);
|
|
2224
|
+
await rm2(temporary, { force: true }).catch(() => void 0);
|
|
2225
|
+
throw error;
|
|
2226
|
+
}
|
|
2227
|
+
await handle.close();
|
|
2228
|
+
await secureWindowsPrivatePath(temporary, "file", "harden", platform, runner);
|
|
2229
|
+
const hardenedIdentity = platform === "win32" ? windowsAclCache.get(aclCacheKey(temporary, "file", "harden")) : void 0;
|
|
2230
|
+
if (platform === "win32" && !hardenedIdentity) {
|
|
2231
|
+
await rm2(temporary, { force: true }).catch(() => void 0);
|
|
2232
|
+
throw new Error("Windows private ACL hardening was not durably verified.");
|
|
2233
|
+
}
|
|
2234
|
+
try {
|
|
2235
|
+
invalidateWindowsAclCache(path);
|
|
2236
|
+
await replaceAtomicInferencePrivateFile(temporary, path, {
|
|
2237
|
+
platform,
|
|
2238
|
+
renameFile: dependencies.renameFile,
|
|
2239
|
+
sleep: dependencies.sleep,
|
|
2240
|
+
maxAttempts: dependencies.replaceMaxAttempts,
|
|
2241
|
+
retryDelayMs: dependencies.replaceRetryDelayMs
|
|
2242
|
+
});
|
|
2243
|
+
if (platform === "win32" && hardenedIdentity) {
|
|
2244
|
+
await rebindHardenedWindowsAclAfterRename(
|
|
2245
|
+
temporary,
|
|
2246
|
+
path,
|
|
2247
|
+
"file",
|
|
2248
|
+
hardenedIdentity
|
|
2249
|
+
);
|
|
2250
|
+
} else {
|
|
2251
|
+
invalidateWindowsAclCache(temporary);
|
|
2252
|
+
}
|
|
2253
|
+
await syncInferenceDirectory(directory, platform);
|
|
2254
|
+
if (platform === "win32" && hardenedDirectoryIdentity) {
|
|
2255
|
+
await rebindHardenedWindowsDirectoryAfterOwnedMutation(
|
|
2256
|
+
directory,
|
|
2257
|
+
hardenedDirectoryIdentity
|
|
2258
|
+
);
|
|
2259
|
+
}
|
|
2260
|
+
} catch (error) {
|
|
2261
|
+
await rm2(temporary, { force: true }).catch(() => void 0);
|
|
2262
|
+
invalidateWindowsAclCache(temporary);
|
|
2263
|
+
throw error;
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
var PROCESS_LOCK_PUBLICATION_GRACE_MS = 1e4;
|
|
2267
|
+
var PROCESS_LOCK_MAX_RECORD_BYTES = 64 * 1024;
|
|
2268
|
+
var PROCESS_LOCK_GUARD_ELECTION_LEASE_MS = 5e3;
|
|
2269
|
+
var PROCESS_LOCK_GUARD_ELECTION_STALE_MS = 3e4;
|
|
2270
|
+
var PROCESS_LOCK_GUARD_ELECTION_ATTEMPTS = 8;
|
|
2271
|
+
var PROCESS_LOCK_GUARD_ELECTION_BACKOFF_MS = 10;
|
|
2272
|
+
var PROCESS_LOCK_LABEL = "Inference host process lock";
|
|
2273
|
+
var PROCESS_LOCK_GUARD_LABEL = "Inference host process lock reclamation guard";
|
|
2274
|
+
var sameProcessLockFile = (left, right) => left.dev === right.dev && left.ino === right.ino && left.size === right.size && left.mtimeMs === right.mtimeMs && left.ctimeMs === right.ctimeMs;
|
|
2275
|
+
var openExclusiveProcessLockFile = async (path) => {
|
|
2276
|
+
try {
|
|
2277
|
+
return await open(
|
|
2278
|
+
path,
|
|
2279
|
+
constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW,
|
|
2280
|
+
384
|
|
2281
|
+
);
|
|
2282
|
+
} catch (error) {
|
|
2283
|
+
if (error.code === "EEXIST") return null;
|
|
2284
|
+
throw error;
|
|
2285
|
+
}
|
|
2286
|
+
};
|
|
2287
|
+
var inspectProcessLockFile = async (path, label, probes) => {
|
|
2288
|
+
let before;
|
|
2289
|
+
try {
|
|
2290
|
+
before = await lstat(path);
|
|
2291
|
+
} catch (error) {
|
|
2292
|
+
if (error.code === "ENOENT") return { state: "missing" };
|
|
2293
|
+
throw error;
|
|
2294
|
+
}
|
|
2295
|
+
if (before.isSymbolicLink() || !before.isFile() || process.platform !== "win32" && (before.mode & 63) !== 0 || typeof process.getuid === "function" && before.uid !== process.getuid()) {
|
|
2296
|
+
throw new Error(`${label} is unsafe.`);
|
|
2297
|
+
}
|
|
2298
|
+
let handle;
|
|
2299
|
+
try {
|
|
2300
|
+
await secureExistingWindowsPath(path, "file");
|
|
2301
|
+
handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
2302
|
+
} catch (error) {
|
|
2303
|
+
if (error.code === "ENOENT") return { state: "missing" };
|
|
2304
|
+
throw error;
|
|
2305
|
+
}
|
|
2306
|
+
let stale = false;
|
|
2307
|
+
try {
|
|
2308
|
+
const opened = await handle.stat();
|
|
2309
|
+
if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino) {
|
|
2310
|
+
return { state: "busy" };
|
|
2311
|
+
}
|
|
2312
|
+
const raw = opened.size > PROCESS_LOCK_MAX_RECORD_BYTES ? "" : await handle.readFile("utf8");
|
|
2313
|
+
const stat = await handle.stat();
|
|
2314
|
+
if (!sameProcessLockFile(opened, stat)) return { state: "busy" };
|
|
2315
|
+
let record = null;
|
|
2316
|
+
try {
|
|
2317
|
+
const parsed = JSON.parse(raw);
|
|
2318
|
+
if (parsed && typeof parsed === "object") record = parsed;
|
|
2319
|
+
} catch {
|
|
2320
|
+
record = null;
|
|
2321
|
+
}
|
|
2322
|
+
if (!record || record.schema_version !== 2 || !Number.isSafeInteger(record.pid) || Number(record.pid) < 1 || typeof record.owner_token !== "string" || !record.owner_token || typeof record.process_identity !== "string" || !record.process_identity || typeof record.boot_identity !== "string" || !record.boot_identity) {
|
|
2323
|
+
if (Date.now() - stat.mtimeMs < PROCESS_LOCK_PUBLICATION_GRACE_MS) return { state: "busy" };
|
|
2324
|
+
stale = true;
|
|
2325
|
+
return { state: "stale", handle, stat };
|
|
2326
|
+
}
|
|
2327
|
+
const ownerPid = Number(record.pid);
|
|
2328
|
+
const storedIdentity = record.process_identity;
|
|
2329
|
+
const cacheKey = resolve(path);
|
|
2330
|
+
if (await probes.currentBootIdentity() === record.boot_identity && probes.isProcessAlive(ownerPid)) {
|
|
2331
|
+
const cached = processLockObservationCache.get(cacheKey);
|
|
2332
|
+
const observedIdentity = cached && cached.dev === stat.dev && cached.ino === stat.ino && cached.pid === ownerPid && cached.storedIdentity === storedIdentity && Date.now() - cached.observedAtMs < 5e3 ? cached.observedIdentity : await probes.observeIdentity(ownerPid);
|
|
2333
|
+
if (!observedIdentity) {
|
|
2334
|
+
throw new Error("Inference host could not verify the existing lock owner identity.");
|
|
2335
|
+
}
|
|
2336
|
+
processLockObservationCache.set(cacheKey, {
|
|
2337
|
+
dev: stat.dev,
|
|
2338
|
+
ino: stat.ino,
|
|
2339
|
+
pid: ownerPid,
|
|
2340
|
+
storedIdentity,
|
|
2341
|
+
observedIdentity,
|
|
2342
|
+
observedAtMs: Date.now()
|
|
2343
|
+
});
|
|
2344
|
+
if (inferenceProcessIdentitiesMatch(storedIdentity, observedIdentity)) {
|
|
2345
|
+
return { state: "busy" };
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
stale = true;
|
|
2349
|
+
return { state: "stale", handle, stat };
|
|
2350
|
+
} finally {
|
|
2351
|
+
if (!stale) await handle.close().catch(() => void 0);
|
|
2352
|
+
}
|
|
2353
|
+
};
|
|
2354
|
+
var removeVerifiedStaleProcessLockFile = async (path, inspection, assertExclusive) => {
|
|
2355
|
+
try {
|
|
2356
|
+
await assertExclusive();
|
|
2357
|
+
let current;
|
|
2358
|
+
try {
|
|
2359
|
+
current = await lstat(path);
|
|
2360
|
+
} catch (error) {
|
|
2361
|
+
if (error.code === "ENOENT") return;
|
|
2362
|
+
throw error;
|
|
2363
|
+
}
|
|
2364
|
+
const opened = await inspection.handle.stat();
|
|
2365
|
+
if (current.isSymbolicLink() || !sameProcessLockFile(current, opened) || !sameProcessLockFile(opened, inspection.stat)) {
|
|
2366
|
+
throw new InferenceHostProcessLockBusyError();
|
|
2367
|
+
}
|
|
2368
|
+
if (process.platform === "win32") await inspection.handle.close();
|
|
2369
|
+
try {
|
|
2370
|
+
await rm2(path);
|
|
2371
|
+
} catch (error) {
|
|
2372
|
+
if (error.code !== "ENOENT") throw error;
|
|
2373
|
+
}
|
|
2374
|
+
} finally {
|
|
2375
|
+
await inspection.handle.close().catch(() => void 0);
|
|
2376
|
+
}
|
|
2377
|
+
processLockObservationCache.delete(resolve(path));
|
|
2378
|
+
invalidateWindowsAclCache(path);
|
|
2379
|
+
await syncInferenceDirectory(dirname2(path));
|
|
2380
|
+
};
|
|
2381
|
+
var removeStaleProcessLockReclaimGuard = async (lockPath, guardPath, inspection) => {
|
|
2382
|
+
const directory = dirname2(lockPath);
|
|
2383
|
+
const prefix = `${basename(lockPath)}.reclaim-ballot.`;
|
|
2384
|
+
try {
|
|
2385
|
+
for (let attempt = 0; attempt < PROCESS_LOCK_GUARD_ELECTION_ATTEMPTS; attempt += 1) {
|
|
2386
|
+
if (attempt > 0) {
|
|
2387
|
+
await new Promise((resolvePromise) => setTimeout(
|
|
2388
|
+
resolvePromise,
|
|
2389
|
+
Math.floor(Math.random() * PROCESS_LOCK_GUARD_ELECTION_BACKOFF_MS * 2 ** Math.min(attempt, 4))
|
|
2390
|
+
));
|
|
2391
|
+
let current;
|
|
2392
|
+
try {
|
|
2393
|
+
current = await lstat(guardPath);
|
|
2394
|
+
} catch (error) {
|
|
2395
|
+
if (error.code === "ENOENT") return;
|
|
2396
|
+
throw error;
|
|
2397
|
+
}
|
|
2398
|
+
if (!sameProcessLockFile(current, inspection.stat)) return;
|
|
2399
|
+
}
|
|
2400
|
+
const ballotName = `${prefix}${randomBytes(16).toString("hex")}`;
|
|
2401
|
+
const ballotPath = join2(directory, ballotName);
|
|
2402
|
+
const ballot = await openExclusiveProcessLockFile(ballotPath);
|
|
2403
|
+
if (!ballot) continue;
|
|
2404
|
+
await ballot.close();
|
|
2405
|
+
const electedAt = performance.now();
|
|
2406
|
+
try {
|
|
2407
|
+
let contested = false;
|
|
2408
|
+
for (const name of await readdir2(directory)) {
|
|
2409
|
+
if (!name.startsWith(prefix) || name === ballotName) continue;
|
|
2410
|
+
const otherPath = join2(directory, name);
|
|
2411
|
+
let other;
|
|
2412
|
+
try {
|
|
2413
|
+
other = await lstat(otherPath);
|
|
2414
|
+
} catch (error) {
|
|
2415
|
+
if (error.code === "ENOENT") continue;
|
|
2416
|
+
throw error;
|
|
2417
|
+
}
|
|
2418
|
+
if (!other.isDirectory() && Date.now() - other.mtimeMs > PROCESS_LOCK_GUARD_ELECTION_STALE_MS) {
|
|
2419
|
+
await rm2(otherPath, { force: true });
|
|
2420
|
+
continue;
|
|
2421
|
+
}
|
|
2422
|
+
contested = true;
|
|
2423
|
+
}
|
|
2424
|
+
if (contested) continue;
|
|
2425
|
+
await removeVerifiedStaleProcessLockFile(guardPath, inspection, async () => {
|
|
2426
|
+
if (performance.now() - electedAt > PROCESS_LOCK_GUARD_ELECTION_LEASE_MS) {
|
|
2427
|
+
throw new InferenceHostProcessLockBusyError();
|
|
2428
|
+
}
|
|
2429
|
+
});
|
|
2430
|
+
return;
|
|
2431
|
+
} finally {
|
|
2432
|
+
await rm2(ballotPath, { force: true }).catch(() => void 0);
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
throw new InferenceHostProcessLockBusyError();
|
|
2436
|
+
} finally {
|
|
2437
|
+
await inspection.handle.close().catch(() => void 0);
|
|
2438
|
+
}
|
|
2439
|
+
};
|
|
2440
|
+
var acquireProcessLockReclaimGuard = async (lockPath, ownerRecord, probes) => {
|
|
2441
|
+
const guardPath = `${lockPath}.reclaim`;
|
|
2442
|
+
const guardToken = randomBytes(24).toString("base64url");
|
|
2443
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
2444
|
+
const handle = await openExclusiveProcessLockFile(guardPath);
|
|
2445
|
+
if (handle) {
|
|
2446
|
+
try {
|
|
2447
|
+
await handle.writeFile(ownerRecord(guardToken), "utf8");
|
|
2448
|
+
} catch (error) {
|
|
2449
|
+
await handle.close().catch(() => void 0);
|
|
2450
|
+
await rm2(guardPath, { force: true }).catch(() => void 0);
|
|
2451
|
+
throw error;
|
|
2452
|
+
}
|
|
2453
|
+
await handle.close();
|
|
2454
|
+
await secureWindowsPrivatePath(guardPath, "file", "harden");
|
|
2455
|
+
const isHeld = async () => {
|
|
2456
|
+
let reader;
|
|
2457
|
+
try {
|
|
2458
|
+
reader = await open(guardPath, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
2459
|
+
} catch (error) {
|
|
2460
|
+
if (error.code === "ENOENT") return false;
|
|
2461
|
+
throw error;
|
|
2462
|
+
}
|
|
2463
|
+
try {
|
|
2464
|
+
const [opened, current] = await Promise.all([reader.stat(), lstat(guardPath)]);
|
|
2465
|
+
if (current.isSymbolicLink() || current.dev !== opened.dev || current.ino !== opened.ino) {
|
|
2466
|
+
return false;
|
|
2467
|
+
}
|
|
2468
|
+
const parsed = JSON.parse(await reader.readFile("utf8"));
|
|
2469
|
+
return parsed.owner_token === guardToken;
|
|
2470
|
+
} catch {
|
|
2471
|
+
return false;
|
|
2472
|
+
} finally {
|
|
2473
|
+
await reader.close().catch(() => void 0);
|
|
2474
|
+
}
|
|
2475
|
+
};
|
|
2476
|
+
return {
|
|
2477
|
+
assertHeld: async () => {
|
|
2478
|
+
if (!await isHeld()) throw new InferenceHostProcessLockBusyError();
|
|
2479
|
+
},
|
|
2480
|
+
// Best effort: a guard left behind carries this live process identity
|
|
2481
|
+
// and becomes reclaimable once this process exits.
|
|
2482
|
+
release: async () => {
|
|
2483
|
+
try {
|
|
2484
|
+
if (!await isHeld()) return;
|
|
2485
|
+
await rm2(guardPath);
|
|
2486
|
+
processLockObservationCache.delete(resolve(guardPath));
|
|
2487
|
+
invalidateWindowsAclCache(guardPath);
|
|
2488
|
+
} catch {
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
};
|
|
2492
|
+
}
|
|
2493
|
+
if (attempt > 0) break;
|
|
2494
|
+
const inspection = await inspectProcessLockFile(guardPath, PROCESS_LOCK_GUARD_LABEL, probes);
|
|
2495
|
+
if (inspection.state === "busy") break;
|
|
2496
|
+
if (inspection.state === "stale") {
|
|
2497
|
+
await removeStaleProcessLockReclaimGuard(lockPath, guardPath, inspection);
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
throw new InferenceHostProcessLockBusyError();
|
|
2501
|
+
};
|
|
2502
|
+
async function acquireInferenceHostProcessLock(path, dependencies = {}) {
|
|
2503
|
+
const directory = dirname2(path);
|
|
2504
|
+
await ensureInferencePrivateDirectory(directory);
|
|
2505
|
+
const ownerToken = randomBytes(24).toString("base64url");
|
|
2506
|
+
const probes = {
|
|
2507
|
+
currentBootIdentity: dependencies.currentBootIdentity ?? (() => readInferenceSystemBootIdentity()),
|
|
2508
|
+
observeIdentity: dependencies.processIdentity ?? defaultProcessIdentity,
|
|
2509
|
+
isProcessAlive: dependencies.processAlive ?? ((pid) => {
|
|
2510
|
+
try {
|
|
2511
|
+
process.kill(pid, 0);
|
|
2512
|
+
return true;
|
|
2513
|
+
} catch (error) {
|
|
2514
|
+
if (error.code === "ESRCH") return false;
|
|
2515
|
+
throw error;
|
|
2516
|
+
}
|
|
2517
|
+
})
|
|
2518
|
+
};
|
|
2519
|
+
const [ownerIdentity, bootIdentity] = await Promise.all([
|
|
2520
|
+
(dependencies.currentProcessIdentity ?? defaultCurrentProcessIdentity)(),
|
|
2521
|
+
probes.currentBootIdentity()
|
|
2522
|
+
]);
|
|
2523
|
+
const ownerRecord = (token) => `${JSON.stringify({
|
|
2524
|
+
schema_version: 2,
|
|
2525
|
+
pid: process.pid,
|
|
2526
|
+
owner_token: token,
|
|
2527
|
+
process_identity: ownerIdentity,
|
|
2528
|
+
boot_identity: bootIdentity
|
|
2529
|
+
})}
|
|
2530
|
+
`;
|
|
2531
|
+
let handle = await openExclusiveProcessLockFile(path);
|
|
2532
|
+
if (!handle) {
|
|
2533
|
+
const inspection = await inspectProcessLockFile(path, PROCESS_LOCK_LABEL, probes);
|
|
2534
|
+
if (inspection.state === "busy") throw new InferenceHostProcessLockBusyError();
|
|
2535
|
+
if (inspection.state === "stale") {
|
|
2536
|
+
await inspection.handle.close();
|
|
2537
|
+
const guard = await acquireProcessLockReclaimGuard(path, ownerRecord, probes);
|
|
2538
|
+
try {
|
|
2539
|
+
const guarded = await inspectProcessLockFile(path, PROCESS_LOCK_LABEL, probes);
|
|
2540
|
+
if (guarded.state === "busy") throw new InferenceHostProcessLockBusyError();
|
|
2541
|
+
if (guarded.state === "stale") {
|
|
2542
|
+
await removeVerifiedStaleProcessLockFile(path, guarded, guard.assertHeld);
|
|
2543
|
+
}
|
|
2544
|
+
handle = await openExclusiveProcessLockFile(path);
|
|
2545
|
+
} finally {
|
|
2546
|
+
await guard.release();
|
|
2547
|
+
}
|
|
2548
|
+
} else {
|
|
2549
|
+
handle = await openExclusiveProcessLockFile(path);
|
|
2550
|
+
}
|
|
2551
|
+
if (!handle) throw new InferenceHostProcessLockBusyError();
|
|
2552
|
+
}
|
|
2553
|
+
try {
|
|
2554
|
+
await handle.writeFile(ownerRecord(ownerToken), "utf8");
|
|
2555
|
+
await handle.sync();
|
|
2556
|
+
} catch (error) {
|
|
2557
|
+
const created = await handle.stat().catch(() => null);
|
|
2558
|
+
await handle.close().catch(() => void 0);
|
|
2559
|
+
const current = await lstat(path).catch(() => null);
|
|
2560
|
+
if (created && current && current.dev === created.dev && current.ino === created.ino) {
|
|
2561
|
+
await rm2(path, { force: true }).catch(() => void 0);
|
|
2562
|
+
}
|
|
2563
|
+
throw error;
|
|
2564
|
+
}
|
|
2565
|
+
await handle.close();
|
|
2566
|
+
await secureWindowsPrivatePath(path, "file", "harden");
|
|
2567
|
+
await syncInferenceDirectory(directory);
|
|
2568
|
+
let released = false;
|
|
2569
|
+
return {
|
|
2570
|
+
path,
|
|
2571
|
+
ownerToken,
|
|
2572
|
+
release: async () => {
|
|
2573
|
+
if (released) return;
|
|
2574
|
+
const metadata = await lstat(path);
|
|
2575
|
+
if (metadata.isSymbolicLink() || !metadata.isFile()) {
|
|
2576
|
+
throw new Error("Inference host process lock is unsafe.");
|
|
2577
|
+
}
|
|
2578
|
+
const parsed = JSON.parse(await readFile2(path, "utf8"));
|
|
2579
|
+
if (parsed.owner_token !== ownerToken) {
|
|
2580
|
+
throw new Error("Inference host process lock ownership changed.");
|
|
2581
|
+
}
|
|
2582
|
+
await rm2(path);
|
|
2583
|
+
processLockObservationCache.delete(resolve(path));
|
|
2584
|
+
await syncInferenceDirectory(directory);
|
|
2585
|
+
released = true;
|
|
2586
|
+
}
|
|
2587
|
+
};
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
// lib/inference-host/routing.ts
|
|
2591
|
+
init_define_VTX_EXO_POLICY();
|
|
2592
|
+
init_define_VTX_GROK_POLICY();
|
|
2593
|
+
init_define_VTX_PI_MODEL_POLICY();
|
|
2594
|
+
var INFERENCE_HOST_VALUE_OPTIONS = /* @__PURE__ */ new Set([
|
|
2595
|
+
"--display-name",
|
|
2596
|
+
"--max-concurrency",
|
|
2597
|
+
"--adapter",
|
|
2598
|
+
"--model",
|
|
2599
|
+
"--model-label",
|
|
2600
|
+
"--effort",
|
|
2601
|
+
"--wait-seconds",
|
|
2602
|
+
"--lines",
|
|
2603
|
+
"--service-manifest",
|
|
2604
|
+
"--instance"
|
|
2605
|
+
]);
|
|
2606
|
+
var isInferenceHostCliInvocation = (argv) => {
|
|
2607
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
2608
|
+
const argument = argv[index];
|
|
2609
|
+
if (INFERENCE_HOST_VALUE_OPTIONS.has(argument)) {
|
|
2610
|
+
index += 1;
|
|
2611
|
+
continue;
|
|
2612
|
+
}
|
|
2613
|
+
if (argument.startsWith("--")) {
|
|
2614
|
+
continue;
|
|
2615
|
+
}
|
|
2616
|
+
return argument === "inference-host";
|
|
2617
|
+
}
|
|
2618
|
+
return false;
|
|
2619
|
+
};
|
|
2620
|
+
|
|
2621
|
+
// lib/cli-release/updater.ts
|
|
2622
|
+
var CLI_PACKAGE_NAME = "@vtxmacro/cli";
|
|
2623
|
+
var OFFICIAL_NPM_REGISTRY = "https://registry.npmjs.org";
|
|
2624
|
+
var CLI_UPDATE_COMMAND = `npm install -g ${CLI_PACKAGE_NAME}@latest`;
|
|
2625
|
+
var parseCliReleaseVersion = (value) => {
|
|
2626
|
+
const match = /^(\d{4})\.(\d{1,2})\.(\d{1,3})$/u.exec(value);
|
|
2627
|
+
if (!match) return null;
|
|
2628
|
+
const values = match.slice(1).map(Number);
|
|
2629
|
+
if (values.some((entry) => !Number.isSafeInteger(entry) || entry < 0)) return null;
|
|
2630
|
+
return values;
|
|
2631
|
+
};
|
|
2632
|
+
var compareCliReleaseVersions = (left, right) => {
|
|
2633
|
+
const leftParts = parseCliReleaseVersion(left);
|
|
2634
|
+
const rightParts = parseCliReleaseVersion(right);
|
|
2635
|
+
if (!leftParts || !rightParts) {
|
|
2636
|
+
throw new Error("CLI release version is invalid.");
|
|
2637
|
+
}
|
|
2638
|
+
for (let index = 0; index < leftParts.length; index += 1) {
|
|
2639
|
+
const difference = leftParts[index] - rightParts[index];
|
|
2640
|
+
if (difference !== 0) return difference;
|
|
2641
|
+
}
|
|
2642
|
+
return 0;
|
|
2643
|
+
};
|
|
2644
|
+
var isPathWithin = (parent, child) => {
|
|
2645
|
+
const childRelative = relative2(resolve2(parent), resolve2(child));
|
|
2646
|
+
return childRelative !== "" && !childRelative.startsWith("..") && !isAbsolute2(childRelative);
|
|
2647
|
+
};
|
|
2648
|
+
var defaultRunCliCommand = async (command, args, options = {}) => await new Promise((resolvePromise) => {
|
|
2649
|
+
const child = spawn2(command, [...args], {
|
|
2650
|
+
windowsHide: true,
|
|
2651
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2652
|
+
...options.signal ? { signal: options.signal, killSignal: "SIGKILL" } : {}
|
|
2653
|
+
});
|
|
2654
|
+
const stdout = [];
|
|
2655
|
+
const stderr = [];
|
|
2656
|
+
let settled = false;
|
|
2657
|
+
const settle = (result) => {
|
|
2658
|
+
if (settled) return;
|
|
2659
|
+
settled = true;
|
|
2660
|
+
resolvePromise(result);
|
|
2661
|
+
};
|
|
2662
|
+
child.stdout.on("data", (chunk) => stdout.push(Buffer.from(chunk)));
|
|
2663
|
+
child.stderr.on("data", (chunk) => stderr.push(Buffer.from(chunk)));
|
|
2664
|
+
child.once("error", (error) => settle({ exitCode: 1, stdout: "", stderr: error.message }));
|
|
2665
|
+
child.once("exit", (code) => settle({
|
|
2666
|
+
exitCode: code ?? 1,
|
|
2667
|
+
stdout: Buffer.concat(stdout).toString("utf8"),
|
|
2668
|
+
stderr: Buffer.concat(stderr).toString("utf8")
|
|
2669
|
+
}));
|
|
2670
|
+
});
|
|
2671
|
+
var runCliReleaseNpmCommand = async (executable, args, platform = process.platform, runCommand = defaultRunCliCommand) => {
|
|
2672
|
+
if (platform !== "win32") return await runCommand(join3(dirname3(executable), "npm"), args);
|
|
2673
|
+
const npmScript = windowsPath2.join(
|
|
2674
|
+
windowsPath2.dirname(executable),
|
|
2675
|
+
"node_modules",
|
|
2676
|
+
"npm",
|
|
2677
|
+
"bin",
|
|
2678
|
+
"npm-cli.js"
|
|
2679
|
+
);
|
|
2680
|
+
return await runCommand(executable, [npmScript, ...args]);
|
|
2681
|
+
};
|
|
2682
|
+
var fetchLatestCliRelease = async (fetchImpl, options) => {
|
|
2683
|
+
const registry = (options.registry ?? OFFICIAL_NPM_REGISTRY).replace(/\/+$/u, "");
|
|
2684
|
+
const response = await fetchImpl(`${registry}/%40vtxmacro%2Fcli/latest`, {
|
|
2685
|
+
signal: AbortSignal.any([
|
|
2686
|
+
AbortSignal.timeout(options.timeoutMs),
|
|
2687
|
+
...options.signal ? [options.signal] : []
|
|
2688
|
+
])
|
|
2689
|
+
});
|
|
2690
|
+
if (!response.ok) throw new Error("registry_request_failed");
|
|
2691
|
+
const value = await response.json();
|
|
2692
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2693
|
+
throw new Error("registry_metadata_invalid");
|
|
2694
|
+
}
|
|
2695
|
+
const record = value;
|
|
2696
|
+
const distribution = record.dist;
|
|
2697
|
+
if (record.name !== CLI_PACKAGE_NAME || typeof record.version !== "string" || !parseCliReleaseVersion(record.version) || !distribution || typeof distribution !== "object" || Array.isArray(distribution) || typeof distribution.integrity !== "string") throw new Error("registry_metadata_invalid");
|
|
2698
|
+
return {
|
|
2699
|
+
version: record.version,
|
|
2700
|
+
integrity: distribution.integrity
|
|
2701
|
+
};
|
|
2702
|
+
};
|
|
2703
|
+
var verifyCliReleaseIntegrity = async (tarballPath, integrity) => {
|
|
2704
|
+
const candidates = integrity.split(/\s+/u).map((entry) => {
|
|
2705
|
+
const separator = entry.indexOf("-");
|
|
2706
|
+
return separator > 0 ? { algorithm: entry.slice(0, separator), digest: entry.slice(separator + 1) } : null;
|
|
2707
|
+
}).filter((entry) => entry !== null);
|
|
2708
|
+
const supported = ["sha512", "sha384", "sha256"].map((algorithm) => candidates.find((candidate) => candidate.algorithm === algorithm)).find(Boolean);
|
|
2709
|
+
if (!supported) throw new Error("release_integrity_unsupported");
|
|
2710
|
+
const actual = createHash(supported.algorithm).update(await readFile3(tarballPath)).digest("base64");
|
|
2711
|
+
if (actual !== supported.digest) throw new Error("release_integrity_mismatch");
|
|
2712
|
+
};
|
|
2713
|
+
var verifyInstalledCliRelease = async (prefix, targetVersion, executable, platform, runCommand) => {
|
|
2714
|
+
const packagePath = platform === "win32" ? windowsPath2.join(prefix, "node_modules", "@vtxmacro", "cli") : join3(prefix, "lib", "node_modules", "@vtxmacro", "cli");
|
|
2715
|
+
if (!isPathWithin(prefix, packagePath)) throw new Error("release_install_invalid");
|
|
2716
|
+
const packageJson = JSON.parse(await readFile3(join3(packagePath, "package.json"), "utf8"));
|
|
2717
|
+
if (!packageJson || typeof packageJson !== "object" || Array.isArray(packageJson)) {
|
|
2718
|
+
throw new Error("release_install_invalid");
|
|
2719
|
+
}
|
|
2720
|
+
const packageRecord = packageJson;
|
|
2721
|
+
const bin = packageRecord.bin;
|
|
2722
|
+
const binPath = bin && typeof bin === "object" && !Array.isArray(bin) ? bin.vtx : null;
|
|
2723
|
+
if (packageRecord.name !== CLI_PACKAGE_NAME || packageRecord.version !== targetVersion || typeof binPath !== "string") throw new Error("release_install_invalid");
|
|
2724
|
+
const script = resolve2(packagePath, binPath);
|
|
2725
|
+
if (!isPathWithin(packagePath, script)) throw new Error("release_install_invalid");
|
|
2726
|
+
await access(script);
|
|
2727
|
+
const verified = await runCommand(executable, [script, "--version"]);
|
|
2728
|
+
if (verified.exitCode !== 0 || verified.stdout.trim() !== targetVersion) {
|
|
2729
|
+
throw new Error("release_version_verification_failed");
|
|
2730
|
+
}
|
|
2731
|
+
return script;
|
|
2732
|
+
};
|
|
2733
|
+
var cleanupInterruptedReleaseStaging = async (releasesPath) => {
|
|
2734
|
+
const entries = await readdir3(releasesPath, { withFileTypes: true });
|
|
2735
|
+
for (const entry of entries) {
|
|
2736
|
+
if (entry.isDirectory() && /^\.(?:staging|download)-\d{4}\.\d{1,2}\.\d{1,3}-[0-9a-f-]{36}$/u.test(entry.name)) {
|
|
2737
|
+
await rm3(resolve2(releasesPath, entry.name), { recursive: true, force: true });
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
};
|
|
2741
|
+
var ensureUpdaterPrivateDirectory = async (path, root, dependencies = {}) => {
|
|
2742
|
+
const platform = dependencies.platform ?? process.platform;
|
|
2743
|
+
if (platform !== "win32" && (resolve2(path) === resolve2(root) || isPathWithin(root, path))) {
|
|
2744
|
+
const existing = await lstat2(path).catch(() => null);
|
|
2745
|
+
if (existing && existing.isDirectory() && !existing.isSymbolicLink() && (existing.mode & 63) !== 0 && (typeof process.getuid !== "function" || existing.uid === process.getuid())) {
|
|
2746
|
+
await chmod(path, 448);
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
await ensureInferencePrivateDirectory(path, { ...dependencies, defaultRoot: root });
|
|
2750
|
+
};
|
|
2751
|
+
var stageCliRelease = async (input) => {
|
|
2752
|
+
const { releasesPath, target, executable, platform } = input;
|
|
2753
|
+
const registry = input.registry ?? OFFICIAL_NPM_REGISTRY;
|
|
2754
|
+
const deadline = new AbortController();
|
|
2755
|
+
let reason = "release_stage_timeout";
|
|
2756
|
+
const timer = setTimeout(() => deadline.abort(), input.timeoutMs);
|
|
2757
|
+
const onCancel = () => {
|
|
2758
|
+
reason = "release_stage_cancelled";
|
|
2759
|
+
deadline.abort();
|
|
2760
|
+
};
|
|
2761
|
+
if (input.signal?.aborted) onCancel();
|
|
2762
|
+
input.signal?.addEventListener("abort", onCancel, { once: true });
|
|
2763
|
+
const timedOut = () => new Error(reason);
|
|
2764
|
+
const runCommand = async (command, args) => {
|
|
2765
|
+
if (deadline.signal.aborted) throw timedOut();
|
|
2766
|
+
return await new Promise((resolveCommand, rejectCommand) => {
|
|
2767
|
+
const onAbort = () => rejectCommand(timedOut());
|
|
2768
|
+
deadline.signal.addEventListener("abort", onAbort, { once: true });
|
|
2769
|
+
input.runCommand(command, args, { signal: deadline.signal }).then(resolveCommand, rejectCommand).finally(() => deadline.signal.removeEventListener("abort", onAbort));
|
|
2770
|
+
});
|
|
2771
|
+
};
|
|
2772
|
+
try {
|
|
2773
|
+
return await stageCliReleaseWithin({ ...input, registry, runCommand });
|
|
2774
|
+
} finally {
|
|
2775
|
+
clearTimeout(timer);
|
|
2776
|
+
input.signal?.removeEventListener("abort", onCancel);
|
|
2777
|
+
}
|
|
2778
|
+
};
|
|
2779
|
+
var stageCliReleaseWithin = async (input) => {
|
|
2780
|
+
const { releasesPath, target, executable, platform, runCommand, registry } = input;
|
|
2781
|
+
const finalPath = join3(releasesPath, target.version);
|
|
2782
|
+
const stagingPath = join3(releasesPath, `.staging-${target.version}-${randomUUID()}`);
|
|
2783
|
+
const downloadPath = join3(releasesPath, `.download-${target.version}-${randomUUID()}`);
|
|
2784
|
+
await ensureUpdaterPrivateDirectory(input.privateRoot, input.privateRoot, { platform });
|
|
2785
|
+
await ensureUpdaterPrivateDirectory(releasesPath, input.privateRoot, { platform });
|
|
2786
|
+
await cleanupInterruptedReleaseStaging(releasesPath);
|
|
2787
|
+
try {
|
|
2788
|
+
await access(finalPath);
|
|
2789
|
+
const existingScript = await verifyInstalledCliRelease(
|
|
2790
|
+
finalPath,
|
|
2791
|
+
target.version,
|
|
2792
|
+
executable,
|
|
2793
|
+
platform,
|
|
2794
|
+
runCommand
|
|
2795
|
+
);
|
|
2796
|
+
return { package_version: target.version, integrity: target.integrity, script: existingScript };
|
|
2797
|
+
} catch (error) {
|
|
2798
|
+
if (error.code !== "ENOENT") throw error;
|
|
2799
|
+
}
|
|
2800
|
+
await mkdir3(stagingPath, { recursive: true, mode: 448 });
|
|
2801
|
+
await mkdir3(downloadPath, { recursive: true, mode: 448 });
|
|
2802
|
+
try {
|
|
2803
|
+
const packed = await runCliReleaseNpmCommand(executable, [
|
|
2804
|
+
"pack",
|
|
2805
|
+
`${CLI_PACKAGE_NAME}@${target.version}`,
|
|
2806
|
+
"--pack-destination",
|
|
2807
|
+
downloadPath,
|
|
2808
|
+
"--registry",
|
|
2809
|
+
registry,
|
|
2810
|
+
"--json"
|
|
2811
|
+
], platform, runCommand);
|
|
2812
|
+
if (packed.exitCode !== 0) throw new Error("release_download_failed");
|
|
2813
|
+
let filename;
|
|
2814
|
+
try {
|
|
2815
|
+
const parsed = JSON.parse(packed.stdout);
|
|
2816
|
+
filename = String(parsed[0]?.filename ?? "");
|
|
2817
|
+
} catch {
|
|
2818
|
+
throw new Error("release_download_invalid");
|
|
2819
|
+
}
|
|
2820
|
+
const tarballPath = resolve2(downloadPath, filename);
|
|
2821
|
+
if (!filename || !isPathWithin(downloadPath, tarballPath)) {
|
|
2822
|
+
throw new Error("release_download_invalid");
|
|
2823
|
+
}
|
|
2824
|
+
await verifyCliReleaseIntegrity(tarballPath, target.integrity);
|
|
2825
|
+
const installed = await runCliReleaseNpmCommand(executable, [
|
|
2826
|
+
"install",
|
|
2827
|
+
"--global",
|
|
2828
|
+
"--prefix",
|
|
2829
|
+
stagingPath,
|
|
2830
|
+
tarballPath,
|
|
2831
|
+
"--no-audit",
|
|
2832
|
+
"--no-fund",
|
|
2833
|
+
"--registry",
|
|
2834
|
+
registry
|
|
2835
|
+
], platform, runCommand);
|
|
2836
|
+
if (installed.exitCode !== 0) throw new Error("release_install_failed");
|
|
2837
|
+
const stagedScript = await verifyInstalledCliRelease(
|
|
2838
|
+
stagingPath,
|
|
2839
|
+
target.version,
|
|
2840
|
+
executable,
|
|
2841
|
+
platform,
|
|
2842
|
+
runCommand
|
|
2843
|
+
);
|
|
2844
|
+
try {
|
|
2845
|
+
await rename2(stagingPath, finalPath);
|
|
2846
|
+
} catch (error) {
|
|
2847
|
+
if (!["EEXIST", "ENOTEMPTY"].includes(String(error.code))) throw error;
|
|
2848
|
+
throw new Error("release_path_already_exists");
|
|
2849
|
+
}
|
|
2850
|
+
return {
|
|
2851
|
+
package_version: target.version,
|
|
2852
|
+
integrity: target.integrity,
|
|
2853
|
+
script: resolve2(finalPath, relative2(stagingPath, stagedScript))
|
|
2854
|
+
};
|
|
2855
|
+
} finally {
|
|
2856
|
+
await rm3(stagingPath, { recursive: true, force: true }).catch(() => void 0);
|
|
2857
|
+
await rm3(downloadPath, { recursive: true, force: true }).catch(() => void 0);
|
|
2858
|
+
}
|
|
2859
|
+
};
|
|
2860
|
+
var readCliReleaseRejection = async (path) => {
|
|
2861
|
+
try {
|
|
2862
|
+
const value = JSON.parse(await readFile3(path, "utf8"));
|
|
2863
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
2864
|
+
const record = value;
|
|
2865
|
+
if (record.schema_version !== "vtx_inference_service_release_rejection_v1" || typeof record.package_version !== "string" || !parseCliReleaseVersion(record.package_version) || typeof record.rejected_at !== "string" || !Number.isFinite(Date.parse(record.rejected_at)) || typeof record.reason !== "string" || !/^[a-z0-9_]{1,96}$/u.test(record.reason)) return null;
|
|
2866
|
+
return record;
|
|
2867
|
+
} catch {
|
|
2868
|
+
return null;
|
|
2869
|
+
}
|
|
2870
|
+
};
|
|
2871
|
+
var writeCliReleaseRejection = async (path, rejection, privateRoot) => {
|
|
2872
|
+
if (privateRoot) await ensureUpdaterPrivateDirectory(privateRoot, privateRoot);
|
|
2873
|
+
await writeAtomicInferencePrivateFile(
|
|
2874
|
+
path,
|
|
2875
|
+
`${JSON.stringify(rejection, null, 2)}
|
|
2876
|
+
`,
|
|
2877
|
+
privateRoot ? { defaultRoot: privateRoot } : {}
|
|
2878
|
+
);
|
|
2879
|
+
};
|
|
2880
|
+
var CLI_BACKGROUND_STAGE_ARGUMENT = "--vtx-internal-stage-cli-update";
|
|
2881
|
+
var READY_MESSAGE_TYPE = "vtx-cli-ready-v1";
|
|
2882
|
+
var CHILD_ENV = "VTX_CLI_SELF_UPDATE_CHILD";
|
|
2883
|
+
var READY_PROTOCOL_ENV = "VTX_CLI_READY_PROTOCOL";
|
|
2884
|
+
var EMPTY_STATE = {
|
|
2885
|
+
schema_version: "vtx_cli_update_state_v1",
|
|
2886
|
+
checked_at: null,
|
|
2887
|
+
latest: null,
|
|
2888
|
+
minimum_supported_version: null,
|
|
2889
|
+
minimum_api_url: null,
|
|
2890
|
+
active: null,
|
|
2891
|
+
previous: null
|
|
2892
|
+
};
|
|
2893
|
+
var resolveCliUpdatePaths = (env) => {
|
|
2894
|
+
const home = String(env.VTX_HOME || "").trim() || join3(homedir3(), ".vtx");
|
|
2895
|
+
const root = join3(home, "cli-update");
|
|
2896
|
+
return {
|
|
2897
|
+
root,
|
|
2898
|
+
statePath: join3(root, "state.json"),
|
|
2899
|
+
releasesPath: join3(root, "releases"),
|
|
2900
|
+
lockPath: join3(root, "update.lock"),
|
|
2901
|
+
logPath: join3(root, "update.log"),
|
|
2902
|
+
rejectionPath: join3(root, "release-rejection.json")
|
|
2903
|
+
};
|
|
2904
|
+
};
|
|
2905
|
+
var cliAutoUpdateDisabled = (env) => /^(?:0|false|off|no)$/iu.test(String(env.VTX_CLI_AUTO_UPDATE ?? "").trim());
|
|
2906
|
+
var resolveRegistry = (env) => {
|
|
2907
|
+
const configured = String(env.VTX_CLI_UPDATE_REGISTRY || "").trim().replace(/\/+$/u, "");
|
|
2908
|
+
if (!configured) return OFFICIAL_NPM_REGISTRY;
|
|
2909
|
+
let url;
|
|
2910
|
+
try {
|
|
2911
|
+
url = new URL(configured);
|
|
2912
|
+
} catch {
|
|
2913
|
+
throw new Error("VTX_CLI_UPDATE_REGISTRY is not a URL.");
|
|
2914
|
+
}
|
|
2915
|
+
const loopback = ["127.0.0.1", "localhost", "[::1]"].includes(url.hostname);
|
|
2916
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) {
|
|
2917
|
+
throw new Error("VTX_CLI_UPDATE_REGISTRY must be https, or http on loopback.");
|
|
2918
|
+
}
|
|
2919
|
+
return configured;
|
|
2920
|
+
};
|
|
2921
|
+
var validSelection = async (value, releasesPath) => {
|
|
2922
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
2923
|
+
const record = value;
|
|
2924
|
+
if (typeof record.package_version !== "string" || !parseCliReleaseVersion(record.package_version) || typeof record.integrity !== "string" || typeof record.script !== "string" || !isPathWithin(join3(releasesPath, record.package_version), record.script) || typeof record.selected_at !== "string" || record.confirmed_at !== null && typeof record.confirmed_at !== "string") return null;
|
|
2925
|
+
try {
|
|
2926
|
+
await access(record.script);
|
|
2927
|
+
} catch {
|
|
2928
|
+
return null;
|
|
2929
|
+
}
|
|
2930
|
+
return record;
|
|
2931
|
+
};
|
|
2932
|
+
var readState = async (paths) => {
|
|
2933
|
+
let value;
|
|
2934
|
+
try {
|
|
2935
|
+
value = JSON.parse(await readFile3(paths.statePath, "utf8"));
|
|
2936
|
+
} catch {
|
|
2937
|
+
return EMPTY_STATE;
|
|
2938
|
+
}
|
|
2939
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return EMPTY_STATE;
|
|
2940
|
+
const record = value;
|
|
2941
|
+
if (record.schema_version !== "vtx_cli_update_state_v1") return EMPTY_STATE;
|
|
2942
|
+
const latest = record.latest && typeof record.latest === "object" && typeof record.latest.version === "string" && parseCliReleaseVersion(String(record.latest.version)) && typeof record.latest.integrity === "string" ? record.latest : null;
|
|
2943
|
+
const minimum = typeof record.minimum_supported_version === "string" && parseCliReleaseVersion(record.minimum_supported_version) ? record.minimum_supported_version : null;
|
|
2944
|
+
return {
|
|
2945
|
+
schema_version: "vtx_cli_update_state_v1",
|
|
2946
|
+
checked_at: typeof record.checked_at === "string" && Number.isFinite(Date.parse(record.checked_at)) ? record.checked_at : null,
|
|
2947
|
+
latest,
|
|
2948
|
+
minimum_supported_version: minimum,
|
|
2949
|
+
minimum_api_url: typeof record.minimum_api_url === "string" ? record.minimum_api_url : null,
|
|
2950
|
+
active: await validSelection(record.active, paths.releasesPath),
|
|
2951
|
+
previous: await validSelection(record.previous, paths.releasesPath)
|
|
2952
|
+
};
|
|
2953
|
+
};
|
|
2954
|
+
var writeState = async (paths, state) => {
|
|
2955
|
+
await ensureUpdaterPrivateDirectory(paths.root, paths.root);
|
|
2956
|
+
await writeAtomicInferencePrivateFile(
|
|
2957
|
+
paths.statePath,
|
|
2958
|
+
`${JSON.stringify(state, null, 2)}
|
|
2959
|
+
`,
|
|
2960
|
+
{ defaultRoot: paths.root }
|
|
2961
|
+
);
|
|
2962
|
+
};
|
|
2963
|
+
var appendCliUpdateLog = async (paths, event, fields = {}) => {
|
|
2964
|
+
try {
|
|
2965
|
+
await ensureUpdaterPrivateDirectory(paths.root, paths.root);
|
|
2966
|
+
await appendFile(
|
|
2967
|
+
paths.logPath,
|
|
2968
|
+
`${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), event, ...fields })}
|
|
2969
|
+
`,
|
|
2970
|
+
{ encoding: "utf8", mode: 384 }
|
|
2971
|
+
);
|
|
2972
|
+
} catch {
|
|
2973
|
+
}
|
|
2974
|
+
};
|
|
2975
|
+
var isSkippedInvocation = (argv, env) => (
|
|
2976
|
+
// A re-run on a selected release, or a process the service updater owns.
|
|
2977
|
+
env[CHILD_ENV] === "1" || env.VTX_INFERENCE_HOST_SERVICE_CHILD === "1" || env.VTX_INFERENCE_HOST_LAUNCHER_PROTOCOL === "1" || isInferenceHostCliInvocation(argv) && argv.includes("run-internal")
|
|
2978
|
+
);
|
|
2979
|
+
var isVersionQuery = (argv) => argv.length === 1 && (argv[0] === "--version" || argv[0] === "-v");
|
|
2980
|
+
var policyApiUrl = (entry, argv, env) => entry === "vtx" && isInferenceHostCliInvocation(argv) ? resolveInferenceHostApiUrl(env) : resolveAgentCliConfig(env).apiUrl;
|
|
2981
|
+
var fetchMinimumSupportedVersion = async (fetchImpl, apiUrl, timeoutMs, signal) => {
|
|
2982
|
+
const response = await fetchImpl(`${apiUrl}/status/agent-cli`, {
|
|
2983
|
+
headers: { Accept: "application/json" },
|
|
2984
|
+
signal: AbortSignal.any([AbortSignal.timeout(timeoutMs), ...signal ? [signal] : []])
|
|
2985
|
+
});
|
|
2986
|
+
if (!response.ok) throw new Error("policy_request_failed");
|
|
2987
|
+
const value = await response.json();
|
|
2988
|
+
const minimum = value?.minimum_supported_version;
|
|
2989
|
+
if (value?.package_name !== CLI_PACKAGE_NAME || typeof minimum !== "string" || !parseCliReleaseVersion(minimum)) {
|
|
2990
|
+
throw new Error("policy_invalid");
|
|
2991
|
+
}
|
|
2992
|
+
return minimum;
|
|
2993
|
+
};
|
|
2994
|
+
var classifyCliInstall = async (script, platform = process.platform, env = process.env) => {
|
|
2995
|
+
try {
|
|
2996
|
+
const real = await realpath2(script);
|
|
2997
|
+
const releasesPath = resolveCliUpdatePaths(env).releasesPath;
|
|
2998
|
+
if (isPathWithin(releasesPath, real) || isPathWithin(await realpath2(releasesPath).catch(() => releasesPath), real) || /\.service-releases[\\/]/u.test(real)) return "managed";
|
|
2999
|
+
const pathApi = platform === "win32" ? windowsPath2 : { dirname: dirname3, join: join3, resolve: resolve2 };
|
|
3000
|
+
const packageRoot = pathApi.dirname(pathApi.dirname(real));
|
|
3001
|
+
const modulesRoot = pathApi.dirname(pathApi.dirname(packageRoot));
|
|
3002
|
+
if (!packageRoot.endsWith(pathApi.join("node_modules", "@vtxmacro", "cli")) || /[\\/]_npx[\\/]/u.test(real)) return "managed";
|
|
3003
|
+
if (platform === "win32") {
|
|
3004
|
+
await access(windowsPath2.join(windowsPath2.dirname(modulesRoot), "vtx.cmd"));
|
|
3005
|
+
return "global";
|
|
3006
|
+
}
|
|
3007
|
+
if (!modulesRoot.endsWith(join3("lib", "node_modules"))) return "managed";
|
|
3008
|
+
const binLink = join3(dirname3(dirname3(modulesRoot)), "bin", "vtx");
|
|
3009
|
+
return await realpath2(binLink) === await realpath2(join3(packageRoot, "bin", "vtx.js")) ? "global" : "managed";
|
|
3010
|
+
} catch {
|
|
3011
|
+
return "managed";
|
|
3012
|
+
}
|
|
3013
|
+
};
|
|
3014
|
+
var refusal = (current, minimum, reason) => `VTX CLI ${current} is below the minimum supported version ${minimum} and ${reason}. Update it with: ${CLI_UPDATE_COMMAND}`;
|
|
3015
|
+
var inferenceHostServiceManifestPaths = async (env) => {
|
|
3016
|
+
const rootManifest = inferenceHostSupervisorServiceManifestPath(env);
|
|
3017
|
+
const instancesPath = join3(dirname3(rootManifest), "instances");
|
|
3018
|
+
const instances = await readdir3(instancesPath, { withFileTypes: true }).catch(() => []);
|
|
3019
|
+
return [
|
|
3020
|
+
rootManifest,
|
|
3021
|
+
...instances.filter((entry) => entry.isDirectory()).map((entry) => join3(instancesPath, entry.name, "state.json.service.json"))
|
|
3022
|
+
];
|
|
3023
|
+
};
|
|
3024
|
+
var prune = async (paths, keep, env) => {
|
|
3025
|
+
const retained = new Set(keep.filter((item) => item !== null).map((item) => item.package_version));
|
|
3026
|
+
for (const manifestPath of await inferenceHostServiceManifestPaths(env)) {
|
|
3027
|
+
try {
|
|
3028
|
+
const manifest = JSON.parse(await readFile3(manifestPath, "utf8"));
|
|
3029
|
+
if (typeof manifest.script === "string" && isPathWithin(paths.releasesPath, manifest.script)) {
|
|
3030
|
+
retained.add(relative2(paths.releasesPath, manifest.script).split(/[\\/]/u)[0]);
|
|
3031
|
+
}
|
|
3032
|
+
} catch {
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
let entries;
|
|
3036
|
+
try {
|
|
3037
|
+
entries = await readdir3(paths.releasesPath, { withFileTypes: true });
|
|
3038
|
+
} catch {
|
|
3039
|
+
return;
|
|
3040
|
+
}
|
|
3041
|
+
for (const entry of entries) {
|
|
3042
|
+
if (entry.isDirectory() && parseCliReleaseVersion(entry.name) && !retained.has(entry.name)) {
|
|
3043
|
+
await rm3(join3(paths.releasesPath, entry.name), { recursive: true, force: true }).catch(() => void 0);
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
};
|
|
3047
|
+
var stageAndSelect = async (paths, target, input) => {
|
|
3048
|
+
let lock;
|
|
3049
|
+
try {
|
|
3050
|
+
await ensureUpdaterPrivateDirectory(paths.root, paths.root, { platform: input.platform });
|
|
3051
|
+
} catch (error) {
|
|
3052
|
+
const reason = `update_directory_unsafe: ${error instanceof Error ? error.message.slice(0, 120) : "unknown"}`;
|
|
3053
|
+
await appendCliUpdateLog(paths, "cli_update_failed", { package_version: target.version, reason });
|
|
3054
|
+
return { kind: "failed", reason };
|
|
3055
|
+
}
|
|
3056
|
+
try {
|
|
3057
|
+
lock = await acquireInferenceHostProcessLock(paths.lockPath);
|
|
3058
|
+
} catch {
|
|
3059
|
+
return { kind: "busy" };
|
|
3060
|
+
}
|
|
3061
|
+
try {
|
|
3062
|
+
const staged = await stageCliRelease({
|
|
3063
|
+
releasesPath: paths.releasesPath,
|
|
3064
|
+
target,
|
|
3065
|
+
executable: input.executable,
|
|
3066
|
+
platform: input.platform,
|
|
3067
|
+
runCommand: input.runCommand,
|
|
3068
|
+
registry: resolveRegistry(input.env),
|
|
3069
|
+
privateRoot: paths.root,
|
|
3070
|
+
timeoutMs: runtime_policy_generated_default.agent_cli.stage_timeout_ms,
|
|
3071
|
+
signal: input.signal
|
|
3072
|
+
});
|
|
3073
|
+
const state = await readState(paths);
|
|
3074
|
+
const selection = {
|
|
3075
|
+
...staged,
|
|
3076
|
+
selected_at: input.now().toISOString(),
|
|
3077
|
+
confirmed_at: null
|
|
3078
|
+
};
|
|
3079
|
+
const previous = state.active && state.active.package_version !== selection.package_version ? state.active : state.previous;
|
|
3080
|
+
await writeState(paths, { ...state, active: selection, previous });
|
|
3081
|
+
await prune(paths, [selection, previous], input.env);
|
|
3082
|
+
await appendCliUpdateLog(paths, "cli_release_staged", { package_version: selection.package_version });
|
|
3083
|
+
return { kind: "staged", selection };
|
|
3084
|
+
} catch (error) {
|
|
3085
|
+
const reason = error instanceof Error ? error.message.slice(0, 160) : "unknown";
|
|
3086
|
+
await appendCliUpdateLog(paths, "cli_update_failed", { package_version: target.version, reason });
|
|
3087
|
+
return { kind: "failed", reason };
|
|
3088
|
+
} finally {
|
|
3089
|
+
await lock.release().catch(() => void 0);
|
|
3090
|
+
}
|
|
3091
|
+
};
|
|
3092
|
+
var planCliEntryUpdate = async (entry, argv, env, script, dependencies = {}) => {
|
|
3093
|
+
if (isSkippedInvocation(argv, env) || !script) return { kind: "run" };
|
|
3094
|
+
const paths = resolveCliUpdatePaths(env);
|
|
3095
|
+
const current = dependencies.currentVersion ?? agent_cli_release_default.package_version;
|
|
3096
|
+
const now = dependencies.now ?? (() => /* @__PURE__ */ new Date());
|
|
3097
|
+
const executable = dependencies.executable ?? process.execPath;
|
|
3098
|
+
const platform = dependencies.platform ?? process.platform;
|
|
3099
|
+
const runCommand = dependencies.runCommand ?? defaultRunCliCommand;
|
|
3100
|
+
const fetchImpl = dependencies.fetch ?? fetch;
|
|
3101
|
+
const policy = runtime_policy_generated_default.agent_cli;
|
|
3102
|
+
const disabled = cliAutoUpdateDisabled(env);
|
|
3103
|
+
const launcher = resolve2(script);
|
|
3104
|
+
try {
|
|
3105
|
+
let state = await readState(paths);
|
|
3106
|
+
const rejection = await readCliReleaseRejection(paths.rejectionPath);
|
|
3107
|
+
let knownKind = null;
|
|
3108
|
+
const installKind = async () => knownKind ??= await classifyCliInstall(launcher, platform, env);
|
|
3109
|
+
const apiUrl = policyApiUrl(entry, argv, env);
|
|
3110
|
+
const selected = !disabled && state.active && compareCliReleaseVersions(state.active.package_version, current) > 0 && rejection?.package_version !== state.active.package_version && await installKind() === "global" ? state.active : null;
|
|
3111
|
+
const effective = selected?.package_version ?? current;
|
|
3112
|
+
if (isVersionQuery(argv)) return selected ? { kind: "reexec", selection: selected } : { kind: "run" };
|
|
3113
|
+
const cachedMinimum = state.minimum_api_url === apiUrl ? state.minimum_supported_version : null;
|
|
3114
|
+
const checkedAt = state.checked_at ? Date.parse(state.checked_at) : Number.NaN;
|
|
3115
|
+
const due = !Number.isFinite(checkedAt) || now().getTime() - checkedAt >= policy.update_check_interval_ms || now().getTime() < checkedAt || cachedMinimum !== null && compareCliReleaseVersions(effective, cachedMinimum) < 0;
|
|
3116
|
+
let refreshed = false;
|
|
3117
|
+
if (due) {
|
|
3118
|
+
refreshed = true;
|
|
3119
|
+
const [minimumResult, latestResult] = await Promise.allSettled([
|
|
3120
|
+
fetchMinimumSupportedVersion(fetchImpl, apiUrl, policy.update_check_timeout_ms, dependencies.signal),
|
|
3121
|
+
disabled ? Promise.reject(new Error("disabled")) : fetchLatestCliRelease(fetchImpl, {
|
|
3122
|
+
registry: resolveRegistry(env),
|
|
3123
|
+
timeoutMs: policy.update_check_timeout_ms,
|
|
3124
|
+
signal: dependencies.signal
|
|
3125
|
+
})
|
|
3126
|
+
]);
|
|
3127
|
+
state = {
|
|
3128
|
+
...state,
|
|
3129
|
+
checked_at: now().toISOString(),
|
|
3130
|
+
latest: latestResult.status === "fulfilled" ? latestResult.value : state.latest,
|
|
3131
|
+
minimum_supported_version: minimumResult.status === "fulfilled" ? minimumResult.value : state.minimum_supported_version,
|
|
3132
|
+
minimum_api_url: minimumResult.status === "fulfilled" ? apiUrl : state.minimum_api_url
|
|
3133
|
+
};
|
|
3134
|
+
await writeState(paths, state).catch(() => void 0);
|
|
3135
|
+
}
|
|
3136
|
+
const minimum = state.minimum_api_url === apiUrl ? state.minimum_supported_version : null;
|
|
3137
|
+
const belowMinimum = minimum !== null && compareCliReleaseVersions(effective, minimum) < 0;
|
|
3138
|
+
const runSelected = (warning) => selected ? { kind: "reexec", selection: selected } : { kind: "run", ...warning ? { warning } : {} };
|
|
3139
|
+
if (disabled) {
|
|
3140
|
+
return belowMinimum ? { kind: "refuse", message: refusal(current, minimum, "automatic updates are off (VTX_CLI_AUTO_UPDATE)") } : { kind: "run" };
|
|
3141
|
+
}
|
|
3142
|
+
const latest = state.latest;
|
|
3143
|
+
const updateAvailable = latest !== null && compareCliReleaseVersions(latest.version, effective) > 0 && rejection?.package_version !== latest.version;
|
|
3144
|
+
if ((refreshed || belowMinimum) && updateAvailable) {
|
|
3145
|
+
if (await installKind() !== "global") {
|
|
3146
|
+
return belowMinimum ? { kind: "refuse", message: refusal(effective, minimum, "this install is managed outside the VTX CLI updater") } : { kind: "run" };
|
|
3147
|
+
}
|
|
3148
|
+
if (belowMinimum && compareCliReleaseVersions(latest.version, minimum) < 0) {
|
|
3149
|
+
return { kind: "refuse", message: refusal(effective, minimum, `npm latest (${latest.version}) does not meet it`) };
|
|
3150
|
+
}
|
|
3151
|
+
const stageInput = { env, executable, platform, runCommand, now, signal: dependencies.signal };
|
|
3152
|
+
if (entry === "vtx-mcp" && !belowMinimum) {
|
|
3153
|
+
(dependencies.spawnBackgroundStage ?? spawnBackgroundStage)(executable, launcher, env);
|
|
3154
|
+
return runSelected();
|
|
3155
|
+
}
|
|
3156
|
+
(dependencies.stderr ?? process.stderr).write(
|
|
3157
|
+
`Updating VTX CLI ${effective} to ${latest.version}...
|
|
3158
|
+
`
|
|
3159
|
+
);
|
|
3160
|
+
const outcome = await stageAndSelect(paths, latest, stageInput);
|
|
3161
|
+
if (outcome.kind === "staged") return { kind: "reexec", selection: outcome.selection };
|
|
3162
|
+
const reason = outcome.kind === "busy" ? "another update is in progress" : `the update failed (${outcome.reason})`;
|
|
3163
|
+
if (belowMinimum) return { kind: "refuse", message: refusal(effective, minimum, reason) };
|
|
3164
|
+
return runSelected(`VTX CLI update to ${latest.version} did not complete: ${reason}; continuing on ${effective}.`);
|
|
3165
|
+
}
|
|
3166
|
+
if (belowMinimum) {
|
|
3167
|
+
const reason = latest === null ? "the npm registry could not be reached" : rejection?.package_version === latest.version ? `release ${latest.version} failed to start here` : "no newer release is available";
|
|
3168
|
+
return { kind: "refuse", message: refusal(effective, minimum, reason) };
|
|
3169
|
+
}
|
|
3170
|
+
return runSelected();
|
|
3171
|
+
} catch (error) {
|
|
3172
|
+
await appendCliUpdateLog(paths, "cli_update_check_failed", {
|
|
3173
|
+
reason: error instanceof Error ? error.message.slice(0, 160) : "unknown"
|
|
3174
|
+
});
|
|
3175
|
+
return { kind: "run" };
|
|
3176
|
+
}
|
|
3177
|
+
};
|
|
3178
|
+
var spawnBackgroundStage = (executable, script, env) => {
|
|
3179
|
+
try {
|
|
3180
|
+
const child = spawn2(executable, [script, CLI_BACKGROUND_STAGE_ARGUMENT], {
|
|
3181
|
+
detached: true,
|
|
3182
|
+
stdio: "ignore",
|
|
3183
|
+
windowsHide: true,
|
|
3184
|
+
env
|
|
3185
|
+
});
|
|
3186
|
+
child.on("error", () => void 0);
|
|
3187
|
+
child.unref();
|
|
3188
|
+
} catch {
|
|
3189
|
+
}
|
|
3190
|
+
};
|
|
3191
|
+
var runCliBackgroundStage = async (env = process.env, dependencies = {}) => {
|
|
3192
|
+
const paths = resolveCliUpdatePaths(env);
|
|
3193
|
+
const state = await readState(paths);
|
|
3194
|
+
const current = dependencies.currentVersion ?? agent_cli_release_default.package_version;
|
|
3195
|
+
const rejection = await readCliReleaseRejection(paths.rejectionPath);
|
|
3196
|
+
if (cliAutoUpdateDisabled(env) || !state.latest || compareCliReleaseVersions(state.latest.version, current) <= 0 || rejection?.package_version === state.latest.version || state.active && compareCliReleaseVersions(state.active.package_version, state.latest.version) >= 0) return 0;
|
|
3197
|
+
const outcome = await stageAndSelect(paths, state.latest, {
|
|
3198
|
+
env,
|
|
3199
|
+
executable: dependencies.executable ?? process.execPath,
|
|
3200
|
+
platform: dependencies.platform ?? process.platform,
|
|
3201
|
+
runCommand: dependencies.runCommand ?? defaultRunCliCommand,
|
|
3202
|
+
now: dependencies.now ?? (() => /* @__PURE__ */ new Date())
|
|
3203
|
+
});
|
|
3204
|
+
return outcome.kind === "failed" ? 1 : 0;
|
|
3205
|
+
};
|
|
3206
|
+
var installStopTracker = () => {
|
|
3207
|
+
const stopped = new AbortController();
|
|
3208
|
+
const tracker = {
|
|
3209
|
+
stops: 0,
|
|
3210
|
+
child: null,
|
|
3211
|
+
stopped: stopped.signal,
|
|
3212
|
+
uninstall: () => void 0
|
|
3213
|
+
};
|
|
3214
|
+
const onInterrupt = () => {
|
|
3215
|
+
tracker.stops += 1;
|
|
3216
|
+
stopped.abort();
|
|
3217
|
+
if (process.platform === "win32") return;
|
|
3218
|
+
try {
|
|
3219
|
+
tracker.child?.kill("SIGTERM");
|
|
3220
|
+
} catch {
|
|
3221
|
+
}
|
|
3222
|
+
};
|
|
3223
|
+
const forward = (signal) => () => {
|
|
3224
|
+
tracker.stops += 1;
|
|
3225
|
+
stopped.abort();
|
|
3226
|
+
try {
|
|
3227
|
+
tracker.child?.kill(signal);
|
|
3228
|
+
} catch {
|
|
3229
|
+
}
|
|
3230
|
+
};
|
|
3231
|
+
const forwarded = ["SIGTERM", "SIGHUP"].map((signal) => [signal, forward(signal)]);
|
|
3232
|
+
process.on("SIGINT", onInterrupt);
|
|
3233
|
+
for (const [signal, handler] of forwarded) process.on(signal, handler);
|
|
3234
|
+
tracker.uninstall = () => {
|
|
3235
|
+
process.removeListener("SIGINT", onInterrupt);
|
|
3236
|
+
for (const [signal, handler] of forwarded) process.removeListener(signal, handler);
|
|
3237
|
+
};
|
|
3238
|
+
return tracker;
|
|
3239
|
+
};
|
|
3240
|
+
var runReleaseProcess = async (executable, script, argv, env, readyTimeoutMs, onReady, supervisorStops) => await new Promise((resolvePromise) => {
|
|
3241
|
+
const child = spawn2(executable, [script, ...argv], {
|
|
3242
|
+
stdio: ["inherit", "inherit", "inherit", "ipc"],
|
|
3243
|
+
env: { ...env, [CHILD_ENV]: "1", [READY_PROTOCOL_ENV]: "1" }
|
|
3244
|
+
});
|
|
3245
|
+
let ready = false;
|
|
3246
|
+
let timedOut = false;
|
|
3247
|
+
const stops = supervisorStops ?? installStopTracker();
|
|
3248
|
+
const stopsBefore = supervisorStops ? supervisorStops.stops : 0;
|
|
3249
|
+
stops.child = child;
|
|
3250
|
+
const timer = setTimeout(() => {
|
|
3251
|
+
if (ready) return;
|
|
3252
|
+
timedOut = true;
|
|
3253
|
+
try {
|
|
3254
|
+
child.kill("SIGKILL");
|
|
3255
|
+
} catch {
|
|
3256
|
+
}
|
|
3257
|
+
}, readyTimeoutMs);
|
|
3258
|
+
child.on("message", (message) => {
|
|
3259
|
+
if (message && typeof message === "object" && message.type === READY_MESSAGE_TYPE) {
|
|
3260
|
+
if (ready) return;
|
|
3261
|
+
ready = true;
|
|
3262
|
+
clearTimeout(timer);
|
|
3263
|
+
onReady();
|
|
3264
|
+
}
|
|
3265
|
+
});
|
|
3266
|
+
const finish = (result) => {
|
|
3267
|
+
clearTimeout(timer);
|
|
3268
|
+
stops.child = null;
|
|
3269
|
+
if (!supervisorStops) stops.uninstall();
|
|
3270
|
+
resolvePromise(result);
|
|
3271
|
+
};
|
|
3272
|
+
child.once("error", () => finish({ kind: "not_ready", reason: "startup_exited_before_ready", exitCode: 1 }));
|
|
3273
|
+
child.once("exit", (code, signal) => {
|
|
3274
|
+
if (ready) {
|
|
3275
|
+
finish({ kind: "exited", exitCode: code ?? 1, signal });
|
|
3276
|
+
return;
|
|
3277
|
+
}
|
|
3278
|
+
if (stops.stops > stopsBefore || signal !== null && !timedOut) {
|
|
3279
|
+
finish({ kind: "interrupted", exitCode: code ?? 1, signal });
|
|
3280
|
+
return;
|
|
3281
|
+
}
|
|
3282
|
+
finish({
|
|
3283
|
+
kind: "not_ready",
|
|
3284
|
+
reason: timedOut ? "startup_ready_timeout" : "startup_exited_before_ready",
|
|
3285
|
+
exitCode: code ?? 1
|
|
3286
|
+
});
|
|
3287
|
+
});
|
|
3288
|
+
});
|
|
3289
|
+
var reportCliEntryReady = async () => {
|
|
3290
|
+
if (process.env[READY_PROTOCOL_ENV] !== "1" || typeof process.send !== "function") return;
|
|
3291
|
+
delete process.env[READY_PROTOCOL_ENV];
|
|
3292
|
+
await new Promise((resolvePromise) => {
|
|
3293
|
+
try {
|
|
3294
|
+
process.send({ type: READY_MESSAGE_TYPE, package_version: agent_cli_release_default.package_version }, void 0, {}, () => {
|
|
3295
|
+
resolvePromise();
|
|
3296
|
+
});
|
|
3297
|
+
} catch {
|
|
3298
|
+
resolvePromise();
|
|
3299
|
+
}
|
|
3300
|
+
});
|
|
3301
|
+
try {
|
|
3302
|
+
process.disconnect?.();
|
|
3303
|
+
} catch {
|
|
3304
|
+
}
|
|
3305
|
+
};
|
|
3306
|
+
var markConfirmed = async (paths, selection, now) => {
|
|
3307
|
+
try {
|
|
3308
|
+
const state = await readState(paths);
|
|
3309
|
+
if (state.active?.package_version !== selection.package_version || state.active.confirmed_at) return;
|
|
3310
|
+
await writeState(paths, { ...state, active: { ...state.active, confirmed_at: now().toISOString() } });
|
|
3311
|
+
await appendCliUpdateLog(paths, "cli_release_confirmed", { package_version: selection.package_version });
|
|
3312
|
+
} catch {
|
|
3313
|
+
}
|
|
3314
|
+
};
|
|
3315
|
+
var ROLLBACK_SKIPPED = /* @__PURE__ */ Symbol("rollback-skipped");
|
|
3316
|
+
var rollBack = async (paths, selection, reason, now, env) => {
|
|
3317
|
+
await ensureUpdaterPrivateDirectory(paths.root, paths.root).catch(() => void 0);
|
|
3318
|
+
const lock = await acquireInferenceHostProcessLock(paths.lockPath).catch(() => null);
|
|
3319
|
+
try {
|
|
3320
|
+
const current = await readState(paths);
|
|
3321
|
+
if (current.active?.package_version === selection.package_version && current.active.confirmed_at) {
|
|
3322
|
+
await appendCliUpdateLog(paths, "cli_release_rollback_skipped", {
|
|
3323
|
+
package_version: selection.package_version,
|
|
3324
|
+
reason
|
|
3325
|
+
});
|
|
3326
|
+
return ROLLBACK_SKIPPED;
|
|
3327
|
+
}
|
|
3328
|
+
await writeCliReleaseRejection(paths.rejectionPath, {
|
|
3329
|
+
schema_version: "vtx_inference_service_release_rejection_v1",
|
|
3330
|
+
package_version: selection.package_version,
|
|
3331
|
+
rejected_at: now().toISOString(),
|
|
3332
|
+
reason
|
|
3333
|
+
}, paths.root).catch(() => void 0);
|
|
3334
|
+
const state = await readState(paths);
|
|
3335
|
+
const fallback = state.active?.package_version === selection.package_version ? state.previous : state.active;
|
|
3336
|
+
await writeState(paths, { ...state, active: fallback, previous: null }).catch(() => void 0);
|
|
3337
|
+
if (lock) await prune(paths, [fallback], env);
|
|
3338
|
+
return await finishRollBack(paths, selection, reason, fallback);
|
|
3339
|
+
} finally {
|
|
3340
|
+
await lock?.release().catch(() => void 0);
|
|
3341
|
+
}
|
|
3342
|
+
};
|
|
3343
|
+
var finishRollBack = async (paths, selection, reason, fallback) => {
|
|
3344
|
+
await appendCliUpdateLog(paths, "cli_release_rolled_back", {
|
|
3345
|
+
package_version: selection.package_version,
|
|
3346
|
+
restored_version: fallback?.package_version ?? null,
|
|
3347
|
+
reason
|
|
3348
|
+
});
|
|
3349
|
+
return fallback?.package_version ?? null;
|
|
3350
|
+
};
|
|
3351
|
+
var INTERRUPTED_EXIT_CODE = 130;
|
|
3352
|
+
var superviseRuntime = async (argv, dependencies, env, launcher) => {
|
|
3353
|
+
const stderr = dependencies.stderr ?? process.stderr;
|
|
3354
|
+
const paths = resolveCliUpdatePaths(env);
|
|
3355
|
+
const now = dependencies.now ?? (() => /* @__PURE__ */ new Date());
|
|
3356
|
+
const current = dependencies.currentVersion ?? agent_cli_release_default.package_version;
|
|
3357
|
+
const supervisorEnv = {
|
|
3358
|
+
...env,
|
|
3359
|
+
[RUNTIME_RESUME_FILE_ENV]: join3(paths.root, "runtime-resume", `${process.pid}-${randomUUID()}.json`)
|
|
3360
|
+
};
|
|
3361
|
+
const stops = installStopTracker();
|
|
3362
|
+
const endAfterStop = async (exitCode) => {
|
|
3363
|
+
if (!await readRuntimeResume(supervisorEnv) || !dependencies.stopRuntimeBetweenChildren) return exitCode;
|
|
3364
|
+
const stopExitCode = await dependencies.stopRuntimeBetweenChildren(supervisorEnv);
|
|
3365
|
+
return stopExitCode === 0 ? exitCode : stopExitCode;
|
|
3366
|
+
};
|
|
3367
|
+
let resuming = false;
|
|
3368
|
+
let resumeRecord = null;
|
|
3369
|
+
let failedStarts = 0;
|
|
3370
|
+
try {
|
|
3371
|
+
for (; ; ) {
|
|
3372
|
+
if (stops.stops > 0) return resuming ? await endAfterStop(INTERRUPTED_EXIT_CODE) : INTERRUPTED_EXIT_CODE;
|
|
3373
|
+
const plan = await planCliEntryUpdate("vtx", argv, env, launcher, { ...dependencies, signal: stops.stopped });
|
|
3374
|
+
if (stops.stops > 0) return resuming ? await endAfterStop(INTERRUPTED_EXIT_CODE) : INTERRUPTED_EXIT_CODE;
|
|
3375
|
+
if (plan.kind === "refuse") {
|
|
3376
|
+
stderr.write(`${plan.message}
|
|
3377
|
+
`);
|
|
3378
|
+
return 1;
|
|
3379
|
+
}
|
|
3380
|
+
if (plan.kind === "run" && plan.warning) stderr.write(`${plan.warning}
|
|
3381
|
+
`);
|
|
3382
|
+
const childScript = plan.kind === "reexec" ? await releaseEntryScript(plan.selection, "vtx").catch(() => null) : launcher;
|
|
3383
|
+
const childEnv = {
|
|
3384
|
+
...supervisorEnv,
|
|
3385
|
+
[RUNTIME_SUPERVISED_ENV]: "1",
|
|
3386
|
+
[RUNTIME_SUPERVISOR_PID_ENV]: String(process.pid)
|
|
3387
|
+
};
|
|
3388
|
+
if (resuming) childEnv[RUNTIME_RESUME_ENV] = "1";
|
|
3389
|
+
else delete childEnv[RUNTIME_RESUME_ENV];
|
|
3390
|
+
const childArgv = resuming && resumeRecord && !argv.includes("--profile") ? [...argv, "--profile", String(resumeRecord.profileId)] : argv;
|
|
3391
|
+
let confirmation = null;
|
|
3392
|
+
if (stops.stops > 0) return resuming ? await endAfterStop(INTERRUPTED_EXIT_CODE) : INTERRUPTED_EXIT_CODE;
|
|
3393
|
+
const result = childScript === null ? { kind: "not_ready", reason: "startup_exited_before_ready", exitCode: 1 } : await (dependencies.runRelease ?? runReleaseProcess)(
|
|
3394
|
+
dependencies.executable ?? process.execPath,
|
|
3395
|
+
childScript,
|
|
3396
|
+
childArgv,
|
|
3397
|
+
childEnv,
|
|
3398
|
+
dependencies.readyTimeoutMs ?? runtime_policy_generated_default.agent_cli.startup_ready_timeout_ms,
|
|
3399
|
+
() => {
|
|
3400
|
+
if (plan.kind === "reexec" && !plan.selection.confirmed_at) {
|
|
3401
|
+
confirmation = markConfirmed(paths, plan.selection, now);
|
|
3402
|
+
}
|
|
3403
|
+
},
|
|
3404
|
+
stops
|
|
3405
|
+
);
|
|
3406
|
+
await confirmation;
|
|
3407
|
+
if (result.kind === "exited" && result.signal === null && result.exitCode === RUNTIME_RESTART_FOR_UPDATE_EXIT_CODE) {
|
|
3408
|
+
resumeRecord = await readRuntimeResume(supervisorEnv);
|
|
3409
|
+
if (!resumeRecord) {
|
|
3410
|
+
stderr.write("VTX CLI: the runtime ended for an update without a resume record; it was not restarted.\n");
|
|
3411
|
+
return 1;
|
|
3412
|
+
}
|
|
3413
|
+
resuming = true;
|
|
3414
|
+
failedStarts = 0;
|
|
3415
|
+
continue;
|
|
3416
|
+
}
|
|
3417
|
+
if (result.kind === "exited" || result.kind === "interrupted") {
|
|
3418
|
+
const childExitCode = result.signal ? 128 + (osConstants.signals[result.signal] ?? 0) : result.exitCode;
|
|
3419
|
+
return stops.stops > 0 ? await endAfterStop(childExitCode) : childExitCode;
|
|
3420
|
+
}
|
|
3421
|
+
if (plan.kind !== "reexec" || plan.selection.confirmed_at || failedStarts >= 2) {
|
|
3422
|
+
return result.exitCode;
|
|
3423
|
+
}
|
|
3424
|
+
failedStarts += 1;
|
|
3425
|
+
const restored = await rollBack(paths, plan.selection, result.reason, now, env);
|
|
3426
|
+
if (restored !== ROLLBACK_SKIPPED) {
|
|
3427
|
+
stderr.write(
|
|
3428
|
+
`VTX CLI ${plan.selection.package_version} failed to start (${result.reason}); rolled back to ${restored ?? current}.
|
|
3429
|
+
`
|
|
3430
|
+
);
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
3433
|
+
} finally {
|
|
3434
|
+
stops.uninstall();
|
|
3435
|
+
await clearRuntimeResume(supervisorEnv).catch(() => void 0);
|
|
3436
|
+
}
|
|
3437
|
+
};
|
|
3438
|
+
var releaseEntryScript = async (selection, entry) => {
|
|
3439
|
+
if (entry === "vtx") return selection.script;
|
|
3440
|
+
const packageRoot = dirname3(dirname3(selection.script));
|
|
3441
|
+
const packageJson = JSON.parse(await readFile3(join3(packageRoot, "package.json"), "utf8"));
|
|
3442
|
+
const binPath = packageJson.bin?.[entry];
|
|
3443
|
+
if (typeof binPath !== "string") throw new Error("release_entry_missing");
|
|
3444
|
+
const script = resolve2(packageRoot, binPath);
|
|
3445
|
+
if (!isPathWithin(packageRoot, script)) throw new Error("release_entry_missing");
|
|
3446
|
+
await access(script);
|
|
3447
|
+
return script;
|
|
3448
|
+
};
|
|
3449
|
+
var runCliEntry = async (entry, argv, runHere, dependencies = {}, env = process.env, script = process.argv[1] ?? "") => {
|
|
3450
|
+
const stderr = dependencies.stderr ?? process.stderr;
|
|
3451
|
+
if (argv[0] === CLI_BACKGROUND_STAGE_ARGUMENT) {
|
|
3452
|
+
return await runCliBackgroundStage(env, dependencies).catch(() => 1);
|
|
3453
|
+
}
|
|
3454
|
+
if (entry === "vtx" && isSupervisableRuntimeCommand(argv) && !isSkippedInvocation(argv, env) && !cliAutoUpdateDisabled(env) && script && await classifyCliInstall(resolve2(script), dependencies.platform ?? process.platform, env) === "global") {
|
|
3455
|
+
return await superviseRuntime(argv, dependencies, env, resolve2(script));
|
|
3456
|
+
}
|
|
3457
|
+
const paths = resolveCliUpdatePaths(env);
|
|
3458
|
+
const now = dependencies.now ?? (() => /* @__PURE__ */ new Date());
|
|
3459
|
+
const current = dependencies.currentVersion ?? agent_cli_release_default.package_version;
|
|
3460
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
3461
|
+
const plan = await planCliEntryUpdate(entry, argv, env, script, dependencies);
|
|
3462
|
+
if (plan.kind === "refuse") {
|
|
3463
|
+
stderr.write(`${plan.message}
|
|
3464
|
+
`);
|
|
3465
|
+
return 1;
|
|
3466
|
+
}
|
|
3467
|
+
if (plan.kind === "run") {
|
|
3468
|
+
if (plan.warning) stderr.write(`${plan.warning}
|
|
3469
|
+
`);
|
|
3470
|
+
return await runHere();
|
|
3471
|
+
}
|
|
3472
|
+
let confirmation = null;
|
|
3473
|
+
const entryScript = await releaseEntryScript(plan.selection, entry).catch(() => null);
|
|
3474
|
+
const result = entryScript === null ? { kind: "not_ready", reason: "startup_exited_before_ready", exitCode: 1 } : await (dependencies.runRelease ?? runReleaseProcess)(
|
|
3475
|
+
dependencies.executable ?? process.execPath,
|
|
3476
|
+
entryScript,
|
|
3477
|
+
argv,
|
|
3478
|
+
env,
|
|
3479
|
+
dependencies.readyTimeoutMs ?? runtime_policy_generated_default.agent_cli.startup_ready_timeout_ms,
|
|
3480
|
+
() => {
|
|
3481
|
+
if (!plan.selection.confirmed_at) confirmation = markConfirmed(paths, plan.selection, now);
|
|
3482
|
+
}
|
|
3483
|
+
);
|
|
3484
|
+
await confirmation;
|
|
3485
|
+
if (result.kind === "exited" || result.kind === "interrupted") {
|
|
3486
|
+
if (result.signal) {
|
|
3487
|
+
process.kill(process.pid, result.signal);
|
|
3488
|
+
}
|
|
3489
|
+
return result.exitCode;
|
|
3490
|
+
}
|
|
3491
|
+
if (plan.selection.confirmed_at) {
|
|
3492
|
+
return result.exitCode;
|
|
3493
|
+
}
|
|
3494
|
+
const restored = await rollBack(paths, plan.selection, result.reason, now, env);
|
|
3495
|
+
if (restored === ROLLBACK_SKIPPED) continue;
|
|
3496
|
+
stderr.write(
|
|
3497
|
+
`VTX CLI ${plan.selection.package_version} failed to start (${result.reason}); rolled back to ${restored ?? current}.
|
|
3498
|
+
`
|
|
3499
|
+
);
|
|
3500
|
+
}
|
|
3501
|
+
return await runHere();
|
|
3502
|
+
};
|
|
3503
|
+
var RUNTIME_RESTART_FOR_UPDATE_EXIT_CODE = 75;
|
|
3504
|
+
var RUNTIME_SUPERVISED_ENV = "VTX_CLI_RUNTIME_SUPERVISED";
|
|
3505
|
+
var RUNTIME_RESUME_ENV = "VTX_CLI_RUNTIME_RESUME";
|
|
3506
|
+
var RUNTIME_SUPERVISOR_PID_ENV = "VTX_CLI_RUNTIME_SUPERVISOR_PID";
|
|
3507
|
+
var RUNTIME_RESUME_FILE_ENV = "VTX_CLI_RUNTIME_RESUME_FILE";
|
|
3508
|
+
var runtimeResumeFile = (env) => String(env[RUNTIME_RESUME_FILE_ENV] ?? "").trim() || null;
|
|
3509
|
+
var parseRuntimeResumeRecord = (value) => {
|
|
3510
|
+
const record = value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
3511
|
+
const profileId = Number(record?.profileId);
|
|
3512
|
+
const text = (field) => typeof record?.[field] === "string" ? String(record[field]).trim() : "";
|
|
3513
|
+
if (!Number.isSafeInteger(profileId) || profileId <= 0) return null;
|
|
3514
|
+
if (!text("runtimeSessionId") || !text("deviceId") || !text("handoffKey")) return null;
|
|
3515
|
+
return {
|
|
3516
|
+
profileId,
|
|
3517
|
+
runtimeSessionId: text("runtimeSessionId"),
|
|
3518
|
+
deviceId: text("deviceId"),
|
|
3519
|
+
leaseToken: text("leaseToken") || null,
|
|
3520
|
+
handoffKey: text("handoffKey")
|
|
3521
|
+
};
|
|
3522
|
+
};
|
|
3523
|
+
var readRuntimeResume = async (env = process.env) => {
|
|
3524
|
+
const path = runtimeResumeFile(env);
|
|
3525
|
+
if (!path) return null;
|
|
3526
|
+
try {
|
|
3527
|
+
return parseRuntimeResumeRecord(JSON.parse(await readFile3(path, "utf8")));
|
|
3528
|
+
} catch {
|
|
3529
|
+
return null;
|
|
3530
|
+
}
|
|
3531
|
+
};
|
|
3532
|
+
var clearRuntimeResume = async (env = process.env) => {
|
|
3533
|
+
const path = runtimeResumeFile(env);
|
|
3534
|
+
if (path) await rm3(path, { force: true });
|
|
3535
|
+
};
|
|
3536
|
+
var isSupervisableRuntimeCommand = (argv) => {
|
|
3537
|
+
if (argv.includes("--once") || isInferenceHostCliInvocation(argv)) return false;
|
|
3538
|
+
const words = argv.filter((item) => !item.startsWith("--"));
|
|
3539
|
+
const at = words.indexOf("runtime");
|
|
3540
|
+
if (at < 0) return false;
|
|
3541
|
+
return words[at + 1] === "run" || words[at + 1] === "start" && argv.includes("--follow");
|
|
3542
|
+
};
|
|
3543
|
+
|
|
3544
|
+
// bin/vtx-mcp.ts
|
|
3545
|
+
process.exitCode = await runCliEntry("vtx-mcp", process.argv.slice(2), async () => {
|
|
3546
|
+
const { runVtxMcpStdioServer: runVtxMcpStdioServer2 } = await Promise.resolve().then(() => (init_stdio(), stdio_exports));
|
|
3547
|
+
await reportCliEntryReady();
|
|
3548
|
+
await runVtxMcpStdioServer2();
|
|
3549
|
+
return typeof process.exitCode === "number" ? process.exitCode : 0;
|
|
3550
|
+
});
|
|
3551
|
+
/*! Bundled license information:
|
|
3552
|
+
|
|
3553
|
+
smol-toml/dist/date.js:
|
|
3554
|
+
smol-toml/dist/error.js:
|
|
3555
|
+
smol-toml/dist/util.js:
|
|
3556
|
+
smol-toml/dist/primitive.js:
|
|
3557
|
+
smol-toml/dist/extract.js:
|
|
3558
|
+
smol-toml/dist/struct.js:
|
|
3559
|
+
smol-toml/dist/parse.js:
|
|
3560
|
+
smol-toml/dist/stringify.js:
|
|
3561
|
+
smol-toml/dist/index.js:
|
|
3562
|
+
(*!
|
|
3563
|
+
* Copyright (c) Squirrel Chat et al., All rights reserved.
|
|
3564
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
3565
|
+
*
|
|
3566
|
+
* Redistribution and use in source and binary forms, with or without
|
|
3567
|
+
* modification, are permitted provided that the following conditions are met:
|
|
3568
|
+
*
|
|
3569
|
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
3570
|
+
* list of conditions and the following disclaimer.
|
|
3571
|
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
3572
|
+
* this list of conditions and the following disclaimer in the
|
|
3573
|
+
* documentation and/or other materials provided with the distribution.
|
|
3574
|
+
* 3. Neither the name of the copyright holder nor the names of its contributors
|
|
3575
|
+
* may be used to endorse or promote products derived from this software without
|
|
3576
|
+
* specific prior written permission.
|
|
3577
|
+
*
|
|
3578
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
3579
|
+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
3580
|
+
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
3581
|
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
3582
|
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
3583
|
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
3584
|
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
3585
|
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
3586
|
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
3587
|
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
3588
|
+
*)
|
|
3589
|
+
*/
|