@bnbagent/studio-cli 0.0.13-alpha.8 → 0.0.14-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 (29) hide show
  1. package/README.md +8 -0
  2. package/dist/bag.js +3556 -1565
  3. package/dist/{chunk-NTDWVEW2.js → chunk-QVYWAJEK.js} +228 -72
  4. package/dist/{deployCli-ZESBUWQB.js → deployCli-F3AOM5UO.js} +1 -2
  5. package/package.json +2 -2
  6. package/recipes/agent/code/{{PKG}}/deliveryPolicy.ts.tmpl +43 -0
  7. package/recipes/agent/code/{{PKG}}/readToolCatalog.ts.tmpl +102 -0
  8. package/recipes/agent/code/{{PKG}}/signing.ts.tmpl +7 -2
  9. package/recipes/agent/recipe.toml +4 -3
  10. package/recipes/runtimes/agentcore/code/{{PKG}}/dualMain.ts.tmpl +6 -2
  11. package/recipes/runtimes/agentcore/code/{{PKG}}/mcpMain.ts.tmpl +173 -134
  12. package/recipes/runtimes/agentcore/code/{{PKG}}/model.ts.tmpl +7 -0
  13. package/recipes/runtimes/agentcore/code/{{PKG}}/sellerCore.ts.tmpl +31 -46
  14. package/recipes/runtimes/agentcore/code/{{PKG}}/tools.ts.tmpl +19 -48
  15. package/recipes/runtimes/agentcore/code/{{PKG}}/unifiedMain.ts.tmpl +2 -1
  16. package/recipes/runtimes/azure-foundry/code/{{PKG}}/mcpMain.ts.tmpl +173 -134
  17. package/recipes/runtimes/azure-foundry/code/{{PKG}}/model.ts.tmpl +7 -0
  18. package/recipes/runtimes/azure-foundry/code/{{PKG}}/sellerCore.ts.tmpl +31 -46
  19. package/recipes/runtimes/azure-foundry/code/{{PKG}}/tools.ts.tmpl +19 -48
  20. package/recipes/runtimes/azure-foundry/code/{{PKG}}/unifiedMain.ts.tmpl +2 -1
  21. package/recipes/wallet/recipe.toml +3 -2
  22. package/skills/bnbagent-studio.md +12 -1
  23. package/skills/references/bnbagent-studio-operating.md +1 -0
  24. package/skills/references/bnbagent-studio-scaffolding-agent.md +11 -34
  25. package/skills/references/bnbagent-studio-selling-via-b402.md +3 -3
  26. package/skills/references/bnbagent-studio-using-altana-wallet.md +6 -1
  27. package/skills/references/bnbagent-studio-using-twak-wallet.md +24 -76
  28. package/dist/_twak-4XF4H5PL.js +0 -25
  29. package/dist/chunk-RO726HJG.js +0 -175
@@ -38,24 +38,38 @@
38
38
  */
39
39
 
40
40
  import { ERC8183JobOps } from "@bnbagent/sdk/erc8183";
41
+ import { maskUrlSecrets } from "@bnbagent/studio-runtime/audit";
41
42
  import { SubmitPermanentlyUnsupportedError } from "@bnbagent/studio-runtime/erc8183";
42
43
  import { getWallet } from "@bnbagent/studio-runtime/wallet";
44
+ import {
45
+ DeliveryTimeoutError,
46
+ deliveryTimeoutSeconds,
47
+ envSeconds,
48
+ minimumDeliveryWindowSeconds,
49
+ withTimeout,
50
+ } from "./deliveryPolicy.js";
43
51
  import { limitCommerceOperation } from "./requestLimits.js";
44
52
  import * as defaultSigning from "./signing.js";
45
53
 
54
+ function safeLogText(value: unknown): string {
55
+ const text =
56
+ value instanceof Error
57
+ ? (value.stack ?? `${value.name}: ${value.message}`)
58
+ : String(value ?? "");
59
+ return maskUrlSecrets(text);
60
+ }
61
+
46
62
  const log = {
47
- info: (msg: string) => console.log(`[seller-agent.core] ${msg}`),
48
- warn: (msg: string) => console.warn(`[seller-agent.core] WARNING ${msg}`),
63
+ info: (msg: string) => console.log(`[seller-agent.core] ${safeLogText(msg)}`),
64
+ warn: (msg: string) =>
65
+ console.warn(`[seller-agent.core] WARNING ${safeLogText(msg)}`),
49
66
  error: (msg: string, e?: unknown) =>
50
- console.error(`[seller-agent.core] ERROR ${msg}`, e ?? ""),
67
+ console.error(
68
+ `[seller-agent.core] ERROR ${safeLogText(msg)}`,
69
+ safeLogText(e),
70
+ ),
51
71
  };
52
72
 
53
- /** Read a positive timeout (seconds) from the env, falling back to `dflt`. */
54
- function envSeconds(name: string, dflt: number): number {
55
- const v = Number(process.env[name] || dflt);
56
- return Number.isFinite(v) && v > 0 ? v : dflt;
57
- }
58
-
59
73
  // Background-task ceilings. notifyFunded ACKs immediately and delivers in a
60
74
  // BACKGROUND task; AgentCore keeps the scale-to-zero microVM warm
61
75
  // (HEALTHY_BUSY) while isBusy() is true. A delivery (LLM text + on-chain
@@ -65,43 +79,10 @@ function envSeconds(name: string, dflt: number): number {
65
79
  // billing memory the whole time. A timed-out job is treated as TRANSIENT
66
80
  // (not dropped): the funded job stays on-chain and a later sweep re-delivers
67
81
  // it idempotently. (Read lazily so tests can tune them via the env.)
68
- const jobDeliveryTimeoutSeconds = () =>
69
- envSeconds("NOTIFY_DELIVERY_TIMEOUT_SECONDS", 600);
70
82
  const sweepTimeoutSeconds = () => envSeconds("NOTIFY_SWEEP_TIMEOUT_SECONDS", 60);
71
83
  const preverifyTimeoutSeconds = () =>
72
84
  envSeconds("NOTIFY_PREVERIFY_TIMEOUT_SECONDS", 30);
73
85
 
74
- /** Rejection raised by {@link withTimeout} when the deadline fires. */
75
- export class DeliveryTimeoutError extends Error {}
76
-
77
- /**
78
- * Race `work` against a deadline, aborting `controller` when it fires.
79
- *
80
- * JS cannot hard-cancel an arbitrary promise the way asyncio.wait_for
81
- * cancels a coroutine: the abort signal stops the LLM call (the AI SDK
82
- * honours it), and the on-chain layers are idempotent — `verifySignedJob`
83
- * returns non-OK for an already-SUBMITTED job and `submitResult` re-verifies
84
- * FUNDED — so an orphaned straggler can never double-deliver.
85
- */
86
- async function withTimeout<T>(
87
- work: Promise<T>,
88
- seconds: number,
89
- controller?: AbortController,
90
- ): Promise<T> {
91
- let timer: ReturnType<typeof setTimeout> | undefined;
92
- const deadline = new Promise<never>((_, reject) => {
93
- timer = setTimeout(() => {
94
- controller?.abort();
95
- reject(new DeliveryTimeoutError(`timed out after ${seconds}s`));
96
- }, seconds * 1000);
97
- });
98
- try {
99
- return await Promise.race([work, deadline]);
100
- } finally {
101
- clearTimeout(timer);
102
- }
103
- }
104
-
105
86
  /**
106
87
  * The LLM work hook: produce the deliverable text for a prompt.
107
88
  *
@@ -125,6 +106,7 @@ export interface SigningApi {
125
106
  ): Promise<Record<string, unknown>>;
126
107
  verifySignedJob(
127
108
  jobId: number,
109
+ minimumRemainingSeconds?: number,
128
110
  ): Promise<{ ok: boolean; reason: string; permanent: boolean }>;
129
111
  jobSpec(
130
112
  jobId: number,
@@ -284,7 +266,7 @@ export class SellerCore {
284
266
  // Time-bounded: a hung RPC must not stall the ack path. On timeout we
285
267
  // fall through to accept-and-re-verify below.
286
268
  const v = await withTimeout(
287
- this.signing.verifySignedJob(jobId),
269
+ this.signing.verifySignedJob(jobId, minimumDeliveryWindowSeconds()),
288
270
  preverifyTimeoutSeconds(),
289
271
  );
290
272
  if (!v.ok && v.permanent) {
@@ -351,7 +333,7 @@ export class SellerCore {
351
333
  verified
352
334
  ? this.doWorkAndSubmit(jobId, controller.signal)
353
335
  : this.fulfillJob(jobId, controller.signal),
354
- jobDeliveryTimeoutSeconds(),
336
+ deliveryTimeoutSeconds(),
355
337
  controller,
356
338
  );
357
339
  log.info(`notify_funded job ${jobId} → ${JSON.stringify(result)}`);
@@ -367,7 +349,7 @@ export class SellerCore {
367
349
  if (e instanceof DeliveryTimeoutError) {
368
350
  // Transient by design — leave terminal false so a later sweep retries.
369
351
  log.warn(
370
- `background delivery of job ${jobId} timed out after ${jobDeliveryTimeoutSeconds()}s; will retry`,
352
+ `background delivery of job ${jobId} timed out after ${deliveryTimeoutSeconds()}s; will retry`,
371
353
  );
372
354
  } else {
373
355
  log.error(`background delivery of job ${jobId} failed`, e);
@@ -399,7 +381,10 @@ export class SellerCore {
399
381
  jobId: number,
400
382
  abortSignal: AbortSignal,
401
383
  ): Promise<Record<string, unknown>> {
402
- const v = await this.signing.verifySignedJob(jobId);
384
+ const v = await this.signing.verifySignedJob(
385
+ jobId,
386
+ minimumDeliveryWindowSeconds(),
387
+ );
403
388
  if (!v.ok) {
404
389
  return { ok: false, job_id: jobId, skip: v.permanent, reason: v.reason };
405
390
  }
@@ -30,6 +30,7 @@ import * as cr from "@bnbagent/studio-runtime/tools";
30
30
  import { loadStudioToml } from "@bnbagent/studio-runtime/config";
31
31
  import { tool, type ToolSet } from "ai";
32
32
  import { z } from "zod";
33
+ import { READ_TOOL_CATALOG } from "./readToolCatalog.js";
33
34
 
34
35
  /**
35
36
  * The project-wide default network (`[network].default`) — tool calls that
@@ -46,51 +47,34 @@ function defaultNetwork(): string {
46
47
  }
47
48
  }
48
49
 
49
- const networkArg = z
50
- .string()
51
- .optional()
52
- .describe("studio network name (defaults to the project's [network].default)");
53
-
54
50
  export const LLM_READ_TOOLS: ToolSet = {
55
51
  // --- Wallet & chain basics ---
56
52
  wallet_info: tool({
57
- description:
58
- "Describe the agent's active wallet (address, kind, key location).",
59
- inputSchema: z.object({}),
53
+ description: READ_TOOL_CATALOG.wallet_info.description,
54
+ inputSchema: z.object(READ_TOOL_CATALOG.wallet_info.inputSchema),
60
55
  execute: async () => cr.walletInfo(),
61
56
  }),
62
57
  balance_native: tool({
63
- description:
64
- "Native BNB balance of an address (defaults to the agent's own wallet).",
65
- inputSchema: z.object({
66
- address: z.string().optional().describe("0x address; omit for own wallet"),
67
- network: networkArg,
68
- }),
58
+ description: READ_TOOL_CATALOG.balance_native.description,
59
+ inputSchema: z.object(READ_TOOL_CATALOG.balance_native.inputSchema),
69
60
  execute: async ({ address, network }) =>
70
61
  cr.balanceNative(address ?? null, network ?? defaultNetwork()),
71
62
  }),
72
63
  balance_u: tool({
73
64
  // requires [u_token] in studio.toml
74
- description:
75
- "$U (payment token) balance of an address (defaults to the agent's own wallet).",
76
- inputSchema: z.object({
77
- address: z.string().optional().describe("0x address; omit for own wallet"),
78
- network: networkArg,
79
- }),
65
+ description: READ_TOOL_CATALOG.balance_u.description,
66
+ inputSchema: z.object(READ_TOOL_CATALOG.balance_u.inputSchema),
80
67
  execute: async ({ address, network }) =>
81
68
  cr.balanceU(address ?? null, network ?? defaultNetwork()),
82
69
  }),
83
70
  network_info: tool({
84
- description: "Chain id / RPC / token info for a studio network.",
85
- inputSchema: z.object({ network: networkArg }),
71
+ description: READ_TOOL_CATALOG.network_info.description,
72
+ inputSchema: z.object(READ_TOOL_CATALOG.network_info.inputSchema),
86
73
  execute: async ({ network }) => cr.networkInfo(network ?? defaultNetwork()),
87
74
  }),
88
75
  tx_status: tool({
89
- description: "Status + receipt summary of a transaction hash.",
90
- inputSchema: z.object({
91
- tx_hash: z.string().describe("0x transaction hash"),
92
- network: networkArg,
93
- }),
76
+ description: READ_TOOL_CATALOG.tx_status.description,
77
+ inputSchema: z.object(READ_TOOL_CATALOG.tx_status.inputSchema),
94
78
  execute: async ({ tx_hash, network }) =>
95
79
  cr.txStatus(tx_hash, network ?? defaultNetwork()),
96
80
  }),
@@ -106,21 +90,15 @@ export const LLM_READ_TOOLS: ToolSet = {
106
90
  // --- ERC-8004 identity (read-only lookups the LLM may want for context) ---
107
91
  agent_info: tool({
108
92
  // requires [erc8004] in studio.toml
109
- description: "ERC-8004 identity record for an agent id.",
110
- inputSchema: z.object({
111
- agent_id: z.number().int().describe("ERC-8004 agent id"),
112
- network: networkArg,
113
- }),
93
+ description: READ_TOOL_CATALOG.agent_info.description,
94
+ inputSchema: z.object(READ_TOOL_CATALOG.agent_info.inputSchema),
114
95
  execute: async ({ agent_id, network }) =>
115
96
  cr.agentInfo(agent_id, network ?? defaultNetwork()),
116
97
  }),
117
98
  agent_by_address: tool({
118
99
  // requires [erc8004] in studio.toml
119
- description: "Look up an ERC-8004 agent registration by wallet address.",
120
- inputSchema: z.object({
121
- address: z.string().describe("0x wallet address"),
122
- network: networkArg,
123
- }),
100
+ description: READ_TOOL_CATALOG.agent_by_address.description,
101
+ inputSchema: z.object(READ_TOOL_CATALOG.agent_by_address.inputSchema),
124
102
  execute: async ({ address, network }) =>
125
103
  cr.agentByAddress(address, network ?? defaultNetwork()),
126
104
  }),
@@ -128,22 +106,15 @@ export const LLM_READ_TOOLS: ToolSet = {
128
106
  // --- ERC-8183 jobs (READ-ONLY status/list — writes live in signing.ts) ---
129
107
  job_status: tool({
130
108
  // requires [erc8183] in studio.toml
131
- description: "Read-only ERC-8183 job summary (status, budget, deliverable URL).",
132
- inputSchema: z.object({
133
- job_id: z.number().int().describe("on-chain job id"),
134
- network: networkArg,
135
- }),
109
+ description: READ_TOOL_CATALOG.job_status.description,
110
+ inputSchema: z.object(READ_TOOL_CATALOG.job_status.inputSchema),
136
111
  execute: async ({ job_id, network }) =>
137
112
  cr.jobStatus(job_id, network ?? defaultNetwork()),
138
113
  }),
139
114
  job_list: tool({
140
115
  // requires [erc8183] in studio.toml
141
- description: "List recent ERC-8183 jobs (optionally only this agent's).",
142
- inputSchema: z.object({
143
- limit: z.number().int().optional(),
144
- mine: z.boolean().optional().describe("only jobs assigned to this agent"),
145
- network: networkArg,
146
- }),
116
+ description: READ_TOOL_CATALOG.job_list.description,
117
+ inputSchema: z.object(READ_TOOL_CATALOG.job_list.inputSchema),
147
118
  execute: async ({ limit, mine, network }) =>
148
119
  cr.jobList({ limit, mine, network: network ?? defaultNetwork() }),
149
120
  }),
@@ -225,7 +225,8 @@ export function buildRunWork(): RunWork {
225
225
 
226
226
  function hasErc8183Rail(cfg: TomlTable): boolean {
227
227
  const payments = asTable(cfg.payments);
228
- return asTable(payments?.erc8183) !== null;
228
+ const rail = asTable(payments?.erc8183);
229
+ return rail !== null && rail.enabled !== false;
229
230
  }
230
231
 
231
232
  function asTable(value: unknown): TomlTable | null {