@cerebro-labs/agentnava-sdk

中文文档
0.80.0发布于 2026/11/08Apache-2.0TypeScriptES Module零依赖近 30 天下载 1813

The AgentNava SDK. Configure an agent, start a conversation, and work with it.

原始使用文档
文档目录20 节
  1. Connecting an account, end to end
  2. 0.80.0: an agent can remember each end user
  3. 0.79.0 is a breaking change: an end user belongs to one age…
  4. 0.44.0 is a breaking change: apps and accounts
  5. 0.2.0 is a breaking change. Upgrade from 0.1.1, do not stay…
  6. Holding an object is free
  7. Asking: one method, two shapes
  8. Changing an agent publishes a version
  9. A conversation pins its version
  10. Two drives
  11. Whose account an agent uses
  12. Drafting tools from an API you already describe
  13. HTTP tool response limits
  14. Errors
  15. Configuration
  16. Pointing at a different deployment
  17. Licence
  18. Widgets in replies (0.50)
  19. Where an agent is live (0.41)
  20. Enterprise HTTP contracts (0.34)

@cerebro-labs/agentnava-sdk

Configure an agent, start a conversation, work with it.

npm install @cerebro-labs/agentnava-sdk

Node 18 or later, or Bun, or any runtime with fetch. Both import and require work, and TypeScript types ship with it.

import { AgentNava } from '@cerebro-labs/agentnava-sdk';

const ws = AgentNava.getWorkspace(); // reads AGENTNAVA_API_KEY

const agent = await ws.agents.create({
  name: 'Refund checker',
  instructions: 'Decide whether a late delivery qualifies for a refund.',
});

const conversation = await agent.start({ endUserId: 'u_ananya' });
console.log(await conversation.ask('Order 4471 arrived nine days late.'));

The full reference is at https://docs.agentnava.com. (It used to point here at a file path inside our own repository, which nobody who installs this package has.) This README covers only the things that are easy to get wrong.

Connecting an account, end to end

An account is one saved sign-in to one app. A connection is an entry in an agent's setup that names one, so the agent uses exactly that account and never another. ws.accounts.connect() gives you a URL for a person to visit, and waitUntilConnected() tells you when they are done.

import { App, Connection } from '@cerebro-labs/agentnava-sdk';

const { account, url } = await ws.accounts.connect({ app: App.GMAIL, nickname: 'Support inbox' });
showToUser(url);                      // popup, redirect, QR code, your choice
await account.waitUntilConnected();   // resolves once they have signed in
await agent.connections.add(Connection.creatorAccount(App.GMAIL, account));

How you show the URL is yours: this SDK authenticates with an API key, so it runs on your server, and the browser half is your application's.

On timeout it throws account_not_connected and the message names the account and where it stands. It is safe to call again; someone who has not finished signing in may just need longer. After account.reconnect() it waits for the new sign-in too: the account keeps working meanwhile, with reconnecting true.

For an app each of YOUR users signs in to themselves, the connection uses the end user's account. Each person signs in once, it is remembered for them, and nothing is attached per conversation:

await agent.connections.add(Connection.endUserAccount(App.GOOGLE_CALENDAR));

const conversation = await agent.start({ endUserId: user.id });
for (const { app, url } of await conversation.accountsNeeded()) {
  showToUser(app, url);               // the ones this person has not connected yet
}

An end user's account is an ordinary Account: agent.endUser(id).accounts.connect() returns one, and waitUntilConnected(), ws.account(id) and every other account call work on it the same way. account.data.owner says whose it is.

The builders make plain objects, { app, account: '<id>' } and { app, endUser: true }, and either object written by hand is accepted too. App.GMAIL is the string 'gmail', so a plain string works anywhere a constant does, including for an app added after your SDK version.

0.80.0: an agent can remember each end user

Nothing changes until you turn it on, and nothing in 0.79 breaks.

await agent.update({ memorySettings: { on: true } });
const conversation = await agent.start({ endUserId: 'u_ananya' });

After a chat goes quiet, a background agent keeps a short memory about that end user, on that agent only, and later chats use it. Your team can read and change it. It needs a Team plan or higher.

New in 0.80What it does
memorySettings on agent.update and ws.agents.createTurn it on, add your own instructions, set how long it is kept
(await agent.get()).memorySettings.noticeThe line to show near your message box, if you draw your own chat
agent.endUser(id).memoryget, set, delete and stop
agent.memories()Who has a memory on this agent, newest change first
agent.start({ endUserId, memory: false })One conversation that uses nothing remembered and adds nothing

The id you pass decides whose memory it is, so use one stable id per person. The guide, with when you may need consent: https://docs.agentnava.com/memory

0.79.0 is a breaking change: an end user belongs to one agent

Each agent is its own app, with its own end users. The same id on two agents is two different people, with separate accounts, secrets and conversations. A person who uses two of your agents signs in to each.

Until now a person's connected account was found by workspace and id alone, so agent B could use an account that a different person with the same id connected through agent A. That is what this closes.

Up to 0.78From 0.79
ws.endUser(id)agent.endUser(id). ws.endUser is gone
ws.endUser(id).accounts.connect({ app })agent.endUser(id).accounts.connect({ app }), once per person on each agent
ws.endUser(id).secrets.create({ name, value, agentId? })agent.endUser(id).secrets.create({ name, value })
ws.secrets.create({ name, value, endUserId })agent.endUser(endUserId).secrets.create({ name, value })
ws.endUser(id).conversations(), across every agentagent.endUser(id).conversations(), this agent. Ask each agent
accounts.disconnectAll() to off-board someoneagent.endUser(id).erase(): their accounts, secrets and conversations on this agent, and it returns how many of each

What an older SDK gets. ws.endUser(id).accounts.* and ws.endUser(id).secrets.* are refused with a 410 (end_users_are_per_agent) that says what to call instead. An empty list would read as "nothing connected", so they never answer one. agent.start, conversation.ask, and conversation.accountsNeeded() with its sign-in link keep working on an older SDK: a conversation knows its agent.

Existing accounts were filed under the agent that used them, with no new sign-in. One that several agents used went to none of them, because the same id may be two people, so each agent asks the person to sign in. Workspace members are unchanged: a member's own account belongs to no agent.

0.44.0 is a breaking change: apps and accounts

Two words now mean two things. An account is one saved sign-in to one app. A connection is an entry in an agent's setup: a name, an app, and whose account it uses, built with Connection.creatorAccount(app, account) for the agent creator's account or Connection.endUserAccount(app) for each end user's own. A turn uses exactly the account the setup names. Nothing is attached to a conversation any more, and nothing falls back to some other account of the same app.

Upgrade together with the production release that serves the new routes: 0.44 calls /v1/accounts, /v1/end-users/:id/accounts and the agent connection routes, and the connection routes 0.43 calls are removed in that same release. There is no compatibility shim: this package is pre-launch, so the old names were removed rather than deprecated.

0.430.44
ws.connections.create({ provider })ws.accounts.connect({ app, nickname? }), returns { account, url }
ws.connections.create({ provider, agentId })ws.accounts.connect({ app }), then agent.connections.add(Connection.creatorAccount(app, account))
ws.connections.create({ provider, endUserId })ws.endUser(id).accounts.connect({ app })
ws.connections.list()ws.accounts.list({ app? }), each with usedBy
ws.connections.list({ endUserId })ws.endUser(id).accounts.list()
ws.connection(id)ws.account(id)
Connection (class): a signed-in accountAccount (class). The name Connection now builds an entry of an agent's setup
connection.get(), returned ConnectionStatusaccount.get(), returns AccountData; a missing account throws account_not_found
connection.waitUntilConnected()account.waitUntilConnected(); times out with account_not_connected
connection.revoke()account.disconnect(), returns { agents }: the agents that lose it
connection.provider, .connected, .owner, .endUserIdaccount.app, .status, .data.owner, .data.endUserId
connection.urlthe url that connect returns
noneaccount.reconnect(), account.rename(nickname), account.check(), account.label, account.nickname, account.usedBy, account.data.reconnecting
agent.authorize(provider)ws.accounts.connect({ app }), then agent.connections.add(Connection.creatorAccount(app, account))
agent.attach(connection)agent.connections.add(Connection.creatorAccount(app, account, { name? }))
noneagent.connections.add(Connection.endUserAccount(app, { name? }))
noneagent.connections.change(name, { account }) or (name, { endUser: true })
noneagent.connections.change(name, { tools }): which of the app's own tools it may use, ids from ws.apps.tools(app); null for the app's default
agent.disconnect(provider), which deleted the shared accountagent.connections.remove(name), which never touches the account
agent.connections()agent.connections.list()
agent.attach(secret), which was accepted and did nothingrefused with not_a_secret; attach a stored secret to a conversation instead
agent.start({ attach: connection })agent.start({ endUserId }): the setup names the accounts
conversation.authorize(provider)conversation.accountsNeeded(): the links an end user still has to sign in at
conversation.attach(connection)none: the agent's setup decides
endUser.connections.create({ provider })endUser.accounts.connect({ app })
endUser.connections.list()endUser.accounts.list()
endUser.revokeConnections()endUser.accounts.disconnectAll(), returns { disconnected }
setup connections: [{ provider, scope: 'fixed', connectionId }]Connection.creatorAccount(app, account, { name?, tools? }), which is { name?, app, account: '<id>', tools? }
setup connections: [{ provider, scope: 'conversation' }]Connection.endUserAccount(app, { name?, tools? }), which is { name?, app, endUser: true, tools? }
type ConnectionRequirementAgentConnection (as read back) and Connection (as you send it), with CreatorAccountConnection and EndUserAccountConnection
type ConnectionStatus, keyed by providerConnectionStatus, keyed by name: { name, app, account?, endUser?, status, inDraft, removedInDraft }
type CredentialOwnerremoved: AccountData.owner is 'workspace' or 'end-user'
Bindings.connectionsremoved: Bindings carries secrets only
noneApp: every app id as a constant (App.GMAIL), and the type AppId

New error codes to branch on: account_not_found, connection_name_taken, account_app_mismatch, connection_not_found (from agent.connections.change), account_not_connected (from waitUntilConnected), account_required (from Connection.creatorAccount with no account), and not_a_secret (an account handed to attach).

Secrets did not move: agent.attach({ secrets }), conversation.attach(secret), conversation.attach({ secrets }) and agent.start({ attach: secret, secrets }) work as before.

0.2.0 is a breaking change. Upgrade from 0.1.1, do not stay on it.

"session" is gone from everything you touch. It survives only in our storage, which you never see.

0.1.10.2.0
GET /v1/sessionsGET /v1/conversations (the old path now 404s)
ConnectionStatus.sessionIdConnectionStatus.conversationId
conversation.authorize() sent sessionIdsends conversationId
scope: 'session' came back from the APIscope: 'conversation', the value the type always declared

0.1.1 stops working once these reach production, because the API reads only the new field names. There is no compatibility shim: this package is private and pre-launch, so the old spellings were removed rather than deprecated.

If you are on 0.1.1 the change is mechanical: rename sessionId to conversationId wherever you read it or pass it. Nothing else about the shape moved.

Holding an object is free

ws.agent(id), agent.conversation(id), ws.project(id) and ws.account(id) make no network call. You get something you can call methods on, and the first request happens when you call one.

Store the id, not the object. A returned object is a handle, not JSON.

Asking: one method, two shapes

const reply = await conversation.ask('...');          // just the answer
for await (const e of conversation.ask('...')) {      // as it happens
  if (e.type === 'progress') showSteps(e.steps);       // the whole list, each time
  if (e.type === 'text') append(e.text);
  if (e.type === 'file') show(await conversation.readFile(e.path));
  if (e.type === 'done') save(e.reply);
}

progress lists the steps the AgentNava app shows while the agent works, in the same words ("Searching the web: steel tariffs"), each active, done or error. Replace what you show with each one. file names a file the agent wrote, once per file.

The message is sent when you call ask, not when you await or iterate it. Calling ask twice generates two submission keys and asks twice. If you need to identify an explicit retry after a lost response, save a key before sending:

const idempotencyKey = crypto.randomUUID();
const reply = await conversation.ask('...', { idempotencyKey });

Reuse that key only with the same content in the same conversation. The API refuses an already accepted turn with turn_already_accepted instead of running it again. A new intentional question needs a new key. Errors include the original key and instructions for checking status; a retryable: false error must not be retried as a fresh submission.

You may await the same turn more than once; it is a promise, and you get the same answer. You may not iterate it twice, or mix the two, because the events are a live stream rather than a stored list and the second consumer would receive nothing at all. That throws instead.

The turn is not your request. Once the server accepts it, a dropped connection, aborting AskOptions.signal, or leaving a for await loop early stops receiving the response. Accepted work can continue and normal billing applies; disconnecting does not cancel server work or automatically refund the turn. An already-aborted signal passed to ask() prevents submission, so no turn is accepted or charged for that call.

Use a signal to limit how long your application waits:

const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 10_000);
try {
  const reply = await conversation.ask('Summarize the report.', {
    signal: controller.signal,
  });
  save(reply);
} catch (error) {
  if (!controller.signal.aborted) throw error;
  // We stopped waiting. A turn already accepted by the server can still run.
} finally {
  clearTimeout(timer);
}

After an interruption, read await conversation.messages() before another ask(). The answer may still be in progress, so check history again later instead of submitting the same question again.

A successful await or done event requires the server's completion marker. If the stream closes early, the SDK reports a nonretryable interruption even when reply text already arrived; use the original submission key to check its status before starting more work.

Changing an agent publishes a version

There is no separate save. agent.update(), setWorkflow, removeWorkflow and setTriggers each publish a new version.

Send only what changes. Everything you do not send is inherited from the current version. That is what makes changing one field safe, and it is not a convenience: a caller that rebuilds and resends a whole configuration drops whatever it forgot, which has caused four separate data-loss bugs in this codebase.

await agent.update({ instructions: 'Be brief.' });    // keeps everything else

A conversation pins its version

It pins the agent's version when it starts and keeps it. Publishing afterwards does not move a conversation that is already open.

Knowledge is the exception, and deliberately so: it is read by reference, so replacing a knowledge file changes what open conversations see on their next turn. A price list you could not correct without republishing the agent would be useless.

Two drives

The agent seesBelongs toYou writeThe agent writes
Knowledge/knowledgethe agentyesno
Conversation/workspaceone conversationyesyes

An application stages material by writing it in before asking about it:

await conversation.writeFile('/input/contract.pdf', bytes);
await conversation.ask('What are the payment terms?');

Anywhere under the root works. input/ is a convention here and nothing enforces it, because the agent and your application share one namespace.

Whose account an agent uses

Each connection in an agent's setup names one account, and a turn uses exactly that one:

Built withWhoseWho signs in
Connection.creatorAccount(app, account)the agent creator'syou or a colleague, once; every conversation uses it
Connection.endUserAccount(app)the end user'seach person, once on each agent, remembered for them

There is no fallback. An account that is not signed in leaves its connection unavailable, and the agent says so rather than reaching for another account of the same app.

Secrets still come in two tiers that do not substitute: fixed is set once on the agent, conversation is supplied per conversation. Check conversation.ready and conversation.requires to see what is outstanding.

Drafting tools from an API you already describe

importTools turns an OpenAPI document, a Postman collection or a working curl command into draft httpTools, so nobody hand-writes one declaration per endpoint.

import { importTools } from '@cerebro-labs/agentnava-sdk';

const draft = importTools({ openapi }, { include: (c) => !c.url.includes('/admin/') });
await ws.agents.create({ id: 'support', instructions, httpTools: draft.tools });

It runs entirely on your machine. Nothing about the source is sent to AgentNava, which matters because these files routinely contain a live token: any credential found is replaced with a {{secrets.NAME}} placeholder and listed in draft.secrets, with no value carried across. Each drafted tool also declares the secrets it uses in its own secrets field, which is what lets the runtime load them for the turn.

They are drafts. draft.warnings names what a human still has to decide, and the big one is description-missing: none of the three formats contains the sentence a model reads to pick a tool, and a bad description does not fail, it makes the agent call the wrong thing.

A rule in an OpenAPI document (a minimum, a length, a pattern, a format, a oneOf) does not cost you the operation. It is written into the parameter's description, Rules: at least 1; at most 100., and your API enforces it as it does for any caller; its 400 reaches the agent as a failed result it can correct. A document that defines one security scheme and never lists it under security gets that scheme on every operation, with a security-choice warning; with several schemes, none is applied and the warning says so.

HTTP tool response limits

Declarative HTTP tools retain at most 64 KiB of response data and stop reading when overflow is detected. The limit counts UTF-8 bytes, not characters. Oversized responses return ok: false, truncated: true, and a response_too_large error body with pagination/filtering guidance; the HTTP status remains the upstream status. Partial JSON is never presented as a complete result. Use the API's filters and pagination to retrieve a large dataset in bounded pages.

Errors

Every failure throws AgentNavaError.

catch (err) {
  if (err instanceof AgentNavaError) {
    err.code;       // 'agent_not_found', branch on THIS
    err.status;     // 404
    err.retryable;  // 5xx, 429, and network failures
    err.retryAfter; // seconds, on a 429
  }
}

Branch on code, not on message. Codes are stable; messages are written for a person reading a log.

retryable is the only thing worth automating on: a 400 will fail identically forever, and a 404 will not become found.

404 covers both "no such thing" and "not yours", so a caller probing ids cannot tell another customer's agent from one that never existed. There is no 403.

Configuration

AgentNava.getWorkspace();                                   // AGENTNAVA_API_KEY
AgentNava.getWorkspace({ apiKey, baseURL, timeout });
AgentNava.getWorkspace({ fetch: myFetch });                 // a proxy, or a test

Runs anywhere fetch exists: Node, Bun, Deno, Cloudflare Workers, the browser. loadWorkflows is the one exception; it reads a directory off disk and so needs Node or Bun.

Pointing at a different deployment

The SDK talks to https://api.agentnava.com unless you say otherwise. Two ways to change it, and the explicit option wins over the environment variable.

// a dedicated enterprise endpoint
const ws = AgentNava.getWorkspace({ apiKey, baseURL: 'https://acme.agentnava.example' });
# staging, which is where a test key works
AGENTNAVA_BASE_URL=https://api-staging.agentnava.com

A staging key against the production URL fails with invalid api key, which reads like a bad key rather than the wrong host. If you were given a test key, set the staging URL above. A trailing slash is fine, and a base with a path prefix works too, for an API gateway sitting in front of us.

Licence

Apache License 2.0. See LICENSE, and NOTICE for the attribution that section 4(d) asks you to carry along.

Free to use, modify and redistribute, including commercially, with an explicit patent grant. Section 6 grants no rights to the names: "AgentNava" and "Cerebro Labs" remain trademarks of Cerebro Labs, Inc.

Widgets in replies (0.50)

agent.start({ endUserId, widgets: true }) says your screen can draw cards, tables and charts. Each turn then streams widget events (a whole widget each time, replace it by surfaceId), done carries the final widgets, and messages() returns them on each reply. Continue one with agent.conversation(id, { widgets: true }). Widgets use Google's open A2UI format with AgentNava's own set of parts (Widget, WidgetPart): draw each with your own components, starting from its part with id root, or use the ready-made React views below. placeWidgets(text, widgets) splits a reply into words and widgets in reading order, for any framework.

import { ReplyView } from '@cerebro-labs/agentnava-sdk/react'
import '@cerebro-labs/agentnava-sdk/react/styles.css'

<ReplyView text={reply} widgets={widgets} onAsk={send} />

React is an optional peer: only @cerebro-labs/agentnava-sdk/react imports it. Every kind of part has a default look, themed with --an-w-* CSS variables; parts={{ Stat: MyStat }} swaps any kind for your own component, renderText draws the words, currency and locale format numbers, and allowUrl narrows which links and pictures open (https:// only by default). An agent's own widgets: false turns them off for it. The agent field's old meaning, a list of widget kinds nothing read, is gone, and so is the widgets.json file loadAgentDir used to load.

Where an agent is live (0.41)

agent.get() and ws.agents.list() carry a read-only status: where the agent is live, by the rule the API runs it by, and in needs any required account or secret not set up yet (it still runs; the tools that use them fail until they are).

Enterprise HTTP contracts (0.34)

Requires the corresponding API and runtime update. Parameter types include object and array, nested properties/items, per-property required, nullable, integer and additionalProperties. Use choices for primitive enums. Supply native JSON values to paramsJson; an object body is not a JSON-encoded string.

The OpenAPI importer respects operation security, document security, and security:[] (public). When a document defines exactly one supported scheme and no security array anywhere names one, that scheme applies to every operation that does not say security: [], with a security-choice warning; with several schemes, or one the importer cannot set up (OAuth, cookie), none is applied and the warning says the tools send no credential. Bearer, Basic and header/query API keys are supported. It preserves inherited parameters, resolves acyclic local schema references, and URL-encodes path/query values. Omitted optional query/header values and an optional whole body are not sent; missing required values still fail before HTTP. Secrets in query values use secretsEncoded.

The secretName override can represent only one credential per operation. The importer checks each security alternative together with the operation's credential header parameters and selects the first compatible alternative. If none can use a single credential, the importer skips the operation with a warning instead of reusing the same secret for independent credentials. Omit the override to generate independent secret names, or configure the operation explicitly.

Review import warnings. Schema rules never skip an operation: bounds, lengths, patterns, string formats and item or field counts are written into the parameter's description, and the API being called enforces them. oneOf/anyOf become one parameter that admits every alternative, with the alternatives described (One of: Card (with type, token); BankTransfer (with type, iban).); "X or null" becomes a nullable X, and a map (additionalProperties holding a schema) stays open to any key. When the alternatives cannot share one shape (a list or an object), the first is kept and an unsupported-body warning names the field. allOf members merge, including a rule-only member beside a type. Choosing among security alternatives produces a warning. What still skips an operation, with an unsupported-operation warning: a contradictory allOf, tuple arrays (prefixItems), recursive or external references, a schema with no type, structured query serialization, cookie authentication and OAuth acquisition/refresh. Schemas cannot declare a property named __proto__, which the Workers schema adapter cannot preserve.

Keywords beside a schema $ref apply together with the referenced schema, as OpenAPI 3.1 defines, at every step of a reference chain: a description replaces the referenced one, a rule is described, an enum narrows the choices. Response-only properties marked readOnly: true are still omitted before importing their request schemas.

All three execution paths share one HTTP executor. Results include lower-cased Link, Retry-After, request ID, ETag, Last-Modified, Content-Type and rate-limit headers. Credential-bearing headers are excluded and known secret values in metadata are redacted. HTTP errors and oversized responses have ok:false at the outer tool envelope too. The response byte limit remains 64 KiB: filter or paginate larger records.

版本与发布记录

@cerebro-labs/agentnava-sdk 共发布 12 个版本,首次发布于 2026/09/21;过去 12 个月发布了 12 个版本。

版本发布日期距今
0.80.0latest2026/11/081 天前
0.79.02026/11/063 天前
0.75.12026/11/018 天前
0.75.02026/11/018 天前
0.74.02026/10/3010 天前
0.53.22026/10/2218 天前

关于 @cerebro-labs/agentnava-sdk 的常见问题

@cerebro-labs/agentnava-sdk 是什么?

The AgentNava SDK. Configure an agent, start a conversation, and work with it.

如何安装 @cerebro-labs/agentnava-sdk?

在项目目录运行 npm install @cerebro-labs/agentnava-sdk,或使用 pnpm add / yarn add / bun add。

@cerebro-labs/agentnava-sdk 支持 TypeScript 吗?

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

@cerebro-labs/agentnava-sdk 支持 ESM 还是 CommonJS?

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

@cerebro-labs/agentnava-sdk 需要什么版本的 Node.js?

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

@cerebro-labs/agentnava-sdk 可以商用吗?

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

@cerebro-labs/agentnava-sdk 的下载量有多少?

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

@cerebro-labs/agentnava-sdk 还在维护吗?

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