@bnbagent/studio-cli 0.0.13-alpha.2 → 0.0.13-alpha.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 (25) hide show
  1. package/README.md +34 -6
  2. package/dist/bag.js +1571 -335
  3. package/dist/{chunk-XCKI45TP.js → chunk-E4C75NSV.js} +139 -43
  4. package/dist/{deployCli-HZNZU3CN.js → deployCli-UOUCOIW5.js} +1 -1
  5. package/package.json +4 -3
  6. package/recipes/agent/recipe.toml +1 -1
  7. package/recipes/mpp-buyer/code/{{PKG}}/mppBuyer.ts.tmpl +113 -0
  8. package/recipes/mpp-buyer/recipe.toml +15 -0
  9. package/recipes/runtimes/agentcore/code/{{PKG}}/dualMain.ts.tmpl +13 -12
  10. package/recipes/runtimes/agentcore/code/{{PKG}}/mcpMain.ts.tmpl +10 -9
  11. package/recipes/runtimes/agentcore/code/{{PKG}}/unifiedMain.ts.tmpl +16 -15
  12. package/recipes/runtimes/agentcore/recipe.toml +1 -1
  13. package/recipes/runtimes/azure-foundry/code/{{PKG}}/mcpMain.ts.tmpl +10 -9
  14. package/recipes/runtimes/azure-foundry/code/{{PKG}}/unifiedMain.ts.tmpl +16 -15
  15. package/recipes/runtimes/azure-foundry/recipe.toml +1 -1
  16. package/recipes/x402-buyer/recipe.toml +1 -1
  17. package/skills/bnbagent-studio.md +5 -4
  18. package/skills/references/bnbagent-studio-buying-via-8183.md +2 -2
  19. package/skills/references/bnbagent-studio-buying-via-mpp.md +44 -0
  20. package/skills/references/bnbagent-studio-scaffolding-agent.md +9 -5
  21. package/skills/references/bnbagent-studio-selling-via-8183.md +1 -1
  22. package/skills/references/bnbagent-studio-selling-via-b402.md +1 -1
  23. package/skills/references/bnbagent-studio-use-aws-agentcore.md +1 -1
  24. package/skills/references/bnbagent-studio-use-azure-foundry.md +44 -14
  25. package/skills/references/bnbagent-studio-use-bnb-trial.md +10 -3
package/dist/bag.js CHANGED
@@ -5,12 +5,13 @@ import {
5
5
  B402_RUNTIME_KEYS,
6
6
  CliExit,
7
7
  DEFAULT_B402_PRICE_USD,
8
- DEFAULT_B402_TESTNET_BASE_URL,
9
8
  LEGACY_ENTRY_STEMS,
9
+ MPP_RUNTIME_KEYS,
10
10
  X402_CAPABLE_RUNTIMES,
11
11
  act,
12
12
  agentcoreFlavor,
13
13
  b402Credentials,
14
+ b402PaymentProtocol,
14
15
  bnbEnv,
15
16
  bnbPlatformApiUrl,
16
17
  checkBunx,
@@ -24,7 +25,9 @@ import {
24
25
  entryStemOf,
25
26
  getEnvVar,
26
27
  hasA2aFace,
28
+ hasB402PaymentFace,
27
29
  hasMcpFace,
30
+ hasMppFace,
28
31
  hasX402Face,
29
32
  isB402TestnetBaseUrl,
30
33
  nativeProtocolOf,
@@ -52,7 +55,7 @@ import {
52
55
  x402SellerIsFree,
53
56
  x402SellerPricingState,
54
57
  x402SellerUsesB402
55
- } from "./chunk-XCKI45TP.js";
58
+ } from "./chunk-E4C75NSV.js";
56
59
  import {
57
60
  TWAK_CLI_MIN_VERSION,
58
61
  TWAK_CLI_VERSION,
@@ -230,7 +233,7 @@ import {
230
233
  var CAMPAIGN_DOC_URL = "https://www.bnbchain.org/en/blog/bnb-agent-studio-is-live-on-bnb-chain-ai-agents-from-one-prompt";
231
234
  var CAMPAIGN_CHECK_TIMEOUT_MS = 6e3;
232
235
  async function fetchCampaignActive() {
233
- const { bnbPlatformApiUrl: bnbPlatformApiUrl2 } = await import("./deployCli-HZNZU3CN.js");
236
+ const { bnbPlatformApiUrl: bnbPlatformApiUrl2 } = await import("./deployCli-UOUCOIW5.js");
234
237
  const controller = new AbortController();
235
238
  const timer = setTimeout(() => controller.abort(), CAMPAIGN_CHECK_TIMEOUT_MS);
236
239
  try {
@@ -1862,11 +1865,15 @@ ${result.stderr.trim()}` : ""}`;
1862
1865
  }
1863
1866
  return vendored;
1864
1867
  }
1865
- async function provisionInstall(workspaceRoot) {
1868
+ async function provisionInstall(workspaceRoot, opts = {}) {
1866
1869
  process.stdout.write(
1867
1870
  " \xB7 installing dependencies with `pnpm install` (may take a minute)\u2026\n"
1868
1871
  );
1869
- const result = await runCapture("pnpm", ["install"], {
1872
+ const args = [
1873
+ "install",
1874
+ ...opts.force ? ["--force", "--no-frozen-lockfile"] : []
1875
+ ];
1876
+ const result = await runCapture("pnpm", args, {
1870
1877
  cwd: workspaceRoot,
1871
1878
  timeoutMs: 6e5
1872
1879
  });
@@ -2856,8 +2863,11 @@ async function readUBalanceRaw(address, networkName) {
2856
2863
  if (uToken === void 0) {
2857
2864
  return null;
2858
2865
  }
2866
+ return readTokenBalanceRaw(address, uToken.address, networkName);
2867
+ }
2868
+ async function readTokenBalanceRaw(address, tokenAddress, networkName) {
2859
2869
  const raw = await client(networkName).readContract({
2860
- address: getAddress(uToken.address),
2870
+ address: getAddress(tokenAddress),
2861
2871
  abi: ERC20_BALANCE_ABI,
2862
2872
  functionName: "balanceOf",
2863
2873
  args: [getAddress(address)]
@@ -3086,7 +3096,7 @@ function storageLocalNotDeployableCheck(root) {
3086
3096
  return {
3087
3097
  level: Level.CRITICAL,
3088
3098
  name: "storage_kind_not_deployable",
3089
- message: "[storage].kind = 'local' is not deployable \u2014 a deployed seller needs durable public storage. Switch to 'ipfs' and set STORAGE_API_URL (+ STORAGE_API_KEY) in .studio/.env.local before deploying.",
3099
+ message: "[storage].kind = 'local' is not deployable \u2014 a deployed seller needs durable public storage. Switch to 'ipfs', 's3', or 'azure-blob' and configure its user-owned write credentials before deploying.",
3090
3100
  fixCmd: "bag config set storage.kind ipfs",
3091
3101
  details: { storage_kind: "local" }
3092
3102
  };
@@ -3106,10 +3116,86 @@ function storageIpfsEndpointRequiredCheck(root, opts = {}) {
3106
3116
  details: { storage_kind: "ipfs", missing: ["STORAGE_API_URL"] }
3107
3117
  };
3108
3118
  }
3119
+ function publicBaseIssue(value) {
3120
+ const text2 = String(value ?? "").trim();
3121
+ if (!text2) return null;
3122
+ try {
3123
+ const url = new URL(text2);
3124
+ if (url.protocol !== "https:") return "must use https";
3125
+ if (url.username || url.password) return "must not contain credentials";
3126
+ if (url.search || url.hash) return "must not contain a query or fragment";
3127
+ return null;
3128
+ } catch {
3129
+ return "is not a valid URL";
3130
+ }
3131
+ }
3132
+ function storageObjectProviderRequiredCheck(root) {
3133
+ const cfg = loadAgentToml(root);
3134
+ const storage = tableOf2(cfg, "storage");
3135
+ const kind = String(storage.kind ?? "").trim().toLowerCase();
3136
+ if (kind !== "s3" && kind !== "azure-blob") return null;
3137
+ const credentialMode = String(storage.credential_mode ?? "env").trim().toLowerCase();
3138
+ const missing = [];
3139
+ const invalid = [];
3140
+ if (credentialMode !== "env" && credentialMode !== "ambient") {
3141
+ invalid.push("[storage].credential_mode (expected 'env' or 'ambient')");
3142
+ }
3143
+ const baseIssue = publicBaseIssue(storage.public_base_url);
3144
+ if (baseIssue) invalid.push(`[storage].public_base_url ${baseIssue}`);
3145
+ if (kind === "s3") {
3146
+ for (const key of ["bucket", "region"]) {
3147
+ if (!String(storage[key] ?? "").trim()) missing.push(`[storage].${key}`);
3148
+ }
3149
+ if (credentialMode === "env") {
3150
+ for (const key of [
3151
+ "DELIVERABLE_S3_ACCESS_KEY_ID",
3152
+ "DELIVERABLE_S3_SECRET_ACCESS_KEY"
3153
+ ]) {
3154
+ if (!runtimeEnvResolvable(root, key)) missing.push(key);
3155
+ }
3156
+ }
3157
+ } else {
3158
+ for (const key of ["account_url", "container"]) {
3159
+ if (!String(storage[key] ?? "").trim()) missing.push(`[storage].${key}`);
3160
+ }
3161
+ if (credentialMode === "env") {
3162
+ const hasSas = runtimeEnvResolvable(root, "DELIVERABLE_AZURE_SAS_TOKEN");
3163
+ const spKeys = [
3164
+ "DELIVERABLE_AZURE_TENANT_ID",
3165
+ "DELIVERABLE_AZURE_CLIENT_ID",
3166
+ "DELIVERABLE_AZURE_CLIENT_SECRET"
3167
+ ];
3168
+ const presentSpKeys = spKeys.filter(
3169
+ (key) => runtimeEnvResolvable(root, key)
3170
+ );
3171
+ if (!hasSas && presentSpKeys.length === 0) {
3172
+ missing.push(
3173
+ "DELIVERABLE_AZURE_SAS_TOKEN (or a complete service-principal set)"
3174
+ );
3175
+ } else if (!hasSas) {
3176
+ missing.push(
3177
+ ...spKeys.filter((key) => !runtimeEnvResolvable(root, key))
3178
+ );
3179
+ }
3180
+ }
3181
+ }
3182
+ if (missing.length === 0 && invalid.length === 0) return null;
3183
+ return {
3184
+ level: Level.CRITICAL,
3185
+ name: `storage_${kind.replace("-", "_")}_required`,
3186
+ message: `[storage].kind = '${kind}' is not deploy-ready. ${[
3187
+ missing.length > 0 ? `Missing: ${missing.join(", ")}.` : "",
3188
+ invalid.length > 0 ? `Invalid: ${invalid.join(", ")}.` : ""
3189
+ ].filter(Boolean).join(" ")}`,
3190
+ fixCmd: "# update app/agent/studio.toml and .studio/.env.local",
3191
+ details: { storage_kind: kind, missing, invalid }
3192
+ };
3193
+ }
3109
3194
  function fatalStorageDeployChecks(root) {
3110
3195
  return [
3111
3196
  storageLocalNotDeployableCheck(root),
3112
- storageIpfsEndpointRequiredCheck(root)
3197
+ storageIpfsEndpointRequiredCheck(root),
3198
+ storageObjectProviderRequiredCheck(root)
3113
3199
  ].filter((c2) => c2 !== null);
3114
3200
  }
3115
3201
  function hasIpfsEndpointFinding(result) {
@@ -3466,17 +3552,27 @@ function discoverRecordedDeployments(root) {
3466
3552
  });
3467
3553
  }
3468
3554
  const azureEndpoint = text(azure.agent_endpoint);
3555
+ const azureHttpEndpoint = text(azure.http_endpoint);
3556
+ const azureInvocationsEndpoint = text(azure.invocations_endpoint);
3557
+ const azureA2aEndpoint = text(azure.a2a_endpoint);
3558
+ const azureAgentCardEndpoint = text(azure.agent_card_endpoint);
3469
3559
  const azureDeploymentId = text(azure.deployment_id);
3470
3560
  const azureFaces = protocolFaces(azure.deployed_faces);
3471
3561
  const azureX402 = recordedX402Snapshot(azure);
3472
3562
  const azureX402Carrier = recordedX402Carrier(azure);
3473
- if (azureEndpoint || azureDeploymentId || text(azure.last_deploy_at)) {
3563
+ if (azureEndpoint || azureHttpEndpoint || azureInvocationsEndpoint || azureA2aEndpoint || azureDeploymentId || text(azure.last_deploy_at)) {
3474
3564
  found.push({
3475
3565
  provider: "azure",
3476
3566
  target: PROVIDER_TARGETS.azure,
3477
3567
  name: text(azure.agent_name) ?? projectName2,
3478
3568
  deploymentId: azureDeploymentId,
3479
- endpoint: azureEndpoint,
3569
+ endpoint: azureEndpoint ?? azureA2aEndpoint ?? azureHttpEndpoint,
3570
+ endpoints: {
3571
+ http: azureHttpEndpoint,
3572
+ invocations: azureInvocationsEndpoint,
3573
+ a2a: azureA2aEndpoint,
3574
+ agentCard: azureAgentCardEndpoint
3575
+ },
3480
3576
  ...azureFaces ? { faces: azureFaces } : {},
3481
3577
  ...azureX402 ? { x402: azureX402 } : {},
3482
3578
  ...azureX402Carrier ? { x402Carrier: azureX402Carrier } : {},
@@ -3495,7 +3591,7 @@ function unavailableProvidersForProject(root) {
3495
3591
  const cfg = loadStudioToml6(path12.join(agentRoot2, "studio.toml"));
3496
3592
  runtime = text(tableOf3(cfg, "stack").runtime) ?? "agentcore";
3497
3593
  const faces = stackFaces(tableOf3(cfg, "stack"), tableOf3(cfg, "payments"));
3498
- azureProtocolUnsupported = hasMcpFace(faces) || !hasA2aFace(faces) && !hasX402Face(faces);
3594
+ azureProtocolUnsupported = hasMcpFace(faces) || !hasA2aFace(faces) && !hasB402PaymentFace(faces);
3499
3595
  } catch {
3500
3596
  return {};
3501
3597
  }
@@ -4227,12 +4323,12 @@ function inspectAzureX402Carrier(agentRoot2) {
4227
4323
  };
4228
4324
  }
4229
4325
  const source = fs17.readFileSync(entryFile, "utf8").replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
4230
- const hasSeller = /\bX402Seller\s*\.\s*create\s*\(/m.test(source);
4326
+ const hasSeller = /\b(?:B402|X402)Seller\s*\.\s*create\s*\(/m.test(source);
4231
4327
  const hasCarrier = /\bcreateEnvelopeMiddleware\s*\(/m.test(source);
4232
4328
  if (!hasSeller || !hasCarrier) {
4233
4329
  const missing = [
4234
4330
  ...hasCarrier ? [] : ["createEnvelopeMiddleware"],
4235
- ...hasSeller ? [] : ["X402Seller.create"]
4331
+ ...hasSeller ? [] : ["B402Seller.create"]
4236
4332
  ];
4237
4333
  return {
4238
4334
  status: "incompatible-scaffold",
@@ -4243,7 +4339,7 @@ function inspectAzureX402Carrier(agentRoot2) {
4243
4339
  return {
4244
4340
  status: "http-envelope-v1",
4245
4341
  entrypoint,
4246
- detail: `${entrypoint} mounts the X402 seller and envelope carrier`
4342
+ detail: `${entrypoint} mounts the B402 seller and envelope carrier`
4247
4343
  };
4248
4344
  }
4249
4345
  function tableOf4(cfg, key) {
@@ -4268,6 +4364,26 @@ function loadStudioCfg(workspaceRoot) {
4268
4364
  return {};
4269
4365
  }
4270
4366
  }
4367
+ function foundryA2aSiblingEndpoints(value) {
4368
+ if (!value) return null;
4369
+ let url;
4370
+ try {
4371
+ url = new URL(value);
4372
+ } catch {
4373
+ return null;
4374
+ }
4375
+ const suffix = "/endpoint/protocols/a2a";
4376
+ const path49 = url.pathname.replace(/\/+$/, "");
4377
+ if (!path49.endsWith(suffix)) return null;
4378
+ const root = `${url.origin}${path49.slice(0, -suffix.length)}`;
4379
+ const a2a = `${root}${suffix}`;
4380
+ return {
4381
+ http: `${root}/endpoint/protocols/openai/responses?api-version=v1`,
4382
+ invocations: `${root}/endpoint/protocols/invocations?api-version=v1`,
4383
+ a2a,
4384
+ agentCard: `${a2a}/agentCard/v1.0`
4385
+ };
4386
+ }
4271
4387
  function recordAzureDeployment(root, data, endpoint) {
4272
4388
  const agentRoot2 = findSubProjectRoot7("agent", deployWorkspaceRoot(root));
4273
4389
  if (agentRoot2 === null) return;
@@ -4278,7 +4394,7 @@ function recordAzureDeployment(root, data, endpoint) {
4278
4394
  try {
4279
4395
  const cfg = loadStudioCfg(deployWorkspaceRoot(root));
4280
4396
  deployedFaces = stackFaces(tableOf4(cfg, "stack"), tableOf4(cfg, "payments"));
4281
- if (hasX402Face(deployedFaces)) {
4397
+ if (hasB402PaymentFace(deployedFaces)) {
4282
4398
  deployedX402 = x402DeploymentSnapshot(agentRoot2, "self");
4283
4399
  deployedX402Carrier = inspectAzureX402Carrier(agentRoot2).status;
4284
4400
  }
@@ -4290,9 +4406,87 @@ function recordAzureDeployment(root, data, endpoint) {
4290
4406
  patchTomlKv(tomlPath, "azure", "agent_name", name);
4291
4407
  patchTomlKv(tomlPath, "azure", "deployment_id", name);
4292
4408
  }
4293
- if (endpoint) {
4294
- patchTomlKv(tomlPath, "azure", "agent_endpoint", endpoint);
4295
- patchTomlKv(tomlPath, "identity", "endpoint", endpoint);
4409
+ const endpoints = tableOf4(data, "endpoints");
4410
+ const recordEndpoints = tableOf4(tableOf4(data, "record"), "endpoints");
4411
+ const reportedHttpEndpoint = deployEndpointValue(
4412
+ endpoints.http,
4413
+ recordEndpoints.http,
4414
+ tableOf4(data, "record").invokeUrl,
4415
+ data.invokeUrl
4416
+ );
4417
+ const reportedInvocationsEndpoint = deployEndpointValue(
4418
+ endpoints.invocations,
4419
+ recordEndpoints.invocations
4420
+ );
4421
+ const reportedA2aEndpoint = deployEndpointValue(
4422
+ endpoints.a2a,
4423
+ recordEndpoints.a2a
4424
+ );
4425
+ const reportedAgentCardEndpoint = deployEndpointValue(
4426
+ endpoints.agentCard,
4427
+ recordEndpoints.agentCard
4428
+ );
4429
+ const siblings = foundryA2aSiblingEndpoints(
4430
+ reportedA2aEndpoint ?? endpoint
4431
+ );
4432
+ const httpEndpoint = reportedHttpEndpoint ?? siblings?.http ?? null;
4433
+ const config = loadStudioToml7(tomlPath);
4434
+ const faces = stackFaces(
4435
+ tableOf4(config, "stack"),
4436
+ tableOf4(config, "payments")
4437
+ );
4438
+ const invocationsEndpoint = reportedInvocationsEndpoint ?? (hasB402PaymentFace(faces) ? siblings?.invocations ?? null : null);
4439
+ const a2aEndpoint = reportedA2aEndpoint ?? siblings?.a2a ?? null;
4440
+ const agentCardEndpoint = reportedAgentCardEndpoint ?? siblings?.agentCard ?? null;
4441
+ const primaryEndpoint = a2aEndpoint ?? endpoint ?? httpEndpoint;
4442
+ if (primaryEndpoint) {
4443
+ patchTomlKv(tomlPath, "azure", "agent_endpoint", primaryEndpoint);
4444
+ patchTomlKv(
4445
+ tomlPath,
4446
+ "identity",
4447
+ "endpoint",
4448
+ agentCardEndpoint ?? primaryEndpoint
4449
+ );
4450
+ }
4451
+ if (httpEndpoint)
4452
+ patchTomlKv(tomlPath, "azure", "http_endpoint", httpEndpoint);
4453
+ if (invocationsEndpoint)
4454
+ patchTomlKv(
4455
+ tomlPath,
4456
+ "azure",
4457
+ "invocations_endpoint",
4458
+ invocationsEndpoint
4459
+ );
4460
+ if (a2aEndpoint)
4461
+ patchTomlKv(tomlPath, "azure", "a2a_endpoint", a2aEndpoint);
4462
+ if (agentCardEndpoint)
4463
+ patchTomlKv(tomlPath, "azure", "agent_card_endpoint", agentCardEndpoint);
4464
+ if (deployedFaces) {
4465
+ patchTomlKv(tomlPath, "azure", "deployed_faces", deployedFaces);
4466
+ patchTomlKv(
4467
+ tomlPath,
4468
+ "azure",
4469
+ "deployed_x402_status",
4470
+ deployedX402?.status ?? ""
4471
+ );
4472
+ patchTomlKv(
4473
+ tomlPath,
4474
+ "azure",
4475
+ "deployed_x402_price_mode",
4476
+ deployedX402?.priceMode ?? ""
4477
+ );
4478
+ patchTomlKv(
4479
+ tomlPath,
4480
+ "azure",
4481
+ "deployed_x402_credentials_complete",
4482
+ deployedX402?.credentialsComplete ?? false
4483
+ );
4484
+ patchTomlKv(
4485
+ tomlPath,
4486
+ "azure",
4487
+ "deployed_x402_carrier",
4488
+ deployedX402Carrier ?? ""
4489
+ );
4296
4490
  }
4297
4491
  if (deployedFaces) {
4298
4492
  patchTomlKv(tomlPath, "azure", "deployed_faces", deployedFaces);
@@ -4332,6 +4526,10 @@ function clearAzureEndpoint(root) {
4332
4526
  try {
4333
4527
  for (const key of [
4334
4528
  "agent_endpoint",
4529
+ "http_endpoint",
4530
+ "invocations_endpoint",
4531
+ "a2a_endpoint",
4532
+ "agent_card_endpoint",
4335
4533
  "agent_name",
4336
4534
  "deployment_id",
4337
4535
  "last_deploy_at"
@@ -4419,10 +4617,17 @@ async function deployAzureFoundry(opts) {
4419
4617
  }
4420
4618
  );
4421
4619
  const record = recordOf(data);
4620
+ const endpoints = tableOf4(data, "endpoints");
4621
+ const recordEndpoints = tableOf4(record, "endpoints");
4422
4622
  const endpoint = deployEndpointValue(
4423
- record.invokeUrl,
4623
+ endpoints.a2a,
4624
+ recordEndpoints.a2a,
4424
4625
  record.endpoint,
4425
- data.endpoint
4626
+ data.endpoint,
4627
+ endpoints.http,
4628
+ recordEndpoints.http,
4629
+ record.invokeUrl,
4630
+ data.invokeUrl
4426
4631
  );
4427
4632
  if (code !== 0) {
4428
4633
  if (endpoint) {
@@ -4635,12 +4840,12 @@ var checkEntrypointAndDockerfile = async (root, _target) => {
4635
4840
  const findings = [];
4636
4841
  const cfg = loadStudioCfg(deployWorkspaceRoot(root));
4637
4842
  const faces = stackFaces(tableOf4(cfg, "stack"), tableOf4(cfg, "payments"));
4638
- if (hasMcpFace(faces) || !hasA2aFace(faces) && !hasX402Face(faces)) {
4843
+ if (hasMcpFace(faces) || !hasA2aFace(faces) && !hasB402PaymentFace(faces)) {
4639
4844
  return [
4640
4845
  {
4641
4846
  level: "CRITICAL",
4642
4847
  name: "azure_protocol_supported",
4643
- message: "azure-foundry deploy supports the A2A carrier, including X402-only seller configs. The delegated Foundry version declares the invocations contract (:8088, /readiness, /invocations), not native MCP /mcp. Re-scaffold with --protocols A2A, X402, or A2A,X402; or use AgentCore for MCP.",
4848
+ message: "azure-foundry deploy supports the A2A carrier, including payment-only X402 or MPP seller configs. The delegated Foundry version declares the invocations contract (:8088, /readiness, /invocations), not native MCP /mcp. Re-scaffold with A2A and at most one B402 payment face; or use AgentCore for MCP.",
4644
4849
  details: { faces }
4645
4850
  }
4646
4851
  ];
@@ -4655,13 +4860,13 @@ var checkEntrypointAndDockerfile = async (root, _target) => {
4655
4860
  details: { entrypoint: entry }
4656
4861
  });
4657
4862
  }
4658
- if (hasX402Face(faces)) {
4863
+ if (hasB402PaymentFace(faces)) {
4659
4864
  const carrier = inspectAzureX402Carrier(agentRoot2);
4660
4865
  if (carrier.status !== "http-envelope-v1") {
4661
4866
  findings.push({
4662
4867
  level: "CRITICAL",
4663
4868
  name: "azure_x402_entrypoint_compatible",
4664
- message: `${carrier.detail}. This scaffold cannot serve X402 even though studio.toml enables it. In a clean parent directory, run \`bag init azurex402migration --runtime azure-foundry --protocols ${faces.join(",")} --no-install --no-onboard\`, then review and merge its src/unifiedMain.ts, Dockerfile, and package scripts before redeploying. Studio will not overwrite user-owned agent code.`,
4869
+ message: `${carrier.detail}. This scaffold cannot serve the selected B402 payment protocol even though studio.toml enables it. In a clean parent directory, run \`bag init azureb402migration --runtime azure-foundry --protocols ${faces.join(",")} --no-install --no-onboard\`, then review and merge its src/unifiedMain.ts, Dockerfile, and package scripts before redeploying. Studio will not overwrite user-owned agent code.`,
4665
4870
  details: {
4666
4871
  entrypoint: carrier.entrypoint,
4667
4872
  carrier: carrier.status
@@ -4854,9 +5059,9 @@ function writeEnvLocal(key, value, envPath = ".env.local") {
4854
5059
  return setEnvVar(envPath, key, value);
4855
5060
  }
4856
5061
  function ensureSiweCapable(wallet) {
4857
- const probe = wallet.supports;
4858
- if (typeof probe !== "function") return;
4859
- if (probe.call(wallet, "sign.message")) return;
5062
+ const probe2 = wallet.supports;
5063
+ if (typeof probe2 !== "function") return;
5064
+ if (probe2.call(wallet, "sign.message")) return;
4860
5065
  const kind = wallet.kind;
4861
5066
  throw new PieverseCliError(
4862
5067
  `the ${kind ? `'${kind}' ` : ""}wallet cannot log in to Pieverse: SIWE requires an EIP-191 personal-sign ('sign.message'), which this wallet kind never supports. Pieverse LLM is not usable with this wallet \u2014 set [llm].provider in studio.toml to 'openrouter', 'openai', or 'anthropic' (with the matching API key in .studio/.env.local), or use an 'evm-local' or 'twak' wallet.`
@@ -6650,6 +6855,7 @@ import {
6650
6855
  import { auditedOp as auditedOp2 } from "@bnbagent/studio-runtime/audit";
6651
6856
  import { loadStudioToml as loadStudioToml9 } from "@bnbagent/studio-runtime/config";
6652
6857
  import {
6858
+ fromRaw as fromRaw3,
6653
6859
  getNetwork as getNetwork5,
6654
6860
  tbnbFaucetHint,
6655
6861
  toRaw as toRaw2
@@ -6735,6 +6941,48 @@ function requirePassword() {
6735
6941
  }
6736
6942
  return password;
6737
6943
  }
6944
+ var RELAY_GAS_FLOOR_WEI = 10n ** 16n;
6945
+ function isBareRelayExecutionError(error) {
6946
+ return error instanceof Error && error.message.includes("An error occurred while executing calls") && /Reason: 0x(?![0-9a-fA-F])/.test(error.message);
6947
+ }
6948
+ async function diagnoseRelayError(ctx, error) {
6949
+ if (!isBareRelayExecutionError(error)) return error;
6950
+ let balance;
6951
+ try {
6952
+ balance = await readNativeBalanceRaw(ctx.address, ctx.networkName);
6953
+ } catch {
6954
+ return error;
6955
+ }
6956
+ if (balance >= RELAY_GAS_FLOOR_WEI) return error;
6957
+ const symbol = ctx.networkName === "bsc-testnet" ? "tBNB" : "BNB";
6958
+ const funding = ctx.networkName === "bsc-testnet" ? ` ${tbnbFaucetHint(ctx.address)}` : "";
6959
+ return new Error(
6960
+ `${error.message}
6961
+ Likely cause: admin wallet ${ctx.address} holds ${fromRaw3(balance, 18)} ${symbol} on ${ctx.networkName} \u2014 not enough to repay the relay-fronted gas (the relay recovers its fee from the wallet in the same transaction; an unpayable fee reverts with empty revert data). Fund it to at least 0.01 ${symbol} and retry.${funding}`,
6962
+ { cause: error }
6963
+ );
6964
+ }
6965
+ function withRelayGasDiagnosis(ctx, admin) {
6966
+ const guard = (fn) => async (...args) => {
6967
+ try {
6968
+ return await fn(...args);
6969
+ } catch (error) {
6970
+ throw await diagnoseRelayError(ctx, error);
6971
+ }
6972
+ };
6973
+ return {
6974
+ address: admin.address,
6975
+ grantSession: guard(admin.grantSession.bind(admin)),
6976
+ revokeSession: guard(admin.revokeSession.bind(admin)),
6977
+ approveQuoteSignatureChecker: guard(
6978
+ admin.approveQuoteSignatureChecker.bind(admin)
6979
+ ),
6980
+ approveX402SignatureChecker: guard(
6981
+ admin.approveX402SignatureChecker.bind(admin)
6982
+ ),
6983
+ setPermit2Allowance: guard(admin.setPermit2Allowance.bind(admin))
6984
+ };
6985
+ }
6738
6986
  function buildAdmin(ctx) {
6739
6987
  const admin = adminFactory({
6740
6988
  password: requirePassword(),
@@ -6747,7 +6995,7 @@ function buildAdmin(ctx) {
6747
6995
  `Altana admin address ${admin.address} does not match [wallet].address ${ctx.address}.`
6748
6996
  );
6749
6997
  }
6750
- return admin;
6998
+ return withRelayGasDiagnosis(ctx, admin);
6751
6999
  }
6752
7000
  function parseSessionEnvelope(serialized) {
6753
7001
  let parsed;
@@ -7874,7 +8122,12 @@ var SUPPORTED_PROVIDERS = [
7874
8122
  "pieverse-llm"
7875
8123
  ];
7876
8124
  var SUPPORTED_NETWORKS = ["bsc-testnet", "bsc-mainnet"];
7877
- var SUPPORTED_STORAGE_PROVIDERS = ["ipfs", "local"];
8125
+ var SUPPORTED_STORAGE_PROVIDERS = [
8126
+ "ipfs",
8127
+ "s3",
8128
+ "azure-blob",
8129
+ "local"
8130
+ ];
7878
8131
  var SUPPORTED_DESTINATIONS = ["self", "platform"];
7879
8132
  var RUNTIMES = {
7880
8133
  agentcore: { label: "AWS Bedrock AgentCore", hidden: false },
@@ -8112,7 +8365,7 @@ function registerInit(program) {
8112
8365
  acceptChoices(
8113
8366
  new Option2(
8114
8367
  "--runtime <name>",
8115
- `Runtime / deploy target to scaffold for (choices: ${visibleRuntimes.join(", ")}); recorded in studio.toml [stack].runtime. Use azure-foundry before \`bag deploy --provider azure\`.`
8368
+ `Runtime scaffold (${visibleRuntimes.join(", ")}); Azure: bag deploy --provider azure.`
8116
8369
  ).default("agentcore"),
8117
8370
  SUPPORTED_RUNTIMES,
8118
8371
  visibleRuntimes
@@ -8140,8 +8393,13 @@ function registerInit(program) {
8140
8393
  ).addOption(
8141
8394
  new Option2(
8142
8395
  "--protocols <faces>",
8143
- "Comma-separated public faces to expose; recorded in studio.toml [stack].protocols. Any non-empty combination of A2A, MCP, X402 (case-insensitive; default: A2A,X402)."
8396
+ "Comma-separated public faces to expose; recorded in studio.toml [stack].protocols. Any non-empty combination of A2A, MCP and one B402 payment face (X402 or MPP; default: A2A,X402)."
8144
8397
  )
8398
+ ).addOption(
8399
+ new Option2(
8400
+ "--payment-protocol <protocol>",
8401
+ "B402 HTTP protocol adapter: x402 or mpp (default: x402). The two are alternatives."
8402
+ ).choices(["x402", "mpp"])
8145
8403
  ).addOption(
8146
8404
  new Option2(
8147
8405
  "--protocol <protocol>",
@@ -8157,7 +8415,7 @@ function registerInit(program) {
8157
8415
  "ERC-8183 list price as a non-negative integer string in token base units. Use 0 for FREE. Default: 100000000000000000 (0.1 U)."
8158
8416
  ).option(
8159
8417
  "--b402-price <usd>",
8160
- "B402/x402 per-request price as a non-negative decimal USD string. Use 0 for FREE passthrough (no B402 verify/settle). Default: 0.01."
8418
+ "B402 per-request price for x402 or MPP as a non-negative decimal USD string. Use 0 for FREE passthrough (no B402 verify/settle). Default: 0.01."
8161
8419
  ).addOption(
8162
8420
  new Option2(
8163
8421
  "--llm-provider <provider>",
@@ -8182,7 +8440,7 @@ function registerInit(program) {
8182
8440
  ).addOption(
8183
8441
  new Option2(
8184
8442
  "--storage-provider <kind>",
8185
- "Where job deliverables are stored: 'local' (default; offline dev) or 'ipfs' (durable, public, deploy-ready \u2014 needs STORAGE_API_URL + STORAGE_API_KEY). Written to studio.toml [storage].kind."
8443
+ "Where job deliverables are stored: 'local' (default; offline dev) or durable self-hosted BYOS via 'ipfs', 's3', or 'azure-blob'. Self-hosted S3/Blob need the user's storage config and namespaced runtime credentials; Platform ignores this deploy-time choice and injects scoped access to managed S3 or Azure Blob. Written to studio.toml [storage].kind."
8186
8444
  ).default("local").choices([...SUPPORTED_STORAGE_PROVIDERS])
8187
8445
  ).option(
8188
8446
  "--ipfs-key <key>",
@@ -8303,10 +8561,11 @@ async function cmdInitFlow(nameArg, opts) {
8303
8561
  return 2;
8304
8562
  }
8305
8563
  const facesExplicit = opts.protocols !== void 0 || opts.protocol !== void 0;
8564
+ const paymentFace = opts.paymentProtocol === "mpp" ? "MPP" : "X402";
8306
8565
  let faces;
8307
8566
  try {
8308
8567
  faces = normalizeProtocolFaces(
8309
- opts.protocols ?? opts.protocol ?? "A2A,X402"
8568
+ opts.protocols ?? opts.protocol ?? `A2A,${paymentFace}`
8310
8569
  );
8311
8570
  if (opts.protocol !== void 0 && faces.length !== 1) {
8312
8571
  throw new Error("--protocol is a single-face alias; use --protocols");
@@ -8317,6 +8576,12 @@ async function cmdInitFlow(nameArg, opts) {
8317
8576
  );
8318
8577
  return 2;
8319
8578
  }
8579
+ if (opts.paymentProtocol !== void 0 && facesExplicit && (opts.paymentProtocol === "mpp" && !hasMppFace(faces) || opts.paymentProtocol === "x402" && !hasX402Face(faces))) {
8580
+ printErr(
8581
+ "error: --payment-protocol must match the X402 or MPP face selected by --protocols."
8582
+ );
8583
+ return 2;
8584
+ }
8320
8585
  if (opts.twakHome && opts.walletKind !== "twak") {
8321
8586
  printErr("error: --twak-home is only valid with --wallet-kind twak.");
8322
8587
  return 2;
@@ -8343,20 +8608,20 @@ async function cmdInitFlow(nameArg, opts) {
8343
8608
  if (llmProvider === null) {
8344
8609
  return 2;
8345
8610
  }
8346
- const inferredRails = opts.rails ?? (facesExplicit ? hasX402Face(faces) ? "both" : "8183" : void 0);
8611
+ const inferredRails = opts.rails ?? (facesExplicit ? hasB402PaymentFace(faces) ? "both" : "8183" : void 0);
8347
8612
  let rails = await resolveRails(inferredRails, isTty);
8348
8613
  if (!facesExplicit) {
8349
8614
  faces = normalizeProtocolFaces(
8350
- rails === "8183" ? ["A2A"] : ["A2A", "X402"]
8615
+ rails === "8183" ? ["A2A"] : ["A2A", paymentFace]
8351
8616
  );
8352
8617
  }
8353
- if (hasX402Face(faces) && rails === "8183") {
8618
+ if (hasB402PaymentFace(faces) && rails === "8183") {
8354
8619
  rails = "both";
8355
- } else if (hasX402Face(faces) && rails !== "both") {
8620
+ } else if (hasB402PaymentFace(faces) && rails !== "both") {
8356
8621
  rails = "b402";
8357
8622
  }
8358
- if ((rails === "b402" || rails === "both") && !hasX402Face(faces)) {
8359
- faces = normalizeProtocolFaces([...faces, "X402"]);
8623
+ if ((rails === "b402" || rails === "both") && !hasB402PaymentFace(faces)) {
8624
+ faces = normalizeProtocolFaces([...faces, paymentFace]);
8360
8625
  }
8361
8626
  if (opts.erc8183Price !== void 0 && rails === "b402") {
8362
8627
  printErr(
@@ -8423,9 +8688,9 @@ async function cmdInitFlow(nameArg, opts) {
8423
8688
  destination = "self";
8424
8689
  runtime = "agentcore";
8425
8690
  }
8426
- if (destination === "self" && runtime === "azure-foundry" && (hasMcpFace(faces) || !hasA2aFace(faces) && !hasX402Face(faces))) {
8691
+ if (destination === "self" && runtime === "azure-foundry" && (hasMcpFace(faces) || !hasA2aFace(faces) && !hasB402PaymentFace(faces))) {
8427
8692
  printErr(
8428
- "error: azure-foundry supports the A2A carrier (including X402-only seller configs) and does not support an MCP face. Foundry is delegated through its invocations container contract (:8088, /readiness, /invocations), not the native MCP /mcp transport. Use --protocols A2A, X402, or A2A,X402; or use --runtime agentcore for MCP."
8693
+ "error: azure-foundry supports the A2A carrier (including payment-only seller configs) and does not support an MCP face. Foundry is delegated through its invocations container contract (:8088, /readiness, /invocations), not the native MCP /mcp transport. Use A2A with one X402/MPP payment face, or use --runtime agentcore for MCP."
8429
8694
  );
8430
8695
  return 2;
8431
8696
  }
@@ -8595,24 +8860,27 @@ async function cmdInitFlow(nameArg, opts) {
8595
8860
  );
8596
8861
  }
8597
8862
  if (opts.storageProvider === "local") {
8598
- printLocalStorageWarning();
8863
+ printLocalStorageWarning(destination, runtime);
8599
8864
  }
8600
8865
  if (hasMcpFace(faces) && destination === "platform") {
8601
8866
  printPlatformMcpSyncWarning(faces);
8602
8867
  }
8603
- if (faces.length === 1 && hasX402Face(faces) && destination === "platform") {
8868
+ if (faces.length === 1 && hasB402PaymentFace(faces) && destination === "platform") {
8869
+ const paymentPath = hasMppFace(faces) ? "/mpp" : "/x402";
8604
8870
  printErr(
8605
- "info: X402-only publishes only the /x402 gateway face; the native AgentCore A2A protocol declaration is suppressed from discovery."
8871
+ `info: payment-only publishes only the ${paymentPath} gateway face; the native AgentCore A2A protocol declaration is suppressed from discovery.`
8606
8872
  );
8607
- } else if (faces.length === 1 && hasX402Face(faces) && destination === "self") {
8873
+ } else if (faces.length === 1 && hasB402PaymentFace(faces) && destination === "self") {
8874
+ const paymentName = hasMppFace(faces) ? "MPP" : "X402";
8875
+ const paymentPath = hasMppFace(faces) ? "/mpp" : "/x402";
8608
8876
  const free = x402SellerPricingState({ price_usd: b402Price }).kind === "free";
8609
8877
  if (runtime === "azure-foundry") {
8610
8878
  printErr(
8611
- free ? "warning: X402-only FREE mode on Azure Foundry exposes no public A2A face or anonymous /x402 URL; run an external HTTPS gateway that carries http-envelope-v1 over the Entra-authenticated Invocations endpoint." : "warning: X402-only on Azure Foundry exposes no public A2A face or anonymous /x402 URL; the seller stays dormant until the B402 credentials are filled, then an external HTTPS gateway must carry http-envelope-v1 over the Entra-authenticated Invocations endpoint."
8879
+ free ? `warning: ${paymentName}-only FREE mode on Azure Foundry exposes no public A2A face or anonymous ${paymentPath} URL; run an external HTTPS gateway that carries http-envelope-v1 over the Entra-authenticated Invocations endpoint.` : `warning: ${paymentName}-only on Azure Foundry exposes no public A2A face or anonymous ${paymentPath} URL; the seller stays dormant until its credentials are filled, then an external HTTPS gateway must carry http-envelope-v1 over the Entra-authenticated Invocations endpoint.`
8612
8880
  );
8613
8881
  } else {
8614
8882
  printErr(
8615
- free ? "warning: X402-only FREE mode uses an A2A-native AgentCore process internally, but exposes no public A2A face; a self-hosted AgentCore target still needs your own HTTP front to reach the anonymous /x402 passthrough." : "warning: X402-only uses an A2A-native AgentCore process internally, but exposes no public A2A face; the seller stays dormant until the B402 credentials are filled, and a self-hosted AgentCore target needs your own HTTP front to reach /x402."
8883
+ free ? `warning: ${paymentName}-only FREE mode uses an A2A-native AgentCore process internally, but exposes no public A2A face; a self-hosted target still needs your own HTTP front for ${paymentPath}.` : `warning: ${paymentName}-only uses an A2A-native AgentCore process internally, but exposes no public A2A face; the seller stays dormant until its credentials are filled, and a self-hosted target needs your own HTTP front for ${paymentPath}.`
8616
8884
  );
8617
8885
  }
8618
8886
  }
@@ -8739,7 +9007,7 @@ function scaffold(target, name, o) {
8739
9007
  o.walletKind,
8740
9008
  o.rails,
8741
9009
  o.b402Price,
8742
- o.network
9010
+ hasMppFace(o.faces) ? "mpp" : "x402"
8743
9011
  )
8744
9012
  );
8745
9013
  fs23.writeFileSync(path22.join(agentRoot2, ".gitignore"), renderAgentGitignore());
@@ -8974,6 +9242,12 @@ function renderAgentStorageSection(storageProvider) {
8974
9242
  if (storageProvider === "local") {
8975
9243
  return '[storage]\n# Deliverables written to local disk (file://). Offline-dev only \u2014\n# does NOT survive deploy. Path from STORAGE_LOCAL_PATH.\nkind = "local"\n';
8976
9244
  }
9245
+ if (storageProvider === "s3") {
9246
+ return '[storage]\n# User-owned S3 deliverable storage. Objects are immutable and keyed\n# as deliverables/sha256/<content-hash>.json. Set credentials in\n# .studio/.env.local, never here. For an IAM role, set\n# credential_mode = "ambient". public_base_url is optional.\nkind = "s3"\nbucket = ""\nregion = ""\nkey_prefix = "deliverables"\ncredential_mode = "env"\n# public_base_url = "https://artifacts.customer.com"\n';
9247
+ }
9248
+ if (storageProvider === "azure-blob") {
9249
+ return '[storage]\n# User-owned Azure Blob deliverable storage. Objects are immutable and\n# keyed as deliverables/sha256/<content-hash>.json. Set a write SAS or\n# service-principal credentials in .studio/.env.local, never here. For\n# Managed Identity, set credential_mode = "ambient".\nkind = "azure-blob"\naccount_url = ""\ncontainer = ""\nkey_prefix = "deliverables"\ncredential_mode = "env"\n# public_base_url = "https://artifacts.customer.com"\n';
9250
+ }
8977
9251
  return '[storage]\n# Deliverables pinned to IPFS. upload() returns ipfs://CID, published\n# on-chain verbatim \u2014 durable, public, deploy-ready. Works with any\n# pinning service or self-hosted node: the upload endpoint is\n# STORAGE_API_URL and the write key/JWT is STORAGE_API_KEY, both in\n# .studio/.env.local.\nkind = "ipfs"\n';
8978
9252
  }
8979
9253
  function renderWalletSection(walletKind2, twakHome) {
@@ -9074,10 +9348,11 @@ expected_recipient = ""
9074
9348
  allowed_hosts = ["llm.pieverse.io"]
9075
9349
  `;
9076
9350
  }
9077
- function renderX402SellerSection(priceUsd) {
9351
+ function renderB402SellerSection(priceUsd, protocol) {
9352
+ const path49 = protocol === "mpp" ? "/mpp" : "/x402";
9078
9353
  return `
9079
- [payments.x402_seller]
9080
- # Public seller rail mounted at /x402. Credentials stay in
9354
+ [payments.b402_seller]
9355
+ # Public ${protocol.toUpperCase()} seller rail mounted at ${path49}. Credentials stay in
9081
9356
  # <workspace>/.studio/.env.local and are issued per B402 environment.
9082
9357
  enabled = true
9083
9358
  # Decimal USD string; "0" is explicit FREE passthrough and bypasses B402.
@@ -9089,7 +9364,7 @@ pay_to = ""
9089
9364
  # Payment settles before work; this bounds only the post-settlement work.
9090
9365
  work_timeout_seconds = 60
9091
9366
  # Optional Bazaar metadata can be added later as scalar JSON fields under
9092
- # [payments.x402_seller.bazaar]: info_json, schema_json, description,
9367
+ # [payments.b402_seller.bazaar]: info_json, schema_json, description,
9093
9368
  # and route_template.
9094
9369
  `;
9095
9370
  }
@@ -9133,13 +9408,20 @@ project_name = "${projectName2}"
9133
9408
  # captured at \`bag deploy --provider azure\` (deploys are delegated to bnbagent-deploy:
9134
9409
  # explicit saved browser sign-in or ambient CI credentials; runtime secrets ride as Foundry CustomKeys).
9135
9410
  agent_endpoint = ""
9411
+ http_endpoint = ""
9412
+ invocations_endpoint = ""
9413
+ a2a_endpoint = ""
9414
+ agent_card_endpoint = ""
9136
9415
  `;
9137
9416
  }
9138
9417
  function renderAgentStudioToml(name, o) {
9139
9418
  const model = o.model ?? PROVIDER_DEFAULT_MODEL[o.provider] ?? "";
9140
9419
  const pieverseSection = o.provider === "pieverse-llm" ? renderPieverseSections() : "";
9141
9420
  const x402Section = o.provider === "pieverse-llm" && o.budgetChoice === "enable" ? renderX402Section() : "";
9142
- const x402SellerSection = o.rails === "b402" || o.rails === "both" || hasX402Face(o.faces) ? renderX402SellerSection(o.b402Price) : "";
9421
+ const x402SellerSection = o.rails === "b402" || o.rails === "both" || hasB402PaymentFace(o.faces) ? renderB402SellerSection(
9422
+ o.b402Price,
9423
+ hasMppFace(o.faces) ? "mpp" : "x402"
9424
+ ) : "";
9143
9425
  const budgetSection = renderBudgetSection(o.budgetChoice);
9144
9426
  const storageSection = renderAgentStorageSection(o.storageProvider);
9145
9427
  const azureSection = o.runtime === "azure-foundry" ? renderAzureSection(name, o.azureAccount, o.azureProject) : "";
@@ -9174,9 +9456,9 @@ runtime = "${o.runtime}"
9174
9456
  # MCP \u2014 streamable-HTTP /mcp: negotiate (signed quote) + notify_funded
9175
9457
  # (verify \u2192 deliver \u2192 submit, synchronously within the tool call) +
9176
9458
  # read-only chain tools.
9177
- # X402 \u2014 publish the sibling /x402 seller route. X402-only still declares
9178
- # A2A natively to AgentCore but suppresses that protocol face at the
9179
- # platform gateway.
9459
+ # X402 or MPP \u2014 publish exactly one sibling B402 seller route (/x402 or
9460
+ # /mpp). Payment-only still uses A2A as the cloud carrier but hides
9461
+ # that native face at the public gateway.
9180
9462
  protocols = ${JSON.stringify(o.faces)}
9181
9463
 
9182
9464
  [network]
@@ -9191,7 +9473,7 @@ ${pieverseSection}${erc8183Section}
9191
9473
  ${storageSection}
9192
9474
  ${x402Section}${x402SellerSection}${budgetSection}${azureSection}`;
9193
9475
  }
9194
- function renderAgentEnvLocal(provider, storageProvider, walletKind2, rails, b402Price, network) {
9476
+ function renderAgentEnvLocal(provider, storageProvider, walletKind2, rails, b402Price, paymentProtocol) {
9195
9477
  const keyEnv = PROVIDER_KEY_ENV[provider];
9196
9478
  const lines = [
9197
9479
  "# \u26A0\uFE0F SECRETS \u2014 do NOT paste this file into issue trackers, logs, or AI chats.",
@@ -9255,7 +9537,7 @@ function renderAgentEnvLocal(provider, storageProvider, walletKind2, rails, b402
9255
9537
  "# bedrock uses AWS credentials chain \u2014 no API key env var needed."
9256
9538
  );
9257
9539
  }
9258
- if (storageProvider !== "local") {
9540
+ if (storageProvider === "ipfs") {
9259
9541
  lines.push(
9260
9542
  "",
9261
9543
  "# IPFS upload endpoint \u2014 the Agent pins deliverables at submit. Any",
@@ -9269,10 +9551,29 @@ function renderAgentEnvLocal(provider, storageProvider, walletKind2, rails, b402
9269
9551
  "# Public read gateway (default: https://ipfs.io/ipfs/):",
9270
9552
  "# STORAGE_GATEWAY_URL=https://ipfs.io/ipfs/"
9271
9553
  );
9554
+ } else if (storageProvider === "s3") {
9555
+ lines.push(
9556
+ "",
9557
+ "# User-owned S3 upload credentials. Prefer credential_mode='ambient'",
9558
+ "# on a self-deployed runtime with a scoped IAM role.",
9559
+ "DELIVERABLE_S3_ACCESS_KEY_ID=",
9560
+ "DELIVERABLE_S3_SECRET_ACCESS_KEY=",
9561
+ "# DELIVERABLE_S3_SESSION_TOKEN="
9562
+ );
9563
+ } else if (storageProvider === "azure-blob") {
9564
+ lines.push(
9565
+ "",
9566
+ "# User-owned Blob upload credential. A write SAS is the simplest path.",
9567
+ "# It is never included in the public deliverable URL.",
9568
+ "DELIVERABLE_AZURE_SAS_TOKEN=",
9569
+ "# Or use a complete service-principal set:",
9570
+ "# DELIVERABLE_AZURE_TENANT_ID=",
9571
+ "# DELIVERABLE_AZURE_CLIENT_ID=",
9572
+ "# DELIVERABLE_AZURE_CLIENT_SECRET="
9573
+ );
9272
9574
  }
9273
9575
  if (rails === "b402" || rails === "both") {
9274
9576
  const free = x402SellerPricingState({ price_usd: b402Price }).kind === "free";
9275
- const baseUrl = !free && network === "bsc-testnet" ? DEFAULT_B402_TESTNET_BASE_URL : "";
9276
9577
  lines.push(
9277
9578
  "",
9278
9579
  "# B402 merchant credentials (issued per environment after manual approval).",
@@ -9281,14 +9582,25 @@ function renderAgentEnvLocal(provider, storageProvider, walletKind2, rails, b402
9281
9582
  "# for that exact wallet and environment. Never reuse merchant credentials",
9282
9583
  "# across wallets.",
9283
9584
  `# Apply: ${B402_DEVELOPER_ACCOUNT_URL}`,
9284
- "# PAID requires all four; all absent = dormant, partial = boot error."
9585
+ "# PAID requires all four; all absent or partial = dormant."
9285
9586
  ],
9286
- `B402_BASE_URL=${baseUrl}`,
9587
+ "B402_BASE_URL=",
9287
9588
  "B402_CLIENT_ID=",
9288
9589
  "B402_ACCESS_TOKEN=",
9289
9590
  "B402_PRIVATE_KEY=",
9290
9591
  "# (alternative one-line PKCS#8 DER: B402_PRIVATE_KEY_B64=)"
9291
9592
  );
9593
+ if (paymentProtocol === "mpp") {
9594
+ lines.push(
9595
+ "",
9596
+ "# MPP challenge authenticity. Generate at least 32 random bytes.",
9597
+ "MPP_SECRET_KEY=",
9598
+ "# Stable public realm/hostname used in MPP challenges.",
9599
+ "MPP_REALM=",
9600
+ "# Production must inject a durable atomic replayStore in runtime code.",
9601
+ "# Managed single-process test sandboxes may set B402_REPLAY_STORE_MODE=memory."
9602
+ );
9603
+ }
9292
9604
  }
9293
9605
  if (rails === "8183" || rails === "both") {
9294
9606
  lines.push(
@@ -9308,8 +9620,9 @@ function renderAgentReadme(name, faces, destination, walletKind2) {
9308
9620
  const mode = recipeModeOf(faces);
9309
9621
  const entry = `src/${entryStemOf(faces)}.ts`;
9310
9622
  const port = String(devPortOf(faces));
9311
- const x402Only = faces.length === 1 && hasX402Face(faces);
9312
- const surface = mode === "both" ? "A2A natively on `0.0.0.0:9000` (agent card + JSON-RPC `message/send`) and buffered streamable-HTTP MCP on `/mcp` through the platform envelope tunnel" : mode === "mcp" ? "streamable-HTTP MCP on `0.0.0.0:8000/mcp` (negotiate + notify_funded sync-delivery + read-only chain tools)" : x402Only ? "only the public `/x402` seller face; AgentCore uses an internal A2A-native process on port 9000 but the gateway suppresses A2A discovery" : "`serveA2a`: the agent card at `/.well-known/agent-card.json` + JSON-RPC `message/send` on `0.0.0.0:9000`";
9623
+ const paymentOnly = faces.length === 1 && hasB402PaymentFace(faces);
9624
+ const paymentPath = hasMppFace(faces) ? "/mpp" : "/x402";
9625
+ const surface = mode === "both" ? "A2A natively on `0.0.0.0:9000` (agent card + JSON-RPC `message/send`) and buffered streamable-HTTP MCP on `/mcp` through the platform envelope tunnel" : mode === "mcp" ? "streamable-HTTP MCP on `0.0.0.0:8000/mcp` (negotiate + notify_funded sync-delivery + read-only chain tools)" : paymentOnly ? `only the public \`${paymentPath}\` seller face; AgentCore uses an internal A2A-native process on port 9000 but the gateway suppresses A2A discovery` : "`serveA2a`: the agent card at `/.well-known/agent-card.json` + JSON-RPC `message/send` on `0.0.0.0:9000`";
9313
9626
  const filesA2a = `- \`src/unifiedMain.ts\` \u2014 unified serving entrypoint (A2A on port 9000 + Foundry invocations on 8088).
9314
9627
  - \`src/executor.ts\` \u2014 the SellerAgentExecutor: the negotiate + notify_funded A2A skills.
9315
9628
  - \`src/agentCard.ts\` \u2014 the discoverable AgentCard (+ OAuth2/Cognito scheme).`;
@@ -9808,7 +10121,7 @@ async function runOnboarding(o) {
9808
10121
  process.env.WALLET_PASSWORD = savedPw;
9809
10122
  }
9810
10123
  }
9811
- if (o.storageProvider !== "local") {
10124
+ if (o.storageProvider === "ipfs") {
9812
10125
  await onboardIpfsKey(envLocalPath20(agentRoot2), o.ipfsKey);
9813
10126
  }
9814
10127
  if (address) {
@@ -9942,7 +10255,7 @@ Guide: the bnbagent-studio-using-twak-wallet.md reference (installed by \`bag sk
9942
10255
  } finally {
9943
10256
  process.chdir(savedCwd);
9944
10257
  }
9945
- if (o.storageProvider !== "local") {
10258
+ if (o.storageProvider === "ipfs") {
9946
10259
  const { envLocalPath: envLocalPath20 } = await import("@bnbagent/studio-runtime/config");
9947
10260
  await onboardIpfsKey(envLocalPath20(agentRoot2), o.ipfsKey);
9948
10261
  }
@@ -10187,15 +10500,46 @@ NOTE \u2014 destination = "platform" (48h TESTNET TRIAL on the BNB Chain managed
10187
10500
  address on mainnet is later soft-warned.`
10188
10501
  );
10189
10502
  }
10190
- function printLocalStorageWarning() {
10503
+ function printLocalStorageWarning(destination, runtime) {
10504
+ if (destination === "platform") {
10505
+ const managedStorage = runtime === "azure-foundry" ? "Azure Blob" : "S3";
10506
+ printErr(
10507
+ `
10508
+ \u26A0 WARNING \u2014 storage.kind = "local" (DEV-ONLY):
10509
+ \xB7 Local runs write deliverables to disk (file://).
10510
+ \xB7 Platform deploy does not use this local path and does not require BYOS:
10511
+ the API injects an agent-scoped upload token and stores deliverables in
10512
+ managed ${managedStorage} behind the bnbagent-api URL.
10513
+ \xB7 Deliverables share the Agent lifecycle; after the Agent is deleted,
10514
+ their API URLs return 404.`
10515
+ );
10516
+ return;
10517
+ }
10518
+ if (runtime === "azure-foundry") {
10519
+ printErr(
10520
+ `
10521
+ \u26A0 WARNING \u2014 storage.kind = "local" (DEV-ONLY):
10522
+ \xB7 Local runs write deliverables to disk (file://).
10523
+ \xB7 Managed Platform deploy (\`bag deploy --provider bnb --backend azure\`)
10524
+ does not use this local path and does not require BYOS: the API injects an
10525
+ agent-scoped upload token and stores deliverables in managed Azure Blob.
10526
+ Deliverables share the Agent lifecycle and their API URLs return 404 after
10527
+ Agent deletion.
10528
+ \xB7 Self-hosted Azure deploy (\`bag deploy --provider azure\`) does require
10529
+ durable BYOS via IPFS, S3, or Azure Blob; its credentials, public URL,
10530
+ cost, and retention remain your responsibility.`
10531
+ );
10532
+ return;
10533
+ }
10191
10534
  printErr(
10192
10535
  `
10193
10536
  \u26A0 WARNING \u2014 storage.kind = "local" (DEV-ONLY):
10194
10537
  \xB7 Deliverables are written to local disk (file://) and served from this
10195
10538
  machine \u2014 works for \`bag dev\`, but does NOT survive \`bag deploy\` (the file
10196
10539
  isn't in the deployed runtime \u2192 the buyer gets a 404).
10197
- \xB7 Before \`bag deploy\`: run bag config set storage.kind ipfs then set
10198
- STORAGE_API_URL + STORAGE_API_KEY in .studio/.env.local.
10540
+ \xB7 Before \`bag deploy\`: configure durable BYOS with IPFS, S3, or Azure Blob.
10541
+ The storage account, public URL/CDN, credentials, cost, and retention remain
10542
+ your responsibility for self-hosted deployment.
10199
10543
  (\`bag deploy prepare\` will re-flag this as CRITICAL until you do.)`
10200
10544
  );
10201
10545
  }
@@ -10223,8 +10567,8 @@ function entrypointHint(faces) {
10223
10567
  if (hasMcpFace(faces)) {
10224
10568
  return "app/agent/src/mcpMain.ts = MCP entrypoint (build your value here)";
10225
10569
  }
10226
- if (faces.length === 1 && hasX402Face(faces)) {
10227
- return "app/agent/src/unifiedMain.ts = X402-only runtime entrypoint (build your value here)";
10570
+ if (faces.length === 1 && hasB402PaymentFace(faces)) {
10571
+ return `app/agent/src/unifiedMain.ts = ${hasMppFace(faces) ? "MPP" : "X402"}-only runtime entrypoint (build your value here)`;
10228
10572
  }
10229
10573
  return "app/agent/src/unifiedMain.ts + executor.ts = A2A entrypoint (build your value here)";
10230
10574
  }
@@ -10523,11 +10867,14 @@ function printConfigSummary(agentRoot2) {
10523
10867
  return cur;
10524
10868
  };
10525
10869
  const shown = (v, unset = "<unset \u2014 fill before going live>") => v === void 0 || v === null || v === "" ? unset : String(v);
10526
- const table = (v) => v !== null && typeof v === "object" && !Array.isArray(v) ? v : {};
10527
- const faces = stackFaces(table(agent.stack), table(agent.payments));
10528
- const payments = table(agent.payments);
10529
- const erc8183 = table(payments.erc8183);
10530
- const x402Seller = table(payments.x402_seller);
10870
+ const table2 = (v) => v !== null && typeof v === "object" && !Array.isArray(v) ? v : {};
10871
+ const faces = stackFaces(table2(agent.stack), table2(agent.payments));
10872
+ const payments = table2(agent.payments);
10873
+ const erc8183 = table2(payments.erc8183);
10874
+ const canonicalSeller = table2(payments.b402_seller);
10875
+ const x402Seller = Object.keys(canonicalSeller).length > 0 ? canonicalSeller : table2(payments.x402_seller);
10876
+ const paymentProtocol = hasMppFace(faces) ? "MPP+B402" : "x402";
10877
+ const paymentPath = hasMppFace(faces) ? "/mpp" : "/x402";
10531
10878
  const hasErc8183 = Object.keys(erc8183).length > 0;
10532
10879
  const hasX402Seller = x402Seller.enabled === true;
10533
10880
  const x402Pricing = x402SellerPricingState(x402Seller);
@@ -10547,8 +10894,10 @@ function printConfigSummary(agentRoot2) {
10547
10894
  const contractOverrides = erc8183ContractOverrideState(
10548
10895
  erc8183ContractEnvForProject(agentRoot2)
10549
10896
  );
10897
+ const destination = shown(g(agent, "deploy", "destination"), "self");
10898
+ const runtime = shown(g(agent, "stack", "runtime"), "?");
10550
10899
  const storageKind2 = shown(g(agent, "storage", "kind"), "?");
10551
- const storageDesc = storageKind2 === "ipfs" ? "IPFS (durable, public)" : "local disk (offline dev only)";
10900
+ const storageDesc = storageKind2 === "ipfs" ? "IPFS (durable, public)" : storageKind2 === "s3" ? "user-owned S3 (durable BYOS)" : storageKind2 === "azure-blob" ? "user-owned Azure Blob (durable BYOS)" : destination === "platform" ? "local disk for dev; Platform injects managed object storage" : runtime === "azure-foundry" ? "local disk for dev; Platform injects Azure Blob; self-hosted needs BYOS" : "local disk (offline dev only)";
10552
10901
  const walletKind2 = shown(g(agent, "wallet", "kind"), "evm-local");
10553
10902
  const walletAddr = g(agent, "wallet", "address");
10554
10903
  const budgetOn = g(agent, "budget", "enabled");
@@ -10560,11 +10909,11 @@ function printConfigSummary(agentRoot2) {
10560
10909
  printOut(bar);
10561
10910
  printOut("");
10562
10911
  printOut(
10563
- onlyX402 && isFreeX402 ? "Per request \u2014 how this FREE x402 seller delivers work:" : onlyX402 ? "Per request \u2014 how this x402 seller earns $U:" : isFreePrice ? "Per task \u2014 how this FREE seller delivers work:" : "Per task \u2014 how this seller earns $U:"
10912
+ onlyX402 && isFreeX402 ? `Per request \u2014 how this FREE ${paymentProtocol} seller delivers work:` : onlyX402 ? `Per request \u2014 how this ${paymentProtocol} seller earns $U:` : isFreePrice ? "Per task \u2014 how this FREE seller delivers work:" : "Per task \u2014 how this seller earns $U:"
10564
10913
  );
10565
10914
  if (onlyX402 && isFreeX402) {
10566
10915
  printOut(
10567
- " anonymous request reaches /x402 \u2192 B402 verify/settle is bypassed \u2192"
10916
+ ` anonymous request reaches ${paymentPath} \u2192 B402 verify/settle is bypassed \u2192`
10568
10917
  );
10569
10918
  printOut(
10570
10919
  " the Agent runs the work and returns the result with no payment."
@@ -10670,11 +11019,11 @@ function printConfigSummary(agentRoot2) {
10670
11019
  }
10671
11020
  if (hasX402Seller) {
10672
11021
  printOut(
10673
- isFreeX402 ? " x402 : FREE \u2014 anonymous passthrough; B402 verify/settle is bypassed" : ` x402 : PAID \u2014 $${shown(x402Seller.price_usd, DEFAULT_B402_PRICE_USD)} per request through B402`
11022
+ isFreeX402 ? ` ${paymentProtocol.padEnd(10)}: FREE \u2014 anonymous passthrough; B402 verify/settle is bypassed` : ` ${paymentProtocol.padEnd(10)}: PAID \u2014 $${shown(x402Seller.price_usd, DEFAULT_B402_PRICE_USD)} per request through B402`
10674
11023
  );
10675
11024
  }
10676
11025
  printOut(
10677
- ` storage : ${storageDesc} (options: local | ipfs \u2014 ipfs required to deploy)`
11026
+ ` storage : ${storageDesc} (options: local | ipfs | s3 | azure-blob)`
10678
11027
  );
10679
11028
  if (budgetOn !== void 0) {
10680
11029
  const bt = budgetOn ? "ENABLED \u2014 wallet may auto-refill within your caps" : "disabled \u2014 wallet is NEVER auto-charged (opt in: `bag budget enable`)";
@@ -10694,14 +11043,21 @@ function printConfigSummary(agentRoot2) {
10694
11043
  );
10695
11044
  }
10696
11045
  if (hasX402Seller && !isFreeX402) {
10697
- todo.push(`PAID /x402 onboarding \u2014 ${B402_PAID_ONBOARDING_GUIDANCE}`);
11046
+ todo.push(
11047
+ `PAID ${paymentPath} onboarding \u2014 ${B402_PAID_ONBOARDING_GUIDANCE}`
11048
+ );
11049
+ if (hasMppFace(faces)) {
11050
+ todo.push(
11051
+ "set MPP_SECRET_KEY / MPP_REALM and inject a durable production replayStore"
11052
+ );
11053
+ }
10698
11054
  todo.push(
10699
11055
  "then set B402_BASE_URL / CLIENT_ID / ACCESS_TOKEN / private key"
10700
11056
  );
10701
11057
  }
10702
- if (storageKind2 === "local") {
11058
+ if (storageKind2 === "local" && destination !== "platform" && runtime !== "azure-foundry") {
10703
11059
  todo.push(
10704
- "[storage].kind \u2014 switch to 'ipfs' + set STORAGE_API_URL/STORAGE_API_KEY before deploy (local disk is NOT deployable)"
11060
+ "[storage].kind \u2014 switch to durable BYOS (ipfs, s3, or azure-blob) before deploy; local disk is NOT deployable"
10705
11061
  );
10706
11062
  }
10707
11063
  if (todo.length > 0) {
@@ -10774,6 +11130,34 @@ function runtimeEnvKeysCore(cfg, resolvable) {
10774
11130
  if (resolvable("STORAGE_API_KEY")) {
10775
11131
  keys.push("STORAGE_API_KEY");
10776
11132
  }
11133
+ } else if (storageKind2 === "s3") {
11134
+ const storage = tableOf8(cfg, "storage");
11135
+ const credentialMode = String(storage.credential_mode ?? "env");
11136
+ if (credentialMode !== "ambient") {
11137
+ keys.push(
11138
+ "DELIVERABLE_S3_ACCESS_KEY_ID",
11139
+ "DELIVERABLE_S3_SECRET_ACCESS_KEY"
11140
+ );
11141
+ if (resolvable("DELIVERABLE_S3_SESSION_TOKEN")) {
11142
+ keys.push("DELIVERABLE_S3_SESSION_TOKEN");
11143
+ }
11144
+ }
11145
+ } else if (storageKind2 === "azure-blob") {
11146
+ const storage = tableOf8(cfg, "storage");
11147
+ const credentialMode = String(storage.credential_mode ?? "env");
11148
+ if (credentialMode !== "ambient") {
11149
+ const servicePrincipalKeys = [
11150
+ "DELIVERABLE_AZURE_TENANT_ID",
11151
+ "DELIVERABLE_AZURE_CLIENT_ID",
11152
+ "DELIVERABLE_AZURE_CLIENT_SECRET"
11153
+ ];
11154
+ const usesServicePrincipal = servicePrincipalKeys.some(resolvable);
11155
+ if (resolvable("DELIVERABLE_AZURE_SAS_TOKEN") || !usesServicePrincipal) {
11156
+ keys.push("DELIVERABLE_AZURE_SAS_TOKEN");
11157
+ } else {
11158
+ keys.push(...servicePrincipalKeys);
11159
+ }
11160
+ }
10777
11161
  }
10778
11162
  if (resolvable("RPC_URL")) {
10779
11163
  keys.push("RPC_URL");
@@ -10792,8 +11176,16 @@ function runtimeEnvKeysCore(cfg, resolvable) {
10792
11176
  }
10793
11177
  }
10794
11178
  if (X402_CAPABLE_RUNTIMES.has(runtimeName) && x402SellerUsesB402(cfg)) {
10795
- for (const key of B402_RUNTIME_KEYS) {
10796
- if (resolvable(key)) keys.push(key);
11179
+ const b402Complete = resolvable("B402_BASE_URL") && resolvable("B402_CLIENT_ID") && resolvable("B402_ACCESS_TOKEN") && (resolvable("B402_PRIVATE_KEY") || resolvable("B402_PRIVATE_KEY_B64"));
11180
+ if (b402Complete) {
11181
+ for (const key of B402_RUNTIME_KEYS) {
11182
+ if (resolvable(key)) keys.push(key);
11183
+ }
11184
+ if (b402PaymentProtocol(cfg) === "mpp") {
11185
+ for (const key of MPP_RUNTIME_KEYS) {
11186
+ if (resolvable(key)) keys.push(key);
11187
+ }
11188
+ }
10797
11189
  }
10798
11190
  }
10799
11191
  return keys;
@@ -11451,12 +11843,12 @@ Switch to Node \u226522 so it wins on PATH, then reopen the shell and retry. Ver
11451
11843
  `bag dev: test with curl / an A2A client sending a {"skill":...} DataPart \u2014 NOT the :8081 chat box (it can't build a seller's structured skills).`
11452
11844
  );
11453
11845
  }
11454
- if (hasX402Face(faces)) {
11455
- const free = x402SellerIsFree(
11456
- loadStudioToml11(path23.join(agentDir, "studio.toml"))
11457
- );
11846
+ if (hasB402PaymentFace(faces)) {
11847
+ const cfg = loadStudioToml11(path23.join(agentDir, "studio.toml"));
11848
+ const free = x402SellerIsFree(cfg);
11849
+ const paymentProtocol = b402PaymentProtocol(cfg);
11458
11850
  printOut(
11459
- free ? `bag dev: x402 seller at http://localhost:${agentPort}/x402 (FREE \u2014 anonymous passthrough; B402 bypassed)` : `bag dev: x402 seller at http://localhost:${agentPort}/x402 (PAID \u2014 active when B402 credentials are configured)`
11851
+ free ? `bag dev: ${paymentProtocol} seller at http://localhost:${agentPort}/${paymentProtocol} (FREE \u2014 anonymous passthrough; B402 bypassed)` : `bag dev: ${paymentProtocol} seller at http://localhost:${agentPort}/${paymentProtocol} (PAID \u2014 active when B402${paymentProtocol === "mpp" ? " and MPP" : ""} credentials are configured)`
11460
11852
  );
11461
11853
  }
11462
11854
  await launch();
@@ -12001,9 +12393,11 @@ function dockerUsesNode22OrNewer(source) {
12001
12393
  }
12002
12394
  function x402SellerRailChecks(agentRoot2, cfg, target) {
12003
12395
  const payments = tableOf2(cfg, "payments");
12004
- const rawSeller = payments.x402_seller;
12396
+ const rawSeller = payments.b402_seller !== void 0 ? payments.b402_seller : payments.x402_seller;
12005
12397
  const sellerPresent = rawSeller !== null && typeof rawSeller === "object" && !Array.isArray(rawSeller);
12006
12398
  const seller = sellerPresent ? rawSeller : {};
12399
+ const paymentProtocol = b402PaymentProtocol(cfg);
12400
+ const configSection = payments.b402_seller !== void 0 ? "payments.b402_seller" : "payments.x402_seller";
12007
12401
  const rails = commerceRails(cfg);
12008
12402
  const credentials = b402Credentials(agentRoot2);
12009
12403
  const out = [];
@@ -12015,14 +12409,14 @@ function x402SellerRailChecks(agentRoot2, cfg, target) {
12015
12409
  out.push({
12016
12410
  level: Level.CRITICAL,
12017
12411
  name: "x402_price_invalid",
12018
- message: '[payments.x402_seller].price_usd must be a non-negative decimal string; "0" is valid and enables the free passthrough.',
12412
+ message: `[${configSection}].price_usd must be a non-negative decimal string; "0" is valid and enables the free passthrough.`,
12019
12413
  details: {}
12020
12414
  });
12021
12415
  } else if (free) {
12022
12416
  out.push({
12023
12417
  level: Level.INFO,
12024
12418
  name: "x402_zero_price_enabled",
12025
- message: "The x402 seller is explicitly FREE. /x402 is anonymous, and B402 verify/settle, token payment, and settlement audit are bypassed.",
12419
+ message: `The ${paymentProtocol.toUpperCase()} seller is explicitly FREE. /${paymentProtocol} is anonymous, and B402 verify/settle, token payment, and settlement audit are bypassed.`,
12026
12420
  details: { price_usd: pricing.priceUsd, facilitator_bypassed: true }
12027
12421
  });
12028
12422
  }
@@ -12031,7 +12425,7 @@ function x402SellerRailChecks(agentRoot2, cfg, target) {
12031
12425
  out.push({
12032
12426
  level: Level.CRITICAL,
12033
12427
  name: "x402_assets_unsupported",
12034
- message: '[payments.x402_seller].assets must be exactly ["U"] in v1.',
12428
+ message: `[${configSection}].assets must be exactly ["U"] in v1.`,
12035
12429
  details: {}
12036
12430
  });
12037
12431
  }
@@ -12040,7 +12434,7 @@ function x402SellerRailChecks(agentRoot2, cfg, target) {
12040
12434
  out.push({
12041
12435
  level: Level.CRITICAL,
12042
12436
  name: "x402_work_timeout_invalid",
12043
- message: "[payments.x402_seller].work_timeout_seconds must be a positive integer.",
12437
+ message: `[${configSection}].work_timeout_seconds must be a positive integer.`,
12044
12438
  details: {}
12045
12439
  });
12046
12440
  }
@@ -12078,6 +12472,35 @@ function x402SellerRailChecks(agentRoot2, cfg, target) {
12078
12472
  details: {}
12079
12473
  });
12080
12474
  }
12475
+ if (paid && paymentProtocol === "mpp") {
12476
+ const envPath = envLocalPath12(agentRoot2);
12477
+ const secretKey = process.env.MPP_SECRET_KEY || getEnvVar(envPath, "MPP_SECRET_KEY");
12478
+ const realm = process.env.MPP_REALM || getEnvVar(envPath, "MPP_REALM");
12479
+ if (!secretKey || Buffer.byteLength(secretKey, "utf8") < 32) {
12480
+ out.push({
12481
+ level: Level.CRITICAL,
12482
+ name: "mpp_secret_key_missing",
12483
+ message: "MPP+B402 paid mode requires MPP_SECRET_KEY with at least 32 UTF-8 bytes.",
12484
+ details: {}
12485
+ });
12486
+ }
12487
+ if (!realm) {
12488
+ out.push({
12489
+ level: Level.CRITICAL,
12490
+ name: "mpp_realm_missing",
12491
+ message: "MPP+B402 paid mode requires a stable MPP_REALM hostname.",
12492
+ details: {}
12493
+ });
12494
+ }
12495
+ if (!target.startsWith("platform")) {
12496
+ out.push({
12497
+ level: Level.WARNING,
12498
+ name: "mpp_replay_store_external",
12499
+ message: "Production MPP+B402 must inject a durable atomic replayStore shared by every runtime instance; Store.memory is development/single-process only.",
12500
+ details: {}
12501
+ });
12502
+ }
12503
+ }
12081
12504
  if ((!rails.x402 || free) && credentials.any) {
12082
12505
  out.push({
12083
12506
  level: Level.WARNING,
@@ -12152,7 +12575,7 @@ async function checkCommerceReady(root, target) {
12152
12575
  out.push({
12153
12576
  level: Level.CRITICAL,
12154
12577
  name: "commerce_no_rail",
12155
- message: "No commerce rail is configured. Enable [payments.erc8183] or [payments.x402_seller] before deploy.",
12578
+ message: "No commerce rail is configured. Enable [payments.erc8183] or [payments.b402_seller] before deploy.",
12156
12579
  details: {}
12157
12580
  });
12158
12581
  }
@@ -13237,8 +13660,8 @@ function agentTable(agentRoot2, key) {
13237
13660
  } catch {
13238
13661
  return {};
13239
13662
  }
13240
- const table = cfg[key];
13241
- return table !== null && typeof table === "object" && !Array.isArray(table) ? table : {};
13663
+ const table2 = cfg[key];
13664
+ return table2 !== null && typeof table2 === "object" && !Array.isArray(table2) ? table2 : {};
13242
13665
  }
13243
13666
  function deployTable(agentRoot2) {
13244
13667
  return agentTable(agentRoot2, "deploy");
@@ -13281,16 +13704,22 @@ function checkPackagingDrift(root, destination, walletKind2, force) {
13281
13704
  return null;
13282
13705
  }
13283
13706
  if (packagingChannel(persisted) !== packagingChannel(derived)) {
13284
- if (force) {
13707
+ const historicalManagedAzureZipLock = stackRuntime(agentRoot2) === "azure-foundry" && packagingChannel(persisted) === "zip" && packagingChannel(derived) === "container";
13708
+ if (historicalManagedAzureZipLock) {
13709
+ printErr(
13710
+ "note: repairing the historical managed-Azure packaging lock from 'zip' to 'container' after this successful deploy."
13711
+ );
13712
+ } else if (force) {
13285
13713
  printErr(
13286
13714
  `warning: packaging lock '${persisted}' differs from the derived '${derived}' (wallet.kind/destination changed); proceeding under --force without re-stamping the lock.`
13287
13715
  );
13288
13716
  return null;
13717
+ } else {
13718
+ printErr(
13719
+ `error: packaging is locked to '${persisted}' but this deploy derives '${derived}' (wallet.kind/destination changed). In-place packaging switch is provisionally disallowed pending live-verify; run 'bag deploy destroy' then redeploy, or pass --force to override.`
13720
+ );
13721
+ return 1;
13289
13722
  }
13290
- printErr(
13291
- `error: packaging is locked to '${persisted}' but this deploy derives '${derived}' (wallet.kind/destination changed). In-place packaging switch is provisionally disallowed pending live-verify; run 'bag deploy destroy' then redeploy, or pass --force to override.`
13292
- );
13293
- return 1;
13294
13723
  }
13295
13724
  const dockerfile = path29.join(agentRoot2, "Dockerfile");
13296
13725
  let dockerfilePresent = false;
@@ -14028,13 +14457,13 @@ var checkPlatformDockerAvailable = async (root, target) => {
14028
14457
  }
14029
14458
  ];
14030
14459
  };
14031
- var checkPlatformStorageEndpoint = async (root, _target) => {
14032
- const check = storageIpfsEndpointRequiredCheck(root, {
14033
- name: "platform_storage_endpoint",
14034
- message: "[storage].kind = 'ipfs' but STORAGE_API_URL is unset \u2014 the deployed seller cannot pin deliverables and funded jobs may get stuck."
14035
- });
14036
- return check ? [check] : [];
14037
- };
14460
+ var checkPlatformStorageEndpoint = async (_root, _target) => [
14461
+ {
14462
+ level: Level.INFO,
14463
+ name: "platform_managed_deliverable_storage",
14464
+ message: "the managed platform injects an agent-scoped deliverable upload token; self-hosted IPFS/S3/Blob credentials are not sent to the platform."
14465
+ }
14466
+ ];
14038
14467
  var checkPlatformMcpSyncTimeout = async (root, _target) => {
14039
14468
  const data = loadAgent(root);
14040
14469
  const faces = stackFaces(tableOf11(data, "stack"), tableOf11(data, "payments"));
@@ -14105,7 +14534,6 @@ var criticalChecks = [
14105
14534
  checkPlatformLoggedIn,
14106
14535
  checkDeployCliRunnable,
14107
14536
  checkPlatformDockerAvailable,
14108
- checkPlatformStorageEndpoint,
14109
14537
  checkTwakCustomContractsUnsupported,
14110
14538
  checkAltanaCustomContractsUnsupported,
14111
14539
  checkLocalKeystoreExists,
@@ -14127,6 +14555,7 @@ var warningChecks = [
14127
14555
  var infoChecks = [
14128
14556
  checkPlatformTrialStatus,
14129
14557
  checkWalletBalanceSummary,
14558
+ checkPlatformStorageEndpoint,
14130
14559
  checkPlatformFundingHint
14131
14560
  ];
14132
14561
 
@@ -14407,15 +14836,15 @@ async function runVerify(opts) {
14407
14836
  exitCode: 1
14408
14837
  });
14409
14838
  }
14410
- const tableOf17 = (key) => {
14839
+ const tableOf18 = (key) => {
14411
14840
  const v = data[key];
14412
14841
  return v !== null && typeof v === "object" && !Array.isArray(v) ? v : {};
14413
14842
  };
14414
- const network = opts.network || String(tableOf17("network").default ?? "bsc-testnet");
14843
+ const network = opts.network || String(tableOf18("network").default ?? "bsc-testnet");
14415
14844
  const protocol = nativeProtocolOf(
14416
- stackFaces(tableOf17("stack"), tableOf17("payments"))
14845
+ stackFaces(tableOf18("stack"), tableOf18("payments"))
14417
14846
  );
14418
- const identity = tableOf17("identity");
14847
+ const identity = tableOf18("identity");
14419
14848
  const existingAgentId = identity.agent_id;
14420
14849
  const accessDescription = accessSummaryForAgentcore(root, { protocol });
14421
14850
  let agentId = null;
@@ -14576,6 +15005,13 @@ var WALLET_MATERIAL_KEYS = [
14576
15005
  TWAK_WALLET_JSON_ENV2,
14577
15006
  ALTANA_SESSION_ENV2
14578
15007
  ];
15008
+ function withoutPlatformByosStorageSecrets(secrets) {
15009
+ return Object.fromEntries(
15010
+ Object.entries(secrets).filter(
15011
+ ([key]) => !key.startsWith("STORAGE_") && !key.startsWith("DELIVERABLE_S3_") && !key.startsWith("DELIVERABLE_AZURE_")
15012
+ )
15013
+ );
15014
+ }
14579
15015
  var RETIRED_AGENT_STATES = /* @__PURE__ */ new Set([
14580
15016
  "deleted",
14581
15017
  "retired",
@@ -14804,6 +15240,7 @@ function buyerAccess(root) {
14804
15240
  let a2aUrl = null;
14805
15241
  let mcpUrl = null;
14806
15242
  let x402Url = null;
15243
+ let mppUrl = null;
14807
15244
  let scope = null;
14808
15245
  if (agentId) {
14809
15246
  const rt = `${base}/v1/rt/${agentId}`;
@@ -14815,7 +15252,10 @@ function buyerAccess(root) {
14815
15252
  mcpUrl = `${rt}/mcp`;
14816
15253
  buyerUrl ??= mcpUrl;
14817
15254
  }
14818
- if (hasX402Face(faces)) {
15255
+ if (hasMppFace(faces)) {
15256
+ mppUrl = `${rt}/mpp`;
15257
+ buyerUrl ??= mppUrl;
15258
+ } else if (hasB402PaymentFace(faces)) {
14819
15259
  x402Url = `${rt}/x402`;
14820
15260
  buyerUrl ??= x402Url;
14821
15261
  }
@@ -14832,7 +15272,8 @@ function buyerAccess(root) {
14832
15272
  buyerUrl,
14833
15273
  a2aUrl,
14834
15274
  mcpUrl,
14835
- x402Url
15275
+ x402Url,
15276
+ mppUrl
14836
15277
  };
14837
15278
  }
14838
15279
  function resolveSlug(root) {
@@ -14874,28 +15315,31 @@ async function platformAgent(opts) {
14874
15315
  `note: deploying to the BNB Chain managed platform (backend=${backend}) \u2014 a 48h TESTNET trial sandbox (network is forced to bsc-testnet; the runtime is auto-reclaimed at 48h). Not a production seller.`
14875
15316
  );
14876
15317
  const platformCmd = opts.runPlatformCommand ?? runPlatformAccountCommand;
14877
- if (backend === "azure") {
14878
- const cfg2 = loadAgentCfg(root);
14879
- const faces2 = stackFaces(tableOf12(cfg2, "stack"), tableOf12(cfg2, "payments"));
14880
- const protocol2 = nativeProtocolOf(faces2);
15318
+ const cfg = loadAgentCfg(root);
15319
+ const faces = stackFaces(tableOf12(cfg, "stack"), tableOf12(cfg, "payments"));
15320
+ const paymentProtocol = b402PaymentProtocol(cfg);
15321
+ const paymentFace = hasB402PaymentFace(faces);
15322
+ if (backend === "azure" || paymentFace && paymentProtocol === "mpp") {
15323
+ const protocol2 = nativeProtocolOf(faces);
14881
15324
  const runtimeProfile = protocol2 === "A2A" ? "foundry-responses-v1" : "foundry-invocations-v1";
14882
15325
  const capability = await platformCmd(["capabilities"], { json: true });
14883
15326
  const platform = isJsonObject(capability.data.platform) ? capability.data.platform : {};
14884
15327
  const backends = isJsonObject(platform.backends) ? platform.backends : {};
14885
- const azure = isJsonObject(backends.azure) ? backends.azure : {};
15328
+ const backendCapability = isJsonObject(backends[backend]) ? backends[backend] : {};
15329
+ const azure = backend === "azure" ? backendCapability : {};
14886
15330
  const profiles = Array.isArray(azure.runtimeProfiles) ? azure.runtimeProfiles.map(String) : [];
14887
15331
  const protocols = Array.isArray(azure.protocols) ? azure.protocols.map(String) : [];
14888
- if (capability.code !== 0 || azure.enabled !== true || !profiles.includes(runtimeProfile) || !protocols.includes(protocol2)) {
15332
+ if (backend === "azure" && (capability.code !== 0 || azure.enabled !== true || !profiles.includes(runtimeProfile) || !protocols.includes(protocol2))) {
14889
15333
  warn(
14890
15334
  `error: the managed platform does not currently accept Azure ${runtimeProfile}/${protocol2}. No build or upload was started.`
14891
15335
  );
14892
15336
  return capability.code || 1;
14893
15337
  }
14894
- if (hasX402Face(faces2) && azure.x402 !== true) {
15338
+ if (paymentFace && (capability.code !== 0 || backendCapability[paymentProtocol] !== true)) {
14895
15339
  warn(
14896
- "error: this platform deployment does not accept x402 publication on the managed Azure backend yet. No build or upload was started. Deploy without the X402 face, or self-host the x402 gateway (`bag deploy --provider azure` + docs/guides/self-hosted-x402-gateway-azure.md)."
15340
+ `error: this platform deployment does not accept ${paymentProtocol} publication on the managed ${backend === "azure" ? "Azure" : "AWS"} backend yet. No build or upload was started. Deploy without the ${paymentProtocol.toUpperCase()} face, or self-host the ${paymentProtocol} gateway.`
14897
15341
  );
14898
- return 1;
15342
+ return capability.code || 1;
14899
15343
  }
14900
15344
  }
14901
15345
  const campaignProbe = opts.campaignActive ?? (async () => null);
@@ -14933,17 +15377,19 @@ async function platformAgent(opts) {
14933
15377
  }
14934
15378
  const packaging = backend === "azure" ? "container" : platformPackaging(root);
14935
15379
  {
14936
- const confirm = opts.confirmPackaging ?? ((r, p) => confirmPackagingDefault(r, p, {
15380
+ const confirm2 = opts.confirmPackaging ?? ((r, p) => confirmPackagingDefault(r, p, {
14937
15381
  acceptRisk: opts.acceptRisk ?? false,
14938
15382
  isTty,
14939
15383
  promptUser: ask2
14940
15384
  }));
14941
- const rc = await confirm(root, packaging);
15385
+ const rc = await confirm2(root, packaging);
14942
15386
  if (rc !== null && rc !== 0) {
14943
15387
  return rc;
14944
15388
  }
14945
15389
  }
14946
- const secrets = await (opts.collectRuntimeSecrets ?? collectRuntimeSecrets)(root);
15390
+ const secrets = withoutPlatformByosStorageSecrets(
15391
+ await (opts.collectRuntimeSecrets ?? collectRuntimeSecrets)(root)
15392
+ );
14947
15393
  walletWarning(root, secrets);
14948
15394
  if (Object.keys(secrets).length > 0) {
14949
15395
  warn(
@@ -14979,8 +15425,6 @@ then re-run the deploy.`
14979
15425
  );
14980
15426
  return 1;
14981
15427
  }
14982
- const cfg = loadAgentCfg(root);
14983
- const faces = stackFaces(tableOf12(cfg, "stack"), tableOf12(cfg, "payments"));
14984
15428
  const protocol = nativeProtocolOf(faces);
14985
15429
  const runDeploy = opts.runDelegatedDeploy ?? (async (o) => withDeployFiles(
14986
15430
  o.root,
@@ -15040,13 +15484,14 @@ then re-run the deploy.`
15040
15484
  });
15041
15485
  const clockStarted = startedNow ? ` \u23F1 your 48h trial clock just started${trial.expiresAt ? ` \u2014 expires ${trial.expiresAt}` : ""}.
15042
15486
  ` : "";
15487
+ const managedStorage = backend === "azure" ? "Azure Blob" : "S3";
15043
15488
  emit(
15044
15489
  `
15045
15490
  \u2713 deployed to the platform (backend=${backend}, slug=${slug}).
15046
15491
  deployment: ${dep.deploymentId}
15047
15492
  invoke URL: ${dep.invokeUrl || "<pending \u2014 see `bag deploy status`>"}
15048
15493
  ${trialLine(trial)}
15049
- ${clockStarted} At trial expiry the agent and its data are deleted (NOT recoverable).
15494
+ ${clockStarted} At trial expiry the runtime, secrets, and private deployment data are deleted (NOT recoverable). The API injects a scoped upload token; deliverables use managed ${managedStorage} and public URLs stay on bnbagent-api. Deliverables share the Agent lifecycle: after Agent deletion their URLs return 404.
15050
15495
  Activity details: ${CAMPAIGN_DOC_URL}
15051
15496
  Register on-chain so buyers can discover it: bag deploy verify --endpoint ${dep.invokeUrl || "<your-invoke-url>"}`
15052
15497
  );
@@ -15156,11 +15601,12 @@ function recordDeployState(root, slug, dep, backend = "aws") {
15156
15601
  }
15157
15602
  }
15158
15603
  const agentRoot2 = findSubProjectRoot15("agent", root);
15159
- if (agentRoot2 !== null && persistedPackaging2(agentRoot2) === null) {
15604
+ const recordedPackaging = backend === "azure" ? "container" : platformPackaging(root);
15605
+ if (agentRoot2 !== null && (persistedPackaging2(agentRoot2) === null || backend === "azure" && persistedPackaging2(agentRoot2) !== "container")) {
15160
15606
  const tomlPath = agentTomlPath2(root);
15161
15607
  if (tomlPath !== null) {
15162
15608
  try {
15163
- patchTomlKv2(tomlPath, "deploy", "packaging", platformPackaging(root));
15609
+ patchTomlKv2(tomlPath, "deploy", "packaging", recordedPackaging);
15164
15610
  } catch {
15165
15611
  }
15166
15612
  }
@@ -15178,8 +15624,12 @@ function recordShippedWallet(root, secrets) {
15178
15624
  function printAccessNextSteps(root, _invokeUrl) {
15179
15625
  const agentRoot2 = findSubProjectRoot15("agent", root) ?? root;
15180
15626
  const access = buyerAccess(agentRoot2);
15181
- const x402Only = access.faces.length === 1 && hasX402Face(access.faces);
15182
- const x402Free = x402SellerIsFree(loadAgentCfg(agentRoot2));
15627
+ const x402Only = access.faces.length === 1 && hasB402PaymentFace(access.faces);
15628
+ const agentCfg = loadAgentCfg(agentRoot2);
15629
+ const paymentProtocol = b402PaymentProtocol(agentCfg);
15630
+ const paymentPath = paymentProtocol === "mpp" ? "/mpp" : "/x402";
15631
+ const paymentUrl = access.mppUrl ?? access.x402Url;
15632
+ const x402Free = x402SellerIsFree(agentCfg);
15183
15633
  const x402RouteLabel = x402Free ? "anonymous FREE route" : "anonymous paid route";
15184
15634
  emit("\nAccess your agent \u2014");
15185
15635
  if (access.agentId) {
@@ -15197,15 +15647,19 @@ function printAccessNextSteps(root, _invokeUrl) {
15197
15647
  " MCP gateway support is rolling out \u2014 the A2A face is the stable face."
15198
15648
  );
15199
15649
  }
15200
- if (hasX402Face(access.faces)) {
15201
- emit(` x402 endpoint: ${access.x402Url} (${x402RouteLabel})`);
15650
+ if (hasB402PaymentFace(access.faces)) {
15651
+ emit(
15652
+ ` ${paymentProtocol} endpoint: ${paymentUrl} (${x402RouteLabel})`
15653
+ );
15202
15654
  }
15203
15655
  if (!x402Only) {
15204
15656
  emit(
15205
15657
  " mint buyer creds: bag platform invoke-client new (prints client_id + client_secret to POST to the client_credentials token endpoint above \u2014 credentials go in the form BODY; HTTP Basic auth is not accepted)"
15206
15658
  );
15207
15659
  } else {
15208
- emit(" protocol discovery is suppressed for this X402-only deployment.");
15660
+ emit(
15661
+ " protocol discovery is suppressed for this payment-only deployment."
15662
+ );
15209
15663
  }
15210
15664
  } else {
15211
15665
  const rtTmpl = `${access.base}/v1/rt/<agentId>`;
@@ -15222,11 +15676,15 @@ function printAccessNextSteps(root, _invokeUrl) {
15222
15676
  " MCP gateway support is rolling out \u2014 the A2A face is the stable face."
15223
15677
  );
15224
15678
  }
15225
- if (hasX402Face(access.faces)) {
15226
- emit(` x402 endpoint: ${rtTmpl}/x402 (${x402RouteLabel})`);
15679
+ if (hasB402PaymentFace(access.faces)) {
15680
+ emit(
15681
+ ` ${paymentProtocol} endpoint: ${rtTmpl}${paymentPath} (${x402RouteLabel})`
15682
+ );
15227
15683
  }
15228
15684
  if (x402Only) {
15229
- emit(" protocol discovery is suppressed for this X402-only deployment.");
15685
+ emit(
15686
+ " protocol discovery is suppressed for this payment-only deployment."
15687
+ );
15230
15688
  } else {
15231
15689
  emit(
15232
15690
  " run `bag platform invoke-client new` to mint buyer creds and capture your agentId."
@@ -15257,10 +15715,13 @@ function printAgentClientPrompt(root, invokeUrl, status) {
15257
15715
  this endpoint does NOT accept HTTP Basic auth (\`curl -u\` fails with oauth.invalid_request).
15258
15716
  3) send \`Authorization: Bearer <token>\`.`;
15259
15717
  const agentCfg = loadAgentCfg(agentRoot2);
15718
+ const paymentProtocol = b402PaymentProtocol(agentCfg);
15719
+ const paymentPath = paymentProtocol === "mpp" ? "/mpp" : "/x402";
15720
+ const paymentUrl = access.mppUrl ?? access.x402Url;
15260
15721
  const rails = commerceRails(agentCfg);
15261
15722
  const x402Free = x402SellerIsFree(agentCfg);
15262
- const x402Only = faces.length === 1 && hasX402Face(faces) || rails.x402 && !rails.erc8183;
15263
- const x402Summary = x402DeploySummary(agentRoot2, "platform", access.x402Url);
15723
+ const x402Only = faces.length === 1 && hasB402PaymentFace(faces) || rails.x402 && !rails.erc8183;
15724
+ const x402Summary = x402DeploySummary(agentRoot2, "platform", paymentUrl);
15264
15725
  const endpoints = [];
15265
15726
  if (hasA2aFace(faces)) {
15266
15727
  endpoints.push(
@@ -15279,7 +15740,7 @@ function printAgentClientPrompt(root, invokeUrl, status) {
15279
15740
  }
15280
15741
  if (x402Only) {
15281
15742
  endpoints.push(
15282
- ` [X402] Anonymous ${x402Free ? "FREE" : "paid"} endpoint: ${access.x402Url ?? `${rt}/x402`}
15743
+ ` [${paymentProtocol.toUpperCase()}] Anonymous ${x402Free ? "FREE" : "paid"} endpoint: ${paymentUrl ?? `${rt}${paymentPath}`}
15283
15744
  Protocol discovery is suppressed.`
15284
15745
  );
15285
15746
  }
@@ -15299,7 +15760,7 @@ function printAgentClientPrompt(root, invokeUrl, status) {
15299
15760
  "\u{1F4CB} COPY EVERYTHING BELOW THIS LINE INTO CLAUDE CODE OR CURSOR",
15300
15761
  " \u2014 it will build and run a client that tests your deployed agent",
15301
15762
  CLIENT_PROMPT_RULE,
15302
- x402Only ? "I deployed a BNB Chain x402 seller agent. Build a small TypeScript (fetch) client that follows the X402 SELLER instructions below." : "I deployed a BNB Chain seller agent. Build a small TypeScript (fetch) client that authenticates, calls the agent, and prints the responses.",
15763
+ x402Only ? `I deployed a BNB Chain ${paymentProtocol.toUpperCase()}+B402 seller agent. Build a small TypeScript (fetch) client that follows the ${paymentProtocol.toUpperCase()} SELLER instructions below.` : "I deployed a BNB Chain seller agent. Build a small TypeScript (fetch) client that authenticates, calls the agent, and prints the responses.",
15303
15764
  "",
15304
15765
  "WHAT WAS DEPLOYED",
15305
15766
  `- Protocol: ${proto}`,
@@ -15314,7 +15775,11 @@ function printAgentClientPrompt(root, invokeUrl, status) {
15314
15775
  ] : [],
15315
15776
  " 4) poll the CHAIN for the job's SUBMITTED state to read the deliverable_url (the agent serves no job-query endpoint)."
15316
15777
  ] : [],
15317
- ...x402Summary ? ["", "X402 SELLER", ...x402Summary.split("\n")] : [],
15778
+ ...x402Summary ? [
15779
+ "",
15780
+ `${paymentProtocol.toUpperCase()} SELLER`,
15781
+ ...x402Summary.split("\n")
15782
+ ] : [],
15318
15783
  "",
15319
15784
  "ENDPOINT",
15320
15785
  endpointBlock,
@@ -15327,14 +15792,14 @@ function printAgentClientPrompt(root, invokeUrl, status) {
15327
15792
  "",
15328
15793
  ...commerceShapeLines
15329
15794
  ] : [
15330
- "- The anonymous x402 route does not use the OAuth2 token flow shown for ERC-8183."
15795
+ `- The anonymous ${paymentProtocol} route does not use the OAuth2 token flow shown for ERC-8183.`
15331
15796
  ],
15332
15797
  ...!x402Only && hasA2aFace(faces) ? [
15333
15798
  '- A2A callers: send the payload as a DATA part, NOT text \u2014 parts:[{"kind":"data","data":{ ...payload above... }}]. The managed gateway adapts this canonical buyer contract to the selected backend.'
15334
15799
  ] : [],
15335
15800
  "",
15336
15801
  "YOUR TASK",
15337
- x402Only ? x402Free ? "Call the anonymous FREE /x402 endpoint directly. Do not create a payment or call the OAuth2 ERC-8183 endpoint." : "Follow the X402 SELLER activation or anonymous paid-request instructions above. Do not call the OAuth2 ERC-8183 endpoint." : "Write and run a client that (1) obtains a token, (2) calls negotiate with a sample task and prints the signed quote, (3) optionally funds a job on-chain (step 2 of the flow above) and calls notify_funded with the resulting job_id. Use the exact endpoint, headers, and message shapes above. If you don't have client_id/client_secret, ask me for them.",
15802
+ x402Only ? x402Free ? `Call the anonymous FREE ${paymentPath} endpoint directly. Do not create a payment or call the OAuth2 ERC-8183 endpoint.` : `Follow the ${paymentProtocol.toUpperCase()} SELLER activation or anonymous paid-request instructions above. Do not call the OAuth2 ERC-8183 endpoint.` : "Write and run a client that (1) obtains a token, (2) calls negotiate with a sample task and prints the signed quote, (3) optionally funds a job on-chain (step 2 of the flow above) and calls notify_funded with the resulting job_id. Use the exact endpoint, headers, and message shapes above. If you don't have client_id/client_secret, ask me for them.",
15338
15803
  CLIENT_PROMPT_RULE
15339
15804
  ];
15340
15805
  const text2 = lines.join("\n");
@@ -15391,6 +15856,7 @@ async function platformInfo(opts) {
15391
15856
  a2a_url: access.a2aUrl,
15392
15857
  mcp_url: access.mcpUrl,
15393
15858
  x402_url: access.x402Url,
15859
+ mpp_url: access.mppUrl,
15394
15860
  token_url: access.tokenUrl,
15395
15861
  scope: access.scope
15396
15862
  },
@@ -15419,10 +15885,13 @@ async function platformInfo(opts) {
15419
15885
  " MCP gateway support is rolling out \u2014 the A2A face is the stable face."
15420
15886
  );
15421
15887
  }
15422
- if (hasX402Face(access.faces)) {
15423
- emit(` ${"x402 endpoint:".padEnd(15)} ${access.x402Url ?? none}`);
15888
+ if (hasB402PaymentFace(access.faces)) {
15889
+ const paymentProtocol = hasMppFace(access.faces) ? "mpp" : "x402";
15890
+ emit(
15891
+ ` ${`${paymentProtocol} endpoint:`.padEnd(15)} ${access.mppUrl ?? access.x402Url ?? none}`
15892
+ );
15424
15893
  }
15425
- if (!(access.faces.length === 1 && hasX402Face(access.faces))) {
15894
+ if (!(access.faces.length === 1 && hasB402PaymentFace(access.faces))) {
15426
15895
  emit(
15427
15896
  ` ${"token endpoint:".padEnd(15)} ${access.tokenUrl} (client_credentials)`
15428
15897
  );
@@ -15450,7 +15919,7 @@ async function platformDestroy(opts) {
15450
15919
  );
15451
15920
  emit(`deployment: ${recorded.deployment_id || "<none recorded>"}`);
15452
15921
  emit(
15453
- "\nTeardown deletes the platform agent + its deployment via DELETE /v1/agents/:slug. The operator removes the transmitted runtime secret as part of that delete. The slug is permanently retired; a later redeploy must use a new slug."
15922
+ "\nTeardown deletes the platform agent + its deployment via DELETE /v1/agents/:slug. The operator removes the transmitted runtime secret and managed deliverables as part of that delete. Deliverable URLs eventually return 404. The slug is permanently retired; a later redeploy must use a new slug."
15454
15923
  );
15455
15924
  emit(
15456
15925
  "\n--- always note ---\n - your local keystore (.studio/wallets/) is left UNTOUCHED.\n - the on-chain ERC-8004 identity CANNOT be deleted; its endpoint goes stale.\n - the 48h trial sandbox is auto-reclaimed regardless."
@@ -15490,7 +15959,9 @@ async function platformDestroy(opts) {
15490
15959
  root,
15491
15960
  oldInvokeUrl ? String(oldInvokeUrl) : null
15492
15961
  );
15493
- emit(`\u2713 deleted platform agent ${slug} (deployment + transmitted secret).`);
15962
+ emit(
15963
+ `\u2713 deleted platform agent ${slug} (deployment + transmitted secret + managed deliverables). Deliverable URLs will return 404 after asynchronous cleanup.`
15964
+ );
15494
15965
  emit(
15495
15966
  `note: the on-chain ERC-8004 identity is unchanged; its endpoint will go stale. Destroyed slugs are permanently retired, so the local slug was advanced to '${nextSlug}' \u2014 the next deploy uses it automatically. Your local keystore (.studio/wallets/) was left untouched.`
15496
15967
  );
@@ -15612,7 +16083,12 @@ function registerDeploy(program) {
15612
16083
  "--allow-multiple",
15613
16084
  "Allow creating a deployment while another provider remains active."
15614
16085
  ).option("--skip-smoke", "Skip the delegated post-deploy smoke check.").action(act((opts) => cmdDeploy(opts, [])));
15615
- p.command("prepare").description("Run the deploy-readiness sweep on this project.").addOption(runtimeOption()).addOption(targetAlias()).option("--project-root <path>", "Override project root.").option("--json", "Emit JSON instead of a table.").option(
16086
+ p.command("prepare").description("Run the deploy-readiness sweep on this project.").addOption(providerOption()).addOption(
16087
+ new Option4(
16088
+ "--backend <backend>",
16089
+ "Managed backend for --provider bnb (aws or azure). Selects the Platform readiness contract without deploying."
16090
+ ).choices(["aws", "azure"])
16091
+ ).addOption(runtimeOption()).addOption(targetAlias()).option("--project-root <path>", "Override project root.").option("--json", "Emit JSON instead of a table.").option(
15616
16092
  "--ignore-warnings",
15617
16093
  "Exit 0 even if WARNING-level checks fire (still fails on BLOCKED/CRITICAL)."
15618
16094
  ).option("--no-info", "Skip INFO-level checks (CI-friendly).").option(
@@ -15825,7 +16301,7 @@ function resolveTarget(opts, root, destination = agentDeployDestination(root)) {
15825
16301
  return readStackRuntime(root) ?? "agentcore";
15826
16302
  }
15827
16303
  function validateRuntimeAgainstStack(target, force, root) {
15828
- if (target === "platform") {
16304
+ if (target === "platform" || target === "platform-azure") {
15829
16305
  return null;
15830
16306
  }
15831
16307
  const stackRuntime2 = readStackRuntime(root);
@@ -15857,7 +16333,49 @@ async function cmdPrepare(opts) {
15857
16333
  const cwd = path37.resolve(process.cwd());
15858
16334
  root = findStudioWorkspaceRoot3(cwd) ?? cwd;
15859
16335
  }
15860
- const target = resolveTarget(opts, root);
16336
+ let target;
16337
+ if (opts.provider !== void 0) {
16338
+ if (opts.runtime !== void 0 || opts.target !== void 0) {
16339
+ printErr(
16340
+ "error: --provider and --runtime select different readiness axes; pass only --provider (and --backend for bnb)."
16341
+ );
16342
+ return 2;
16343
+ }
16344
+ if (opts.provider === "bnb") {
16345
+ const stackRuntime2 = readStackRuntime(root);
16346
+ const expectedBackend = stackRuntime2 === "azure-foundry" ? "azure" : "aws";
16347
+ if (opts.backend !== void 0 && opts.backend !== expectedBackend) {
16348
+ printErr(
16349
+ `error: --backend=${opts.backend} conflicts with [stack].runtime='${stackRuntime2}', which requires backend=${expectedBackend}.`
16350
+ );
16351
+ return 2;
16352
+ }
16353
+ const backend = opts.backend ?? expectedBackend;
16354
+ target = backend === "azure" ? "platform-azure" : "platform";
16355
+ } else {
16356
+ if (opts.backend !== void 0) {
16357
+ printErr(
16358
+ `error: --backend is accepted only with --provider bnb; --provider ${opts.provider} selects a user-owned cloud directly.`
16359
+ );
16360
+ return 2;
16361
+ }
16362
+ target = opts.provider === "azure" ? "azure-foundry" : "agentcore";
16363
+ }
16364
+ } else {
16365
+ if (opts.backend !== void 0) {
16366
+ printErr("error: --backend requires --provider bnb.");
16367
+ return 2;
16368
+ }
16369
+ const destination = agentDeployDestination(root);
16370
+ const implicitAzureTarget = opts.runtime === void 0 && opts.target === void 0 && destination !== "platform" && readStackRuntime(root) === "azure-foundry";
16371
+ if (implicitAzureTarget) {
16372
+ printErr(
16373
+ "error: Azure prepare target is ambiguous. Choose the intended deployment explicitly: `bag deploy prepare --provider azure` for a user-owned Foundry deployment, or `bag deploy prepare --provider bnb --backend azure` for managed Platform Azure."
16374
+ );
16375
+ return 2;
16376
+ }
16377
+ target = resolveTarget(opts, root);
16378
+ }
15861
16379
  const rc = validateRuntimeAgainstStack(target, Boolean(opts.force), root);
15862
16380
  if (rc !== null) {
15863
16381
  return rc;
@@ -15910,7 +16428,9 @@ function renderPrepareHuman(result, ignoreWarnings) {
15910
16428
  if (result.hasBlockers) {
15911
16429
  printOut("Deploy is BLOCKED. Fix the BLOCKED check(s) above first.");
15912
16430
  } else if (result.hasCriticals) {
15913
- const fixHint = result.checks.find((c2) => c2.fixCmd)?.fixCmd;
16431
+ const fixHint = result.checks.find(
16432
+ (c2) => c2.level === "CRITICAL" && c2.fixCmd !== void 0 && c2.fixCmd !== null && !c2.fixCmd.trimStart().startsWith("#")
16433
+ )?.fixCmd;
15914
16434
  if (fixHint) {
15915
16435
  printOut(`Run ${fixHint} to auto-fix the first CRITICAL, then re-run.`);
15916
16436
  } else {
@@ -15927,14 +16447,17 @@ function renderPrepareHuman(result, ignoreWarnings) {
15927
16447
  }
15928
16448
  }
15929
16449
  function applyStorageGuard(result, root) {
15930
- const checks = [...result.checks];
15931
- const localCheck = storageLocalNotDeployableCheck(root);
15932
- if (localCheck !== null && !checks.some((c2) => c2.name === localCheck.name)) {
15933
- checks.push(localCheck);
16450
+ if (result.target.startsWith("platform")) {
16451
+ return result;
15934
16452
  }
15935
- const ipfsCheck = storageIpfsEndpointRequiredCheck(root);
15936
- if (ipfsCheck !== null && !hasIpfsEndpointFinding(result)) {
15937
- checks.push(ipfsCheck);
16453
+ const checks = [...result.checks];
16454
+ for (const check of fatalStorageDeployChecks(root)) {
16455
+ if (check.name === "storage_ipfs_endpoint_required" && hasIpfsEndpointFinding(result)) {
16456
+ continue;
16457
+ }
16458
+ if (!checks.some((existing) => existing.name === check.name)) {
16459
+ checks.push(check);
16460
+ }
15938
16461
  }
15939
16462
  if (checks.length === result.checks.length) {
15940
16463
  return result;
@@ -15950,6 +16473,9 @@ function warnBrokenStorageBypass(checks) {
15950
16473
  }
15951
16474
  }
15952
16475
  function enforceSkipPrepareStorageGate(opts, root, target) {
16476
+ if (target.startsWith("platform")) {
16477
+ return null;
16478
+ }
15953
16479
  if (!opts.skipPrepare) {
15954
16480
  return null;
15955
16481
  }
@@ -16293,10 +16819,16 @@ async function refreshLocalDeployDependencies(root) {
16293
16819
  try {
16294
16820
  const vendored = await vendorLocalTarballs(agentRoot2, {
16295
16821
  force: true,
16296
- requiredPackages: ["@bnbagent/sdk"]
16822
+ requiredPackages: ["@bnbagent/sdk", "@bnbagent/studio-runtime"]
16297
16823
  });
16298
16824
  if (vendored.length > 0) {
16299
16825
  applyWorkspaceVendorOverrides(workspaceRoot, agentRoot2);
16826
+ if (!await provisionInstall(workspaceRoot, { force: true })) {
16827
+ printErr(
16828
+ "error: refreshed vendored dependencies could not be installed; refusing to package stale node_modules."
16829
+ );
16830
+ return 1;
16831
+ }
16300
16832
  printErr(
16301
16833
  `note: refreshed vendored local tarball(s) in app/agent/vendor/: ${vendored.join(", ")}`
16302
16834
  );
@@ -16681,6 +17213,7 @@ async function printAccessNextSteps2(root, runtime, opts = {}) {
16681
17213
  const proto = nativeProtocolOf(faces);
16682
17214
  printOut("\nAccess your agent \u2014");
16683
17215
  if (runtime === "agentcore") {
17216
+ const paymentProtocol = hasMppFace(faces) ? "mpp" : "x402";
16684
17217
  const summary = accessSummaryForAgentcore(agentRoot2, { protocol: proto });
16685
17218
  if (opts.invokeUrl) {
16686
17219
  printOut(` endpoint: ${opts.invokeUrl}`);
@@ -16696,9 +17229,9 @@ async function printAccessNextSteps2(root, runtime, opts = {}) {
16696
17229
  " call: POST a JSON-RPC message/send to .../invocations with an OAuth2 Bearer token and an X-Amzn-Bedrock-AgentCore-Runtime-Session-Id header (>=33 chars)."
16697
17230
  );
16698
17231
  }
16699
- if (hasA2aFace(faces) && hasMcpFace(faces) || hasX402Face(faces)) {
17232
+ if (hasA2aFace(faces) && hasMcpFace(faces) || hasB402PaymentFace(faces)) {
16700
17233
  printOut(
16701
- " note: /mcp and /x402 have no raw URL on self-managed AgentCore; reach them by wrapping the request as envelope-v1 JSON inside the InvokeAgentRuntime payload (see docs/guides/x402-selling.md)."
17234
+ ` note: /mcp and /${paymentProtocol} have no raw URL on self-managed AgentCore; reach them by wrapping the request as envelope-v1 JSON inside the InvokeAgentRuntime payload (see docs/guides/${paymentProtocol === "mpp" ? "mpp-b402-selling.md" : "x402-selling.md"}).`
16702
17235
  );
16703
17236
  }
16704
17237
  printOut(
@@ -16718,16 +17251,19 @@ async function printAccessNextSteps2(root, runtime, opts = {}) {
16718
17251
  printOut(
16719
17252
  ` Azure Foundry access uses a Microsoft Entra bearer token. Run \`bag deploy info --provider azure --with-curl\` for the exact endpoint, token resource, and Invocations request body; see ${FOUNDRY_GUIDE_URL}.`
16720
17253
  );
16721
- if (hasX402Face(faces)) {
17254
+ if (hasB402PaymentFace(faces)) {
17255
+ const paymentProtocol = hasMppFace(faces) ? "mpp" : "x402";
16722
17256
  const summary = x402DeploySummary(agentRoot2, "self", null);
16723
- if (summary.startsWith("x402 rail is ACTIVE")) {
17257
+ if (summary.startsWith(`${paymentProtocol} rail is ACTIVE`)) {
17258
+ printOut(
17259
+ ` ${paymentProtocol} access: requires-external-gateway \u2014 Foundry exposes no raw anonymous /${paymentProtocol} URL. An operator-run HTTPS gateway must carry each request as http-envelope-v1 over the Entra-authenticated Invocations endpoint.`
17260
+ );
16724
17261
  printOut(
16725
- " x402 access: requires-external-gateway \u2014 Foundry exposes no raw anonymous /x402 URL. An operator-run HTTPS gateway must carry each request as http-envelope-v1 over the Entra-authenticated Invocations endpoint."
17262
+ ` ${paymentProtocol} gateway guide: ${paymentProtocol === "mpp" ? "docs/guides/mpp-b402-selling.md" : foundryX402GatewayGuideUrl()}`
16726
17263
  );
16727
- printOut(` x402 gateway guide: ${foundryX402GatewayGuideUrl()}`);
16728
17264
  } else {
16729
17265
  printOut(
16730
- ` x402 access: dormant \u2014 ${summary || "redeploy after enabling the x402 seller rail."}`
17266
+ ` ${paymentProtocol} access: dormant \u2014 ${summary || `redeploy after enabling the ${paymentProtocol} seller rail.`}`
16731
17267
  );
16732
17268
  }
16733
17269
  }
@@ -16755,6 +17291,9 @@ async function printAgentClientPrompt2(root, destination, opts = {}) {
16755
17291
  2) POST client_credentials -> access_token.
16756
17292
  3) send \`Authorization: Bearer <token>\` AND header \`X-Amzn-Bedrock-AgentCore-Runtime-Session-Id: <stable id >= 33 chars>\`.`;
16757
17293
  const agentCfg = loadAgentCfg2(agentRoot2) ?? {};
17294
+ const paymentProtocol = b402PaymentProtocol(agentCfg);
17295
+ const paymentLabel = paymentProtocol.toUpperCase();
17296
+ const paymentPath = `/${paymentProtocol}`;
16758
17297
  const rails = commerceRails(agentCfg);
16759
17298
  const x402Free = x402SellerIsFree(agentCfg);
16760
17299
  const x402Only = rails.x402 && !rails.erc8183;
@@ -16762,9 +17301,9 @@ async function printAgentClientPrompt2(root, destination, opts = {}) {
16762
17301
  const faces = agentFacesOf(agentRoot2);
16763
17302
  const x402OnlyFace = !hasA2aFace(faces) && !hasMcpFace(faces);
16764
17303
  const a2aPayloadPhrase = x402Only ? "POST JSON-RPC payloads" : "POST the negotiate /\n notify_funded payloads";
16765
- const endpointBlock = proto === "MCP" ? ` [MCP] MCP endpoint (streamable HTTP): ${mcpUrl}` : x402OnlyFace ? ` [X402] Invoke URL (POST-only): ${a2aInvokeUrl}
17304
+ const endpointBlock = proto === "MCP" ? ` [MCP] MCP endpoint (streamable HTTP): ${mcpUrl}` : x402OnlyFace ? ` [${paymentLabel}] Invoke URL (POST-only): ${a2aInvokeUrl}
16766
17305
  NOTE: there is NO public A2A face and NO GET-able agent card.
16767
- Wrap each request as envelope-v1 JSON (see X402 SELLER below)
17306
+ Wrap each request as envelope-v1 JSON (see ${paymentLabel} SELLER below)
16768
17307
  and POST it to the invoke URL above.` : ` [A2A] Invoke (JSON-RPC message/send, POST-only): ${a2aInvokeUrl}
16769
17308
  NOTE: this AgentCore endpoint is POST-only and serves NO
16770
17309
  GET-able agent card. Skip A2A discovery (do NOT fetch
@@ -16800,7 +17339,7 @@ async function printAgentClientPrompt2(root, destination, opts = {}) {
16800
17339
  "\u{1F4CB} COPY EVERYTHING BELOW THIS LINE INTO CLAUDE CODE OR CURSOR",
16801
17340
  " \u2014 it will build and run a client that tests your deployed agent",
16802
17341
  CLIENT_PROMPT_RULE2,
16803
- x402Only ? "I deployed a BNB Chain x402 seller agent. Build a small TypeScript (fetch) client that follows the X402 SELLER instructions below." : "I deployed a BNB Chain seller agent. Build a small TypeScript (fetch) client that authenticates, calls the agent, and prints the responses.",
17342
+ x402Only ? `I deployed a BNB Chain ${paymentProtocol} seller agent. Build a small TypeScript (fetch) client that follows the ${paymentLabel} SELLER instructions below.` : "I deployed a BNB Chain seller agent. Build a small TypeScript (fetch) client that authenticates, calls the agent, and prints the responses.",
16804
17343
  "",
16805
17344
  "WHAT WAS DEPLOYED",
16806
17345
  `- Protocol: ${proto}`,
@@ -16812,7 +17351,7 @@ async function printAgentClientPrompt2(root, destination, opts = {}) {
16812
17351
  ' 3) notify_funded \u2014 send that funded job_id; the agent verifies the on-chain job, produces the deliverable, and submits it on-chain. Returns {status:"accepted", job_id}. Delivery is ASYNC.',
16813
17352
  " 4) poll the CHAIN for the job's SUBMITTED state to read the deliverable_url (the agent serves no job-query endpoint)."
16814
17353
  ] : [],
16815
- ...x402Summary ? ["", "X402 SELLER", ...x402Summary.split("\n")] : [],
17354
+ ...x402Summary ? ["", `${paymentLabel} SELLER`, ...x402Summary.split("\n")] : [],
16816
17355
  "",
16817
17356
  "ENDPOINT",
16818
17357
  endpointBlock,
@@ -16826,14 +17365,14 @@ async function printAgentClientPrompt2(root, destination, opts = {}) {
16826
17365
  ...commerceShapeLines,
16827
17366
  ...mcpSessionLines
16828
17367
  ] : [
16829
- "- The anonymous x402 route does not use the OAuth2 token flow shown for ERC-8183."
17368
+ `- The anonymous ${paymentProtocol} route does not use the OAuth2 token flow shown for ERC-8183.`
16830
17369
  ],
16831
17370
  ...!x402Only && proto !== "MCP" ? [
16832
17371
  '- A2A callers: send the payload as a DATA part, NOT text \u2014 parts:[{"kind":"data","data":{ ...payload above... }}]. A JSON string in a "text" part is rejected (the runtime only reads data parts, so no skill is parsed).'
16833
17372
  ] : [],
16834
17373
  "",
16835
17374
  "YOUR TASK",
16836
- x402Only ? x402Free ? "Call the anonymous FREE /x402 endpoint directly. Do not create a payment or call the OAuth2 ERC-8183 endpoint." : "Follow the X402 SELLER activation or anonymous paid-request instructions above. Do not call the OAuth2 ERC-8183 endpoint." : "Write and run a client that (1) obtains a token, (2) calls negotiate with a sample task and prints the signed quote, (3) optionally funds a job on-chain (step 2 of the flow above) and calls notify_funded with the resulting job_id. Use the exact endpoint, headers, and message shapes above. If you don't have client_id/client_secret, ask me for them.",
17375
+ x402Only ? x402Free ? `Call the anonymous FREE ${paymentPath} endpoint directly. Do not create a payment or call the OAuth2 ERC-8183 endpoint.` : `Follow the ${paymentLabel} SELLER activation or anonymous paid-request instructions above. Do not call the OAuth2 ERC-8183 endpoint.` : "Write and run a client that (1) obtains a token, (2) calls negotiate with a sample task and prints the signed quote, (3) optionally funds a job on-chain (step 2 of the flow above) and calls notify_funded with the resulting job_id. Use the exact endpoint, headers, and message shapes above. If you don't have client_id/client_secret, ask me for them.",
16837
17376
  CLIENT_PROMPT_RULE2
16838
17377
  ];
16839
17378
  const text2 = lines.join("\n");
@@ -16850,11 +17389,16 @@ async function printAzureAgentClientPrompt(root) {
16850
17389
  const deployment = discoverRecordedDeployments(
16851
17390
  deployWorkspaceRoot2(root)
16852
17391
  ).find((candidate) => candidate.provider === "azure");
16853
- const endpoint = deployment?.endpoint ?? recordedEndpoint(root) ?? "<run `bag deploy info --provider azure` to recover the endpoint>";
17392
+ const legacyEndpoint = deployment?.endpoint ?? recordedEndpoint(root) ?? "<run `bag deploy info --provider azure` to recover the endpoint>";
16854
17393
  const faces = deployment?.faces ?? agentFacesOf(agentRoot2);
16855
- const hasX402 = hasX402Face(faces);
17394
+ const hasX402 = hasB402PaymentFace(faces);
17395
+ const paymentProtocol = hasMppFace(faces) ? "mpp" : "x402";
17396
+ const paymentLabel = paymentProtocol.toUpperCase();
16856
17397
  const hasA2a = hasA2aFace(faces);
16857
- const request = hasA2a ? JSON.stringify(azureNegotiateQuickVerifyBody()) : hasX402 ? JSON.stringify(azureX402CarrierBody()) : JSON.stringify(azureNegotiateQuickVerifyBody());
17398
+ const a2aEndpoint = deployment?.endpoints?.a2a ?? deployment?.endpoints?.http ?? legacyEndpoint;
17399
+ const invocationsEndpoint = deployment?.endpoints?.invocations ?? legacyEndpoint;
17400
+ const endpoint = hasA2a ? a2aEndpoint : invocationsEndpoint;
17401
+ const request = hasA2a ? JSON.stringify(azureA2aQuickVerifyBody()) : hasX402 ? JSON.stringify(azureX402CarrierBody(paymentProtocol)) : JSON.stringify(azureA2aQuickVerifyBody());
16858
17402
  const lines = [
16859
17403
  "",
16860
17404
  CLIENT_PROMPT_RULE2,
@@ -16865,21 +17409,24 @@ async function printAzureAgentClientPrompt(root) {
16865
17409
  "",
16866
17410
  "WHAT WAS DEPLOYED",
16867
17411
  `- Faces: ${faces.join(", ") || "unknown"}`,
16868
- `- Entra-authenticated Invocations endpoint: ${endpoint}`,
17412
+ `- Entra-authenticated ${hasA2a ? "A2A" : "Invocations"} endpoint: ${endpoint}`,
17413
+ ...hasA2a && hasX402 ? [
17414
+ `- Entra-authenticated ${paymentLabel} Invocations endpoint: ${invocationsEndpoint}`
17415
+ ] : [],
16869
17416
  `- Token scope: ${AZURE_FOUNDRY_TOKEN_SCOPE}`,
16870
- "- This is not an anonymous buyer URL. A public x402 buyer requires an external HTTPS gateway in front of Foundry.",
17417
+ `- This is not an anonymous buyer URL. A public ${paymentProtocol} buyer requires an external HTTPS gateway in front of Foundry.`,
16871
17418
  "",
16872
17419
  "REQUEST",
16873
17420
  `- POST application/json: ${request}`,
16874
- ...hasA2a ? ["- The input field contains the JSON-stringified negotiate payload."] : [],
17421
+ ...hasA2a ? ["- Send `A2A-Version: 1.0` with the native JSON-RPC A2A request."] : [],
16875
17422
  ...hasX402 ? [
16876
17423
  ...hasA2a ? [
16877
- `- X402 carrier alternative: ${JSON.stringify(azureX402CarrierBody())}`
17424
+ `- ${paymentLabel} carrier alternative: ${JSON.stringify(azureX402CarrierBody(paymentProtocol))}`
16878
17425
  ] : [],
16879
17426
  "- The outer object is http-envelope-v1. Build it with:",
16880
17427
  ' const inner = { prompt: "Say hi in one short sentence." };',
16881
17428
  ' const body = Buffer.from(JSON.stringify(inner)).toString("base64");',
16882
- "- The outer response is another HTTP envelope. Decode response.body from base64 before parsing the inner x402 JSON."
17429
+ `- The outer response is another HTTP envelope. Decode response.body from base64 before parsing the inner ${paymentProtocol} response.`
16883
17430
  ] : [],
16884
17431
  "",
16885
17432
  "AUTHENTICATION AND LOGS",
@@ -17154,11 +17701,12 @@ function printVerifyCurl(access) {
17154
17701
  const scope = access.scope;
17155
17702
  const tokenUrl = access.tokenUrl;
17156
17703
  const invokeUrl = `${access.base}/v1/rt/${agentId}`;
17157
- if (access.faces.length === 1 && hasX402Face(access.faces)) {
17704
+ if (access.faces.length === 1 && hasB402PaymentFace(access.faces)) {
17705
+ const paymentProtocol = hasMppFace(access.faces) ? "mpp" : "x402";
17158
17706
  printOut(
17159
17707
  `
17160
- Quick-verify x402 endpoint:
17161
- curl -i ${invokeUrl}/x402 # expect payment requirements until an x402 client pays`
17708
+ Quick-verify ${paymentProtocol} endpoint:
17709
+ curl -i ${invokeUrl}/${paymentProtocol} # expect payment requirements until an ${paymentProtocol} client pays`
17162
17710
  );
17163
17711
  return;
17164
17712
  }
@@ -17234,11 +17782,32 @@ function azureNegotiateQuickVerifyBody() {
17234
17782
  })
17235
17783
  };
17236
17784
  }
17237
- function azureX402CarrierBody() {
17785
+ function azureA2aQuickVerifyBody() {
17786
+ return {
17787
+ jsonrpc: "2.0",
17788
+ id: "bag-quick-verify",
17789
+ method: "message/send",
17790
+ params: {
17791
+ message: {
17792
+ messageId: "bag-quick-verify",
17793
+ role: "user",
17794
+ parts: [
17795
+ {
17796
+ kind: "text",
17797
+ text: JSON.stringify(
17798
+ JSON.parse(azureNegotiateQuickVerifyBody().input)
17799
+ )
17800
+ }
17801
+ ]
17802
+ }
17803
+ }
17804
+ };
17805
+ }
17806
+ function azureX402CarrierBody(protocol = "x402") {
17238
17807
  return {
17239
17808
  v: 1,
17240
17809
  method: "POST",
17241
- path: "/x402",
17810
+ path: protocol === "mpp" ? "/mpp" : "/x402",
17242
17811
  query: {},
17243
17812
  headers: { "content-type": "application/json" },
17244
17813
  body: Buffer.from(
@@ -17257,8 +17826,12 @@ function sameX402Snapshot(a, b) {
17257
17826
  return a.status === b.status && a.priceMode === b.priceMode && a.credentialsComplete === b.credentialsComplete;
17258
17827
  }
17259
17828
  function printAzureInfo(root, deployment, opts) {
17260
- const endpoint = deployment.endpoint;
17261
- if (!endpoint) {
17829
+ const legacyEndpoint = deployment.endpoint;
17830
+ const httpEndpoint = deployment.endpoints?.http ?? legacyEndpoint;
17831
+ const invocationsEndpoint = deployment.endpoints?.invocations ?? null;
17832
+ const a2aEndpoint = deployment.endpoints?.a2a ?? null;
17833
+ const agentCardEndpoint = deployment.endpoints?.agentCard ?? null;
17834
+ if (!legacyEndpoint && !httpEndpoint && !invocationsEndpoint && !a2aEndpoint) {
17262
17835
  if (opts.json) {
17263
17836
  printOut(
17264
17837
  JSON.stringify(
@@ -17266,7 +17839,12 @@ function printAzureInfo(root, deployment, opts) {
17266
17839
  provider: "azure",
17267
17840
  target: deployment.target,
17268
17841
  name: deployment.name,
17269
- endpoint: null,
17842
+ endpoints: {
17843
+ http: null,
17844
+ invocations: null,
17845
+ a2a: null,
17846
+ agent_card: null
17847
+ },
17270
17848
  error: "No Azure invocation endpoint is recorded."
17271
17849
  },
17272
17850
  null,
@@ -17283,15 +17861,19 @@ function printAzureInfo(root, deployment, opts) {
17283
17861
  const agentRoot2 = findSubProjectRoot16("agent", root) ?? root;
17284
17862
  const currentFaces = agentFacesOf(agentRoot2);
17285
17863
  const faces = deployment.faces ?? currentFaces;
17864
+ const paymentProtocol = hasMppFace(faces) ? "mpp" : "x402";
17865
+ const paymentLabel = paymentProtocol.toUpperCase();
17286
17866
  const facesSource = deployment.faces ? "deployment-record" : "current-scaffold";
17287
17867
  const facesDrift = deployment.faces !== void 0 && !sameFaces(deployment.faces, currentFaces);
17288
- const hasX402 = hasX402Face(faces);
17868
+ const hasX402 = hasB402PaymentFace(faces);
17289
17869
  const hasA2a = hasA2aFace(faces);
17290
- const currentX402 = hasX402Face(currentFaces) ? x402DeploymentSnapshot(agentRoot2, "self") : null;
17870
+ const endpoint = hasA2a ? a2aEndpoint ?? httpEndpoint ?? legacyEndpoint ?? invocationsEndpoint : hasX402 ? invocationsEndpoint ?? legacyEndpoint ?? httpEndpoint ?? a2aEndpoint : legacyEndpoint ?? httpEndpoint ?? a2aEndpoint ?? invocationsEndpoint;
17871
+ const x402Endpoint = invocationsEndpoint ?? legacyEndpoint;
17872
+ const currentX402 = hasB402PaymentFace(currentFaces) ? x402DeploymentSnapshot(agentRoot2, "self") : null;
17291
17873
  const x402State = deployment.x402 ?? currentX402;
17292
17874
  const x402StateSource = deployment.x402 ? "deployment-record" : "current-scaffold";
17293
17875
  const x402Drift = deployment.x402 !== void 0 && !sameX402Snapshot(deployment.x402, currentX402);
17294
- const currentCarrier = hasX402Face(currentFaces) ? inspectAzureX402Carrier(agentRoot2).status : null;
17876
+ const currentCarrier = hasB402PaymentFace(currentFaces) ? inspectAzureX402Carrier(agentRoot2).status : null;
17295
17877
  const carrierDrift = deployment.x402Carrier !== void 0 && deployment.x402Carrier !== currentCarrier;
17296
17878
  const configurationDrift = facesDrift || x402Drift || carrierDrift;
17297
17879
  const carrierVerified = deployment.x402Carrier === "http-envelope-v1";
@@ -17299,11 +17881,12 @@ function printAzureInfo(root, deployment, opts) {
17299
17881
  deployment.x402,
17300
17882
  "azure-foundry",
17301
17883
  "self",
17302
- null
17303
- ) : x402DeploySummary(agentRoot2, "self", null) : deployment.x402Carrier === "incompatible-scaffold" ? "The deployed Azure entrypoint did not mount createEnvelopeMiddleware and X402Seller. Re-scaffold the unified entrypoint, review the diff, and redeploy." : "This deployment record predates X402 carrier verification, so the deployed artifact cannot be treated as X402-capable. Verify or migrate the unified entrypoint and redeploy." : "";
17304
- const x402Status = carrierVerified ? x402State?.status ?? (x402Summary.includes("ACTIVE in FREE mode") ? "free" : x402Summary.startsWith("x402 rail is ACTIVE") ? "active" : "dormant") : "incompatible-scaffold";
17305
- const x402Body = hasX402 && carrierVerified ? azureX402CarrierBody() : void 0;
17306
- const body = hasA2a ? azureNegotiateQuickVerifyBody() : x402Body;
17884
+ null,
17885
+ paymentProtocol
17886
+ ) : x402DeploySummary(agentRoot2, "self", null) : deployment.x402Carrier === "incompatible-scaffold" ? "The deployed Azure entrypoint did not mount createEnvelopeMiddleware and B402Seller. Re-scaffold the unified entrypoint, review the diff, and redeploy." : `This deployment record predates ${paymentLabel} carrier verification, so the deployed artifact cannot be treated as ${paymentLabel}-capable. Verify or migrate the unified entrypoint and redeploy.` : "";
17887
+ const x402Status = carrierVerified ? x402State?.status ?? (x402Summary.includes("ACTIVE in FREE mode") ? "free" : x402Summary.startsWith(`${paymentProtocol} rail is ACTIVE`) ? "active" : "dormant") : "incompatible-scaffold";
17888
+ const x402Body = hasX402 && carrierVerified ? azureX402CarrierBody(paymentProtocol) : void 0;
17889
+ const body = hasA2a ? azureA2aQuickVerifyBody() : x402Body;
17307
17890
  const x402Info = hasX402 ? carrierVerified ? {
17308
17891
  status: x402Status,
17309
17892
  access: x402Status === "dormant" ? "dormant" : "requires-external-gateway",
@@ -17314,6 +17897,7 @@ function printAzureInfo(root, deployment, opts) {
17314
17897
  state_source: x402StateSource,
17315
17898
  ...x402Status === "dormant" ? {} : {
17316
17899
  request: {
17900
+ endpoint: x402Endpoint,
17317
17901
  method: "POST",
17318
17902
  content_type: "application/json",
17319
17903
  body: x402Body,
@@ -17336,6 +17920,12 @@ function printAzureInfo(root, deployment, opts) {
17336
17920
  name: deployment.name,
17337
17921
  deployment_id: deployment.deploymentId,
17338
17922
  endpoint,
17923
+ endpoints: {
17924
+ http: httpEndpoint,
17925
+ invocations: invocationsEndpoint,
17926
+ a2a: a2aEndpoint,
17927
+ agent_card: agentCardEndpoint
17928
+ },
17339
17929
  faces,
17340
17930
  faces_source: facesSource,
17341
17931
  configuration_drift: configurationDrift,
@@ -17349,7 +17939,7 @@ function printAzureInfo(root, deployment, opts) {
17349
17939
  content_type: "application/json",
17350
17940
  body
17351
17941
  } : null,
17352
- ...x402Info ? { x402: x402Info } : {}
17942
+ ...x402Info ? { [paymentProtocol]: x402Info } : {}
17353
17943
  },
17354
17944
  null,
17355
17945
  2
@@ -17358,7 +17948,17 @@ function printAzureInfo(root, deployment, opts) {
17358
17948
  return 0;
17359
17949
  }
17360
17950
  printOut("bag deploy info \u2014 Azure Foundry self-deploy");
17361
- printOut(` endpoint: ${endpoint}`);
17951
+ if (httpEndpoint) printOut(` HTTP endpoint: ${httpEndpoint}`);
17952
+ if (invocationsEndpoint) {
17953
+ printOut(` Invocations tunnel endpoint: ${invocationsEndpoint}`);
17954
+ }
17955
+ if (a2aEndpoint) printOut(` A2A endpoint: ${a2aEndpoint}`);
17956
+ if (agentCardEndpoint) {
17957
+ printOut(` Agent Card endpoint: ${agentCardEndpoint}`);
17958
+ }
17959
+ if (!httpEndpoint && !invocationsEndpoint && !a2aEndpoint && endpoint) {
17960
+ printOut(` endpoint: ${endpoint}`);
17961
+ }
17362
17962
  printOut(` deployed faces: ${faces.join(", ")} (${facesSource})`);
17363
17963
  if (configurationDrift) {
17364
17964
  printErr(
@@ -17373,31 +17973,38 @@ function printAzureInfo(root, deployment, opts) {
17373
17973
  printOut(` body: ${JSON.stringify(body)}`);
17374
17974
  } else {
17375
17975
  printOut(
17376
- " request: unavailable until the X402 scaffold is migrated and redeployed"
17976
+ ` request: unavailable until the ${paymentLabel} scaffold is migrated and redeployed`
17377
17977
  );
17378
17978
  }
17379
17979
  if (x402Info) {
17380
17980
  if (x402Info.access === "incompatible-scaffold") {
17381
- printOut(` x402 access: incompatible-scaffold \u2014 ${x402Info.summary}`);
17981
+ printOut(
17982
+ ` ${paymentProtocol} access: incompatible-scaffold \u2014 ${x402Info.summary}`
17983
+ );
17382
17984
  } else if (x402Info.access === "dormant") {
17383
- printOut(` x402 access: dormant \u2014 ${x402Info.summary}`);
17985
+ printOut(` ${paymentProtocol} access: dormant \u2014 ${x402Info.summary}`);
17384
17986
  } else {
17385
17987
  printOut(
17386
- " x402 access: requires-external-gateway (the Foundry endpoint has no raw anonymous /x402 URL)"
17988
+ ` ${paymentProtocol} access: requires-external-gateway (the Foundry endpoint has no raw anonymous /${paymentProtocol} URL)`
17387
17989
  );
17388
17990
  printOut(
17389
- " x402 carrier: http-envelope-v1 over this Entra-authenticated Invocations endpoint"
17991
+ ` ${paymentProtocol} carrier: http-envelope-v1 over this Entra-authenticated Invocations endpoint`
17390
17992
  );
17391
17993
  if (hasA2a && x402Body) {
17392
17994
  printOut(
17393
- ` x402 request example: ${JSON.stringify(x402Body)} (expect work in FREE mode or 402 before payment in PAID mode)`
17995
+ ` ${paymentProtocol} request example: ${JSON.stringify(x402Body)} (expect work in FREE mode or 402 before payment in PAID mode)`
17394
17996
  );
17395
17997
  }
17396
- printOut(` x402 gateway guide: ${foundryX402GatewayGuideUrl()}`);
17998
+ printOut(
17999
+ ` ${paymentProtocol} gateway guide: ${paymentProtocol === "mpp" ? "docs/guides/mpp-b402-selling.md" : foundryX402GatewayGuideUrl()}`
18000
+ );
17397
18001
  }
17398
18002
  }
17399
18003
  if (opts.withCurl && body) {
17400
- printOut(hasA2a ? "\nQuick-verify curl:" : "\nX402 request curl:");
18004
+ printOut(
18005
+ hasA2a ? "\nQuick-verify curl:" : `
18006
+ ${paymentLabel} request curl:`
18007
+ );
17401
18008
  printOut(
17402
18009
  ` AZURE_TOKEN=$(az account get-access-token --resource ${AZURE_FOUNDRY_TOKEN_RESOURCE} --query accessToken -o tsv)`
17403
18010
  );
@@ -17406,7 +18013,7 @@ function printAzureInfo(root, deployment, opts) {
17406
18013
  printOut(
17407
18014
  ` AZURE_RESPONSE=$(curl -sS -D "$AZURE_HEADERS" -X POST "$AZURE_ENDPOINT" \\
17408
18015
  -H "Authorization: Bearer $AZURE_TOKEN" \\
17409
- -H "Content-Type: application/json" \\
18016
+ -H "Content-Type: application/json"${hasA2a ? ' \\\n -H "A2A-Version: 1.0"' : ""} \\
17410
18017
  -d '${JSON.stringify(body)}')`
17411
18018
  );
17412
18019
  printOut(' printf "%s\\n" "$AZURE_RESPONSE"');
@@ -17417,7 +18024,8 @@ function printAzureInfo(root, deployment, opts) {
17417
18024
  printOut(' bag deploy logs --provider azure --session "$AZURE_SESSION"');
17418
18025
  if (!hasA2a && x402Body) {
17419
18026
  printOut(
17420
- "\n Decode response.body from base64 to read the inner x402 JSON."
18027
+ `
18028
+ Decode response.body from base64 to read the inner ${paymentProtocol} response.`
17421
18029
  );
17422
18030
  printOut(
17423
18031
  ` node -e 'const outer=JSON.parse(process.argv[1]); console.log(Buffer.from(outer.body,"base64").toString("utf8"))' "$AZURE_RESPONSE"`
@@ -17428,7 +18036,7 @@ function printAzureInfo(root, deployment, opts) {
17428
18036
  );
17429
18037
  } else if (opts.withCurl) {
17430
18038
  printErr(
17431
- "warning: no X402 curl was emitted because the recorded Azure deployment does not prove http-envelope-v1 support; migrate and redeploy first."
18039
+ `warning: no ${paymentLabel} curl was emitted because the recorded Azure deployment does not prove http-envelope-v1 support; migrate and redeploy first.`
17432
18040
  );
17433
18041
  }
17434
18042
  return 0;
@@ -17697,7 +18305,7 @@ import {
17697
18305
  BSC_TESTNET_MIN_U,
17698
18306
  BSC_TESTNET_U_FAUCET_URL as BSC_TESTNET_U_FAUCET_URL3,
17699
18307
  ERC8183_SUBMIT_GAS_RESERVE_BNB,
17700
- fromRaw as fromRaw3,
18308
+ fromRaw as fromRaw4,
17701
18309
  getNetwork as getNetwork9,
17702
18310
  tbnbFaucetHint as tbnbFaucetHint3
17703
18311
  } from "@bnbagent/studio-runtime/networks";
@@ -17720,6 +18328,13 @@ var SECRET_ENV_KEYS = [
17720
18328
  "WALLET_PASSWORD",
17721
18329
  "PIEVERSE_LLM_API_KEY",
17722
18330
  "STORAGE_API_KEY",
18331
+ "DELIVERABLE_S3_ACCESS_KEY_ID",
18332
+ "DELIVERABLE_S3_SECRET_ACCESS_KEY",
18333
+ "DELIVERABLE_S3_SESSION_TOKEN",
18334
+ "DELIVERABLE_AZURE_SAS_TOKEN",
18335
+ "DELIVERABLE_AZURE_TENANT_ID",
18336
+ "DELIVERABLE_AZURE_CLIENT_ID",
18337
+ "DELIVERABLE_AZURE_CLIENT_SECRET",
17723
18338
  "TWAK_ACCESS_ID",
17724
18339
  "TWAK_HMAC_SECRET",
17725
18340
  "TURNKEY_API_PRIVATE_KEY"
@@ -17799,11 +18414,11 @@ async function cmdDoctor(opts) {
17799
18414
  );
17800
18415
  checks.push(...checkPriceBounds(data));
17801
18416
  checks.push(...checkX402Seller(data, projectRoot));
17802
- checks.push(...checkStorage(data));
18417
+ const destination = String(tableOf14(data, "deploy").destination ?? "self").trim().toLowerCase();
18418
+ checks.push(...checkStorage(data, destination));
17803
18419
  checks.push(...await checkBalances(projectRoot, data, opts.network));
17804
18420
  checks.push(...await checkPieverseMainnetU(projectRoot, data));
17805
18421
  checks.push(...await check8004(projectRoot, data, opts.network));
17806
- const destination = String(tableOf14(data, "deploy").destination ?? "self").trim().toLowerCase();
17807
18422
  checks.push(...await checkDeployTools(data, destination));
17808
18423
  checks.push(
17809
18424
  ...await checkPlatformZipBundle(data, destination, projectRoot)
@@ -18185,7 +18800,7 @@ async function checkWalletAltana(walletCfg, projectRoot, data) {
18185
18800
  allowanceRaw > 0n ? {
18186
18801
  name: "[wallet] Altana x402 buying",
18187
18802
  status: PASS,
18188
- detail: `U\u2192Permit2 allowance ${fromRaw3(allowanceRaw, uToken.decimals)} U on ${networkName} (b402 permit2 rail armed)`
18803
+ detail: `U\u2192Permit2 allowance ${fromRaw4(allowanceRaw, uToken.decimals)} U on ${networkName} (b402 permit2 rail armed)`
18189
18804
  } : {
18190
18805
  name: "[wallet] Altana x402 buying",
18191
18806
  status: WARN,
@@ -18621,19 +19236,21 @@ function checkPriceBounds(data) {
18621
19236
  }
18622
19237
  function checkX402Seller(data, agentRoot2) {
18623
19238
  const payments = tableOf14(data, "payments");
18624
- const sellerValue = payments.x402_seller;
19239
+ const sellerValue = payments.b402_seller !== void 0 ? payments.b402_seller : payments.x402_seller;
18625
19240
  if (sellerValue === null || typeof sellerValue !== "object" || Array.isArray(sellerValue)) {
18626
19241
  return [];
18627
19242
  }
18628
19243
  const seller = sellerValue;
18629
19244
  if (seller.enabled !== true) return [];
19245
+ const paymentProtocol = b402PaymentProtocol(data);
19246
+ const paymentPath = paymentProtocol === "mpp" ? "/mpp" : "/x402";
18630
19247
  const pricing = x402SellerPricingState(seller);
18631
19248
  if (pricing.kind === "invalid") {
18632
19249
  return [
18633
19250
  {
18634
19251
  name: "x402 pricing",
18635
19252
  status: FAIL,
18636
- detail: "[payments.x402_seller].price_usd must be a non-negative decimal string."
19253
+ detail: "[payments.b402_seller].price_usd must be a non-negative decimal string."
18637
19254
  }
18638
19255
  ];
18639
19256
  }
@@ -18645,7 +19262,7 @@ function checkX402Seller(data, agentRoot2) {
18645
19262
  {
18646
19263
  name: "x402 pricing",
18647
19264
  status: PASS,
18648
- detail: "FREE \u2014 /x402 is anonymous; B402 verify/settle, token payment, and settlement audit are bypassed."
19265
+ detail: `FREE \u2014 ${paymentPath} is anonymous; B402 verify/settle, token payment, and settlement audit are bypassed.`
18649
19266
  }
18650
19267
  ];
18651
19268
  const credentials2 = b402Credentials(agentRoot2);
@@ -18660,7 +19277,13 @@ function checkX402Seller(data, agentRoot2) {
18660
19277
  out2.push({
18661
19278
  name: "x402 runtime",
18662
19279
  status: fallbackStatus,
18663
- detail: `${runtime} has no /x402 route; FREE pricing does not remove that runtime limitation.`
19280
+ detail: `${runtime} has no ${paymentPath} route; FREE pricing does not remove that runtime limitation.`
19281
+ });
19282
+ } else if (runtime === "azure-foundry") {
19283
+ out2.push({
19284
+ name: "x402 ingress",
19285
+ status: INFO,
19286
+ detail: `Azure Foundry runs ${paymentPath} through http-envelope-v1; anonymous access requires an operator-run external gateway.`
18664
19287
  });
18665
19288
  } else if (runtime === "azure-foundry") {
18666
19289
  out2.push({
@@ -18694,11 +19317,32 @@ function checkX402Seller(data, agentRoot2) {
18694
19317
  detail: credentials.any ? `B402 credentials are partial; set BASE_URL, CLIENT_ID, ACCESS_TOKEN, and exactly one private-key form. Confirm they were issued for this exact agent wallet and environment; never reuse them across wallets. Apply: ${B402_DEVELOPER_ACCOUNT_URL}` : `PAID mode needs the four B402 merchant credentials; without them the rail starts dormant. ${B402_PAID_ONBOARDING_GUIDANCE}`
18695
19318
  });
18696
19319
  }
19320
+ if (paymentProtocol === "mpp") {
19321
+ const envPath = envLocalPath18(agentRoot2);
19322
+ const secretKey = process.env.MPP_SECRET_KEY || getEnvVar(envPath, "MPP_SECRET_KEY");
19323
+ const realm = process.env.MPP_REALM || getEnvVar(envPath, "MPP_REALM");
19324
+ out.push({
19325
+ name: "MPP challenge key",
19326
+ status: secretKey && Buffer.byteLength(secretKey, "utf8") >= 32 ? PASS : fallbackStatus,
19327
+ detail: secretKey && Buffer.byteLength(secretKey, "utf8") >= 32 ? "MPP_SECRET_KEY has at least 32 bytes." : "PAID MPP+B402 requires MPP_SECRET_KEY with at least 32 UTF-8 bytes."
19328
+ });
19329
+ out.push({
19330
+ name: "MPP realm",
19331
+ status: realm ? PASS : fallbackStatus,
19332
+ detail: realm ? `MPP_REALM=${realm}` : "PAID MPP+B402 requires a stable MPP_REALM hostname."
19333
+ });
19334
+ }
18697
19335
  if (!X402_CAPABLE_RUNTIMES.has(runtime)) {
18698
19336
  out.push({
18699
19337
  name: "x402 runtime",
18700
19338
  status: fallbackStatus,
18701
- detail: `${runtime} has no /x402 route; deploy this rail to AgentCore.`
19339
+ detail: `${runtime} has no ${paymentPath} route; deploy this rail to AgentCore.`
19340
+ });
19341
+ } else if (runtime === "azure-foundry") {
19342
+ out.push({
19343
+ name: "x402 ingress",
19344
+ status: INFO,
19345
+ detail: `Azure Foundry runs ${paymentPath} through http-envelope-v1; anonymous access requires an operator-run external gateway.`
18702
19346
  });
18703
19347
  } else if (runtime === "azure-foundry") {
18704
19348
  out.push({
@@ -18709,8 +19353,18 @@ function checkX402Seller(data, agentRoot2) {
18709
19353
  }
18710
19354
  return out;
18711
19355
  }
18712
- function checkStorage(data) {
18713
- const kind = String(tableOf14(data, "storage").kind ?? "local").toLowerCase();
19356
+ function checkStorage(data, destination = "self") {
19357
+ const storage = tableOf14(data, "storage");
19358
+ const kind = String(storage.kind ?? "local").toLowerCase();
19359
+ if (destination === "platform") {
19360
+ return [
19361
+ {
19362
+ name: "storage (managed platform)",
19363
+ status: INFO,
19364
+ detail: "deliverable storage is managed by the Platform; user-owned S3/Azure configuration and credentials are not required."
19365
+ }
19366
+ ];
19367
+ }
18714
19368
  if (kind === "ipfs") {
18715
19369
  if (!process.env.STORAGE_API_URL) {
18716
19370
  return [
@@ -18738,6 +19392,68 @@ function checkStorage(data) {
18738
19392
  }
18739
19393
  ];
18740
19394
  }
19395
+ if (kind === "s3") {
19396
+ const missingConfig = ["bucket", "region"].filter(
19397
+ (key) => !String(storage[key] ?? "").trim()
19398
+ );
19399
+ const ambient = String(storage.credential_mode ?? "env") === "ambient";
19400
+ const missingSecrets = ambient ? [] : [
19401
+ "DELIVERABLE_S3_ACCESS_KEY_ID",
19402
+ "DELIVERABLE_S3_SECRET_ACCESS_KEY"
19403
+ ].filter((key) => !process.env[key]);
19404
+ if (missingConfig.length > 0 || missingSecrets.length > 0) {
19405
+ return [
19406
+ {
19407
+ name: "storage (s3)",
19408
+ status: WARN,
19409
+ detail: `kind=s3 is incomplete; missing ${[
19410
+ ...missingConfig.map((key) => `[storage].${key}`),
19411
+ ...missingSecrets
19412
+ ].join(
19413
+ ", "
19414
+ )}. Use namespaced credentials or set credential_mode='ambient' for a user-owned runtime identity.`
19415
+ }
19416
+ ];
19417
+ }
19418
+ return [
19419
+ {
19420
+ name: "storage (s3)",
19421
+ status: PASS,
19422
+ detail: `kind=s3; bucket/region configured; credentials=${ambient ? "ambient identity" : "injected secret"}; public URL=${storage.public_base_url ? "custom base" : "native S3"}`
19423
+ }
19424
+ ];
19425
+ }
19426
+ if (kind === "azure-blob") {
19427
+ const missingConfig = ["account_url", "container"].filter(
19428
+ (key) => !String(storage[key] ?? "").trim()
19429
+ );
19430
+ const ambient = String(storage.credential_mode ?? "env") === "ambient";
19431
+ const hasSas = Boolean(process.env.DELIVERABLE_AZURE_SAS_TOKEN);
19432
+ const servicePrincipalKeys = [
19433
+ "DELIVERABLE_AZURE_TENANT_ID",
19434
+ "DELIVERABLE_AZURE_CLIENT_ID",
19435
+ "DELIVERABLE_AZURE_CLIENT_SECRET"
19436
+ ];
19437
+ const hasServicePrincipal = servicePrincipalKeys.every(
19438
+ (key) => process.env[key]
19439
+ );
19440
+ if (missingConfig.length > 0 || !ambient && !hasSas && !hasServicePrincipal) {
19441
+ return [
19442
+ {
19443
+ name: "storage (azure-blob)",
19444
+ status: WARN,
19445
+ detail: `kind=azure-blob is incomplete${missingConfig.length > 0 ? `; missing ${missingConfig.map((key) => `[storage].${key}`).join(", ")}` : ""}. Configure DELIVERABLE_AZURE_SAS_TOKEN, a complete namespaced service-principal credential set, or credential_mode='ambient'.`
19446
+ }
19447
+ ];
19448
+ }
19449
+ return [
19450
+ {
19451
+ name: "storage (azure-blob)",
19452
+ status: PASS,
19453
+ detail: `kind=azure-blob; account/container configured; credentials=${ambient ? "ambient identity" : hasSas ? "SAS secret" : "service principal"}; public URL=${storage.public_base_url ? "custom base" : "native Blob"}`
19454
+ }
19455
+ ];
19456
+ }
18741
19457
  if (kind === "local") {
18742
19458
  return [
18743
19459
  {
@@ -18751,7 +19467,7 @@ function checkStorage(data) {
18751
19467
  {
18752
19468
  name: "storage",
18753
19469
  status: WARN,
18754
- detail: `unknown [storage].kind "${kind}" \u2014 falls back to local storage. Set kind = "ipfs" (any pinning service / self-hosted node) or "local".`
19470
+ detail: `unknown [storage].kind "${kind}" \u2014 falls back to local storage. Set kind = "ipfs", "s3", "azure-blob", or "local".`
18755
19471
  }
18756
19472
  ];
18757
19473
  }
@@ -18897,7 +19613,7 @@ async function checkPieverseMainnetU(projectRoot, data) {
18897
19613
  return [];
18898
19614
  }
18899
19615
  const raw = await readUBalanceRaw(addr, pvNetwork) ?? 0n;
18900
- const human = Number.parseFloat(fromRaw3(raw, uToken.decimals));
19616
+ const human = Number.parseFloat(fromRaw4(raw, uToken.decimals));
18901
19617
  if (human < threshold) {
18902
19618
  return [
18903
19619
  {
@@ -19274,7 +19990,7 @@ import {
19274
19990
  updateServiceEndpoint as updateServiceEndpoint2
19275
19991
  } from "@bnbagent/studio-runtime/erc8004";
19276
19992
  import {
19277
- fromRaw as fromRaw4,
19993
+ fromRaw as fromRaw5,
19278
19994
  getNetwork as getNetwork10,
19279
19995
  tbnbFaucetHint as tbnbFaucetHint4
19280
19996
  } from "@bnbagent/studio-runtime/networks";
@@ -19401,9 +20117,9 @@ function resolveProtocol(override) {
19401
20117
  return "A2A";
19402
20118
  }
19403
20119
  const faces = stackFaces(tableOf15(cfg, "stack"), tableOf15(cfg, "payments"));
19404
- if (faces.length === 1 && hasX402Face(faces)) {
20120
+ if (faces.length === 1 && hasB402PaymentFace(faces)) {
19405
20121
  printErr(
19406
- "warning: this X402-only project has no public ERC-8004 protocol face; A2A is only the internal AgentCore protocol declaration."
20122
+ `warning: this ${faces[0]}-only project has no public ERC-8004 protocol face; A2A is only the internal AgentCore protocol declaration.`
19407
20123
  );
19408
20124
  }
19409
20125
  return nativeProtocolOf(faces);
@@ -19428,7 +20144,7 @@ async function precheckRegisterGas(wallet, network) {
19428
20144
  let balance;
19429
20145
  try {
19430
20146
  const raw = await readNativeBalanceRaw(wallet.address, network);
19431
- balance = Number.parseFloat(fromRaw4(raw, 18));
20147
+ balance = Number.parseFloat(fromRaw5(raw, 18));
19432
20148
  } catch (exc) {
19433
20149
  printErr(
19434
20150
  `warning: could not read ${wallet.address} balance to pre-check gas (${errMsg4(exc)}); proceeding.`
@@ -20592,12 +21308,517 @@ function registerUninstall(program) {
20592
21308
  );
20593
21309
  }
20594
21310
 
20595
- // src/cli/platform.ts
21311
+ // src/cli/mpp.ts
21312
+ import * as fs42 from "fs";
20596
21313
  import * as path43 from "path";
21314
+ import { maskUrlSecrets, recordChainOp } from "@bnbagent/studio-runtime/audit";
21315
+ import {
21316
+ findProjectRoot as findProjectRoot6,
21317
+ loadStudioToml as loadStudioToml23
21318
+ } from "@bnbagent/studio-runtime/config";
21319
+ import {
21320
+ MppBuyerPolicy,
21321
+ fetchMppWithPayment,
21322
+ quoteMppUrl
21323
+ } from "@bnbagent/studio-runtime/mpp";
21324
+ import { getNetwork as getNetwork11 } from "@bnbagent/studio-runtime/networks";
21325
+ import * as walletRt8 from "@bnbagent/studio-runtime/wallet";
21326
+ import {
21327
+ SELLER_TOKENS,
21328
+ caip2,
21329
+ usdToAtomic
21330
+ } from "@bnbagent/studio-runtime/x402";
21331
+ import { getAddress as getAddress3, isAddress } from "viem";
21332
+
21333
+ // src/cli/_signingTrust.ts
21334
+ import { getAddress as getAddress2 } from "viem";
21335
+ function table(value) {
21336
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
21337
+ }
21338
+ function trustEip712Domain(text2, cfg, network, asset) {
21339
+ const wallet = table(cfg.wallet);
21340
+ const walletKind2 = String(wallet.kind ?? "evm-local");
21341
+ if (walletKind2 !== "evm-local") return text2;
21342
+ const match = /^eip155:(\d+)$/.exec(network);
21343
+ if (match === null) {
21344
+ throw new Error(`cannot trust a non-EVM signing domain: ${network}`);
21345
+ }
21346
+ const chainId = Number(match[1]);
21347
+ if (!Number.isSafeInteger(chainId) || chainId <= 0) {
21348
+ throw new Error(`cannot trust invalid EIP-155 chain id: ${network}`);
21349
+ }
21350
+ const address = getAddress2(asset);
21351
+ const signing = table(wallet.signing);
21352
+ const rawDomains = signing.extra_domains ?? [];
21353
+ if (!Array.isArray(rawDomains)) {
21354
+ throw new Error("[wallet.signing].extra_domains must be an array");
21355
+ }
21356
+ const domains = rawDomains.map((entry, index) => {
21357
+ if (!Array.isArray(entry) || entry.length !== 2) {
21358
+ throw new Error(
21359
+ `[wallet.signing].extra_domains[${index}] must be a [chain_id, address] pair`
21360
+ );
21361
+ }
21362
+ const existingChainId = Number(entry[0]);
21363
+ const existingAddress = getAddress2(String(entry[1]));
21364
+ return [existingChainId, existingAddress];
21365
+ });
21366
+ const alreadyTrusted = domains.some(
21367
+ (entry) => Array.isArray(entry) && Number(entry[0]) === chainId && String(entry[1]).toLowerCase() === address.toLowerCase()
21368
+ );
21369
+ if (alreadyTrusted) return text2;
21370
+ return updateSection(text2, "wallet.signing", {
21371
+ extra_domains: [...domains, [chainId, address]]
21372
+ });
21373
+ }
21374
+
21375
+ // src/cli/mpp.ts
21376
+ function registerMpp(program) {
21377
+ const mpp = program.command("mpp").description("Native MPP+B402 payment client helpers.");
21378
+ mpp.action(
21379
+ act(() => {
21380
+ printOut(
21381
+ "bag mpp: pick a sub-command. Try `bag mpp quote --help`, `bag mpp buy --help`, or `bag mpp trust --help`."
21382
+ );
21383
+ return 0;
21384
+ })
21385
+ );
21386
+ mpp.command("quote").description("Probe a native MPP+B402 URL without paying.").argument("<url>", "Absolute URL of the MPP-protected resource.").option("--network <name>", "Override [network].default.").option("--method <verb>", "HTTP verb (default: GET).", "GET").option("--json <body>", "JSON object body.").option("--local-dev", "DEV ONLY: permit an untrusted loopback target.").action(
21387
+ act(
21388
+ (url, opts) => cmdQuote(url, opts)
21389
+ )
21390
+ );
21391
+ mpp.command("buy").description("Pay a native MPP+B402 charge with $U via EIP-3009.").argument("<url>", "Absolute URL of the MPP-protected resource.").requiredOption(
21392
+ "--max-usd <n>",
21393
+ "Hard per-request USD cap.",
21394
+ Number.parseFloat
21395
+ ).option("--network <name>", "Override [network].default.").option("--method <verb>", "HTTP verb (default: GET).", "GET").option("--json <body>", "JSON object body.").option(
21396
+ "--local-dev",
21397
+ "DEV ONLY: permit loopback and pin recipient/realm from its live challenge."
21398
+ ).action(
21399
+ act(
21400
+ (url, opts) => cmdBuy2(url, opts)
21401
+ )
21402
+ );
21403
+ mpp.command("trust").description(
21404
+ "Probe and pin an MPP merchant's domain, realm, recipient, asset, and per-call cap."
21405
+ ).argument("<url>", "Full https:// MPP-protected URL.").option(
21406
+ "--cap <usd>",
21407
+ "Per-call cap (default: live price).",
21408
+ Number.parseFloat
21409
+ ).option("--name <key>", "Merchant config key (default: derived from host).").option(
21410
+ "--pay-to <addr>",
21411
+ "Expected recipient; must match the live challenge."
21412
+ ).option(
21413
+ "--realm <realm>",
21414
+ "Expected MPP realm; must match the live challenge."
21415
+ ).option(
21416
+ "--network <name>",
21417
+ "Settlement network (default: project network)."
21418
+ ).option("--method <verb>", "Probe this HTTP verb first.").option("--yes", "Write without interactive confirmation.").action(
21419
+ act(
21420
+ (url, opts) => cmdTrust(url, opts)
21421
+ )
21422
+ );
21423
+ }
21424
+ function errorMessage(error) {
21425
+ return error instanceof Error ? error.message : String(error);
21426
+ }
21427
+ function printMppError(error) {
21428
+ const name = error instanceof Error ? error.name : "";
21429
+ printErr(
21430
+ name.startsWith("Mpp") ? `error: ${name}: ${errorMessage(error)}` : `error: ${errorMessage(error)}`
21431
+ );
21432
+ }
21433
+ function tableOf16(cfg, key) {
21434
+ const value = cfg[key];
21435
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
21436
+ }
21437
+ function loadPolicy2() {
21438
+ const root = findProjectRoot6();
21439
+ if (root === null) {
21440
+ printErr("error: no studio.toml found in cwd or any parent.");
21441
+ return null;
21442
+ }
21443
+ try {
21444
+ const cfg = loadStudioToml23(path43.join(root, "studio.toml"));
21445
+ return { root, cfg, policy: MppBuyerPolicy.fromToml(cfg) };
21446
+ } catch (error) {
21447
+ printErr(`error: failed to load studio.toml: ${errorMessage(error)}`);
21448
+ return null;
21449
+ }
21450
+ }
21451
+ function resolveNetwork3(cfg, override) {
21452
+ return override ?? String(tableOf16(cfg, "network").default ?? "bsc-mainnet");
21453
+ }
21454
+ function hostOf(url) {
21455
+ try {
21456
+ return new URL(url).hostname.toLowerCase();
21457
+ } catch {
21458
+ return "";
21459
+ }
21460
+ }
21461
+ function loopback(host) {
21462
+ return ["localhost", "127.0.0.1", "::1"].includes(
21463
+ host.replace(/^\[|\]$/g, "").toLowerCase()
21464
+ );
21465
+ }
21466
+ function hostAllowed(url, policy) {
21467
+ const host = hostOf(url);
21468
+ return policy.effectiveAllowedHosts.some(
21469
+ (allowed) => allowed.toLowerCase() === host
21470
+ );
21471
+ }
21472
+ function parseJson(raw) {
21473
+ if (raw === void 0) return null;
21474
+ const value = JSON.parse(raw);
21475
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
21476
+ throw new Error("--json must decode to a JSON object");
21477
+ }
21478
+ return value;
21479
+ }
21480
+ function assertLocalDev(url, enabled) {
21481
+ if (enabled && !loopback(hostOf(url))) {
21482
+ printErr("error: --local-dev applies to loopback targets only.");
21483
+ return false;
21484
+ }
21485
+ return true;
21486
+ }
21487
+ async function cmdQuote(url, opts) {
21488
+ const loaded = loadPolicy2();
21489
+ if (loaded === null) return 1;
21490
+ const localDev = opts.localDev ?? false;
21491
+ if (!assertLocalDev(url, localDev)) return 2;
21492
+ if (!localDev && !hostAllowed(url, loaded.policy)) {
21493
+ printErr(
21494
+ `error: MPP host '${hostOf(url) || url}' is not trusted; run \`bag mpp trust <url>\` first.`
21495
+ );
21496
+ return 1;
21497
+ }
21498
+ try {
21499
+ const quote = await quoteMppUrl(url, {
21500
+ method: opts.method,
21501
+ jsonBody: parseJson(opts.json),
21502
+ networkName: resolveNetwork3(loaded.cfg, opts.network),
21503
+ policy: loaded.policy,
21504
+ allowedHosts: localDev ? null : void 0,
21505
+ allowInsecureLoopback: localDev
21506
+ });
21507
+ printOut(JSON.stringify(quote, jsonReplacer, 2));
21508
+ return quote.challenge === null && quote.statusCode === 402 ? 1 : 0;
21509
+ } catch (error) {
21510
+ printMppError(error);
21511
+ return error instanceof SyntaxError ? 2 : 1;
21512
+ }
21513
+ }
21514
+ async function cmdBuy2(url, opts) {
21515
+ const loaded = loadPolicy2();
21516
+ if (loaded === null) return 1;
21517
+ if (!Number.isFinite(opts.maxUsd) || opts.maxUsd <= 0) {
21518
+ printErr("error: --max-usd must be a positive number.");
21519
+ return 2;
21520
+ }
21521
+ const localDev = opts.localDev ?? false;
21522
+ if (!assertLocalDev(url, localDev)) return 2;
21523
+ if (!localDev && !hostAllowed(url, loaded.policy)) {
21524
+ printErr(
21525
+ `error: MPP host '${hostOf(url) || url}' is not trusted; run \`bag mpp trust <url>\` first.`
21526
+ );
21527
+ return 1;
21528
+ }
21529
+ let jsonBody;
21530
+ try {
21531
+ jsonBody = parseJson(opts.json);
21532
+ } catch (error) {
21533
+ printErr(`error: ${errorMessage(error)}`);
21534
+ return 2;
21535
+ }
21536
+ const networkName = resolveNetwork3(loaded.cfg, opts.network);
21537
+ let expectedRecipient = null;
21538
+ let expectedRealm = null;
21539
+ if (localDev) {
21540
+ try {
21541
+ const quote = await quoteMppUrl(url, {
21542
+ method: opts.method,
21543
+ jsonBody,
21544
+ networkName,
21545
+ policy: loaded.policy,
21546
+ allowedHosts: null,
21547
+ allowInsecureLoopback: true
21548
+ });
21549
+ if (quote.challenge === null) {
21550
+ printErr(
21551
+ "error: loopback target returned no payable MPP+B402 challenge."
21552
+ );
21553
+ return 1;
21554
+ }
21555
+ expectedRecipient = quote.challenge.recipient;
21556
+ expectedRealm = quote.challenge.realm;
21557
+ printOut(
21558
+ `\u26A0 DEV-ONLY: accepting live recipient ${expectedRecipient} and realm '${expectedRealm}'. Never use this outside your own machine.`
21559
+ );
21560
+ } catch (error) {
21561
+ printMppError(error);
21562
+ return 1;
21563
+ }
21564
+ }
21565
+ let wallet;
21566
+ try {
21567
+ wallet = walletRt8.getWallet();
21568
+ } catch (error) {
21569
+ printErr(`error: wallet unavailable: ${errorMessage(error)}`);
21570
+ return 1;
21571
+ }
21572
+ if (typeof wallet.supports === "function" && !wallet.supports("sign.typed_data")) {
21573
+ printErr(
21574
+ "error: MPP+B402 buying requires wallet capability sign.typed_data; use wallet.kind = 'evm-local'."
21575
+ );
21576
+ return 1;
21577
+ }
21578
+ const network = getNetwork11(networkName);
21579
+ const token = SELLER_TOKENS[caip2(network.chainId)];
21580
+ if (!token) {
21581
+ printErr(`error: MPP+B402 has no trusted buyer token on ${networkName}.`);
21582
+ return 1;
21583
+ }
21584
+ try {
21585
+ const current = await readTokenBalanceRaw(
21586
+ wallet.address,
21587
+ token.address,
21588
+ networkName
21589
+ );
21590
+ const needed = BigInt(
21591
+ usdToAtomic(decimalText(opts.maxUsd, token.decimals), token.decimals)
21592
+ );
21593
+ if (current < needed) {
21594
+ printErr(
21595
+ `error: insufficient B402 $U on ${networkName}; current=${current} atomic, needed=${needed} atomic.`
21596
+ );
21597
+ return 1;
21598
+ }
21599
+ } catch (error) {
21600
+ printErr(`error: B402 $U balance check failed: ${errorMessage(error)}`);
21601
+ return 1;
21602
+ }
21603
+ const commonAudit = {
21604
+ chainId: network.chainId,
21605
+ asset: "U",
21606
+ agentAddr: wallet.address,
21607
+ projectRoot: loaded.root
21608
+ };
21609
+ const auditUrl = maskUrlSecrets(url);
21610
+ recordChainOp("mpp_buy", {
21611
+ ...commonAudit,
21612
+ amount: String(opts.maxUsd),
21613
+ txHash: null,
21614
+ status: "submitted",
21615
+ context: { url: auditUrl, method: opts.method }
21616
+ });
21617
+ let result;
21618
+ try {
21619
+ result = await fetchMppWithPayment(url, {
21620
+ wallet,
21621
+ maxUsd: opts.maxUsd,
21622
+ networkName,
21623
+ method: opts.method,
21624
+ jsonBody,
21625
+ policy: loaded.policy,
21626
+ allowedHosts: localDev ? null : void 0,
21627
+ allowInsecureLoopback: localDev,
21628
+ expectedRecipient,
21629
+ expectedRealm
21630
+ });
21631
+ } catch (error) {
21632
+ const unknown = error instanceof Error && error.name === "MppPaymentOutcomeUnknownError";
21633
+ const amount = unknown && typeof error.amountUsd === "number" ? String(error.amountUsd) : String(opts.maxUsd);
21634
+ recordChainOp("mpp_buy", {
21635
+ ...commonAudit,
21636
+ amount,
21637
+ txHash: null,
21638
+ status: unknown ? "unknown" : "failed",
21639
+ context: {
21640
+ url: auditUrl,
21641
+ method: opts.method,
21642
+ payment_outcome: unknown ? "unknown" : "not-dispatched",
21643
+ error: errorMessage(error).slice(0, 200)
21644
+ }
21645
+ });
21646
+ printMppError(error);
21647
+ if (unknown) printErr("Do not retry blindly; reconcile the payment first.");
21648
+ return 1;
21649
+ }
21650
+ const txHash = transactionReference(result.receipt?.reference);
21651
+ recordChainOp("mpp_buy", {
21652
+ ...commonAudit,
21653
+ amount: String(result.paidUsd ?? 0),
21654
+ txHash,
21655
+ status: result.paymentOutcome === "unknown" ? "unknown" : "confirmed",
21656
+ counterparty: result.challenge?.recipient ?? null,
21657
+ context: {
21658
+ url: auditUrl,
21659
+ method: opts.method,
21660
+ payment_outcome: result.paymentOutcome,
21661
+ ...result.receipt?.reference ? { payment_reference: result.receipt.reference } : {}
21662
+ }
21663
+ });
21664
+ printResult(result);
21665
+ const ok = result.statusCode >= 200 && result.statusCode < 300;
21666
+ return ok && result.paymentOutcome !== "unknown" ? 0 : 1;
21667
+ }
21668
+ async function cmdTrust(url, opts) {
21669
+ const loaded = loadPolicy2();
21670
+ if (loaded === null) return 1;
21671
+ const host = hostOf(url);
21672
+ const localHttp = url.startsWith("http://") && loopback(host);
21673
+ if (!url.startsWith("https://") && !localHttp || !host) {
21674
+ printErr(
21675
+ "error: trust requires an absolute https:// URL; http:// is allowed only for loopback development."
21676
+ );
21677
+ return 1;
21678
+ }
21679
+ const networkName = resolveNetwork3(loaded.cfg, opts.network);
21680
+ const probed = await probe(url, networkName, opts.method, localHttp);
21681
+ if (probed === null) return 1;
21682
+ const { offer, method } = probed;
21683
+ if (opts.payTo && (!isAddress(opts.payTo) || getAddress3(opts.payTo) !== offer.recipient)) {
21684
+ printErr(
21685
+ `error: live recipient ${offer.recipient} does not match --pay-to ${opts.payTo}.`
21686
+ );
21687
+ return 1;
21688
+ }
21689
+ if (opts.realm && opts.realm.trim().toLowerCase() !== offer.realm) {
21690
+ printErr(
21691
+ `error: live realm '${offer.realm}' does not match --realm '${opts.realm}'.`
21692
+ );
21693
+ return 1;
21694
+ }
21695
+ const cap = opts.cap ?? offer.amountUsd;
21696
+ if (!Number.isFinite(cap) || cap < offer.amountUsd) {
21697
+ printErr(
21698
+ `error: cap must be at least the live price $${offer.amountUsd.toFixed(6)}.`
21699
+ );
21700
+ return 1;
21701
+ }
21702
+ const key = opts.name ?? host.replaceAll(".", "-");
21703
+ printOut(`Merchant: ${host} (UNREVIEWED)`);
21704
+ printOut(` Realm: ${offer.realm}`);
21705
+ printOut(` payTo: ${offer.recipient}`);
21706
+ printOut(` Asset: ${offer.asset} on ${offer.network}`);
21707
+ printOut(` Method: b402.charge / ${offer.transferMethod}`);
21708
+ printOut(` Price: $${offer.amountUsd.toFixed(6)}`);
21709
+ printOut(` Cap: $${cap.toFixed(6)}`);
21710
+ printOut(
21711
+ " \u26A0 The endpoint is not Studio-reviewed. Verify realm and recipient out-of-band before trusting."
21712
+ );
21713
+ if (!await confirm(opts.yes ?? false)) {
21714
+ printOut("aborted \u2014 nothing written.");
21715
+ return 1;
21716
+ }
21717
+ try {
21718
+ const tomlPath = path43.join(loaded.root, "studio.toml");
21719
+ const text2 = fs42.readFileSync(tomlPath, "utf8");
21720
+ const fields = {
21721
+ domain: host,
21722
+ realm: offer.realm,
21723
+ pay_to: offer.recipient,
21724
+ network: offer.network,
21725
+ asset: offer.asset,
21726
+ per_call_cap_usd: cap,
21727
+ verified: false
21728
+ };
21729
+ const withMerchant = updateSection(
21730
+ text2,
21731
+ `payments.mpp.merchants.${key}`,
21732
+ fields
21733
+ );
21734
+ fs42.writeFileSync(
21735
+ tomlPath,
21736
+ trustEip712Domain(withMerchant, loaded.cfg, offer.network, offer.asset),
21737
+ "utf8"
21738
+ );
21739
+ printOut(`\u2713 Wrote [payments.mpp.merchants.${key}] to ${tomlPath}`);
21740
+ const methodFlag = method === "GET" ? "" : ` --method ${method}`;
21741
+ printOut(` bag mpp quote ${url}${methodFlag}`);
21742
+ printOut(` bag mpp buy ${url} --max-usd ${cap}${methodFlag}`);
21743
+ printOut(" bag recipe code mpp-buyer");
21744
+ return 0;
21745
+ } catch (error) {
21746
+ printErr(`error: failed to write studio.toml: ${errorMessage(error)}`);
21747
+ return 1;
21748
+ }
21749
+ }
21750
+ async function probe(url, networkName, methodHint, allowInsecureLoopback = false) {
21751
+ const methods = [
21752
+ ...new Set([methodHint, "GET", "POST"].filter(Boolean))
21753
+ ];
21754
+ let lastError = null;
21755
+ for (const method of methods) {
21756
+ try {
21757
+ const quote = await quoteMppUrl(url, {
21758
+ method,
21759
+ networkName,
21760
+ policy: null,
21761
+ allowedHosts: null,
21762
+ allowInsecureLoopback
21763
+ });
21764
+ if (quote.challenge) return { offer: quote.challenge, method };
21765
+ lastError = new Error(
21766
+ `HTTP ${quote.statusCode} returned no b402.charge challenge`
21767
+ );
21768
+ } catch (error) {
21769
+ lastError = error;
21770
+ }
21771
+ }
21772
+ printErr(
21773
+ `error: could not obtain an MPP+B402 challenge: ${errorMessage(lastError)}`
21774
+ );
21775
+ return null;
21776
+ }
21777
+ async function confirm(yes) {
21778
+ if (yes) return true;
21779
+ if (!stdinIsTty()) {
21780
+ printErr("error: non-interactive shell; re-run with --yes after review.");
21781
+ return false;
21782
+ }
21783
+ const answer = (await promptUser("Trust this MPP merchant? [y/N] ")).trim().toLowerCase();
21784
+ return answer === "y" || answer === "yes";
21785
+ }
21786
+ function printResult(result) {
21787
+ if (result.paymentOutcome === "confirmed") {
21788
+ printOut(`\u2713 Paid: $${(result.paidUsd ?? 0).toFixed(6)} via MPP+B402`);
21789
+ } else if (result.paymentOutcome === "unknown") {
21790
+ printErr(
21791
+ "error: payment credential was dispatched but no successful B402 receipt was returned; outcome unknown. Do not retry blindly."
21792
+ );
21793
+ } else {
21794
+ printOut(`\u2713 No payment required (HTTP ${result.statusCode}).`);
21795
+ }
21796
+ printOut(` URL: ${result.url}`);
21797
+ printOut(` Status: ${result.statusCode}`);
21798
+ if (result.receipt?.reference) {
21799
+ printOut(` Reference: ${result.receipt.reference}`);
21800
+ }
21801
+ const body = result.json ? JSON.stringify(result.json, null, 2) : result.content.toString("utf8");
21802
+ printOut(
21803
+ ` Response: ${body.slice(0, 200).replaceAll("\n", "\n ")}`
21804
+ );
21805
+ }
21806
+ function transactionReference(value) {
21807
+ return value && /^0x[0-9a-fA-F]{64}$/.test(value) ? value : null;
21808
+ }
21809
+ function decimalText(value, decimals) {
21810
+ return value.toFixed(decimals).replace(/(?:\.0+|(?:(\.\d*?)0+))$/, "$1");
21811
+ }
21812
+ function jsonReplacer(_key, value) {
21813
+ return Buffer.isBuffer(value) ? value.toString("utf8") : value;
21814
+ }
21815
+
21816
+ // src/cli/platform.ts
21817
+ import * as path44 from "path";
20597
21818
  import {
20598
21819
  STUDIO_TOML as STUDIO_TOML2,
20599
21820
  findSubProjectRoot as findSubProjectRoot18,
20600
- loadStudioToml as loadStudioToml23
21821
+ loadStudioToml as loadStudioToml24
20601
21822
  } from "@bnbagent/studio-runtime/config";
20602
21823
  var ACCOUNT_SLUG = "studio-platform";
20603
21824
  var PlatformUsageError = class extends Error {
@@ -20796,10 +22017,10 @@ function localPlatformCfg() {
20796
22017
  if (agentRoot2 === null) {
20797
22018
  return [null, {}];
20798
22019
  }
20799
- const tomlPath = path43.join(agentRoot2, STUDIO_TOML2);
22020
+ const tomlPath = path44.join(agentRoot2, STUDIO_TOML2);
20800
22021
  let cfg;
20801
22022
  try {
20802
- cfg = loadStudioToml23(tomlPath);
22023
+ cfg = loadStudioToml24(tomlPath);
20803
22024
  } catch {
20804
22025
  return [null, {}];
20805
22026
  }
@@ -20830,14 +22051,14 @@ function findAgentId(data) {
20830
22051
  tables.push(nested);
20831
22052
  }
20832
22053
  }
20833
- for (const table of tables) {
22054
+ for (const table2 of tables) {
20834
22055
  for (const key of ["agent_id", "agentId"]) {
20835
- const value = table[key];
22056
+ const value = table2[key];
20836
22057
  if (typeof value === "string" && value) {
20837
22058
  return value;
20838
22059
  }
20839
22060
  }
20840
- const scope = table.scope;
22061
+ const scope = table2.scope;
20841
22062
  if (typeof scope === "string" && scope.includes("invoke:")) {
20842
22063
  const id = scope.split("invoke:").pop() ?? "";
20843
22064
  if (id) {
@@ -20869,8 +22090,8 @@ function formatRemaining(seconds) {
20869
22090
  }
20870
22091
 
20871
22092
  // src/cli/recipe.ts
20872
- import * as fs42 from "fs";
20873
- import * as path44 from "path";
22093
+ import * as fs43 from "fs";
22094
+ import * as path45 from "path";
20874
22095
  import { parse as parse6 } from "smol-toml";
20875
22096
  function registerRecipe(program) {
20876
22097
  const p = program.command("recipe").description("Print recipe code/files (agent / wallet / 8004).");
@@ -21000,7 +22221,7 @@ function resolvePkgContext(explicit) {
21000
22221
  let data;
21001
22222
  try {
21002
22223
  data = parse6(
21003
- fs42.readFileSync(path44.join(projectRoot, "studio.toml"), "utf-8")
22224
+ fs43.readFileSync(path45.join(projectRoot, "studio.toml"), "utf-8")
21004
22225
  );
21005
22226
  } catch {
21006
22227
  return [null, null];
@@ -21018,8 +22239,8 @@ function resolvePkgContext(explicit) {
21018
22239
  }
21019
22240
 
21020
22241
  // src/cli/scan.ts
21021
- import * as fs43 from "fs";
21022
- import * as path45 from "path";
22242
+ import * as fs44 from "fs";
22243
+ import * as path46 from "path";
21023
22244
  import { Option as Option8 } from "commander";
21024
22245
  import { parse as parse7 } from "smol-toml";
21025
22246
  function registerScan(program) {
@@ -21036,7 +22257,7 @@ function registerScan(program) {
21036
22257
  "note: `--json` is deprecated (JSON is the default output); ignoring."
21037
22258
  );
21038
22259
  }
21039
- const root = path45.resolve(opts.path ?? process.cwd());
22260
+ const root = path46.resolve(opts.path ?? process.cwd());
21040
22261
  const report = scanProject(root);
21041
22262
  if (opts.text) {
21042
22263
  process.stdout.write(formatText(report));
@@ -21050,24 +22271,24 @@ function registerScan(program) {
21050
22271
  }
21051
22272
  function isFile19(p) {
21052
22273
  try {
21053
- return fs43.statSync(p).isFile();
22274
+ return fs44.statSync(p).isFile();
21054
22275
  } catch {
21055
22276
  return false;
21056
22277
  }
21057
22278
  }
21058
22279
  function isDir10(p) {
21059
22280
  try {
21060
- return fs43.statSync(p).isDirectory();
22281
+ return fs44.statSync(p).isDirectory();
21061
22282
  } catch {
21062
22283
  return false;
21063
22284
  }
21064
22285
  }
21065
22286
  function layerBase(root) {
21066
- return isDir10(path45.join(root, "app", "agent")) ? path45.join(root, "app") : root;
22287
+ return isDir10(path46.join(root, "app", "agent")) ? path46.join(root, "app") : root;
21067
22288
  }
21068
22289
  function safeReadJson(p) {
21069
22290
  try {
21070
- const data = JSON.parse(fs43.readFileSync(p, "utf-8"));
22291
+ const data = JSON.parse(fs44.readFileSync(p, "utf-8"));
21071
22292
  return data !== null && typeof data === "object" && !Array.isArray(data) ? data : null;
21072
22293
  } catch {
21073
22294
  return null;
@@ -21075,7 +22296,7 @@ function safeReadJson(p) {
21075
22296
  }
21076
22297
  function safeReadToml(p) {
21077
22298
  try {
21078
- return parse7(fs43.readFileSync(p, "utf-8"));
22299
+ return parse7(fs44.readFileSync(p, "utf-8"));
21079
22300
  } catch {
21080
22301
  return null;
21081
22302
  }
@@ -21105,33 +22326,33 @@ function resolvePackageName(projectName2, root) {
21105
22326
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(candidate)) {
21106
22327
  return null;
21107
22328
  }
21108
- if (!isDir10(path45.join(root, candidate))) {
22329
+ if (!isDir10(path46.join(root, candidate))) {
21109
22330
  return null;
21110
22331
  }
21111
22332
  return candidate;
21112
22333
  }
21113
22334
  function inferLayout(root) {
21114
- const agentDir = path45.join(layerBase(root), "agent");
21115
- const hasAgent = isFile19(path45.join(agentDir, "main.ts")) || isFile19(path45.join(agentDir, "main.py"));
22335
+ const agentDir = path46.join(layerBase(root), "agent");
22336
+ const hasAgent = isFile19(path46.join(agentDir, "main.ts")) || isFile19(path46.join(agentDir, "main.py"));
21116
22337
  return {
21117
22338
  has_agent: hasAgent,
21118
22339
  has_agent_descriptor: isFile19(
21119
- path45.join(root, "agentcore", "agentcore.json")
22340
+ path46.join(root, "agentcore", "agentcore.json")
21120
22341
  ),
21121
22342
  role: hasAgent ? "agent" : "none"
21122
22343
  };
21123
22344
  }
21124
22345
  function inferPackageManager(root, hasPackageJson) {
21125
- if (isFile19(path45.join(root, "pnpm-lock.yaml"))) {
22346
+ if (isFile19(path46.join(root, "pnpm-lock.yaml"))) {
21126
22347
  return "pnpm";
21127
22348
  }
21128
- if (isFile19(path45.join(root, "yarn.lock"))) {
22349
+ if (isFile19(path46.join(root, "yarn.lock"))) {
21129
22350
  return "yarn";
21130
22351
  }
21131
- if (isFile19(path45.join(root, "bun.lockb"))) {
22352
+ if (isFile19(path46.join(root, "bun.lockb"))) {
21132
22353
  return "bun";
21133
22354
  }
21134
- if (isFile19(path45.join(root, "package-lock.json"))) {
22355
+ if (isFile19(path46.join(root, "package-lock.json"))) {
21135
22356
  return "npm";
21136
22357
  }
21137
22358
  if (hasPackageJson) {
@@ -21144,7 +22365,7 @@ function inferRecipes(root, studioData, layout) {
21144
22365
  if (layout.has_agent) {
21145
22366
  recipes.push("agent");
21146
22367
  }
21147
- const walletsDir = path45.join(root, ".studio", "wallets");
22368
+ const walletsDir = path46.join(root, ".studio", "wallets");
21148
22369
  const walletSection = studioData?.wallet;
21149
22370
  const hasWalletSection = walletSection !== null && typeof walletSection === "object" && !Array.isArray(walletSection);
21150
22371
  if (isDir10(walletsDir) && hasWalletSection) {
@@ -21153,13 +22374,13 @@ function inferRecipes(root, studioData, layout) {
21153
22374
  return recipes;
21154
22375
  }
21155
22376
  function scanProject(root) {
21156
- const studioToml = path45.join(root, "studio.toml");
21157
- const packageJson = path45.join(root, "package.json");
21158
- const agentcoreJson = path45.join(root, "agentcore", "agentcore.json");
22377
+ const studioToml = path46.join(root, "studio.toml");
22378
+ const packageJson = path46.join(root, "package.json");
22379
+ const agentcoreJson = path46.join(root, "agentcore", "agentcore.json");
21159
22380
  const hasStudioToml = isFile19(studioToml);
21160
22381
  const hasPackageJson = isFile19(packageJson);
21161
22382
  const hasAgentcoreJson = isFile19(agentcoreJson);
21162
- const hasDotStudioDir = isDir10(path45.join(root, ".studio"));
22383
+ const hasDotStudioDir = isDir10(path46.join(root, ".studio"));
21163
22384
  const pkgData = hasPackageJson ? safeReadJson(packageJson) : null;
21164
22385
  const studioData = hasStudioToml ? safeReadToml(studioToml) : null;
21165
22386
  const rawName = pkgData?.name;
@@ -21216,23 +22437,23 @@ function formatText(report) {
21216
22437
  }
21217
22438
 
21218
22439
  // src/cli/x402.ts
21219
- import * as fs44 from "fs";
21220
- import * as path46 from "path";
22440
+ import * as fs45 from "fs";
22441
+ import * as path47 from "path";
21221
22442
  import { auditedOp as auditedOp4 } from "@bnbagent/studio-runtime/audit";
21222
22443
  import {
21223
22444
  envLocalPath as envLocalPath19,
21224
- findProjectRoot as findProjectRoot6,
21225
- loadStudioToml as loadStudioToml24
22445
+ findProjectRoot as findProjectRoot7,
22446
+ loadStudioToml as loadStudioToml25
21226
22447
  } from "@bnbagent/studio-runtime/config";
21227
- import { fromRaw as fromRaw5, getNetwork as getNetwork11 } from "@bnbagent/studio-runtime/networks";
21228
- import * as walletRt8 from "@bnbagent/studio-runtime/wallet";
22448
+ import { fromRaw as fromRaw6, getNetwork as getNetwork12 } from "@bnbagent/studio-runtime/networks";
22449
+ import * as walletRt9 from "@bnbagent/studio-runtime/wallet";
21229
22450
  import {
21230
- SELLER_TOKENS,
22451
+ SELLER_TOKENS as SELLER_TOKENS2,
21231
22452
  X402BuyerPolicy,
21232
22453
  X402SellerPolicy,
21233
22454
  _parseAccepted,
21234
22455
  _probeB402Supported,
21235
- caip2,
22456
+ caip2 as caip22,
21236
22457
  fetchWithPayment,
21237
22458
  quoteUrl
21238
22459
  } from "@bnbagent/studio-runtime/x402";
@@ -21305,7 +22526,7 @@ function registerX402(program) {
21305
22526
  "DEV ONLY: skip the host allowlist for a localhost/127.0.0.1 target."
21306
22527
  ).action(
21307
22528
  act(
21308
- (url, opts) => cmdQuote(url, opts)
22529
+ (url, opts) => cmdQuote2(url, opts)
21309
22530
  )
21310
22531
  );
21311
22532
  p.command("buy").description("Pay an x402-protected URL with $U via EIP-3009.").argument("<url>", "Absolute URL of the x402-protected resource.").requiredOption(
@@ -21323,7 +22544,7 @@ function registerX402(program) {
21323
22544
  "DEV ONLY: for a localhost/127.0.0.1 target, skip the host allowlist and accept the challenge's payTo as the recipient (printed loudly)."
21324
22545
  ).action(
21325
22546
  act(
21326
- (url, opts) => cmdBuy2(url, opts)
22547
+ (url, opts) => cmdBuy3(url, opts)
21327
22548
  )
21328
22549
  );
21329
22550
  p.command("trust").description(
@@ -21349,7 +22570,7 @@ function registerX402(program) {
21349
22570
  "Skip the interactive confirmation (required in non-interactive shells)."
21350
22571
  ).action(
21351
22572
  act(
21352
- (merchant, opts) => cmdTrust(merchant, opts)
22573
+ (merchant, opts) => cmdTrust2(merchant, opts)
21353
22574
  )
21354
22575
  );
21355
22576
  const sell = p.command("sell").description("Configure and inspect the B402-backed x402 seller rail.");
@@ -21365,7 +22586,7 @@ function registerX402(program) {
21365
22586
  ).action(act((opts) => cmdSellInit(opts.priceUsd)));
21366
22587
  sell.command("status").description("Show x402 seller config and B402 capability status.").option("--no-probe", "Skip the authenticated B402 /supported probe.").action(act((opts) => cmdSellStatus(opts.probe)));
21367
22588
  }
21368
- function hostOf(url) {
22589
+ function hostOf2(url) {
21369
22590
  try {
21370
22591
  return (new URL(url).hostname || "").toLowerCase();
21371
22592
  } catch {
@@ -21390,13 +22611,13 @@ function printLocalDevHint(url) {
21390
22611
  );
21391
22612
  }
21392
22613
  function checkHostAllowed(url, allowedHosts) {
21393
- const host = hostOf(url);
22614
+ const host = hostOf2(url);
21394
22615
  const allowedLower = new Set(
21395
22616
  allowedHosts.map((h) => String(h).toLowerCase())
21396
22617
  );
21397
22618
  return allowedLower.has(host);
21398
22619
  }
21399
- function tableOf16(cfg, key) {
22620
+ function tableOf17(cfg, key) {
21400
22621
  const v = cfg[key];
21401
22622
  return v !== null && typeof v === "object" && !Array.isArray(v) ? v : {};
21402
22623
  }
@@ -21404,14 +22625,14 @@ function effectiveAllowedHosts(cfg) {
21404
22625
  return [...X402BuyerPolicy.fromToml(cfg).effectiveAllowedHosts];
21405
22626
  }
21406
22627
  function loadX402Policy() {
21407
- const root = findProjectRoot6();
22628
+ const root = findProjectRoot7();
21408
22629
  if (root === null) {
21409
22630
  printErr("error: no studio.toml found in cwd or any parent.");
21410
22631
  return null;
21411
22632
  }
21412
22633
  let cfg;
21413
22634
  try {
21414
- cfg = loadStudioToml24(path46.join(root, "studio.toml"));
22635
+ cfg = loadStudioToml25(path47.join(root, "studio.toml"));
21415
22636
  } catch (exc) {
21416
22637
  printErr(`error: failed to parse studio.toml: ${errMsg6(exc)}`);
21417
22638
  return null;
@@ -21419,23 +22640,23 @@ function loadX402Policy() {
21419
22640
  return [root, cfg, effectiveAllowedHosts(cfg)];
21420
22641
  }
21421
22642
  function loadSellerConfig() {
21422
- const root = findProjectRoot6();
22643
+ const root = findProjectRoot7();
21423
22644
  if (root === null) {
21424
22645
  printErr("error: no studio.toml found in cwd or any parent.");
21425
22646
  return null;
21426
22647
  }
21427
22648
  try {
21428
- return [root, loadStudioToml24(path46.join(root, "studio.toml"))];
22649
+ return [root, loadStudioToml25(path47.join(root, "studio.toml"))];
21429
22650
  } catch (exc) {
21430
22651
  printErr(`error: failed to parse studio.toml: ${errMsg6(exc)}`);
21431
22652
  return null;
21432
22653
  }
21433
22654
  }
21434
- function resolveNetwork3(cfg, override) {
22655
+ function resolveNetwork4(cfg, override) {
21435
22656
  if (override) {
21436
22657
  return override;
21437
22658
  }
21438
- const net2 = tableOf16(cfg, "network").default;
22659
+ const net2 = tableOf17(cfg, "network").default;
21439
22660
  return net2 ? String(net2) : "bsc-mainnet";
21440
22661
  }
21441
22662
  function parseJsonBody(raw) {
@@ -21455,7 +22676,7 @@ function parseJsonBody(raw) {
21455
22676
  }
21456
22677
  function resolveWallet2() {
21457
22678
  try {
21458
- return walletRt8.getWallet();
22679
+ return walletRt9.getWallet();
21459
22680
  } catch (exc) {
21460
22681
  printErr(`error: wallet unavailable: ${errMsg6(exc)}`);
21461
22682
  return null;
@@ -21471,9 +22692,13 @@ async function cmdSellInit(priceFlag) {
21471
22692
  const loaded = loadSellerConfig();
21472
22693
  if (loaded === null) return 1;
21473
22694
  const [root, cfg] = loaded;
21474
- const tomlPath = path46.join(root, "studio.toml");
21475
- const original = fs44.readFileSync(tomlPath, "utf8");
21476
- const sellerExists = hasSection(original, "payments.x402_seller");
22695
+ const tomlPath = path47.join(root, "studio.toml");
22696
+ const original = fs45.readFileSync(tomlPath, "utf8");
22697
+ const canonicalExists = hasSection(original, "payments.b402_seller");
22698
+ const legacyExists = hasSection(original, "payments.x402_seller");
22699
+ const sellerExists = canonicalExists || legacyExists;
22700
+ const sellerSection = legacyExists ? "payments.x402_seller" : "payments.b402_seller";
22701
+ const paymentProtocol = b402PaymentProtocol(cfg);
21477
22702
  let priceUsd;
21478
22703
  try {
21479
22704
  priceUsd = priceFlag !== void 0 ? normalizeB402PriceUsd(priceFlag) : sellerExists ? X402SellerPolicy.fromToml(cfg).priceUsd : await resolveSellInitPrice(stdinIsTty());
@@ -21482,7 +22707,7 @@ async function cmdSellInit(priceFlag) {
21482
22707
  return 2;
21483
22708
  }
21484
22709
  const free = x402SellerPricingState({ price_usd: priceUsd }).kind === "free";
21485
- const walletKind2 = String(tableOf16(cfg, "wallet").kind ?? "evm-local");
22710
+ const walletKind2 = String(tableOf17(cfg, "wallet").kind ?? "evm-local");
21486
22711
  if (!free && !["evm-local", "twak", "turnkey", "altana"].includes(walletKind2)) {
21487
22712
  printErr(
21488
22713
  `error: the b402 seller rail supports wallet.kind evm-local, twak, turnkey or altana only; '${walletKind2}' cannot act as the b402 payout wallet.`
@@ -21490,9 +22715,9 @@ async function cmdSellInit(priceFlag) {
21490
22715
  return 1;
21491
22716
  }
21492
22717
  if (!sellerExists) {
21493
- fs44.writeFileSync(
22718
+ fs45.writeFileSync(
21494
22719
  tomlPath,
21495
- updateSection(original, "payments.x402_seller", {
22720
+ updateSection(original, sellerSection, {
21496
22721
  enabled: true,
21497
22722
  price_usd: priceUsd,
21498
22723
  assets: ["U"],
@@ -21502,9 +22727,9 @@ async function cmdSellInit(priceFlag) {
21502
22727
  "utf8"
21503
22728
  );
21504
22729
  } else if (priceFlag !== void 0) {
21505
- fs44.writeFileSync(
22730
+ fs45.writeFileSync(
21506
22731
  tomlPath,
21507
- updateSection(original, "payments.x402_seller", {
22732
+ updateSection(original, sellerSection, {
21508
22733
  price_usd: priceUsd
21509
22734
  }),
21510
22735
  "utf8"
@@ -21513,21 +22738,24 @@ async function cmdSellInit(priceFlag) {
21513
22738
  const envPath = envLocalPath19(root);
21514
22739
  for (const key of B402_ENV_KEYS) {
21515
22740
  const existing = getEnvVar(envPath, key);
21516
- if (key === "B402_BASE_URL" && !free && resolveNetwork3(cfg).toLowerCase() === "bsc-testnet" && !process.env.B402_BASE_URL && !existing) {
21517
- setEnvVar(envPath, key, DEFAULT_B402_TESTNET_BASE_URL);
21518
- } else if (existing === null) {
22741
+ if (existing === null) {
21519
22742
  setEnvVar(envPath, key, "");
21520
22743
  }
21521
22744
  }
22745
+ if (paymentProtocol === "mpp") {
22746
+ for (const key of ["MPP_SECRET_KEY", "MPP_REALM"]) {
22747
+ if (getEnvVar(envPath, key) === null) setEnvVar(envPath, key, "");
22748
+ }
22749
+ }
21522
22750
  printOut(
21523
- free ? "\u2713 x402 seller config is ready in FREE mode; B402 credentials are not required." : "\u2713 x402 seller config and B402 placeholders are ready for PAID mode."
22751
+ free ? `\u2713 ${paymentProtocol.toUpperCase()} seller config is ready in FREE mode; B402 credentials are not required.` : `\u2713 ${paymentProtocol.toUpperCase()} seller config and credential placeholders are ready for PAID mode.`
21524
22752
  );
21525
22753
  if (!free) {
21526
22754
  printOut(`Next: ${B402_PAID_ONBOARDING_GUIDANCE}`);
21527
22755
  }
21528
22756
  if (free) {
21529
22757
  printErr(
21530
- "warning: /x402 is an anonymous FREE endpoint; B402 verify/settle and payment audit are bypassed."
22758
+ `warning: /${paymentProtocol} is an anonymous FREE endpoint; B402 verify/settle and payment audit are bypassed.`
21531
22759
  );
21532
22760
  }
21533
22761
  return 0;
@@ -21552,7 +22780,7 @@ async function resolveSellInitPrice(isTty) {
21552
22780
  }
21553
22781
  return DEFAULT_B402_PRICE_USD;
21554
22782
  }
21555
- async function cmdSellStatus(probe) {
22783
+ async function cmdSellStatus(probe2) {
21556
22784
  const loaded = loadSellerConfig();
21557
22785
  if (loaded === null) return 1;
21558
22786
  const [, cfg] = loaded;
@@ -21560,7 +22788,7 @@ async function cmdSellStatus(probe) {
21560
22788
  try {
21561
22789
  policy = X402SellerPolicy.fromToml(cfg);
21562
22790
  } catch (exc) {
21563
- printErr(`error: invalid [payments.x402_seller]: ${errMsg6(exc)}`);
22791
+ printErr(`error: invalid B402 seller config: ${errMsg6(exc)}`);
21564
22792
  return 1;
21565
22793
  }
21566
22794
  const presence = {
@@ -21590,7 +22818,7 @@ async function cmdSellStatus(probe) {
21590
22818
  if (policy.enabled && !free && !credentialsComplete) {
21591
22819
  printOut(`Onboarding: ${B402_PAID_ONBOARDING_GUIDANCE}`);
21592
22820
  }
21593
- if (!probe) {
22821
+ if (!probe2) {
21594
22822
  printOut("B402 probe: skipped (--no-probe)");
21595
22823
  return 0;
21596
22824
  }
@@ -21602,9 +22830,9 @@ async function cmdSellStatus(probe) {
21602
22830
  printOut("B402 probe: skipped (rail disabled or credentials incomplete)");
21603
22831
  return 0;
21604
22832
  }
21605
- const networkName = resolveNetwork3(cfg);
21606
- const network = caip2(getNetwork11(networkName).chainId);
21607
- const token = SELLER_TOKENS[network];
22833
+ const networkName = resolveNetwork4(cfg);
22834
+ const network = caip22(getNetwork12(networkName).chainId);
22835
+ const token = SELLER_TOKENS2[network];
21608
22836
  if (!token) {
21609
22837
  printOut(`B402 probe: mismatch for ${network}; no pinned U token`);
21610
22838
  return 0;
@@ -21634,7 +22862,7 @@ async function cmdSellStatus(probe) {
21634
22862
  }
21635
22863
  return 0;
21636
22864
  }
21637
- async function cmdQuote(url, opts) {
22865
+ async function cmdQuote2(url, opts) {
21638
22866
  const loaded = loadX402Policy();
21639
22867
  if (loaded === null) {
21640
22868
  return 1;
@@ -21647,12 +22875,12 @@ async function cmdQuote(url, opts) {
21647
22875
  printErr(`error: ${errMsg6(exc)}`);
21648
22876
  return 2;
21649
22877
  }
21650
- if (opts.localDev && !isLoopbackHost(hostOf(url))) {
22878
+ if (opts.localDev && !isLoopbackHost(hostOf2(url))) {
21651
22879
  printErr("error: --local-dev applies to localhost/127.0.0.1 targets only.");
21652
22880
  return 2;
21653
22881
  }
21654
22882
  if (!opts.localDev && !checkHostAllowed(url, hosts)) {
21655
- printScopeError(hostOf(url) || url, url);
22883
+ printScopeError(hostOf2(url) || url, url);
21656
22884
  return 1;
21657
22885
  }
21658
22886
  let body;
@@ -21688,18 +22916,18 @@ async function cmdQuote(url, opts) {
21688
22916
  }
21689
22917
  return 0;
21690
22918
  }
21691
- async function cmdBuy2(url, opts) {
22919
+ async function cmdBuy3(url, opts) {
21692
22920
  const loaded = loadX402Policy();
21693
22921
  if (loaded === null) {
21694
22922
  return 1;
21695
22923
  }
21696
22924
  const [, cfg, hosts] = loaded;
21697
- if (opts.localDev && !isLoopbackHost(hostOf(url))) {
22925
+ if (opts.localDev && !isLoopbackHost(hostOf2(url))) {
21698
22926
  printErr("error: --local-dev applies to localhost/127.0.0.1 targets only.");
21699
22927
  return 2;
21700
22928
  }
21701
22929
  if (!opts.localDev && !checkHostAllowed(url, hosts)) {
21702
- printScopeError(hostOf(url) || url, url);
22930
+ printScopeError(hostOf2(url) || url, url);
21703
22931
  return 1;
21704
22932
  }
21705
22933
  let jsonBody;
@@ -21709,7 +22937,7 @@ async function cmdBuy2(url, opts) {
21709
22937
  printErr(`error: ${errMsg6(exc)}`);
21710
22938
  return 2;
21711
22939
  }
21712
- const networkName = resolveNetwork3(cfg, opts.network);
22940
+ const networkName = resolveNetwork4(cfg, opts.network);
21713
22941
  let localDevExpectedTo = null;
21714
22942
  if (opts.localDev) {
21715
22943
  let challenge;
@@ -21754,7 +22982,7 @@ async function cmdBuy2(url, opts) {
21754
22982
  );
21755
22983
  return 1;
21756
22984
  }
21757
- const buyWalletKind = String(tableOf16(cfg, "wallet").kind ?? "evm-local");
22985
+ const buyWalletKind = String(tableOf17(cfg, "wallet").kind ?? "evm-local");
21758
22986
  if (buyWalletKind === "altana") {
21759
22987
  let allowanceRaw;
21760
22988
  try {
@@ -21768,9 +22996,9 @@ async function cmdBuy2(url, opts) {
21768
22996
  return 1;
21769
22997
  }
21770
22998
  if (allowanceRaw !== null && allowanceRaw < neededRaw) {
21771
- const uToken = getNetwork11(networkName).token("U");
22999
+ const uToken = getNetwork12(networkName).token("U");
21772
23000
  printErr(
21773
- `error: U\u2192Permit2 allowance ${fromRaw5(allowanceRaw, uToken.decimals)} U cannot cover max_usd $${opts.maxUsd.toFixed(2)}: b402 settles Altana payments by pulling through Permit2 (${PERMIT2_ADDRESS}).`
23001
+ `error: U\u2192Permit2 allowance ${fromRaw6(allowanceRaw, uToken.decimals)} U cannot cover max_usd $${opts.maxUsd.toFixed(2)}: b402 settles Altana payments by pulling through Permit2 (${PERMIT2_ADDRESS}).`
21774
23002
  );
21775
23003
  printErr(
21776
23004
  "fix: bag wallet session x402-setup --allowance-u <amount> (admin key; approves the x402 signature checker and sets the bounded Permit2 allowance)"
@@ -21783,7 +23011,7 @@ async function cmdBuy2(url, opts) {
21783
23011
  result = await auditedOp4(
21784
23012
  "x402_buy",
21785
23013
  {
21786
- chainId: getNetwork11(networkName).chainId,
23014
+ chainId: getNetwork12(networkName).chainId,
21787
23015
  asset: "U",
21788
23016
  amount: String(opts.maxUsd),
21789
23017
  agentAddr: wallet.address,
@@ -21810,7 +23038,7 @@ async function cmdBuy2(url, opts) {
21810
23038
  );
21811
23039
  } catch (exc) {
21812
23040
  printX402Error(exc);
21813
- if (errName4(exc) === "X402RecipientRequiredError" && isLoopbackHost(hostOf(url))) {
23041
+ if (errName4(exc) === "X402RecipientRequiredError" && isLoopbackHost(hostOf2(url))) {
21814
23042
  printLocalDevHint(url);
21815
23043
  }
21816
23044
  return 1;
@@ -21880,7 +23108,7 @@ async function probeChallenge(url, methodHint) {
21880
23108
  return null;
21881
23109
  }
21882
23110
  function selectPayable(challenge, networkName) {
21883
- const network = getNetwork11(networkName);
23111
+ const network = getNetwork12(networkName);
21884
23112
  const uToken = network.token("U");
21885
23113
  let matched;
21886
23114
  try {
@@ -21910,21 +23138,26 @@ async function confirmOrAbort(prompt, assumeYes) {
21910
23138
  const answer = (await promptUser(`${prompt} [y/N] `)).trim().toLowerCase();
21911
23139
  return answer === "y" || answer === "yes";
21912
23140
  }
21913
- function writeMerchantEntry(tomlPath, fields) {
21914
- const text2 = fs44.readFileSync(tomlPath, "utf-8");
23141
+ function writeMerchantEntry(tomlPath, cfg, fields) {
23142
+ const text2 = fs45.readFileSync(tomlPath, "utf-8");
21915
23143
  const entries = {
21916
23144
  domain: fields.domain,
21917
23145
  pay_to: fields.payTo,
21918
23146
  per_call_cap_usd: fields.capUsd,
21919
23147
  verified: fields.verified
21920
23148
  };
21921
- fs44.writeFileSync(
23149
+ const withMerchant = updateSection(
23150
+ text2,
23151
+ `payments.x402.merchants.${fields.key}`,
23152
+ entries
23153
+ );
23154
+ fs45.writeFileSync(
21922
23155
  tomlPath,
21923
- updateSection(text2, `payments.x402.merchants.${fields.key}`, entries),
23156
+ trustEip712Domain(withMerchant, cfg, fields.network, fields.asset),
21924
23157
  "utf-8"
21925
23158
  );
21926
23159
  }
21927
- async function cmdTrust(merchant, opts) {
23160
+ async function cmdTrust2(merchant, opts) {
21928
23161
  const loaded = loadX402Policy();
21929
23162
  if (loaded === null) {
21930
23163
  return 1;
@@ -21936,7 +23169,7 @@ async function cmdTrust(merchant, opts) {
21936
23169
  let methodHint;
21937
23170
  if (merchant.includes("://")) {
21938
23171
  probeUrl = merchant;
21939
- host = hostOf(probeUrl);
23172
+ host = hostOf2(probeUrl);
21940
23173
  const plainHttp = probeUrl.toLowerCase().startsWith("http://");
21941
23174
  const localDevHttp = plainHttp && isLoopbackHost(host);
21942
23175
  if (!probeUrl.toLowerCase().startsWith("https://") && !localDevHttp || !host) {
@@ -21964,7 +23197,7 @@ async function cmdTrust(merchant, opts) {
21964
23197
  host = rec.domain;
21965
23198
  methodHint = rec.probeMethod;
21966
23199
  }
21967
- const networkName = opts.network || (rec ? rec.network : resolveNetwork3(cfg, null));
23200
+ const networkName = opts.network || (rec ? rec.network : resolveNetwork4(cfg, null));
21968
23201
  const probed = await probeChallenge(probeUrl, methodHint);
21969
23202
  if (probed === null) {
21970
23203
  return 1;
@@ -21977,7 +23210,7 @@ async function cmdTrust(merchant, opts) {
21977
23210
  const [accepted, uToken] = selected2;
21978
23211
  const livePayTo = String(accepted.payTo);
21979
23212
  const priceUsd = Number.parseFloat(
21980
- fromRaw5(BigInt(String(accepted.amount)), uToken.decimals)
23213
+ fromRaw6(BigInt(String(accepted.amount)), uToken.decimals)
21981
23214
  );
21982
23215
  let pinned;
21983
23216
  if (opts.payTo) {
@@ -22029,7 +23262,7 @@ The merchant may have rotated its address (or the response was tampered with). U
22029
23262
  " \u26A0 MAINNET: calls spend real $U from the agent wallet. The cap above and [budget].max_per_day_usd bound the exposure."
22030
23263
  );
22031
23264
  }
22032
- const projectDefault = tableOf16(cfg, "network").default;
23265
+ const projectDefault = tableOf17(cfg, "network").default;
22033
23266
  if (projectDefault && String(projectDefault) !== networkName) {
22034
23267
  printOut(
22035
23268
  ` note: your project default is ${projectDefault}; this paid capability settles on ${networkName} independently of it.`
@@ -22047,9 +23280,11 @@ The merchant may have rotated its address (or the response was tampered with). U
22047
23280
  printOut("aborted \u2014 nothing written.");
22048
23281
  return 1;
22049
23282
  }
22050
- const tomlPath = path46.join(root, "studio.toml");
23283
+ const tomlPath = path47.join(root, "studio.toml");
22051
23284
  try {
22052
- writeMerchantEntry(tomlPath, {
23285
+ writeMerchantEntry(tomlPath, cfg, {
23286
+ network: String(accepted.network),
23287
+ asset: String(accepted.asset),
22053
23288
  key,
22054
23289
  domain: host,
22055
23290
  payTo: pinned,
@@ -22103,6 +23338,7 @@ function buildProgram() {
22103
23338
  registerErc8004(program);
22104
23339
  registerErc8183(program);
22105
23340
  registerX402(program);
23341
+ registerMpp(program);
22106
23342
  registerLlm(program);
22107
23343
  registerDoctor(program);
22108
23344
  registerPlatform(program);
@@ -22114,19 +23350,19 @@ function cliVersion() {
22114
23350
  }
22115
23351
 
22116
23352
  // src/cli/updateCheck.ts
22117
- import * as fs45 from "fs";
23353
+ import * as fs46 from "fs";
22118
23354
  import * as os7 from "os";
22119
- import * as path47 from "path";
23355
+ import * as path48 from "path";
22120
23356
  var PACKAGE = "@bnbagent/studio-cli";
22121
23357
  var REGISTRY_LATEST_URL = `https://registry.npmjs.org/${PACKAGE}/latest`;
22122
23358
  var CHECK_INTERVAL_SECONDS = 24 * 3600;
22123
23359
  var TIMEOUT_MS = 2e3;
22124
23360
  function cachePath(home) {
22125
- return path47.join(home, ".bag", "update-check.json");
23361
+ return path48.join(home, ".bag", "update-check.json");
22126
23362
  }
22127
23363
  function loadCache(home) {
22128
23364
  try {
22129
- const data = JSON.parse(fs45.readFileSync(cachePath(home), "utf-8"));
23365
+ const data = JSON.parse(fs46.readFileSync(cachePath(home), "utf-8"));
22130
23366
  return data !== null && typeof data === "object" && !Array.isArray(data) ? data : {};
22131
23367
  } catch {
22132
23368
  return {};
@@ -22135,8 +23371,8 @@ function loadCache(home) {
22135
23371
  function saveCache(state, home) {
22136
23372
  try {
22137
23373
  const p = cachePath(home);
22138
- fs45.mkdirSync(path47.dirname(p), { recursive: true });
22139
- fs45.writeFileSync(p, `${JSON.stringify(state, null, 2)}
23374
+ fs46.mkdirSync(path48.dirname(p), { recursive: true });
23375
+ fs46.writeFileSync(p, `${JSON.stringify(state, null, 2)}
22140
23376
  `);
22141
23377
  } catch {
22142
23378
  }