@vtxmacro/cli 0.1.0 → 2026.6.22

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 CHANGED
@@ -22,8 +22,14 @@ vtx auth login --scopes read,bot:control
22
22
  vtx --json auth whoami
23
23
  vtx --json bots status
24
24
  vtx --profile <profile-id> runtime run --follow
25
+ vtx auth login --scopes read,insights:connect
26
+ vtx --profile <profile-id> insights connect
25
27
  ```
26
28
 
29
+ The Insights connector talks only to a loopback OpenAI-compatible model server.
30
+ It defaults to LM Studio at `http://127.0.0.1:1234/v1` and reads its optional
31
+ API key from `LM_STUDIO_API_KEY`. The key and local URL never leave the device.
32
+
27
33
  ## MCP
28
34
 
29
35
  Configure your MCP-compatible agent app to start:
package/bin/vtx-mcp.js CHANGED
@@ -256,6 +256,33 @@ var VtxAgentClient = class _VtxAgentClient {
256
256
  getLlmConfig(profileId) {
257
257
  return this.request("/llm/config", { profileId });
258
258
  }
259
+ registerInsightConnector(profileId, payload) {
260
+ return this.request("/insights/connectors/register", {
261
+ method: "POST",
262
+ profileId,
263
+ body: payload
264
+ });
265
+ }
266
+ claimInsightConnectorJob(profileId, connectorId, deviceId) {
267
+ return this.request(
268
+ `/insights/connectors/${encodeURIComponent(connectorId)}/jobs:claim`,
269
+ { method: "POST", profileId, body: { device_id: deviceId } }
270
+ );
271
+ }
272
+ completeInsightConnectorJob(profileId, connectorId, jobId, deviceId, payload) {
273
+ const params = new URLSearchParams({ device_id: deviceId });
274
+ return this.request(
275
+ `/insights/connectors/${encodeURIComponent(connectorId)}/jobs/${encodeURIComponent(jobId)}:complete?${params}`,
276
+ { method: "POST", profileId, body: payload }
277
+ );
278
+ }
279
+ heartbeatInsightConnectorJob(profileId, connectorId, jobId, deviceId, claimToken) {
280
+ const params = new URLSearchParams({ device_id: deviceId });
281
+ return this.request(
282
+ `/insights/connectors/${encodeURIComponent(connectorId)}/jobs/${encodeURIComponent(jobId)}:heartbeat?${params}`,
283
+ { method: "POST", profileId, body: { claim_token: claimToken } }
284
+ );
285
+ }
259
286
  heartbeatRuntime(profileId, payload, leaseToken) {
260
287
  return this.request("/trading/ai/runtime/heartbeat", {
261
288
  method: "POST",
@@ -968,7 +995,7 @@ async function handleMcpJsonRpcRequest(request, clientFactory = createDefaultMcp
968
995
  },
969
996
  serverInfo: {
970
997
  name: "vtx-macro",
971
- version: "0.1.0"
998
+ version: "2026.6.22"
972
999
  }
973
1000
  });
974
1001
  }
package/bin/vtx.js CHANGED
@@ -1632,10 +1632,13 @@ var init_hyperliquid_account_state_adapter = __esm({
1632
1632
  });
1633
1633
 
1634
1634
  // lib/byokProviderRegistry.ts
1635
+ function normalizeProviderAlias(value) {
1636
+ return String(value ?? "").trim().toLowerCase().replace(/[._-]+/g, " ").replace(/\s+/g, " ");
1637
+ }
1635
1638
  function byokProvidersForClientRuntime() {
1636
1639
  return BYOK_PROVIDERS.filter((provider) => provider.clientRuntimeSupported);
1637
1640
  }
1638
- var BYOK_UI_GROUP, BYOK_PROVIDERS;
1641
+ var BYOK_UI_GROUP, BYOK_PROVIDERS, BYOK_PROVIDER_KEY_BY_ALIAS;
1639
1642
  var init_byokProviderRegistry = __esm({
1640
1643
  "lib/byokProviderRegistry.ts"() {
1641
1644
  "use strict";
@@ -1658,6 +1661,15 @@ var init_byokProviderRegistry = __esm({
1658
1661
  { key: "venice", runtimeProvider: "venice", displayName: "Venice AI", apiKeyField: "venice_api_key", envKey: "VENICE_API_KEY", baseUrl: "https://api.venice.ai/api/v1", modelsUrl: "https://api.venice.ai/api/v1/models", requestContractStrategy: "venice", clientRuntimeSupported: true, uiGroup: BYOK_UI_GROUP, providerKind: "hosted", displayOrder: 150, aliases: ["venice.ai"] },
1659
1662
  { key: "xai", runtimeProvider: "grok", displayName: "xAI", apiKeyField: "grok_api_key", envKey: "XAI_API_KEY", baseUrl: "https://api.x.ai/v1", modelsUrl: "https://api.x.ai/v1/models", requestContractStrategy: "native", clientRuntimeSupported: true, uiGroup: BYOK_UI_GROUP, providerKind: "direct", displayOrder: 160, aliases: ["x", "x.ai", "grok"] }
1660
1663
  ];
1664
+ BYOK_PROVIDER_KEY_BY_ALIAS = /* @__PURE__ */ new Map();
1665
+ for (const provider of BYOK_PROVIDERS) {
1666
+ for (const alias of [provider.key, provider.displayName, ...provider.aliases]) {
1667
+ const normalizedAlias = normalizeProviderAlias(alias);
1668
+ if (!BYOK_PROVIDER_KEY_BY_ALIAS.has(normalizedAlias)) {
1669
+ BYOK_PROVIDER_KEY_BY_ALIAS.set(normalizedAlias, provider.key);
1670
+ }
1671
+ }
1672
+ }
1661
1673
  }
1662
1674
  });
1663
1675
 
@@ -1978,22 +1990,22 @@ var init_api = __esm({
1978
1990
  return process.env.API_URL_SERVER || process.env.NEXT_PUBLIC_API_URL || "http://api:8000";
1979
1991
  }
1980
1992
  if (process.env.NODE_ENV === "development") {
1981
- const hostname = window.location.hostname;
1982
- const isLocalNetworkIP = hostname.startsWith("192.168.") || hostname.startsWith("10.") || hostname.startsWith("172.") && parseInt(hostname.split(".")[1]) >= 16 && parseInt(hostname.split(".")[1]) <= 31;
1993
+ const hostname2 = window.location.hostname;
1994
+ const isLocalNetworkIP = hostname2.startsWith("192.168.") || hostname2.startsWith("10.") || hostname2.startsWith("172.") && parseInt(hostname2.split(".")[1]) >= 16 && parseInt(hostname2.split(".")[1]) <= 31;
1983
1995
  if (isLocalNetworkIP) {
1984
- return `http://${hostname}:8000`;
1996
+ return `http://${hostname2}:8000`;
1985
1997
  }
1986
- if (hostname === "localhost" || hostname === "127.0.0.1") {
1987
- return `http://${hostname}:8000`;
1998
+ if (hostname2 === "localhost" || hostname2 === "127.0.0.1") {
1999
+ return `http://${hostname2}:8000`;
1988
2000
  }
1989
2001
  }
1990
2002
  if (process.env.NEXT_PUBLIC_API_URL) {
1991
2003
  return process.env.NEXT_PUBLIC_API_URL;
1992
2004
  }
1993
2005
  try {
1994
- const hostname = window.location.hostname;
2006
+ const hostname2 = window.location.hostname;
1995
2007
  const protocol = window.location.protocol;
1996
- const baseHost = hostname.startsWith("www.") ? hostname.slice(4) : hostname;
2008
+ const baseHost = hostname2.startsWith("www.") ? hostname2.slice(4) : hostname2;
1997
2009
  const apiHost = baseHost.startsWith("api.") ? baseHost : `api.${baseHost}`;
1998
2010
  return `${protocol}//${apiHost}`;
1999
2011
  } catch {
@@ -11557,7 +11569,7 @@ var init_hyperliquid_signer = __esm({
11557
11569
  });
11558
11570
 
11559
11571
  // lib/runtime/hyperliquid-client.ts
11560
- var HyperliquidExchangeRejectionError, normalizeSymbol, HYPERLIQUID_NONCE_STORAGE_KEY, HYPERLIQUID_NONCE_LOCK_NAME, lastGeneratedNonce, readBrowserStoredHyperliquidNonce, writeBrowserStoredHyperliquidNonce, browserNonceCoordinator, allocateHyperliquidNonce, generateHyperliquidNonce, roundToSigFigs, normalizeDecimalString, toPlainDecimalString, roundDirectionalDecimalString, formatHyperliquidPerpPriceWire, countSignificantDigits, countDecimalPlaces, validateHyperliquidPerpPriceWire, validateHyperliquidSizeWire, formatSizeWire, inferApiUrl, inferIsTestnet, postHyperliquidInfo, toFiniteNumber, firstFiniteNumber2, parseFundingUsdcFromInfoPosition2, isExchangeIsolatedOnly, computeHip3AssetId, getPerpDexIndexMap, buildAssetInfosFromMetaResponse, requireWalletAddress2, assertPositiveNumber, normalizeOrderSide, resolveAssets, findResolvedAsset, normalizeExecutionAssetMetadata, resolveHyperliquidAsset, fetchBrowserAvailableAssets, fetchBrowserAllMids, fetchBrowserSymbolMidPrice, fetchBrowserAccountState2, fetchBrowserPosition, buildHyperliquidOrderWire, signHyperliquidPayload, buildHyperliquidUpdateLeveragePayload, buildHyperliquidMarketOrderPayload, extractHyperliquidExchangeError, buildHyperliquidOrderDiagnostics, submitHyperliquidExchangePayload;
11572
+ var HyperliquidExchangeRejectionError, normalizeSymbol, HYPERLIQUID_NONCE_STORAGE_KEY, HYPERLIQUID_NONCE_LOCK_NAME, lastGeneratedNonce, readBrowserStoredHyperliquidNonce, writeBrowserStoredHyperliquidNonce, browserNonceCoordinator, allocateHyperliquidNonce, generateHyperliquidNonce, roundToSigFigs, normalizeDecimalString, toPlainDecimalString, roundDirectionalDecimalString, formatHyperliquidPerpPriceWire, formatHyperliquidTriggerPriceWire, countSignificantDigits, countDecimalPlaces, validateHyperliquidPerpPriceWire, validateHyperliquidSizeWire, formatSizeWire, inferApiUrl, inferIsTestnet, postHyperliquidInfo, toFiniteNumber, firstFiniteNumber2, parseFundingUsdcFromInfoPosition2, isExchangeIsolatedOnly, computeHip3AssetId, getPerpDexIndexMap, buildAssetInfosFromMetaResponse, requireWalletAddress2, assertPositiveNumber, normalizeOrderSide, resolveAssets, findResolvedAsset, normalizeExecutionAssetMetadata, resolveHyperliquidAsset, fetchBrowserAvailableAssets, fetchBrowserAllMids, fetchBrowserSymbolMidPrice, fetchBrowserAccountState2, fetchBrowserPosition, buildHyperliquidOrderWire, signHyperliquidPayload, buildHyperliquidUpdateLeveragePayload, buildHyperliquidMarketOrderPayload, buildHyperliquidTriggerOrderPayload, extractHyperliquidExchangeError, buildHyperliquidOrderDiagnostics, submitHyperliquidExchangePayload;
11561
11573
  var init_hyperliquid_client = __esm({
11562
11574
  "lib/runtime/hyperliquid-client.ts"() {
11563
11575
  "use strict";
@@ -11692,6 +11704,14 @@ var init_hyperliquid_client = __esm({
11692
11704
  assertPositiveNumber(decimalRounded, "Hyperliquid order price");
11693
11705
  return normalizeDecimalString(roundedWire);
11694
11706
  };
11707
+ formatHyperliquidTriggerPriceWire = (value, sizeDecimals) => {
11708
+ const sigFigRounded = roundToSigFigs(value, 5);
11709
+ const priceDecimals = Math.max(0, 6 - sizeDecimals);
11710
+ const scale = 10 ** priceDecimals;
11711
+ const decimalRounded = Math.round(sigFigRounded * scale) / scale;
11712
+ assertPositiveNumber(decimalRounded, "Hyperliquid trigger price");
11713
+ return normalizeDecimalString(decimalRounded.toFixed(priceDecimals));
11714
+ };
11695
11715
  countSignificantDigits = (value) => {
11696
11716
  const normalized = String(value || "").replace("-", "");
11697
11717
  const [integerPartRaw, fractionPartRaw = ""] = normalized.split(".");
@@ -11818,7 +11838,7 @@ var init_hyperliquid_client = __esm({
11818
11838
  }
11819
11839
  return null;
11820
11840
  };
11821
- isExchangeIsolatedOnly = (asset) => asset.only_isolated === true || String(asset.margin_mode || "").trim().toLowerCase() === "nocross";
11841
+ isExchangeIsolatedOnly = (asset) => asset.only_isolated === true || ["nocross", "strictisolated"].includes(String(asset.margin_mode || "").trim().toLowerCase());
11822
11842
  computeHip3AssetId = (perpDexIndex, indexInMeta) => 1e5 + Math.trunc(perpDexIndex) * 1e4 + Math.trunc(indexInMeta);
11823
11843
  getPerpDexIndexMap = (payload) => {
11824
11844
  if (!Array.isArray(payload)) {
@@ -12183,6 +12203,44 @@ var init_hyperliquid_client = __esm({
12183
12203
  grouping: "na"
12184
12204
  });
12185
12205
  };
12206
+ buildHyperliquidTriggerOrderPayload = async (input) => {
12207
+ assertPositiveNumber(input.size, "Hyperliquid trigger order size");
12208
+ assertPositiveNumber(input.triggerPrice, "Hyperliquid trigger price");
12209
+ const { asset_index: assetIndex, sz_decimals: sizeDecimals } = await resolveHyperliquidAsset(input);
12210
+ const side = normalizeOrderSide(input.side);
12211
+ const roundedSize = Number(input.size.toFixed(sizeDecimals));
12212
+ assertPositiveNumber(roundedSize, "Hyperliquid trigger order size");
12213
+ const priceWire = formatHyperliquidTriggerPriceWire(input.triggerPrice, sizeDecimals);
12214
+ const sizeWire = formatSizeWire(roundedSize, sizeDecimals);
12215
+ const priceValidation = validateHyperliquidPerpPriceWire(priceWire, sizeDecimals);
12216
+ if (!priceValidation.valid) {
12217
+ throw new Error(`Invalid Hyperliquid trigger price wire: ${priceValidation.reason}.`);
12218
+ }
12219
+ const sizeValidation = validateHyperliquidSizeWire(sizeWire, sizeDecimals);
12220
+ if (!sizeValidation.valid) {
12221
+ throw new Error(`Invalid Hyperliquid trigger size wire: ${sizeValidation.reason}.`);
12222
+ }
12223
+ return signHyperliquidPayload(input, {
12224
+ type: "order",
12225
+ orders: [
12226
+ {
12227
+ a: assetIndex,
12228
+ b: side === "buy",
12229
+ p: priceWire,
12230
+ s: sizeWire,
12231
+ r: input.reduceOnly !== false,
12232
+ t: {
12233
+ trigger: {
12234
+ isMarket: true,
12235
+ triggerPx: priceWire,
12236
+ tpsl: input.isTakeProfit === true ? "tp" : "sl"
12237
+ }
12238
+ }
12239
+ }
12240
+ ],
12241
+ grouping: "na"
12242
+ });
12243
+ };
12186
12244
  extractHyperliquidExchangeError = (payload) => {
12187
12245
  if (!payload || typeof payload !== "object") {
12188
12246
  return "Unexpected response format from Hyperliquid.";
@@ -12426,6 +12484,16 @@ function validateDeviceSecrets(value, options) {
12426
12484
  );
12427
12485
  const hasHyperliquidSigningKey = Boolean(hyperliquidSigningKey);
12428
12486
  const providerKeyCount = Object.keys(providerKeysWithFirstSlots).length;
12487
+ const activeProviderKeyAliases = activeProvider ? (() => {
12488
+ const provider = byokProvidersForClientRuntime().find(
12489
+ (candidate) => candidate.runtimeProvider === activeProvider
12490
+ );
12491
+ return /* @__PURE__ */ new Set([
12492
+ activeProvider,
12493
+ ...provider ? [provider.key, provider.runtimeProvider] : []
12494
+ ]);
12495
+ })() : null;
12496
+ const hasActiveProviderKey = activeProviderKeyAliases ? Object.keys(providerKeysWithFirstSlots).some((provider) => activeProviderKeyAliases.has(provider)) : providerKeyCount > 0;
12429
12497
  const missing = [];
12430
12498
  if (!hasHyperliquidSigningKey) {
12431
12499
  missing.push("hyperliquid signing key");
@@ -12434,10 +12502,10 @@ function validateDeviceSecrets(value, options) {
12434
12502
  if (!hasLocalAiConfig) {
12435
12503
  missing.push("local ai config");
12436
12504
  }
12437
- } else if (providerKeyCount === 0 && !(activeProvider === "openai" && hasOpenAiCompatibleConfig)) {
12505
+ } else if (!hasActiveProviderKey && !(activeProvider === "openai" && hasOpenAiCompatibleConfig)) {
12438
12506
  missing.push("model provider key");
12439
12507
  }
12440
- const hasProviderRequirement = isLocalProvider ? hasLocalAiConfig : activeProvider === "openai" ? providerKeyCount > 0 || hasOpenAiCompatibleConfig : providerKeyCount > 0;
12508
+ const hasProviderRequirement = isLocalProvider ? hasLocalAiConfig : activeProvider === "openai" ? hasActiveProviderKey || hasOpenAiCompatibleConfig : hasActiveProviderKey;
12441
12509
  return {
12442
12510
  normalized: {
12443
12511
  hyperliquidSigningKey,
@@ -12469,6 +12537,7 @@ var init_vault = __esm({
12469
12537
  init_key_preview_contract();
12470
12538
  init_client_runtime_provider_visibility_contract();
12471
12539
  init_client_runtime_secret_rate_limit_contract();
12540
+ init_byokProviderRegistry();
12472
12541
  VAULT_PREFIX = "vtx_vault_";
12473
12542
  VAULT_DB_NAME = "vtx-client-runtime-vault";
12474
12543
  VAULT_DB_VERSION = 1;
@@ -12977,7 +13046,7 @@ var init_vault = __esm({
12977
13046
  });
12978
13047
 
12979
13048
  // lib/runtime/browser-trading.ts
12980
- var resolveProfileId, requirePositiveNumber, throwIfAborted, loadSigningKey, resolveSigningKey, requireWalletAddress3, resolveEstimatedPrice, executeClientLeverageUpdate, executeClientMarketOrder, executeClientClosePosition;
13049
+ var resolveProfileId, requirePositiveNumber, throwIfAborted, loadSigningKey, resolveSigningKey, requireWalletAddress3, resolveEstimatedPrice, executeClientLeverageUpdate, executeClientMarketOrder, executeClientTriggerOrder, executeClientClosePosition;
12981
13050
  var init_browser_trading = __esm({
12982
13051
  "lib/runtime/browser-trading.ts"() {
12983
13052
  "use strict";
@@ -13083,6 +13152,26 @@ var init_browser_trading = __esm({
13083
13152
  signal: input.signal
13084
13153
  });
13085
13154
  };
13155
+ executeClientTriggerOrder = async (input) => {
13156
+ throwIfAborted(input.signal);
13157
+ const signingKey = await resolveSigningKey(input);
13158
+ throwIfAborted(input.signal);
13159
+ const payload = await buildHyperliquidTriggerOrderPayload({
13160
+ config: input.config ?? null,
13161
+ signingKey,
13162
+ symbol: input.symbol,
13163
+ side: input.side,
13164
+ size: input.size,
13165
+ triggerPrice: input.triggerPrice,
13166
+ reduceOnly: input.reduceOnly,
13167
+ isTakeProfit: input.isTakeProfit
13168
+ });
13169
+ return submitHyperliquidExchangePayload({
13170
+ config: input.config ?? null,
13171
+ payload,
13172
+ signal: input.signal
13173
+ });
13174
+ };
13086
13175
  executeClientClosePosition = async (input) => {
13087
13176
  throwIfAborted(input.signal);
13088
13177
  const profileId = resolveProfileId(input.profileId);
@@ -13118,7 +13207,7 @@ var init_browser_trading = __esm({
13118
13207
  });
13119
13208
 
13120
13209
  // lib/runtime/execution-guard-contract.ts
13121
- var unitsFromPosition, signedUnitsFromPosition, classifyExecutionIntent;
13210
+ var unitsFromPosition, signedUnitsFromPosition, classifyExecutionIntent, resolveExecutionTarget;
13122
13211
  var init_execution_guard_contract = __esm({
13123
13212
  "lib/runtime/execution-guard-contract.ts"() {
13124
13213
  "use strict";
@@ -13299,11 +13388,52 @@ var init_execution_guard_contract = __esm({
13299
13388
  targetSignedUnits: -(units - Math.abs(snapshotSignedUnits))
13300
13389
  };
13301
13390
  };
13391
+ resolveExecutionTarget = (classified, liveSignedUnits) => {
13392
+ const liveUnits = Number.isFinite(liveSignedUnits) ? Math.trunc(liveSignedUnits) : 0;
13393
+ const deltaSignedUnits = classified.targetSignedUnits - liveUnits;
13394
+ if (classified.side !== "buy" && classified.side !== "sell") {
13395
+ return {
13396
+ allowed: false,
13397
+ reasonCode: classified.requestedAction === "HOLD" ? "hold_no_action" : "invalid_decision",
13398
+ deltaSignedUnits,
13399
+ closeOpposingPosition: false,
13400
+ orderUnits: 0
13401
+ };
13402
+ }
13403
+ if (deltaSignedUnits === 0) {
13404
+ return {
13405
+ allowed: false,
13406
+ reasonCode: "noop_already_at_target",
13407
+ deltaSignedUnits: 0,
13408
+ closeOpposingPosition: false,
13409
+ orderUnits: 0
13410
+ };
13411
+ }
13412
+ const expectedDeltaSign = classified.side === "buy" ? 1 : -1;
13413
+ const actualDeltaSign = deltaSignedUnits > 0 ? 1 : -1;
13414
+ if (actualDeltaSign !== expectedDeltaSign) {
13415
+ return {
13416
+ allowed: false,
13417
+ reasonCode: "stale_target_conflict",
13418
+ deltaSignedUnits,
13419
+ closeOpposingPosition: false,
13420
+ orderUnits: 0
13421
+ };
13422
+ }
13423
+ const closeOpposingPosition = classified.intent === "flip" && (classified.side === "buy" && liveUnits < 0 || classified.side === "sell" && liveUnits > 0);
13424
+ return {
13425
+ allowed: true,
13426
+ reasonCode: "executed",
13427
+ deltaSignedUnits,
13428
+ closeOpposingPosition,
13429
+ orderUnits: closeOpposingPosition ? Math.abs(classified.targetSignedUnits) : Math.abs(deltaSignedUnits)
13430
+ };
13431
+ };
13302
13432
  }
13303
13433
  });
13304
13434
 
13305
13435
  // lib/runtime/runtime-execution.ts
13306
- var createRuntimeId, _extractOrderId, _toExecutionMetadata, throwIfAborted2, sleep2, roundAssetSize, resolvePositionSize, resolveCurrentPositionSize, buildPositionSnapshot, executeClientRuntimeDecision;
13436
+ var createRuntimeId, _extractOrderId, _toExecutionMetadata, toPositiveNumber, resolveRiskPct, buildTriggerPrice, throwIfAborted2, sleep2, roundAssetSize, resolvePositionSize, resolveCurrentPositionSize, buildPositionSnapshot, executeClientRuntimeDecision;
13307
13437
  var init_runtime_execution = __esm({
13308
13438
  "lib/runtime/runtime-execution.ts"() {
13309
13439
  "use strict";
@@ -13366,6 +13496,25 @@ var init_runtime_execution = __esm({
13366
13496
  }
13367
13497
  return { raw: value ?? null };
13368
13498
  };
13499
+ toPositiveNumber = (value) => {
13500
+ const parsed = Number(value);
13501
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
13502
+ };
13503
+ resolveRiskPct = (mode, percentValue, usdcValue, positionNotional) => {
13504
+ if (mode === "usdc") {
13505
+ const usdc = toPositiveNumber(usdcValue);
13506
+ return positionNotional > 0 ? usdc / positionNotional * 100 : 0;
13507
+ }
13508
+ return toPositiveNumber(percentValue);
13509
+ };
13510
+ buildTriggerPrice = (entryPrice, riskPct, isLong, isTakeProfit) => {
13511
+ if (!Number.isFinite(entryPrice) || entryPrice <= 0 || !Number.isFinite(riskPct) || riskPct <= 0) {
13512
+ return 0;
13513
+ }
13514
+ const multiplier = isTakeProfit ? isLong ? 1 + riskPct / 100 : 1 - riskPct / 100 : isLong ? 1 - riskPct / 100 : 1 + riskPct / 100;
13515
+ const price = entryPrice * multiplier;
13516
+ return Number.isFinite(price) && price > 0 ? price : 0;
13517
+ };
13369
13518
  throwIfAborted2 = (signal) => {
13370
13519
  if (signal?.aborted) {
13371
13520
  throw new DOMException("The operation was aborted.", "AbortError");
@@ -13481,8 +13630,8 @@ var init_runtime_execution = __esm({
13481
13630
  label: "execution_start",
13482
13631
  note: `decision=${input.decision} units=${requestedUnits} symbol=${input.executionContext.symbol}`
13483
13632
  });
13633
+ const decisionSnapshotPositionSize = Number.isFinite(input.executionContext.currentPositionSize) ? Number(input.executionContext.currentPositionSize) : 0;
13484
13634
  if (input.decision === "HOLD" || requestedUnits <= 0) {
13485
- const snapshotPositionSize = Number.isFinite(input.executionContext.currentPositionSize) ? Number(input.executionContext.currentPositionSize) : 0;
13486
13635
  return {
13487
13636
  status: "skipped",
13488
13637
  intent: "hold",
@@ -13490,7 +13639,7 @@ var init_runtime_execution = __esm({
13490
13639
  reduceOnly: false,
13491
13640
  requestedUnits,
13492
13641
  targetSignedUnits: signedUnitsFromPosition(
13493
- snapshotPositionSize,
13642
+ decisionSnapshotPositionSize,
13494
13643
  input.executionContext.lastPrice,
13495
13644
  input.executionContext.unitSizeUsdc
13496
13645
  ),
@@ -13509,14 +13658,25 @@ var init_runtime_execution = __esm({
13509
13658
  } = await resolveCurrentPositionSize(input);
13510
13659
  throwIfAborted2(input.signal);
13511
13660
  const currentPosition = buildPositionSnapshot(input.executionContext.symbol, currentPositionSize);
13661
+ const decisionSnapshotSignedUnits = signedUnitsFromPosition(
13662
+ decisionSnapshotPositionSize,
13663
+ input.executionContext.lastPrice,
13664
+ input.executionContext.unitSizeUsdc
13665
+ );
13666
+ const liveSignedUnits = signedUnitsFromPosition(
13667
+ currentPositionSize,
13668
+ executionPrice,
13669
+ input.executionContext.unitSizeUsdc
13670
+ );
13512
13671
  const classified = classifyExecutionIntent(
13513
13672
  input.decision,
13514
13673
  requestedUnits,
13515
- signedUnitsFromPosition(currentPositionSize, executionPrice, input.executionContext.unitSizeUsdc)
13674
+ decisionSnapshotSignedUnits
13516
13675
  );
13676
+ const targetResolution = resolveExecutionTarget(classified, liveSignedUnits);
13517
13677
  appendRuntimeDebugEvent({
13518
13678
  label: "execution_classified",
13519
- note: `symbol=${input.executionContext.symbol} intent=${classified.intent} side=${classified.side ?? "none"} snapshot_units=${classified.snapshotSignedUnits} target_units=${classified.targetSignedUnits} requested_units=${classified.requestedUnits} reduceOnly=${String(classified.reduceOnly)} execution_snapshot_id=${executionSnapshotId}`
13679
+ note: `symbol=${input.executionContext.symbol} intent=${classified.intent} side=${classified.side ?? "none"} decision_snapshot_units=${classified.snapshotSignedUnits} live_units=${liveSignedUnits} target_units=${classified.targetSignedUnits} delta_units=${targetResolution.deltaSignedUnits} requested_units=${classified.requestedUnits} reduceOnly=${String(classified.reduceOnly)} resolution=${targetResolution.reasonCode} execution_snapshot_id=${executionSnapshotId}`
13520
13680
  });
13521
13681
  if (!classified.side || classified.intent === "hold") {
13522
13682
  return {
@@ -13533,6 +13693,28 @@ var init_runtime_execution = __esm({
13533
13693
  executionSnapshotCapturedAt
13534
13694
  };
13535
13695
  }
13696
+ if (!targetResolution.allowed) {
13697
+ const staleTargetConflict = targetResolution.reasonCode === "stale_target_conflict";
13698
+ appendRuntimeDebugEvent({
13699
+ label: staleTargetConflict ? "execution_stale_target_skipped" : "execution_target_noop",
13700
+ note: `symbol=${input.executionContext.symbol} intent=${classified.intent} decision_snapshot_units=${classified.snapshotSignedUnits} live_units=${liveSignedUnits} target_units=${classified.targetSignedUnits} delta_units=${targetResolution.deltaSignedUnits}`,
13701
+ outcome: "ok"
13702
+ });
13703
+ return {
13704
+ status: "skipped",
13705
+ intent: classified.intent,
13706
+ side: classified.side,
13707
+ reduceOnly: classified.reduceOnly,
13708
+ requestedUnits: classified.requestedUnits,
13709
+ targetSignedUnits: classified.targetSignedUnits,
13710
+ executedSizeAsset: 0,
13711
+ executionReports: [],
13712
+ message: staleTargetConflict ? "Live state moved beyond the requested target; skipping stale decision" : "No-op: live position already matches target state",
13713
+ reasonCode: staleTargetConflict ? "stale_target_conflict" : "noop_already_at_target",
13714
+ executionSnapshotId,
13715
+ executionSnapshotCapturedAt
13716
+ };
13717
+ }
13536
13718
  if (classified.intent === "open" || classified.intent === "add" || classified.intent === "flip") {
13537
13719
  throwIfAborted2(input.signal);
13538
13720
  await executeClientLeverageUpdate({
@@ -13582,7 +13764,7 @@ var init_runtime_execution = __esm({
13582
13764
  };
13583
13765
  }
13584
13766
  const executionReports = [];
13585
- if (classified.intent === "flip" && currentPositionSize !== 0) {
13767
+ if (targetResolution.closeOpposingPosition) {
13586
13768
  throwIfAborted2(input.signal);
13587
13769
  const closeResponse = await executeClientClosePosition({
13588
13770
  profileId: input.profileId,
@@ -13606,10 +13788,7 @@ var init_runtime_execution = __esm({
13606
13788
  await sleep2(Math.round(input.executionContext.closePositionDelaySeconds * 1e3), input.signal);
13607
13789
  }
13608
13790
  }
13609
- let actualUnits = Math.abs(classified.targetSignedUnits - classified.snapshotSignedUnits);
13610
- if (classified.intent === "flip") {
13611
- actualUnits = Math.abs(classified.targetSignedUnits);
13612
- }
13791
+ const actualUnits = targetResolution.orderUnits;
13613
13792
  const sizeAsset = roundAssetSize(
13614
13793
  input.executionContext.unitSizeUsdc * actualUnits / executionPrice,
13615
13794
  input.executionContext.sizeDecimals
@@ -13658,6 +13837,84 @@ var init_runtime_execution = __esm({
13658
13837
  status: "executed",
13659
13838
  execution_metadata: _toExecutionMetadata(marketOrderResponse)
13660
13839
  });
13840
+ const shouldAttachSlTp = input.executionContext.tpSlEnabled === true && classified.reduceOnly !== true && (classified.intent === "open" || classified.intent === "add" || classified.intent === "flip");
13841
+ if (shouldAttachSlTp) {
13842
+ const isLong = classified.side === "buy";
13843
+ const exitSide = isLong ? "sell" : "buy";
13844
+ const positionNotional = Math.abs(executionPrice * sizeAsset);
13845
+ const slPct = input.executionContext.stopLossEnabled === false ? 0 : resolveRiskPct(
13846
+ input.executionContext.stopLossMode,
13847
+ input.executionContext.stopLossPct,
13848
+ input.executionContext.stopLossUsdc,
13849
+ positionNotional
13850
+ );
13851
+ const tpPct = input.executionContext.takeProfitEnabled === false ? 0 : resolveRiskPct(
13852
+ input.executionContext.takeProfitMode,
13853
+ input.executionContext.takeProfitPct,
13854
+ input.executionContext.takeProfitUsdc,
13855
+ positionNotional
13856
+ );
13857
+ const slPrice = buildTriggerPrice(executionPrice, slPct, isLong, false);
13858
+ const tpPrice = buildTriggerPrice(executionPrice, tpPct, isLong, true);
13859
+ const triggerInputs = [
13860
+ { label: "AUTO_SL", triggerPrice: slPrice, isTakeProfit: false },
13861
+ { label: "AUTO_TP", triggerPrice: tpPrice, isTakeProfit: true }
13862
+ ];
13863
+ for (const triggerInput of triggerInputs) {
13864
+ if (triggerInput.triggerPrice <= 0) {
13865
+ continue;
13866
+ }
13867
+ try {
13868
+ throwIfAborted2(input.signal);
13869
+ const triggerResponse = await executeClientTriggerOrder({
13870
+ profileId: input.profileId,
13871
+ walletAddress: input.executionContext.walletAddress,
13872
+ config: input.config ?? null,
13873
+ signal: input.signal,
13874
+ signingKey: input.signingKey,
13875
+ symbol: input.executionContext.symbol,
13876
+ side: exitSide,
13877
+ size: sizeAsset,
13878
+ triggerPrice: triggerInput.triggerPrice,
13879
+ reduceOnly: true,
13880
+ isTakeProfit: triggerInput.isTakeProfit
13881
+ });
13882
+ executionReports.push({
13883
+ order_id: _extractOrderId(triggerResponse),
13884
+ symbol: input.executionContext.symbol,
13885
+ action: triggerInput.label,
13886
+ status: "executed",
13887
+ execution_metadata: {
13888
+ trigger_price: triggerInput.triggerPrice,
13889
+ is_take_profit: triggerInput.isTakeProfit,
13890
+ ..._toExecutionMetadata(triggerResponse)
13891
+ }
13892
+ });
13893
+ appendRuntimeDebugEvent({
13894
+ label: "execution_auto_sl_tp_submitted",
13895
+ note: `symbol=${input.executionContext.symbol} kind=${triggerInput.isTakeProfit ? "tp" : "sl"} side=${exitSide} sizeAsset=${sizeAsset} triggerPrice=${triggerInput.triggerPrice} order_id=${_extractOrderId(triggerResponse) || "none"}`
13896
+ });
13897
+ } catch (triggerError) {
13898
+ const message = triggerError instanceof Error ? triggerError.message : String(triggerError);
13899
+ executionReports.push({
13900
+ order_id: null,
13901
+ symbol: input.executionContext.symbol,
13902
+ action: triggerInput.label,
13903
+ status: "failed",
13904
+ execution_metadata: {
13905
+ trigger_price: triggerInput.triggerPrice,
13906
+ is_take_profit: triggerInput.isTakeProfit,
13907
+ error: message
13908
+ }
13909
+ });
13910
+ appendRuntimeDebugEvent({
13911
+ label: "execution_auto_sl_tp_failed",
13912
+ note: `symbol=${input.executionContext.symbol} kind=${triggerInput.isTakeProfit ? "tp" : "sl"} error=${message}`,
13913
+ outcome: "error"
13914
+ });
13915
+ }
13916
+ }
13917
+ }
13661
13918
  appendRuntimeDebugEvent({
13662
13919
  label: "execution_order_submitted",
13663
13920
  note: `symbol=${input.executionContext.symbol} side=${classified.side} sizeAsset=${sizeAsset} reduceOnly=${String(classified.reduceOnly)} order_id=${_extractOrderId(marketOrderResponse) || "none"}`
@@ -13763,9 +14020,11 @@ function extractDecisionPayload(content, symbol) {
13763
14020
  const decisionSource = scoped ?? parsed;
13764
14021
  const decision = normalizeDecision(decisionSource.decision);
13765
14022
  const units = decision === "HOLD" ? 0 : Math.max(0, Math.trunc(Number(decisionSource.units) || 0));
14023
+ const parsedTradability = Number(decisionSource.tradability);
14024
+ const tradability = Number.isInteger(parsedTradability) && parsedTradability >= 1 && parsedTradability <= 10 ? parsedTradability : null;
13766
14025
  const fallbackReasoning = content.trim() || "Headless runtime produced no reasoning.";
13767
14026
  const reasoning = optionalText(decisionSource.reasoning) ?? optionalText(decisionSource.analysis) ?? fallbackReasoning;
13768
- return { decision, units, reasoning };
14027
+ return { decision, units, tradability, reasoning };
13769
14028
  }
13770
14029
  function buildClientExchangeSnapshot(input) {
13771
14030
  const accountSummary = input.accountState.accountSummary;
@@ -13774,6 +14033,7 @@ function buildClientExchangeSnapshot(input) {
13774
14033
  captured_at: (/* @__PURE__ */ new Date()).toISOString(),
13775
14034
  wallet_address: input.walletAddress,
13776
14035
  symbol: input.symbol,
14036
+ mark_price: input.accountState.availableToTrade.mark_price ?? null,
13777
14037
  account_summary: {
13778
14038
  account_value: accountSummary.account_value ?? null,
13779
14039
  total_margin_used: accountSummary.total_margin_used ?? null,
@@ -13848,7 +14108,7 @@ async function fetchLocalHyperliquidSnapshot(input) {
13848
14108
  const executionConfig = buildHeadlessExchangeConfig(input.llmConfig);
13849
14109
  const promptKnobs = objectOrNull2(input.metadata.prompt_knobs) ?? {};
13850
14110
  const leverage = requirePositiveNumber2(promptKnobs.leverage ?? input.llmConfig.default_leverage, "leverage");
13851
- const accountState = await fetchBrowserAccountState({
14111
+ const accountState = await input.fetchAccountState({
13852
14112
  config: executionConfig,
13853
14113
  walletAddress,
13854
14114
  symbol: input.symbol,
@@ -13974,6 +14234,7 @@ function resolveProviderInvocation(input) {
13974
14234
  }
13975
14235
  function createHeadlessLocalWorker(options) {
13976
14236
  const fetchImpl = options.fetchImpl ?? fetch;
14237
+ const fetchAccountState = options.fetchAccountState ?? fetchBrowserAccountState;
13977
14238
  return async function runHeadlessLocalWorker(input) {
13978
14239
  if (!input.runtimeState.leaseToken) {
13979
14240
  throw new Error("Headless runtime worker requires an active runtime lease.");
@@ -14005,7 +14266,31 @@ function createHeadlessLocalWorker(options) {
14005
14266
  const secrets = objectOrNull2(input.secrets) ?? {};
14006
14267
  const decisionSnapshotId = `headless-decision-${Date.now()}`;
14007
14268
  const decisionSnapshotCapturedAt = (/* @__PURE__ */ new Date()).toISOString();
14008
- const localHyperliquidContext = providerDecision.decision === "HOLD" || providerDecision.units <= 0 ? null : await fetchLocalHyperliquidSnapshot({
14269
+ const promptKnobs = objectOrNull2(metadata.prompt_knobs) ?? {};
14270
+ const tradabilityEnabled = promptKnobs.tradability_enabled === true;
14271
+ const tradabilityOpenEnabled = promptKnobs.tradability_open_enabled === true;
14272
+ const tradabilityIncreaseEnabled = promptKnobs.tradability_increase_enabled === true;
14273
+ const tradabilityMinimumBound = tradabilityEnabled ? requireFiniteNumber(llmConfig.tradability_threshold_min, "Tradability minimum bound") : 0;
14274
+ const tradabilityMaximumBound = tradabilityEnabled ? requireFiniteNumber(llmConfig.tradability_threshold_max, "Tradability maximum bound") : 0;
14275
+ const resolveTradabilityMinimum = (value, label) => {
14276
+ if (!tradabilityEnabled) return 0;
14277
+ const parsed = requireFiniteNumber(value, label);
14278
+ if (!Number.isInteger(parsed) || parsed < tradabilityMinimumBound || parsed > tradabilityMaximumBound) {
14279
+ throw new Error(
14280
+ `Invalid headless runtime ${label}: expected an integer from ${tradabilityMinimumBound} to ${tradabilityMaximumBound}.`
14281
+ );
14282
+ }
14283
+ return parsed;
14284
+ };
14285
+ const tradabilityOpenMinimum = resolveTradabilityMinimum(
14286
+ promptKnobs.tradability_open_minimum,
14287
+ "Open Position Tradability minimum"
14288
+ );
14289
+ const tradabilityIncreaseMinimum = resolveTradabilityMinimum(
14290
+ promptKnobs.tradability_increase_minimum,
14291
+ "Increase Position Tradability minimum"
14292
+ );
14293
+ const candidateHyperliquidContext = providerDecision.decision === "HOLD" || providerDecision.units <= 0 ? null : await fetchLocalHyperliquidSnapshot({
14009
14294
  client: options.client,
14010
14295
  profileId: input.profileId,
14011
14296
  llmConfig,
@@ -14014,8 +14299,36 @@ function createHeadlessLocalWorker(options) {
14014
14299
  symbol,
14015
14300
  timeframe,
14016
14301
  decisionSnapshotId,
14017
- decisionSnapshotCapturedAt
14302
+ decisionSnapshotCapturedAt,
14303
+ fetchAccountState
14018
14304
  });
14305
+ const classifiedIntent = candidateHyperliquidContext ? classifyExecutionIntent(
14306
+ providerDecision.decision,
14307
+ providerDecision.units,
14308
+ signedUnitsFromPosition(
14309
+ candidateHyperliquidContext.executionContext.currentPositionSize,
14310
+ candidateHyperliquidContext.executionContext.lastPrice,
14311
+ candidateHyperliquidContext.executionContext.unitSizeUsdc
14312
+ )
14313
+ ).intent : "hold";
14314
+ const applicableMinimum = classifiedIntent === "open" && tradabilityOpenEnabled ? tradabilityOpenMinimum : classifiedIntent === "add" && tradabilityIncreaseEnabled ? tradabilityIncreaseMinimum : null;
14315
+ const tradabilityRejected = tradabilityEnabled && applicableMinimum !== null && (providerDecision.tradability == null || providerDecision.tradability < applicableMinimum);
14316
+ const localHyperliquidContext = tradabilityRejected ? null : candidateHyperliquidContext;
14317
+ const tradabilityBlockExecution = tradabilityRejected && applicableMinimum !== null ? {
14318
+ order_id: null,
14319
+ symbol,
14320
+ action: providerDecision.decision,
14321
+ status: "blocked",
14322
+ execution_metadata: {
14323
+ reason_code: "tradability_below_minimum",
14324
+ control: "tradability",
14325
+ intent: classifiedIntent,
14326
+ tradability: providerDecision.tradability,
14327
+ minimum: applicableMinimum,
14328
+ decision_snapshot_id: decisionSnapshotId,
14329
+ message: `Tradability ${providerDecision.tradability ?? "missing"} is below the ${classifiedIntent === "open" ? "Open" : "Add"} minimum of ${applicableMinimum}. No order was placed.`
14330
+ }
14331
+ } : null;
14019
14332
  const envelope = metadata.envelope ?? {};
14020
14333
  const analysisRunId = `headless-${Date.now()}`;
14021
14334
  const baseTradeSync = {
@@ -14038,6 +14351,7 @@ function createHeadlessLocalWorker(options) {
14038
14351
  policy_generation_id: requireText(envelope.policy_generation_id, "policy generation id"),
14039
14352
  decision: providerDecision.decision,
14040
14353
  units: providerDecision.units,
14354
+ final_tradability: providerDecision.tradability,
14041
14355
  reasoning: providerDecision.reasoning,
14042
14356
  model: requireText(prompt.model ?? metadata.model, "model"),
14043
14357
  model_source: normalizeModelSource(metadata.model_source),
@@ -14056,7 +14370,11 @@ function createHeadlessLocalWorker(options) {
14056
14370
  is_analyzing: false,
14057
14371
  is_executing: Boolean(localHyperliquidContext)
14058
14372
  },
14059
- tradeSync: localHyperliquidContext ? null : { ...baseTradeSync, executions: [] },
14373
+ tradeSync: localHyperliquidContext ? null : {
14374
+ ...baseTradeSync,
14375
+ execution_snapshot_id: tradabilityBlockExecution ? decisionSnapshotId : void 0,
14376
+ executions: tradabilityBlockExecution ? [tradabilityBlockExecution] : []
14377
+ },
14060
14378
  afterDecision: localHyperliquidContext ? async () => {
14061
14379
  const executionResult = await executeClientRuntimeDecision({
14062
14380
  profileId: String(input.profileId),
@@ -14085,6 +14403,7 @@ var init_headless_local_worker = __esm({
14085
14403
  init_hyperliquid_account_state_adapter();
14086
14404
  init_hyperliquid_market_symbol();
14087
14405
  init_runtime_execution();
14406
+ init_execution_guard_contract();
14088
14407
  OPENAI_COMPATIBLE_SOURCE_KEY = {
14089
14408
  openai: "openai",
14090
14409
  groq: "groq",
@@ -14110,6 +14429,7 @@ var init_headless_local_worker = __esm({
14110
14429
  // lib/agent-cli/vtx.ts
14111
14430
  import { randomUUID as randomUUID3 } from "node:crypto";
14112
14431
  import { spawn } from "node:child_process";
14432
+ import { hostname } from "node:os";
14113
14433
 
14114
14434
  // lib/agent-core/config.ts
14115
14435
  import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
@@ -14448,6 +14768,33 @@ var VtxAgentClient = class _VtxAgentClient {
14448
14768
  getLlmConfig(profileId) {
14449
14769
  return this.request("/llm/config", { profileId });
14450
14770
  }
14771
+ registerInsightConnector(profileId, payload) {
14772
+ return this.request("/insights/connectors/register", {
14773
+ method: "POST",
14774
+ profileId,
14775
+ body: payload
14776
+ });
14777
+ }
14778
+ claimInsightConnectorJob(profileId, connectorId, deviceId) {
14779
+ return this.request(
14780
+ `/insights/connectors/${encodeURIComponent(connectorId)}/jobs:claim`,
14781
+ { method: "POST", profileId, body: { device_id: deviceId } }
14782
+ );
14783
+ }
14784
+ completeInsightConnectorJob(profileId, connectorId, jobId, deviceId, payload) {
14785
+ const params = new URLSearchParams({ device_id: deviceId });
14786
+ return this.request(
14787
+ `/insights/connectors/${encodeURIComponent(connectorId)}/jobs/${encodeURIComponent(jobId)}:complete?${params}`,
14788
+ { method: "POST", profileId, body: payload }
14789
+ );
14790
+ }
14791
+ heartbeatInsightConnectorJob(profileId, connectorId, jobId, deviceId, claimToken) {
14792
+ const params = new URLSearchParams({ device_id: deviceId });
14793
+ return this.request(
14794
+ `/insights/connectors/${encodeURIComponent(connectorId)}/jobs/${encodeURIComponent(jobId)}:heartbeat?${params}`,
14795
+ { method: "POST", profileId, body: { claim_token: claimToken } }
14796
+ );
14797
+ }
14451
14798
  heartbeatRuntime(profileId, payload, leaseToken) {
14452
14799
  return this.request("/trading/ai/runtime/heartbeat", {
14453
14800
  method: "POST",
@@ -14710,7 +15057,8 @@ async function startHeadlessRuntime(options) {
14710
15057
  session_id: runtimeSessionId,
14711
15058
  device_id: deviceId,
14712
15059
  mode: "trader",
14713
- force_takeover: Boolean(options.forceTakeover)
15060
+ force_takeover: Boolean(options.forceTakeover),
15061
+ takeover_initiator: options.forceTakeover ? "agent_forced" : void 0
14714
15062
  });
14715
15063
  if (startResponse.status === "takeover_required") {
14716
15064
  throw new HeadlessRuntimeTakeoverRequiredError(startResponse);
@@ -14805,6 +15153,277 @@ async function startHeadlessRuntime(options) {
14805
15153
  }
14806
15154
  }
14807
15155
 
15156
+ // lib/agent-core/insights-local-connector.ts
15157
+ var delay = (milliseconds, signal) => new Promise((resolve, reject) => {
15158
+ if (signal?.aborted) {
15159
+ reject(signal?.reason ?? new Error("Local Insights connector stopped"));
15160
+ return;
15161
+ }
15162
+ const onAbort = () => {
15163
+ clearTimeout(timer);
15164
+ reject(signal?.reason ?? new Error("Local Insights connector stopped"));
15165
+ };
15166
+ const timer = setTimeout(() => {
15167
+ signal?.removeEventListener("abort", onAbort);
15168
+ resolve();
15169
+ }, milliseconds);
15170
+ signal?.addEventListener("abort", onAbort, { once: true });
15171
+ });
15172
+ function normalizeLocalModelBaseUrl(value) {
15173
+ const raw = String(value || "http://127.0.0.1:1234/v1").trim().replace(/\/+$/, "");
15174
+ const parsed = new URL(raw);
15175
+ const hostname2 = parsed.hostname.toLowerCase();
15176
+ if (!["localhost", "127.0.0.1", "::1", "[::1]"].includes(hostname2)) {
15177
+ throw new Error("The Local Insights connector only accepts a loopback model URL");
15178
+ }
15179
+ if (!["http:", "https:"].includes(parsed.protocol)) {
15180
+ throw new Error("The local model URL must use HTTP or HTTPS");
15181
+ }
15182
+ if (parsed.username || parsed.password || parsed.search || parsed.hash) {
15183
+ throw new Error("The local model URL cannot contain credentials, a query, or a fragment");
15184
+ }
15185
+ return raw;
15186
+ }
15187
+ function authHeaders(apiKey) {
15188
+ const value = String(apiKey || "").trim();
15189
+ return value ? { Authorization: `Bearer ${value}` } : {};
15190
+ }
15191
+ function isRetryableVtxTransportError(error) {
15192
+ return !(error instanceof AgentClientError) || error.status === 429 || error.status >= 500;
15193
+ }
15194
+ async function withVtxTransportRetry(operation, retryMilliseconds, signal) {
15195
+ while (true) {
15196
+ try {
15197
+ return await operation();
15198
+ } catch (error) {
15199
+ if (!isRetryableVtxTransportError(error)) throw error;
15200
+ await delay(retryMilliseconds, signal);
15201
+ }
15202
+ }
15203
+ }
15204
+ function modelArray(payload) {
15205
+ if (Array.isArray(payload)) return payload.filter((item) => Boolean(item && typeof item === "object"));
15206
+ if (!payload || typeof payload !== "object") return [];
15207
+ const record = payload;
15208
+ for (const key of ["data", "models"]) {
15209
+ if (Array.isArray(record[key])) {
15210
+ return record[key].filter((item) => Boolean(item && typeof item === "object"));
15211
+ }
15212
+ }
15213
+ return [];
15214
+ }
15215
+ function positiveInteger(value) {
15216
+ const parsed = Number(value);
15217
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
15218
+ }
15219
+ function connectorModels(payload) {
15220
+ const models = modelArray(payload).flatMap((item) => {
15221
+ const modelType = String(item.type || "").trim().toLowerCase();
15222
+ if (modelType && !["llm", "vlm"].includes(modelType)) return [];
15223
+ const modelState = String(item.state || "").trim().toLowerCase();
15224
+ if (modelState && modelState !== "loaded") return [];
15225
+ const id2 = String(item.id || item.model || item.key || "").trim();
15226
+ if (!id2) return [];
15227
+ const loadedInstances = Array.isArray(item.loaded_instances) ? item.loaded_instances.filter((instance) => Boolean(instance && typeof instance === "object")) : [];
15228
+ const loadedConfig = loadedInstances[0]?.config;
15229
+ const loadedContextLength = loadedConfig && typeof loadedConfig === "object" ? loadedConfig.context_length : null;
15230
+ return [{
15231
+ id: id2,
15232
+ name: String(item.display_name || item.name || item.id || id2).trim() || id2,
15233
+ context_length: positiveInteger(
15234
+ item.loaded_context_length ?? loadedContextLength ?? item.max_context_length ?? item.context_length ?? item.context_window
15235
+ )
15236
+ }];
15237
+ });
15238
+ return [...new Map(models.map((model) => [model.id, model])).values()].sort((left, right) => left.id.localeCompare(right.id));
15239
+ }
15240
+ async function fetchJson(fetchImpl, url, init, failureMessage) {
15241
+ const response = await fetchImpl(url, { ...init, redirect: "error" });
15242
+ if (!response.ok) throw new Error(`${failureMessage} (${response.status})`);
15243
+ const payload = await response.json();
15244
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
15245
+ throw new Error(`${failureMessage} (invalid JSON response)`);
15246
+ }
15247
+ return payload;
15248
+ }
15249
+ async function discoverLocalConnectorModels(options = {}) {
15250
+ const baseUrl = normalizeLocalModelBaseUrl(options.baseUrl);
15251
+ const fetchImpl = options.fetchImpl ?? fetch;
15252
+ const headers = { Accept: "application/json", ...authHeaders(options.apiKey) };
15253
+ const parsed = new URL(baseUrl);
15254
+ const candidates = [
15255
+ `${parsed.origin}/api/v0/models`,
15256
+ `${baseUrl}/models`
15257
+ ];
15258
+ let lastError = null;
15259
+ for (const url of candidates) {
15260
+ try {
15261
+ const payload = await fetchJson(fetchImpl, url, { headers }, "Unable to list local models");
15262
+ const models = connectorModels(payload);
15263
+ if (models.length > 0) return models;
15264
+ } catch (error) {
15265
+ lastError = error;
15266
+ }
15267
+ }
15268
+ throw lastError instanceof Error ? lastError : new Error("The local model server returned no models");
15269
+ }
15270
+ async function completeFailedJob(options, connector, job, errorClass) {
15271
+ try {
15272
+ await withVtxTransportRetry(
15273
+ () => options.client.completeInsightConnectorJob(
15274
+ options.profileId,
15275
+ connector.id,
15276
+ job.id,
15277
+ options.deviceId,
15278
+ {
15279
+ claim_token: job.claim_token,
15280
+ status: "failed",
15281
+ error_class: errorClass.slice(0, 64)
15282
+ }
15283
+ ),
15284
+ connector.poll_interval_milliseconds,
15285
+ options.signal
15286
+ );
15287
+ return true;
15288
+ } catch (error) {
15289
+ if (error instanceof AgentClientError && error.status === 409) return false;
15290
+ throw error;
15291
+ }
15292
+ }
15293
+ async function runJob(options, connector, job, baseUrl, fetchImpl) {
15294
+ const requestController = new AbortController();
15295
+ const abortFromParent = () => requestController.abort(
15296
+ options.signal?.reason ?? new Error("Local Insights connector stopped")
15297
+ );
15298
+ options.signal?.addEventListener("abort", abortFromParent, { once: true });
15299
+ let heartbeatError = null;
15300
+ let finished = false;
15301
+ const heartbeatInterval = options.heartbeatIntervalMilliseconds ?? Math.max(1e3, connector.poll_interval_milliseconds * 10);
15302
+ const heartbeatTask = (async () => {
15303
+ try {
15304
+ while (!finished && !requestController.signal.aborted) {
15305
+ await delay(heartbeatInterval, requestController.signal);
15306
+ if (finished) return;
15307
+ try {
15308
+ await options.client.heartbeatInsightConnectorJob(
15309
+ options.profileId,
15310
+ connector.id,
15311
+ job.id,
15312
+ options.deviceId,
15313
+ job.claim_token
15314
+ );
15315
+ } catch (error) {
15316
+ if (isRetryableVtxTransportError(error)) continue;
15317
+ throw error;
15318
+ }
15319
+ }
15320
+ } catch (error) {
15321
+ if (!finished && !options.signal?.aborted) {
15322
+ heartbeatError = error;
15323
+ requestController.abort(error);
15324
+ }
15325
+ }
15326
+ })();
15327
+ let response;
15328
+ try {
15329
+ try {
15330
+ response = await fetchJson(fetchImpl, `${baseUrl}/chat/completions`, {
15331
+ method: "POST",
15332
+ headers: {
15333
+ Accept: "application/json",
15334
+ "Content-Type": "application/json",
15335
+ ...authHeaders(options.apiKey)
15336
+ },
15337
+ body: JSON.stringify({ ...job.request, model: job.model_id, stream: false }),
15338
+ signal: requestController.signal
15339
+ }, "The local model request failed");
15340
+ } catch (error) {
15341
+ if (options.signal?.aborted) throw error;
15342
+ if (heartbeatError instanceof AgentClientError && heartbeatError.status === 409) {
15343
+ return false;
15344
+ }
15345
+ if (heartbeatError) throw heartbeatError;
15346
+ const completed = await completeFailedJob(
15347
+ options,
15348
+ connector,
15349
+ job,
15350
+ "local_model_request_failed"
15351
+ );
15352
+ return completed;
15353
+ }
15354
+ if (heartbeatError instanceof AgentClientError && heartbeatError.status === 409) {
15355
+ return false;
15356
+ }
15357
+ if (heartbeatError) throw heartbeatError;
15358
+ try {
15359
+ await withVtxTransportRetry(
15360
+ () => options.client.completeInsightConnectorJob(
15361
+ options.profileId,
15362
+ connector.id,
15363
+ job.id,
15364
+ options.deviceId,
15365
+ {
15366
+ claim_token: job.claim_token,
15367
+ status: "succeeded",
15368
+ response
15369
+ }
15370
+ ),
15371
+ connector.poll_interval_milliseconds,
15372
+ options.signal
15373
+ );
15374
+ return true;
15375
+ } catch (error) {
15376
+ if (error instanceof AgentClientError && error.status === 409) return false;
15377
+ throw error;
15378
+ }
15379
+ } finally {
15380
+ finished = true;
15381
+ requestController.abort(new Error("Local model job finished"));
15382
+ options.signal?.removeEventListener("abort", abortFromParent);
15383
+ await heartbeatTask;
15384
+ }
15385
+ }
15386
+ async function runLocalInsightsConnector(options) {
15387
+ const baseUrl = normalizeLocalModelBaseUrl(options.baseUrl);
15388
+ const fetchImpl = options.fetchImpl ?? fetch;
15389
+ const models = await discoverLocalConnectorModels({
15390
+ baseUrl,
15391
+ apiKey: options.apiKey,
15392
+ fetchImpl
15393
+ });
15394
+ const connector = await options.client.registerInsightConnector(options.profileId, {
15395
+ device_id: options.deviceId,
15396
+ display_name: options.displayName,
15397
+ models
15398
+ });
15399
+ options.onRegistered?.(connector);
15400
+ let jobsCompleted = 0;
15401
+ do {
15402
+ let job;
15403
+ try {
15404
+ job = await options.client.claimInsightConnectorJob(
15405
+ options.profileId,
15406
+ connector.id,
15407
+ options.deviceId
15408
+ );
15409
+ } catch (error) {
15410
+ if (!isRetryableVtxTransportError(error)) throw error;
15411
+ await delay(connector.poll_interval_milliseconds, options.signal);
15412
+ continue;
15413
+ }
15414
+ if (job) {
15415
+ if (await runJob(options, connector, job, baseUrl, fetchImpl)) {
15416
+ jobsCompleted += 1;
15417
+ }
15418
+ if (options.once) break;
15419
+ continue;
15420
+ }
15421
+ if (options.once) break;
15422
+ await delay(connector.poll_interval_milliseconds, options.signal);
15423
+ } while (!options.signal?.aborted);
15424
+ return { connector, jobsCompleted };
15425
+ }
15426
+
14808
15427
  // lib/agent-cli/vtx.ts
14809
15428
  init_hyperliquid_market_symbol();
14810
15429
  var WRITABLE_SECRET_FIELDS = /* @__PURE__ */ new Set([
@@ -14852,7 +15471,7 @@ function redactCliOutput(value) {
14852
15471
  }
14853
15472
  return result2;
14854
15473
  }
14855
- var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
15474
+ var delay2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
14856
15475
  function openBrowser(url) {
14857
15476
  const platform = process.platform;
14858
15477
  const command = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
@@ -15084,11 +15703,11 @@ Waiting for approval...
15084
15703
  if (polled.status === "denied") {
15085
15704
  throw new Error(polled.error || "CLI login was denied");
15086
15705
  }
15087
- await delay(Math.max(1, Number(started.interval || 2)) * 1e3);
15706
+ await delay2(Math.max(1, Number(started.interval || 2)) * 1e3);
15088
15707
  }
15089
15708
  throw new Error("Timed out waiting for CLI login approval");
15090
15709
  }
15091
- async function runVtxCli(argv, env = process.env) {
15710
+ async function runVtxCli(argv, env = process.env, onProgress = () => void 0) {
15092
15711
  try {
15093
15712
  const parsed = parseFlags(argv);
15094
15713
  const [group, command, ...args] = parsed.rest;
@@ -15243,7 +15862,8 @@ async function runVtxCli(argv, env = process.env) {
15243
15862
  session_id: runtimeSessionId,
15244
15863
  device_id: deviceId,
15245
15864
  mode: "trader",
15246
- force_takeover: args.includes("--force-takeover")
15865
+ force_takeover: args.includes("--force-takeover"),
15866
+ takeover_initiator: args.includes("--force-takeover") ? "agent_forced" : void 0
15247
15867
  });
15248
15868
  if (objectOrNull3(response)?.status === "takeover_required") {
15249
15869
  await clearRuntimeState(config.statePath);
@@ -15329,6 +15949,52 @@ async function runVtxCli(argv, env = process.env) {
15329
15949
  if (group === "ai" && command === "config") {
15330
15950
  return { exitCode: 0, stdout: render(await client.getLlmConfig(parsed.profileId ?? config.activeProfileId), json), stderr: "" };
15331
15951
  }
15952
+ if (group === "insights" && command === "connect") {
15953
+ const profileId = requireProfileId(parsed.profileId, config.activeProfileId);
15954
+ const hostLabel = hostname().replace(/[^A-Za-z0-9._-]+/g, "-").slice(0, 96) || "local-device";
15955
+ const deviceId = optionalValue(args, "--device-id") || config.runtimeDeviceId || `insights-${hostLabel}`;
15956
+ const displayName = optionalValue(args, "--name") || `LM Studio on ${hostLabel}`;
15957
+ const apiKeyEnvironment = optionalValue(args, "--api-key-env") || "LM_STUDIO_API_KEY";
15958
+ const apiKey = String(env[apiKeyEnvironment] || "").trim() || null;
15959
+ const abortController = new AbortController();
15960
+ const stop = () => abortController.abort(new Error("Local Insights connector stopped"));
15961
+ process.once("SIGINT", stop);
15962
+ process.once("SIGTERM", stop);
15963
+ try {
15964
+ const result2 = await runLocalInsightsConnector({
15965
+ client,
15966
+ profileId,
15967
+ deviceId,
15968
+ displayName,
15969
+ baseUrl: optionalValue(args, "--base-url") || void 0,
15970
+ apiKey,
15971
+ once: args.includes("--once"),
15972
+ signal: abortController.signal,
15973
+ onRegistered: (connector) => onProgress(
15974
+ `Local Insights connector online: ${connector.display_name} (${connector.models.length} models)
15975
+ `
15976
+ )
15977
+ });
15978
+ return {
15979
+ exitCode: 0,
15980
+ stdout: render({
15981
+ status: "stopped",
15982
+ connector_id: result2.connector.id,
15983
+ models: result2.connector.models.map((model) => model.id),
15984
+ jobs_completed: result2.jobsCompleted
15985
+ }, json),
15986
+ stderr: ""
15987
+ };
15988
+ } catch (error) {
15989
+ if (abortController.signal.aborted) {
15990
+ return { exitCode: 0, stdout: render({ status: "stopped" }, json), stderr: "" };
15991
+ }
15992
+ throw error;
15993
+ } finally {
15994
+ process.removeListener("SIGINT", stop);
15995
+ process.removeListener("SIGTERM", stop);
15996
+ }
15997
+ }
15332
15998
  throw new Error(`Unsupported command: ${[group, command].filter(Boolean).join(" ") || "(none)"}`);
15333
15999
  } catch (error) {
15334
16000
  const message = error instanceof AgentClientError ? error.message : error instanceof HeadlessRuntimeTakeoverRequiredError ? error.message : error instanceof Error ? error.message : "Unknown CLI error";
@@ -15338,7 +16004,11 @@ async function runVtxCli(argv, env = process.env) {
15338
16004
  }
15339
16005
 
15340
16006
  // bin/vtx.ts
15341
- var result = await runVtxCli(process.argv.slice(2));
16007
+ var result = await runVtxCli(
16008
+ process.argv.slice(2),
16009
+ process.env,
16010
+ (message) => process.stderr.write(message)
16011
+ );
15342
16012
  if (result.stdout) {
15343
16013
  process.stdout.write(result.stdout);
15344
16014
  }
package/package.json CHANGED
@@ -1,9 +1,13 @@
1
1
  {
2
2
  "name": "@vtxmacro/cli",
3
- "version": "0.1.0",
3
+ "version": "2026.6.22",
4
4
  "description": "VTX Macro CLI and MCP server for agent-token automation.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/DataDoesYou/VTX-Macro.git"
10
+ },
7
11
  "private": false,
8
12
  "bin": {
9
13
  "vtx": "bin/vtx.js",