@beeos-ai/bma-executor-sdk

中文文档
1.1.0发布于 2026/11/08Apache-2.0TypeScriptES Module构建来源已验证近 30 天下载 3599

BMA executor protocol (bma.executor.v1.1): executor, harness and agent ends with WebSocket, local socket and in-process transports.

原始使用文档
文档目录8 节
  1. Executor quick start
  2. Executor lifecycle, cancellation, and results
  3. Harness quick start
  4. Transports
  5. Agent executors
  6. Examples
  7. Go ↔ TypeScript
  8. Tests

@beeos-ai/bma-executor-sdk (TypeScript)

npm

Connect your machine (hands) or your own agent (brain) to BMA — the executor, harness and agent ends of the bma.executor protocol. @beeos-ai/bma-sdk drives and uses agents (REST / façade client); bma-executor-sdk connects hands or brains to BMA.

Executor (ExecutorClient), harness (Harness) and agent ends of the BMA Executor Protocol bma.executor.v1.1 (and v1), with the same three parts and behavior as the Go executor module. Protocol types are generated from the contract (src/generated/executor.ts, snapshot and lock in contracts/bma/executor); the runtime is handwritten. Transports: WebSocket, WebSocket over a Unix domain socket or Windows named pipe (unix://, npipe://), in-process.

Version1.0.0 (tag v1.0.0), see CHANGELOG
Protocolbma.executor.v1.1 (contract 1.1.0, lock.json)
Node.js20 or newer; ESM, depends on ws and @types/ws (the declarations use ws types)
Integration guidedocs/executor (中文)
npm install @beeos-ai/[email protected]

The REST / façade client is the separate package @beeos-ai/bma-sdk.

Executor quick start

import { ExecutorClient } from "@beeos-ai/bma-executor-sdk";

const client = new ExecutorClient({
  baseUrl: process.env.BMA_BASE_URL, // required: see below
  environmentKey: process.env.BMA_ENVIRONMENT_KEY,
  environmentId: process.env.BMA_ENVIRONMENT_ID!,
  executor: { name: "my-executor", os: process.platform, arch: process.arch, version: "0.1.0" },
});
client.tools.bash.handle(async (input, { signal, progress }) => {   // input: BashInput
  progress("running");
  const r = await run(input.command, { cwd: input.cwd, env: input.env, signal });
  return { stdout: r.stdout, stderr: r.stderr, exitCode: r.code };    // rendered like Go Core
});
client.tools.read.handle(async ({ path, offset, limit }) => readText(path, offset, limit)); // string or content blocks
await client.start();
await client.waitClosed(); // rejects when the client ends for good

Set baseUrl to your BMA deployment: the hosted service is in early access and the default DEFAULT_BASE_URL (https://api.bma.beeos.ai) does not resolve yet. It is the deployment origin, the same value as BMA_BASE_URL in bma-sdk (for example https://api.staging.bma.beeos.ai); the client posts the environment key to {baseUrl}/v1/executors/token and drops a trailing /api/v1. The SDK does not read BMA_BASE_URL itself. A tokenSource carries its own channel URL and ignores baseUrl.

A machine-scoped executor must declare process or agent

With an empty environmentId (a machine token) the platform binds the connection as the machine's runtime and refuses the registration of an executor that advertises neither the process nor the agent capability: it answers executor.register, then closes with 4002 "runtime registration refused". Shell and files alone are such an executor. Declare process and serve process.* (or serveAgent), with executor.version as MAJOR.MINOR.PATCH:

const client = new ExecutorClient({
  baseUrl: process.env.BMA_BASE_URL,
  environmentKey: process.env.BMA_MACHINE_TOKEN, // the machine token
  environmentId: '',                              // machine scope
  executor: { name: 'my-machine', os: process.platform, arch: process.arch, version: '1.0.0' },
  capabilities: { process: { enabled: true } },
})
client.onRequest('process.start', async (params) => { /* start it, answer with the result */ })
// ... onRequest for process.read / write / interrupt / terminate / list as needed
await client.start()
await client.waitReady(30_000) // registered and still open one heartbeat later

The SDK treats a refusal as a configuration error: after 3 registrations in a row refused right after executor.register (close 4002 "runtime registration refused", or an executor.register error capability_unavailable / invalid_params), waitClosed() rejects with RegistrationRefusedError; other 4002 closes (for example "daemon runtime ended") reconnect as before. state is ready as soon as executor.register returns, which a refused registration also does; waitReady(timeoutMs?) resolves only after the registration stayed open for one heartbeat interval, and rejects with the error of a client that ended.

Executor lifecycle, cancellation, and results

  • tool.cancel aborts the handler's signal (so do deadline_ms and stop()); the client answers {"canceled":true} and reports outcome: "canceled". A repeated tool.call id is acknowledged, not run twice. Each built-in tool has a typed slot client.tools.<name>.handle(fn) (bash, read, write, edit, glob, grep, list_files; the computer_* tools get slots once the contract defines their input) whose input is the contract's <Tool>Input. Input missing a required member fails the call (bash requires command) before the handler runs. A handler returns a string, content blocks or { content, isError, artifacts }; bash may return { stdout, stderr, exitCode, truncated? }, rendered as Go Core does (stdout, stderr, exit_code: N, cut to Core's preview beyond INLINE_RESULT_BYTES). throw new ToolError(msg) reports a tool-level error (is_error: true); any other exception is outcome: "failed". A tool.call for a tool without a handler is refused before the ack with -32012 (data.capability: "tool:<name>"), as Go Core refuses it.
  • The shell and files capabilities are enabled while a handler of the group is registered, unless you set enabled: false (which wins, like MCP's RegisteredTool.enabled); a handler registered after start() sends executor.capabilities.update, as an MCP server sends tools/list_changed.
  • Call sessionReady after workspace/MCP setup and sessionEnded after cleanup. tool.result is retried on -32009, -32004 or data.retryable: true, idempotent by tool_call_id; when every attempt fails onToolResultLost receives the result to persist and replay.
  • Reconnect: until stop() by default, with exponential backoff (1 s base, ×2, 60 s cap, jitter), a fresh token per dial and resume (executor_id, last_connection_id, active_sessions). Only close codes 4003 and 4004, and 3 refused registrations in a row, are terminal. A positive maxReconnectAttempts bounds the attempts. await client.waitClosed() resolves after stop() / executor.close and rejects with ChannelClosedError (4003/4004), RegistrationRefusedError, CredentialRejectedError (the token endpoint answered 401/403 on reconnect: revoked or invalid credential, not retried) or ReconnectExhaustedError; exit the process on a rejection so a supervisor can restart it.
  • openMcpChannel(sessionId, serverKey) opens the raw MCP channel (?channel=mcp&session_id=&server_key=); read()/write() carry MCP JSON-RPC frames unchanged.
  • A handler that throws RpcError keeps its code and data on the wire. onRequest('process.start', (params) => …) serves process.* (read sessionContext(params.process_id) there and refuse with RpcError.withReason(code, ErrorReason.X, msg); see session context), terminal.*, skill.install and workspace.backup with the contract's params and result types; call(method, params) and notify(method, params) send executor-to-harness methods typed the same way (MethodParamsMap / MethodResultMap).

Harness quick start

import { Harness } from "@beeos-ai/bma-executor-sdk";

const harness = new Harness({ authenticate: (request) => verify(request) }); // omit only on loopback
const { url } = await harness.listen(); // or harness.attach(httpServer)

const executor = await harness.waitExecutor();
const session = await executor.assignSession({ session_id: "sess_1" });
await session.ready();
const result = await session.tools.bash.call({ command: "ls" }); // or callTool("bash", { command: "ls" }, { toolCallId })
const mcp = await session.mcp("echo"); // raw MCP JSON-RPC
await session.stop("done");

Semantics match the Go Harness: callTool is idempotent by toolCallId; an unknown call's tool.result is -32002, a foreign session -32007; sessions and pending calls survive a reconnect within leaseTtlMs; a newer connection replaces the old one with 4004; lease expiry or executor.close fails pending calls with ToolOutcomeUnknownError.

Transports

Messages are identical on every transport; who dials is independent of the protocol role (the executor always sends executor.register first).

TransportExecutor endHarness end
WebSocket (cloud, executor dials)default, or webSocketDialer(...)listen(), attach(server), handleUpgrade(...)
Local socket, executor dialswebSocketDialer({ ..., tokenSource: staticToken('unix:///path.sock', token) })new Harness({ authenticate: bearerAuthenticator(token) }).listenLocal(path)
Local socket, harness dials (bmad listens)listenExecutor({ path, token }) → new ExecutorClient({ dialer: listener.dialer })harness.dialExecutor(listener.url, { token })
In-processdialer: harness.inProcessDialer()—

Local sockets carry the same WebSocket protocol as the cloud (subprotocol negotiation, bearer token, close codes) over a Unix domain socket (unix:///path.sock, 0700 directory, 0600 socket) or a Windows named pipe (npipe:////./pipe/<name>), as the Go and Python SDKs do. Each start writes a fresh local token (createLocalToken, 0600); environment keys are never used locally (see src/local.ts). When the harness dials the executor it also dials the session MCP channels (session.mcp).

Agent executors

client.serveAgent(
  {
    openTurn: async (p) => ({ accepted: true, turn_id: p.turn_id, executor_session_ref: "conv_1", resumed: false, effective: {} }),
    runTurn: async (turn, signal) => {
      const answer = await turn.requestInteraction({ kind: "permission", tool_use_id: "tu_1", tool_name: "bash" }, signal);
      turn.emit({ type: "agent.message", content: [{ type: "text", text: answer.outcome }] });
    },
    closeSession: async () => {},
  },
  { models: [{ id: "my-model" }] },
);

The SDK assigns event ids and sequences, delivers events in order with ACKs, resends after a reconnect, and frames each turn with session.status_running and session.status_idle (completed, canceled, failed). The harness side is HarnessOptions.agent (AgentPlatform) plus executor.startTurn / sendTurnInput / cancelTurn / closeAgentSession / respondInteraction; a sequence gap answers -32013 with data.expected_sequence.

Examples

examples (npm install there first): executor-minimal (cloud executor with bash), harness-loopback (bmad-style local socket: executor listens, harness dials), agent-minimal (agent executor and in-memory platform in one process).

Go ↔ TypeScript

Go (github.com/beeos-ai/bma-executor-sdk)TypeScript (@beeos-ai/bma-executor-sdk)
NewClient(Options) / Start / Stopnew ExecutorClient(options) / start() / stop()
Options.TokenSource, StaticTokentokenSource, staticToken (or dialer)
Tools.<Name>.Handle(func(ctx, input, tool))tools.<name>.handle(input, { signal, progress })
OnSessionAssign / OnSessionStop / OnDrainonSessionAssign / onSessionStop / onDrain
Requests.ProcessStart.Handle(client, fn) / Calls.X.Call(ctx, client, p) / Notifications.X.Notify(ctx, client, p)onRequest('process.start', fn) / call('x', p) / notify('x', p)
SessionReady / SessionEnded / ReportToolResultsessionReady / sessionEnded / reportToolResult
SendSessionStatus / SendToolProgress / SendExecutorCapabilitiesUpdatesessionStatus / toolProgress / updateCapabilities
OpenMCPChannel → MCPChannelopenMcpChannel → FrameChannel
ServeAgent(AgentHandler, AgentCapability)serveAgent(handler, capability)
AgentTurn.Emit/EmitWithID/Delta/Flush/Inputs/RequestInteraction/CancelInteractionemit/emitWithId/delta/flush/inputs/requestInteraction/cancelInteraction
NewHarness(HarnessOptions) (http.Handler)new Harness(options) + listen/listenLocal/attach/handleUpgrade/accept/dialExecutor/inProcessDialer
Harness.WaitExecutor / ExecutorswaitExecutor / executors
Executor.AssignSession/Drain/StartTurn/SendTurnInput/CancelTurn/CloseAgentSession/RespondInteractionHarnessExecutor.assignSession/drain/startTurn/sendTurnInput/cancelTurn/closeAgentSession/respondInteraction
Session.Ready/CallTool/CancelTool/Stop/MCP/Done/EndReasonHarnessSession.ready/callTool/cancelTool/stop/mcp/done/endReason
AgentPlatformAgentPlatform
*RPCError, ErrNotConnected, ErrClientStopped, ErrSessionEnded, ErrToolOutcomeUnknown, ErrAssignRejectedRpcError, NotConnectedError, ClientStoppedError, SessionEndedError, ToolOutcomeUnknownError, AssignRejectedError
Core (bash/files/stdio MCP)not provided
StaticToken("unix://…"), ListenUnixSocket + Harness.DialExecutor, InProcessDialerHarness.listenLocal / listenExecutor + Harness.dialExecutor / inProcessDialer (named pipes in Go: ListenNamedPipe, NamedPipeListener; Python: serve_npipe)

Tests

npm test runs the conformance examples against both TS ends (test/conformance.test.ts), executor ↔ harness over every transport with reconnect and lease (test/executor.test.ts), the agent capability (test/agent.test.ts), and interop with the Go SDK (test/interop.test.ts: TS executor ↔ Go harness, TS agent ↔ Go platform, TS harness ↔ Go executor; needs Go), and interop with the Python SDK over WebSocket on a Unix socket (test/interop-python.test.ts: TS executor ↔ Python harness, Python executor ↔ TS harness; BMA_PYTHON_SDK=<python dir with .venv>, skipped without it), and TS ↔ Go over a Unix socket (test/interop-go-unix.test.ts).

版本与发布记录

@beeos-ai/bma-executor-sdk 共发布 17 个版本,首次发布于 2026/11/01;过去 12 个月发布了 17 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
1.1.0latest2026/11/08今天
1.0.52026/11/07今天
1.0.32026/11/07今天
1.0.22026/11/07今天
1.0.12026/11/071 天前
1.0.02026/11/061 天前

关于 @beeos-ai/bma-executor-sdk 的常见问题

@beeos-ai/bma-executor-sdk 是什么?

BMA executor protocol (bma.executor.v1.1): executor, harness and agent ends with WebSocket, local socket and in-process transports.

如何安装 @beeos-ai/bma-executor-sdk?

在项目目录运行 npm install @beeos-ai/bma-executor-sdk,或使用 pnpm add / yarn add / bun add。

@beeos-ai/bma-executor-sdk 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@beeos-ai/bma-executor-sdk 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@beeos-ai/bma-executor-sdk 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@beeos-ai/bma-executor-sdk 可以商用吗?

@beeos-ai/bma-executor-sdk 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@beeos-ai/bma-executor-sdk 的下载量有多少?

根据 npm 官方下载统计,@beeos-ai/bma-executor-sdk 在 2026/10/08 – 2026/11/06 这 30 天内被下载 3,599 次,日均约 120 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@beeos-ai/bma-executor-sdk 还在维护吗?

最新版本 1.1.0 发布于 2026/11/08(今天);至今共发布 17 个版本,其中过去 12 个月发布了 17 个;首次发布于 2026/11/01。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。