feat(managed-agent): 增加托管 Agent 全面管理命令集及功能实现

- 扩展 CLI 命令,新增托管 Agent 相关全部操作命令包括能力、Agent、环境、技能、保管库、部署、会话及文件管理
- 实现 Agent 列表、详情、版本、搜索等功能
- 实现环境列表、详情及搜索功能
- 实现部署列表、详情、搜索、运行、暂停与恢复功能
- 实现会话的搜索、更新、归档及事件操作功能
- 实现文件上传、列表、下载、删除及搜索功能
- 添加托管 Agent API 操作能力展示命令
- 统一异常处理及输出格式支持,支持 JSON 与表格展示
- 完善分页、查询参数及权限验证逻辑,支持按需筛选与翻页
- 新增输入输出文件支持与控制台保护机制,提升命令行体验和稳定性
This commit is contained in:
chenanran555
2026-08-24 20:15:09 +08:00
parent e76ebee681
commit 69c1fb9f08
25 changed files with 4854 additions and 338 deletions
+75
View File
@@ -151,6 +151,43 @@ import {
managedAgentSessionSend,
managedAgentSessionEvents,
managedAgentSkillList,
managedAgentCapabilities,
managedAgentAgentList,
managedAgentAgentGet,
managedAgentAgentSearch,
managedAgentAgentVersions,
managedAgentEnvironmentList,
managedAgentEnvironmentGet,
managedAgentEnvironmentSearch,
managedAgentSkillGet,
managedAgentSkillSearch,
managedAgentSkillVersions,
managedAgentSkillDownload,
managedAgentVaultList,
managedAgentVaultGet,
managedAgentVaultSearch,
managedAgentDeploymentList,
managedAgentDeploymentGet,
managedAgentDeploymentSearch,
managedAgentDeploymentRunsList,
managedAgentDeploymentRunsGet,
managedAgentDeploymentRun,
managedAgentDeploymentPause,
managedAgentDeploymentUnpause,
managedAgentSessionSearch,
managedAgentSessionUpdate,
managedAgentSessionArchive,
managedAgentSessionEventList,
managedAgentSessionEventSend,
managedAgentSessionEventStream,
managedAgentSessionDebug,
managedAgentSessionExport,
managedAgentFileUpload,
managedAgentFileList,
managedAgentFileGet,
managedAgentFileSearch,
managedAgentFileDownload,
managedAgentFileDelete,
} from "bailian-cli-commands";
// Full bailian-cli product: every command, exposed under the `bl` binary.
@@ -312,6 +349,44 @@ export const commands: Record<string, AnyCommand> = {
"managed-agent session send": managedAgentSessionSend,
"managed-agent session events": managedAgentSessionEvents,
"managed-agent skill-list": managedAgentSkillList,
"managed-agent capabilities": managedAgentCapabilities,
"managed-agent agent list": managedAgentAgentList,
"managed-agent agent get": managedAgentAgentGet,
"managed-agent agent search": managedAgentAgentSearch,
"managed-agent agent versions": managedAgentAgentVersions,
"managed-agent environment list": managedAgentEnvironmentList,
"managed-agent environment get": managedAgentEnvironmentGet,
"managed-agent environment search": managedAgentEnvironmentSearch,
"managed-agent skill list": managedAgentSkillList,
"managed-agent skill get": managedAgentSkillGet,
"managed-agent skill search": managedAgentSkillSearch,
"managed-agent skill versions": managedAgentSkillVersions,
"managed-agent skill download": managedAgentSkillDownload,
"managed-agent vault list": managedAgentVaultList,
"managed-agent vault get": managedAgentVaultGet,
"managed-agent vault search": managedAgentVaultSearch,
"managed-agent deployment list": managedAgentDeploymentList,
"managed-agent deployment get": managedAgentDeploymentGet,
"managed-agent deployment search": managedAgentDeploymentSearch,
"managed-agent deployment runs list": managedAgentDeploymentRunsList,
"managed-agent deployment runs get": managedAgentDeploymentRunsGet,
"managed-agent deployment run": managedAgentDeploymentRun,
"managed-agent deployment pause": managedAgentDeploymentPause,
"managed-agent deployment unpause": managedAgentDeploymentUnpause,
"managed-agent session search": managedAgentSessionSearch,
"managed-agent session update": managedAgentSessionUpdate,
"managed-agent session archive": managedAgentSessionArchive,
"managed-agent session event list": managedAgentSessionEventList,
"managed-agent session event send": managedAgentSessionEventSend,
"managed-agent session event stream": managedAgentSessionEventStream,
"managed-agent session debug": managedAgentSessionDebug,
"managed-agent session export": managedAgentSessionExport,
"managed-agent file upload": managedAgentFileUpload,
"managed-agent file list": managedAgentFileList,
"managed-agent file get": managedAgentFileGet,
"managed-agent file search": managedAgentFileSearch,
"managed-agent file download": managedAgentFileDownload,
"managed-agent file delete": managedAgentFileDelete,
};
/**
@@ -0,0 +1,201 @@
import type { FlagsDef } from "bailian-cli-core";
import { emitBare, emitResult, formatTable } from "bailian-cli-runtime";
import type { PagedResult } from "./pagination.ts";
export const API_TARGET_FLAGS = {
file: {
type: "string",
valueHint: "<path>",
description: {
"en-US": "Config file path (default: agents.yaml)",
"zh-CN": "配置文件路径(默认:agents.yaml)",
},
},
provider: {
type: "string",
valueHint: "<name>",
description: { "en-US": "Target provider", "zh-CN": "目标 Provider" },
},
} satisfies FlagsDef;
export const CURSOR_FLAGS = {
limit: {
type: "number",
valueHint: "<n>",
description: {
"en-US": "Page size (1-100)",
"zh-CN": "单页数量(1-100)",
},
},
page: {
type: "string",
valueHint: "<cursor>",
description: {
"en-US": "Opaque page cursor returned by a previous request",
"zh-CN": "上一次请求返回的不透明分页 Cursor",
},
},
all: {
type: "switch",
description: {
"en-US": "Fetch all pages by following opaque cursors",
"zh-CN": "跟随不透明 Cursor 获取全部分页",
},
},
} satisfies FlagsDef;
export const SEARCH_FLAGS = {
query: {
type: "string",
valueHint: "<text>",
required: true,
description: {
"en-US": "Case-insensitive text to find in IDs, names, and descriptions",
"zh-CN": "在 ID、名称和描述中进行不区分大小写的文本搜索",
},
},
pageLimit: {
type: "number",
valueHint: "<n>",
description: {
"en-US": "Maximum pages to scan for client-side search (default: 10)",
"zh-CN": "客户端搜索最多扫描的页数(默认:10)",
},
},
} satisfies FlagsDef;
export const INCLUDE_ARCHIVED_FLAG = {
includeArchived: {
type: "switch",
description: {
"en-US": "Include archived resources",
"zh-CN": "包含已归档资源",
},
},
} satisfies FlagsDef;
export function validateLimitAndPageLimit(flags: {
limit?: number;
pageLimit?: number;
}): string | undefined {
if (
flags.limit !== undefined &&
(!Number.isInteger(flags.limit) || flags.limit < 1 || flags.limit > 100)
) {
return "--limit must be an integer between 1 and 100.";
}
if (
flags.pageLimit !== undefined &&
(!Number.isInteger(flags.pageLimit) || flags.pageLimit < 1 || flags.pageLimit > 100)
) {
return "--page-limit must be an integer between 1 and 100.";
}
return undefined;
}
export function splitCommaSeparated(value?: string): string[] | undefined {
if (!value) return undefined;
const values = value
.split(",")
.map((entry) => entry.trim())
.filter(Boolean);
return values.length > 0 ? values : undefined;
}
export function matchesQuery(query: string, ...values: unknown[]): boolean {
const normalized = query.trim().toLocaleLowerCase();
if (!normalized) return true;
return values.some((value) => {
if (value === undefined || value === null) return false;
const text = typeof value === "string" ? value : JSON.stringify(value);
return text.toLocaleLowerCase().includes(normalized);
});
}
export interface SearchResult<T> extends PagedResult<T> {
scannedPages: number;
truncated: boolean;
}
export async function searchCursorPages<T>(
fetchPage: (page?: string) => Promise<PagedResult<T>>,
matches: (item: T) => boolean,
pageLimit = 10,
): Promise<SearchResult<T>> {
const items: T[] = [];
let page: string | undefined;
let hasMore = false;
let nextPage: string | undefined;
let scannedPages = 0;
do {
const result = await fetchPage(page);
scannedPages += 1;
items.push(...result.items.filter(matches));
hasMore = result.hasMore;
nextPage = result.nextPage;
page = result.nextPage;
} while (hasMore && page && scannedPages < pageLimit);
return {
items,
hasMore,
nextPage,
scannedPages,
truncated: Boolean(hasMore && nextPage),
};
}
export function emitCollection<T>(options: {
format: "json" | "text";
key: string;
items: T[];
headers: string[];
rows: string[][];
hasMore?: boolean;
nextPage?: string;
truncated?: boolean;
scannedPages?: number;
emptyMessage?: string;
}): void {
const {
format,
key,
items,
headers,
rows,
hasMore = false,
nextPage,
truncated,
scannedPages,
emptyMessage = "No resources found.",
} = options;
if (format === "json") {
emitResult(
{
[key]: items,
has_more: hasMore,
next_page: nextPage,
...(truncated === undefined ? {} : { truncated }),
...(scannedPages === undefined ? {} : { scanned_pages: scannedPages }),
},
format,
);
return;
}
if (items.length === 0) {
emitBare(emptyMessage);
return;
}
for (const line of formatTable(headers, rows)) emitBare(line);
emitBare(`\nTotal: ${items.length}`);
if (truncated) emitBare("Search stopped at --page-limit; more pages remain.");
else if (hasMore)
emitBare(`More results are available.${nextPage ? ` Next page: ${nextPage}` : ""}`);
}
export function displayValue(value: unknown, maxLength = 40): string {
if (value === undefined || value === null || value === "") return "-";
const text = typeof value === "string" ? value : JSON.stringify(value);
return text.length > maxLength ? `${text.slice(0, maxLength - 3)}...` : text;
}
@@ -0,0 +1,68 @@
import { randomUUID } from "node:crypto";
import { link, mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
import { basename, dirname, resolve } from "node:path";
import { BailianError, ExitCode } from "bailian-cli-core";
export async function readInputFile(path: string): Promise<Uint8Array> {
return new Uint8Array(await readFile(resolve(path)));
}
export async function readJsonInput(argument: string): Promise<unknown> {
const source = argument.startsWith("@")
? await readFile(resolve(argument.slice(1)), "utf8")
: argument;
try {
return JSON.parse(source) as unknown;
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new BailianError(`Invalid JSON input: ${message}`, ExitCode.USAGE);
}
}
export async function writeOutputFile(
outputPath: string,
content: Uint8Array,
force = false,
): Promise<string> {
const destination = resolve(outputPath);
const parent = dirname(destination);
await mkdir(parent, { recursive: true });
const temporary = `${destination}.${randomUUID()}.tmp`;
await writeFile(temporary, content, { flag: "wx" });
try {
if (force) {
await rename(temporary, destination);
} else {
try {
await link(temporary, destination);
} catch (error) {
const code = (error as NodeJS.ErrnoException).code;
if (code === "EEXIST") {
throw new BailianError(
`Output file already exists: ${destination}`,
ExitCode.USAGE,
"Choose another --output-file path or re-run with --force.",
);
}
throw error;
}
await unlink(temporary);
}
return destination;
} catch (error) {
await unlink(temporary).catch(() => undefined);
throw error;
}
}
export function inferMimeType(path: string): string {
const filename = basename(path).toLocaleLowerCase();
if (filename.endsWith(".json")) return "application/json";
if (filename.endsWith(".md")) return "text/markdown";
if (filename.endsWith(".txt")) return "text/plain";
if (filename.endsWith(".pdf")) return "application/pdf";
if (filename.endsWith(".png")) return "image/png";
if (filename.endsWith(".jpg") || filename.endsWith(".jpeg")) return "image/jpeg";
if (filename.endsWith(".zip")) return "application/zip";
return "application/octet-stream";
}
@@ -8,8 +8,9 @@ export interface PagedResult<T> {
export async function fetchAllPages<T>(
fetchPage: (page?: string) => Promise<PagedResult<T>>,
all?: boolean,
initialPage?: string,
): Promise<PagedResult<T>> {
const first = await fetchPage();
const first = await fetchPage(initialPage);
const items = [...first.items];
let hasMore = first.hasMore;
let nextPage = first.nextPage;
@@ -0,0 +1,236 @@
import type { CloudAgent } from "@openagentpack/sdk";
import { getRemoteAgent, listRemoteAgents, listRemoteAgentVersions } from "@openagentpack/sdk";
import { defineCommand, detectOutputFormat } from "bailian-cli-core";
import { emitBare, emitResult } from "bailian-cli-runtime";
import {
API_TARGET_FLAGS,
CURSOR_FLAGS,
displayValue,
emitCollection,
INCLUDE_ARCHIVED_FLAG,
matchesQuery,
SEARCH_FLAGS,
searchCursorPages,
validateLimitAndPageLimit,
} from "./_engine/api-helpers.ts";
import { buildAgentRuntime, CREDENTIALS_NOTE } from "./_engine/config-loader.ts";
import { withStdoutProtected } from "./_engine/console-capture.ts";
import { withAgentErrors } from "./_engine/errors.ts";
import { fetchAllPages } from "./_engine/pagination.ts";
const AGENT_LIST_FLAGS = {
...API_TARGET_FLAGS,
...CURSOR_FLAGS,
...INCLUDE_ARCHIVED_FLAG,
};
const AGENT_SEARCH_FLAGS = {
...API_TARGET_FLAGS,
limit: CURSOR_FLAGS.limit,
...SEARCH_FLAGS,
...INCLUDE_ARCHIVED_FLAG,
};
const AGENT_GET_FLAGS = {
...API_TARGET_FLAGS,
agentId: {
type: "string",
valueHint: "<id>",
required: true,
description: { "en-US": "Agent ID", "zh-CN": "Agent ID" },
},
agentVersion: {
type: "number",
valueHint: "<n>",
description: { "en-US": "Specific agent version", "zh-CN": "指定 Agent 版本" },
},
} as const;
const AGENT_VERSIONS_FLAGS = {
...API_TARGET_FLAGS,
...CURSOR_FLAGS,
agentId: AGENT_GET_FLAGS.agentId,
};
function agentRows(agents: CloudAgent[]): string[][] {
return agents.map((agent) => [
agent.id,
displayValue(agent.name),
displayValue(agent.version),
displayValue(agent.type),
displayValue(agent.updated_at),
]);
}
export const managedAgentAgentList = defineCommand({
description: { "en-US": "List Managed Agents", "zh-CN": "列出托管 Agent" },
auth: "apiKey",
usageArgs: "[--limit <n>] [--page <cursor>] [--all] [--include-archived] [--file <path>]",
flags: AGENT_LIST_FLAGS,
exampleArgs: ["", "--limit 50", "--all --include-archived --output json"],
notes: CREDENTIALS_NOTE,
validate: validateLimitAndPageLimit,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
const result = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return fetchAllPages(
async (page) => {
const response = await listRemoteAgents(runtime, {
provider: ctx.flags.provider,
limit: ctx.flags.limit,
page,
include_archived: ctx.flags.includeArchived,
});
return {
items: response.data,
hasMore: response.has_more,
nextPage: response.next_page,
};
},
ctx.flags.all,
ctx.flags.page,
);
}),
);
emitCollection({
format,
key: "agents",
items: result.items,
headers: ["ID", "NAME", "VERSION", "TYPE", "UPDATED"],
rows: agentRows(result.items),
hasMore: result.hasMore,
nextPage: result.nextPage,
emptyMessage: "No agents found.",
});
},
});
export const managedAgentAgentGet = defineCommand({
description: { "en-US": "Get a Managed Agent", "zh-CN": "获取托管 Agent 详情" },
auth: "apiKey",
usageArgs: "--agent-id <id> [--agent-version <n>] [--file <path>]",
flags: AGENT_GET_FLAGS,
exampleArgs: ["--agent-id agent_abc", "--agent-id agent_abc --agent-version 3 --output json"],
notes: CREDENTIALS_NOTE,
validate: (flags) =>
flags.agentVersion !== undefined &&
(!Number.isInteger(flags.agentVersion) || flags.agentVersion < 1)
? "--agent-version must be a positive integer."
: undefined,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
const agent = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return getRemoteAgent(runtime, ctx.flags.agentId, {
provider: ctx.flags.provider,
version: ctx.flags.agentVersion,
});
}),
);
if (format === "json") {
emitResult(agent, format);
return;
}
emitBare(`ID: ${agent.id}`);
emitBare(`Name: ${displayValue(agent.name)}`);
emitBare(`Description: ${displayValue(agent.description, 120)}`);
emitBare(`Version: ${displayValue(agent.version)}`);
emitBare(`Type: ${displayValue(agent.type)}`);
emitBare(`Created: ${displayValue(agent.created_at)}`);
emitBare(`Updated: ${displayValue(agent.updated_at)}`);
},
});
export const managedAgentAgentSearch = defineCommand({
description: { "en-US": "Search Managed Agents", "zh-CN": "搜索托管 Agent" },
auth: "apiKey",
usageArgs: "--query <text> [--limit <n>] [--page-limit <n>] [--include-archived]",
flags: AGENT_SEARCH_FLAGS,
exampleArgs: ["--query assistant", "--query code --page-limit 20 --output json"],
notes: CREDENTIALS_NOTE,
validate: validateLimitAndPageLimit,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
const result = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return searchCursorPages(
async (page) => {
const response = await listRemoteAgents(runtime, {
provider: ctx.flags.provider,
limit: ctx.flags.limit ?? 100,
page,
include_archived: ctx.flags.includeArchived,
});
return {
items: response.data,
hasMore: response.has_more,
nextPage: response.next_page,
};
},
(agent) => matchesQuery(ctx.flags.query, agent.id, agent.name, agent.description),
ctx.flags.pageLimit,
);
}),
);
emitCollection({
format,
key: "agents",
items: result.items,
headers: ["ID", "NAME", "VERSION", "TYPE", "UPDATED"],
rows: agentRows(result.items),
hasMore: result.hasMore,
nextPage: result.nextPage,
truncated: result.truncated,
scannedPages: result.scannedPages,
emptyMessage: "No matching agents found.",
});
},
});
export const managedAgentAgentVersions = defineCommand({
description: { "en-US": "List Managed Agent versions", "zh-CN": "列出托管 Agent 版本" },
auth: "apiKey",
usageArgs: "--agent-id <id> [--limit <n>] [--page <cursor>] [--all]",
flags: AGENT_VERSIONS_FLAGS,
exampleArgs: ["--agent-id agent_abc", "--agent-id agent_abc --all --output json"],
notes: CREDENTIALS_NOTE,
validate: validateLimitAndPageLimit,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
const result = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return fetchAllPages(
async (page) => {
const response = await listRemoteAgentVersions(runtime, ctx.flags.agentId, {
provider: ctx.flags.provider,
limit: ctx.flags.limit,
page,
});
return {
items: response.data,
hasMore: response.has_more,
nextPage: response.next_page,
};
},
ctx.flags.all,
ctx.flags.page,
);
}),
);
emitCollection({
format,
key: "versions",
items: result.items,
headers: ["ID", "NAME", "VERSION", "TYPE", "UPDATED"],
rows: agentRows(result.items),
hasMore: result.hasMore,
nextPage: result.nextPage,
emptyMessage: "No agent versions found.",
});
},
});
@@ -0,0 +1,49 @@
import { getManagedAgentProviderCapabilities } from "@openagentpack/sdk";
import { defineCommand, detectOutputFormat } from "bailian-cli-core";
import { emitBare, emitResult, formatTable } from "bailian-cli-runtime";
const FLAGS = {
provider: {
type: "string",
valueHint: "<name>",
description: {
"en-US": "Provider to inspect (default: bailian)",
"zh-CN": "要检查的 Provider(默认:bailian)",
},
},
} as const;
export default defineCommand({
description: {
"en-US": "Show operation-level Managed Agents API capabilities",
"zh-CN": "显示 Managed Agents API 的操作级能力",
},
auth: "none",
usageArgs: "[--provider <name>]",
flags: FLAGS,
exampleArgs: ["", "--provider bailian --output json"],
notes: [
{
"en-US":
"Capabilities distinguish public Managed Agents APIs from client-side compositions and unsupported resources.",
"zh-CN": "Capabilities 会区分公开 Managed Agents API、客户端组合能力和不支持的资源。",
},
],
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
const capabilities = getManagedAgentProviderCapabilities(ctx.flags.provider ?? "bailian");
if (format === "json") {
emitResult(capabilities, format);
return;
}
const rows = Object.entries(capabilities.operations).map(([operation, capability]) => [
operation,
capability.supported ? "yes" : "no",
capability.auth ?? "-",
capability.reason ?? "-",
]);
for (const line of formatTable(["OPERATION", "SUPPORTED", "AUTH", "REASON"], rows)) {
emitBare(line);
}
},
});
@@ -0,0 +1,471 @@
import type { DeploymentInfo, DeploymentRunInfo, ProjectRuntimeContext } from "@openagentpack/sdk";
import {
getRemoteDeployment,
getRemoteDeploymentRun,
listRemoteDeploymentRuns,
listRemoteDeployments,
runRemoteDeployment,
setRemoteDeploymentPaused,
UserError,
} from "@openagentpack/sdk";
import { BailianError, defineCommand, detectOutputFormat, ExitCode } from "bailian-cli-core";
import { emitBare, emitResult } from "bailian-cli-runtime";
import {
API_TARGET_FLAGS,
CURSOR_FLAGS,
displayValue,
emitCollection,
INCLUDE_ARCHIVED_FLAG,
SEARCH_FLAGS,
validateLimitAndPageLimit,
} from "./_engine/api-helpers.ts";
import { buildAgentRuntime, CREDENTIALS_NOTE } from "./_engine/config-loader.ts";
import { withStdoutProtected } from "./_engine/console-capture.ts";
import { withAgentErrors } from "./_engine/errors.ts";
import { fetchAllPages } from "./_engine/pagination.ts";
const DEPLOYMENT_ID_FLAG = {
deploymentId: {
type: "string",
valueHint: "<id>",
required: true,
description: { "en-US": "Deployment ID", "zh-CN": "Deployment ID" },
},
} as const;
const LIST_FILTER_FLAGS = {
agentId: {
type: "string",
valueHint: "<id>",
description: { "en-US": "Filter by agent ID", "zh-CN": "按 Agent ID 筛选" },
},
status: {
type: "string",
valueHint: "<status>",
choices: ["active", "paused"] as const,
description: { "en-US": "Filter by deployment status", "zh-CN": "按 Deployment 状态筛选" },
},
...INCLUDE_ARCHIVED_FLAG,
createdAtGte: {
type: "string",
valueHint: "<timestamp>",
description: {
"en-US": "Created at or after this timestamp",
"zh-CN": "创建时间不早于该时间戳",
},
},
createdAtLte: {
type: "string",
valueHint: "<timestamp>",
description: {
"en-US": "Created at or before this timestamp",
"zh-CN": "创建时间不晚于该时间戳",
},
},
} as const;
const LIST_FLAGS = { ...API_TARGET_FLAGS, ...CURSOR_FLAGS, ...LIST_FILTER_FLAGS };
const GET_FLAGS = { ...API_TARGET_FLAGS, ...DEPLOYMENT_ID_FLAG };
const SEARCH_RESOURCE_FLAGS = {
...API_TARGET_FLAGS,
...CURSOR_FLAGS,
query: SEARCH_FLAGS.query,
...LIST_FILTER_FLAGS,
};
const RUN_LIST_FLAGS = { ...API_TARGET_FLAGS, ...DEPLOYMENT_ID_FLAG, ...CURSOR_FLAGS };
const RUN_GET_FLAGS = {
...API_TARGET_FLAGS,
runId: {
type: "string",
valueHint: "<id>",
required: true,
description: { "en-US": "Deployment run ID", "zh-CN": "Deployment Run ID" },
},
} as const;
const ACTION_TARGET_FLAGS = {
...API_TARGET_FLAGS,
deployment: {
type: "string",
valueHint: "<name>",
description: {
"en-US": "Logical deployment name in agents.yaml/state",
"zh-CN": "agents.yaml/State 中的逻辑 Deployment 名称",
},
},
deploymentId: {
type: "string",
valueHint: "<id>",
description: { "en-US": "Direct deployment ID", "zh-CN": "直接指定 Deployment ID" },
},
} as const;
const RUN_ACTION_FLAGS = {
...ACTION_TARGET_FLAGS,
yes: {
type: "switch",
description: { "en-US": "Confirm deployment run", "zh-CN": "确认运行 Deployment" },
},
} as const;
function validateActionTarget(flags: {
deployment?: string;
deploymentId?: string;
}): string | undefined {
if (Boolean(flags.deployment) === Boolean(flags.deploymentId)) {
return "Provide exactly one of --deployment or --deployment-id.";
}
return undefined;
}
function deploymentRows(deployments: DeploymentInfo[]): string[][] {
return deployments.map((deployment) => [
displayValue(deployment.id),
deployment.status,
displayValue(deployment.schedule?.expression),
displayValue(deployment.paused_reason?.type),
]);
}
function runRows(runs: DeploymentRunInfo[]): string[][] {
return runs.map((run) => [
run.id,
displayValue(run.deployment_id),
displayValue(run.session_id),
displayValue(run.status),
displayValue(run.created_at),
]);
}
async function resolveDeploymentTarget(
runtime: ProjectRuntimeContext,
options: { provider?: string; deployment?: string; deploymentId?: string },
): Promise<string> {
if (options.deploymentId) return options.deploymentId;
const configuredProviders = Array.from(runtime.providers.keys());
const provider =
options.provider ??
(configuredProviders.length === 1
? configuredProviders[0]
: (() => {
throw new UserError("Multiple providers configured. Use --provider to specify one.");
})());
const state = runtime.state.getResource({
provider,
type: "deployment",
name: options.deployment!,
});
if (!state?.remote_id) {
throw new UserError(
`Deployment '${options.deployment}' is not tracked in state. Use --deployment-id or run managed-agent apply/import first.`,
);
}
return state.remote_id;
}
export const managedAgentDeploymentList = defineCommand({
description: { "en-US": "List Managed Agent deployments", "zh-CN": "列出托管 Agent Deployment" },
auth: "apiKey",
usageArgs: "[--agent-id <id>] [--status active|paused] [--limit <n>] [--page <cursor>] [--all]",
flags: LIST_FLAGS,
exampleArgs: ["", "--status active --all --output json"],
notes: CREDENTIALS_NOTE,
validate: validateLimitAndPageLimit,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
const result = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return fetchAllPages(
async (page) => {
const response = await listRemoteDeployments(runtime, {
provider: ctx.flags.provider,
agent_id: ctx.flags.agentId,
status: ctx.flags.status,
include_archived: ctx.flags.includeArchived,
created_at_gte: ctx.flags.createdAtGte,
created_at_lte: ctx.flags.createdAtLte,
limit: ctx.flags.limit,
page,
});
return {
items: response.deployments,
hasMore: response.has_more,
nextPage: response.next_page,
};
},
ctx.flags.all,
ctx.flags.page,
);
}),
);
emitCollection({
format,
key: "deployments",
items: result.items,
headers: ["ID", "STATUS", "SCHEDULE", "PAUSED REASON"],
rows: deploymentRows(result.items),
hasMore: result.hasMore,
nextPage: result.nextPage,
emptyMessage: "No deployments found.",
});
},
});
export const managedAgentDeploymentGet = defineCommand({
description: {
"en-US": "Get a Managed Agent deployment",
"zh-CN": "获取托管 Agent Deployment 详情",
},
auth: "apiKey",
usageArgs: "--deployment-id <id>",
flags: GET_FLAGS,
exampleArgs: ["--deployment-id dep_abc"],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
const deployment = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return getRemoteDeployment(runtime, ctx.flags.deploymentId, {
provider: ctx.flags.provider,
});
}),
);
if (format === "json") emitResult(deployment, format);
else {
emitBare(`ID: ${displayValue(deployment.id)}`);
emitBare(`Status: ${deployment.status}`);
emitBare(`Schedule:${displayValue(deployment.schedule?.expression)}`);
emitBare(`Paused: ${displayValue(deployment.paused_reason)}`);
}
},
});
export const managedAgentDeploymentSearch = defineCommand({
description: {
"en-US": "Search Managed Agent deployments",
"zh-CN": "搜索托管 Agent Deployment",
},
auth: "apiKey",
usageArgs: "--query <text> [--limit <n>] [--page <cursor>] [--all]",
flags: SEARCH_RESOURCE_FLAGS,
exampleArgs: ["--query report", "--query nightly --all --output json"],
notes: [
...CREDENTIALS_NOTE,
{
"en-US": "Deployment search maps --query to the provider's server-side keyword parameter.",
"zh-CN": "Deployment 搜索会把 --query 映射为 Provider 服务端 keyword 参数。",
},
],
validate: validateLimitAndPageLimit,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
const result = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return fetchAllPages(
async (page) => {
const response = await listRemoteDeployments(runtime, {
provider: ctx.flags.provider,
keyword: ctx.flags.query,
agent_id: ctx.flags.agentId,
status: ctx.flags.status,
include_archived: ctx.flags.includeArchived,
created_at_gte: ctx.flags.createdAtGte,
created_at_lte: ctx.flags.createdAtLte,
limit: ctx.flags.limit,
page,
});
return {
items: response.deployments,
hasMore: response.has_more,
nextPage: response.next_page,
};
},
ctx.flags.all,
ctx.flags.page,
);
}),
);
emitCollection({
format,
key: "deployments",
items: result.items,
headers: ["ID", "STATUS", "SCHEDULE", "PAUSED REASON"],
rows: deploymentRows(result.items),
hasMore: result.hasMore,
nextPage: result.nextPage,
emptyMessage: "No matching deployments found.",
});
},
});
export const managedAgentDeploymentRunsList = defineCommand({
description: {
"en-US": "List runs for a Managed Agent deployment",
"zh-CN": "列出托管 Agent Deployment Run",
},
auth: "apiKey",
usageArgs: "--deployment-id <id> [--limit <n>] [--page <cursor>] [--all]",
flags: RUN_LIST_FLAGS,
exampleArgs: ["--deployment-id dep_abc", "--deployment-id dep_abc --all --output json"],
notes: CREDENTIALS_NOTE,
validate: validateLimitAndPageLimit,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
const result = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return fetchAllPages(
async (page) => {
const response = await listRemoteDeploymentRuns(runtime, ctx.flags.deploymentId, {
provider: ctx.flags.provider,
limit: ctx.flags.limit,
page,
});
return {
items: response.data,
hasMore: response.has_more,
nextPage: response.next_page,
};
},
ctx.flags.all,
ctx.flags.page,
);
}),
);
emitCollection({
format,
key: "runs",
items: result.items,
headers: ["ID", "DEPLOYMENT", "SESSION", "STATUS", "CREATED"],
rows: runRows(result.items),
hasMore: result.hasMore,
nextPage: result.nextPage,
emptyMessage: "No deployment runs found.",
});
},
});
export const managedAgentDeploymentRunsGet = defineCommand({
description: {
"en-US": "Get a Managed Agent deployment run",
"zh-CN": "获取托管 Agent Deployment Run 详情",
},
auth: "apiKey",
usageArgs: "--run-id <id>",
flags: RUN_GET_FLAGS,
exampleArgs: ["--run-id run_abc"],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
const run = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return getRemoteDeploymentRun(runtime, ctx.flags.runId, { provider: ctx.flags.provider });
}),
);
if (format === "json") emitResult(run, format);
else {
emitBare(`ID: ${run.id}`);
emitBare(`Deployment: ${displayValue(run.deployment_id)}`);
emitBare(`Session: ${displayValue(run.session_id)}`);
emitBare(`Status: ${displayValue(run.status)}`);
emitBare(`Created: ${displayValue(run.created_at)}`);
emitBare(`Error: ${displayValue(run.error)}`);
}
},
});
export const managedAgentDeploymentRun = defineCommand({
description: {
"en-US": "Run a Managed Agent deployment now",
"zh-CN": "立即运行托管 Agent Deployment",
},
auth: "apiKey",
usageArgs: "(--deployment <name> | --deployment-id <id>) --yes",
flags: RUN_ACTION_FLAGS,
exampleArgs: ["--deployment daily-report --dry-run", "--deployment-id dep_abc --yes"],
notes: CREDENTIALS_NOTE,
validate: validateActionTarget,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
if (ctx.settings.dryRun) {
emitResult(
{
would_run_deployment: ctx.flags.deploymentId ?? ctx.flags.deployment,
target_kind: ctx.flags.deploymentId ? "id" : "state_name",
},
format,
);
return;
}
if (!ctx.flags.yes) {
throw new BailianError(
"Refusing to run the deployment without confirmation.",
ExitCode.USAGE,
"Re-run with --yes or preview with --dry-run.",
);
}
const result = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
const deploymentId = await resolveDeploymentTarget(runtime, ctx.flags);
return {
deploymentId,
run: await runRemoteDeployment(runtime, deploymentId, { provider: ctx.flags.provider }),
};
}),
);
if (format === "json")
emitResult({ deployment_id: result.deploymentId, ...result.run }, format);
else emitBare(`Deployment ${result.deploymentId} started. Run: ${result.run.run_id ?? "-"}`);
},
});
function createPauseCommand(paused: boolean) {
return defineCommand({
description: paused
? { "en-US": "Pause a Managed Agent deployment", "zh-CN": "暂停托管 Agent Deployment" }
: { "en-US": "Unpause a Managed Agent deployment", "zh-CN": "恢复托管 Agent Deployment" },
auth: "apiKey",
usageArgs: "(--deployment <name> | --deployment-id <id>)",
flags: ACTION_TARGET_FLAGS,
exampleArgs: [`--deployment daily-report --dry-run`, `--deployment-id dep_abc`],
notes: CREDENTIALS_NOTE,
validate: validateActionTarget,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
if (ctx.settings.dryRun) {
emitResult(
{
[paused ? "would_pause_deployment" : "would_unpause_deployment"]:
ctx.flags.deploymentId ?? ctx.flags.deployment,
target_kind: ctx.flags.deploymentId ? "id" : "state_name",
},
format,
);
return;
}
const result = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
const deploymentId = await resolveDeploymentTarget(runtime, ctx.flags);
return {
deploymentId,
deployment: await setRemoteDeploymentPaused(runtime, deploymentId, paused, {
provider: ctx.flags.provider,
}),
};
}),
);
if (format === "json") {
emitResult({ deployment_id: result.deploymentId, deployment: result.deployment }, format);
} else {
emitBare(`Deployment ${result.deploymentId} ${paused ? "paused" : "unpaused"}.`);
}
},
});
}
export const managedAgentDeploymentPause = createPauseCommand(true);
export const managedAgentDeploymentUnpause = createPauseCommand(false);
@@ -0,0 +1,174 @@
import type { CloudEnvironment } from "@openagentpack/sdk";
import { getRemoteEnvironment, listRemoteEnvironments } from "@openagentpack/sdk";
import { defineCommand, detectOutputFormat } from "bailian-cli-core";
import { emitBare, emitResult } from "bailian-cli-runtime";
import {
API_TARGET_FLAGS,
CURSOR_FLAGS,
displayValue,
emitCollection,
INCLUDE_ARCHIVED_FLAG,
matchesQuery,
SEARCH_FLAGS,
searchCursorPages,
validateLimitAndPageLimit,
} from "./_engine/api-helpers.ts";
import { buildAgentRuntime, CREDENTIALS_NOTE } from "./_engine/config-loader.ts";
import { withStdoutProtected } from "./_engine/console-capture.ts";
import { withAgentErrors } from "./_engine/errors.ts";
import { fetchAllPages } from "./_engine/pagination.ts";
const LIST_FLAGS = { ...API_TARGET_FLAGS, ...CURSOR_FLAGS, ...INCLUDE_ARCHIVED_FLAG };
const SEARCH_RESOURCE_FLAGS = {
...API_TARGET_FLAGS,
limit: CURSOR_FLAGS.limit,
...SEARCH_FLAGS,
...INCLUDE_ARCHIVED_FLAG,
};
const GET_FLAGS = {
...API_TARGET_FLAGS,
environmentId: {
type: "string",
valueHint: "<id>",
required: true,
description: { "en-US": "Environment ID", "zh-CN": "Environment ID" },
},
} as const;
function environmentRows(environments: CloudEnvironment[]): string[][] {
return environments.map((environment) => [
environment.id,
displayValue(environment.name),
displayValue(environment.scope),
displayValue(environment.version),
displayValue(environment.updated_at),
]);
}
export const managedAgentEnvironmentList = defineCommand({
description: { "en-US": "List Managed Agent environments", "zh-CN": "列出托管 Agent 环境" },
auth: "apiKey",
usageArgs: "[--limit <n>] [--page <cursor>] [--all] [--include-archived]",
flags: LIST_FLAGS,
exampleArgs: ["", "--all --output json"],
notes: CREDENTIALS_NOTE,
validate: validateLimitAndPageLimit,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
const result = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return fetchAllPages(
async (page) => {
const response = await listRemoteEnvironments(runtime, {
provider: ctx.flags.provider,
limit: ctx.flags.limit,
page,
include_archived: ctx.flags.includeArchived,
});
return {
items: response.data,
hasMore: response.has_more,
nextPage: response.next_page,
};
},
ctx.flags.all,
ctx.flags.page,
);
}),
);
emitCollection({
format,
key: "environments",
items: result.items,
headers: ["ID", "NAME", "SCOPE", "VERSION", "UPDATED"],
rows: environmentRows(result.items),
hasMore: result.hasMore,
nextPage: result.nextPage,
emptyMessage: "No environments found.",
});
},
});
export const managedAgentEnvironmentGet = defineCommand({
description: { "en-US": "Get a Managed Agent environment", "zh-CN": "获取托管 Agent 环境详情" },
auth: "apiKey",
usageArgs: "--environment-id <id>",
flags: GET_FLAGS,
exampleArgs: ["--environment-id env_abc"],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
const environment = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return getRemoteEnvironment(runtime, ctx.flags.environmentId, {
provider: ctx.flags.provider,
});
}),
);
if (format === "json") {
emitResult(environment, format);
return;
}
emitBare(`ID: ${environment.id}`);
emitBare(`Name: ${displayValue(environment.name)}`);
emitBare(`Description: ${displayValue(environment.description, 120)}`);
emitBare(`Scope: ${displayValue(environment.scope)}`);
emitBare(`Version: ${displayValue(environment.version)}`);
emitBare(`Updated: ${displayValue(environment.updated_at)}`);
},
});
export const managedAgentEnvironmentSearch = defineCommand({
description: { "en-US": "Search Managed Agent environments", "zh-CN": "搜索托管 Agent 环境" },
auth: "apiKey",
usageArgs: "--query <text> [--limit <n>] [--page-limit <n>] [--include-archived]",
flags: SEARCH_RESOURCE_FLAGS,
exampleArgs: ["--query sandbox", "--query production --page-limit 20 --output json"],
notes: CREDENTIALS_NOTE,
validate: validateLimitAndPageLimit,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
const result = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return searchCursorPages(
async (page) => {
const response = await listRemoteEnvironments(runtime, {
provider: ctx.flags.provider,
limit: ctx.flags.limit ?? 100,
page,
include_archived: ctx.flags.includeArchived,
});
return {
items: response.data,
hasMore: response.has_more,
nextPage: response.next_page,
};
},
(environment) =>
matchesQuery(
ctx.flags.query,
environment.id,
environment.name,
environment.description,
),
ctx.flags.pageLimit,
);
}),
);
emitCollection({
format,
key: "environments",
items: result.items,
headers: ["ID", "NAME", "SCOPE", "VERSION", "UPDATED"],
rows: environmentRows(result.items),
hasMore: result.hasMore,
nextPage: result.nextPage,
truncated: result.truncated,
scannedPages: result.scannedPages,
emptyMessage: "No matching environments found.",
});
},
});
@@ -0,0 +1,327 @@
import { basename } from "node:path";
import type { ProviderFileInfo } from "@openagentpack/sdk";
import {
deleteFile,
downloadRemoteFile,
getFileInfo,
listRemoteFiles,
uploadFile,
} from "@openagentpack/sdk";
import { BailianError, defineCommand, detectOutputFormat, ExitCode } from "bailian-cli-core";
import { emitBare, emitResult } from "bailian-cli-runtime";
import {
API_TARGET_FLAGS,
CURSOR_FLAGS,
displayValue,
emitCollection,
matchesQuery,
SEARCH_FLAGS,
searchCursorPages,
validateLimitAndPageLimit,
} from "./_engine/api-helpers.ts";
import { buildAgentRuntime, CREDENTIALS_NOTE } from "./_engine/config-loader.ts";
import { withStdoutProtected } from "./_engine/console-capture.ts";
import { withAgentErrors } from "./_engine/errors.ts";
import { inferMimeType, readInputFile, writeOutputFile } from "./_engine/output-file.ts";
import { fetchAllPages } from "./_engine/pagination.ts";
const FILE_ID_FLAG = {
fileId: {
type: "string",
valueHint: "<id>",
required: true,
description: { "en-US": "Remote file ID", "zh-CN": "远端 File ID" },
},
} as const;
const SCOPE_ID_FLAG = {
scopeId: {
type: "string",
valueHint: "<id>",
description: { "en-US": "Filter by scope ID", "zh-CN": "按 Scope ID 筛选" },
},
} as const;
const UPLOAD_FLAGS = {
...API_TARGET_FLAGS,
path: {
type: "string",
valueHint: "<path>",
required: true,
description: { "en-US": "Local file path", "zh-CN": "本地文件路径" },
},
filename: {
type: "string",
valueHint: "<name>",
description: { "en-US": "Remote filename override", "zh-CN": "覆盖远端文件名" },
},
mimeType: {
type: "string",
valueHint: "<type>",
description: { "en-US": "MIME type override", "zh-CN": "覆盖 MIME 类型" },
},
purpose: {
type: "string",
valueHint: "<purpose>",
description: { "en-US": "Provider upload purpose", "zh-CN": "Provider 上传用途" },
},
} as const;
const LIST_FLAGS = { ...API_TARGET_FLAGS, ...CURSOR_FLAGS, ...SCOPE_ID_FLAG };
const SEARCH_RESOURCE_FLAGS = {
...API_TARGET_FLAGS,
limit: CURSOR_FLAGS.limit,
...SEARCH_FLAGS,
...SCOPE_ID_FLAG,
};
const GET_FLAGS = { ...API_TARGET_FLAGS, ...FILE_ID_FLAG };
const DOWNLOAD_FLAGS = {
...GET_FLAGS,
outputFile: {
type: "string",
valueHint: "<path>",
required: true,
description: { "en-US": "Destination path", "zh-CN": "目标路径" },
},
force: {
type: "switch",
description: { "en-US": "Overwrite an existing output file", "zh-CN": "覆盖已存在的输出文件" },
},
} as const;
const DELETE_FLAGS = {
...GET_FLAGS,
yes: {
type: "switch",
description: { "en-US": "Confirm permanent file deletion", "zh-CN": "确认永久删除文件" },
},
} as const;
function fileRows(files: ProviderFileInfo[]): string[][] {
return files.map((file) => [
file.id,
displayValue(file.filename),
displayValue(file.status),
String(file.size_bytes),
displayValue(file.scope?.id),
displayValue(file.created_at),
]);
}
export const managedAgentFileUpload = defineCommand({
description: { "en-US": "Upload a Managed Agent file", "zh-CN": "上传托管 Agent 文件" },
auth: "apiKey",
usageArgs: "--path <path> [--filename <name>] [--mime-type <type>] [--purpose <purpose>]",
flags: UPLOAD_FLAGS,
exampleArgs: ["--path ./report.pdf", "--path ./data.json --purpose assistants"],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
if (ctx.settings.dryRun) {
emitResult(
{
would_upload_file: ctx.flags.path,
filename: ctx.flags.filename ?? basename(ctx.flags.path),
mime_type: ctx.flags.mimeType ?? inferMimeType(ctx.flags.path),
purpose: ctx.flags.purpose,
},
format,
);
return;
}
const content = await readInputFile(ctx.flags.path);
const file = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return uploadFile(runtime, content, ctx.flags.filename ?? basename(ctx.flags.path), {
provider: ctx.flags.provider,
mimeType: ctx.flags.mimeType ?? inferMimeType(ctx.flags.path),
purpose: ctx.flags.purpose,
});
}),
);
if (format === "json") emitResult(file, format);
else emitBare(`File uploaded: ${file.id} (${file.filename})`);
},
});
export const managedAgentFileList = defineCommand({
description: { "en-US": "List Managed Agent files", "zh-CN": "列出托管 Agent 文件" },
auth: "apiKey",
usageArgs: "[--scope-id <id>] [--limit <n>] [--page <cursor>] [--all]",
flags: LIST_FLAGS,
exampleArgs: ["", "--scope-id sess_abc --all --output json"],
notes: CREDENTIALS_NOTE,
validate: validateLimitAndPageLimit,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
const result = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return fetchAllPages(
async (page) => {
const response = await listRemoteFiles(runtime, {
provider: ctx.flags.provider,
scope_id: ctx.flags.scopeId,
limit: ctx.flags.limit,
page,
});
return {
items: response.data,
hasMore: response.has_more,
nextPage: response.next_page,
};
},
ctx.flags.all,
ctx.flags.page,
);
}),
);
emitCollection({
format,
key: "files",
items: result.items,
headers: ["ID", "FILENAME", "STATUS", "BYTES", "SCOPE", "CREATED"],
rows: fileRows(result.items),
hasMore: result.hasMore,
nextPage: result.nextPage,
emptyMessage: "No files found.",
});
},
});
export const managedAgentFileGet = defineCommand({
description: { "en-US": "Get Managed Agent file metadata", "zh-CN": "获取托管 Agent 文件元数据" },
auth: "apiKey",
usageArgs: "--file-id <id>",
flags: GET_FLAGS,
exampleArgs: ["--file-id file_abc"],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
const file = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return getFileInfo(runtime, ctx.flags.fileId, { provider: ctx.flags.provider });
}),
);
if (format === "json") emitResult(file, format);
else {
emitBare(`ID: ${file.id}`);
emitBare(`Filename: ${file.filename}`);
emitBare(`MIME: ${file.mime_type}`);
emitBare(`Bytes: ${file.size_bytes}`);
emitBare(`Status: ${displayValue(file.status)}`);
emitBare(`Scope: ${displayValue(file.scope?.id)}`);
}
},
});
export const managedAgentFileSearch = defineCommand({
description: { "en-US": "Search Managed Agent files", "zh-CN": "搜索托管 Agent 文件" },
auth: "apiKey",
usageArgs: "--query <text> [--scope-id <id>] [--limit <n>] [--page-limit <n>]",
flags: SEARCH_RESOURCE_FLAGS,
exampleArgs: ["--query report", "--query pdf --scope-id sess_abc --output json"],
notes: CREDENTIALS_NOTE,
validate: validateLimitAndPageLimit,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
const result = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return searchCursorPages(
async (page) => {
const response = await listRemoteFiles(runtime, {
provider: ctx.flags.provider,
scope_id: ctx.flags.scopeId,
limit: ctx.flags.limit ?? 100,
page,
});
return {
items: response.data,
hasMore: response.has_more,
nextPage: response.next_page,
};
},
(file) => matchesQuery(ctx.flags.query, file.id, file.filename, file.mime_type),
ctx.flags.pageLimit,
);
}),
);
emitCollection({
format,
key: "files",
items: result.items,
headers: ["ID", "FILENAME", "STATUS", "BYTES", "SCOPE", "CREATED"],
rows: fileRows(result.items),
hasMore: result.hasMore,
nextPage: result.nextPage,
truncated: result.truncated,
scannedPages: result.scannedPages,
emptyMessage: "No matching files found.",
});
},
});
export const managedAgentFileDownload = defineCommand({
description: {
"en-US": "Download Managed Agent file content",
"zh-CN": "下载托管 Agent 文件内容",
},
auth: "apiKey",
usageArgs: "--file-id <id> --output-file <path> [--force]",
flags: DOWNLOAD_FLAGS,
exampleArgs: ["--file-id file_abc --output-file ./artifact.pdf"],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
if (ctx.settings.dryRun) {
emitResult(
{ would_download_file: ctx.flags.fileId, output_file: ctx.flags.outputFile },
format,
);
return;
}
const content = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return downloadRemoteFile(runtime, ctx.flags.fileId, { provider: ctx.flags.provider });
}),
);
const outputFile = await writeOutputFile(ctx.flags.outputFile, content, ctx.flags.force);
if (format === "json")
emitResult({ downloaded: ctx.flags.fileId, output_file: outputFile }, format);
else emitBare(`File downloaded to ${outputFile}`);
},
});
export const managedAgentFileDelete = defineCommand({
description: { "en-US": "Delete a Managed Agent file", "zh-CN": "删除托管 Agent 文件" },
auth: "apiKey",
usageArgs: "--file-id <id> --yes",
flags: DELETE_FLAGS,
exampleArgs: ["--file-id file_abc --dry-run", "--file-id file_abc --yes"],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
if (ctx.settings.dryRun) {
emitResult({ would_delete_file: ctx.flags.fileId }, format);
return;
}
if (!ctx.flags.yes) {
throw new BailianError(
`Refusing to delete file ${ctx.flags.fileId} without confirmation.`,
ExitCode.USAGE,
"Re-run with --yes or preview with --dry-run.",
);
}
await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
await deleteFile(runtime, ctx.flags.fileId, { provider: ctx.flags.provider });
}),
);
if (format === "json") emitResult({ deleted: ctx.flags.fileId }, format);
else emitBare(`File ${ctx.flags.fileId} deleted.`);
},
});
@@ -0,0 +1,317 @@
import {
getManagedAgentProviderCapabilities,
getSession,
listRemoteFiles,
listSessionEvents,
type ProjectRuntimeContext,
type ProviderFileInfo,
type ProviderSessionEvent,
type ProviderSessionInfo,
} from "@openagentpack/sdk";
import { sanitizeSessionEvents } from "@openagentpack/sdk/session-events";
import { defineCommand, detectOutputFormat } from "bailian-cli-core";
import { emitBare, emitResult } from "bailian-cli-runtime";
import { API_TARGET_FLAGS } from "./_engine/api-helpers.ts";
import { buildAgentRuntime, CREDENTIALS_NOTE } from "./_engine/config-loader.ts";
import { withStdoutProtected } from "./_engine/console-capture.ts";
import { withAgentErrors } from "./_engine/errors.ts";
import { writeOutputFile } from "./_engine/output-file.ts";
import { fetchAllPages } from "./_engine/pagination.ts";
const SESSION_ID_FLAG = {
sessionId: {
type: "string",
valueHint: "<id>",
required: true,
description: { "en-US": "Session ID", "zh-CN": "Session ID" },
},
} as const;
const DEBUG_FLAGS = { ...API_TARGET_FLAGS, ...SESSION_ID_FLAG };
const EXPORT_FLAGS = {
...DEBUG_FLAGS,
outputFile: {
type: "string",
valueHint: "<path>",
required: true,
description: { "en-US": "Destination ZIP path", "zh-CN": "目标 ZIP 路径" },
},
force: {
type: "switch",
description: { "en-US": "Overwrite an existing output file", "zh-CN": "覆盖已存在的输出文件" },
},
} as const;
interface SessionDiagnosticBundle {
session: ProviderSessionInfo;
events: unknown[];
files: ProviderFileInfo[];
capabilities: ReturnType<typeof getManagedAgentProviderCapabilities>;
errors: Array<{ component: "events" | "files"; message: string }>;
}
function errorMessage(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
async function collectSessionDiagnostics(
runtime: ProjectRuntimeContext,
sessionId: string,
provider?: string,
): Promise<SessionDiagnosticBundle> {
// Session lookup is the identity anchor. If it fails, the aggregate is not meaningful.
const session = await getSession(runtime, sessionId, provider);
const providerName =
provider ??
(runtime.providers.size === 1 ? Array.from(runtime.providers.keys())[0]! : "bailian");
const errors: SessionDiagnosticBundle["errors"] = [];
let events: ProviderSessionEvent[] = [];
let files: ProviderFileInfo[] = [];
try {
const result = await fetchAllPages(async (page) => {
const response = await listSessionEvents(runtime, sessionId, {
provider,
limit: 100,
page_token: page,
order: "asc",
});
return { items: response.events, hasMore: response.has_more, nextPage: response.next_page };
}, true);
events = result.items;
} catch (error) {
errors.push({ component: "events", message: errorMessage(error) });
}
try {
const result = await fetchAllPages(async (page) => {
const response = await listRemoteFiles(runtime, {
provider,
scope_id: sessionId,
limit: 100,
page,
});
return { items: response.data, hasMore: response.has_more, nextPage: response.next_page };
}, true);
files = result.items;
} catch (error) {
errors.push({ component: "files", message: errorMessage(error) });
}
return {
session,
events: sanitizeSessionEvents(events),
files,
capabilities: getManagedAgentProviderCapabilities(providerName),
errors,
};
}
const SENSITIVE_KEY =
/(api[_-]?key|access[_-]?key|secret|token|authorization|credential|password)/i;
export function redactSensitiveValues(value: unknown): unknown {
if (Array.isArray(value)) return value.map(redactSensitiveValues);
if (!value || typeof value !== "object") return value;
return Object.fromEntries(
Object.entries(value).map(([key, entry]) => [
key,
SENSITIVE_KEY.test(key) ? "[REDACTED]" : redactSensitiveValues(entry),
]),
);
}
function jsonBytes(value: unknown): Uint8Array {
return new TextEncoder().encode(`${JSON.stringify(redactSensitiveValues(value), null, 2)}\n`);
}
// Minimal ZIP writer using stored (uncompressed) entries. This avoids shelling out
// and keeps export deterministic across supported Node runtimes.
function crc32(bytes: Uint8Array): number {
let checksum = 0xffffffff;
for (const byte of bytes) {
checksum ^= byte;
for (let bit = 0; bit < 8; bit += 1) {
checksum = (checksum >>> 1) ^ (checksum & 1 ? 0xedb88320 : 0);
}
}
return (checksum ^ 0xffffffff) >>> 0;
}
function concatBytes(parts: Uint8Array[]): Uint8Array {
const total = parts.reduce((sum, part) => sum + part.length, 0);
const output = new Uint8Array(total);
let offset = 0;
for (const part of parts) {
output.set(part, offset);
offset += part.length;
}
return output;
}
function littleEndian(values: Array<[number, number]>): Uint8Array {
const size = values.reduce((sum, [, bytes]) => sum + bytes, 0);
const buffer = new ArrayBuffer(size);
const view = new DataView(buffer);
let offset = 0;
for (const [value, bytes] of values) {
if (bytes === 2) view.setUint16(offset, value, true);
else view.setUint32(offset, value, true);
offset += bytes;
}
return new Uint8Array(buffer);
}
export function createZip(entries: Array<{ name: string; content: Uint8Array }>): Uint8Array {
const encoder = new TextEncoder();
const localParts: Uint8Array[] = [];
const centralParts: Uint8Array[] = [];
let localOffset = 0;
for (const entry of entries) {
const name = encoder.encode(entry.name);
const checksum = crc32(entry.content);
const localHeader = littleEndian([
[0x04034b50, 4],
[20, 2],
[0, 2],
[0, 2],
[0, 2],
[0, 2],
[checksum, 4],
[entry.content.length, 4],
[entry.content.length, 4],
[name.length, 2],
[0, 2],
]);
const local = concatBytes([localHeader, name, entry.content]);
localParts.push(local);
const centralHeader = littleEndian([
[0x02014b50, 4],
[20, 2],
[20, 2],
[0, 2],
[0, 2],
[0, 2],
[0, 2],
[checksum, 4],
[entry.content.length, 4],
[entry.content.length, 4],
[name.length, 2],
[0, 2],
[0, 2],
[0, 2],
[0, 2],
[0, 4],
[localOffset, 4],
]);
centralParts.push(concatBytes([centralHeader, name]));
localOffset += local.length;
}
const central = concatBytes(centralParts);
const end = littleEndian([
[0x06054b50, 4],
[0, 2],
[0, 2],
[entries.length, 2],
[entries.length, 2],
[central.length, 4],
[localOffset, 4],
[0, 2],
]);
return concatBytes([...localParts, central, end]);
}
export const managedAgentSessionDebug = defineCommand({
description: { "en-US": "Aggregate session diagnostics", "zh-CN": "聚合 Session 诊断信息" },
auth: "apiKey",
usageArgs: "--session-id <id>",
flags: DEBUG_FLAGS,
exampleArgs: ["--session-id sess_abc", "--session-id sess_abc --output json"],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
const bundle = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return collectSessionDiagnostics(runtime, ctx.flags.sessionId, ctx.flags.provider);
}),
);
const redacted = redactSensitiveValues(bundle);
if (format === "json") {
emitResult(redacted, format);
return;
}
emitBare(`Session: ${bundle.session.id} (${bundle.session.status})`);
emitBare(`Events: ${bundle.events.length}`);
emitBare(`Scoped files: ${bundle.files.length}`);
emitBare(`Partial errors: ${bundle.errors.length}`);
for (const error of bundle.errors) emitBare(` ${error.component}: ${error.message}`);
},
});
export const managedAgentSessionExport = defineCommand({
description: {
"en-US": "Export session diagnostics as a ZIP",
"zh-CN": "将 Session 诊断信息导出为 ZIP",
},
auth: "apiKey",
usageArgs: "--session-id <id> --output-file <path> [--force]",
flags: EXPORT_FLAGS,
exampleArgs: ["--session-id sess_abc --output-file ./session-debug.zip"],
notes: [
...CREDENTIALS_NOTE,
{
"en-US":
"The ZIP contains metadata only; file bodies and credential-like values are excluded/redacted.",
"zh-CN": "ZIP 仅包含元数据;不会包含文件正文,凭证类字段会被移除或脱敏。",
},
],
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
if (ctx.settings.dryRun) {
emitResult(
{ would_export_session: ctx.flags.sessionId, output_file: ctx.flags.outputFile },
format,
);
return;
}
const bundle = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return collectSessionDiagnostics(runtime, ctx.flags.sessionId, ctx.flags.provider);
}),
);
const manifest = {
schema_version: 1,
generated_at: new Date().toISOString(),
session_id: bundle.session.id,
partial: bundle.errors.length > 0,
counts: {
events: bundle.events.length,
files: bundle.files.length,
errors: bundle.errors.length,
},
contents: ["session.json", "events.json", "files.json", "capabilities.json", "errors.json"],
note: "File bodies are not included. Credential-like fields are redacted.",
};
const zip = createZip([
{ name: "manifest.json", content: jsonBytes(manifest) },
{ name: "session.json", content: jsonBytes(bundle.session) },
{ name: "events.json", content: jsonBytes(bundle.events) },
{ name: "files.json", content: jsonBytes(bundle.files) },
{ name: "capabilities.json", content: jsonBytes(bundle.capabilities) },
{ name: "errors.json", content: jsonBytes(bundle.errors) },
]);
const outputFile = await writeOutputFile(ctx.flags.outputFile, zip, ctx.flags.force);
if (format === "json") {
emitResult(
{ exported: bundle.session.id, output_file: outputFile, partial: manifest.partial },
format,
);
} else {
emitBare(
`Session diagnostics exported to ${outputFile}${manifest.partial ? " (partial)" : ""}`,
);
}
},
});
@@ -1,4 +1,10 @@
import { defineCommand, detectOutputFormat, type FlagsDef } from "bailian-cli-core";
import {
BailianError,
defineCommand,
detectOutputFormat,
ExitCode,
type FlagsDef,
} from "bailian-cli-core";
import { emitBare, emitResult } from "bailian-cli-runtime";
import { deleteSession } from "@openagentpack/sdk";
import { buildAgentRuntime, CREDENTIALS_NOTE } from "./_engine/config-loader.ts";
@@ -25,12 +31,16 @@ const SESSION_DELETE_FLAGS = {
valueHint: "<name>",
description: { "en-US": "Target provider", "zh-CN": "目标 Provider" },
},
yes: {
type: "switch",
description: { "en-US": "Confirm permanent session deletion", "zh-CN": "确认永久删除 Session" },
},
} satisfies FlagsDef;
export default defineCommand({
description: { "en-US": "Delete a session", "zh-CN": "删除 Session" },
auth: "apiKey",
usageArgs: "--session-id <id> [--provider <name>] [--file <path>]",
usageArgs: "--session-id <id> --yes [--provider <name>] [--file <path>]",
flags: SESSION_DELETE_FLAGS,
exampleArgs: ["--session-id sess_abc123"],
notes: CREDENTIALS_NOTE,
@@ -51,6 +61,14 @@ export default defineCommand({
return;
}
if (!flags.yes) {
throw new BailianError(
`Refusing to delete session ${flags.sessionId} without confirmation.`,
ExitCode.USAGE,
"Re-run with --yes or preview with --dry-run.",
);
}
await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, file);
@@ -0,0 +1,249 @@
import type { ProviderSessionEvent, SessionEventInput } from "@openagentpack/sdk";
import {
isTerminalSessionStatus,
listSessionEvents,
sendRemoteSessionEvents,
streamSessionEvents,
} from "@openagentpack/sdk";
import { sanitizeSessionEvents } from "@openagentpack/sdk/session-events";
import { BailianError, defineCommand, detectOutputFormat, ExitCode } from "bailian-cli-core";
import { emitBare, emitResult } from "bailian-cli-runtime";
import {
API_TARGET_FLAGS,
CURSOR_FLAGS,
displayValue,
emitCollection,
splitCommaSeparated,
validateLimitAndPageLimit,
} from "./_engine/api-helpers.ts";
import { buildAgentRuntime, CREDENTIALS_NOTE } from "./_engine/config-loader.ts";
import { withStdoutProtected } from "./_engine/console-capture.ts";
import { withAgentErrors } from "./_engine/errors.ts";
import { readJsonInput } from "./_engine/output-file.ts";
import { fetchAllPages } from "./_engine/pagination.ts";
const SESSION_ID_FLAG = {
sessionId: {
type: "string",
valueHint: "<id>",
required: true,
description: { "en-US": "Session ID", "zh-CN": "Session ID" },
},
} as const;
const EVENT_LIST_FLAGS = {
...API_TARGET_FLAGS,
...SESSION_ID_FLAG,
...CURSOR_FLAGS,
order: {
type: "string",
valueHint: "<order>",
choices: ["asc", "desc"] as const,
description: { "en-US": "Event order: asc or desc", "zh-CN": "事件顺序:asc 或 desc" },
},
types: {
type: "string",
valueHint: "<types>",
description: {
"en-US": "Comma-separated raw event types",
"zh-CN": "原始事件类型,多个以逗号分隔",
},
},
createdAtGte: {
type: "string",
valueHint: "<timestamp>",
description: {
"en-US": "Created at or after this timestamp",
"zh-CN": "创建时间不早于该时间戳",
},
},
createdAtLte: {
type: "string",
valueHint: "<timestamp>",
description: {
"en-US": "Created at or before this timestamp",
"zh-CN": "创建时间不晚于该时间戳",
},
},
} as const;
const EVENT_SEND_FLAGS = {
...API_TARGET_FLAGS,
...SESSION_ID_FLAG,
event: {
type: "string",
valueHint: "<json|@path>",
required: true,
description: {
"en-US": "Raw event object/array as JSON or @event.json",
"zh-CN": "原始事件对象/数组 JSON,或 @event.json",
},
},
} as const;
const EVENT_STREAM_FLAGS = {
...API_TARGET_FLAGS,
...SESSION_ID_FLAG,
afterId: {
type: "string",
valueHint: "<event-id>",
description: { "en-US": "Resume after this event ID", "zh-CN": "从该 Event ID 之后继续" },
},
} as const;
function eventRows(events: ProviderSessionEvent[]): string[][] {
return events.map((event) => [
displayValue(event.id),
event.raw_type,
displayValue(event.session_thread_id),
displayValue(event.content ?? event.status ?? event.tool_name, 70),
]);
}
function normalizeEventInput(value: unknown): SessionEventInput[] {
const values = Array.isArray(value) ? value : [value];
if (
values.length === 0 ||
values.some((entry) => !entry || typeof entry !== "object" || Array.isArray(entry))
) {
throw new BailianError(
"Event input must be a JSON object or a non-empty array of objects.",
ExitCode.USAGE,
);
}
return values as SessionEventInput[];
}
export const managedAgentSessionEventList = defineCommand({
description: {
"en-US": "List events for a Managed Agent session",
"zh-CN": "列出托管 Agent Session 事件",
},
auth: "apiKey",
usageArgs:
"--session-id <id> [--types <types>] [--order asc|desc] [--limit <n>] [--page <cursor>] [--all]",
flags: EVENT_LIST_FLAGS,
exampleArgs: ["--session-id sess_abc", "--session-id sess_abc --all --output json"],
notes: [
...CREDENTIALS_NOTE,
{
"en-US": "--types is applied client-side to each page returned by the provider.",
"zh-CN": "--types 会在客户端对 Provider 返回的每一页结果进行过滤。",
},
],
validate: validateLimitAndPageLimit,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
const result = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return fetchAllPages(
async (page) => {
const response = await listSessionEvents(runtime, ctx.flags.sessionId, {
provider: ctx.flags.provider,
limit: ctx.flags.limit,
page_token: page,
order: ctx.flags.order,
types: splitCommaSeparated(ctx.flags.types),
created_at_gte: ctx.flags.createdAtGte,
created_at_lte: ctx.flags.createdAtLte,
});
return {
items: response.events,
hasMore: response.has_more,
nextPage: response.next_page,
};
},
ctx.flags.all,
ctx.flags.page,
);
}),
);
emitCollection({
format,
key: "events",
items: sanitizeSessionEvents(result.items),
headers: ["ID", "TYPE", "THREAD", "CONTENT"],
rows: eventRows(result.items),
hasMore: result.hasMore,
nextPage: result.nextPage,
emptyMessage: "No events found.",
});
},
});
export const managedAgentSessionEventSend = defineCommand({
description: {
"en-US": "Send raw events to a Managed Agent session",
"zh-CN": "向托管 Agent Session 发送原始事件",
},
auth: "apiKey",
usageArgs: "--session-id <id> --event <json|@path>",
flags: EVENT_SEND_FLAGS,
exampleArgs: [
'--session-id sess_abc --event \'{"type":"message","role":"user","content":"hello"}\'',
"--session-id sess_abc --event @event.json",
],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
const events = normalizeEventInput(await readJsonInput(ctx.flags.event));
if (ctx.settings.dryRun) {
emitResult({ would_send_events: events, session_id: ctx.flags.sessionId }, format);
return;
}
const result = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return sendRemoteSessionEvents(runtime, ctx.flags.sessionId, events, {
provider: ctx.flags.provider,
});
}),
);
if (format === "json") emitResult({ session_id: ctx.flags.sessionId, ...result }, format);
else emitBare(`Sent ${events.length} event(s): ${result.event_ids.join(", ")}`);
},
});
export const managedAgentSessionEventStream = defineCommand({
description: {
"en-US": "Stream events from a Managed Agent session",
"zh-CN": "流式读取托管 Agent Session 事件",
},
auth: "apiKey",
usageArgs: "--session-id <id> [--after-id <event-id>]",
flags: EVENT_STREAM_FLAGS,
exampleArgs: ["--session-id sess_abc", "--session-id sess_abc --after-id evt_123 --output json"],
notes: [
...CREDENTIALS_NOTE,
{
"en-US":
"When the provider has no native event cursor, --after-id resumes through paginated history polling and event ID de-duplication.",
"zh-CN":
"当 Provider 不支持原生事件 Cursor 时,--after-id 会通过分页历史轮询和 Event ID 去重实现续传。",
},
],
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
const events: ProviderSessionEvent[] = [];
for await (const event of streamSessionEvents(runtime, ctx.flags.sessionId, {
provider: ctx.flags.provider,
after_id: ctx.flags.afterId,
})) {
events.push(event);
if (format !== "json") emitBare(JSON.stringify(sanitizeSessionEvents([event])[0]));
if (event.type === "status" && isTerminalSessionStatus(event.status)) break;
}
if (format === "json") {
emitResult(
{ session_id: ctx.flags.sessionId, events: sanitizeSessionEvents(events) },
format,
);
}
}),
);
},
});
@@ -1,98 +1,2 @@
import { defineCommand, detectOutputFormat, type FlagsDef } from "bailian-cli-core";
import { emitBare, emitResult, formatTable } from "bailian-cli-runtime";
import { listSessionEvents } from "@openagentpack/sdk";
import { sanitizeSessionEvents } from "@openagentpack/sdk/session-events";
import { buildAgentRuntime, CREDENTIALS_NOTE } from "./_engine/config-loader.ts";
import { withStdoutProtected } from "./_engine/console-capture.ts";
import { withAgentErrors } from "./_engine/errors.ts";
import { fetchAllPages } from "./_engine/pagination.ts";
const SESSION_EVENTS_FLAGS = {
sessionId: {
type: "string",
valueHint: "<id>",
description: { "en-US": "Session ID (required)", "zh-CN": "Session ID(必填)" },
required: true,
},
file: {
type: "string",
valueHint: "<path>",
description: {
"en-US": "Config file path (default: agents.yaml)",
"zh-CN": "配置文件路径(默认:agents.yaml)",
},
},
provider: {
type: "string",
valueHint: "<name>",
description: { "en-US": "Target provider", "zh-CN": "目标 Provider" },
},
limit: {
type: "number",
valueHint: "<n>",
description: { "en-US": "Maximum number of events to fetch", "zh-CN": "要获取的最大事件数" },
},
all: {
type: "switch",
description: {
"en-US": "Fetch all pages by following the cursor",
"zh-CN": "跟随 Cursor 获取全部分页",
},
},
} satisfies FlagsDef;
export default defineCommand({
description: { "en-US": "List event history for a session", "zh-CN": "列出 Session 的事件历史" },
auth: "apiKey",
usageArgs: "--session-id <id> [--limit <n>] [--all] [--file <path>]",
flags: SESSION_EVENTS_FLAGS,
exampleArgs: ["--session-id sess_abc123", "--session-id sess_abc123 --all"],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const { settings, flags } = ctx;
const format = detectOutputFormat(settings.output);
const file = flags.file ?? "agents.yaml";
const { items: events, hasMore } = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, file);
return fetchAllPages(async (page) => {
const result = await listSessionEvents(runtime, flags.sessionId, {
provider: flags.provider,
limit: flags.limit,
page_token: page,
});
return {
items: result.events,
hasMore: result.has_more,
nextPage: result.next_page,
};
}, flags.all);
}),
);
if (format === "json") {
emitResult({ events: sanitizeSessionEvents(events), has_more: hasMore }, format);
return;
}
if (events.length === 0) {
emitBare("No events found.");
return;
}
const headers = ["#", "TYPE", "CONTENT"];
const rows = events.map((event, index) => {
let preview = "";
if (event.type === "message") preview = (event.content ?? "").slice(0, 60);
else if (event.type === "tool_use") preview = event.tool_name ?? "";
else if (event.type === "tool_result") preview = (event.content ?? "").slice(0, 60);
else if (event.type === "status") preview = event.status ?? "";
else if (event.type === "error") preview = (event.content ?? "").slice(0, 60);
else preview = event.raw_type;
return [String(index + 1), event.type, preview];
});
for (const line of formatTable(headers, rows)) emitBare(line);
emitBare(`\nTotal: ${events.length}`);
if (hasMore) emitBare("More events available. Use --all to fetch all.");
},
});
// Backward-compatible alias for `managed-agent session event list`.
export { managedAgentSessionEventList as default } from "./session-event.ts";
@@ -5,6 +5,11 @@ import { buildAgentRuntime, CREDENTIALS_NOTE } from "./_engine/config-loader.ts"
import { withStdoutProtected } from "./_engine/console-capture.ts";
import { withAgentErrors } from "./_engine/errors.ts";
import { fetchAllPages } from "./_engine/pagination.ts";
import {
CURSOR_FLAGS,
splitCommaSeparated,
validateLimitAndPageLimit,
} from "./_engine/api-helpers.ts";
const SESSION_LIST_FLAGS = {
file: {
@@ -27,6 +32,32 @@ const SESSION_LIST_FLAGS = {
"zh-CN": "跟随 Cursor 获取全部分页",
},
},
limit: CURSOR_FLAGS.limit,
page: CURSOR_FLAGS.page,
statuses: {
type: "string",
valueHint: "<statuses>",
description: {
"en-US": "Comma-separated session statuses",
"zh-CN": "Session 状态,多个以逗号分隔",
},
},
createdAtGte: {
type: "string",
valueHint: "<timestamp>",
description: {
"en-US": "Created at or after this RFC 3339 timestamp",
"zh-CN": "创建时间不早于该 RFC 3339 时间戳",
},
},
createdAtLte: {
type: "string",
valueHint: "<timestamp>",
description: {
"en-US": "Created at or before this RFC 3339 timestamp",
"zh-CN": "创建时间不晚于该 RFC 3339 时间戳",
},
},
provider: {
type: "string",
valueHint: "<name>",
@@ -40,36 +71,51 @@ export default defineCommand({
"zh-CN": "列出 Provider 中的 Session",
},
auth: "apiKey",
usageArgs: "[--agent <name>] [--all] [--provider <name>] [--file <path>]",
usageArgs: "[--agent <name>] [--statuses <statuses>] [--limit <n>] [--page <cursor>] [--all]",
flags: SESSION_LIST_FLAGS,
exampleArgs: ["", "--agent assistant", "--all"],
notes: CREDENTIALS_NOTE,
validate: validateLimitAndPageLimit,
async run(ctx) {
const { settings, flags } = ctx;
const format = detectOutputFormat(settings.output);
const file = flags.file ?? "agents.yaml";
const { items: summaries, hasMore } = await withAgentErrors(() =>
const {
items: summaries,
hasMore,
nextPage,
} = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, file);
return fetchAllPages(async (page) => {
const result = await listSessionSummaries(runtime, {
agent: flags.agent,
provider: flags.provider,
filter: page ? { page } : undefined,
});
return {
items: result.summaries,
hasMore: result.hasMore,
nextPage: result.nextPage,
};
}, flags.all);
return fetchAllPages(
async (page) => {
const result = await listSessionSummaries(runtime, {
agent: flags.agent,
provider: flags.provider,
filter: {
page,
limit: flags.limit,
statuses: splitCommaSeparated(flags.statuses),
created_at_gte: flags.createdAtGte,
created_at_lte: flags.createdAtLte,
},
});
return {
items: result.summaries,
hasMore: result.hasMore,
nextPage: result.nextPage,
};
},
flags.all,
flags.page,
);
}),
);
const sessions = summaries.map((summary) => summary.session);
if (format === "json") {
emitResult({ sessions, has_more: hasMore }, format);
emitResult({ sessions, has_more: hasMore, next_page: nextPage }, format);
return;
}
if (sessions.length === 0) {
@@ -0,0 +1,246 @@
import type { ProviderSessionInfo } from "@openagentpack/sdk";
import {
archiveRemoteSession,
listSessionSummaries,
updateRemoteSession,
} from "@openagentpack/sdk";
import { BailianError, defineCommand, detectOutputFormat, ExitCode } from "bailian-cli-core";
import { emitBare, emitResult } from "bailian-cli-runtime";
import {
API_TARGET_FLAGS,
CURSOR_FLAGS,
displayValue,
emitCollection,
matchesQuery,
SEARCH_FLAGS,
searchCursorPages,
splitCommaSeparated,
validateLimitAndPageLimit,
} from "./_engine/api-helpers.ts";
import { buildAgentRuntime, CREDENTIALS_NOTE } from "./_engine/config-loader.ts";
import { withStdoutProtected } from "./_engine/console-capture.ts";
import { withAgentErrors } from "./_engine/errors.ts";
import { readJsonInput } from "./_engine/output-file.ts";
const SESSION_ID_FLAG = {
sessionId: {
type: "string",
valueHint: "<id>",
required: true,
description: { "en-US": "Session ID", "zh-CN": "Session ID" },
},
} as const;
const SEARCH_RESOURCE_FLAGS = {
...API_TARGET_FLAGS,
limit: CURSOR_FLAGS.limit,
...SEARCH_FLAGS,
agent: {
type: "string",
valueHint: "<name>",
description: {
"en-US": "Filter by configured agent name",
"zh-CN": "按配置中的 Agent 名称筛选",
},
},
statuses: {
type: "string",
valueHint: "<statuses>",
description: {
"en-US": "Comma-separated session statuses",
"zh-CN": "Session 状态,多个以逗号分隔",
},
},
createdAtGte: {
type: "string",
valueHint: "<timestamp>",
description: {
"en-US": "Created at or after this timestamp",
"zh-CN": "创建时间不早于该时间戳",
},
},
createdAtLte: {
type: "string",
valueHint: "<timestamp>",
description: {
"en-US": "Created at or before this timestamp",
"zh-CN": "创建时间不晚于该时间戳",
},
},
} as const;
const UPDATE_FLAGS = {
...API_TARGET_FLAGS,
...SESSION_ID_FLAG,
title: {
type: "string",
valueHint: "<title>",
description: { "en-US": "New session title", "zh-CN": "新的 Session 标题" },
},
metadata: {
type: "string",
valueHint: "<json|@path>",
description: {
"en-US": "String-valued metadata JSON or @file",
"zh-CN": "值为字符串的 Metadata JSON 或 @file",
},
},
} as const;
const ARCHIVE_FLAGS = {
...API_TARGET_FLAGS,
...SESSION_ID_FLAG,
yes: {
type: "switch",
description: { "en-US": "Confirm session archive", "zh-CN": "确认归档 Session" },
},
} as const;
function sessionRows(sessions: ProviderSessionInfo[]): string[][] {
return sessions.map((session) => [
session.id,
displayValue(session.title),
displayValue(session.agent_id),
session.status,
displayValue(session.updated_at),
]);
}
async function parseMetadata(value?: string): Promise<Record<string, string> | undefined> {
if (!value) return undefined;
const parsed = await readJsonInput(value);
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
throw new BailianError("--metadata must be a JSON object.", ExitCode.USAGE);
}
for (const [key, entry] of Object.entries(parsed)) {
if (typeof entry !== "string") {
throw new BailianError(`Metadata value for '${key}' must be a string.`, ExitCode.USAGE);
}
}
return parsed as Record<string, string>;
}
export const managedAgentSessionSearch = defineCommand({
description: { "en-US": "Search Managed Agent sessions", "zh-CN": "搜索托管 Agent Session" },
auth: "apiKey",
usageArgs:
"--query <text> [--agent <name>] [--statuses <statuses>] [--limit <n>] [--page-limit <n>]",
flags: SEARCH_RESOURCE_FLAGS,
exampleArgs: ["--query debug", "--query failed --statuses failed --output json"],
notes: CREDENTIALS_NOTE,
validate: validateLimitAndPageLimit,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
const result = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return searchCursorPages(
async (page) => {
const response = await listSessionSummaries(runtime, {
agent: ctx.flags.agent,
provider: ctx.flags.provider,
filter: {
page,
limit: ctx.flags.limit ?? 100,
statuses: splitCommaSeparated(ctx.flags.statuses),
created_at_gte: ctx.flags.createdAtGte,
created_at_lte: ctx.flags.createdAtLte,
},
});
return {
items: response.summaries,
hasMore: response.hasMore,
nextPage: response.nextPage,
};
},
(summary) =>
matchesQuery(
ctx.flags.query,
summary.session.id,
summary.session.title,
summary.session.status,
summary.session.agent_id,
summary.agentName,
),
ctx.flags.pageLimit,
);
}),
);
const sessions = result.items.map((summary) => summary.session);
emitCollection({
format,
key: "sessions",
items: sessions,
headers: ["ID", "TITLE", "AGENT", "STATUS", "UPDATED"],
rows: sessionRows(sessions),
hasMore: result.hasMore,
nextPage: result.nextPage,
truncated: result.truncated,
scannedPages: result.scannedPages,
emptyMessage: "No matching sessions found.",
});
},
});
export const managedAgentSessionUpdate = defineCommand({
description: { "en-US": "Update a Managed Agent session", "zh-CN": "更新托管 Agent Session" },
auth: "apiKey",
usageArgs: "--session-id <id> [--title <title>] [--metadata <json|@path>]",
flags: UPDATE_FLAGS,
exampleArgs: [
"--session-id sess_abc --title 'investigation'",
"--session-id sess_abc --metadata @metadata.json",
],
notes: CREDENTIALS_NOTE,
validate: (flags) =>
!flags.title && !flags.metadata ? "Provide --title or --metadata." : undefined,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
const input = { title: ctx.flags.title, metadata: await parseMetadata(ctx.flags.metadata) };
if (ctx.settings.dryRun) {
emitResult({ would_update_session: ctx.flags.sessionId, input }, format);
return;
}
const session = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return updateRemoteSession(runtime, ctx.flags.sessionId, input, {
provider: ctx.flags.provider,
});
}),
);
if (format === "json") emitResult(session, format);
else emitBare(`Session ${session.id} updated.`);
},
});
export const managedAgentSessionArchive = defineCommand({
description: { "en-US": "Archive a Managed Agent session", "zh-CN": "归档托管 Agent Session" },
auth: "apiKey",
usageArgs: "--session-id <id> --yes",
flags: ARCHIVE_FLAGS,
exampleArgs: ["--session-id sess_abc --dry-run", "--session-id sess_abc --yes"],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
if (ctx.settings.dryRun) {
emitResult({ would_archive_session: ctx.flags.sessionId }, format);
return;
}
if (!ctx.flags.yes) {
throw new BailianError(
`Refusing to archive session ${ctx.flags.sessionId} without confirmation.`,
ExitCode.USAGE,
"Re-run with --yes or preview with --dry-run.",
);
}
const session = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return archiveRemoteSession(runtime, ctx.flags.sessionId, { provider: ctx.flags.provider });
}),
);
if (format === "json") emitResult({ archived: ctx.flags.sessionId, session }, format);
else emitBare(`Session ${ctx.flags.sessionId} archived.`);
},
});
@@ -1,115 +1,2 @@
import { defineCommand, detectOutputFormat, type FlagsDef } from "bailian-cli-core";
import { emitBare, emitResult, formatTable } from "bailian-cli-runtime";
import { listSkills } from "@openagentpack/sdk";
import { buildAgentRuntime, CREDENTIALS_NOTE } from "./_engine/config-loader.ts";
import { withStdoutProtected } from "./_engine/console-capture.ts";
import { withAgentErrors } from "./_engine/errors.ts";
const SKILL_SOURCES = ["custom", "official", "all"] as const;
type SkillSource = (typeof SKILL_SOURCES)[number];
const SKILL_LIST_FLAGS = {
file: {
type: "string",
valueHint: "<path>",
description: {
"en-US": "Config file path (default: agents.yaml)",
"zh-CN": "配置文件路径(默认:agents.yaml)",
},
},
source: {
type: "string",
valueHint: "<source>",
description: {
"en-US":
"Skill catalog: custom (workspace-uploaded, default), official (built-in), or all (both catalogs in one call)",
"zh-CN":
"Skill Catalog:custom(Workspace 上传,默认)、official(内置)或 all(一次调用获取两类 Catalog)",
},
},
provider: {
type: "string",
valueHint: "<name>",
description: { "en-US": "Target provider", "zh-CN": "目标 Provider" },
},
} satisfies FlagsDef;
export default defineCommand({
description: {
"en-US": "List skills from the provider's skill catalog",
"zh-CN": "列出 Provider Skill Catalog 中的 Skill",
},
auth: "apiKey",
usageArgs: "[--source custom|official|all] [--provider <name>] [--file <path>]",
flags: SKILL_LIST_FLAGS,
exampleArgs: [
"",
"--source official",
"--source all --output json",
"--source custom --provider bailian",
],
notes: [
...CREDENTIALS_NOTE,
{
"en-US": "Providers without a skill listing API (e.g. ark) return an empty list.",
"zh-CN": "没有 Skill 列表 API 的 Provider(例如 ark)会返回空列表。",
},
{
"en-US":
"For agent-driven skill selection, use `--source all --output json`: one call returns both catalogs with per-skill `source` and `description` fields to pick from.",
"zh-CN":
"由 Agent 选择 Skill 时,请使用 `--source all --output json`:一次调用返回两类 Catalog,并为每个 Skill 提供 `source` 和 `description` 字段用于选择。",
},
{
"en-US":
"When generating a task that needs a suitable skill, call this command to match official or custom skills before wiring them into the task.",
"zh-CN": "生成需要合适 Skill 的任务时,先调用此命令匹配官方或自定义 Skill,再将其接入任务。",
},
],
validate: (f) =>
f.source && !SKILL_SOURCES.includes(f.source as SkillSource)
? "--source must be one of: custom, official, all."
: undefined,
async run(ctx) {
const { settings, flags } = ctx;
const format = detectOutputFormat(settings.output);
const file = flags.file ?? "agents.yaml";
const source = (flags.source as SkillSource | undefined) ?? "custom";
const skills = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, file);
if (source !== "all") {
return listSkills(runtime, { provider: flags.provider, source });
}
// Both catalogs in one call; each entry carries its own `source` field.
const [customSkills, officialSkills] = await Promise.all([
listSkills(runtime, { provider: flags.provider, source: "custom" }),
listSkills(runtime, { provider: flags.provider, source: "official" }),
]);
return [...customSkills, ...officialSkills];
}),
);
if (format === "json") {
emitResult({ source, skills }, format);
return;
}
if (skills.length === 0) {
emitBare(source === "all" ? "No skills found." : `No ${source} skills found.`);
return;
}
const headers = ["ID", "NAME", "SOURCE", "STATUS", "VERSION", "CREATED"];
const rows = skills.map((skill) => [
skill.id,
skill.name.slice(0, 32),
skill.source,
skill.status,
skill.latest_version ?? "-",
skill.created_at ?? "-",
]);
for (const line of formatTable(headers, rows)) emitBare(line);
emitBare(`\nTotal: ${skills.length} (${source})`);
},
});
// Backward-compatible alias for the API-oriented `managed-agent skill list` command.
export { managedAgentSkillList as default } from "./skill.ts";
@@ -0,0 +1,363 @@
import type { ProviderSkillInfo, SkillVersionInfo } from "@openagentpack/sdk";
import {
downloadRemoteSkill,
getRemoteSkill,
listRemoteSkills,
listRemoteSkillVersions,
} from "@openagentpack/sdk";
import { defineCommand, detectOutputFormat } from "bailian-cli-core";
import { emitBare, emitResult } from "bailian-cli-runtime";
import {
API_TARGET_FLAGS,
CURSOR_FLAGS,
displayValue,
emitCollection,
matchesQuery,
SEARCH_FLAGS,
searchCursorPages,
validateLimitAndPageLimit,
} from "./_engine/api-helpers.ts";
import { buildAgentRuntime, CREDENTIALS_NOTE } from "./_engine/config-loader.ts";
import { withStdoutProtected } from "./_engine/console-capture.ts";
import { withAgentErrors } from "./_engine/errors.ts";
import { writeOutputFile } from "./_engine/output-file.ts";
import { fetchAllPages } from "./_engine/pagination.ts";
const SKILL_SOURCES = ["custom", "official", "all"] as const;
type SkillSource = (typeof SKILL_SOURCES)[number];
const SOURCE_FLAG = {
source: {
type: "string",
valueHint: "<source>",
choices: SKILL_SOURCES,
description: {
"en-US": "Skill catalog: custom (default), official, or all",
"zh-CN": "Skill Catalog:custom(默认)、official 或 all",
},
},
} as const;
const LIST_FLAGS = { ...API_TARGET_FLAGS, ...CURSOR_FLAGS, ...SOURCE_FLAG };
const SEARCH_RESOURCE_FLAGS = {
...API_TARGET_FLAGS,
limit: CURSOR_FLAGS.limit,
...SEARCH_FLAGS,
...SOURCE_FLAG,
};
const GET_FLAGS = {
...API_TARGET_FLAGS,
skillId: {
type: "string",
valueHint: "<id>",
required: true,
description: { "en-US": "Skill ID", "zh-CN": "Skill ID" },
},
} as const;
const VERSIONS_FLAGS = {
...GET_FLAGS,
...CURSOR_FLAGS,
};
const DOWNLOAD_FLAGS = {
...GET_FLAGS,
skillVersion: {
type: "string",
valueHint: "<version>",
required: true,
description: { "en-US": "Skill version", "zh-CN": "Skill 版本" },
},
outputFile: {
type: "string",
valueHint: "<path>",
required: true,
description: { "en-US": "Destination ZIP path", "zh-CN": "目标 ZIP 路径" },
},
force: {
type: "switch",
description: { "en-US": "Overwrite an existing output file", "zh-CN": "覆盖已存在的输出文件" },
},
} as const;
function skillRows(skills: ProviderSkillInfo[]): string[][] {
return skills.map((skill) => [
skill.id,
displayValue(skill.name),
skill.source,
skill.status,
displayValue(skill.latest_version),
displayValue(skill.updated_at ?? skill.created_at),
]);
}
function versionRows(versions: SkillVersionInfo[]): string[][] {
return versions.map((version) => [
displayValue(version.version),
displayValue(version.name),
displayValue(version.type),
displayValue(version.status),
displayValue(version.updated_at ?? version.created_at),
]);
}
async function listOneCatalog(
runtime: Parameters<typeof listRemoteSkills>[0],
source: Exclude<SkillSource, "all">,
options: { provider?: string; limit?: number; page?: string; all?: boolean },
) {
return fetchAllPages(
async (page) => {
const response = await listRemoteSkills(runtime, {
provider: options.provider,
source,
limit: options.limit,
page,
});
return { items: response.data, hasMore: response.has_more, nextPage: response.next_page };
},
options.all,
options.page,
);
}
export const managedAgentSkillList = defineCommand({
description: { "en-US": "List Managed Agent skills", "zh-CN": "列出托管 Agent Skill" },
auth: "apiKey",
usageArgs: "[--source custom|official|all] [--limit <n>] [--page <cursor>] [--all]",
flags: LIST_FLAGS,
exampleArgs: ["", "--source official", "--source all --all --output json"],
notes: [
...CREDENTIALS_NOTE,
{
"en-US":
"--source all combines one page from each catalog, or every page with --all; it does not accept --page.",
"zh-CN":
"--source all 默认合并两个 Catalog 的各一页,传入 --all 时获取全部分页;该模式不接受 --page。",
},
],
validate: (flags) =>
validateLimitAndPageLimit(flags) ??
(flags.source === "all" && flags.page
? "--source all cannot be combined with --page."
: undefined),
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
const source = (ctx.flags.source as SkillSource | undefined) ?? "custom";
const result = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
if (source !== "all") {
return listOneCatalog(runtime, source, {
provider: ctx.flags.provider,
limit: ctx.flags.limit,
page: ctx.flags.page,
all: ctx.flags.all,
});
}
const [custom, official] = await Promise.all([
listOneCatalog(runtime, "custom", {
provider: ctx.flags.provider,
limit: ctx.flags.limit,
all: ctx.flags.all,
}),
listOneCatalog(runtime, "official", {
provider: ctx.flags.provider,
limit: ctx.flags.limit,
all: ctx.flags.all,
}),
]);
return {
items: [...custom.items, ...official.items],
hasMore: custom.hasMore || official.hasMore,
nextPage: undefined,
};
}),
);
emitCollection({
format,
key: "skills",
items: result.items,
headers: ["ID", "NAME", "SOURCE", "STATUS", "VERSION", "UPDATED"],
rows: skillRows(result.items),
hasMore: result.hasMore,
nextPage: result.nextPage,
emptyMessage: "No skills found.",
});
},
});
export const managedAgentSkillGet = defineCommand({
description: { "en-US": "Get a Managed Agent skill", "zh-CN": "获取托管 Agent Skill 详情" },
auth: "apiKey",
usageArgs: "--skill-id <id>",
flags: GET_FLAGS,
exampleArgs: ["--skill-id skill_abc"],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
const skill = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return getRemoteSkill(runtime, ctx.flags.skillId, { provider: ctx.flags.provider });
}),
);
if (format === "json") {
emitResult(skill, format);
return;
}
emitBare(`ID: ${skill.id}`);
emitBare(`Name: ${skill.name}`);
emitBare(`Description: ${displayValue(skill.description, 120)}`);
emitBare(`Source: ${skill.source}`);
emitBare(`Status: ${skill.status}`);
emitBare(`Version: ${displayValue(skill.latest_version)}`);
},
});
export const managedAgentSkillSearch = defineCommand({
description: { "en-US": "Search Managed Agent skills", "zh-CN": "搜索托管 Agent Skill" },
auth: "apiKey",
usageArgs: "--query <text> [--source custom|official|all] [--limit <n>] [--page-limit <n>]",
flags: SEARCH_RESOURCE_FLAGS,
exampleArgs: ["--query browser --source official", "--query report --source all --output json"],
notes: CREDENTIALS_NOTE,
validate: validateLimitAndPageLimit,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
const source = (ctx.flags.source as SkillSource | undefined) ?? "custom";
const result = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
const searchCatalog = (catalog: Exclude<SkillSource, "all">) =>
searchCursorPages(
async (page) => {
const response = await listRemoteSkills(runtime, {
provider: ctx.flags.provider,
source: catalog,
limit: ctx.flags.limit ?? 100,
page,
});
return {
items: response.data,
hasMore: response.has_more,
nextPage: response.next_page,
};
},
(skill) => matchesQuery(ctx.flags.query, skill.id, skill.name, skill.description),
ctx.flags.pageLimit,
);
if (source !== "all") return searchCatalog(source);
const [custom, official] = await Promise.all([
searchCatalog("custom"),
searchCatalog("official"),
]);
return {
items: [...custom.items, ...official.items],
hasMore: custom.hasMore || official.hasMore,
nextPage: undefined,
scannedPages: custom.scannedPages + official.scannedPages,
truncated: custom.truncated || official.truncated,
};
}),
);
emitCollection({
format,
key: "skills",
items: result.items,
headers: ["ID", "NAME", "SOURCE", "STATUS", "VERSION", "UPDATED"],
rows: skillRows(result.items),
hasMore: result.hasMore,
nextPage: result.nextPage,
truncated: result.truncated,
scannedPages: result.scannedPages,
emptyMessage: "No matching skills found.",
});
},
});
export const managedAgentSkillVersions = defineCommand({
description: {
"en-US": "List Managed Agent skill versions",
"zh-CN": "列出托管 Agent Skill 版本",
},
auth: "apiKey",
usageArgs: "--skill-id <id> [--limit <n>] [--page <cursor>] [--all]",
flags: VERSIONS_FLAGS,
exampleArgs: ["--skill-id skill_abc", "--skill-id skill_abc --all --output json"],
notes: CREDENTIALS_NOTE,
validate: validateLimitAndPageLimit,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
const result = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return fetchAllPages(
async (page) => {
const response = await listRemoteSkillVersions(runtime, ctx.flags.skillId, {
provider: ctx.flags.provider,
limit: ctx.flags.limit,
page,
});
return {
items: response.data,
hasMore: response.has_more,
nextPage: response.next_page,
};
},
ctx.flags.all,
ctx.flags.page,
);
}),
);
emitCollection({
format,
key: "versions",
items: result.items,
headers: ["VERSION", "NAME", "TYPE", "STATUS", "UPDATED"],
rows: versionRows(result.items),
hasMore: result.hasMore,
nextPage: result.nextPage,
emptyMessage: "No skill versions found.",
});
},
});
export const managedAgentSkillDownload = defineCommand({
description: {
"en-US": "Download a Managed Agent skill version",
"zh-CN": "下载托管 Agent Skill 版本",
},
auth: "apiKey",
usageArgs: "--skill-id <id> --skill-version <version> --output-file <path> [--force]",
flags: DOWNLOAD_FLAGS,
exampleArgs: ["--skill-id skill_abc --skill-version 3 --output-file ./skill.zip"],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
if (ctx.settings.dryRun) {
emitResult(
{
would_download_skill: ctx.flags.skillId,
version: ctx.flags.skillVersion,
output_file: ctx.flags.outputFile,
},
format,
);
return;
}
const content = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return downloadRemoteSkill(runtime, ctx.flags.skillId, ctx.flags.skillVersion, {
provider: ctx.flags.provider,
});
}),
);
const outputFile = await writeOutputFile(ctx.flags.outputFile, content, ctx.flags.force);
if (format === "json")
emitResult(
{ downloaded: ctx.flags.skillId, version: ctx.flags.skillVersion, output_file: outputFile },
format,
);
else emitBare(`Skill downloaded to ${outputFile}`);
},
});
@@ -0,0 +1,165 @@
import type { CloudVault } from "@openagentpack/sdk";
import { getRemoteVault, listRemoteVaults } from "@openagentpack/sdk";
import { defineCommand, detectOutputFormat } from "bailian-cli-core";
import { emitBare, emitResult } from "bailian-cli-runtime";
import {
API_TARGET_FLAGS,
CURSOR_FLAGS,
displayValue,
emitCollection,
INCLUDE_ARCHIVED_FLAG,
matchesQuery,
SEARCH_FLAGS,
searchCursorPages,
validateLimitAndPageLimit,
} from "./_engine/api-helpers.ts";
import { buildAgentRuntime, CREDENTIALS_NOTE } from "./_engine/config-loader.ts";
import { withStdoutProtected } from "./_engine/console-capture.ts";
import { withAgentErrors } from "./_engine/errors.ts";
import { fetchAllPages } from "./_engine/pagination.ts";
const LIST_FLAGS = { ...API_TARGET_FLAGS, ...CURSOR_FLAGS, ...INCLUDE_ARCHIVED_FLAG };
const SEARCH_RESOURCE_FLAGS = {
...API_TARGET_FLAGS,
limit: CURSOR_FLAGS.limit,
...SEARCH_FLAGS,
...INCLUDE_ARCHIVED_FLAG,
};
const GET_FLAGS = {
...API_TARGET_FLAGS,
vaultId: {
type: "string",
valueHint: "<id>",
required: true,
description: { "en-US": "Vault ID", "zh-CN": "Vault ID" },
},
} as const;
function vaultRows(vaults: CloudVault[]): string[][] {
return vaults.map((vault) => [
vault.id,
displayValue(vault.display_name),
displayValue(vault.type),
displayValue(vault.created_at),
displayValue(vault.updated_at),
]);
}
export const managedAgentVaultList = defineCommand({
description: { "en-US": "List Managed Agent vaults", "zh-CN": "列出托管 Agent Vault" },
auth: "apiKey",
usageArgs: "[--limit <n>] [--page <cursor>] [--all] [--include-archived]",
flags: LIST_FLAGS,
exampleArgs: ["", "--all --output json"],
notes: CREDENTIALS_NOTE,
validate: validateLimitAndPageLimit,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
const result = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return fetchAllPages(
async (page) => {
const response = await listRemoteVaults(runtime, {
provider: ctx.flags.provider,
limit: ctx.flags.limit,
page,
include_archived: ctx.flags.includeArchived,
});
return {
items: response.data,
hasMore: response.has_more,
nextPage: response.next_page,
};
},
ctx.flags.all,
ctx.flags.page,
);
}),
);
emitCollection({
format,
key: "vaults",
items: result.items,
headers: ["ID", "NAME", "TYPE", "CREATED", "UPDATED"],
rows: vaultRows(result.items),
hasMore: result.hasMore,
nextPage: result.nextPage,
emptyMessage: "No vaults found.",
});
},
});
export const managedAgentVaultGet = defineCommand({
description: { "en-US": "Get a Managed Agent vault", "zh-CN": "获取托管 Agent Vault 详情" },
auth: "apiKey",
usageArgs: "--vault-id <id>",
flags: GET_FLAGS,
exampleArgs: ["--vault-id vault_abc"],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
const vault = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return getRemoteVault(runtime, ctx.flags.vaultId, { provider: ctx.flags.provider });
}),
);
if (format === "json") {
emitResult(vault, format);
return;
}
emitBare(`ID: ${vault.id}`);
emitBare(`Name: ${displayValue(vault.display_name)}`);
emitBare(`Type: ${displayValue(vault.type)}`);
emitBare(`Created: ${displayValue(vault.created_at)}`);
emitBare(`Updated: ${displayValue(vault.updated_at)}`);
},
});
export const managedAgentVaultSearch = defineCommand({
description: { "en-US": "Search Managed Agent vaults", "zh-CN": "搜索托管 Agent Vault" },
auth: "apiKey",
usageArgs: "--query <text> [--limit <n>] [--page-limit <n>] [--include-archived]",
flags: SEARCH_RESOURCE_FLAGS,
exampleArgs: ["--query github", "--query production --page-limit 20 --output json"],
notes: CREDENTIALS_NOTE,
validate: validateLimitAndPageLimit,
async run(ctx) {
const format = detectOutputFormat(ctx.settings.output);
const result = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml");
return searchCursorPages(
async (page) => {
const response = await listRemoteVaults(runtime, {
provider: ctx.flags.provider,
limit: ctx.flags.limit ?? 100,
page,
include_archived: ctx.flags.includeArchived,
});
return {
items: response.data,
hasMore: response.has_more,
nextPage: response.next_page,
};
},
(vault) => matchesQuery(ctx.flags.query, vault.id, vault.display_name, vault.metadata),
ctx.flags.pageLimit,
);
}),
);
emitCollection({
format,
key: "vaults",
items: result.items,
headers: ["ID", "NAME", "TYPE", "CREATED", "UPDATED"],
rows: vaultRows(result.items),
hasMore: result.hasMore,
nextPage: result.nextPage,
truncated: result.truncated,
scannedPages: result.scannedPages,
emptyMessage: "No matching vaults found.",
});
},
});
+55
View File
@@ -148,6 +148,61 @@ export { default as managedAgentSessionRun } from "./commands/managed-agent/sess
export { default as managedAgentSessionSend } from "./commands/managed-agent/session-send.ts";
export { default as managedAgentSessionEvents } from "./commands/managed-agent/session-events.ts";
export { default as managedAgentSkillList } from "./commands/managed-agent/skill-list.ts";
export { default as managedAgentCapabilities } from "./commands/managed-agent/capabilities.ts";
export {
managedAgentAgentGet,
managedAgentAgentList,
managedAgentAgentSearch,
managedAgentAgentVersions,
} from "./commands/managed-agent/agent.ts";
export {
managedAgentEnvironmentGet,
managedAgentEnvironmentList,
managedAgentEnvironmentSearch,
} from "./commands/managed-agent/environment.ts";
export {
managedAgentSkillDownload,
managedAgentSkillGet,
managedAgentSkillSearch,
managedAgentSkillVersions,
} from "./commands/managed-agent/skill.ts";
export {
managedAgentVaultGet,
managedAgentVaultList,
managedAgentVaultSearch,
} from "./commands/managed-agent/vault.ts";
export {
managedAgentDeploymentGet,
managedAgentDeploymentList,
managedAgentDeploymentPause,
managedAgentDeploymentRun,
managedAgentDeploymentRunsGet,
managedAgentDeploymentRunsList,
managedAgentDeploymentSearch,
managedAgentDeploymentUnpause,
} from "./commands/managed-agent/deployment.ts";
export {
managedAgentSessionArchive,
managedAgentSessionSearch,
managedAgentSessionUpdate,
} from "./commands/managed-agent/session-management.ts";
export {
managedAgentSessionEventList,
managedAgentSessionEventSend,
managedAgentSessionEventStream,
} from "./commands/managed-agent/session-event.ts";
export {
managedAgentSessionDebug,
managedAgentSessionExport,
} from "./commands/managed-agent/session-debug.ts";
export {
managedAgentFileDelete,
managedAgentFileDownload,
managedAgentFileGet,
managedAgentFileList,
managedAgentFileSearch,
managedAgentFileUpload,
} from "./commands/managed-agent/file.ts";
export { default as workspaceInit } from "./commands/workspace/init.ts";
export { default as pluginInstall } from "./commands/plugin/install.ts";
export { default as pluginLink } from "./commands/plugin/link.ts";
@@ -199,9 +199,88 @@ describe("e2e: managed-agent", () => {
expect(stderr).not.toMatch(/--source must be one of/i);
expect(stderr).toMatch(/File not found.*agents\.e2e-missing\.yaml/i);
});
test("managed-agent skill versions --help 展示 cursor 分页选项", async () => {
const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
"managed-agent",
"skill",
"versions",
"--help",
]);
expect(exitCode, stderr).toBe(0);
expect(stderr).toMatch(/--skill-id|--limit|--page|--all/i);
});
});
describe("e2e: managed-agent(--dry-run 短路,不联网不写盘)", () => {
test("capabilities 明确区分 Session Event 与独立 Thread API", async () => {
const { stdout, stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
"managed-agent",
"capabilities",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{
operations?: Record<string, { supported?: boolean; reason?: string }>;
}>(stdout);
expect(data.operations?.["session.event.list"]?.supported).toBe(true);
expect(data.operations?.["session_thread.list"]?.supported).toBe(false);
expect(data.operations?.["session_thread.list"]?.reason).toMatch(/no independent Thread/i);
});
test.each([
["session archive", ["session", "archive", "--session-id", "sess_e2e"]],
["session update", ["session", "update", "--session-id", "sess_e2e", "--title", "new"]],
[
"session event send",
[
"session",
"event",
"send",
"--session-id",
"sess_e2e",
"--event",
'{"type":"message","content":"hello"}',
],
],
[
"session export",
["session", "export", "--session-id", "sess_e2e", "--output-file", "debug.zip"],
],
["file upload", ["file", "upload", "--path", "missing.txt"]],
[
"file download",
["file", "download", "--file-id", "file_e2e", "--output-file", "artifact.bin"],
],
["file delete", ["file", "delete", "--file-id", "file_e2e"]],
[
"skill download",
[
"skill",
"download",
"--skill-id",
"skill_e2e",
"--skill-version",
"1",
"--output-file",
"skill.zip",
],
],
["deployment run", ["deployment", "run", "--deployment-id", "dep_e2e"]],
["deployment pause", ["deployment", "pause", "--deployment-id", "dep_e2e"]],
["deployment unpause", ["deployment", "unpause", "--deployment-id", "dep_e2e"]],
])("%s --dry-run 在构建 SDK runtime 前短路", async (_label, commandArgs) => {
const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
"managed-agent",
...commandArgs,
"--dry-run",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
});
test("init --dry-run 仅输出计划,不创建文件", async () => {
const { stdout, stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
"managed-agent",
@@ -194,7 +194,47 @@ export const MANAGED_AGENT_ROUTES: E2eRouteExports = {
"managed-agent session delete": "managedAgentSessionDelete",
"managed-agent session run": "managedAgentSessionRun",
"managed-agent session send": "managedAgentSessionSend",
"managed-agent session list": "managedAgentSessionList",
"managed-agent session events": "managedAgentSessionEvents",
"managed-agent skill-list": "managedAgentSkillList",
"managed-agent capabilities": "managedAgentCapabilities",
"managed-agent agent list": "managedAgentAgentList",
"managed-agent agent get": "managedAgentAgentGet",
"managed-agent agent search": "managedAgentAgentSearch",
"managed-agent agent versions": "managedAgentAgentVersions",
"managed-agent environment list": "managedAgentEnvironmentList",
"managed-agent environment get": "managedAgentEnvironmentGet",
"managed-agent environment search": "managedAgentEnvironmentSearch",
"managed-agent skill list": "managedAgentSkillList",
"managed-agent skill get": "managedAgentSkillGet",
"managed-agent skill search": "managedAgentSkillSearch",
"managed-agent skill versions": "managedAgentSkillVersions",
"managed-agent skill download": "managedAgentSkillDownload",
"managed-agent vault list": "managedAgentVaultList",
"managed-agent vault get": "managedAgentVaultGet",
"managed-agent vault search": "managedAgentVaultSearch",
"managed-agent deployment list": "managedAgentDeploymentList",
"managed-agent deployment get": "managedAgentDeploymentGet",
"managed-agent deployment search": "managedAgentDeploymentSearch",
"managed-agent deployment runs list": "managedAgentDeploymentRunsList",
"managed-agent deployment runs get": "managedAgentDeploymentRunsGet",
"managed-agent deployment run": "managedAgentDeploymentRun",
"managed-agent deployment pause": "managedAgentDeploymentPause",
"managed-agent deployment unpause": "managedAgentDeploymentUnpause",
"managed-agent session search": "managedAgentSessionSearch",
"managed-agent session update": "managedAgentSessionUpdate",
"managed-agent session archive": "managedAgentSessionArchive",
"managed-agent session event list": "managedAgentSessionEventList",
"managed-agent session event send": "managedAgentSessionEventSend",
"managed-agent session event stream": "managedAgentSessionEventStream",
"managed-agent session debug": "managedAgentSessionDebug",
"managed-agent session export": "managedAgentSessionExport",
"managed-agent file upload": "managedAgentFileUpload",
"managed-agent file list": "managedAgentFileList",
"managed-agent file get": "managedAgentFileGet",
"managed-agent file search": "managedAgentFileSearch",
"managed-agent file download": "managedAgentFileDownload",
"managed-agent file delete": "managedAgentFileDelete",
};
export const KNOWLEDGE_KB_LIST_ROUTES: E2eRouteExports = {
@@ -0,0 +1,41 @@
import { expect, test } from "vite-plus/test";
import { createZip, redactSensitiveValues } from "../src/commands/managed-agent/session-debug.ts";
import { searchCursorPages } from "../src/commands/managed-agent/_engine/api-helpers.ts";
test("searchCursorPages preserves opaque cursors and marks truncated scans", async () => {
const cursors: Array<string | undefined> = [];
const result = await searchCursorPages(
async (page) => {
cursors.push(page);
if (!page) return { items: ["one"], hasMore: true, nextPage: "opaque-A" };
return { items: ["two"], hasMore: true, nextPage: "opaque-B" };
},
(item) => item.includes("o"),
2,
);
expect(cursors).toEqual([undefined, "opaque-A"]);
expect(result.items).toEqual(["one", "two"]);
expect(result.truncated).toBe(true);
expect(result.nextPage).toBe("opaque-B");
});
test("session export ZIP is valid-shaped and redacts credential-like values", () => {
const redacted = redactSensitiveValues({
api_key: "secret",
nested: { authorization: "Bearer secret", normal: "kept" },
});
expect(redacted).toEqual({
api_key: "[REDACTED]",
nested: { authorization: "[REDACTED]", normal: "kept" },
});
const zip = createZip([
{ name: "manifest.json", content: new TextEncoder().encode('{"schema_version":1}\n') },
{ name: "events.json", content: new TextEncoder().encode("[]\n") },
]);
expect(Array.from(zip.slice(0, 4))).toEqual([0x50, 0x4b, 0x03, 0x04]);
const decoded = new TextDecoder().decode(zip);
expect(decoded).toContain("manifest.json");
expect(decoded).toContain("events.json");
expect(Array.from(zip.slice(-22, -18))).toEqual([0x50, 0x4b, 0x05, 0x06]);
});
+36 -6
View File
@@ -5,10 +5,10 @@ metadata:
requires:
bins: ["bl"]
description: >-
阿里云百炼托管 Agent 声明式基础设施入口:用户要创建agent、初始化 agents.yaml、校验或预览 agent 配置变更、
创建/更新/销毁百炼托管 Agent 或 Deployment、和托管 agent 对话、查会话事件历史、导入或取消跟踪远端资源时使用
`bl managed-agent`。以 agents.yaml 为唯一事实源做 IaC:init 建脚手架、validate 离线校验、plan 预览 diff、
apply / destroy 变更远端资源且必须带 `--yes`,务必先 plan 给用户看 diff 再让其确认。
阿里云百炼托管 Agent 声明式基础设施与 API 命令入口:用户要创建agent、初始化 agents.yaml、校验或预览配置变更、
创建/更新/销毁托管 Agent 或 Deployment,或查询 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 自身的记忆、技能、
子代理不走本 skill;生图生视频走 bailian-gen。
官方安装:`bl skill init`(与共享协议 bailian-protocol 同装)。
@@ -20,12 +20,16 @@ description: >-
## Safety guardrail (the most important rule)
`apply` / `destroy` **mutate remote resources** and only execute when `--yes` is passed:
`apply` / `destroy` **mutate persistent remote resources** and only execute when `--yes` is passed:
1. Always run `bl managed-agent plan` first and show the diff to the user.
1. For `agents.yaml` resource changes, always run `bl managed-agent plan` first and show the diff to the user.
2. Only after explicit user confirmation, retry `apply` / `destroy` with `--yes`.
3. Never add `--yes` on your own initiative before the user has confirmed.
API-oriented commands do not replace IaC. Agent / Environment / Skill / Vault 的持久配置仍通过
`agents.yaml → plan → apply` 管理;命令式写操作只覆盖 Session、Event、File 和 Deployment 运行时动作。
`session archive|delete`、`file delete`、`deployment run` 也需要先 `--dry-run`,确认后才传 `--yes`。
## IaC lifecycle
```
@@ -66,6 +70,32 @@ deployments:
| List session event history | `bl managed-agent session events` |
| Delete a session | `bl managed-agent session delete` |
规范路径是 `session event list|send|stream`;`session events` 保留为 `session event list` 的兼容别名。
Managed Agents 的子线程通过 Event 中的 `session_thread_id` 暴露;公开 API 当前没有独立 Thread 资源 CRUD,
不要构造 `session thread list|get|archive|events` 命令。
## API-oriented resource commands
| Intent | Command family |
| -------------------------------------- | ------------------------------- | ------- | ------------------------------ | --------- | --------- | ------- |
| Check exact API support/auth/reason | `bl managed-agent capabilities` |
| Discover agents and versions | `agent list | get | search | versions` |
| Discover environments | `environment list | get | search` |
| Discover skills and download a version | `skill list | get | search | versions | download` |
| Inspect vault envelopes | `vault list | get | search` |
| Inspect deployments and run history | `deployment list | get | search`, `deployment runs list | get` |
| Run or pause deployments | `deployment run | pause | unpause` |
| Manage session metadata/lifecycle | `session list | get | search | update | archive | delete` |
| Work with raw events | `session event send | list | stream` |
| Diagnose/export a session | `session debug | export` |
| Work with files | `file upload | list | get | search | download | delete` |
- 所有 Cursor 都是不透明字符串:只回传 `next_page`,不得转换为数字页码。
- 客户端搜索默认最多扫描 10 页;需要扩大范围时显式传 `--page-limit`。Deployment 搜索直接映射服务端 `keyword`。
- 下载必须给出 `--output-file`;默认不覆盖已有文件,只有用户确认后才可加 `--force`。
- `session export` 只导出诊断元数据,不含 File 正文,并会脱敏凭证类字段。
- 公开 Managed Agents API 没有模型 Catalog,也没有 MCP OAuth Login;以 `capabilities` 返回的 unsupported 原因为准。
## Local state management
| Intent | Command |
+60 -22
View File
@@ -9,31 +9,69 @@ Use this index for the skill-scoped quick index and global flags.
## Quick index
| Command | Authentication | Description | Detail |
| --------------------------------- | -------------- | ------------------------------------------------------------- | ------------------------------------ |
| `bl managed-agent apply` | API Key | Apply planned changes to create/update/delete agent resources | [managed-agent.md](managed-agent.md) |
| `bl managed-agent destroy` | API Key | Destroy all managed agent resources tracked in state | [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 plan` | API Key | Show what changes would be applied to agent infrastructure | [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 delete` | API Key | Delete a session | [managed-agent.md](managed-agent.md) |
| `bl managed-agent session events` | API Key | List event history for a session | [managed-agent.md](managed-agent.md) |
| `bl managed-agent session get` | API Key | Get details of a session | [managed-agent.md](managed-agent.md) |
| `bl managed-agent session list` | API Key | List sessions from the provider | [managed-agent.md](managed-agent.md) |
| `bl managed-agent session run` | API Key | Create a session, send a message, and stream the response | [managed-agent.md](managed-agent.md) |
| `bl managed-agent session send` | API Key | Send a message to an existing session and stream the response | [managed-agent.md](managed-agent.md) |
| `bl managed-agent skill-list` | API Key | List skills from the provider's skill catalog | [managed-agent.md](managed-agent.md) |
| `bl managed-agent state import` | API Key | Import an existing remote resource into agents state | [managed-agent.md](managed-agent.md) |
| `bl managed-agent state list` | No Auth | List resources tracked in agents state | [managed-agent.md](managed-agent.md) |
| `bl managed-agent state rm` | No Auth | Remove a resource from state without destroying it remotely | [managed-agent.md](managed-agent.md) |
| `bl managed-agent state show` | No Auth | Show details of a resource in agents state | [managed-agent.md](managed-agent.md) |
| `bl managed-agent validate` | No Auth | Validate an agents.yaml configuration (offline) | [managed-agent.md](managed-agent.md) |
| Command | Authentication | Description | Detail |
| --------------------------------------- | -------------- | ------------------------------------------------------------- | ------------------------------------ |
| `bl managed-agent agent get` | API Key | Get a Managed Agent | [managed-agent.md](managed-agent.md) |
| `bl managed-agent agent list` | API Key | List Managed Agents | [managed-agent.md](managed-agent.md) |
| `bl managed-agent agent search` | API Key | Search Managed Agents | [managed-agent.md](managed-agent.md) |
| `bl managed-agent agent versions` | API Key | List Managed Agent versions | [managed-agent.md](managed-agent.md) |
| `bl managed-agent apply` | API Key | Apply planned changes to create/update/delete agent resources | [managed-agent.md](managed-agent.md) |
| `bl managed-agent capabilities` | No Auth | Show operation-level Managed Agents API capabilities | [managed-agent.md](managed-agent.md) |
| `bl managed-agent deployment get` | API Key | Get a Managed Agent deployment | [managed-agent.md](managed-agent.md) |
| `bl managed-agent deployment list` | API Key | List Managed Agent deployments | [managed-agent.md](managed-agent.md) |
| `bl managed-agent deployment pause` | API Key | Pause a Managed Agent deployment | [managed-agent.md](managed-agent.md) |
| `bl managed-agent deployment run` | API Key | Run a Managed Agent deployment now | [managed-agent.md](managed-agent.md) |
| `bl managed-agent deployment runs get` | API Key | Get a Managed Agent deployment run | [managed-agent.md](managed-agent.md) |
| `bl managed-agent deployment runs list` | API Key | List runs for a Managed Agent deployment | [managed-agent.md](managed-agent.md) |
| `bl managed-agent deployment search` | API Key | Search Managed Agent deployments | [managed-agent.md](managed-agent.md) |
| `bl managed-agent deployment unpause` | API Key | Unpause a Managed Agent deployment | [managed-agent.md](managed-agent.md) |
| `bl managed-agent destroy` | API Key | Destroy all managed agent resources tracked in state | [managed-agent.md](managed-agent.md) |
| `bl managed-agent environment get` | API Key | Get a Managed Agent environment | [managed-agent.md](managed-agent.md) |
| `bl managed-agent environment list` | API Key | List Managed Agent environments | [managed-agent.md](managed-agent.md) |
| `bl managed-agent environment search` | API Key | Search Managed Agent environments | [managed-agent.md](managed-agent.md) |
| `bl managed-agent file delete` | API Key | Delete a Managed Agent file | [managed-agent.md](managed-agent.md) |
| `bl managed-agent file download` | API Key | Download Managed Agent file content | [managed-agent.md](managed-agent.md) |
| `bl managed-agent file get` | API Key | Get Managed Agent file metadata | [managed-agent.md](managed-agent.md) |
| `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 plan` | API Key | Show what changes would be applied to agent infrastructure | [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) |
| `bl managed-agent session delete` | API Key | Delete a session | [managed-agent.md](managed-agent.md) |
| `bl managed-agent session event list` | API Key | List events for a Managed Agent session | [managed-agent.md](managed-agent.md) |
| `bl managed-agent session event send` | API Key | Send raw events to a Managed Agent session | [managed-agent.md](managed-agent.md) |
| `bl managed-agent session event stream` | API Key | Stream events from a Managed Agent session | [managed-agent.md](managed-agent.md) |
| `bl managed-agent session events` | API Key | List events for a Managed Agent session | [managed-agent.md](managed-agent.md) |
| `bl managed-agent session export` | API Key | Export session diagnostics as a ZIP | [managed-agent.md](managed-agent.md) |
| `bl managed-agent session get` | API Key | Get details of a session | [managed-agent.md](managed-agent.md) |
| `bl managed-agent session list` | API Key | List sessions from the provider | [managed-agent.md](managed-agent.md) |
| `bl managed-agent session run` | API Key | Create a session, send a message, and stream the response | [managed-agent.md](managed-agent.md) |
| `bl managed-agent session search` | API Key | Search Managed Agent sessions | [managed-agent.md](managed-agent.md) |
| `bl managed-agent session send` | API Key | Send a message to an existing session and stream the response | [managed-agent.md](managed-agent.md) |
| `bl managed-agent session update` | API Key | Update a Managed Agent session | [managed-agent.md](managed-agent.md) |
| `bl managed-agent skill download` | API Key | Download a Managed Agent skill version | [managed-agent.md](managed-agent.md) |
| `bl managed-agent skill get` | API Key | Get a Managed Agent skill | [managed-agent.md](managed-agent.md) |
| `bl managed-agent skill list` | API Key | List Managed Agent skills | [managed-agent.md](managed-agent.md) |
| `bl managed-agent skill search` | API Key | Search Managed Agent skills | [managed-agent.md](managed-agent.md) |
| `bl managed-agent skill versions` | API Key | List Managed Agent skill versions | [managed-agent.md](managed-agent.md) |
| `bl managed-agent skill-list` | API Key | List Managed Agent skills | [managed-agent.md](managed-agent.md) |
| `bl managed-agent state import` | API Key | Import an existing remote resource into agents state | [managed-agent.md](managed-agent.md) |
| `bl managed-agent state list` | No Auth | List resources tracked in agents state | [managed-agent.md](managed-agent.md) |
| `bl managed-agent state rm` | No Auth | Remove a resource from state without destroying it remotely | [managed-agent.md](managed-agent.md) |
| `bl managed-agent state show` | No Auth | Show details of a resource in agents state | [managed-agent.md](managed-agent.md) |
| `bl managed-agent validate` | No Auth | Validate an agents.yaml configuration (offline) | [managed-agent.md](managed-agent.md) |
| `bl managed-agent vault get` | API Key | Get a Managed Agent vault | [managed-agent.md](managed-agent.md) |
| `bl managed-agent vault list` | API Key | List Managed Agent vaults | [managed-agent.md](managed-agent.md) |
| `bl managed-agent vault search` | API Key | Search Managed Agent vaults | [managed-agent.md](managed-agent.md) |
## By group
| Group | Commands | Reference |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
| `managed-agent` | `apply`, `destroy`, `init`, `plan`, `session create`, `session delete`, `session events`, `session get`, `session list`, `session run`, `session send`, `skill-list`, `state import`, `state list`, `state rm`, `state show`, `validate` | [managed-agent.md](managed-agent.md) |
| Group | Commands | Reference |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
| `managed-agent` | `agent get`, `agent list`, `agent search`, `agent versions`, `apply`, `capabilities`, `deployment get`, `deployment list`, `deployment pause`, `deployment run`, `deployment runs get`, `deployment runs list`, `deployment search`, `deployment unpause`, `destroy`, `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 download`, `skill get`, `skill list`, `skill search`, `skill versions`, `skill-list`, `state import`, `state list`, `state rm`, `state show`, `validate`, `vault get`, `vault list`, `vault search` | [managed-agent.md](managed-agent.md) |
## Global flags
File diff suppressed because it is too large Load Diff