@run402/sdk 4.126.1 → 4.126.3

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 (41) hide show
  1. package/dist/kernel.d.ts +9 -0
  2. package/dist/kernel.d.ts.map +1 -1
  3. package/dist/kernel.js +13 -4
  4. package/dist/kernel.js.map +1 -1
  5. package/dist/namespaces/deploy.d.ts.map +1 -1
  6. package/dist/namespaces/deploy.js +2 -0
  7. package/dist/namespaces/deploy.js.map +1 -1
  8. package/dist/namespaces/deploy.types.d.ts +43 -0
  9. package/dist/namespaces/deploy.types.d.ts.map +1 -1
  10. package/dist/namespaces/deploy.types.js +32 -0
  11. package/dist/namespaces/deploy.types.js.map +1 -1
  12. package/dist/namespaces/events.d.ts +3 -1
  13. package/dist/namespaces/events.d.ts.map +1 -1
  14. package/dist/namespaces/events.js +4 -2
  15. package/dist/namespaces/events.js.map +1 -1
  16. package/dist/namespaces/projects.d.ts +5 -1
  17. package/dist/namespaces/projects.d.ts.map +1 -1
  18. package/dist/namespaces/projects.js +116 -24
  19. package/dist/namespaces/projects.js.map +1 -1
  20. package/dist/namespaces/push.d.ts +3 -1
  21. package/dist/namespaces/push.d.ts.map +1 -1
  22. package/dist/namespaces/push.js +5 -1
  23. package/dist/namespaces/push.js.map +1 -1
  24. package/dist/namespaces/rooms.d.ts.map +1 -1
  25. package/dist/namespaces/rooms.js +106 -124
  26. package/dist/namespaces/rooms.js.map +1 -1
  27. package/dist/namespaces/snapshots.d.ts +6 -0
  28. package/dist/namespaces/snapshots.d.ts.map +1 -1
  29. package/dist/namespaces/snapshots.js +9 -0
  30. package/dist/namespaces/snapshots.js.map +1 -1
  31. package/dist/node/actions-node.d.ts.map +1 -1
  32. package/dist/node/actions-node.js +159 -56
  33. package/dist/node/actions-node.js.map +1 -1
  34. package/dist/node/workflow-output.d.ts.map +1 -1
  35. package/dist/node/workflow-output.js +27 -3
  36. package/dist/node/workflow-output.js.map +1 -1
  37. package/dist/project-id.d.ts +31 -0
  38. package/dist/project-id.d.ts.map +1 -0
  39. package/dist/project-id.js +53 -0
  40. package/dist/project-id.js.map +1 -0
  41. package/package.json +1 -1
@@ -128,7 +128,7 @@ export class NodeActions {
128
128
  }
129
129
  const tier = input.tier ?? DEFAULT_BOOTSTRAP_TIER;
130
130
  if (run.autoPrerequisites) {
131
- await this.#ensureCloudTier(run, tier);
131
+ await this.#ensureCloudTier(run, tier, { explicitTier: input.tier !== undefined });
132
132
  }
133
133
  const idempotencyKey = input.idempotencyKey ?? run.rootIdempotencyKey;
134
134
  const step = run.addStep({
@@ -280,7 +280,7 @@ export class NodeActions {
280
280
  });
281
281
  }
282
282
  else if (this.#targetKind() !== "core") {
283
- await this.#ensureCloudTier(run, input.tier ?? DEFAULT_BOOTSTRAP_TIER);
283
+ await this.#ensureCloudTier(run, input.tier ?? DEFAULT_BOOTSTRAP_TIER, { explicitTier: input.tier !== undefined });
284
284
  }
285
285
  else {
286
286
  run.skipStep({
@@ -1122,7 +1122,7 @@ export class NodeActions {
1122
1122
  app_result: blocked,
1123
1123
  });
1124
1124
  }
1125
- await this.#ensureCloudTier(run, input.tier ?? DEFAULT_BOOTSTRAP_TIER);
1125
+ await this.#ensureCloudTier(run, input.tier ?? DEFAULT_BOOTSTRAP_TIER, { explicitTier: input.tier !== undefined });
1126
1126
  const resolved = await this.#resolveProject(input, manifest, workspaceDir, run);
1127
1127
  await this.#recordAppInstallState({
1128
1128
  projectId: resolved.projectId,
@@ -1720,8 +1720,15 @@ export class NodeActions {
1720
1720
  propagationWaitMs,
1721
1721
  };
1722
1722
  }
1723
+ /**
1724
+ * Ensure a local wallet exists and, with `fund`, that it holds testnet USDC.
1725
+ * Returns `funded` when this call dripped the faucet, and `planned` when a
1726
+ * dry run planned a wallet that does not exist yet (so nothing about its
1727
+ * organization can be read).
1728
+ */
1723
1729
  async #ensureWallet(run, opts = { fund: false }) {
1724
1730
  const status = await this.sdk.wallets.status();
1731
+ let balanceUnknown = false;
1725
1732
  if (status.configured) {
1726
1733
  run.skipStep({
1727
1734
  action: "wallets.create",
@@ -1731,8 +1738,19 @@ export class NodeActions {
1731
1738
  details: { address: status.address, path: status.path ?? null },
1732
1739
  });
1733
1740
  if (!opts.fund)
1734
- return false;
1735
- const balance = await prototypeBalance(status.address);
1741
+ return { funded: false, planned: false };
1742
+ let balance;
1743
+ try {
1744
+ balance = await prototypeBalance(status.address);
1745
+ }
1746
+ catch (err) {
1747
+ // A dry run only describes; an unreadable balance plans the faucet
1748
+ // instead of failing the plan. Execution still refuses to guess.
1749
+ if (!run.dryRun)
1750
+ throw err;
1751
+ balance = 0n;
1752
+ balanceUnknown = true;
1753
+ }
1736
1754
  if (balance > 0n) {
1737
1755
  run.skipStep({
1738
1756
  action: "wallets.faucet",
@@ -1741,7 +1759,7 @@ export class NodeActions {
1741
1759
  auto: true,
1742
1760
  details: { address: status.address, balance_usd_micros: balance.toString() },
1743
1761
  });
1744
- return false;
1762
+ return { funded: false, planned: false };
1745
1763
  }
1746
1764
  }
1747
1765
  else {
@@ -1754,7 +1772,7 @@ export class NodeActions {
1754
1772
  await run.approve(createStep, ["wallets.create"], "Create a local wallet.");
1755
1773
  if (run.dryRun) {
1756
1774
  run.setState(createStep, "planned");
1757
- return false;
1775
+ return { funded: false, planned: true };
1758
1776
  }
1759
1777
  run.setState(createStep, "running");
1760
1778
  const created = await this.sdk.wallets.create();
@@ -1763,7 +1781,7 @@ export class NodeActions {
1763
1781
  path: created.path ?? null,
1764
1782
  });
1765
1783
  if (!opts.fund)
1766
- return false;
1784
+ return { funded: false, planned: false };
1767
1785
  }
1768
1786
  const faucetKey = run.childKey("wallets.faucet");
1769
1787
  const faucetStep = run.addStep({
@@ -1771,12 +1789,16 @@ export class NodeActions {
1771
1789
  description: "Request testnet faucet funds for wallet",
1772
1790
  mutation: true,
1773
1791
  auto: true,
1774
- details: { idempotency_key: faucetKey ?? null },
1792
+ details: {
1793
+ idempotency_key: faucetKey ?? null,
1794
+ ...(opts.reason ? { reason: opts.reason } : {}),
1795
+ ...(balanceUnknown ? { balance: "unknown" } : {}),
1796
+ },
1775
1797
  });
1776
1798
  await run.approve(faucetStep, ["wallets.faucet"], "Request testnet USDC for the local wallet.");
1777
1799
  if (run.dryRun) {
1778
1800
  run.setState(faucetStep, "planned");
1779
- return false;
1801
+ return { funded: false, planned: false };
1780
1802
  }
1781
1803
  run.setState(faucetStep, "running");
1782
1804
  const faucet = await this.sdk.wallets.faucet(faucetKey ? { idempotencyKey: faucetKey } : {});
@@ -1785,75 +1807,134 @@ export class NodeActions {
1785
1807
  amount: faucet.amount,
1786
1808
  network: faucet.network,
1787
1809
  });
1788
- return true;
1810
+ return { funded: true, planned: false };
1789
1811
  }
1790
- async #ensureCloudTier(run, desiredTier) {
1791
- await this.#ensureWallet(run, { fund: false });
1792
- if (run.dryRun) {
1793
- const faucetStep = run.addStep({
1794
- action: "wallets.faucet",
1795
- description: "Request testnet faucet funds if tier payment is needed",
1796
- mutation: true,
1797
- auto: true,
1798
- });
1799
- await run.approve(faucetStep, ["wallets.faucet"], "Request testnet USDC if tier bootstrap needs payment.");
1800
- run.setState(faucetStep, "planned");
1801
- const step = run.addStep({
1802
- action: Run402Action.TierSet,
1803
- description: `Ensure active ${desiredTier} tier`,
1804
- mutation: true,
1805
- auto: true,
1806
- details: {
1807
- tier: desiredTier,
1808
- idempotency_key: run.childKey("tier.set") ?? null,
1809
- reason: "dry_run",
1810
- },
1812
+ /**
1813
+ * Bootstrap readiness: make sure the caller's organization holds a tier at
1814
+ * least `desiredTier`. A dry run resolves this against the real organization
1815
+ * exactly as execution does (one read of `GET /tiers/v1/status`), so a plan
1816
+ * never lists a purchase that execution would skip. Bootstrap never buys a
1817
+ * tier lower than the organization's current one unless the caller named
1818
+ * that tier explicitly (`explicitTier`).
1819
+ */
1820
+ async #ensureCloudTier(run, desiredTier, opts = {}) {
1821
+ const wallet = await this.#ensureWallet(run, { fund: false });
1822
+ const idempotencyKey = run.childKey("tier.set");
1823
+ if (wallet.planned) {
1824
+ // Dry run, no local wallet yet: a new wallet has no organization and no
1825
+ // tier, so bootstrap would fund it and start the tier.
1826
+ const reason = "new wallet has no tier";
1827
+ await this.#planFaucet(run, reason);
1828
+ await this.#planTierPurchase(run, desiredTier, {
1829
+ description: `Start the ${desiredTier} tier`,
1830
+ details: { tier: desiredTier, current_tier: null, active: false, reason, idempotency_key: idempotencyKey ?? null },
1811
1831
  });
1812
- await run.approve(step, ["tier.set"], `Set the ${desiredTier} tier if no active tier exists.`);
1813
- run.setState(step, "planned");
1814
1832
  return;
1815
1833
  }
1816
- let status;
1834
+ let status = null;
1835
+ let statusError = null;
1817
1836
  try {
1818
1837
  status = await this.sdk.tier.status();
1819
1838
  }
1820
- catch {
1821
- status = null;
1839
+ catch (err) {
1840
+ statusError = err;
1841
+ }
1842
+ if (!status) {
1843
+ const error = statusError instanceof Error ? statusError.message : String(statusError);
1844
+ if (run.dryRun) {
1845
+ const reason = "tier status unavailable";
1846
+ await this.#planFaucet(run, reason);
1847
+ await this.#planTierPurchase(run, desiredTier, {
1848
+ description: `Set the ${desiredTier} tier if the organization has no tier at least ${desiredTier} (current tier unknown)`,
1849
+ details: { tier: desiredTier, current_tier: "unknown", reason, error, idempotency_key: idempotencyKey ?? null },
1850
+ });
1851
+ return;
1852
+ }
1853
+ // Paying blind could replace a higher tier the organization already holds.
1854
+ throw run.error(`Could not read the organization's tier (GET /tiers/v1/status failed: ${error}). Bootstrap does not buy a tier without knowing the current one.`, "RUN402_TIER_STATUS_UNAVAILABLE", {
1855
+ desired_tier: desiredTier,
1856
+ error,
1857
+ next_actions: [{
1858
+ type: "check_usage",
1859
+ command: "run402 tier status",
1860
+ argv: ["run402", "tier", "status"],
1861
+ why: "Confirm the organization's current tier, then retry.",
1862
+ }],
1863
+ });
1822
1864
  }
1823
- const current = normalizeTier(status?.tier);
1824
- if (status?.active && current && TIER_RANK[current] >= TIER_RANK[desiredTier]) {
1865
+ const current = normalizeTier(status.tier);
1866
+ const facts = {
1867
+ current_tier: current ?? status.tier ?? null,
1868
+ desired_tier: desiredTier,
1869
+ active: status.active,
1870
+ lease_perpetual: status.lease_perpetual ?? null,
1871
+ lease_expires_at: status.lease_expires_at ?? null,
1872
+ };
1873
+ if (status.active && current && TIER_RANK[current] >= TIER_RANK[desiredTier]) {
1874
+ const reason = `org has active ${current} tier`;
1825
1875
  run.skipStep({
1826
- action: Run402Action.TierSet,
1827
- description: "Active tier already satisfies bootstrap requirement",
1876
+ action: "wallets.faucet",
1877
+ description: `Faucet not needed: ${reason}`,
1828
1878
  mutation: false,
1829
1879
  auto: true,
1830
- details: { current_tier: current, desired_tier: desiredTier },
1880
+ details: { reason },
1831
1881
  });
1832
- return;
1833
- }
1834
- if (status?.active && current && TIER_RANK[current] > TIER_RANK[desiredTier]) {
1835
1882
  run.skipStep({
1836
1883
  action: Run402Action.TierSet,
1837
- description: "Existing active tier is higher than bootstrap default",
1884
+ description: current === desiredTier
1885
+ ? `Active ${current} tier already in place`
1886
+ : `Active ${current} tier already satisfies the ${desiredTier} requirement`,
1838
1887
  mutation: false,
1839
1888
  auto: true,
1840
- details: { current_tier: current, desired_tier: desiredTier },
1889
+ details: { reason, ...facts },
1841
1890
  });
1842
1891
  return;
1843
1892
  }
1844
- const idempotencyKey = run.childKey("tier.set");
1845
- const justFunded = await this.#ensureWallet(run, { fund: true });
1893
+ if (current && TIER_RANK[current] > TIER_RANK[desiredTier] && !opts.explicitTier) {
1894
+ // The organization's higher tier has lapsed. Buying the default lower
1895
+ // tier would replace it; that takes an explicit choice.
1896
+ const retryVerb = run.input.type === Run402Action.ProjectsProvision
1897
+ ? ["run402", "projects", "provision"]
1898
+ : ["run402", "up"];
1899
+ throw run.error(`The organization's ${current} tier is not active. Bootstrap will not replace it with the lower default ${desiredTier} tier. Renew ${current}, or pass the tier explicitly to accept ${desiredTier}.`, "RUN402_TIER_DOWNGRADE_REFUSED", {
1900
+ ...facts,
1901
+ next_actions: [
1902
+ {
1903
+ type: "renew_tier",
1904
+ command: `run402 tier set ${current}`,
1905
+ argv: ["run402", "tier", "set", current],
1906
+ why: `Renew the organization's ${current} tier, then retry.`,
1907
+ },
1908
+ {
1909
+ type: "retry",
1910
+ command: `${retryVerb.join(" ")} --tier ${desiredTier}`,
1911
+ argv: [...retryVerb, "--tier", desiredTier],
1912
+ why: `Accept the lower ${desiredTier} tier explicitly.`,
1913
+ },
1914
+ ],
1915
+ });
1916
+ }
1917
+ const description = !current
1918
+ ? `Start the ${desiredTier} tier`
1919
+ : current === desiredTier
1920
+ ? `Renew the ${desiredTier} tier (current lease is not active)`
1921
+ : TIER_RANK[current] < TIER_RANK[desiredTier]
1922
+ ? `Upgrade tier from ${current} to ${desiredTier}`
1923
+ : `Set the ${desiredTier} tier as requested (current ${current} tier is not active)`;
1924
+ const reason = !current ? "org has no tier" : status.active ? `org has active ${current} tier` : `org's ${current} tier is not active`;
1925
+ const details = { tier: desiredTier, ...facts, reason, idempotency_key: idempotencyKey ?? null };
1926
+ if (run.dryRun) {
1927
+ await this.#ensureWallet(run, { fund: true, reason: "tier payment may be needed" });
1928
+ await this.#planTierPurchase(run, desiredTier, { description, details });
1929
+ return;
1930
+ }
1931
+ const { funded: justFunded } = await this.#ensureWallet(run, { fund: true });
1846
1932
  const step = run.addStep({
1847
1933
  action: Run402Action.TierSet,
1848
- description: `Ensure active ${desiredTier} tier`,
1934
+ description,
1849
1935
  mutation: true,
1850
1936
  auto: true,
1851
- details: {
1852
- tier: desiredTier,
1853
- current_tier: current ?? null,
1854
- active: status?.active ?? null,
1855
- idempotency_key: idempotencyKey ?? null,
1856
- },
1937
+ details,
1857
1938
  });
1858
1939
  await run.approve(step, ["tier.set"], `Set the ${desiredTier} tier (start, renew, or upgrade).`);
1859
1940
  run.setState(step, "running");
@@ -1861,6 +1942,28 @@ export class NodeActions {
1861
1942
  const result = await (justFunded ? afterFaucet(pay) : pay());
1862
1943
  run.setState(step, "succeeded", { tier: result.tier, action: result.action });
1863
1944
  }
1945
+ async #planFaucet(run, reason) {
1946
+ const step = run.addStep({
1947
+ action: "wallets.faucet",
1948
+ description: "Request testnet faucet funds if tier payment is needed",
1949
+ mutation: true,
1950
+ auto: true,
1951
+ details: { reason },
1952
+ });
1953
+ await run.approve(step, ["wallets.faucet"], "Request testnet USDC if tier bootstrap needs payment.");
1954
+ run.setState(step, "planned");
1955
+ }
1956
+ async #planTierPurchase(run, desiredTier, plan) {
1957
+ const step = run.addStep({
1958
+ action: Run402Action.TierSet,
1959
+ description: plan.description,
1960
+ mutation: true,
1961
+ auto: true,
1962
+ details: plan.details,
1963
+ });
1964
+ await run.approve(step, ["tier.set"], `Set the ${desiredTier} tier (start, renew, or upgrade).`);
1965
+ run.setState(step, "planned");
1966
+ }
1864
1967
  async #resolveProject(input, manifest, workspaceDir, run) {
1865
1968
  const linkPath = join(workspaceDir, ".run402", "project.json");
1866
1969
  const link = await readWorkspaceProjectLink(linkPath);