@ametyst/cli 0.3.4 → 0.3.5

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.
Files changed (2) hide show
  1. package/dist/index.js +117 -57
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -112197,7 +112197,7 @@ var init_version5 = __esm({
112197
112197
  "src/version.ts"() {
112198
112198
  "use strict";
112199
112199
  init_esm_shims();
112200
- CLI_VERSION = true ? "0.3.4" : "0.0.0-dev";
112200
+ CLI_VERSION = true ? "0.3.5" : "0.0.0-dev";
112201
112201
  }
112202
112202
  });
112203
112203
 
@@ -115417,6 +115417,46 @@ var McpEventLogger = class {
115417
115417
  }
115418
115418
  };
115419
115419
 
115420
+ // src/mcp-server/newest-grant.ts
115421
+ init_esm_shims();
115422
+ function nonEmptyLowerCase(value2) {
115423
+ return typeof value2 === "string" && value2.trim() !== "" ? value2.toLowerCase() : null;
115424
+ }
115425
+ function isNewerGrant(candidate, incumbent) {
115426
+ const candidateId = Number(candidate?.id);
115427
+ const incumbentId = Number(incumbent?.id);
115428
+ const candidateIdUsable = Number.isFinite(candidateId);
115429
+ const incumbentIdUsable = Number.isFinite(incumbentId);
115430
+ if (candidateIdUsable && incumbentIdUsable && candidateId !== incumbentId) {
115431
+ return candidateId > incumbentId;
115432
+ }
115433
+ const candidateAt = Date.parse(typeof candidate?.createdAt === "string" ? candidate.createdAt : "");
115434
+ const incumbentAt = Date.parse(typeof incumbent?.createdAt === "string" ? incumbent.createdAt : "");
115435
+ if (Number.isFinite(candidateAt) && Number.isFinite(incumbentAt) && candidateAt !== incumbentAt) {
115436
+ return candidateAt > incumbentAt;
115437
+ }
115438
+ return candidateIdUsable && !incumbentIdUsable;
115439
+ }
115440
+ function selectNewestApprovedWallet(wallets, eoaAddress) {
115441
+ if (!Array.isArray(wallets)) return void 0;
115442
+ const eoa = nonEmptyLowerCase(eoaAddress);
115443
+ if (!eoa) return void 0;
115444
+ let newest;
115445
+ for (const wallet of wallets) {
115446
+ if (wallet?.status !== "approved") continue;
115447
+ if (nonEmptyLowerCase(wallet?.address) !== eoa) continue;
115448
+ if (newest === void 0 || isNewerGrant(wallet, newest)) newest = wallet;
115449
+ }
115450
+ return newest;
115451
+ }
115452
+ function findCurrentApprovedWallet(wallets, eoaAddress, pendingWalletId) {
115453
+ if (!Array.isArray(wallets)) return void 0;
115454
+ if (pendingWalletId) {
115455
+ return wallets.find((w) => String(w?.id) === String(pendingWalletId) && w?.status === "approved");
115456
+ }
115457
+ return selectNewestApprovedWallet(wallets, eoaAddress);
115458
+ }
115459
+
115420
115460
  // src/mcp-server/start-session.ts
115421
115461
  init_esm_shims();
115422
115462
  init_dist();
@@ -115551,38 +115591,57 @@ async function runStartSession(ctx, timing = new TimingCollector("start_session"
115551
115591
  let virtualWalletId = ctx.virtualWalletId;
115552
115592
  let paymentManagerAddress = ctx.paymentManagerAddress;
115553
115593
  let policyId = ctx.policyId;
115554
- if (!walletAddress || !virtualWalletId) {
115555
- try {
115556
- const approvedWallet = wallets.find(
115557
- (w) => w.address?.toLowerCase() === ctx.eoaAddress?.toLowerCase() && w.status === "approved"
115558
- );
115559
- if (approvedWallet) {
115560
- walletAddress = approvedWallet.walletAddress || approvedWallet.kernelAccountAddress;
115561
- virtualWalletId = String(approvedWallet.id);
115562
- paymentManagerAddress = approvedWallet.paymentManagerAddress;
115563
- policyId = String(approvedWallet.policyAssociated);
115564
- credentials.walletAddress = walletAddress;
115565
- credentials.virtualWalletId = virtualWalletId;
115566
- credentials.paymentManagerAddress = paymentManagerAddress;
115567
- credentials.policyId = policyId;
115568
- credentials.authorizationStatus = "approved";
115569
- if (approvedWallet.policyValidUntil != null) {
115570
- credentials.policyValidUntil = BigInt(approvedWallet.policyValidUntil);
115571
- }
115572
- }
115573
- } catch (backendError) {
115594
+ let activeWallet;
115595
+ let grantChanged = false;
115596
+ try {
115597
+ activeWallet = selectNewestApprovedWallet(wallets, ctx.eoaAddress);
115598
+ if (activeWallet) {
115599
+ const newestId = String(activeWallet.id);
115600
+ const rowWalletAddress = activeWallet.walletAddress || activeWallet.kernelAccountAddress;
115601
+ const rowPaymentManagerAddress = activeWallet.paymentManagerAddress;
115602
+ const rowPolicyId = activeWallet.policyAssociated != null ? String(activeWallet.policyAssociated) : void 0;
115603
+ grantChanged = !!virtualWalletId && virtualWalletId !== newestId;
115604
+ if (grantChanged) {
115605
+ console.error(
115606
+ `[start_session] The active grant moved on \u2014 virtual wallet ${virtualWalletId} -> ${newestId}. Adopting the newest approved grant and dropping the session state derived from the old one.`
115607
+ );
115608
+ walletAddress = rowWalletAddress;
115609
+ paymentManagerAddress = rowPaymentManagerAddress;
115610
+ policyId = rowPolicyId;
115611
+ } else {
115612
+ walletAddress = rowWalletAddress || walletAddress;
115613
+ paymentManagerAddress = rowPaymentManagerAddress || paymentManagerAddress;
115614
+ policyId = rowPolicyId ?? policyId;
115615
+ }
115616
+ virtualWalletId = newestId;
115617
+ credentials.walletAddress = walletAddress;
115618
+ credentials.virtualWalletId = virtualWalletId;
115619
+ credentials.paymentManagerAddress = paymentManagerAddress;
115620
+ credentials.policyId = policyId;
115621
+ credentials.authorizationStatus = "approved";
115622
+ if (activeWallet.policyName != null) {
115623
+ credentials.policyName = activeWallet.policyName;
115624
+ }
115625
+ if (activeWallet.policyValidUntil != null) {
115626
+ credentials.policyValidUntil = BigInt(activeWallet.policyValidUntil);
115627
+ }
115628
+ } else if (walletsFetchFailed && virtualWalletId) {
115574
115629
  console.warn(
115575
- "[start_session] Could not check backend:",
115576
- backendError instanceof Error ? backendError.message : String(backendError)
115630
+ `[start_session] \u26A0\uFE0F could not verify the active grant \u2014 signing with the cached one (virtual wallet ${virtualWalletId}, policy ${policyId ?? "unknown"}). If a newer grant was approved since, spends are metered against the older one and may hit its spending limit.`
115577
115631
  );
115578
115632
  }
115633
+ } catch (backendError) {
115634
+ console.warn(
115635
+ "[start_session] Could not check backend:",
115636
+ backendError instanceof Error ? backendError.message : String(backendError)
115637
+ );
115579
115638
  }
115580
115639
  const mergedAuth = credentials.authorizationStatus !== void 0 ? credentials.authorizationStatus : ctx.authorizationStatus;
115581
115640
  console.error(
115582
115641
  `[start_session] State check \u2014 walletAddress: ${walletAddress}, virtualWalletId: ${virtualWalletId}, paymentManagerAddress: ${paymentManagerAddress}, policyId: ${policyId}`
115583
115642
  );
115584
115643
  if (mergedAuth === "expired") {
115585
- return { responseKey: "unlocked_expired", credentials, timingReport: timing.report() };
115644
+ return { responseKey: "unlocked_expired", credentials, timingReport: timing.report(), grantChanged };
115586
115645
  }
115587
115646
  if (walletAddress && virtualWalletId && paymentManagerAddress && policyId) {
115588
115647
  let privateKey = null;
@@ -115604,12 +115663,9 @@ async function runStartSession(ctx, timing = new TimingCollector("start_session"
115604
115663
  }
115605
115664
  credentials.walletAddress = walletAddress;
115606
115665
  credentials.paymentManagerAddress = paymentManagerAddress;
115607
- const approvedWallet = wallets.find(
115608
- (w) => w.address?.toLowerCase() === ctx.eoaAddress?.toLowerCase() && w.status === "approved"
115609
- );
115610
- const preloadedWalletData = approvedWallet != null && approvedWallet.policyValidAfter != null && approvedWallet.policyValidUntil != null ? {
115611
- policyValidAfter: Number(approvedWallet.policyValidAfter),
115612
- policyValidUntil: Number(approvedWallet.policyValidUntil)
115666
+ const preloadedWalletData = activeWallet != null && activeWallet.policyValidAfter != null && activeWallet.policyValidUntil != null ? {
115667
+ policyValidAfter: Number(activeWallet.policyValidAfter),
115668
+ policyValidUntil: Number(activeWallet.policyValidUntil)
115613
115669
  } : void 0;
115614
115670
  if (ctx.readPolicyActiveOnchain) {
115615
115671
  const nowSeconds = Math.floor(Date.now() / 1e3);
@@ -115631,7 +115687,7 @@ async function runStartSession(ctx, timing = new TimingCollector("start_session"
115631
115687
  maxAttempts
115632
115688
  });
115633
115689
  if (bootAction === "reauthorize") {
115634
- return { responseKey: "unlocked_pending_authorization", credentials, timingReport: timing.report() };
115690
+ return { responseKey: "unlocked_pending_authorization", credentials, timingReport: timing.report(), grantChanged };
115635
115691
  }
115636
115692
  }
115637
115693
  const { kernelClient: virtualWalletKernelAccountClient, permissionPlugin: virtualWalletPermissionPlugin, kernelDomain } = await virtualWalletsManagers2.configureVirtualWalletKernelAccount(
@@ -115661,13 +115717,14 @@ async function runStartSession(ctx, timing = new TimingCollector("start_session"
115661
115717
  console.warn("[start_session] \u26A0\uFE0F Could not fetch credit token address (Path B routing disabled):", e);
115662
115718
  }
115663
115719
  }
115664
- return { responseKey: "unlocked_and_authorized", credentials, timingReport: timing.report() };
115720
+ return { responseKey: "unlocked_and_authorized", credentials, timingReport: timing.report(), grantChanged };
115665
115721
  } catch (error) {
115666
115722
  console.error("\u274C [start_session] Kernel client creation failed:", error);
115667
115723
  return {
115668
115724
  responseKey: "unlocked_not_configured",
115669
115725
  credentials,
115670
115726
  timingReport: timing.report(),
115727
+ grantChanged,
115671
115728
  kernelErrorMessage: error instanceof Error ? error.message : String(error)
115672
115729
  };
115673
115730
  } finally {
@@ -115675,9 +115732,9 @@ async function runStartSession(ctx, timing = new TimingCollector("start_session"
115675
115732
  }
115676
115733
  }
115677
115734
  if (!walletsFetchFailed && wallets.length === 0) {
115678
- return { responseKey: "unlocked_provisioning_pending", credentials, timingReport: timing.report() };
115735
+ return { responseKey: "unlocked_provisioning_pending", credentials, timingReport: timing.report(), grantChanged };
115679
115736
  }
115680
- return { responseKey: "unlocked_pending_authorization", credentials, timingReport: timing.report() };
115737
+ return { responseKey: "unlocked_pending_authorization", credentials, timingReport: timing.report(), grantChanged };
115681
115738
  }
115682
115739
  var WALLET_SCOPED_CREDENTIAL_KEYS = [
115683
115740
  "virtualWalletId",
@@ -115707,9 +115764,9 @@ function clearWalletScopedCredentials(target) {
115707
115764
  function sameAddress(a, b) {
115708
115765
  return String(a ?? "").toLowerCase() === String(b ?? "").toLowerCase();
115709
115766
  }
115710
- function mergeStartSessionCredentials(target, patch, responseKey) {
115767
+ function mergeStartSessionCredentials(target, patch, responseKey, options = {}) {
115711
115768
  const walletChanged = patch.eoaAddress !== void 0 && !sameAddress(patch.eoaAddress, target.eoaAddress);
115712
- if (walletChanged) {
115769
+ if (walletChanged || options.grantChanged) {
115713
115770
  clearWalletScopedCredentials(target);
115714
115771
  }
115715
115772
  if (patch.walletKeystoreJson !== void 0) {
@@ -122739,17 +122796,6 @@ var approvalWaitConfig = (() => {
122739
122796
  delay: (ms) => new Promise((resolve) => setTimeout(resolve, ms))
122740
122797
  };
122741
122798
  })();
122742
- function findCurrentApprovedWallet(wallets, eoaAddress, pendingWalletId) {
122743
- if (!Array.isArray(wallets)) return void 0;
122744
- if (pendingWalletId) {
122745
- return wallets.find(
122746
- (w) => String(w?.id) === String(pendingWalletId) && w?.status === "approved"
122747
- );
122748
- }
122749
- return wallets.find(
122750
- (w) => w?.address?.toLowerCase() === eoaAddress?.toLowerCase() && w?.status === "approved"
122751
- );
122752
- }
122753
122799
  async function tryResolvePendingApproval(probe) {
122754
122800
  if (currentCredentials.authorizationStatus === "approved") return true;
122755
122801
  if (currentCredentials.authorizationStatus !== "pending" || !currentCredentials.apiKey || !currentCredentials.eoaAddress) {
@@ -122887,11 +122933,20 @@ async function applyStartSessionUnlock(passphrase, resetRequested) {
122887
122933
  persistVault: persistVault2,
122888
122934
  wipeVault: wipeVault2,
122889
122935
  sdk,
122890
- fetchWallets: () => fetchVirtualWalletsFromBackend(currentCredentials.apiKey),
122936
+ // FORCED REFRESH. `start_session` verifies the session's grant against the
122937
+ // backend (see `runStartSession`), and a verification run against the cache
122938
+ // this process loaded at boot verifies nothing: a grant approved since boot
122939
+ // would be invisible to exactly the check that exists to find it.
122940
+ fetchWallets: () => fetchVirtualWalletsFromBackend(currentCredentials.apiKey, true),
122891
122941
  readPolicyActiveOnchain: buildPolicyActiveOnchainReader(sdk)
122892
122942
  }
122893
122943
  );
122894
- const { walletChanged } = mergeStartSessionCredentials(currentCredentials, result.credentials, result.responseKey);
122944
+ const { walletChanged } = mergeStartSessionCredentials(
122945
+ currentCredentials,
122946
+ result.credentials,
122947
+ result.responseKey,
122948
+ { grantChanged: result.grantChanged }
122949
+ );
122895
122950
  refreshPlaintextVaultMode();
122896
122951
  if (walletChanged) {
122897
122952
  walletsCache = null;
@@ -123390,10 +123445,9 @@ server.tool(
123390
123445
  fetchVirtualWalletsFromBackend(currentCredentials.apiKey, !refreshedFromBackend),
123391
123446
  fetchPoliciesFromBackend(currentCredentials.apiKey, true)
123392
123447
  ]);
123393
- const freshWallet = freshWallets.find(
123394
- (w) => w.address?.toLowerCase() === currentCredentials.eoaAddress?.toLowerCase()
123395
- );
123396
- if (freshWallet && freshWallet.status === "approved") {
123448
+ const freshWallet = selectNewestApprovedWallet(freshWallets, currentCredentials.eoaAddress);
123449
+ const freshWalletIsActiveGrant = String(freshWallet?.id) === String(currentCredentials.virtualWalletId);
123450
+ if (freshWallet && freshWalletIsActiveGrant) {
123397
123451
  if (freshWallet.policyAssociated != null) {
123398
123452
  currentCredentials.policyId = String(freshWallet.policyAssociated);
123399
123453
  }
@@ -123414,6 +123468,14 @@ server.tool(
123414
123468
  eoaAddress: currentCredentials.eoaAddress || null,
123415
123469
  walletAddress: currentCredentials.walletAddress || null,
123416
123470
  kernelClientActive: !!currentCredentials.virtualWalletKernelAccountClient,
123471
+ // WHICH GRANT THIS SESSION SIGNS WITH. The 2026-09-01 incident — three
123472
+ // approved rows for one EOA, every session silently signing with the oldest
123473
+ // — was invisible in every payload: diagnosing it took a nonce-key autopsy.
123474
+ // These two ids are the row `start_session` actually selected, so the next
123475
+ // stale grant is a field to read rather than a forensic exercise. Null until
123476
+ // a session has adopted one.
123477
+ activeVirtualWalletId: currentCredentials.virtualWalletId != null ? String(currentCredentials.virtualWalletId) : null,
123478
+ activePolicyId: currentCredentials.policyId != null ? String(currentCredentials.policyId) : null,
123417
123479
  // WHICH WORKSPACE THIS IS. The 2026-08-31 incident produced writes into
123418
123480
  // the wrong workspace with nothing in any payload naming one — an agent
123419
123481
  // reading wallet status could not have told, and neither could a human
@@ -123427,7 +123489,7 @@ server.tool(
123427
123489
  }
123428
123490
  }
123429
123491
  if (walletsCache && currentCredentials.eoaAddress) {
123430
- const wallet = walletsCache.find(
123492
+ const wallet = selectNewestApprovedWallet(walletsCache, currentCredentials.eoaAddress) ?? walletsCache.find(
123431
123493
  (w) => w.address?.toLowerCase() === currentCredentials.eoaAddress?.toLowerCase()
123432
123494
  );
123433
123495
  if (wallet) {
@@ -125792,9 +125854,7 @@ async function reconcileApprovalStateOnConnect() {
125792
125854
  }
125793
125855
  try {
125794
125856
  const wallets = await fetchVirtualWalletsFromBackend(currentCredentials.apiKey, true);
125795
- const approved = wallets.find(
125796
- (w) => w.address?.toLowerCase() === currentCredentials.eoaAddress?.toLowerCase() && w.status === "approved"
125797
- );
125857
+ const approved = selectNewestApprovedWallet(wallets, currentCredentials.eoaAddress);
125798
125858
  if (!approved) return;
125799
125859
  if (typeof approved.id === "number" && hasProcessedApprovalEventId(approved.id)) return;
125800
125860
  console.error(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ametyst/cli",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "private": false,
5
5
  "description": "Ametyst CLI — embedded MCP server, wallet ops, agent payments",
6
6
  "type": "module",
@@ -90,10 +90,10 @@
90
90
  "esbuild@>=0.27.3 <0.28.1": "0.28.1"
91
91
  },
92
92
  "optionalDependencies": {
93
- "@ametyst/cli-darwin-arm64": "0.3.4",
94
- "@ametyst/cli-darwin-x64": "0.3.4",
95
- "@ametyst/cli-linux-x64-gnu": "0.3.4",
96
- "@ametyst/cli-win32-x64-msvc": "0.3.4"
93
+ "@ametyst/cli-darwin-arm64": "0.3.5",
94
+ "@ametyst/cli-darwin-x64": "0.3.5",
95
+ "@ametyst/cli-linux-x64-gnu": "0.3.5",
96
+ "@ametyst/cli-win32-x64-msvc": "0.3.5"
97
97
  },
98
98
  "scripts": {
99
99
  "build:native": "cd native && cargo build --release && napi build --release",