Merge remote-tracking branch 'origin/main' into feat/sandbox

This commit is contained in:
chenanran555
2026-09-08 14:23:30 +08:00
30 changed files with 2037 additions and 37 deletions
+13
View File
@@ -16,6 +16,19 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
- Sandbox calls use the Bailian API Key as a Bearer token without an E2B SDK or E2B API key. Returned connection credentials and dry-run environment values are redacted by default; use `--show-credentials` only when the raw connection tokens are required.
## [1.21.0] - 2026-09-07
### Added
- **Managed Agent directory projects** — initialize, validate, build, and publish local projects with Agent-scoped resource configuration and resource examples.
- **Local project versions** — enable or disable snapshot versioning, list and preview history, and restore project files without Git.
- **Project Workbench** — edit resources, review changes, publish updates, and manage local versions in a browser.
### Changed
- **Playground version resolution** — check npm before launch, reuse matching local versions, and fetch the latest version when needed; retain explicit version and binary overrides.
- **Managed Agent SDK** — upgrade to `0.7.0` and consume project workspace and version services through SDK subpath exports.
## [1.20.0] - 2026-09-03
> Managed Agents now combines YAML-first infrastructure management with direct Bailian AgentStudio resource and runtime operations.
+13
View File
@@ -16,6 +16,19 @@
- Sandbox 调用使用百炼 API Key 作为 Bearer Token不依赖 E2B SDK也不发送 E2B API Key。连接凭据和 dry-run 中的环境变量默认脱敏;仅在确实需要原始连接 Token 时使用 `--show-credentials`
## [1.21.0] - 2026-09-07
### 新增
- **Managed Agent 目录项目** —— 支持初始化、校验、构建和发布本地项目,按 Agent 组织资源配置,并提供资源示例。
- **本地项目版本管理** —— 无需 Git 即可启停快照版本管理、查看和预览历史,以及恢复项目文件。
- **项目 Workbench** —— 在浏览器中编辑资源、审阅变更、发布更新和管理本地版本。
### 变更
- **Playground 版本选择** —— 启动前查询 npm复用版本一致的本地安装否则按需下载最新版保留显式版本和启动文件覆盖。
- **Managed Agent SDK** —— 升级至 `0.7.0`,通过 SDK 子路径复用项目目录和版本服务。
## [1.20.0] - 2026-09-03
> Managed Agent 现在同时提供 YAML-first 基础设施管理与百炼 AgentStudio 资源、运行时 API 操作。
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "bailian-cli",
"version": "1.20.0",
"version": "1.21.0",
"description": "CLI for Aliyun Model Studio (DashScope) AI Platform.",
"keywords": [
"agent",
+24
View File
@@ -140,6 +140,18 @@ import {
managedAgentPlan,
managedAgentApply,
managedAgentDestroy,
managedAgentPlayground,
managedAgentProjectInit,
managedAgentProjectValidate,
managedAgentProjectBuild,
managedAgentProjectPublish,
managedAgentProjectWorkbench,
managedAgentProjectVersionEnable,
managedAgentProjectVersionDisable,
managedAgentProjectVersionStatus,
managedAgentProjectVersionList,
managedAgentProjectVersionPreview,
managedAgentProjectVersionRestore,
managedAgentStateList,
managedAgentStateShow,
managedAgentStateRm,
@@ -357,6 +369,18 @@ export const commands: Record<string, AnyCommand> = {
"managed-agent plan": managedAgentPlan,
"managed-agent apply": managedAgentApply,
"managed-agent destroy": managedAgentDestroy,
"managed-agent playground": managedAgentPlayground,
"managed-agent project init": managedAgentProjectInit,
"managed-agent project validate": managedAgentProjectValidate,
"managed-agent project build": managedAgentProjectBuild,
"managed-agent project publish": managedAgentProjectPublish,
"managed-agent project workbench": managedAgentProjectWorkbench,
"managed-agent project version enable": managedAgentProjectVersionEnable,
"managed-agent project version disable": managedAgentProjectVersionDisable,
"managed-agent project version status": managedAgentProjectVersionStatus,
"managed-agent project version list": managedAgentProjectVersionList,
"managed-agent project version preview": managedAgentProjectVersionPreview,
"managed-agent project version restore": managedAgentProjectVersionRestore,
"managed-agent state list": managedAgentStateList,
"managed-agent state show": managedAgentStateShow,
"managed-agent state rm": managedAgentStateRm,
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "bailian-cli-commands",
"version": "1.20.0",
"version": "1.21.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": {
@@ -40,7 +40,7 @@
"check": "vp check"
},
"dependencies": {
"@openagentpack/sdk": "0.5.0",
"@openagentpack/sdk": "0.7.0",
"bailian-cli-core": "workspace:*",
"bailian-cli-runtime": "workspace:*",
"boxen": "catalog:",
@@ -30,9 +30,11 @@ export const BAILIAN_PROVIDER = "bailian";
interface AgentConfigOptions {
resolveEnv?: boolean;
environment?: Record<string, string>;
projectName?: string;
statePath?: string;
credentials?: CredentialScope;
overrideBailianBaseUrl?: boolean;
}
/**
@@ -56,7 +58,9 @@ export async function resolveAgentProjectConfig(
prepareProviderEnv();
const resolved = await resolveProjectConfig(filePath, options);
normalizeInterpolatedProviderBlocks(resolved.config.providers);
injectProviderCredentials(resolved.config.providers, host);
injectProviderCredentials(resolved.config.providers, host, {
overrideBaseUrl: options.overrideBailianBaseUrl,
});
scrubCredentialEnv();
assertBailianOnlyProviders(resolved.config.providers);
if ((options.credentials ?? "all") !== "none") {
@@ -99,9 +99,10 @@ export function prepareProviderEnv(): void {
* Override the bailian provider block with bl's authStage-resolved credential, so
* the bailian API key is authoritatively the CLI auth chain's never a config
* file bare-read or a stale env value. `api_key` is replaced unconditionally
* when a credential resolved; `base_url` / `workspace_id` are filled only when
* the block references them and the interpolated value is empty (a literal in
* agents.yaml is respected).
* when a credential resolved. `base_url` is normally filled only when empty so
* a literal in agents.yaml remains supported; directory projects pass
* `overrideBaseUrl` so their connection always follows the Bailian CLI auth
* chain. `workspace_id` is filled only when empty.
*
* `base_url` carries {@link AGENTSTUDIO_API_PATH} because the SDK appends resource
* paths onto it verbatim; a value already ending in the suffix is left as-is.
@@ -114,6 +115,7 @@ export function prepareProviderEnv(): void {
export function injectProviderCredentials(
providers: Record<string, unknown>,
host: CredentialHost,
options: { overrideBaseUrl?: boolean } = {},
): void {
const bailian = providers.bailian;
if (!bailian || typeof bailian !== "object") return;
@@ -121,7 +123,7 @@ export function injectProviderCredentials(
const cred = host.client.exportApiCredential();
if (cred) block.api_key = cred.token;
if ("base_url" in block && !block.base_url) {
if ("base_url" in block && (options.overrideBaseUrl || !block.base_url)) {
// Defensive normalization: the auth chain already normalizes base_url to
// an origin, but never let a trailing slash produce "//api/v1/agentstudio".
const origin = host.client.baseUrl.replace(/\/+$/, "");
@@ -0,0 +1,432 @@
import { execFile, spawn, type ChildProcess } from "node:child_process";
import { createHash, randomBytes } from "node:crypto";
import { existsSync, readFileSync } from "node:fs";
import { createRequire } from "node:module";
import { dirname, resolve } from "node:path";
import { promisify } from "node:util";
import { BailianError, type Client, ExitCode, type Settings } from "bailian-cli-core";
import { emitBare } from "bailian-cli-runtime";
const PLAYGROUND_PACKAGE = "@openagentpack/playground";
const execFileAsync = promisify(execFile);
const DEFAULT_PORT = 4848;
const PLAYGROUND_URL_PATTERN = /running at http:\/\/localhost:(\d+)/i;
export interface PlaygroundLaunchOptions {
port?: number;
open: boolean;
file?: string;
project?: string;
agent?: string;
surface: "preview" | "workbench";
client: Client;
settings: Settings;
}
interface Launcher {
command: string;
args: string[];
version?: string;
fetched: boolean;
}
interface ExistingPlayground {
version: string;
pid: number;
projectId?: string;
}
interface PlaygroundProjectSummary {
status?: string;
agents?: Array<{ agent?: { id?: string } }>;
}
export interface PlaygroundBrowserTarget {
url: string;
warning?: string;
}
export async function launchManagedAgentPlayground(
options: PlaygroundLaunchOptions,
): Promise<void> {
assertSupportedNodeVersion();
const port = options.port ?? DEFAULT_PORT;
if (!Number.isInteger(port) || port <= 0 || port > 65_535) {
throw new BailianError(`Invalid --port '${port}'.`, ExitCode.USAGE);
}
const sourcePath = resolve(
options.surface === "workbench" ? (options.project ?? ".") : (options.file ?? "agents.yaml"),
);
const projectId = createHash("sha256").update(sourcePath).digest("hex").slice(0, 16);
const launcher = await resolveLauncher();
const existing = await probeExistingPlayground(port);
if (existing) {
const reusable =
existing.projectId === projectId &&
(launcher.version === undefined || existing.version === launcher.version);
if (reusable) {
emitBare(`Workbench already running at http://localhost:${port} (pid ${existing.pid}).`);
await openPlaygroundSurface(port, options.surface, options.agent, options.open);
return;
}
const released = await replaceExistingPlayground(existing, port);
if (!released) {
throw new BailianError(
`Could not stop the existing Workbench process (pid ${existing.pid}) on port ${port}.`,
ExitCode.GENERAL,
"Stop it manually or choose another --port.",
);
}
}
const environment = buildPlaygroundEnvironment(options, port, sourcePath);
if (launcher.fetched) {
emitBare(`Fetching ${PLAYGROUND_PACKAGE} (first run may take a moment)...`);
}
const child = spawn(launcher.command, launcher.args, {
env: environment,
stdio: ["inherit", "pipe", "inherit"],
});
const removeSignalForwarding = forwardSignals(child);
try {
const readyPort = await waitForPlaygroundReady(child, port, 30_000, projectId);
if (readyPort === null) {
throw new BailianError(
`Workbench did not become ready in time. Check the logs above, then open http://localhost:${port}.`,
ExitCode.GENERAL,
);
}
emitBare(`Workbench ready at http://localhost:${readyPort}`);
await openPlaygroundSurface(readyPort, options.surface, options.agent, options.open);
const exitCode = await waitForChildExit(child);
if (exitCode !== 0) {
throw new BailianError(`Workbench exited with code ${exitCode}.`, ExitCode.GENERAL);
}
} finally {
removeSignalForwarding();
}
}
export function playgroundBrowserTargetFromSummary(
baseUrl: string,
summary: PlaygroundProjectSummary,
requestedAgent?: string,
): PlaygroundBrowserTarget {
if (summary.status !== "valid") return { url: baseUrl };
const agentIds = (summary.agents ?? [])
.map((entry) => entry.agent?.id?.trim())
.filter((agentId): agentId is string => Boolean(agentId));
const requested = requestedAgent?.trim();
if (requested) {
if (agentIds.includes(requested)) {
return { url: `${baseUrl}/agents/${encodeURIComponent(requested)}/preview` };
}
return {
url: baseUrl,
warning: `Agent '${requested}' was not found. Opening the project Workbench instead.`,
};
}
if (agentIds.length === 1) {
return { url: `${baseUrl}/agents/${encodeURIComponent(agentIds[0]!)}/preview` };
}
if (agentIds.length > 1) {
return {
url: baseUrl,
warning:
"This project declares multiple Agents. Opening the Workbench; rerun with --agent <id> for Preview.",
};
}
return { url: baseUrl };
}
function assertSupportedNodeVersion(): void {
const majorVersion = Number(process.versions.node.split(".")[0]);
if (Number.isFinite(majorVersion) && majorVersion >= 22) return;
throw new BailianError(
"Managed Agent Workbench requires Node.js 22 or later.",
ExitCode.USAGE,
"Upgrade Node.js for Workbench; other Bailian CLI commands continue to support Node.js 18.17+.",
);
}
export async function resolveLauncher(): Promise<Launcher> {
const explicit =
process.env.BAILIAN_MANAGED_AGENT_PLAYGROUND_BIN?.trim() ||
process.env.AGENTS_PLAYGROUND_BIN?.trim();
if (explicit) {
if (!existsSync(explicit)) {
throw new BailianError(
`Configured Workbench binary does not exist: ${explicit}`,
ExitCode.USAGE,
);
}
return { command: process.execPath, args: [explicit], fetched: false };
}
const requestedVersion = process.env.BAILIAN_MANAGED_AGENT_PLAYGROUND_VERSION?.trim() || "latest";
const { stdout } = await execFileAsync(
"npm",
[
"view",
`${PLAYGROUND_PACKAGE}@${requestedVersion}`,
"version",
"--json",
"--prefer-online",
"--fetch-retries=0",
"--fetch-timeout=10000",
],
{ timeout: 15_000, maxBuffer: 1024 * 1024 },
);
let resolvedVersion: unknown;
try {
resolvedVersion = JSON.parse(stdout);
} catch {
resolvedVersion = undefined;
}
if (
typeof resolvedVersion !== "string" ||
!/^\d+\.\d+\.\d+(?:-[\da-zA-Z.-]+)?(?:\+[\da-zA-Z.-]+)?$/.test(resolvedVersion)
) {
throw new BailianError(
"npm did not return a single valid Playground version. Specify an exact version or dist-tag. / npm 未返回唯一有效的 Playground 版本号。请指定精确版本或 dist-tag。",
ExitCode.GENERAL,
);
}
const installed = resolveInstalledPlayground();
if (installed?.version === resolvedVersion) return installed;
const monorepoBinary = findLocalPlaygroundBin(process.cwd());
if (
monorepoBinary &&
readPlaygroundVersion(resolve(dirname(monorepoBinary), "../../package.json")) ===
resolvedVersion
) {
return {
command: process.execPath,
args: [monorepoBinary],
version: resolvedVersion,
fetched: false,
};
}
return {
command: "npx",
args: ["-y", `${PLAYGROUND_PACKAGE}@${resolvedVersion}`],
version: resolvedVersion,
fetched: true,
};
}
function readPlaygroundVersion(manifestPath: string): string | undefined {
try {
return (JSON.parse(readFileSync(manifestPath, "utf8")) as { version?: string }).version;
} catch {
return undefined;
}
}
function resolveInstalledPlayground(): Launcher | undefined {
try {
const require = createRequire(import.meta.url);
const packageJsonPath = require.resolve(`${PLAYGROUND_PACKAGE}/package.json`);
const manifest = JSON.parse(readFileSync(packageJsonPath, "utf8")) as {
version?: string;
bin?: string | Record<string, string>;
};
const relativeBinary =
typeof manifest.bin === "string" ? manifest.bin : manifest.bin?.["agents-playground"];
if (!relativeBinary) return undefined;
const binaryPath = resolve(dirname(packageJsonPath), relativeBinary);
if (!existsSync(binaryPath)) return undefined;
return {
command: process.execPath,
args: [binaryPath],
version: manifest.version,
fetched: false,
};
} catch {
return undefined;
}
}
function findLocalPlaygroundBin(startDirectory: string): string | undefined {
let directory = startDirectory;
for (let depth = 0; depth < 10; depth += 1) {
const candidate = resolve(directory, "packages/playground/dist/bin/playground.js");
if (existsSync(candidate)) return candidate;
const parent = dirname(directory);
if (parent === directory) break;
directory = parent;
}
return undefined;
}
function buildPlaygroundEnvironment(
options: PlaygroundLaunchOptions,
port: number,
sourcePath: string,
): NodeJS.ProcessEnv {
const credential = options.client.exportApiCredential();
const environment: NodeJS.ProcessEnv = {
...process.env,
PORT: String(port),
AGENTS_PLAYGROUND_TOKEN: randomBytes(32).toString("hex"),
};
if (options.surface === "workbench") {
delete environment.AGENTS_CONFIG_PATH;
environment.AGENTS_PROJECT_ROOT = sourcePath;
} else {
delete environment.AGENTS_PROJECT_ROOT;
environment.AGENTS_CONFIG_PATH = sourcePath;
}
if (credential) environment.DASHSCOPE_API_KEY = credential.token;
const baseUrl = options.client.baseUrl.replace(/\/+$/, "");
environment.BAILIAN_BASE_URL = baseUrl.endsWith("/api/v1/agentstudio")
? baseUrl
: `${baseUrl}/api/v1/agentstudio`;
if (options.settings.workspaceId) {
environment.BAILIAN_WORKSPACE_ID = options.settings.workspaceId;
}
return environment;
}
async function waitForPlaygroundReady(
child: ChildProcess,
fallbackPort: number,
timeoutMs: number,
expectedProjectId: string,
): Promise<number | null> {
let port = fallbackPort;
let outputBuffer = "";
child.stdout?.on("data", (chunk: Buffer | string) => {
process.stdout.write(chunk);
outputBuffer += chunk.toString();
const match = outputBuffer.match(PLAYGROUND_URL_PATTERN);
if (match?.[1]) port = Number(match[1]);
});
const deadline = Date.now() + timeoutMs;
while (Date.now() <= deadline) {
if (child.exitCode !== null) return null;
try {
const response = await fetch(`http://localhost:${port}/health`, {
signal: AbortSignal.timeout(1_000),
});
const body = response.ok
? ((await response.json()) as { playground?: { project_id?: string } })
: undefined;
if (body?.playground?.project_id === expectedProjectId) return port;
} catch {
// Not ready yet.
}
await new Promise<void>((resolveWait) => setTimeout(resolveWait, 300));
}
return null;
}
async function probeExistingPlayground(port: number): Promise<ExistingPlayground | null> {
try {
const response = await fetch(`http://localhost:${port}/health`, {
signal: AbortSignal.timeout(2_000),
});
if (!response.ok) return null;
const body = (await response.json()) as {
playground?: { version?: string; pid?: number; project_id?: string };
};
if (!body.playground?.pid) return null;
return {
version: body.playground.version ?? "unknown",
pid: body.playground.pid,
projectId: body.playground.project_id,
};
} catch {
return null;
}
}
async function replaceExistingPlayground(
existing: ExistingPlayground,
port: number,
): Promise<boolean> {
emitBare(`Replacing Workbench v${existing.version} (pid ${existing.pid}) on port ${port}...`);
try {
process.kill(existing.pid, "SIGTERM");
} catch {
return true;
}
for (let attempt = 0; attempt < 30; attempt += 1) {
await new Promise<void>((resolveWait) => setTimeout(resolveWait, 100));
if (!(await probeExistingPlayground(port))) return true;
}
return false;
}
async function openPlaygroundSurface(
port: number,
surface: "preview" | "workbench",
requestedAgent: string | undefined,
shouldOpen: boolean,
): Promise<void> {
if (!shouldOpen) return;
const target =
surface === "workbench"
? { url: `http://localhost:${port}` }
: await resolvePlaygroundBrowserTarget(port, requestedAgent);
if (target.warning) emitBare(`Warning: ${target.warning}`);
openBrowser(target.url);
}
async function resolvePlaygroundBrowserTarget(
port: number,
requestedAgent?: string,
): Promise<PlaygroundBrowserTarget> {
const baseUrl = `http://localhost:${port}`;
try {
const response = await fetch(`${baseUrl}/api/project`, {
signal: AbortSignal.timeout(3_000),
});
if (!response.ok) return { url: baseUrl };
return playgroundBrowserTargetFromSummary(
baseUrl,
(await response.json()) as PlaygroundProjectSummary,
requestedAgent,
);
} catch {
return { url: baseUrl };
}
}
function openBrowser(url: string): void {
const command =
process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
const args = process.platform === "win32" ? ["", url] : [url];
try {
spawn(command, args, {
stdio: "ignore",
detached: true,
shell: process.platform === "win32",
}).unref();
} catch {
emitBare(`Could not open a browser automatically. Visit ${url}`);
}
}
function forwardSignals(child: ChildProcess): () => void {
const forwardInterrupt = () => child.kill("SIGINT");
const forwardTerminate = () => child.kill("SIGTERM");
process.on("SIGINT", forwardInterrupt);
process.on("SIGTERM", forwardTerminate);
return () => {
process.off("SIGINT", forwardInterrupt);
process.off("SIGTERM", forwardTerminate);
};
}
function waitForChildExit(child: ChildProcess): Promise<number> {
if (child.exitCode !== null) return Promise.resolve(child.exitCode);
return new Promise((resolveExit, rejectExit) => {
child.once("exit", (exitCode) => resolveExit(exitCode ?? 0));
child.once("error", rejectExit);
});
}
@@ -12,6 +12,7 @@ import { emitBare, emitResult } from "bailian-cli-runtime";
const GITIGNORE_ADDITIONS = `
# agents
agents.state.json
.openagentpack/versions/
.env
`;
@@ -71,8 +72,8 @@ const INIT_FLAGS = {
export default defineCommand({
description: {
"en-US": "Create a new agents.yaml template",
"zh-CN": "创建新的 agents.yaml 模板",
"en-US": "Create an agents.yaml template",
"zh-CN": "创建 agents.yaml 模板",
},
auth: "none",
usageArgs: "[--agent-name <name>] [--file <path>] [--force]",
@@ -0,0 +1,430 @@
import {
BailianError,
defineCommand,
detectOutputFormat,
ExitCode,
type FlagsDef,
} from "bailian-cli-core";
import { emitBare, emitResult } from "bailian-cli-runtime";
import {
commitProjectBuild,
createDirectoryWorkspaceVersionService,
executeProjectPublish,
initializeDirectoryProject,
planProjectPublish,
previewProjectBuild,
type ProjectBuildResolver,
validateDirectoryProject,
} from "@openagentpack/sdk/project-workspace";
import { CREDENTIALS_NOTE, resolveAgentProjectConfig } from "./_engine/config-loader.ts";
import { withStdoutProtected } from "./_engine/console-capture.ts";
import { withAgentErrors } from "./_engine/errors.ts";
import { renderAgentFeedback } from "./_engine/feedback.ts";
import { launchManagedAgentPlayground } from "./_engine/playground-launcher.ts";
import { installSdkTransport } from "./_engine/transport.ts";
import { formatResourceLabel } from "./_engine/address-utils.ts";
const PROJECT_FLAG = {
project: {
type: "string",
valueHint: "<directory>",
description: {
"en-US": "Directory project root (default: current directory)",
"zh-CN": "目录项目根路径(默认:当前目录)",
},
},
} satisfies FlagsDef;
const JSON_FORMAT = "json" as const;
export const managedAgentProjectInit = defineCommand({
description: {
"en-US": "Create a directory project or convert the local agents.yaml",
"zh-CN": "创建目录项目,或转换当前 agents.yaml",
},
auth: "none",
usageArgs: "[--project <directory>]",
flags: PROJECT_FLAG,
exampleArgs: ["", "--project ./my-agent"],
notes: [
{
"en-US":
"New projects include Skill, File, Vault, and Environment examples under each resource directory's _examples/. They are not referenced by agent.json and are excluded from Build/Publish. Copy an example outside _examples/ to enable it, then configure its Agent reference.",
"zh-CN":
"新项目在四类资源目录的 _examples/ 下生成配置示例,不写入 agent.json 引用,也不参与 Build/Publish。需要使用时将示例复制到 _examples/ 外,再配置 Agent 引用。",
},
],
async run(ctx) {
if (ctx.settings.dryRun) {
emitResult(
{
would_initialize_project: ctx.flags.project ?? ".",
},
detectOutputFormat(ctx.settings.output),
);
return;
}
const result = await initializeDirectoryProject({ projectRoot: ctx.flags.project ?? "." });
emitResult(result, detectOutputFormat(ctx.settings.output));
},
});
export const managedAgentProjectValidate = defineCommand({
description: { "en-US": "Validate a directory Agent project", "zh-CN": "校验目录式 Agent 项目" },
auth: "none",
usageArgs: "[--project <directory>]",
flags: PROJECT_FLAG,
exampleArgs: ["", "--project ./my-agent"],
async run(ctx) {
const result = await validateDirectoryProject(ctx.flags.project ?? ".");
const format = detectOutputFormat(ctx.settings.output);
if (format === JSON_FORMAT) emitResult(safeInspection(result), format);
else {
for (const diagnostic of [...result.diagnostics, ...result.warnings]) {
emitBare(`${diagnostic.severity}: ${diagnostic.code}: ${diagnostic.message}`);
}
if (!result.diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
emitBare(`Project is valid (${result.project_revision.slice(0, 12)}).`);
}
}
if (result.diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
throw new BailianError("Directory project validation failed.", ExitCode.GENERAL);
}
},
});
export const managedAgentProjectBuild = defineCommand({
description: {
"en-US": "Organize directory source and generate the immutable Publish Build",
"zh-CN": "整理目录源文件并生成不可变的发布 Build",
},
auth: "none",
risk: {
level: "high",
message: {
"en-US":
"This organizes project source, moves literal Vault secrets into the local .env, and writes the previewed immutable Build.",
"zh-CN":
"该操作会整理项目源文件,将 Vault 明文密钥移入本地 .env并写入已预览的不可变 Build。",
},
},
usageArgs: "[--project <directory>]",
flags: PROJECT_FLAG,
exampleArgs: ["--dry-run", "--yes", "--project ./my-agent --yes"],
async run(ctx) {
const root = ctx.flags.project ?? ".";
const preview = await previewProjectBuild(root);
const format = detectOutputFormat(ctx.settings.output);
if (ctx.settings.dryRun) {
emitResult(safeInspection(preview), format);
return;
}
if (!preview.can_build)
throw new BailianError("Directory project is invalid and cannot be built.", ExitCode.GENERAL);
const built = await commitProjectBuild({
projectRoot: root,
baseRevision: preview.project_revision,
});
emitResult(
{ manifest: built.manifest, organization_moves: preview.organization_moves },
format,
);
},
});
export const managedAgentProjectPublish = defineCommand({
description: {
"en-US": "Publish the current directory-project Build and record a version",
"zh-CN": "发布当前目录项目 Build 并记录版本",
},
auth: "apiKey",
risk: {
level: "high",
message: {
"en-US":
"This publishes the current directory-project Build and may create, update, or delete remote managed Agent resources.",
"zh-CN": "该操作会发布当前目录项目 Build可能创建、更新或删除远端托管 Agent 资源。",
},
},
usageArgs: "[--project <directory>] [--no-refresh] [--concurrency <n>]",
flags: {
...PROJECT_FLAG,
noRefresh: {
type: "switch",
description: {
"en-US": "Skip remote refresh before planning",
"zh-CN": "规划前跳过远端刷新",
},
},
concurrency: {
type: "number",
valueHint: "<n>",
description: {
"en-US": "Maximum parallel resource operations",
"zh-CN": "最大并行资源操作数",
},
},
},
exampleArgs: ["--yes", "--project ./my-agent --yes"],
notes: CREDENTIALS_NOTE,
async run(ctx) {
installSdkTransport(ctx);
const root = ctx.flags.project ?? ".";
const resolveBuild: ProjectBuildResolver = async (buildPath, options) =>
(await resolveAgentProjectConfig(ctx, buildPath, {
environment: options?.environment,
overrideBailianBaseUrl: true,
})) as unknown as Awaited<ReturnType<ProjectBuildResolver>>;
const planned = await withAgentErrors(() =>
withStdoutProtected(() =>
planProjectPublish(root, {
provider: "bailian",
refresh: !ctx.flags.noRefresh,
quiet: true,
onFeedback: renderAgentFeedback,
resolveBuild,
}),
),
);
const actions = planned.planned.plan.actions.filter((action) => action.action !== "no-op");
if (ctx.settings.dryRun) {
emitResult(
{
project_revision: planned.project_revision,
build_manifest: planned.build_manifest,
plan: planned.planned.plan,
},
detectOutputFormat(ctx.settings.output),
);
return;
}
for (const action of actions) {
const marker = action.action === "create" ? "+" : action.action === "update" ? "~" : "-";
emitBare(`${marker} ${formatResourceLabel(action.address)}`);
}
const result = await withAgentErrors(() =>
withStdoutProtected(() =>
executeProjectPublish({
projectRoot: planned.project_root,
expectedProjectRevision: planned.project_revision,
expectedYamlHash: planned.build_manifest.yaml_hash,
expectedPlanFingerprint: planned.plan_fingerprint,
provider: "bailian",
refresh: !ctx.flags.noRefresh,
concurrency: ctx.flags.concurrency,
policy: "force",
onFeedback: renderAgentFeedback,
resolveBuild,
}),
),
);
emitResult(result, detectOutputFormat(ctx.settings.output));
},
});
export const managedAgentProjectWorkbench = defineCommand({
description: {
"en-US": "Launch the directory project Workbench",
"zh-CN": "启动目录项目 Workbench",
},
auth: "apiKey",
usageArgs: "[--project <directory>] [--port <n>] [--no-open]",
flags: {
...PROJECT_FLAG,
port: {
type: "number",
valueHint: "<n>",
description: { "en-US": "Local port (default: 4848)", "zh-CN": "本地端口默认4848" },
},
noOpen: {
type: "switch",
description: { "en-US": "Do not open a browser", "zh-CN": "不自动打开浏览器" },
},
},
exampleArgs: ["", "--project ./my-agent --no-open"],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const root = ctx.flags.project ?? ".";
if (ctx.settings.dryRun) {
emitResult(
{ would_launch: "workbench", project_root: root, port: ctx.flags.port ?? 4848 },
detectOutputFormat(ctx.settings.output),
);
return;
}
await launchManagedAgentPlayground({
project: root,
port: ctx.flags.port ?? 4848,
open: !ctx.flags.noOpen,
surface: "workbench",
client: ctx.client,
settings: ctx.settings,
});
},
});
export const managedAgentProjectVersionEnable = versionToggleCommand(true);
export const managedAgentProjectVersionDisable = versionToggleCommand(false);
function versionToggleCommand(enabled: boolean) {
return defineCommand({
description: enabled
? { "en-US": "Enable directory project versions", "zh-CN": "启用目录项目版本管理" }
: { "en-US": "Disable directory project versions", "zh-CN": "关闭目录项目版本管理" },
auth: "none",
usageArgs: "[--project <directory>]",
flags: PROJECT_FLAG,
exampleArgs: ["", "--project ./my-agent"],
async run(ctx) {
const service = createDirectoryWorkspaceVersionService(ctx.flags.project ?? ".");
if (ctx.settings.dryRun) {
emitResult(
{ would_set_enabled: enabled, status: await service.status() },
detectOutputFormat(ctx.settings.output),
);
return;
}
const result = enabled
? await service.enable("Enable project versions")
: await service.disable();
emitResult(result, detectOutputFormat(ctx.settings.output));
},
});
}
export const managedAgentProjectVersionStatus = defineCommand({
description: {
"en-US": "Show directory project version status",
"zh-CN": "显示目录项目版本状态",
},
auth: "none",
usageArgs: "[--project <directory>]",
flags: PROJECT_FLAG,
exampleArgs: ["", "--project ./my-agent --output json"],
async run(ctx) {
emitResult(
await createDirectoryWorkspaceVersionService(ctx.flags.project ?? ".").status(),
detectOutputFormat(ctx.settings.output),
);
},
});
export const managedAgentProjectVersionList = defineCommand({
description: { "en-US": "List directory project versions", "zh-CN": "列出目录项目版本" },
auth: "none",
usageArgs: "[--project <directory>] [--limit <n>] [--cursor <cursor>]",
flags: {
...PROJECT_FLAG,
limit: {
type: "number",
valueHint: "<n>",
description: { "en-US": "Maximum versions to return", "zh-CN": "最多返回的版本数" },
},
cursor: {
type: "string",
valueHint: "<cursor>",
description: { "en-US": "Pagination cursor", "zh-CN": "分页游标" },
},
},
exampleArgs: ["", "--limit 20 --output json"],
async run(ctx) {
emitResult(
await createDirectoryWorkspaceVersionService(ctx.flags.project ?? ".").listVersions({
limit: ctx.flags.limit,
cursor: ctx.flags.cursor,
}),
detectOutputFormat(ctx.settings.output),
);
},
});
const VERSION_ID_FLAG = {
versionId: {
type: "string",
valueHint: "<full-version>",
required: true,
description: { "en-US": "Full project version ID", "zh-CN": "完整的项目版本 ID" },
},
} satisfies FlagsDef;
export const managedAgentProjectVersionPreview = defineCommand({
description: { "en-US": "Preview a directory project version", "zh-CN": "预览目录项目历史版本" },
auth: "none",
usageArgs: "--version-id <full-version> [--project <directory>]",
flags: { ...PROJECT_FLAG, ...VERSION_ID_FLAG },
exampleArgs: ["--version-id <full-version>"],
async run(ctx) {
emitResult(
await createDirectoryWorkspaceVersionService(ctx.flags.project ?? ".").previewVersion(
ctx.flags.versionId,
),
detectOutputFormat(ctx.settings.output),
);
},
});
export const managedAgentProjectVersionRestore = defineCommand({
description: {
"en-US": "Restore a version to the project working directory",
"zh-CN": "将历史版本恢复到项目工作目录",
},
auth: "none",
risk: {
level: "high",
message: {
"en-US":
"This restores the full directory source to the working tree. Version history and remote State will not move.",
"zh-CN": "该操作会将完整目录源文件恢复到工作区;版本历史和远端 State 不会移动。",
},
},
usageArgs: "--version-id <full-version> [--project <directory>]",
flags: { ...PROJECT_FLAG, ...VERSION_ID_FLAG },
exampleArgs: ["--version-id <full-version>", "--version-id <full-version> --yes"],
async run(ctx) {
const service = createDirectoryWorkspaceVersionService(ctx.flags.project ?? ".");
const preview = await service.previewVersion(ctx.flags.versionId);
if (!preview.can_restore)
throw new BailianError(
preview.blockers[0] ?? preview.diagnostics[0]?.message ?? "Version cannot be restored.",
ExitCode.GENERAL,
);
if (ctx.settings.dryRun) {
emitResult(
{ would_restore: ctx.flags.versionId, preview },
detectOutputFormat(ctx.settings.output),
);
return;
}
const restored = await service.restoreVersion(ctx.flags.versionId, {
headVersion: preview.base_head_version,
projectRevision: preview.base_project_revision,
});
emitResult(restored, detectOutputFormat(ctx.settings.output));
},
});
function safeInspection(result: {
project_root: string;
project_revision: string;
source_manifest_hash: string;
yaml_hash: string;
diagnostics: unknown[];
warnings: unknown[];
organization_moves: unknown[];
canonical_yaml: string;
before_yaml?: string;
can_build?: boolean;
}) {
return {
project_root: result.project_root,
project_revision: result.project_revision,
source_manifest_hash: result.source_manifest_hash,
yaml_hash: result.yaml_hash,
diagnostics: result.diagnostics,
warnings: result.warnings,
organization_moves: result.organization_moves,
before_yaml: "before_yaml" in result ? result.before_yaml : undefined,
after_yaml: result.canonical_yaml,
can_build: "can_build" in result ? result.can_build : undefined,
};
}
@@ -0,0 +1,90 @@
import { defineCommand, detectOutputFormat, type FlagsDef } from "bailian-cli-core";
import { emitResult } from "bailian-cli-runtime";
import { CREDENTIALS_NOTE } from "./_engine/config-loader.ts";
import { launchManagedAgentPlayground } from "./_engine/playground-launcher.ts";
const PLAYGROUND_BASE_FLAGS = {
file: {
type: "string",
valueHint: "<path>",
description: {
"en-US": "Config file path (default: agents.yaml)",
"zh-CN": "配置文件路径默认agents.yaml",
},
},
port: {
type: "number",
valueHint: "<n>",
description: {
"en-US": "Local port (default: 4848)",
"zh-CN": "本地端口默认4848",
},
},
noOpen: {
type: "switch",
description: {
"en-US": "Do not open a browser automatically",
"zh-CN": "不自动打开浏览器",
},
},
} satisfies FlagsDef;
const PLAYGROUND_FLAGS = {
...PLAYGROUND_BASE_FLAGS,
agent: {
type: "string",
valueHint: "<id>",
description: {
"en-US": "Agent to preview (required when the project declares multiple Agents)",
"zh-CN": "要预览的 Agent项目包含多个 Agent 时需要指定)",
},
},
} satisfies FlagsDef;
const PLAYGROUND_NOTES = [
...CREDENTIALS_NOTE,
{
"en-US":
"Session Preview requires Node.js 22+ and keeps using an agents.yaml source. Directory Workbench is available under managed-agent project workbench.",
"zh-CN":
"会话预览需要 Node.js 22+,并继续使用 agents.yaml目录 Workbench 位于 managed-agent project workbench。",
},
];
export const managedAgentPlayground = defineCommand({
description: {
"en-US": "Launch a Session Preview for an agents.yaml Agent",
"zh-CN": "为 agents.yaml 中的 Agent 启动会话预览",
},
auth: "apiKey",
usageArgs: "[--file <path>] [--agent <id>] [--port <n>] [--no-open]",
flags: PLAYGROUND_FLAGS,
exampleArgs: ["", "--agent assistant", "--file agents.yaml --no-open"],
notes: PLAYGROUND_NOTES,
async run(ctx) {
const file = ctx.flags.file ?? "agents.yaml";
const port = ctx.flags.port ?? 4848;
if (ctx.settings.dryRun) {
emitResult(
{
would_launch: "playground",
config_file: file,
agent: ctx.flags.agent,
port,
open_browser: !ctx.flags.noOpen,
},
detectOutputFormat(ctx.settings.output),
);
return;
}
await launchManagedAgentPlayground({
file,
agent: ctx.flags.agent,
port,
open: !ctx.flags.noOpen,
surface: "preview",
client: ctx.client,
settings: ctx.settings,
});
},
});
+14
View File
@@ -137,6 +137,20 @@ export { default as managedAgentValidate } from "./commands/managed-agent/valida
export { default as managedAgentPlan } from "./commands/managed-agent/plan.ts";
export { default as managedAgentApply } from "./commands/managed-agent/apply.ts";
export { default as managedAgentDestroy } from "./commands/managed-agent/destroy.ts";
export { managedAgentPlayground } from "./commands/managed-agent/workbench.ts";
export {
managedAgentProjectBuild,
managedAgentProjectInit,
managedAgentProjectPublish,
managedAgentProjectValidate,
managedAgentProjectVersionDisable,
managedAgentProjectVersionEnable,
managedAgentProjectVersionList,
managedAgentProjectVersionPreview,
managedAgentProjectVersionRestore,
managedAgentProjectVersionStatus,
managedAgentProjectWorkbench,
} from "./commands/managed-agent/project.ts";
export { default as managedAgentStateList } from "./commands/managed-agent/state-list.ts";
export { default as managedAgentStateShow } from "./commands/managed-agent/state-show.ts";
export { default as managedAgentStateRm } from "./commands/managed-agent/state-rm.ts";
@@ -104,6 +104,20 @@ test("inject:base_url 已带后缀不重复拼;非空字面量 base_url 保留",
expect(literal.bailian.base_url).toBe("https://custom.example.com/api/v1/agentstudio");
});
test("inject:目录项目可强制使用 Bailian CLI 解析出的 base_url", () => {
const providers = {
bailian: {
api_key: "from-project-env",
base_url: "https://project-env.example.com/api/v1/agentstudio",
},
};
injectProviderCredentials(providers, makeHost({ apiCred: bailianCred() }), {
overrideBaseUrl: true,
});
expect(providers.bailian.api_key).toBe("sk-auth-chain");
expect(providers.bailian.base_url).toBe("https://dashscope.aliyuncs.com/api/v1/agentstudio");
});
test("inject:base_url 尾斜杠被规范化,不产生双斜杠", () => {
const providers = { bailian: { api_key: "", base_url: "" } };
injectProviderCredentials(
@@ -1,4 +1,4 @@
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { mkdir, mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises";
import http from "node:http";
import type { AddressInfo } from "node:net";
import { tmpdir } from "node:os";
@@ -9,7 +9,7 @@ import {
resolveProjectConfig,
type ResourceAddress,
} from "@openagentpack/sdk";
import { describe, expect, test } from "vite-plus/test";
import { afterEach, describe, expect, test } from "vite-plus/test";
import { parse } from "yaml";
import { e2eFixturesDir, parseStdoutJson, runCommandHelp, runCommandE2e } from "./helpers.ts";
import { MANAGED_AGENT_ROUTES } from "./topic-routes.ts";
@@ -29,6 +29,13 @@ const DEPLOYMENT_SAFETY_DIAGNOSTIC_CODES = [
"bailian.deployment.file.mount_path.required",
"bailian.deployment.file.mount_path.duplicate",
];
const projectDirectories: string[] = [];
afterEach(async () => {
for (const directory of projectDirectories.splice(0)) {
await rm(directory, { recursive: true, force: true });
}
});
async function writeLocalSkillDirectory(parentDirectory: string): Promise<string> {
const skillDirectory = join(parentDirectory, "local-report");
@@ -98,8 +105,8 @@ async function seedTrackedResources(
/**
* managed-agenthelp / mutation --dry-run
* SDK runtime / /
* 线init/validate/state list|show|rmauth: "none"
* auth: "apiKey" managed-agent-auth-chain e2e
* 线init/project init/validate/state list|show|rmauth: "none"
* auth: "apiKey" managed-agent-auth-chain e2e
* apply/destroy/session agents.yaml
* dry-run
*/
@@ -226,6 +233,216 @@ describe("e2e: managed-agent", () => {
expect(exitCode, stderr).toBe(0);
expect(stderr).toMatch(/--file|--yes/i);
expect(stderr).not.toContain("--provider");
expect(stderr).not.toContain("--ci");
expect(stderr).not.toContain("--refresh-only");
});
test("managed-agent apply 不再接受 --refresh-only", async () => {
const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
"managed-agent",
"apply",
"--dry-run",
"--refresh-only",
]);
expect(exitCode).toBe(2);
expect(stderr).toMatch(/Unknown flag.*--refresh-only/i);
});
test("managed-agent init 不再暴露 Git 仓库脚手架", async () => {
const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
"managed-agent",
"init",
"--help",
]);
expect(exitCode, stderr).toBe(0);
expect(stderr).not.toContain("--git");
});
test("managed-agent project 暴露目录项目与共享版本管理子命令", async () => {
const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
"managed-agent",
"project",
"--help",
]);
expect(exitCode, stderr).toBe(0);
expect(stderr).toMatch(/init|validate|build|publish|workbench|version/i);
});
test("managed-agent project 完成 init、validate、build 与 version status 本地闭环", async () => {
const projectRoot = await mkdtemp(join(tmpdir(), "bailian-managed-agent-project-"));
projectDirectories.push(projectRoot);
const initialized = await runCommandE2e(MANAGED_AGENT_ROUTES, [
"managed-agent",
"project",
"init",
"--project",
projectRoot,
"--output",
"json",
]);
expect(initialized.exitCode, initialized.stderr).toBe(0);
expect(
parseStdoutJson<{ baseline_version?: string }>(initialized.stdout).baseline_version,
).toMatch(/^[a-f0-9]{64}$/);
expect(JSON.parse(await readFile(join(projectRoot, "project.json"), "utf8"))).toEqual({
version: "1",
});
expect(await stat(join(projectRoot, ".env")).catch(() => null)).toBeNull();
expect(await stat(join(projectRoot, ".gitignore")).catch(() => null)).toBeNull();
expect(
JSON.parse(await readFile(join(projectRoot, "agents/assistant/agent.json"), "utf8")),
).toEqual({ name: "Assistant", model: "qwen3.7-max" });
for (const relativePath of [
"skills/_examples/example-skill/skill.json",
"skills/_examples/example-skill/SKILL.md",
"files/_examples/example-file/file.json",
"files/_examples/example-file/example.md",
"vaults/_examples/example-vault/vault.json",
"environments/_examples/example-env/environment.json",
]) {
expect((await stat(join(projectRoot, "agents/assistant", relativePath))).isFile()).toBe(true);
expect(
await readFile(join(projectRoot, "agents/assistant", relativePath, "../README.md"), "utf8"),
).toContain("_examples/");
}
const skillDirectory = join(projectRoot, "agents/assistant/skills/writer");
const filesDirectory = join(projectRoot, "agents/assistant/files");
const nestedFileDirectory = join(filesDirectory, "mount");
await mkdir(skillDirectory, { recursive: true });
await mkdir(nestedFileDirectory, { recursive: true });
await writeFile(join(skillDirectory, "SKILL.md"), "# Writer\n", "utf8");
await writeFile(join(filesDirectory, "brief.txt"), "Build this brief.\n", "utf8");
await writeFile(join(nestedFileDirectory, "mount.md"), "Mount this file.\n", "utf8");
const validated = await runCommandE2e(MANAGED_AGENT_ROUTES, [
"managed-agent",
"project",
"validate",
"--project",
projectRoot,
"--output",
"json",
]);
expect(validated.exitCode, validated.stderr).toBe(0);
expect(
parseStdoutJson<{ diagnostics?: Array<{ severity?: string }> }>(validated.stdout).diagnostics,
).not.toContainEqual(expect.objectContaining({ severity: "error" }));
const vaultDirectory = join(projectRoot, "agents/assistant/vaults/secrets");
await mkdir(vaultDirectory, { recursive: true });
await writeFile(
join(vaultDirectory, "vault.json"),
JSON.stringify({
id: "secrets",
display_name: "Secrets",
credentials: [
{
name: "service",
type: "environment_variable",
secret_name: "SERVICE_TOKEN",
secret_value: "e2e-vault-private-value",
},
],
}),
);
const dryBuild = await runCommandE2e(MANAGED_AGENT_ROUTES, [
"managed-agent",
"project",
"build",
"--project",
projectRoot,
"--dry-run",
"--output",
"json",
]);
expect(dryBuild.exitCode, dryBuild.stderr).toBe(0);
expect(dryBuild.stdout + dryBuild.stderr).not.toContain("e2e-vault-private-value");
expect(dryBuild.stdout).toContain("AGENTS_VAULT_");
expect(await stat(join(projectRoot, ".env")).catch(() => null)).toBeNull();
const built = await runCommandE2e(MANAGED_AGENT_ROUTES, [
"managed-agent",
"project",
"build",
"--project",
projectRoot,
"--yes",
"--output",
"json",
]);
expect(built.exitCode, built.stderr).toBe(0);
const generatedYaml = await readFile(
join(projectRoot, ".openagentpack/build/agents.yaml"),
"utf8",
);
expect(generatedYaml).not.toContain("example-");
expect(generatedYaml).not.toContain("_examples");
expect(generatedYaml).not.toContain("$" + "{SERVICE_TOKEN}");
expect(built.stdout + built.stderr).not.toContain("e2e-vault-private-value");
expect(await readFile(join(projectRoot, ".env"), "utf8")).toContain("e2e-vault-private-value");
expect(await readFile(join(vaultDirectory, "vault.json"), "utf8")).not.toContain(
"e2e-vault-private-value",
);
expect(
await readFile(join(projectRoot, ".openagentpack/build/agents.yaml"), "utf8"),
).not.toContain("e2e-vault-private-value");
expect(await readFile(join(projectRoot, ".openagentpack/build/agents.yaml"), "utf8")).toContain(
"assistant",
);
expect(await readFile(join(projectRoot, ".openagentpack/build/agents.yaml"), "utf8")).toContain(
"provider: bailian",
);
expect(JSON.parse(await readFile(join(skillDirectory, "skill.json"), "utf8"))).toEqual({
id: "writer",
});
expect(JSON.parse(await readFile(join(filesDirectory, "brief/file.json"), "utf8"))).toEqual({
id: "brief",
name: "brief.txt",
source: "./brief.txt",
});
expect(JSON.parse(await readFile(join(nestedFileDirectory, "file.json"), "utf8"))).toEqual({
id: "mount",
name: "mount.md",
source: "./mount.md",
});
const builtAgent = JSON.parse(
await readFile(join(projectRoot, "agents/assistant/agent.json"), "utf8"),
);
expect(builtAgent.skills).toEqual(["writer"]);
expect(builtAgent.files).toEqual(
expect.arrayContaining([
{ file: "brief", mount_path: "/mnt/brief.txt" },
{ file: "mount", mount_path: "/mnt/mount.md" },
]),
);
const status = await runCommandE2e(MANAGED_AGENT_ROUTES, [
"managed-agent",
"project",
"version",
"status",
"--project",
projectRoot,
"--output",
"json",
]);
expect(status.exitCode, status.stderr).toBe(0);
expect(parseStdoutJson<{ enabled?: boolean }>(status.stdout).enabled).toBe(true);
expect(await stat(join(projectRoot, ".openagentpack/state.json")).catch(() => null)).toBeNull();
});
test("managed-agent project version preview 缺少 --version-id 时退出为用法错误 (2)", async () => {
const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
"managed-agent",
"project",
"version",
"preview",
"--quiet",
]);
expect(exitCode).toBe(2);
expect(stderr).toMatch(/--version-id|Missing required/i);
});
test("managed-agent apply 计划失败时在最终错误中保留首条诊断和剩余数量", async () => {
@@ -1289,6 +1506,29 @@ describe("e2e: managed-agent--dry-run 短路,不联网不写盘)", () =>
expect(data.provider).toBe("bailian");
});
test("project workbench --dry-run 仅输出目录项目启动计划", async () => {
const { stdout, stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
"managed-agent",
"project",
"workbench",
"--dry-run",
"--project",
"./agent-project",
"--no-open",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{
would_launch?: string;
project_root?: string;
port?: number;
}>(stdout);
expect(data.would_launch).toBe("workbench");
expect(data.project_root).toBe("./agent-project");
expect(data.port).toBe(4848);
});
test("init 不再接受 --provider", async () => {
const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
"managed-agent",
@@ -1301,6 +1541,27 @@ describe("e2e: managed-agent--dry-run 短路,不联网不写盘)", () =>
expect(stderr).toMatch(/Unknown flag.*--provider/i);
});
test("project init --dry-run 不需要密钥且不创建目录", async () => {
const parent = await mkdtemp(join(tmpdir(), "bailian-managed-agent-project-dry-run-"));
projectDirectories.push(parent);
const projectRoot = join(parent, "project");
const { stdout, stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
"managed-agent",
"project",
"init",
"--dry-run",
"--project",
projectRoot,
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
expect(
parseStdoutJson<{ would_initialize_project?: string }>(stdout).would_initialize_project,
).toBe(projectRoot);
expect(await stat(projectRoot).catch(() => null)).toBeNull();
});
test("apply --dry-run 仅输出计划", async () => {
const { stdout, stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
"managed-agent",
@@ -204,6 +204,18 @@ export const MANAGED_AGENT_ROUTES: E2eRouteExports = {
"managed-agent plan": "managedAgentPlan",
"managed-agent apply": "managedAgentApply",
"managed-agent destroy": "managedAgentDestroy",
"managed-agent playground": "managedAgentPlayground",
"managed-agent project init": "managedAgentProjectInit",
"managed-agent project validate": "managedAgentProjectValidate",
"managed-agent project build": "managedAgentProjectBuild",
"managed-agent project publish": "managedAgentProjectPublish",
"managed-agent project workbench": "managedAgentProjectWorkbench",
"managed-agent project version enable": "managedAgentProjectVersionEnable",
"managed-agent project version disable": "managedAgentProjectVersionDisable",
"managed-agent project version status": "managedAgentProjectVersionStatus",
"managed-agent project version list": "managedAgentProjectVersionList",
"managed-agent project version preview": "managedAgentProjectVersionPreview",
"managed-agent project version restore": "managedAgentProjectVersionRestore",
"managed-agent state list": "managedAgentStateList",
"managed-agent state rm": "managedAgentStateRm",
"managed-agent state import": "managedAgentStateImport",
@@ -0,0 +1,139 @@
import { chmod, mkdir, mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import {
commitPreparedProjectVersion,
disableProjectVersioning,
enableProjectVersioning,
getProjectVersionStatus,
listProjectVersions,
prepareProjectVersion,
previewProjectVersion,
restoreProjectVersion,
} from "@openagentpack/sdk/project-versions";
import { afterEach, describe, expect, test } from "vite-plus/test";
import { playgroundBrowserTargetFromSummary } from "../src/commands/managed-agent/_engine/playground-launcher.ts";
const temporaryDirectories: string[] = [];
afterEach(async () => {
for (const directory of temporaryDirectories.splice(0)) {
await rm(directory, { recursive: true, force: true });
}
});
describe("managed-agent local snapshot versions", () => {
test("uses the shared path-scoped switch and stores full YAML outside store.json", async () => {
const root = await temporaryDirectory();
const configPath = join(root, "agents.yaml");
const siblingDirectory = join(root, "nested");
const siblingConfigPath = join(siblingDirectory, "agents.yaml");
await mkdir(siblingDirectory);
await writeFile(configPath, projectYaml("First"));
await writeFile(siblingConfigPath, projectYaml("Second"));
const enabled = await enableProjectVersioning(configPath, "Enable Bailian CLI versioning");
expect(enabled.versioning.enabled).toBe(true);
expect(enabled.version?.message).toBe("Enable Bailian CLI versioning");
expect((await getProjectVersionStatus(siblingConfigPath)).enabled).toBe(false);
const storeSource = await readFile(join(root, ".openagentpack/versions/store.json"), "utf8");
expect(storeSource).not.toContain("instructions: First");
const snapshotSource = await readFile(
join(root, ".openagentpack/versions/blobs", `${enabled.version!.source_hash}.yaml`),
"utf8",
);
expect(snapshotSource).toContain("instructions: First");
await writeFile(configPath, projectYaml("First updated"));
const repeated = await enableProjectVersioning(configPath, "Enable Bailian CLI versioning");
expect(repeated.version?.message).toBe("Enable Bailian CLI versioning");
expect((await listProjectVersions(configPath)).versions).toHaveLength(2);
const disabled = await disableProjectVersioning(configPath);
expect(disabled.enabled).toBe(false);
});
test("auto-snapshots after success and restores without changing history or permissions", async () => {
const root = await temporaryDirectory();
const configPath = join(root, "agents.yaml");
await writeFile(configPath, projectYaml("Version one"));
await chmod(configPath, 0o640);
const enabled = await enableProjectVersioning(configPath, "Enable Bailian CLI versioning");
const firstVersion = enabled.version!.version_id;
const secondSource = projectYaml("Version two");
await writeFile(configPath, secondSource);
const prepared = await prepareProjectVersion(configPath, secondSource);
const version = await commitPreparedProjectVersion(prepared!);
const currentVersionBeforeRestore = (await getProjectVersionStatus(configPath)).head_version;
expect(version?.message).toBe("Apply agents.yaml");
const preview = await previewProjectVersion(configPath, firstVersion);
expect(preview.can_restore).toBe(true);
expect(preview.after_yaml).toContain("Version one");
await restoreProjectVersion(configPath, firstVersion, {
headVersion: preview.base_head_version,
sourceRevision: preview.base_source_revision,
});
expect(await readFile(configPath, "utf8")).toContain("Version one");
expect((await getProjectVersionStatus(configPath)).head_version).toBe(
currentVersionBeforeRestore,
);
expect((await stat(configPath)).mode & 0o777).toBe(0o640);
});
test("rejects abbreviated version IDs and plaintext credentials", async () => {
const root = await temporaryDirectory();
const configPath = join(root, "agents.yaml");
await writeFile(configPath, projectYaml("Safe"));
const enabled = await enableProjectVersioning(configPath, "Enable Bailian CLI versioning");
await expect(previewProjectVersion(configPath, enabled.version!.short_version)).rejects.toThrow(
/full 64-character hexadecimal/i,
);
await disableProjectVersioning(configPath);
await writeFile(
configPath,
projectYaml("Unsafe").replace("qoder: {}", "qoder:\n api_key: plaintext-secret"),
);
await expect(
enableProjectVersioning(configPath, "Enable Bailian CLI versioning"),
).rejects.toThrow(/environment variable reference/i);
});
});
describe("managed-agent Workbench policy", () => {
test("Session Preview opens the requested Agent or falls back to Workbench", () => {
const summary = {
status: "valid",
agents: [{ agent: { id: "assistant" } }, { agent: { id: "reviewer" } }],
};
expect(
playgroundBrowserTargetFromSummary("http://localhost:4848", summary, "reviewer"),
).toEqual({ url: "http://localhost:4848/agents/reviewer/preview" });
expect(playgroundBrowserTargetFromSummary("http://localhost:4848", summary)).toEqual(
expect.objectContaining({ url: "http://localhost:4848", warning: expect.any(String) }),
);
});
});
async function temporaryDirectory(): Promise<string> {
const directory = await mkdtemp(join(tmpdir(), "bailian-cli-local-versions-"));
temporaryDirectories.push(directory);
return directory;
}
function projectYaml(instructions: string): string {
return `version: "1"
providers:
qoder: {}
defaults:
provider: qoder
agents:
assistant:
model: ultimate
instructions: ${instructions}
`;
}
@@ -0,0 +1,137 @@
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeEach, expect, test, vi } from "vite-plus/test";
const registry = vi.hoisted(() => ({
version: '"0.7.0"',
error: null as Error | null,
manifest: "",
query: vi.fn(),
}));
vi.mock("node:child_process", async (importOriginal) => ({
...(await importOriginal<typeof import("node:child_process")>()),
execFile: (
command: string,
args: string[],
options: unknown,
callback: (error: Error | null, output: { stdout: string }) => void,
) => {
registry.query(command, args, options);
callback(registry.error, { stdout: registry.version });
},
}));
vi.mock("node:module", () => ({
createRequire: () => ({ resolve: () => registry.manifest }),
}));
import { resolveLauncher } from "../src/commands/managed-agent/_engine/playground-launcher.ts";
let directory: string;
let binary: string;
beforeEach(async () => {
directory = await mkdtemp(join(tmpdir(), "playground-launcher-"));
binary = join(directory, "playground.js");
registry.manifest = join(directory, "package.json");
registry.version = '"0.7.0"';
registry.error = null;
registry.query.mockClear();
await writeFile(binary, "");
vi.stubEnv("BAILIAN_MANAGED_AGENT_PLAYGROUND_BIN", "");
vi.stubEnv("AGENTS_PLAYGROUND_BIN", "");
vi.stubEnv("BAILIAN_MANAGED_AGENT_PLAYGROUND_VERSION", "");
vi.spyOn(process, "cwd").mockReturnValue(directory);
});
afterEach(async () => {
vi.unstubAllEnvs();
vi.restoreAllMocks();
await rm(directory, { recursive: true, force: true });
});
async function install(version: string) {
await writeFile(registry.manifest, JSON.stringify({ version, bin: "playground.js" }));
}
test("checks latest on every resolution and reuses only the matching installed version", async () => {
await install("0.7.0");
expect(await resolveLauncher()).toMatchObject({
args: [binary],
version: "0.7.0",
fetched: false,
});
registry.version = '"0.8.0"';
expect(await resolveLauncher()).toMatchObject({
command: "npx",
args: ["-y", "@openagentpack/playground@0.8.0"],
version: "0.8.0",
fetched: true,
});
expect(registry.query).toHaveBeenCalledTimes(2);
expect(registry.query).toHaveBeenCalledWith(
"npm",
expect.arrayContaining([
"view",
"@openagentpack/playground@latest",
"--prefer-online",
"--fetch-timeout=10000",
]),
expect.objectContaining({ timeout: 15_000 }),
);
});
test("downloads the resolved exact version when local installation is old or missing", async () => {
for (const version of ["0.6.0", undefined]) {
if (version) await install(version);
else await rm(registry.manifest);
expect(await resolveLauncher()).toMatchObject({
command: "npx",
args: ["-y", "@openagentpack/playground@0.7.0"],
});
}
});
test("explicit version overrides an incompatible installed version", async () => {
await install("0.6.0");
vi.stubEnv("BAILIAN_MANAGED_AGENT_PLAYGROUND_VERSION", "0.7.0");
expect(await resolveLauncher()).toMatchObject({ fetched: true, version: "0.7.0" });
expect(registry.query.mock.calls[0]?.[1]).toContain("@openagentpack/playground@0.7.0");
});
test("explicit binary remains an offline development override", async () => {
vi.stubEnv("BAILIAN_MANAGED_AGENT_PLAYGROUND_BIN", binary);
expect(await resolveLauncher()).toMatchObject({ args: [binary], fetched: false });
expect(registry.query).not.toHaveBeenCalled();
});
test("registry failure does not silently reuse an old installation", async () => {
await install("0.6.0");
registry.error = new Error("registry unavailable");
await expect(resolveLauncher()).rejects.toThrow("registry unavailable");
});
test("rejects malformed or ambiguous registry metadata with localized diagnostics", async () => {
for (const output of ["invalid", '["0.6.0","0.7.0"]', '"--unsafe"']) {
registry.version = output;
await expect(resolveLauncher()).rejects.toThrow("single valid Playground version");
await expect(resolveLauncher()).rejects.toThrow("唯一有效的 Playground 版本号");
}
});
test("local source builds must also match the resolved version", async () => {
const packageRoot = join(directory, "packages/playground");
const sourceBinary = join(packageRoot, "dist/bin/playground.js");
await mkdir(join(packageRoot, "dist/bin"), { recursive: true });
await writeFile(sourceBinary, "");
await writeFile(join(packageRoot, "package.json"), JSON.stringify({ version: "0.6.0" }));
expect(await resolveLauncher()).toMatchObject({ fetched: true });
await writeFile(join(packageRoot, "package.json"), JSON.stringify({ version: "0.7.0" }));
expect(await resolveLauncher()).toMatchObject({
args: [sourceBinary],
version: "0.7.0",
fetched: false,
});
});
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "bailian-cli-core",
"version": "1.20.0",
"version": "1.21.0",
"description": "Core SDK for bailian-cli. See https://www.npmjs.com/package/bailian-cli for usage.",
"homepage": "https://bailian.console.aliyun.com/cli",
"bugs": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "knowledge-studio-cli",
"version": "1.20.0",
"version": "1.21.0",
"description": "Lightweight RAG CLI for Aliyun Model Studio — focused on knowledge-base retrieval.",
"keywords": [
"alibaba-cloud",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "bailian-cli-runtime",
"version": "1.20.0",
"version": "1.21.0",
"description": "Runtime framework for bailian-cli (createCli, registry, args, output, pipeline). See https://www.npmjs.com/package/bailian-cli for usage.",
"homepage": "https://bailian.console.aliyun.com/cli",
"bugs": {
+5 -5
View File
@@ -189,8 +189,8 @@ importers:
packages/commands:
dependencies:
'@openagentpack/sdk':
specifier: 0.5.0
version: 0.5.0
specifier: 0.7.0
version: 0.7.0
bailian-cli-core:
specifier: workspace:*
version: link:../core
@@ -1124,8 +1124,8 @@ packages:
'@emnapi/core': ^1.7.1
'@emnapi/runtime': ^1.7.1
'@openagentpack/sdk@0.5.0':
resolution: {integrity: sha512-9mNMvPWuoiK5NQVFN5i2qAhUAFSmw9jO8xrMDXXfa6eSu0XlEAPZzR6DDg4HdhW+ex60KgA+fcNA0h0LRF552A==}
'@openagentpack/sdk@0.7.0':
resolution: {integrity: sha512-MjyKIFvPi4aT1TIcvGUB0bnD4Tly9aMCncjI9+EYG6CPWpuGO4DGj+4zlfBCx1fvtD1G1w4Zi0m8A8ZodqkK9Q==}
engines: {node: '>=18.17.0'}
'@oxc-project/runtime@0.129.0':
@@ -4287,7 +4287,7 @@ snapshots:
'@tybys/wasm-util': 0.10.1
optional: true
'@openagentpack/sdk@0.5.0':
'@openagentpack/sdk@0.7.0':
dependencies:
jszip: 3.10.1
yaml: 2.9.0
+1 -1
View File
@@ -1,7 +1,7 @@
---
name: bailian-cli
metadata:
version: "1.20.0"
version: "1.21.0"
requires:
bins: ["bl"]
description: >-
+1 -1
View File
@@ -1,7 +1,7 @@
---
name: bailian-finetune
metadata:
version: "1.20.0"
version: "1.21.0"
requires:
bins: ["bl"]
description: >-
+1 -1
View File
@@ -1,7 +1,7 @@
---
name: bailian-gen
metadata:
version: "1.20.0"
version: "1.21.0"
requires:
bins: ["bl"]
description: >-
+29 -3
View File
@@ -1,13 +1,14 @@
---
name: bailian-managed-agent
metadata:
version: "1.20.0"
version: "1.21.0"
requires:
bins: ["bl"]
description: >-
阿里云百炼托管 Agent 声明式基础设施与 API 命令入口用户要创建agent、初始化 agents.yaml、校验或预览配置变更、
创建/更新/销毁托管 Agent 或 Deployment,或查询 Agent/Environment/Skill/Vault/Deployment、管理 Session/Event/File、
运行/暂停 Deployment 时使用 `bl managed-agent`。持久资源仍以 agents.yaml 为唯一事实源做 IaC公开 API 能力按资源透出
创建/更新/销毁托管 Agent 或 Deployment、在 Workbench 编辑和调试目录项目、管理本地快照版本,或查询
Agent/Environment/Skill/Vault/Deployment、管理 Session/Event/File、运行/暂停 Deployment 时使用
`bl managed-agent`。持久资源仍以 agents.yaml 为唯一事实源做 IaC公开 API 能力按资源透出
list/get/search/versions/download、数据面和运行时动作命令。apply / destroy 与破坏性 API 命令受统一高风险确认闸门保护;
务必先展示预览再让用户确认,禁止自动添加 `--yes`
反触发:调用已上线的百炼应用/智能体走 bailian-app-call 或 `bl app`;宿主 agent 自身的记忆、技能、
@@ -47,6 +48,31 @@ for explicit confirmation before re-running with `--yes`.
6. Destroy bl managed-agent destroy --yes # separate explicit confirmation
```
## Directory projects, Workbench, and local versions
| Intent | Command |
| --------------------------------------- | ------------------------------------------------------ |
| Create or convert a directory project | `bl managed-agent project init` |
| Validate and Build directory source | `bl managed-agent project validate` / `build` |
| Publish the current immutable Build | `bl managed-agent project publish --yes` |
| Launch project resource editing | `bl managed-agent project workbench` |
| Launch one Agent Session Preview | `bl managed-agent playground --agent <id>` |
| Enable/disable project versions | `bl managed-agent project version enable` / `disable` |
| Inspect local version state and history | `bl managed-agent project version status` / `list` |
| Preview or restore project source | `bl managed-agent project version preview` / `restore` |
- Bailian CLI and Workbench use the same `.openagentpack/versions/project` store and enable switch. Git is not required.
- Directory projects always use Bailian. `project.json` does not declare a Provider; Build supplies the Bailian Provider configuration automatically.
- Fresh `project init` includes complete Skill/File/Vault/Environment examples with bilingual README files under `agents/assistant/<resource-type>/_examples/`. They are not linked in `agent.json` and are excluded from Build discovery, Workbench declarations, and remote Publish. Copy a resource outside `_examples/` and configure its Agent reference to use it. Examples remain local versioned source; never put real secrets into them. / 新项目的四类资源示例默认不启用、不发布;请按 README 复制到 `_examples/` 外再配置引用,不要向示例写入真实密钥。
- `project init`, `validate`, `build`, and version commands are local-only. Publish and Workbench resolve credentials from Bailian CLI flags, shell environment, or the active Profile; project initialization does not write credentials into the project directory.
- Build is local-only. Publish never runs Build implicitly and consumes only a current `.openagentpack/build/agents.yaml` plus manifest.
- Build moves literal Vault `secret_value` / `access_token` values from Agent-local or shared `vault.json` into project-root `.env`, replacing them with generated environment references. Existing references and `.env` entries are preserved; conflicts receive suffixed variable names. Preview/dry-run never write or print secrets. Publish and Workbench read the selected project's root `.env` as a fallback to inherited environment variables, even when invoked elsewhere. `.env` is private plaintext storage, excluded from local versions but not automatically ignored by Git; keep it backed up securely.
- Build 会将 Agent 本地或共享 `vault.json` 中的明文密钥移入项目根目录 `.env`,再写回环境变量引用;保留已有引用和变量,重名时生成后缀。预览不写文件或输出密钥。`.env` 不进入版本快照,也不加密;请自行备份并加入 Git 忽略规则。
- Agent-local File and Skill content supports Build-time association. A File may be copied directly into `agents/<agent>/files/`, or placed in `agents/<agent>/files/<id>/` when that directory contains exactly one content file; Build generates `file.json` and a `/mnt/<filename>` entry in `agent.json.files`. A directory under `agents/<agent>/skills/<id>/` containing `SKILL.md` generates `skill.json` and its `agent.json.skills` entry. Explicit JSON always wins; shared root resources remain explicit. Resources referenced by multiple Agents are promoted to the corresponding root shared directory during Build.
- A successful Publish versions the canonical YAML and the complete project source tree, including Skill scripts/assets and binary files. Remote State is never versioned or restored.
- `project version restore` restores source files to the working directory, invalidates Build, and does not move version history or remote State.
- `managed-agent playground` remains the standalone `agents.yaml` Session Preview path; directory Workbench is only under `managed-agent project workbench`.
## Scoped single-resource create
以下命令都先构造 `agents.yaml` 声明,再通过 SDK 的定向 Plan/Apply 创建远端资源,不绕过 State
@@ -37,8 +37,20 @@ Use this index for the skill-scoped quick index and global flags.
| `bl managed-agent file list` | API Key | List Managed Agent files | [managed-agent.md](managed-agent.md) |
| `bl managed-agent file search` | API Key | Search Managed Agent files | [managed-agent.md](managed-agent.md) |
| `bl managed-agent file upload` | API Key | Upload a Managed Agent file | [managed-agent.md](managed-agent.md) |
| `bl managed-agent init` | No Auth | Create a new agents.yaml template | [managed-agent.md](managed-agent.md) |
| `bl managed-agent init` | No Auth | Create an agents.yaml template | [managed-agent.md](managed-agent.md) |
| `bl managed-agent plan` | API Key | Show what changes would be applied to agent infrastructure | [managed-agent.md](managed-agent.md) |
| `bl managed-agent playground` | API Key | Launch a Session Preview for an agents.yaml Agent | [managed-agent.md](managed-agent.md) |
| `bl managed-agent project build` | No Auth | Organize directory source and generate the immutable Publish Build | [managed-agent.md](managed-agent.md) |
| `bl managed-agent project init` | No Auth | Create a directory project or convert the local agents.yaml | [managed-agent.md](managed-agent.md) |
| `bl managed-agent project publish` | API Key | Publish the current directory-project Build and record a version | [managed-agent.md](managed-agent.md) |
| `bl managed-agent project validate` | No Auth | Validate a directory Agent project | [managed-agent.md](managed-agent.md) |
| `bl managed-agent project version disable` | No Auth | Disable directory project versions | [managed-agent.md](managed-agent.md) |
| `bl managed-agent project version enable` | No Auth | Enable directory project versions | [managed-agent.md](managed-agent.md) |
| `bl managed-agent project version list` | No Auth | List directory project versions | [managed-agent.md](managed-agent.md) |
| `bl managed-agent project version preview` | No Auth | Preview a directory project version | [managed-agent.md](managed-agent.md) |
| `bl managed-agent project version restore` | No Auth | Restore a version to the project working directory | [managed-agent.md](managed-agent.md) |
| `bl managed-agent project version status` | No Auth | Show directory project version status | [managed-agent.md](managed-agent.md) |
| `bl managed-agent project workbench` | API Key | Launch the directory project Workbench | [managed-agent.md](managed-agent.md) |
| `bl managed-agent session archive` | API Key | Archive a Managed Agent session | [managed-agent.md](managed-agent.md) |
| `bl managed-agent session create` | API Key | Create a new session for an agent | [managed-agent.md](managed-agent.md) |
| `bl managed-agent session debug` | API Key | Aggregate session diagnostics | [managed-agent.md](managed-agent.md) |
@@ -74,9 +86,9 @@ Use this index for the skill-scoped quick index and global flags.
## By group
| Group | Commands | Reference |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
| `managed-agent` | `agent create`, `agent get`, `agent list`, `agent search`, `agent versions`, `apply`, `deployment create`, `deployment get`, `deployment list`, `deployment pause`, `deployment run`, `deployment runs get`, `deployment runs list`, `deployment search`, `deployment unpause`, `destroy`, `environment create`, `environment get`, `environment list`, `environment search`, `file delete`, `file download`, `file get`, `file list`, `file search`, `file upload`, `init`, `plan`, `session archive`, `session create`, `session debug`, `session delete`, `session event list`, `session event send`, `session event stream`, `session events`, `session export`, `session get`, `session list`, `session run`, `session search`, `session send`, `session update`, `skill create`, `skill download`, `skill get`, `skill list`, `skill search`, `skill versions`, `skill-list`, `state import`, `state list`, `state rm`, `state show`, `validate`, `vault create`, `vault credential create`, `vault get`, `vault list`, `vault search` | [managed-agent.md](managed-agent.md) |
| Group | Commands | Reference |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
| `managed-agent` | `agent create`, `agent get`, `agent list`, `agent search`, `agent versions`, `apply`, `deployment create`, `deployment get`, `deployment list`, `deployment pause`, `deployment run`, `deployment runs get`, `deployment runs list`, `deployment search`, `deployment unpause`, `destroy`, `environment create`, `environment get`, `environment list`, `environment search`, `file delete`, `file download`, `file get`, `file list`, `file search`, `file upload`, `init`, `plan`, `playground`, `project build`, `project init`, `project publish`, `project validate`, `project version disable`, `project version enable`, `project version list`, `project version preview`, `project version restore`, `project version status`, `project workbench`, `session archive`, `session create`, `session debug`, `session delete`, `session event list`, `session event send`, `session event stream`, `session events`, `session export`, `session get`, `session list`, `session run`, `session search`, `session send`, `session update`, `skill create`, `skill download`, `skill get`, `skill list`, `skill search`, `skill versions`, `skill-list`, `state import`, `state list`, `state rm`, `state show`, `validate`, `vault create`, `vault credential create`, `vault get`, `vault list`, `vault search` | [managed-agent.md](managed-agent.md) |
## Global flags
@@ -35,8 +35,20 @@ Index: [index.md](index.md)
| `bl managed-agent file list` | API Key | List Managed Agent files |
| `bl managed-agent file search` | API Key | Search Managed Agent files |
| `bl managed-agent file upload` | API Key | Upload a Managed Agent file |
| `bl managed-agent init` | No Auth | Create a new agents.yaml template |
| `bl managed-agent init` | No Auth | Create an agents.yaml template |
| `bl managed-agent plan` | API Key | Show what changes would be applied to agent infrastructure |
| `bl managed-agent playground` | API Key | Launch a Session Preview for an agents.yaml Agent |
| `bl managed-agent project build` | No Auth | Organize directory source and generate the immutable Publish Build |
| `bl managed-agent project init` | No Auth | Create a directory project or convert the local agents.yaml |
| `bl managed-agent project publish` | API Key | Publish the current directory-project Build and record a version |
| `bl managed-agent project validate` | No Auth | Validate a directory Agent project |
| `bl managed-agent project version disable` | No Auth | Disable directory project versions |
| `bl managed-agent project version enable` | No Auth | Enable directory project versions |
| `bl managed-agent project version list` | No Auth | List directory project versions |
| `bl managed-agent project version preview` | No Auth | Preview a directory project version |
| `bl managed-agent project version restore` | No Auth | Restore a version to the project working directory |
| `bl managed-agent project version status` | No Auth | Show directory project version status |
| `bl managed-agent project workbench` | API Key | Launch the directory project Workbench |
| `bl managed-agent session archive` | API Key | Archive a Managed Agent session |
| `bl managed-agent session create` | API Key | Create a new session for an agent |
| `bl managed-agent session debug` | API Key | Aggregate session diagnostics |
@@ -1068,7 +1080,7 @@ bl managed-agent file upload --path ./report.pdf
| Field | Value |
| ------------------ | ----------------------------------------------------------------------- |
| **Name** | `managed-agent init` |
| **Description** | Create a new agents.yaml template |
| **Description** | Create an agents.yaml template |
| **Authentication** | No Auth |
| **Usage** | `bl managed-agent init [--agent-name <name>] [--file <path>] [--force]` |
@@ -1126,6 +1138,370 @@ bl managed-agent plan
bl managed-agent plan --no-refresh
```
### `bl managed-agent playground`
| Field | Value |
| ------------------ | ------------------------------------------------------------------------------------- |
| **Name** | `managed-agent playground` |
| **Description** | Launch a Session Preview for an agents.yaml Agent |
| **Authentication** | API Key |
| **Usage** | `bl managed-agent playground [--file <path>] [--agent <id>] [--port <n>] [--no-open]` |
#### Flags
| Flag | Type | Required | Description |
| ------------------ | ------ | -------- | --------------------------------------------------------------------- |
| `--file <path>` | string | no | Config file path (default: agents.yaml) |
| `--port <n>` | number | no | Local port (default: 4848) |
| `--no-open` | switch | no | Do not open a browser automatically |
| `--agent <id>` | string | no | Agent to preview (required when the project declares multiple Agents) |
| `--api-key <key>` | string | no | API key |
| `--base-url <url>` | string | no | API base URL |
#### Notes
- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected.
- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile).
- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env.
- Session Preview requires Node.js 22+ and keeps using an agents.yaml source. Directory Workbench is available under managed-agent project workbench.
#### Examples
```bash
bl managed-agent playground
```
```bash
bl managed-agent playground --agent assistant
```
```bash
bl managed-agent playground --file agents.yaml --no-open
```
### `bl managed-agent project build`
| Field | Value |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------- |
| **Name** | `managed-agent project build` |
| **Description** | Organize directory source and generate the immutable Publish Build |
| **Authentication** | No Auth |
| **Usage** | `bl managed-agent project build [--project <directory>]` |
| **Risk** | `high` |
| **Risk message** | This organizes project source, moves literal Vault secrets into the local .env, and writes the previewed immutable Build. |
> **Agent safety:** Never add `--yes` automatically. On `type="requires_confirmation"`, stop and ask for explicit user confirmation of the same action and scope.
#### Flags
| Flag | Type | Required | Description |
| ----------------------- | ------ | -------- | --------------------------------------------------- |
| `--project <directory>` | string | no | Directory project root (default: current directory) |
| `--yes` | switch | no | Confirm this high-risk operation |
#### Examples
```bash
bl managed-agent project build --dry-run
```
```bash
# Only after explicit user confirmation:
bl managed-agent project build --yes
```
```bash
# Only after explicit user confirmation:
bl managed-agent project build --project ./my-agent --yes
```
### `bl managed-agent project init`
| Field | Value |
| ------------------ | ----------------------------------------------------------- |
| **Name** | `managed-agent project init` |
| **Description** | Create a directory project or convert the local agents.yaml |
| **Authentication** | No Auth |
| **Usage** | `bl managed-agent project init [--project <directory>]` |
#### Flags
| Flag | Type | Required | Description |
| ----------------------- | ------ | -------- | --------------------------------------------------- |
| `--project <directory>` | string | no | Directory project root (default: current directory) |
#### Notes
- New projects include Skill, File, Vault, and Environment examples under each resource directory's \_examples/. They are not referenced by agent.json and are excluded from Build/Publish. Copy an example outside \_examples/ to enable it, then configure its Agent reference.
#### Examples
```bash
bl managed-agent project init
```
```bash
bl managed-agent project init --project ./my-agent
```
### `bl managed-agent project publish`
| Field | Value |
| ------------------ | -------------------------------------------------------------------------------------------------------------------- |
| **Name** | `managed-agent project publish` |
| **Description** | Publish the current directory-project Build and record a version |
| **Authentication** | API Key |
| **Usage** | `bl managed-agent project publish [--project <directory>] [--no-refresh] [--concurrency <n>]` |
| **Risk** | `high` |
| **Risk message** | This publishes the current directory-project Build and may create, update, or delete remote managed Agent resources. |
> **Agent safety:** Never add `--yes` automatically. On `type="requires_confirmation"`, stop and ask for explicit user confirmation of the same action and scope.
#### Flags
| Flag | Type | Required | Description |
| ----------------------- | ------ | -------- | --------------------------------------------------- |
| `--project <directory>` | string | no | Directory project root (default: current directory) |
| `--no-refresh` | switch | no | Skip remote refresh before planning |
| `--concurrency <n>` | number | no | Maximum parallel resource operations |
| `--yes` | switch | no | Confirm this high-risk operation |
| `--api-key <key>` | string | no | API key |
| `--base-url <url>` | string | no | API base URL |
#### Notes
- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected.
- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile).
- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env.
#### Examples
```bash
# Only after explicit user confirmation:
bl managed-agent project publish --yes
```
```bash
# Only after explicit user confirmation:
bl managed-agent project publish --project ./my-agent --yes
```
### `bl managed-agent project validate`
| Field | Value |
| ------------------ | ----------------------------------------------------------- |
| **Name** | `managed-agent project validate` |
| **Description** | Validate a directory Agent project |
| **Authentication** | No Auth |
| **Usage** | `bl managed-agent project validate [--project <directory>]` |
#### Flags
| Flag | Type | Required | Description |
| ----------------------- | ------ | -------- | --------------------------------------------------- |
| `--project <directory>` | string | no | Directory project root (default: current directory) |
#### Examples
```bash
bl managed-agent project validate
```
```bash
bl managed-agent project validate --project ./my-agent
```
### `bl managed-agent project version disable`
| Field | Value |
| ------------------ | ------------------------------------------------------------------ |
| **Name** | `managed-agent project version disable` |
| **Description** | Disable directory project versions |
| **Authentication** | No Auth |
| **Usage** | `bl managed-agent project version disable [--project <directory>]` |
#### Flags
| Flag | Type | Required | Description |
| ----------------------- | ------ | -------- | --------------------------------------------------- |
| `--project <directory>` | string | no | Directory project root (default: current directory) |
#### Examples
```bash
bl managed-agent project version disable
```
```bash
bl managed-agent project version disable --project ./my-agent
```
### `bl managed-agent project version enable`
| Field | Value |
| ------------------ | ----------------------------------------------------------------- |
| **Name** | `managed-agent project version enable` |
| **Description** | Enable directory project versions |
| **Authentication** | No Auth |
| **Usage** | `bl managed-agent project version enable [--project <directory>]` |
#### Flags
| Flag | Type | Required | Description |
| ----------------------- | ------ | -------- | --------------------------------------------------- |
| `--project <directory>` | string | no | Directory project root (default: current directory) |
#### Examples
```bash
bl managed-agent project version enable
```
```bash
bl managed-agent project version enable --project ./my-agent
```
### `bl managed-agent project version list`
| Field | Value |
| ------------------ | ------------------------------------------------------------------------------------------------- |
| **Name** | `managed-agent project version list` |
| **Description** | List directory project versions |
| **Authentication** | No Auth |
| **Usage** | `bl managed-agent project version list [--project <directory>] [--limit <n>] [--cursor <cursor>]` |
#### Flags
| Flag | Type | Required | Description |
| ----------------------- | ------ | -------- | --------------------------------------------------- |
| `--project <directory>` | string | no | Directory project root (default: current directory) |
| `--limit <n>` | number | no | Maximum versions to return |
| `--cursor <cursor>` | string | no | Pagination cursor |
#### Examples
```bash
bl managed-agent project version list
```
```bash
bl managed-agent project version list --limit 20 --output json
```
### `bl managed-agent project version preview`
| Field | Value |
| ------------------ | ---------------------------------------------------------------------------------------------- |
| **Name** | `managed-agent project version preview` |
| **Description** | Preview a directory project version |
| **Authentication** | No Auth |
| **Usage** | `bl managed-agent project version preview --version-id <full-version> [--project <directory>]` |
#### Flags
| Flag | Type | Required | Description |
| ----------------------------- | ------ | -------- | --------------------------------------------------- |
| `--project <directory>` | string | no | Directory project root (default: current directory) |
| `--version-id <full-version>` | string | yes | Full project version ID |
#### Examples
```bash
bl managed-agent project version preview --version-id <full-version>
```
### `bl managed-agent project version restore`
| Field | Value |
| ------------------ | ------------------------------------------------------------------------------------------------------------ |
| **Name** | `managed-agent project version restore` |
| **Description** | Restore a version to the project working directory |
| **Authentication** | No Auth |
| **Usage** | `bl managed-agent project version restore --version-id <full-version> [--project <directory>]` |
| **Risk** | `high` |
| **Risk message** | This restores the full directory source to the working tree. Version history and remote State will not move. |
> **Agent safety:** Never add `--yes` automatically. On `type="requires_confirmation"`, stop and ask for explicit user confirmation of the same action and scope.
#### Flags
| Flag | Type | Required | Description |
| ----------------------------- | ------ | -------- | --------------------------------------------------- |
| `--project <directory>` | string | no | Directory project root (default: current directory) |
| `--version-id <full-version>` | string | yes | Full project version ID |
| `--yes` | switch | no | Confirm this high-risk operation |
#### Examples
```bash
bl managed-agent project version restore --version-id <full-version>
```
```bash
# Only after explicit user confirmation:
bl managed-agent project version restore --version-id <full-version> --yes
```
### `bl managed-agent project version status`
| Field | Value |
| ------------------ | ----------------------------------------------------------------- |
| **Name** | `managed-agent project version status` |
| **Description** | Show directory project version status |
| **Authentication** | No Auth |
| **Usage** | `bl managed-agent project version status [--project <directory>]` |
#### Flags
| Flag | Type | Required | Description |
| ----------------------- | ------ | -------- | --------------------------------------------------- |
| `--project <directory>` | string | no | Directory project root (default: current directory) |
#### Examples
```bash
bl managed-agent project version status
```
```bash
bl managed-agent project version status --project ./my-agent --output json
```
### `bl managed-agent project workbench`
| Field | Value |
| ------------------ | ------------------------------------------------------------------------------------- |
| **Name** | `managed-agent project workbench` |
| **Description** | Launch the directory project Workbench |
| **Authentication** | API Key |
| **Usage** | `bl managed-agent project workbench [--project <directory>] [--port <n>] [--no-open]` |
#### Flags
| Flag | Type | Required | Description |
| ----------------------- | ------ | -------- | --------------------------------------------------- |
| `--project <directory>` | string | no | Directory project root (default: current directory) |
| `--port <n>` | number | no | Local port (default: 4848) |
| `--no-open` | switch | no | Do not open a browser |
| `--api-key <key>` | string | no | API key |
| `--base-url <url>` | string | no | API base URL |
#### Notes
- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected.
- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile).
- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env.
#### Examples
```bash
bl managed-agent project workbench
```
```bash
bl managed-agent project workbench --project ./my-agent --no-open
```
### `bl managed-agent session archive`
| Field | Value |
+1 -1
View File
@@ -1,7 +1,7 @@
---
name: bailian-protocol
metadata:
version: "1.20.0"
version: "1.21.0"
requires:
bins: ["bl"]
description: >-
+1 -1
View File
@@ -1,7 +1,7 @@
---
name: bailian-sandbox
metadata:
version: "1.20.0"
version: "1.21.0"
requires:
bins: ["bl"]
description: >-
+1 -1
View File
@@ -1,7 +1,7 @@
---
name: bailian-web-search
metadata:
version: "1.20.0"
version: "1.21.0"
requires:
bins: ["bl"]
description: >-