Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and

[中文版](CHANGELOG.zh.md) · [README](README.md) · [Contributing](CONTRIBUTING.md)

## [2.4.0] - 2026-10-19

### Added

- **Managed Agent memory** — Manage Memory Stores, memory contents and historical versions, and mount memory into Sessions.
- **Workspace Webhooks** — Manage and test Webhooks, rotate signing secrets, and inspect delivery events.
- **Agent resource bindings** — Agent creation supports default Environment and Vault bindings and MCP market servers through `--environment`, `--vault` and `--mcp`.
- **Runtime environment variables** — Session creation, Session runs and Deployment creation accept repeatable `--environment_variables KEY=VALUE`.

## [2.3.3] - 2026-10-19

### Fixed
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@

[English](CHANGELOG.md) · [README](README.zh.md) · [参与贡献](CONTRIBUTING.zh.md)

## [2.4.0] - 2026-10-19

### 新增

- **Managed Agent 记忆管理** —— 支持管理 Memory Store、记忆内容和历史版本,并将记忆挂载到 Session。
- **工作空间 Webhook** —— 支持管理和测试 Webhook、重置签名密钥及查询投递事件。
- **Agent 资源绑定** —— 创建 Agent 时可通过 `--environment`、`--vault` 和 `--mcp` 绑定默认 Environment、Vault 及 MCP 市场服务。
- **运行时环境变量** —— 创建 Session、运行 Session 和创建 Deployment 时可重复传入 `--environment_variables KEY=VALUE`。

## [2.3.3] - 2026-10-19

### 修复
Expand Down
8 changes: 8 additions & 0 deletions docs/agents/auth-change.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ defineCommand({ auth }) → runtime/authStage → ctx.client → command.run(ctx

禁止命令层直接 `readConfigFile` 裸读凭证;Bailian 字段以 CLI 鉴权链为唯一信源。SDK bootstrap 期间读取到的兼容凭证变量也会在配置解析后统一清扫。

### 例外:工作空间 Webhook 的轻量 SDK context

`managed-agent webhook` 是工作空间 API,使用 `auth: "apiKey"`,不加载 `agents.yaml`、项目 `.env` 或 State,也不走上述项目凭证 bootstrap。命令通过 `managed-agent/_engine/**` 的轻量 context,将 `ctx.client.exportApiCredential()`、解析后的 Base URL 与工作空间作用域仅在内存中交给 SDK,固定 `provider: "bailian"`,并按 runtime 的 `settings.timeout` 构造请求 `AbortSignal`。不得用 YAML 临时文件拼接 Provider,也不得另建一条 HTTP 或凭证解析链。

- `--workspace-id` 由命令自声明,读取 `ctx.flags.workspaceId` 后回退 `ctx.settings.workspaceId`;Base URL 和 API Key 复用 CLI 的 flags → env → Profile 优先级。
- 所有 Webhook 写操作声明 `risk: "high"`;`--dry-run` 在创建 SDK context、读取项目文件或请求远端前返回,不要求 API Key。
- `create` / `reset-secret` 成功响应中的 `signing_secret` 保留在用户请求的结果中;verbose 诊断和错误输出必须脱敏,不得把成功响应的密钥复制到日志。服务端错误的普通 message 仍原样透传。

## 必查清单

### A. core 层(类型 + 解析)
Expand Down
2 changes: 2 additions & 0 deletions docs/agents/command-flag-change.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ pnpm -F bailian-cli exec tsx src/main.ts <command> --new-flag x # 实测一遍

## 常见漏点

- SDK 的通用参数解析器可能接受其他 Provider 的资源类型;产品命令应在鉴权和 dry-run 输出前限制支持的类型,错误文案不得回显资源凭证或任意输入值。
- 定向创建仅解析新增资源声明;已有项目中的 instructions / Memory 正文已完成文件读取,不能再把它们当作原始文件路径解析。跨资源引用继续交给完整项目的定向 plan 校验。
- 修改请求超时时,检查底层 HTTP 限制:core 的公共 HTTP 请求层仅为已设置超时计时器的请求关闭 `headersTimeout`,由请求 `AbortSignal` 控制响应头等待;保留 `bodyTimeout` 空闲保护,不全局关闭其他请求的超时。仅设置 Agent 默认值会被 Node `fetch` 的逐请求默认值覆盖。流式读取的总时长另由调用方管理,不能据此宣称 `--timeout` 覆盖整个流。
- ✗ 加了 array 型 flag 但没考虑用户可能传多次
- ✗ 改默认值忘记更新 description 里的 "(default: xxx)" 文案
Expand Down
2 changes: 2 additions & 0 deletions docs/agents/lint-toolchain.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
- [ ] 检查 workspace 内部依赖在 `dependencies` 里仍是 `"workspace:*"`(不要手改成实际版本号;发布时由 pack/publish 流程解析)
- [ ] 升级后跑 `vp check && vp test`
- [ ] 升级 `@types/node` 时注意 Node API 变化(如 fs.existsSync 行为)
- [ ] 使用 pnpm dependency patch 时提交补丁、注册配置和 lockfile,并用 frozen install 验证;如果依赖在构建中是 external,workspace 补丁不会随 npm 包传递,发布前必须更新到包含修复的上游版本

### E. git hooks / pre-commit

Expand Down Expand Up @@ -66,3 +67,4 @@ node tools/release/check.mjs
- ✗ 改 cli/kscli 的 vite config 把 core/runtime/commands 不小心打成 inline,bundle 体积暴涨
- ✗ Oxlint 配置改了但 IDE 缓存还是旧的(IDE 可能要重启 ts server)
- ✗ 升级依赖一并升 lockfile,改动量大但没拆 commit
- ✗ 本地源码启动器未设置 `NO_UPDATE_NOTIFIER=1`,旧分支运行成功后触发 npm 全局自动升级;验证时应覆盖普通命令路径,不能只测 `--help`
2 changes: 2 additions & 0 deletions docs/agents/telemetry-change.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ source-config 只用于百炼 / DashScope API 侧消费,不发送到通用网

参数默认不上传,只有 `packages/core/src/telemetry/tracker.ts` 的 `PARAM_ALLOWLIST` 中字段会进入事件。不得加入 prompt、凭证、文件路径、URL、账号/租户/工作空间 ID 或其他用户内容。

`page` 仅记录有限数值页码;Webhook 等命令使用的不透明字符串游标不得进入本地日志或远端 AEM,即使字符串内容看起来是数字。

事件同时写入两处:

1. 本地 `~/.bailian/telemetry.jsonl`:权限 `0600`,超过 5 MB 后重建
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bailian-cli",
"version": "2.3.3",
"version": "2.4.0",
"description": "CLI for Aliyun Model Studio (DashScope) AI Platform.",
"keywords": [
"agent",
Expand Down
42 changes: 42 additions & 0 deletions packages/cli/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,27 @@ import {
managedAgentFileSearch,
managedAgentFileDownload,
managedAgentFileDelete,
managedAgentMemoryStoreCreate,
managedAgentMemoryStoreList,
managedAgentMemoryStoreGet,
managedAgentMemoryCreate,
managedAgentMemoryList,
managedAgentMemoryGet,
managedAgentMemoryUpdate,
managedAgentMemoryDelete,
managedAgentMemoryVersionList,
managedAgentMemoryVersionGet,
managedAgentMemoryVersionRedact,
managedAgentWebhookCreate,
managedAgentWebhookList,
managedAgentWebhookGet,
managedAgentWebhookUpdate,
managedAgentWebhookDelete,
managedAgentWebhookEnable,
managedAgentWebhookDisable,
managedAgentWebhookTest,
managedAgentWebhookResetSecret,
managedAgentWebhookEvents,
sandboxCreate,
sandboxOfficialImages,
sandboxFileUpload,
Expand Down Expand Up @@ -566,6 +587,27 @@ export const commands: Record<string, AnyCommand> = {
"managed-agent file search": managedAgentFileSearch,
"managed-agent file download": managedAgentFileDownload,
"managed-agent file delete": managedAgentFileDelete,
"managed-agent memory-store create": managedAgentMemoryStoreCreate,
"managed-agent memory-store list": managedAgentMemoryStoreList,
"managed-agent memory-store get": managedAgentMemoryStoreGet,
"managed-agent memory-store memories create": managedAgentMemoryCreate,
"managed-agent memory-store memories list": managedAgentMemoryList,
"managed-agent memory-store memories get": managedAgentMemoryGet,
"managed-agent memory-store memories update": managedAgentMemoryUpdate,
"managed-agent memory-store memories delete": managedAgentMemoryDelete,
"managed-agent memory-store versions list": managedAgentMemoryVersionList,
"managed-agent memory-store versions get": managedAgentMemoryVersionGet,
"managed-agent memory-store versions redact": managedAgentMemoryVersionRedact,
"managed-agent webhook create": managedAgentWebhookCreate,
"managed-agent webhook list": managedAgentWebhookList,
"managed-agent webhook get": managedAgentWebhookGet,
"managed-agent webhook update": managedAgentWebhookUpdate,
"managed-agent webhook delete": managedAgentWebhookDelete,
"managed-agent webhook enable": managedAgentWebhookEnable,
"managed-agent webhook disable": managedAgentWebhookDisable,
"managed-agent webhook test": managedAgentWebhookTest,
"managed-agent webhook reset-secret": managedAgentWebhookResetSecret,
"managed-agent webhook events": managedAgentWebhookEvents,
"sandbox create": sandboxCreate,
"sandbox official-images": sandboxOfficialImages,
"sandbox file upload": sandboxFileUpload,
Expand Down
1 change: 1 addition & 0 deletions packages/cli/tests/skill-risk-confirmation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const scopedCreateCommands = new Set([
"bl managed-agent agent create",
"bl managed-agent deployment create",
"bl managed-agent environment create",
"bl managed-agent memory-store create",
"bl managed-agent skill create",
"bl managed-agent vault create",
"bl managed-agent vault credential create",
Expand Down
4 changes: 2 additions & 2 deletions packages/commands/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bailian-cli-commands",
"version": "2.3.3",
"version": "2.4.0",
"description": "Command library for bailian-cli products (knowledge, memory, media, …). See https://www.npmjs.com/package/bailian-cli for usage.",
"homepage": "https://bailian.console.aliyun.com/cli",
"bugs": {
Expand Down Expand Up @@ -40,7 +40,7 @@
"check": "vp check"
},
"dependencies": {
"@openagentpack/sdk": "0.7.1",
"@openagentpack/sdk": "0.9.0",
"bailian-cli-core": "workspace:*",
"bailian-cli-runtime": "workspace:*",
"boxen": "catalog:",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { BailianError, ExitCode, type FlagsDef } from "bailian-cli-core";

export const ENVIRONMENT_VARIABLE_FLAGS = {
// Preserve the API-aligned spelling requested for Session and Deployment commands.
environment_variables: {
type: "array",
valueHint: "<KEY=VALUE>",
description: {
"en-US": "Runtime environment variable; repeat for multiple values (last value wins)",
"zh-CN": "运行时环境变量;可重复传入,同名变量以后一次为准",
},
},
} satisfies FlagsDef;

export function parseEnvironmentVariables(
entries: string[] | undefined,
): Record<string, string> | undefined {
if (!entries?.length) return undefined;
return Object.fromEntries(
entries.map((entry) => {
const separator = entry.indexOf("=");
if (separator < 0) {
throw new BailianError(
"--environment_variables values must use KEY=VALUE format. / --environment_variables 必须使用 KEY=VALUE 格式。",
ExitCode.USAGE,
);
}
const key = entry.slice(0, separator).trim();
if (!key) {
throw new BailianError(
"--environment_variables keys must not be empty. / --environment_variables 的键不能为空。",
ExitCode.USAGE,
);
}
return [key, entry.slice(separator + 1)];
}),
);
}

export function redactEnvironmentVariables(
variables: Record<string, unknown> | undefined,
): Record<string, string> | undefined {
return variables === undefined
? undefined
: Object.fromEntries(Object.keys(variables).map((key) => [key, "[REDACTED]"]));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { createManagedApiContext, resolveBailianApiBaseUrl, UserError } from "@openagentpack/sdk";
import { BailianError, type Client, ExitCode } from "bailian-cli-core";
import { type HostContext, installSdkTransport } from "./transport.ts";

export interface ManagedApiHost extends HostContext {
client: Client;
flags: { workspaceId?: string };
}

/** Resolve the CLI connection without loading project files or credentials. */
export function resolveManagedApiBaseUrl(host: ManagedApiHost): string {
const configuredBaseUrl = host.client.url("", () => "") || undefined;
const workspaceId = host.flags.workspaceId ?? host.settings.workspaceId;
if (!configuredBaseUrl && !workspaceId?.trim()) {
throw new BailianError(
"Workspace ID is required when no base URL is configured. / 未配置 Base URL 时必须提供 Workspace ID。",
ExitCode.USAGE,
"Pass --workspace-id, set BAILIAN_WORKSPACE_ID, configure workspace_id, or set --base-url. / 请传入 --workspace-id、设置 BAILIAN_WORKSPACE_ID 或 workspace_id 配置,或通过 --base-url 指定地址。",
);
}
try {
return resolveBailianApiBaseUrl({ baseUrl: configuredBaseUrl, workspaceId });
} catch (error) {
if (!(error instanceof UserError)) throw error;
throw new BailianError(
configuredBaseUrl
? "The configured API base URL is invalid. / 配置的 API Base URL 无效。"
: "Workspace ID must be a valid hostname label. / Workspace ID 必须是有效的主机名标签。",
ExitCode.USAGE,
configuredBaseUrl
? "Configure a valid HTTP or HTTPS API base URL. / 请配置有效的 HTTP 或 HTTPS API Base URL。"
: "Use a workspace ID such as workspace-example, or set --base-url. / 请使用 workspace-example 这样的有效 Workspace ID,或设置 --base-url。",
);
}
}

/** Delegate the authStage-resolved credential to the SDK in memory only. */
export function buildManagedApiContext(host: ManagedApiHost) {
const credential = host.client.exportApiCredential();
if (!credential?.token.trim()) {
throw new BailianError(
"A Bailian API key is required. / 必须提供百炼 API Key。",
ExitCode.AUTH,
"Pass --api-key, set DASHSCOPE_API_KEY, or log in with auth login --api-key <key>. / 请传入 --api-key、设置 DASHSCOPE_API_KEY,或使用 auth login --api-key <key> 登录。",
);
}
const baseUrl = resolveManagedApiBaseUrl(host);
installSdkTransport(host);
return createManagedApiContext({
providers: {
bailian: {
api_key: credential.token,
base_url: baseUrl,
workspace_id: host.flags.workspaceId ?? host.settings.workspaceId,
},
},
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@ import { createFileStateScope } from "./file-state-manager.ts";
import { installSdkTransport } from "./transport.ts";

export type ScopedCreateHost = Pick<CommandContext, "client" | "identity" | "settings">;
export type ScopedCreateGroup = "agents" | "environments" | "skills" | "vaults" | "deployments";
export type ScopedCreateGroup =
| "agents"
| "environments"
| "skills"
| "vaults"
| "deployments"
| "memory_stores";

export interface LoadedScopedCreateProject {
configPath: string;
Expand Down Expand Up @@ -54,7 +60,7 @@ export interface ScopedTopLevelCreateInput {
host: ScopedCreateHost;
project: LoadedScopedCreateProject;
group: Exclude<ScopedCreateGroup, "agents">;
resourceType: "environment" | "skill" | "vault" | "deployment";
resourceType: "environment" | "skill" | "vault" | "deployment" | "memory_store";
displayName: string;
rawDeclaration: Record<string, unknown>;
resolvedDeclaration: Record<string, unknown>;
Expand Down Expand Up @@ -104,7 +110,12 @@ export function selectResourceKey(options: {
.filter(
(resource) =>
resource.address.provider === options.provider &&
options.resourceTypes.includes(resource.address.type),
options.resourceTypes.includes(resource.address.type) &&
!(
resource.address.type === "memory_store" &&
resource.remote_id &&
resource.memory_initialization
),
)
.map((resource) => resource.address.name),
);
Expand Down Expand Up @@ -161,14 +172,19 @@ export async function loadScopedCreateProject(
}

export async function resolveCandidateDeclaration(options: {
project: LoadedScopedCreateProject;
project: Pick<LoadedScopedCreateProject, "config" | "configPath" | "projectName">;
group: ScopedCreateGroup;
rawDeclaration: Record<string, unknown>;
}): Promise<Record<string, unknown>> {
const temporaryKey = "__bailian_cli_create_candidate__";
const rawConfig = structuredClone(options.project.config) as unknown as Record<string, unknown>;
const declarations = (rawConfig[options.group] ?? {}) as Record<string, unknown>;
rawConfig[options.group] = { ...declarations, [temporaryKey]: options.rawDeclaration };
// Existing resources already have resolved file contents. Resolve only the new
// declaration here; scoped planning checks its references against the full project.
const rawConfig = {
version: options.project.config.version,
providers: options.project.config.providers,
defaults: options.project.config.defaults,
[options.group]: { [temporaryKey]: options.rawDeclaration },
};
const resolved = await withAgentErrors(() =>
resolveProjectConfigFromObject(rawConfig, {
projectName: options.project.projectName,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { parseSessionResources } from "@openagentpack/sdk";
import { BailianError, ExitCode, type FlagsDef } from "bailian-cli-core";
import { withAgentErrors } from "./errors.ts";
import { parseJsonInputs } from "./scoped-create.ts";

export const SESSION_RESOURCE_FLAGS = {
resource: {
type: "array",
valueHint: "<json|@path>",
description: {
"en-US":
"Session resource JSON (repeatable); memory_store uses a YAML key, access and instructions",
"zh-CN": "Session 资源 JSON(可重复);memory_store 使用 YAML key、access 和 instructions",
},
},
} satisfies FlagsDef;

export async function readSessionResources(values?: string[]) {
if (values === undefined) return undefined;
const decoded = await parseJsonInputs(values, "resource");
if (
decoded.some(
(resource) =>
resource === null ||
typeof resource !== "object" ||
!("type" in resource) ||
resource.type !== "memory_store",
)
) {
throw new BailianError(
"Bailian Session resources only support memory_store. / 百炼 Session 资源仅支持 memory_store。",
ExitCode.USAGE,
);
}
return withAgentErrors(async () => parseSessionResources(decoded));
}
Loading
Loading