@openagentsinc/pylon 0.2.4 → 0.2.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.
package/README.md CHANGED
@@ -17,6 +17,8 @@ npx @openagentsinc/pylon --version 0.1.16
17
17
  npx @openagentsinc/pylon --no-launch
18
18
  npx @openagentsinc/pylon --no-updates
19
19
  npx @openagentsinc/pylon status --json
20
+ npx @openagentsinc/pylon --register-openagents --no-launch --json
21
+ npx @openagentsinc/pylon --register-openagents --setup-mdk-wallet --no-launch --json
20
22
  npx @openagentsinc/pylon --download-curated-cache --model gemma-4-e2b --run-diagnostics
21
23
  npx @openagentsinc/pylon --verbose
22
24
  ```
@@ -64,6 +66,11 @@ The launcher:
64
66
  unless `--no-launch` is set
65
67
  - forwards CLI subcommands such as `pylon status --json` to the installed
66
68
  `pylon` binary after bootstrap instead of opening `pylon-tui`
69
+ - can register the local Pylon with OpenAgents and send a public-safe
70
+ heartbeat when `--register-openagents` is explicitly set
71
+ - can initialize or reuse a local MoneyDevKit agent wallet, generate local
72
+ receive readiness, and report only redacted wallet/payout-target refs when
73
+ `--setup-mdk-wallet` is explicitly set
67
74
  - on native Windows x86_64, installs `pylon.exe` and `pylon-tui.exe` from the
68
75
  Windows `.zip` release asset and forwards CLI subcommands to `pylon.exe`
69
76
  - while the TUI is running on the default release track, checks GitHub Releases
@@ -87,9 +94,79 @@ The launcher:
87
94
  advertise homework-worker capability. Launcher `0.1.17` adds CLI subcommand
88
95
  forwarding and bounds background GitHub release checks to avoid
89
96
  unauthenticated rate-limit churn.
90
- - does not try to install or register a local runtime automatically; the
91
- bootstrap stays honest about the separate local Gemma runtime
92
- prerequisite instead of mutating the host behind the user's back
97
+ - does not register with OpenAgents by default; registration is an explicit
98
+ token-scoped operator action
99
+
100
+ ## OpenAgents registration
101
+
102
+ Use this only with an active OpenAgents programmatic agent token. Prefer an
103
+ environment variable so the token is not stored in shell history:
104
+
105
+ ```bash
106
+ export OPENAGENTS_AGENT_TOKEN="oa_agent_..."
107
+
108
+ npx @openagentsinc/pylon@latest \
109
+ --register-openagents \
110
+ --openagents-api https://openagents.com \
111
+ --resource-mode background_20 \
112
+ --no-launch \
113
+ --json
114
+ ```
115
+
116
+ The launcher runs the normal first-run smoke, derives or reuses a stable public
117
+ Pylon ref from the local Pylon identity, registers through
118
+ `POST /api/pylons/register`, and sends a heartbeat through
119
+ `POST /api/pylons/{pylonRef}/heartbeat`.
120
+
121
+ Optional public-safe registration flags:
122
+
123
+ ```bash
124
+ --pylon-ref pylon.example.local
125
+ --pylon-display-name "Example Pylon"
126
+ --capability-ref capability.public.gpu
127
+ ```
128
+
129
+ The registration payload deliberately excludes local paths, wallet material,
130
+ private model inventory, hostnames, SSH details, provider credentials, and raw
131
+ payment data.
132
+
133
+ ## MDK wallet readiness
134
+
135
+ After registration, a Pylon can report wallet receive readiness and request
136
+ payout-target admission:
137
+
138
+ ```bash
139
+ export OPENAGENTS_AGENT_TOKEN="oa_agent_..."
140
+
141
+ npx @openagentsinc/pylon@latest \
142
+ --register-openagents \
143
+ --setup-mdk-wallet \
144
+ --openagents-api https://openagents.com \
145
+ --resource-mode background_20 \
146
+ --no-launch \
147
+ --json
148
+ ```
149
+
150
+ Optional test/operator isolation flags:
151
+
152
+ ```bash
153
+ --mdk-wallet-home /path/to/ignored/mdk-home
154
+ --mdk-wallet-port 3457
155
+ --mdk-receive-amount-sats 21
156
+ ```
157
+
158
+ The launcher uses `npx @moneydevkit/agent-wallet@latest` locally. It may run
159
+ `init --show`, `init`, `balance`, and `receive`. It submits only:
160
+
161
+ - `wallet.public.mdk_agent_wallet.<digest>`;
162
+ - `receive.redacted.mdk_agent_wallet.<digest>`;
163
+ - `payout_target.public.mdk_agent_wallet.<digest>`;
164
+ - bucketed readiness refs such as
165
+ `balance.mdk_agent_wallet.minimum_not_satisfied`.
166
+
167
+ It does not submit the mnemonic, wallet config, raw receive invoice, payment
168
+ hash, preimage, exact wallet balance, wallet home path, or private destination
169
+ material to OpenAgents.
93
170
 
94
171
  Set `OPENAGENTS_DISABLE_TELEMETRY=1` to disable installer telemetry, or
95
172
  `OPENAGENTS_TELEMETRY_URL=http://127.0.0.1:8000/api/telemetry/events` to point
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openagentsinc/pylon",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Bootstrap the standalone OpenAgents Pylon release asset and run first-run smoke checks.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -2,6 +2,7 @@ import {
2
2
  DEFAULT_DIAGNOSTIC_MAX_OUTPUT_TOKENS,
3
3
  DEFAULT_DIAGNOSTIC_REPEATS,
4
4
  DEFAULT_MODEL_ID,
5
+ DEFAULT_OPENAGENTS_API_BASE,
5
6
  DEFAULT_RELEASE_API_BASE,
6
7
  DEFAULT_RELEASE_REPO,
7
8
  bootstrapInstalledPylon,
@@ -120,6 +121,28 @@ Options:
120
121
  --verbose Print extra network and recovery detail.
121
122
  --debug-network Alias for --verbose.
122
123
  --json Emit a machine-readable JSON summary.
124
+ --register-openagents Register and heartbeat this Pylon with
125
+ OpenAgents after local first-run smoke.
126
+ Requires OPENAGENTS_AGENT_TOKEN or
127
+ --openagents-agent-token.
128
+ --openagents-api <url> OpenAgents API base for registration.
129
+ Default: ${DEFAULT_OPENAGENTS_API_BASE}
130
+ --openagents-agent-token <token> Agent bearer token for registration.
131
+ Prefer the environment variable.
132
+ --pylon-ref <ref> Explicit public-safe Pylon ref.
133
+ --pylon-display-name <name> Public-safe Pylon display name.
134
+ --resource-mode <mode> Public resource mode for registration.
135
+ Default: background_20
136
+ --capability-ref <ref> Add a public-safe capability ref.
137
+ May be repeated or comma-separated.
138
+ --setup-mdk-wallet Initialize or reuse the local MDK agent
139
+ wallet, create receive readiness, and
140
+ report redacted wallet/payout refs.
141
+ --mdk-wallet-home <path> HOME override for isolated MDK wallet
142
+ config during tests or operator smokes.
143
+ --mdk-wallet-port <port> MDK agent-wallet daemon port override.
144
+ --mdk-receive-amount-sats <amount> Tiny receive amount for readiness.
145
+ Default: 1 satoshi of bitcoin.
123
146
 
124
147
  Test and maintainer options:
125
148
  --repo <owner/name> Override the GitHub release repo.
@@ -147,6 +170,17 @@ export function parseArgs(argv) {
147
170
  json: false,
148
171
  help: false,
149
172
  pylonArgs: [],
173
+ openAgentsRegister: false,
174
+ openAgentsApiBase: DEFAULT_OPENAGENTS_API_BASE,
175
+ openAgentsAgentToken: null,
176
+ openAgentsPylonRef: null,
177
+ openAgentsPylonDisplayName: null,
178
+ openAgentsResourceMode: "background_20",
179
+ openAgentsCapabilityRefs: [],
180
+ openAgentsSetupMdkWallet: false,
181
+ openAgentsMdkWalletHome: null,
182
+ openAgentsMdkWalletPort: null,
183
+ openAgentsMdkReceiveAmountSats: 1,
150
184
  };
151
185
 
152
186
  for (let index = 0; index < argv.length; index += 1) {
@@ -227,6 +261,73 @@ export function parseArgs(argv) {
227
261
  case "--json":
228
262
  options.json = true;
229
263
  break;
264
+ case "--register-openagents":
265
+ options.openAgentsRegister = true;
266
+ break;
267
+ case "--openagents-api":
268
+ options.openAgentsApiBase = argv[++index];
269
+ if (!options.openAgentsApiBase) {
270
+ throw new Error("--openagents-api requires a value.");
271
+ }
272
+ break;
273
+ case "--openagents-agent-token":
274
+ options.openAgentsAgentToken = argv[++index];
275
+ if (!options.openAgentsAgentToken) {
276
+ throw new Error("--openagents-agent-token requires a value.");
277
+ }
278
+ break;
279
+ case "--pylon-ref":
280
+ options.openAgentsPylonRef = argv[++index];
281
+ if (!options.openAgentsPylonRef) {
282
+ throw new Error("--pylon-ref requires a value.");
283
+ }
284
+ break;
285
+ case "--pylon-display-name":
286
+ options.openAgentsPylonDisplayName = argv[++index];
287
+ if (!options.openAgentsPylonDisplayName) {
288
+ throw new Error("--pylon-display-name requires a value.");
289
+ }
290
+ break;
291
+ case "--resource-mode":
292
+ options.openAgentsResourceMode = argv[++index];
293
+ if (!options.openAgentsResourceMode) {
294
+ throw new Error("--resource-mode requires a value.");
295
+ }
296
+ break;
297
+ case "--capability-ref": {
298
+ const value = argv[++index];
299
+ if (!value) {
300
+ throw new Error("--capability-ref requires a value.");
301
+ }
302
+ options.openAgentsCapabilityRefs.push(
303
+ ...value
304
+ .split(",")
305
+ .map((entry) => entry.trim())
306
+ .filter(Boolean),
307
+ );
308
+ break;
309
+ }
310
+ case "--setup-mdk-wallet":
311
+ options.openAgentsSetupMdkWallet = true;
312
+ break;
313
+ case "--mdk-wallet-home":
314
+ options.openAgentsMdkWalletHome = argv[++index];
315
+ if (!options.openAgentsMdkWalletHome) {
316
+ throw new Error("--mdk-wallet-home requires a value.");
317
+ }
318
+ break;
319
+ case "--mdk-wallet-port":
320
+ options.openAgentsMdkWalletPort = parseIntegerFlag(
321
+ argv[++index],
322
+ "--mdk-wallet-port",
323
+ );
324
+ break;
325
+ case "--mdk-receive-amount-sats":
326
+ options.openAgentsMdkReceiveAmountSats = parseIntegerFlag(
327
+ argv[++index],
328
+ "--mdk-receive-amount-sats",
329
+ );
330
+ break;
230
331
  case "--repo":
231
332
  options.repo = argv[++index];
232
333
  if (!options.repo) {
package/src/index.js CHANGED
@@ -15,6 +15,7 @@ export { createTelemetryClient } from "./telemetry.js";
15
15
  export const DEFAULT_RELEASE_REPO = "OpenAgentsInc/openagents";
16
16
  export const DEFAULT_RELEASE_API_BASE = "https://api.github.com";
17
17
  export const DEFAULT_RELEASE_GIT_BASE = "https://github.com";
18
+ export const DEFAULT_OPENAGENTS_API_BASE = "https://openagents.com";
18
19
  export const DEFAULT_RUSTUP_INIT_URL = "https://sh.rustup.rs";
19
20
  export const DEFAULT_MODEL_ID = "gemma-4-e4b";
20
21
  export const DEFAULT_DIAGNOSTIC_REPEATS = 3;
@@ -26,6 +27,12 @@ const PYLON_RELEASE_TAG_PREFIX = "pylon-v";
26
27
  const RELEASE_ASSET_INSTALL_METHOD = "release_asset";
27
28
  const SOURCE_BUILD_INSTALL_METHOD = "source_build";
28
29
  const PREFERRED_RUNTIME_MODEL_NAME = "gemma4:e4b";
30
+ const DEFAULT_PYLON_RESOURCE_MODE = "background_20";
31
+ const DEFAULT_MDK_RECEIVE_AMOUNT_SATS = 1;
32
+ const DEFAULT_PYLON_CAPABILITY_REFS = [
33
+ "capability.public.pylon_launcher",
34
+ "capability.public.inference",
35
+ ];
29
36
  const LEGACY_SOURCE_BUILD_SIBLING_REPOSITORIES = {
30
37
  "spark-sdk": "https://github.com/AtlantisPleb/spark-sdk.git",
31
38
  };
@@ -1549,6 +1556,407 @@ function isUnsupportedGemmaDiagnoseError(error) {
1549
1556
  );
1550
1557
  }
1551
1558
 
1559
+ function publicHash(value) {
1560
+ return createHash("sha256").update(String(value)).digest("hex").slice(0, 16);
1561
+ }
1562
+
1563
+ function firstString(...values) {
1564
+ for (const value of values) {
1565
+ if (typeof value === "string" && value.trim() !== "") {
1566
+ return value.trim();
1567
+ }
1568
+ }
1569
+ return null;
1570
+ }
1571
+
1572
+ function normalizePublicPylonRef(value) {
1573
+ const normalized = String(value ?? "")
1574
+ .trim()
1575
+ .toLowerCase()
1576
+ .replace(/[^a-z0-9_.:-]+/g, "-")
1577
+ .replace(/^[^a-z0-9]+/, "")
1578
+ .slice(0, 120);
1579
+
1580
+ if (!normalized || normalized.length < 3) {
1581
+ return null;
1582
+ }
1583
+
1584
+ return normalized;
1585
+ }
1586
+
1587
+ export function resolveOpenAgentsPylonRef({
1588
+ explicitPylonRef = null,
1589
+ init = null,
1590
+ status = null,
1591
+ configPath = null,
1592
+ target = null,
1593
+ } = {}) {
1594
+ const explicit = normalizePublicPylonRef(explicitPylonRef);
1595
+ if (explicit) {
1596
+ return explicit;
1597
+ }
1598
+
1599
+ const source = firstString(
1600
+ init?.pylon_ref,
1601
+ init?.pylonRef,
1602
+ init?.identity_ref,
1603
+ init?.identityRef,
1604
+ init?.node_id,
1605
+ init?.nodeId,
1606
+ init?.public_key,
1607
+ init?.publicKey,
1608
+ init?.identity?.pylon_ref,
1609
+ init?.identity?.node_id,
1610
+ init?.identity?.public_key,
1611
+ status?.pylon_ref,
1612
+ status?.pylonRef,
1613
+ status?.node_id,
1614
+ status?.nodeId,
1615
+ status?.snapshot?.identity?.pylon_ref,
1616
+ status?.snapshot?.identity?.node_id,
1617
+ status?.snapshot?.identity?.public_key,
1618
+ configPath,
1619
+ );
1620
+
1621
+ if (!source) {
1622
+ return null;
1623
+ }
1624
+
1625
+ const platform = target?.os && target?.arch ? `${target.os}.${target.arch}` : "local";
1626
+ return `pylon.${platform}.${publicHash(source)}`.slice(0, 120);
1627
+ }
1628
+
1629
+ function statusLabelFromPylonStatus(status) {
1630
+ const authoritative = firstString(
1631
+ status?.snapshot?.runtime?.authoritative_status,
1632
+ status?.snapshot?.runtime?.status,
1633
+ status?.runtime?.authoritative_status,
1634
+ status?.status,
1635
+ );
1636
+
1637
+ if (!authoritative) {
1638
+ return "not_ready";
1639
+ }
1640
+
1641
+ const normalized = authoritative.toLowerCase();
1642
+ if (normalized.includes("ready") || normalized.includes("online")) {
1643
+ return "online";
1644
+ }
1645
+ if (normalized.includes("degraded")) {
1646
+ return "degraded";
1647
+ }
1648
+ if (normalized.includes("blocked") || normalized.includes("error")) {
1649
+ return "blocked";
1650
+ }
1651
+ return normalized.slice(0, 80);
1652
+ }
1653
+
1654
+ function capabilityRefsFromOptions(options, inventory) {
1655
+ const explicit = Array.isArray(options.openAgentsCapabilityRefs)
1656
+ ? options.openAgentsCapabilityRefs
1657
+ : [];
1658
+ const refs = new Set(
1659
+ [...DEFAULT_PYLON_CAPABILITY_REFS, ...explicit]
1660
+ .map((ref) => String(ref).trim())
1661
+ .filter(Boolean),
1662
+ );
1663
+
1664
+ if (Array.isArray(inventory?.rows) && inventory.rows.length > 0) {
1665
+ refs.add("capability.public.local_inventory_present");
1666
+ }
1667
+
1668
+ return Array.from(refs);
1669
+ }
1670
+
1671
+ async function postOpenAgentsJson({
1672
+ fetchImpl,
1673
+ url,
1674
+ token,
1675
+ idempotencyKey,
1676
+ body,
1677
+ }) {
1678
+ const response = await fetchImpl(url, {
1679
+ method: "POST",
1680
+ headers: {
1681
+ Authorization: `Bearer ${token}`,
1682
+ "Content-Type": "application/json",
1683
+ "Idempotency-Key": idempotencyKey,
1684
+ },
1685
+ body: JSON.stringify(body),
1686
+ });
1687
+ const text = await response.text();
1688
+ const payload = text.trim() ? JSON.parse(text) : null;
1689
+
1690
+ if (!response.ok) {
1691
+ const reason =
1692
+ payload?.reason ??
1693
+ payload?.error ??
1694
+ `${response.status} ${response.statusText}`.trim();
1695
+ throw new Error(`OpenAgents Pylon registration failed: ${reason}`);
1696
+ }
1697
+
1698
+ return payload;
1699
+ }
1700
+
1701
+ export async function registerPylonWithOpenAgents(
1702
+ options,
1703
+ {
1704
+ init,
1705
+ status,
1706
+ inventory,
1707
+ fetchImpl = globalThis.fetch,
1708
+ onStatus = null,
1709
+ } = {},
1710
+ ) {
1711
+ if (!options.openAgentsRegister) {
1712
+ return null;
1713
+ }
1714
+
1715
+ const token =
1716
+ options.openAgentsAgentToken ??
1717
+ process.env.OPENAGENTS_AGENT_TOKEN ??
1718
+ process.env.OPENAGENTS_PYLON_AGENT_TOKEN;
1719
+
1720
+ if (!token) {
1721
+ throw new Error(
1722
+ "OpenAgents registration requires OPENAGENTS_AGENT_TOKEN or --openagents-agent-token.",
1723
+ );
1724
+ }
1725
+
1726
+ if (typeof fetchImpl !== "function") {
1727
+ throw new Error("A fetch implementation is required for OpenAgents registration.");
1728
+ }
1729
+
1730
+ const apiBase = (options.openAgentsApiBase ?? DEFAULT_OPENAGENTS_API_BASE)
1731
+ .replace(/\/+$/, "");
1732
+ const pylonRef = resolveOpenAgentsPylonRef({
1733
+ explicitPylonRef: options.openAgentsPylonRef,
1734
+ init,
1735
+ status,
1736
+ configPath: init?.config_path ?? options.configPath ?? null,
1737
+ target: options.target,
1738
+ });
1739
+
1740
+ if (!pylonRef) {
1741
+ throw new Error(
1742
+ "OpenAgents registration could not derive a stable public Pylon ref; pass --pylon-ref.",
1743
+ );
1744
+ }
1745
+
1746
+ const resourceMode =
1747
+ options.openAgentsResourceMode ?? DEFAULT_PYLON_RESOURCE_MODE;
1748
+ const displayName =
1749
+ options.openAgentsPylonDisplayName ?? `Pylon ${pylonRef.slice(-8)}`;
1750
+ const heartbeatStatus = statusLabelFromPylonStatus(status);
1751
+
1752
+ emitStatus(onStatus, "Registering Pylon with OpenAgents", pylonRef);
1753
+ const registration = await postOpenAgentsJson({
1754
+ fetchImpl,
1755
+ url: `${apiBase}/api/pylons/register`,
1756
+ token,
1757
+ idempotencyKey: `pylon-register-${pylonRef}`,
1758
+ body: {
1759
+ capabilityRefs: capabilityRefsFromOptions(options, inventory),
1760
+ displayName,
1761
+ pylonRef,
1762
+ resourceMode,
1763
+ statusRefs: [
1764
+ `status.public.${heartbeatStatus}`,
1765
+ `release.public.${String(options.tagName ?? `pylon-v${options.version}`).replace(/[^A-Za-z0-9_.:/-]+/g, "_")}`,
1766
+ ],
1767
+ },
1768
+ });
1769
+
1770
+ emitStatus(onStatus, "Sending OpenAgents Pylon heartbeat", heartbeatStatus);
1771
+ const heartbeat = await postOpenAgentsJson({
1772
+ fetchImpl,
1773
+ url: `${apiBase}/api/pylons/${encodeURIComponent(pylonRef)}/heartbeat`,
1774
+ token,
1775
+ idempotencyKey: `pylon-heartbeat-${pylonRef}-${Math.floor(Date.now() / 60_000)}`,
1776
+ body: {
1777
+ capacityRefs: ["capacity.public.launcher_smoke"],
1778
+ healthRefs: [`health.public.${heartbeatStatus}`],
1779
+ resourceMode,
1780
+ status: heartbeatStatus,
1781
+ },
1782
+ });
1783
+
1784
+ return {
1785
+ apiBase,
1786
+ pylonRef,
1787
+ resourceMode,
1788
+ status: heartbeatStatus,
1789
+ registration: {
1790
+ idempotent: Boolean(registration?.idempotent),
1791
+ publicUrl: `${apiBase}/api/pylons/${encodeURIComponent(pylonRef)}`,
1792
+ },
1793
+ heartbeat: {
1794
+ idempotent: Boolean(heartbeat?.idempotent),
1795
+ },
1796
+ };
1797
+ }
1798
+
1799
+ function buildMdkAgentWalletEnv(options = {}) {
1800
+ const env = { ...process.env };
1801
+ if (options.openAgentsMdkWalletHome) {
1802
+ env.HOME = path.resolve(options.openAgentsMdkWalletHome);
1803
+ }
1804
+ if (options.openAgentsMdkWalletPort) {
1805
+ env.MDK_WALLET_PORT = String(options.openAgentsMdkWalletPort);
1806
+ }
1807
+ return env;
1808
+ }
1809
+
1810
+ async function runMdkAgentWalletJson(args, options, runProcessImpl) {
1811
+ const { stdout } = await runProcessImpl(
1812
+ "npx",
1813
+ ["-y", "@moneydevkit/agent-wallet@latest", ...args],
1814
+ { env: buildMdkAgentWalletEnv(options) },
1815
+ );
1816
+ try {
1817
+ return JSON.parse(stdout);
1818
+ } catch (error) {
1819
+ throw new Error(
1820
+ `MDK agent wallet returned invalid JSON for \`agent-wallet ${args.join(" ")}\`: ${
1821
+ error instanceof Error ? error.message : String(error)
1822
+ }`,
1823
+ );
1824
+ }
1825
+ }
1826
+
1827
+ export async function setupMdkWalletReadinessForOpenAgentsPylon(
1828
+ options,
1829
+ {
1830
+ fetchImpl = globalThis.fetch,
1831
+ init = null,
1832
+ status = null,
1833
+ registration = null,
1834
+ runProcessImpl = runProcess,
1835
+ onStatus = null,
1836
+ } = {},
1837
+ ) {
1838
+ if (!options.openAgentsSetupMdkWallet) {
1839
+ return null;
1840
+ }
1841
+
1842
+ const token =
1843
+ options.openAgentsAgentToken ??
1844
+ process.env.OPENAGENTS_AGENT_TOKEN ??
1845
+ process.env.OPENAGENTS_PYLON_AGENT_TOKEN;
1846
+
1847
+ if (!token) {
1848
+ throw new Error(
1849
+ "MDK wallet readiness reporting requires OPENAGENTS_AGENT_TOKEN or --openagents-agent-token.",
1850
+ );
1851
+ }
1852
+
1853
+ const pylonRef =
1854
+ registration?.pylonRef ??
1855
+ resolveOpenAgentsPylonRef({
1856
+ explicitPylonRef: options.openAgentsPylonRef,
1857
+ init,
1858
+ status,
1859
+ configPath: init?.config_path ?? options.configPath ?? null,
1860
+ target: options.target,
1861
+ });
1862
+
1863
+ if (!pylonRef) {
1864
+ throw new Error(
1865
+ "MDK wallet readiness could not derive a stable public Pylon ref; pass --pylon-ref.",
1866
+ );
1867
+ }
1868
+
1869
+ const apiBase = (options.openAgentsApiBase ?? DEFAULT_OPENAGENTS_API_BASE)
1870
+ .replace(/\/+$/, "");
1871
+ const receiveAmount = Number.isFinite(options.openAgentsMdkReceiveAmountSats)
1872
+ ? options.openAgentsMdkReceiveAmountSats
1873
+ : DEFAULT_MDK_RECEIVE_AMOUNT_SATS;
1874
+
1875
+ emitStatus(onStatus, "Preparing MDK agent wallet readiness", pylonRef);
1876
+ let walletInit;
1877
+ try {
1878
+ walletInit = await runMdkAgentWalletJson(["init", "--show"], options, runProcessImpl);
1879
+ } catch {
1880
+ walletInit = await runMdkAgentWalletJson(["init"], options, runProcessImpl);
1881
+ }
1882
+
1883
+ const balance = await runMdkAgentWalletJson(["balance"], options, runProcessImpl);
1884
+ const receive = await runMdkAgentWalletJson(
1885
+ [
1886
+ "receive",
1887
+ String(receiveAmount),
1888
+ "--description",
1889
+ `OpenAgents Pylon readiness ${pylonRef}`,
1890
+ ],
1891
+ options,
1892
+ runProcessImpl,
1893
+ );
1894
+
1895
+ const walletRef = `wallet.public.mdk_agent_wallet.${publicHash(
1896
+ firstString(walletInit?.walletId, walletInit?.wallet_id, pylonRef) ?? pylonRef,
1897
+ )}`;
1898
+ const receiveRef = `receive.redacted.mdk_agent_wallet.${publicHash(
1899
+ firstString(receive?.invoice, receive?.payment_hash, receive?.paymentHash, pylonRef) ?? pylonRef,
1900
+ )}`;
1901
+ const payoutTargetRef = `payout_target.public.mdk_agent_wallet.${publicHash(
1902
+ `${pylonRef}:${receiveRef}`,
1903
+ )}`;
1904
+ const balanceReady =
1905
+ typeof balance?.balance_sats === "number" && balance.balance_sats > 0
1906
+ ? "balance.mdk_agent_wallet.minimum_satisfied"
1907
+ : "balance.mdk_agent_wallet.minimum_not_satisfied";
1908
+
1909
+ emitStatus(onStatus, "Reporting MDK wallet readiness", pylonRef);
1910
+ const walletReadiness = await postOpenAgentsJson({
1911
+ fetchImpl,
1912
+ url: `${apiBase}/api/pylons/${encodeURIComponent(pylonRef)}/wallet-readiness`,
1913
+ token,
1914
+ idempotencyKey: `pylon-wallet-readiness-${pylonRef}-${receiveRef}`,
1915
+ body: {
1916
+ balanceRefs: [balanceReady],
1917
+ liquidityRefs: ["liquidity.public.receive_ready"],
1918
+ readinessRefs: [
1919
+ "readiness.public.mdk_agent_wallet_initialized",
1920
+ "readiness.public.mdk_agent_wallet_receive_ready",
1921
+ receiveRef,
1922
+ ],
1923
+ status: "ready",
1924
+ walletReady: true,
1925
+ walletRef,
1926
+ },
1927
+ });
1928
+
1929
+ emitStatus(onStatus, "Requesting OpenAgents payout-target admission", pylonRef);
1930
+ const payoutTargetAdmission = await postOpenAgentsJson({
1931
+ fetchImpl,
1932
+ url: `${apiBase}/api/pylons/${encodeURIComponent(pylonRef)}/payout-target-admission`,
1933
+ token,
1934
+ idempotencyKey: `pylon-payout-target-${pylonRef}-${payoutTargetRef}`,
1935
+ body: {
1936
+ admissionRefs: ["admission.public.requested", receiveRef],
1937
+ payoutTargetRef,
1938
+ policyRefs: ["policy.public.operator_review_required"],
1939
+ status: "requested",
1940
+ },
1941
+ });
1942
+
1943
+ return {
1944
+ pylonRef,
1945
+ walletReady: true,
1946
+ walletRef,
1947
+ receiveRef,
1948
+ payoutTargetRef,
1949
+ balanceReadinessRef: balanceReady,
1950
+ walletReadiness: {
1951
+ idempotent: Boolean(walletReadiness?.idempotent),
1952
+ },
1953
+ payoutTargetAdmission: {
1954
+ idempotent: Boolean(payoutTargetAdmission?.idempotent),
1955
+ status: "requested",
1956
+ },
1957
+ };
1958
+ }
1959
+
1552
1960
  export async function ensureReleaseInstall(
1553
1961
  options = {},
1554
1962
  {
@@ -1879,6 +2287,7 @@ export async function ensureReleaseInstall(
1879
2287
  export async function bootstrapInstalledPylon(
1880
2288
  options,
1881
2289
  {
2290
+ fetchImpl = globalThis.fetch,
1882
2291
  runProcessImpl = runProcess,
1883
2292
  onStatus = null,
1884
2293
  telemetryClient = null,
@@ -1928,6 +2337,25 @@ export async function bootstrapInstalledPylon(
1928
2337
  runProcessImpl,
1929
2338
  );
1930
2339
 
2340
+ const openAgentsRegistration = await registerPylonWithOpenAgents(options, {
2341
+ fetchImpl,
2342
+ init,
2343
+ inventory,
2344
+ onStatus,
2345
+ status,
2346
+ });
2347
+ const openAgentsMdkWallet = await setupMdkWalletReadinessForOpenAgentsPylon(
2348
+ options,
2349
+ {
2350
+ fetchImpl,
2351
+ init,
2352
+ onStatus,
2353
+ registration: openAgentsRegistration,
2354
+ runProcessImpl,
2355
+ status,
2356
+ },
2357
+ );
2358
+
1931
2359
  let download = null;
1932
2360
  if (!skipModelDownload) {
1933
2361
  emitStatus(onStatus, "Downloading curated model bundle", model);
@@ -2025,6 +2453,8 @@ export async function bootstrapInstalledPylon(
2025
2453
  init,
2026
2454
  status,
2027
2455
  inventory,
2456
+ openAgentsRegistration,
2457
+ openAgentsMdkWallet,
2028
2458
  model,
2029
2459
  download,
2030
2460
  diagnostic,