@bnbagent/studio-cli 0.0.9 → 0.0.11-alpha.1

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 (36) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +2 -2
  3. package/dist/_agentcoreName-DZDWEYD3.js +0 -0
  4. package/dist/_twak-4XF4H5PL.js +0 -0
  5. package/dist/bag.js +577 -370
  6. package/dist/chunk-RO726HJG.js +0 -0
  7. package/dist/{chunk-YFEM4564.js → chunk-TTPOH453.js} +79 -37
  8. package/dist/chunk-U7IDQ3K5.js +0 -0
  9. package/dist/{deployCli-NJFCWBSF.js → deployCli-K55GXDVO.js} +1 -1
  10. package/package.json +11 -12
  11. package/recipes/runtimes/agentcore/code/{{PKG}}/Dockerfile.tmpl +20 -21
  12. package/recipes/runtimes/agentcore/code/{{PKG}}/executor.ts.tmpl +36 -0
  13. package/recipes/runtimes/agentcore/code/{{PKG}}/unifiedMain.ts.tmpl +620 -0
  14. package/recipes/runtimes/agentcore/recipe.toml +3 -3
  15. package/recipes/runtimes/azure-foundry/code/{{PKG}}/Dockerfile.tmpl +25 -23
  16. package/recipes/runtimes/azure-foundry/code/{{PKG}}/agentCard.ts.tmpl +16 -12
  17. package/recipes/runtimes/azure-foundry/code/{{PKG}}/executor.ts.tmpl +72 -393
  18. package/recipes/runtimes/azure-foundry/code/{{PKG}}/mcpMain.ts.tmpl +160 -43
  19. package/recipes/runtimes/azure-foundry/code/{{PKG}}/sellerCore.ts.tmpl +504 -0
  20. package/recipes/runtimes/azure-foundry/code/{{PKG}}/unifiedMain.ts.tmpl +620 -0
  21. package/recipes/runtimes/azure-foundry/recipe.toml +19 -11
  22. package/recipes/x402-buyer/code/{{PKG}}/x402Buyer.ts.tmpl +6 -4
  23. package/skills/bnbagent-studio.md +2 -2
  24. package/skills/references/bnbagent-studio-adding-to-project.md +1 -1
  25. package/skills/references/bnbagent-studio-buying-from-bazaar.md +1 -1
  26. package/skills/references/bnbagent-studio-operating.md +4 -4
  27. package/skills/references/bnbagent-studio-scaffolding-agent.md +4 -4
  28. package/skills/references/bnbagent-studio-selling-via-8183.md +3 -3
  29. package/skills/references/bnbagent-studio-selling-via-b402.md +2 -2
  30. package/skills/references/bnbagent-studio-use-aws-agentcore.md +1 -1
  31. package/skills/references/bnbagent-studio-use-azure-foundry.md +3 -3
  32. package/skills/references/bnbagent-studio-use-bnb-trial.md +1 -1
  33. package/skills/references/bnbagent-studio-wiring-llm-tools.md +3 -3
  34. package/recipes/runtimes/agentcore/code/{{PKG}}/main.ts.tmpl +0 -347
  35. package/recipes/runtimes/azure-foundry/code/{{PKG}}/foundryMain.ts.tmpl +0 -422
  36. package/recipes/runtimes/azure-foundry/code/{{PKG}}/main.ts.tmpl +0 -196
File without changes
@@ -27,6 +27,11 @@ import { build as esbuildBuild } from "esbuild";
27
27
 
28
28
  // src/cli/utils/protocol.ts
29
29
  var SUPPORTED_FACES = ["A2A", "MCP", "X402"];
30
+ var LEGACY_ENTRY_STEMS = {
31
+ unifiedMain: ["main", "foundryMain"],
32
+ mcpMain: [],
33
+ dualMain: []
34
+ };
30
35
  function table(value) {
31
36
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
32
37
  }
@@ -95,7 +100,7 @@ function entryStemOf(faces) {
95
100
  if (hasA2aFace(faces) && hasMcpFace(faces)) {
96
101
  return "dualMain";
97
102
  }
98
- return nativeProtocolOf(faces) === "MCP" ? "mcpMain" : "main";
103
+ return nativeProtocolOf(faces) === "MCP" ? "mcpMain" : "unifiedMain";
99
104
  }
100
105
  function devPortOf(faces) {
101
106
  return nativeProtocolOf(faces) === "MCP" ? 8e3 : 9e3;
@@ -151,12 +156,16 @@ async function buildZip(agentDir, outZip, opts = {}) {
151
156
  return { checksum: sha256File(outZip), entrypoint };
152
157
  }
153
158
  function resolveEntry(agentDir, stem) {
154
- const candidates = [
155
- path.join(agentDir, "src", `${stem}.ts`),
156
- path.join(agentDir, "dist", `${stem}.js`),
157
- path.join(agentDir, `${stem}.ts`),
158
- path.join(agentDir, `${stem}.js`)
159
+ const stems = [
160
+ stem,
161
+ ...LEGACY_ENTRY_STEMS[stem] ?? []
159
162
  ];
163
+ const candidates = stems.flatMap((s) => [
164
+ path.join(agentDir, "src", `${s}.ts`),
165
+ path.join(agentDir, "dist", `${s}.js`),
166
+ path.join(agentDir, `${s}.ts`),
167
+ path.join(agentDir, `${s}.js`)
168
+ ]);
160
169
  for (const candidate of candidates) {
161
170
  if (isFile(candidate)) {
162
171
  return candidate;
@@ -166,29 +175,49 @@ function resolveEntry(agentDir, stem) {
166
175
  `no ${stem}.ts/${stem}.js entry found under ${agentDir} (looked in src/, dist/, and the project root) \u2014 the zip channel bundles the protocol entry the scaffold emitted. Re-scaffold with \`bag init\`, or switch [deploy].platform_artifact to "container".`
167
176
  );
168
177
  }
178
+ var BUNDLE_OPTIONS = {
179
+ bundle: true,
180
+ platform: "node",
181
+ target: TARGET_NODE,
182
+ format: "esm",
183
+ legalComments: "none",
184
+ sourcemap: false,
185
+ logLevel: "silent",
186
+ banner: {
187
+ js: 'import { createRequire as __bagCreateRequire } from "node:module";const require = __bagCreateRequire(import.meta.url);'
188
+ }
189
+ };
190
+ function bundleFailure(exc) {
191
+ const msg = exc instanceof Error ? exc.message : String(exc);
192
+ return new Error(
193
+ `bundling the agent for the zip runtime failed \u2014 a dependency likely cannot be bundled (native addon / dynamic import esbuild cannot resolve). Fix the import, or set [deploy].platform_artifact = "container". esbuild said:
194
+ ${msg.slice(0, 1e3)}`
195
+ );
196
+ }
169
197
  async function bundleEntry(entry, outfile) {
170
198
  try {
171
199
  await esbuildBuild({
200
+ ...BUNDLE_OPTIONS,
172
201
  entryPoints: [entry],
173
202
  outfile,
174
- bundle: true,
175
- platform: "node",
176
- target: TARGET_NODE,
177
- format: "esm",
178
- minify: true,
179
- legalComments: "none",
180
- sourcemap: false,
181
- logLevel: "silent",
182
- banner: {
183
- js: 'import { createRequire as __bagCreateRequire } from "node:module";const require = __bagCreateRequire(import.meta.url);'
184
- }
203
+ minify: true
185
204
  });
186
205
  } catch (exc) {
187
- const msg = exc instanceof Error ? exc.message : String(exc);
188
- throw new Error(
189
- `bundling the agent for the zip runtime failed \u2014 a dependency likely cannot be bundled (native addon / dynamic import esbuild cannot resolve). Fix the import, or set [deploy].platform_artifact = "container". esbuild said:
190
- ${msg.slice(0, 1e3)}`
191
- );
206
+ throw bundleFailure(exc);
207
+ }
208
+ }
209
+ async function dryRunBundle(agentDir, opts = {}) {
210
+ const faces = opts.protocols ?? normalizeProtocolFaces(opts.protocol ?? "A2A");
211
+ const entry = resolveEntry(agentDir, entryStemOf(faces));
212
+ try {
213
+ await esbuildBuild({
214
+ ...BUNDLE_OPTIONS,
215
+ entryPoints: [entry],
216
+ write: false,
217
+ minify: false
218
+ });
219
+ } catch (exc) {
220
+ throw bundleFailure(exc);
192
221
  }
193
222
  }
194
223
  function stageManifest(agentDir, buildRoot, entrypoint) {
@@ -714,6 +743,10 @@ var B402_RUNTIME_KEYS = [
714
743
  "B402_PRIVATE_KEY",
715
744
  "B402_PRIVATE_KEY_B64"
716
745
  ];
746
+ var X402_CAPABLE_RUNTIMES = /* @__PURE__ */ new Set([
747
+ "agentcore",
748
+ "azure-foundry"
749
+ ]);
717
750
  function commerceRails(cfg) {
718
751
  const payments = table3(cfg.payments);
719
752
  return {
@@ -760,21 +793,25 @@ function x402DeploySummary(root, destination, publicUrl) {
760
793
  const credentials = b402Credentials(agentRoot);
761
794
  const activation = `${B402_PAID_ONBOARDING_GUIDANCE} Then run the bnbagent-studio-selling-via-b402 skill, fill the four B402_* variables in .studio/.env.local, and redeploy.`;
762
795
  const runtime = String(table3(cfg.stack).runtime ?? "agentcore");
763
- if (destination !== "platform" && runtime !== "agentcore") {
764
- return `x402 rail is FORCED DORMANT: the ${runtime} runtime has no x402 path. Deploy to AgentCore (managed platform or self-hosted) to activate the rail.`;
796
+ if (destination !== "platform" && !X402_CAPABLE_RUNTIMES.has(runtime)) {
797
+ return `x402 rail is FORCED DORMANT: the ${runtime} runtime has no x402 path. Deploy to AgentCore or Azure Foundry (managed platform or self-hosted) to activate the rail.`;
765
798
  }
799
+ const azure = runtime === "azure-foundry";
800
+ const runtimeLabel = azure ? "Azure Foundry" : "AgentCore";
801
+ const tunnelLine = azure ? 'The front wraps each request as envelope-v1 JSON, {"v":1,"method":"POST","path":"/x402","headers":{...},"body":"<base64>"}, inside an Entra-authenticated Foundry invocation (POST \u2026/agents/<name>/endpoint/protocols/invocations with an https://ai.azure.com bearer). Limits: 1 MiB request, 5 MiB response, no streaming.' : 'The front wraps each request as envelope-v1 JSON, {"v":1,"method":"POST","path":"/x402","headers":{...},"body":"<base64>"}, inside an authenticated AgentCore invocation. The default Bag self-deploy uses Cognito OAuth over HTTPS; IAM runtimes may use SDK/SigV4. Limits: 1 MiB request, 5 MiB response, no streaming.';
802
+ const gatewayDocLine = azure ? "Gateway example: https://github.com/bnb-chain/bnbagent-studio/blob/main/docs/guides/self-hosted-x402-gateway-azure.md" : "Gateway example: https://github.com/bnb-chain/bnbagent-studio/blob/main/docs/guides/self-hosted-x402-gateway.md";
766
803
  if (pricing.kind === "invalid") {
767
804
  return "x402 rail is DORMANT: price_usd is invalid; run `bag doctor`.";
768
805
  }
769
806
  if (pricing.kind === "free") {
770
807
  if (destination !== "platform") {
771
808
  return [
772
- "x402 rail is ACTIVE in FREE mode (self-hosted AgentCore).",
809
+ `x402 rail is ACTIVE in FREE mode (self-hosted ${runtimeLabel}).`,
773
810
  "B402 verify/settle is bypassed; no credentials, token payment, or settlement audit is used.",
774
811
  "This target has no anonymous public URL; expose /x402 with your own HTTP front.",
775
- 'The front wraps each request as envelope-v1 JSON, {"v":1,"method":"POST","path":"/x402","headers":{...},"body":"<base64>"}, inside an authenticated AgentCore invocation. The default Bag self-deploy uses Cognito OAuth over HTTPS; IAM runtimes may use SDK/SigV4. Limits: 1 MiB request, 5 MiB response, no streaming.',
776
- "You may invoke AgentCore with envelope-v1 directly, but that is not a standard public x402 HTTP endpoint.",
777
- "Gateway example: https://github.com/bnb-chain/bnbagent-studio/blob/main/docs/guides/self-hosted-x402-gateway.md"
812
+ tunnelLine,
813
+ `You may invoke ${runtimeLabel} with envelope-v1 directly, but that is not a standard public x402 HTTP endpoint.`,
814
+ gatewayDocLine
778
815
  ].join("\n");
779
816
  }
780
817
  const url2 = publicUrl ?? "<available after the platform agentId is assigned>";
@@ -793,13 +830,14 @@ function x402DeploySummary(root, destination, publicUrl) {
793
830
  "Compatibility note: current @bnb-chain/b402@0.1.0 makes one settle call and classifies an asynchronous pending response as outcome unknown; reconcile it and do not replay the paid request."
794
831
  ];
795
832
  if (destination !== "platform") {
833
+ const egressLine = azure ? "PAID also needs fixed facilitator egress. Host the gateway on Azure Container Apps in a workload-profiles environment with a VNet + NAT Gateway static IP and allowlist that IP with B402 \u2014 no relay needed." : "PAID also needs fixed facilitator egress. Operate a restricted B402 Relay on a fixed-IP host such as a user-managed VPS, or use AgentCore VPC mode with a private subnet, NAT Gateway, and Elastic IP.";
796
834
  return [
797
- "x402 rail is ACTIVE (self-hosted AgentCore).",
835
+ `x402 rail is ACTIVE (self-hosted ${runtimeLabel}).`,
798
836
  "This target has no anonymous public URL; expose /x402 with your own HTTP front.",
799
- 'The front wraps each request as envelope-v1 JSON, {"v":1,"method":"POST","path":"/x402","headers":{...},"body":"<base64>"}, inside an authenticated AgentCore invocation. The default Bag self-deploy uses Cognito OAuth over HTTPS; IAM runtimes may use SDK/SigV4. Limits: 1 MiB request, 5 MiB response, no streaming.',
800
- "You may invoke AgentCore with envelope-v1 directly, but the caller must unpack the inner HTTP response and the result is not a standard public x402 endpoint.",
801
- "PAID also needs fixed facilitator egress. Operate a restricted B402 Relay on a fixed-IP host such as a user-managed VPS, or use AgentCore VPC mode with a private subnet, NAT Gateway, and Elastic IP.",
802
- "Gateway and Relay examples: https://github.com/bnb-chain/bnbagent-studio/blob/main/docs/guides/self-hosted-x402-gateway.md",
837
+ tunnelLine,
838
+ `You may invoke ${runtimeLabel} with envelope-v1 directly, but the caller must unpack the inner HTTP response and the result is not a standard public x402 endpoint.`,
839
+ egressLine,
840
+ gatewayDocLine.replace("Gateway example:", "Gateway and Relay examples:"),
803
841
  ...settlement
804
842
  ].join("\n");
805
843
  }
@@ -808,7 +846,7 @@ function x402DeploySummary(root, destination, publicUrl) {
808
846
  "x402 rail is ACTIVE.",
809
847
  `Anonymous paid URL: ${url}`,
810
848
  "The managed platform supplies the x402 HTTP Gateway and the fixed-egress B402 Relay; no user-managed gateway, proxy, or VPC is required.",
811
- "Payment policy and RSA signing remain inside the AgentCore seller. The B402 Relay only forwards the signed request to its fixed facilitator upstream.",
849
+ `Payment policy and RSA signing remain inside the ${runtimeLabel} seller. The B402 Relay only forwards the signed request to its fixed facilitator upstream.`,
812
850
  ...settlement
813
851
  ].join("\n");
814
852
  }
@@ -820,7 +858,7 @@ function isTable(value) {
820
858
  }
821
859
 
822
860
  // src/cli/_deploy/deployCli.ts
823
- var DEPLOY_CLI_VERSION = "0.5.4";
861
+ var DEPLOY_CLI_VERSION = "0.5.10";
824
862
  var DEPLOY_CLI_PACKAGE = `@bnbagent/deploy-cli@${DEPLOY_CLI_VERSION}`;
825
863
  var BNB_PLATFORM_API_URL = "https://bnbagent-api.bnbchain.world";
826
864
  var BNB_PLATFORM_API_URL_ENV = "BNBAGENT_API_URL";
@@ -1002,7 +1040,7 @@ function buildDeploySpec(root, opts) {
1002
1040
  if (opts.inlineSecrets) {
1003
1041
  Object.assign(env, opts.inlineSecrets);
1004
1042
  }
1005
- if (opts.target === "bnb/trial" && tableOf(studio, "deploy").destination === "platform" && (!hasProtocolsArray || hasX402Face(faces)) && commerceRails(studio).x402 && (x402SellerIsFree(studio) || b402Credentials(agentRoot).complete)) {
1043
+ if (opts.target === "bnb/trial" && (!hasProtocolsArray || hasX402Face(faces)) && commerceRails(studio).x402 && (x402SellerIsFree(studio) || b402Credentials(agentRoot).complete)) {
1006
1044
  doc.x402 = {
1007
1045
  publicPaths: ["/x402"],
1008
1046
  tunnel: "http-envelope-v1"
@@ -1014,7 +1052,7 @@ function buildDeploySpec(root, opts) {
1014
1052
  tunnel: "http-envelope-v1"
1015
1053
  };
1016
1054
  }
1017
- if (opts.target === "bnb/trial" && tableOf(studio, "deploy").destination === "platform" && hasProtocolsArray && faces.length === 1 && hasX402Face(faces)) {
1055
+ if (opts.target === "bnb/trial" && hasProtocolsArray && faces.length === 1 && hasX402Face(faces)) {
1018
1056
  doc.suppressProtocolFace = true;
1019
1057
  }
1020
1058
  doc.env = env;
@@ -1214,6 +1252,7 @@ export {
1214
1252
  act,
1215
1253
  printOut,
1216
1254
  printErr,
1255
+ LEGACY_ENTRY_STEMS,
1217
1256
  normalizeProtocolFaces,
1218
1257
  stackFaces,
1219
1258
  hasA2aFace,
@@ -1223,6 +1262,7 @@ export {
1223
1262
  entryStemOf,
1224
1263
  devPortOf,
1225
1264
  recipeModeOf,
1265
+ dryRunBundle,
1226
1266
  DEFAULT_B402_PRICE_USD,
1227
1267
  DEFAULT_B402_TESTNET_BASE_URL,
1228
1268
  B402_DEVELOPER_ACCOUNT_URL,
@@ -1234,6 +1274,7 @@ export {
1234
1274
  x402SellerIsFree,
1235
1275
  whichBin,
1236
1276
  agentcoreFlavor,
1277
+ checkBunx,
1237
1278
  checkDocker,
1238
1279
  checkTwak,
1239
1280
  requiredChecks,
@@ -1241,6 +1282,7 @@ export {
1241
1282
  render,
1242
1283
  readAwsTarget,
1243
1284
  B402_RUNTIME_KEYS,
1285
+ X402_CAPABLE_RUNTIMES,
1244
1286
  commerceRails,
1245
1287
  b402Credentials,
1246
1288
  x402DeploySummary,
File without changes
@@ -18,7 +18,7 @@ import {
18
18
  runPlatformAccountCommand,
19
19
  trialFromDeployCliJson,
20
20
  withDeployFiles
21
- } from "./chunk-YFEM4564.js";
21
+ } from "./chunk-TTPOH453.js";
22
22
  import "./chunk-RO726HJG.js";
23
23
  export {
24
24
  BNB_PLATFORM_API_URL,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bnbagent/studio-cli",
3
- "version": "0.0.9",
3
+ "version": "0.0.11-alpha.1",
4
4
  "description": "Skills-first toolkit and bag CLI for BNB Chain seller agents: ERC-8004 identity, ERC-8183 escrowed commerce, and x402 payments.",
5
5
  "keywords": [
6
6
  "bnb-chain",
@@ -25,7 +25,6 @@
25
25
  "directory": "packages/studio-cli"
26
26
  },
27
27
  "type": "module",
28
- "packageManager": "pnpm@10.24.0",
29
28
  "engines": {
30
29
  "node": ">=22"
31
30
  },
@@ -42,16 +41,8 @@
42
41
  "bin": {
43
42
  "bag": "./dist/bag.js"
44
43
  },
45
- "scripts": {
46
- "build": "tsup",
47
- "test": "vitest run",
48
- "lint": "biome check src tests",
49
- "typecheck": "tsc --noEmit",
50
- "check": "pnpm typecheck && pnpm lint && pnpm test && pnpm build"
51
- },
52
44
  "dependencies": {
53
45
  "@bnbagent/sdk": "0.5.0",
54
- "@bnbagent/studio-runtime": "0.0.9",
55
46
  "ai": "^7.0.29",
56
47
  "archiver": "^8.0.0",
57
48
  "commander": "^15.0.0",
@@ -62,7 +53,8 @@
62
53
  "smol-toml": "^1.3.0",
63
54
  "tar": "^7.4.0",
64
55
  "viem": "^2.54.0",
65
- "yaml": "^2.9.0"
56
+ "yaml": "^2.9.0",
57
+ "@bnbagent/studio-runtime": "0.0.11-alpha.1"
66
58
  },
67
59
  "devDependencies": {
68
60
  "@a2a-js/sdk": "^0.3.14",
@@ -78,5 +70,12 @@
78
70
  "typescript": "^5.5.0",
79
71
  "vitest": "^2.0.0",
80
72
  "zod": "^3.25.76"
73
+ },
74
+ "scripts": {
75
+ "build": "tsup",
76
+ "test": "vitest run",
77
+ "lint": "biome check src tests",
78
+ "typecheck": "tsc --noEmit",
79
+ "check": "pnpm typecheck && pnpm lint && pnpm test && pnpm build"
81
80
  }
82
- }
81
+ }
@@ -1,18 +1,19 @@
1
- # syntax=docker/dockerfile:1
2
- # Agent image for wallet.kind = "twak" — SHIPPED BY `bag init`.
1
+ # Agent image — SHIPPED BY `bag init`, ONE Dockerfile for BOTH deploy clouds.
3
2
  #
4
- # Why a custom image: the managed AgentCore Node runtime (CodeZip) ships only
5
- # the project's own bundle, and every signing / on-chain intent for a twak
6
- # wallet shells out to the `twak` CLI (npm @trustwallet/cli), which must be
7
- # installed globally in the image. agentcore.json points at this file via
8
- # runtimes[].build = "Container" + dockerfile = "Dockerfile"; `bag deploy
9
- # --provider aws` hands it to the pinned bnbagent-deploy, which builds the image
10
- # locally with Docker and pushes it to ECR.
3
+ # Why a custom image: the managed Node runtimes ship only the project's own
4
+ # bundle, and every signing / on-chain intent for a twak wallet shells out to
5
+ # the `twak` CLI (npm @trustwallet/cli), which must be installed globally in
6
+ # the image. `bag deploy` hands this file to the pinned bnbagent-deploy,
7
+ # which builds the image locally with Docker and pushes it to the target
8
+ # cloud's registry (ECR for AgentCore, ACR for Azure Foundry).
11
9
  #
12
- # AgentCore runtimes execute on linux/arm64 pin the platform so an x86
13
- # deploy machine cross-builds the right arch (needs docker buildx/containerd).
14
- # buildkit may warn "FromPlatformFlagConstDisallowed" expected; arm64 is pinned intentionally (see above).
15
- FROM --platform=linux/arm64 public.ecr.aws/docker/library/node:22-slim
10
+ # Arch is NOT pinned here: each deploy provider pins it on the build command
11
+ # (`--platform linux/arm64` for AgentCore, `--platform linux/amd64` for
12
+ # Foundry), so the same Dockerfile cross-builds for either cloud. Keep this
13
+ # file classic-builder compatible: no `# syntax=` directive and no
14
+ # BuildKit-only instructions (`RUN --mount=...`), so a plain `docker build`
15
+ # works everywhere.
16
+ FROM public.ecr.aws/docker/library/node:22-slim
16
17
 
17
18
  # The pinned twak CLI.
18
19
  #
@@ -29,8 +30,7 @@ WORKDIR /app
29
30
  # present; a project without one falls back to a plain install.
30
31
  COPY package.json pnpm-lock.yaml* pnpm-workspace.yaml ./
31
32
  COPY vendor ./vendor
32
- RUN --mount=type=cache,target=/root/.local/share/pnpm/store \
33
- pnpm install
33
+ RUN pnpm install
34
34
 
35
35
  COPY . .
36
36
  RUN pnpm build
@@ -38,13 +38,12 @@ RUN pnpm build
38
38
  # Runtime deps only in the final tree (drops typescript etc.).
39
39
  RUN pnpm prune --prod
40
40
 
41
- # AgentCore protocol contract:
42
- # A2A 0.0.0.0:9000 at /
43
- # MCP → 0.0.0.0:8000/mcp
44
- # BOTHA2A-native 0.0.0.0:9000 at / + tunneled MCP at /mcp
41
+ # Hosted-runtime port contracts (the unified entrypoint binds BOTH 9000 and
42
+ # 8088 itself; AGENT_PORT only prepends a primary override):
43
+ # AgentCore A2A → 0.0.0.0:9000 at / MCP → 0.0.0.0:8000/mcp
44
+ # Foundry invocations/responses → 0.0.0.0:8088
45
45
  # `bag init` renders {{CONTAINER_PORT}} / {{ENTRYPOINT}} from the selected
46
- # protocol so the image build never falls back to the HTTP-only 8080 contract.
47
- # ({{ENTRYPOINT}} is the BUILT entrypoint, e.g. dist/main.js.)
46
+ # protocol. ({{ENTRYPOINT}} is the BUILT entrypoint, e.g. dist/unifiedMain.js.)
48
47
  ENV AGENT_PORT={{CONTAINER_PORT}}
49
48
  EXPOSE {{CONTAINER_PORT}}
50
49
  CMD ["node", "{{ENTRYPOINT}}"]
@@ -64,6 +64,42 @@ const log = {
64
64
  * so prose never triggers an LLM call or a paid action.
65
65
  */
66
66
  export class SellerAgentExecutor extends SellerCore implements AgentExecutor {
67
+ /**
68
+ * Text-carrier entrypoint (Foundry invocations / responses SkillRouter).
69
+ *
70
+ * Same skill switch as {@link execute}, but NEVER throws: on a text
71
+ * carrier there is no JSON-RPC error channel, so a fault is returned as an
72
+ * `{"error": ...}` dict and the caller can always reply. The A2A path
73
+ * keeps its own switch below because its fault semantics differ (faults
74
+ * become JSON-RPC -32603 via A2AError).
75
+ */
76
+ async dispatch(
77
+ data: Record<string, unknown>,
78
+ ): Promise<Record<string, unknown>> {
79
+ const skill = data.skill;
80
+ try {
81
+ if (skill === "negotiate") {
82
+ return await this.negotiate(data);
83
+ }
84
+ if (skill === "notify_funded") {
85
+ return await this.notifyFunded(data);
86
+ }
87
+ // Includes a plain text message (no skill envelope → skill is
88
+ // undefined): the seller has no free-form skill, so prose is rejected
89
+ // here.
90
+ return {
91
+ error: `unknown skill: ${JSON.stringify(skill)}`,
92
+ skills: this.skills(),
93
+ };
94
+ } catch (e) {
95
+ // a skill failure must still ACK the buyer
96
+ log.error(`skill ${JSON.stringify(skill)} failed`, e);
97
+ const name = e instanceof Error ? e.constructor.name : "Error";
98
+ const msg = e instanceof Error ? e.message : String(e);
99
+ return { error: `${name}: ${msg}`, skill };
100
+ }
101
+ }
102
+
67
103
  // ── A2A entrypoints ───────────────────────────────────────────────────────
68
104
  execute = async (
69
105
  context: RequestContext,