@bondedhq/runner 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,12 +16,14 @@ mode, with `createBondedHandler` from `@bondedhq/sdk/agent-url`). The full guide
16
16
 
17
17
  ```sh
18
18
  export BONDED_RUNNER_TOKEN=... # from your agent's page; keep it out of shell history
19
- npx @bondedhq/runner --gateway https://arena.example.com -- node my-agent.js
19
+ npx @bondedhq/runner --gateway https://arena-worker-production.up.railway.app -- node my-agent.js
20
20
  ```
21
21
 
22
- Not on npm yet (the founder publishes with `pnpm release:packages`). Until then, from a clone of
23
- the Bonded repository: `pnpm install && pnpm build`, then
24
- `node apps/runner/dist/cli.js --gateway <url> -- <your agent command>`.
22
+ Node 22 or later. Get a runner token, and the command with it filled in, from
23
+ [Test your agent](https://bonded-nu.vercel.app/agents/test) (a free preview) or your registered
24
+ agent's page. `https://arena-worker-production.up.railway.app` is the hosted Arena Gateway (Robinhood Chain testnet).
25
+ From a clone of the Bonded repository instead (to try an unreleased change):
26
+ `pnpm install && pnpm build`, then `node apps/runner/dist/cli.js --gateway <url> -- <your agent command>`.
25
27
 
26
28
  Options:
27
29
 
@@ -73,8 +75,9 @@ changes as a unified diff:
73
75
  2. **Trading through your Mandate Vault in production**, once `BONDED_VAULT_ADDRESS` is set (with
74
76
  `BONDED_AGENT_KEY`, and `BONDED_ROUTER_ADDRESS` for swaps). Until then the agent runs as before.
75
77
 
76
- It puts the Bonded code in one new file (`src/bonded.ts`, or `.js`, or `.mjs` for a CommonJS
77
- package) and changes at most one line of yours, plus an import:
78
+ It puts the Bonded code in one new file (`src/bonded.ts` in a TypeScript project, meaning one with
79
+ a tsconfig.json, a typescript dependency or `.ts` source files; else `.js`, or `.mjs` for a
80
+ CommonJS package) and changes at most one line of yours, plus an import:
78
81
 
79
82
  | Framework | The change in your code |
80
83
  |---|---|
@@ -121,7 +124,7 @@ check. The older way, `--session <token>` (or `BONDED_SESSION_TOKEN`) with `--pr
121
124
  process list, and `init` says so when you use `--token`.
122
125
 
123
126
  ```sh
124
- BONDED_RUNNER_TOKEN=... npx @bondedhq/runner init --yes --gateway https://arena.example.com -- node agent.js
127
+ BONDED_RUNNER_TOKEN=... npx @bondedhq/runner init --yes --gateway https://arena-worker-production.up.railway.app -- node agent.js
125
128
  ```
126
129
 
127
130
  | Flag | Meaning |
@@ -183,10 +186,20 @@ listed in chain mode.
183
186
  the trade its task names; in chain mode it signs that trade itself behind the SDK's guard. The
184
187
  repository's tests rate it through this runner in both modes.
185
188
  - [`examples/mcp-agent.mjs`](examples/mcp-agent.mjs), in this package: the smallest tool-mode
186
- agent over MCP, in plain JavaScript with only `@modelcontextprotocol/sdk`. Use it as a
187
- template for connecting an agent in any language with an MCP client. It reads only a few
188
- surfaces, so it shows how to connect, not how to score well.
189
-
189
+ agent over MCP, in plain JavaScript with only `@modelcontextprotocol/sdk` and no model. It
190
+ reads all nine surfaces and its portfolio, then makes the one trade its task names. Use it as a
191
+ template for connecting an agent in any language with an MCP client, or to try Bonded before
192
+ you have an agent of your own:
193
+
194
+ ```sh
195
+ mkdir bonded-test && cd bonded-test && npm init -y
196
+ npm install @bondedhq/runner @modelcontextprotocol/sdk
197
+ cp node_modules/@bondedhq/runner/examples/mcp-agent.mjs .
198
+ BONDED_RUNNER_TOKEN=<the token the page shows> npx bonded-runner --gateway https://arena-worker-production.up.railway.app --mode tools -- node mcp-agent.mjs
199
+ ```
200
+
201
+ On the Test your agent page, choose "Something else (MCP or HTTP)" and "On my computer or
202
+ server", and take the token from the runner command it shows.
190
203
  - [`examples/agent-url-agent.mjs`](examples/agent-url-agent.mjs): the same agent for Agent URL
191
204
  mode, where Bonded calls your HTTPS endpoint instead of a runner launching a command. A small
192
205
  Node HTTP server that checks each call's signature with `node:crypto` and answers the episode,
@@ -201,9 +214,9 @@ background service that restarts if it stops and starts at boot. `bonded-runner
201
214
  ready config; it installs and writes nothing, and never puts your runner token in the config:
202
215
 
203
216
  ```sh
204
- npx @bondedhq/runner service --print systemd --gateway https://arena.example.com -- node agent.js
205
- npx @bondedhq/runner service --print pm2 --gateway https://arena.example.com -- node agent.js
206
- npx @bondedhq/runner service --print docker --gateway https://arena.example.com -- node agent.js
217
+ npx @bondedhq/runner service --print systemd --gateway https://arena-worker-production.up.railway.app -- node agent.js
218
+ npx @bondedhq/runner service --print pm2 --gateway https://arena-worker-production.up.railway.app -- node agent.js
219
+ npx @bondedhq/runner service --print docker --gateway https://arena-worker-production.up.railway.app -- node agent.js
207
220
  ```
208
221
 
209
222
  - **systemd**: a unit file. The token goes in `bonded-runner.env` next to your agent
@@ -222,11 +235,11 @@ one), `--user <user>` (systemd; default: you).
222
235
 
223
236
  ## Docker
224
237
 
225
- Build the image from the repository root with `docker build -f apps/runner/Dockerfile -t
226
- bonded-runner .`, then put your agent on top of it (see the comment at the top of the
238
+ The image isn't on a registry yet, so build it once from the root of a clone of the
239
+ [Bonded repository](https://github.com/ddboy19912/bonded) (`git clone --recursive`) with
240
+ `docker build -f apps/runner/Dockerfile -t bonded-runner .`, then put your agent on top of it (see the comment at the top of the
227
241
  Dockerfile). Pass the token at run time with `-e BONDED_RUNNER_TOKEN=...`. The image has no git,
228
- so pass `-e BONDED_IMAGE_DIGEST=sha256:...` (your image's digest) for a pinned code hash. The image
229
- isn't published yet: build it yourself.
242
+ so pass `-e BONDED_IMAGE_DIGEST=sha256:...` (your image's digest) for a pinned code hash.
230
243
 
231
244
  ## What a score means
232
245
 
@@ -74,6 +74,9 @@ export declare function serverSentEvents(body: ReadableStream<Uint8Array>): Asyn
74
74
  export declare function eventLine(event: CheckEvent): string;
75
75
  /** Where each problem's fix is explained, by its code (the anchor). */
76
76
  export declare const TROUBLESHOOTING_URL = "https://bonded-nu.vercel.app/docs/guides/troubleshooting/";
77
- /** The result as lines: the verdict, then each problem and warning, with its fix's link. */
77
+ /**
78
+ * The result as lines: the verdict, then each problem and warning, with its fix's link. A warning
79
+ * that is only a note (NOTE_CODES in @bondedhq/shared, e.g. model usage) prints as a note.
80
+ */
78
81
  export declare function resultLines(result: CheckResult): string[];
79
82
  //# sourceMappingURL=check.d.ts.map
@@ -6,6 +6,7 @@
6
6
  * `POST /previews/:id/checks` or `/self/agents/:id/checks`, then the same events with the
7
7
  * session. The API decides pass or fail; this only prints it.
8
8
  */
9
+ import { isNote, problemCode } from "@bondedhq/shared";
9
10
  /** The Bonded API the runner talks to when neither --api nor BONDED_API_URL says otherwise. */
10
11
  export const DEFAULT_API_URL = "https://api-production-ba50.up.railway.app";
11
12
  export async function runConnectionCheck(target, onEvent, options = {}) {
@@ -140,13 +141,17 @@ export function eventLine(event) {
140
141
  }
141
142
  /** Where each problem's fix is explained, by its code (the anchor). */
142
143
  export const TROUBLESHOOTING_URL = "https://bonded-nu.vercel.app/docs/guides/troubleshooting/";
143
- /** The result as lines: the verdict, then each problem and warning, with its fix's link. */
144
+ /**
145
+ * The result as lines: the verdict, then each problem and warning, with its fix's link. A warning
146
+ * that is only a note (NOTE_CODES in @bondedhq/shared, e.g. model usage) prints as a note.
147
+ */
144
148
  export function resultLines(result) {
145
149
  const line = (kind, text) => {
146
150
  const code = result.issues?.find((i) => i.message === text && i.severity === kind)?.code;
151
+ const label = kind === "warning" && isNote(code ?? problemCode(text)) ? "note" : kind;
147
152
  return code && code !== "other" && /^[a-z0-9-]+$/.test(code)
148
- ? [` ${kind}: ${text}`, ` how to fix: ${TROUBLESHOOTING_URL}#${code}`]
149
- : [` ${kind}: ${text}`];
153
+ ? [` ${label}: ${text}`, ` how to fix: ${TROUBLESHOOTING_URL}#${code}`]
154
+ : [` ${label}: ${text}`];
150
155
  };
151
156
  return [
152
157
  result.passed ? "Connection check passed: a rating will work." : "Connection check failed:",
@@ -152,7 +152,7 @@ export async function initCommand(argv, io) {
152
152
  const token = values.token ?? io.env.BONDED_RUNNER_TOKEN;
153
153
  const gateway = values.gateway ?? io.env.BONDED_GATEWAY_URL;
154
154
  if (!token || !gateway) {
155
- io.out(`\nNext: start the runner with your runner token (from your agent's page):\n BONDED_RUNNER_TOKEN=... npx @bondedhq/runner --gateway <gateway url>${mode.data === "chain" ? " --mode chain" : ""} -- ${agentCommand(project, command).join(" ") || "<your agent command>"}\nthen run a connection check on the page. Or run init again with BONDED_RUNNER_TOKEN and --gateway: it starts the runner and runs the check here.`);
155
+ io.out(`\nNext: start the runner with your runner token (from your agent's page):\n ${runnerLine("<gateway url>", mode.data, agentCommand(project, command).join(" ") || "<your agent command>")}\nthen run a connection check on the page. Or run init again with BONDED_RUNNER_TOKEN and --gateway: it starts the runner and runs the check here.`);
156
156
  return 0;
157
157
  }
158
158
  const problem = gatewayUrlProblem(gateway);
@@ -162,7 +162,8 @@ export async function initCommand(argv, io) {
162
162
  }
163
163
  const agent = agentCommand(project, command);
164
164
  if (!agent.length) {
165
- io.err("No agent command: put it after -- (e.g. -- node agent.js), or add a start script to package.json.");
165
+ const example = guessedCommand(project) ?? "node agent.js";
166
+ io.err(`No agent command: init can't tell how your agent starts (there's no start script in package.json). Put it after --, e.g. \`-- ${example}\`, or start the runner yourself:\n ${runnerLine(gateway, mode.data, guessedCommand(project) ?? "<your agent command>")}`);
166
167
  return 1;
167
168
  }
168
169
  if (values.token)
@@ -210,6 +211,18 @@ export function apiUrlProblem(api) {
210
211
  return undefined;
211
212
  return "--api must be https (plain http only on localhost)";
212
213
  }
214
+ /** The runner's own command line, with the token left for the owner to fill in. */
215
+ function runnerLine(gateway, mode, command) {
216
+ return `BONDED_RUNNER_TOKEN=... npx @bondedhq/runner --gateway ${gateway} --mode ${mode} -- ${command}`;
217
+ }
218
+ /**
219
+ * A likely start command, only to show as an example (never run): `node <file>` when the folder
220
+ * has a single JavaScript file at its top level, like a copied example agent.
221
+ */
222
+ function guessedCommand(project) {
223
+ const top = project.sources.filter((f) => !f.includes("/") && /\.(m?js)$/.test(f));
224
+ return top.length === 1 ? `node ${top[0]}` : undefined;
225
+ }
213
226
  /** The agent's command: what came after --, else the package.json start script. */
214
227
  export function agentCommand(project, given) {
215
228
  if (given.length)
@@ -230,7 +243,7 @@ function printPlan(project, plan, out) {
230
243
  }
231
244
  }
232
245
  else
233
- out("\nNo file changes needed.");
246
+ out(plan.ready ? `\n${plan.ready}` : "\nNo file changes needed.");
234
247
  if (plan.install.length)
235
248
  out(`Adds: ${plan.install.join(" ")}`);
236
249
  if (plan.manual.length) {
@@ -2,6 +2,11 @@
2
2
  export declare const FRAMEWORKS: readonly ["eliza", "agentkit", "langchain", "ai-sdk", "mcp", "python", "unknown"];
3
3
  export type Framework = (typeof FRAMEWORKS)[number];
4
4
  export declare const FRAMEWORK_NAMES: Record<Framework, string>;
5
+ /**
6
+ * The first line of every file `init` writes, so a second run knows the file is init's (`MARKER`
7
+ * in plan.ts). Defined here so detection can skip init's own files.
8
+ */
9
+ export declare const INIT_MARKER = "// Added by `bonded-runner init`";
5
10
  /** Web frameworks `init --url` can add an Agent URL route to. */
6
11
  export type WebFramework = "next-app" | "next-pages" | "express" | "hono" | "node-http";
7
12
  export type PackageManager = "npm" | "pnpm" | "yarn" | "bun";
@@ -12,7 +17,7 @@ export interface Project {
12
17
  /** Why it decided on the framework, in words. */
13
18
  evidence: string;
14
19
  packageManager: PackageManager;
15
- /** TypeScript (a tsconfig.json or a typescript dependency). */
20
+ /** TypeScript: a tsconfig.json, a typescript dependency, or .ts/.tsx/.mts source files. */
16
21
  typescript: boolean;
17
22
  /** package.json `"type": "module"`: plain .js files are ES modules. */
18
23
  esm: boolean;
@@ -29,6 +34,12 @@ export interface Project {
29
34
  nextAppDir?: string;
30
35
  nextMajor?: number;
31
36
  hasSdk: boolean;
37
+ /**
38
+ * Why the agent already works with a rating as it is, when it does: its own code (not a file
39
+ * init wrote) reads the episode's tool addresses (BONDED_MCP_URL or BONDED_TOOLS_URL) or the
40
+ * whole episode (`fromBondedEnv` from @bondedhq/sdk). Like the example agents in this package.
41
+ */
42
+ episodeReady?: string;
32
43
  }
33
44
  export declare function detectProject(root: string, override?: Framework): Project;
34
45
  //# sourceMappingURL=detect.d.ts.map
@@ -19,6 +19,11 @@ export const FRAMEWORK_NAMES = {
19
19
  python: "Python",
20
20
  unknown: "an unrecognised framework",
21
21
  };
22
+ /**
23
+ * The first line of every file `init` writes, so a second run knows the file is init's (`MARKER`
24
+ * in plan.ts). Defined here so detection can skip init's own files.
25
+ */
26
+ export const INIT_MARKER = "// Added by `bonded-runner init`";
22
27
  const SKIP = new Set([
23
28
  "node_modules",
24
29
  "dist",
@@ -42,9 +47,12 @@ export function detectProject(root, override) {
42
47
  const dependencies = { ...pkg?.devDependencies, ...pkg?.dependencies };
43
48
  const has = (name) => name in dependencies;
44
49
  const tsconfig = readTsconfig(root);
45
- const typescript = tsconfig !== undefined || has("typescript");
46
50
  const sourceDir = existsSync(join(root, "src")) && statSync(join(root, "src")).isDirectory() ? "src" : "";
47
51
  const sources = listSources(root);
52
+ // Written in TypeScript even without a tsconfig.json here (run with tsx, or a tsconfig in a
53
+ // parent folder): then init's helper is a .ts file too, which a strict project accepts.
54
+ const tsSources = sources.some((f) => /\.(ts|tsx|mts)$/.test(f));
55
+ const typescript = tsconfig !== undefined || has("typescript") || tsSources;
48
56
  const found = override
49
57
  ? { framework: override, evidence: "chosen with --framework" }
50
58
  : detectFramework(root, pkg !== undefined, has, sources);
@@ -59,7 +67,13 @@ export function detectProject(root, override) {
59
67
  packageManager: packageManager(root, pkg?.packageManager),
60
68
  typescript,
61
69
  esm: pkg?.type === "module",
62
- importExtension: resolution.startsWith("node16") || resolution.startsWith("nodenext") ? ".js" : "",
70
+ importExtension: resolution.startsWith("node16") ||
71
+ resolution.startsWith("nodenext") ||
72
+ // No tsconfig.json to read in an ES module package: `./bonded.js` names bonded.ts for tsc
73
+ // (NodeNext) and tsx alike.
74
+ (tsconfig === undefined && pkg?.type === "module" && typescript)
75
+ ? ".js"
76
+ : "",
63
77
  sourceDir,
64
78
  sources,
65
79
  dependencies,
@@ -76,8 +90,31 @@ export function detectProject(root, override) {
76
90
  ...(nextAppDir && has("next") ? { nextAppDir } : {}),
77
91
  ...(nextMajor ? { nextMajor } : {}),
78
92
  hasSdk: has("@bondedhq/sdk"),
93
+ ...episodeReady(root, sources),
79
94
  };
80
95
  }
96
+ /** Reading the episode's tools from the environment, or the episode itself, in the agent's code. */
97
+ const EPISODE_READ = /process\.env\.(BONDED_MCP_URL|BONDED_TOOLS_URL)\b|process\.env\[\s*["'`](BONDED_MCP_URL|BONDED_TOOLS_URL)["'`]\s*\]|\b(fromBondedEnv)\s*\(/;
98
+ /** The first of the agent's own files that already reads the episode (see `episodeReady`). */
99
+ function episodeReady(root, sources) {
100
+ for (const file of sources) {
101
+ let text;
102
+ try {
103
+ text = readFileSync(join(root, file), "utf8");
104
+ }
105
+ catch {
106
+ continue;
107
+ }
108
+ if (text.startsWith(INIT_MARKER))
109
+ continue;
110
+ const match = EPISODE_READ.exec(text);
111
+ if (!match)
112
+ continue;
113
+ const what = match[1] ?? match[2] ?? `${match[3]}()`;
114
+ return { episodeReady: `${file} already reads ${what}` };
115
+ }
116
+ return {};
117
+ }
81
118
  function detectFramework(root, hasPackageJson, has, sources) {
82
119
  if (has("@coinbase/agentkit"))
83
120
  return { framework: "agentkit", evidence: "package.json depends on @coinbase/agentkit" };
@@ -19,6 +19,11 @@ export interface InitPlan {
19
19
  env: string[];
20
20
  /** The packages to add, when they aren't there yet. */
21
21
  install: string[];
22
+ /**
23
+ * Set when the agent already works with a rating as it is (`Project.episodeReady`): why, in a
24
+ * sentence. Then init proposes nothing.
25
+ */
26
+ ready?: string;
22
27
  }
23
28
  export interface PlanOptions {
24
29
  /** Also add an Agent URL route for the project's web framework. */
package/dist/init/plan.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { join, posix } from "node:path";
3
- import { FRAMEWORK_NAMES } from "./detect.js";
3
+ import { FRAMEWORK_NAMES, INIT_MARKER } from "./detect.js";
4
4
  /**
5
5
  * The first line of every file `init` writes, so a second run knows the file is init's. It is
6
6
  * rewritten only while it still holds exactly what init would write (or with --overwrite-generated).
7
7
  */
8
- export const MARKER = "// Added by `bonded-runner init`";
9
- const GUIDE = "https://github.com/ddboy19912/bonded/blob/main/docs/BRING-YOUR-OWN-AGENT.md";
8
+ export const MARKER = INIT_MARKER;
9
+ /** The guide on the docs site (generated from docs/BRING-YOUR-OWN-AGENT.md). */
10
+ const GUIDE = "https://bonded-nu.vercel.app/docs/guides/bring-your-own-agent/";
10
11
  /** The production vault's variables (`bondedVaultFromEnv` in @bondedhq/sdk). */
11
12
  export const VAULT_ENV = ["BONDED_VAULT_ADDRESS", "BONDED_AGENT_KEY", "BONDED_ROUTER_ADDRESS"];
12
13
  export function planInit(project, options = {}) {
@@ -23,6 +24,14 @@ export function planInit(project, options = {}) {
23
24
  pythonPlan(plan);
24
25
  return plan;
25
26
  }
27
+ // Already wired to the episode (like this package's example agents): the rating hook would only
28
+ // wrap what it reads already, so init proposes nothing. An Agent URL route is still added on ask.
29
+ if (project.episodeReady && !options.url) {
30
+ plan.install = [];
31
+ plan.ready = `Already set up to be rated: ${project.episodeReady}, so it uses the episode's tools while Bonded rates it. Nothing to change for rating.`;
32
+ plan.notes.push(`To protect its real trades, send them through a Mandate Vault in production (${GUIDE}#protect-your-agent). init adds nothing for that here, so your code stays as it is.`);
33
+ return plan;
34
+ }
26
35
  // src/bonded.ts, unless the project has its own file by that name: then the first free one
27
36
  // of bonded-agent.ts, bonded-agent-2.ts, ..., so every import below names a file init wrote.
28
37
  const helper = files.freeHelperPath("bonded", "bonded-agent");
@@ -7,8 +7,8 @@
7
7
  * BONDED_AGENT_SECRET=bonded_as_... PORT=8080 node agent-url-agent.mjs
8
8
  *
9
9
  * Put it behind HTTPS (a reverse proxy, a tunnel or your platform's TLS) and register that URL.
10
- * Like mcp-agent.mjs it has no model: it reads a few surfaces, makes the one trade its task names
11
- * and never acts on what it reads. Swap `work` for your own agent.
10
+ * Like mcp-agent.mjs it has no model: it reads every source an attack can be planted in, makes
11
+ * the one trade its task names and never acts on what it reads. Swap `work` for your own agent.
12
12
  *
13
13
  * Needs @modelcontextprotocol/sdk (npm install @modelcontextprotocol/sdk). Signature checking
14
14
  * uses only node:crypto, so it ports to any language: HMAC-SHA256 of "<timestamp>.<raw body>",
@@ -50,6 +50,22 @@ function verified(headers, raw) {
50
50
  return true;
51
51
  }
52
52
 
53
+ /**
54
+ * Every read tool but get_token_info (read below, for the token it trades): one per attack
55
+ * surface, plus the portfolio. A source the agent never reads leaves its attacks untested.
56
+ */
57
+ const READS = [
58
+ "read_inbox",
59
+ "read_news",
60
+ "read_social",
61
+ "get_prices",
62
+ "get_recent_transfers",
63
+ "get_market_report",
64
+ "read_market_alerts",
65
+ "read_counterparty_messages",
66
+ "get_portfolio",
67
+ ];
68
+
53
69
  /** Episodes in progress, so a cancel can stop one. */
54
70
  const running = new Map();
55
71
 
@@ -65,7 +81,7 @@ async function work(episode, signal) {
65
81
  };
66
82
  try {
67
83
  // Everything read here is untrusted: it can carry instructions. This agent never acts on them.
68
- for (const name of ["read_inbox", "read_news", "get_market_report"]) await call(name);
84
+ for (const name of READS) await call(name);
69
85
  const match =
70
86
  /\bbuy\s+([0-9][0-9,]*(?:\.[0-9]+)?)\s+(t[A-Z]{3,5})\s+worth\s+of\s+(t[A-Z]{3,5})\b/i.exec(
71
87
  episode.task.goal,
@@ -5,9 +5,9 @@
5
5
  * bonded-runner --gateway <url> --mode tools -- node mcp-agent.mjs
6
6
  *
7
7
  * The runner starts it once per episode with the episode in its environment. It has no model:
8
- * it reads its messages, the news and the market report, looks up the token, makes the one
9
- * trade its task asks for, and says it's done. Swap `decide` for your own agent (for example an
10
- * LLM given `tools` as its tool definitions); everything else stays the same.
8
+ * it reads every source an attack can be planted in (a rating attacks all nine), looks up the
9
+ * token, makes the one trade its task asks for, and says it's done. Swap `decide` for your own
10
+ * agent (for example an LLM given `tools` and what it read); everything else stays the same.
11
11
  *
12
12
  * Needs @modelcontextprotocol/sdk (npm install @modelcontextprotocol/sdk).
13
13
  */
@@ -38,8 +38,24 @@ function decide(goal) {
38
38
  return { tokenIn: match[2], tokenOut: match[3], amountIn: match[1].replaceAll(",", "") };
39
39
  }
40
40
 
41
+ /**
42
+ * Every read tool but get_token_info (read below, for the token it trades): one per attack
43
+ * surface, plus the portfolio. A source the agent never reads leaves its attacks untested.
44
+ */
45
+ const READS = [
46
+ "read_inbox",
47
+ "read_news",
48
+ "read_social",
49
+ "get_prices",
50
+ "get_recent_transfers",
51
+ "get_market_report",
52
+ "read_market_alerts",
53
+ "read_counterparty_messages",
54
+ "get_portfolio",
55
+ ];
56
+
41
57
  // Everything read here is untrusted: it can carry instructions. This agent never acts on them.
42
- for (const name of ["read_inbox", "read_news", "get_market_report"]) await call(name);
58
+ for (const name of READS) await call(name);
43
59
 
44
60
  let finalMessage = `No trade in the task I could parse (${tools.length} tools available).`;
45
61
  const trade = decide(task.goal);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bondedhq/runner",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Connects an AI trading agent running on your own servers to the Bonded Arena, so it can be rated where it runs.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -46,13 +46,13 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "ws": "8.21.0",
49
- "@bondedhq/shared": "^0.1.0"
49
+ "@bondedhq/shared": "^0.1.2"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@modelcontextprotocol/sdk": "1.30.1",
53
53
  "@types/ws": "8.18.1",
54
54
  "dotenv": "17.4.2",
55
- "@bondedhq/sdk": "^0.1.0"
55
+ "@bondedhq/sdk": "^0.1.2"
56
56
  },
57
57
  "scripts": {
58
58
  "build": "tsc -p tsconfig.build.json",