From 69c1fb9f085aa8f5d3b6125419087f56dc4230db Mon Sep 17 00:00:00 2001 From: chenanran555 Date: Mon, 24 Aug 2026 20:15:09 +0800 Subject: [PATCH 01/10] =?UTF-8?q?feat(managed-agent):=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=89=98=E7=AE=A1=20Agent=20=E5=85=A8=E9=9D=A2=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=91=BD=E4=BB=A4=E9=9B=86=E5=8F=8A=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 扩展 CLI 命令,新增托管 Agent 相关全部操作命令包括能力、Agent、环境、技能、保管库、部署、会话及文件管理 - 实现 Agent 列表、详情、版本、搜索等功能 - 实现环境列表、详情及搜索功能 - 实现部署列表、详情、搜索、运行、暂停与恢复功能 - 实现会话的搜索、更新、归档及事件操作功能 - 实现文件上传、列表、下载、删除及搜索功能 - 添加托管 Agent API 操作能力展示命令 - 统一异常处理及输出格式支持,支持 JSON 与表格展示 - 完善分页、查询参数及权限验证逻辑,支持按需筛选与翻页 - 新增输入输出文件支持与控制台保护机制,提升命令行体验和稳定性 --- packages/cli/src/commands.ts | 75 + .../managed-agent/_engine/api-helpers.ts | 201 ++ .../managed-agent/_engine/output-file.ts | 68 + .../managed-agent/_engine/pagination.ts | 3 +- .../src/commands/managed-agent/agent.ts | 236 +++ .../commands/managed-agent/capabilities.ts | 49 + .../src/commands/managed-agent/deployment.ts | 471 +++++ .../src/commands/managed-agent/environment.ts | 174 ++ .../src/commands/managed-agent/file.ts | 327 ++++ .../commands/managed-agent/session-debug.ts | 317 ++++ .../commands/managed-agent/session-delete.ts | 22 +- .../commands/managed-agent/session-event.ts | 249 +++ .../commands/managed-agent/session-events.ts | 100 +- .../commands/managed-agent/session-list.ts | 76 +- .../managed-agent/session-management.ts | 246 +++ .../src/commands/managed-agent/skill-list.ts | 117 +- .../src/commands/managed-agent/skill.ts | 363 ++++ .../src/commands/managed-agent/vault.ts | 165 ++ packages/commands/src/index.ts | 55 + .../tests/e2e/managed-agent.e2e.test.ts | 79 + packages/commands/tests/e2e/topic-routes.ts | 40 + .../tests/managed-agent-api-helpers.test.ts | 41 + skills/bailian-managed-agent/SKILL.md | 42 +- .../bailian-managed-agent/reference/index.md | 82 +- .../reference/managed-agent.md | 1686 +++++++++++++++-- 25 files changed, 4900 insertions(+), 384 deletions(-) create mode 100644 packages/commands/src/commands/managed-agent/_engine/api-helpers.ts create mode 100644 packages/commands/src/commands/managed-agent/_engine/output-file.ts create mode 100644 packages/commands/src/commands/managed-agent/agent.ts create mode 100644 packages/commands/src/commands/managed-agent/capabilities.ts create mode 100644 packages/commands/src/commands/managed-agent/deployment.ts create mode 100644 packages/commands/src/commands/managed-agent/environment.ts create mode 100644 packages/commands/src/commands/managed-agent/file.ts create mode 100644 packages/commands/src/commands/managed-agent/session-debug.ts create mode 100644 packages/commands/src/commands/managed-agent/session-event.ts create mode 100644 packages/commands/src/commands/managed-agent/session-management.ts create mode 100644 packages/commands/src/commands/managed-agent/skill.ts create mode 100644 packages/commands/src/commands/managed-agent/vault.ts create mode 100644 packages/commands/tests/managed-agent-api-helpers.test.ts diff --git a/packages/cli/src/commands.ts b/packages/cli/src/commands.ts index 27422935b..f5cff6b69 100644 --- a/packages/cli/src/commands.ts +++ b/packages/cli/src/commands.ts @@ -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 = { "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, }; /** diff --git a/packages/commands/src/commands/managed-agent/_engine/api-helpers.ts b/packages/commands/src/commands/managed-agent/_engine/api-helpers.ts new file mode 100644 index 000000000..5fce1c7e7 --- /dev/null +++ b/packages/commands/src/commands/managed-agent/_engine/api-helpers.ts @@ -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: "", + description: { + "en-US": "Config file path (default: agents.yaml)", + "zh-CN": "配置文件路径(默认:agents.yaml)", + }, + }, + provider: { + type: "string", + valueHint: "", + description: { "en-US": "Target provider", "zh-CN": "目标 Provider" }, + }, +} satisfies FlagsDef; + +export const CURSOR_FLAGS = { + limit: { + type: "number", + valueHint: "", + description: { + "en-US": "Page size (1-100)", + "zh-CN": "单页数量(1-100)", + }, + }, + page: { + type: "string", + valueHint: "", + 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: "", + required: true, + description: { + "en-US": "Case-insensitive text to find in IDs, names, and descriptions", + "zh-CN": "在 ID、名称和描述中进行不区分大小写的文本搜索", + }, + }, + pageLimit: { + type: "number", + valueHint: "", + 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 extends PagedResult { + scannedPages: number; + truncated: boolean; +} + +export async function searchCursorPages( + fetchPage: (page?: string) => Promise>, + matches: (item: T) => boolean, + pageLimit = 10, +): Promise> { + 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(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; +} diff --git a/packages/commands/src/commands/managed-agent/_engine/output-file.ts b/packages/commands/src/commands/managed-agent/_engine/output-file.ts new file mode 100644 index 000000000..6271a757e --- /dev/null +++ b/packages/commands/src/commands/managed-agent/_engine/output-file.ts @@ -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 { + return new Uint8Array(await readFile(resolve(path))); +} + +export async function readJsonInput(argument: string): Promise { + 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 { + 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"; +} diff --git a/packages/commands/src/commands/managed-agent/_engine/pagination.ts b/packages/commands/src/commands/managed-agent/_engine/pagination.ts index df2fd9343..23fe98b4f 100644 --- a/packages/commands/src/commands/managed-agent/_engine/pagination.ts +++ b/packages/commands/src/commands/managed-agent/_engine/pagination.ts @@ -8,8 +8,9 @@ export interface PagedResult { export async function fetchAllPages( fetchPage: (page?: string) => Promise>, all?: boolean, + initialPage?: string, ): Promise> { - const first = await fetchPage(); + const first = await fetchPage(initialPage); const items = [...first.items]; let hasMore = first.hasMore; let nextPage = first.nextPage; diff --git a/packages/commands/src/commands/managed-agent/agent.ts b/packages/commands/src/commands/managed-agent/agent.ts new file mode 100644 index 000000000..9289dee94 --- /dev/null +++ b/packages/commands/src/commands/managed-agent/agent.ts @@ -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: "", + required: true, + description: { "en-US": "Agent ID", "zh-CN": "Agent ID" }, + }, + agentVersion: { + type: "number", + valueHint: "", + 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 ] [--page ] [--all] [--include-archived] [--file ]", + 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 [--agent-version ] [--file ]", + 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 [--limit ] [--page-limit ] [--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 [--limit ] [--page ] [--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.", + }); + }, +}); diff --git a/packages/commands/src/commands/managed-agent/capabilities.ts b/packages/commands/src/commands/managed-agent/capabilities.ts new file mode 100644 index 000000000..77e3167ae --- /dev/null +++ b/packages/commands/src/commands/managed-agent/capabilities.ts @@ -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: "", + 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 ]", + 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); + } + }, +}); diff --git a/packages/commands/src/commands/managed-agent/deployment.ts b/packages/commands/src/commands/managed-agent/deployment.ts new file mode 100644 index 000000000..f74cc2262 --- /dev/null +++ b/packages/commands/src/commands/managed-agent/deployment.ts @@ -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: "", + required: true, + description: { "en-US": "Deployment ID", "zh-CN": "Deployment ID" }, + }, +} as const; + +const LIST_FILTER_FLAGS = { + agentId: { + type: "string", + valueHint: "", + description: { "en-US": "Filter by agent ID", "zh-CN": "按 Agent ID 筛选" }, + }, + status: { + type: "string", + valueHint: "", + choices: ["active", "paused"] as const, + description: { "en-US": "Filter by deployment status", "zh-CN": "按 Deployment 状态筛选" }, + }, + ...INCLUDE_ARCHIVED_FLAG, + createdAtGte: { + type: "string", + valueHint: "", + description: { + "en-US": "Created at or after this timestamp", + "zh-CN": "创建时间不早于该时间戳", + }, + }, + createdAtLte: { + type: "string", + valueHint: "", + 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: "", + 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: "", + description: { + "en-US": "Logical deployment name in agents.yaml/state", + "zh-CN": "agents.yaml/State 中的逻辑 Deployment 名称", + }, + }, + deploymentId: { + type: "string", + valueHint: "", + 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 { + 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 ] [--status active|paused] [--limit ] [--page ] [--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 ", + 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 [--limit ] [--page ] [--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 [--limit ] [--page ] [--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 ", + 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 | --deployment-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 | --deployment-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); diff --git a/packages/commands/src/commands/managed-agent/environment.ts b/packages/commands/src/commands/managed-agent/environment.ts new file mode 100644 index 000000000..8ef2caa4d --- /dev/null +++ b/packages/commands/src/commands/managed-agent/environment.ts @@ -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: "", + 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 ] [--page ] [--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 ", + 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 [--limit ] [--page-limit ] [--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.", + }); + }, +}); diff --git a/packages/commands/src/commands/managed-agent/file.ts b/packages/commands/src/commands/managed-agent/file.ts new file mode 100644 index 000000000..0e3037b51 --- /dev/null +++ b/packages/commands/src/commands/managed-agent/file.ts @@ -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: "", + required: true, + description: { "en-US": "Remote file ID", "zh-CN": "远端 File ID" }, + }, +} as const; + +const SCOPE_ID_FLAG = { + scopeId: { + type: "string", + valueHint: "", + description: { "en-US": "Filter by scope ID", "zh-CN": "按 Scope ID 筛选" }, + }, +} as const; + +const UPLOAD_FLAGS = { + ...API_TARGET_FLAGS, + path: { + type: "string", + valueHint: "", + required: true, + description: { "en-US": "Local file path", "zh-CN": "本地文件路径" }, + }, + filename: { + type: "string", + valueHint: "", + description: { "en-US": "Remote filename override", "zh-CN": "覆盖远端文件名" }, + }, + mimeType: { + type: "string", + valueHint: "", + description: { "en-US": "MIME type override", "zh-CN": "覆盖 MIME 类型" }, + }, + purpose: { + type: "string", + valueHint: "", + 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: "", + 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 [--filename ] [--mime-type ] [--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 ] [--limit ] [--page ] [--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 ", + 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 [--scope-id ] [--limit ] [--page-limit ]", + 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 --output-file [--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 --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.`); + }, +}); diff --git a/packages/commands/src/commands/managed-agent/session-debug.ts b/packages/commands/src/commands/managed-agent/session-debug.ts new file mode 100644 index 000000000..fc6d9c143 --- /dev/null +++ b/packages/commands/src/commands/managed-agent/session-debug.ts @@ -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: "", + 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: "", + 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; + 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 { + // 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 ", + 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 --output-file [--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)" : ""}`, + ); + } + }, +}); diff --git a/packages/commands/src/commands/managed-agent/session-delete.ts b/packages/commands/src/commands/managed-agent/session-delete.ts index 9cbd2ee9a..00faa687e 100644 --- a/packages/commands/src/commands/managed-agent/session-delete.ts +++ b/packages/commands/src/commands/managed-agent/session-delete.ts @@ -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: "", 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 [--provider ] [--file ]", + usageArgs: "--session-id --yes [--provider ] [--file ]", 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); diff --git a/packages/commands/src/commands/managed-agent/session-event.ts b/packages/commands/src/commands/managed-agent/session-event.ts new file mode 100644 index 000000000..21cd25544 --- /dev/null +++ b/packages/commands/src/commands/managed-agent/session-event.ts @@ -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: "", + 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: "", + choices: ["asc", "desc"] as const, + description: { "en-US": "Event order: asc or desc", "zh-CN": "事件顺序:asc 或 desc" }, + }, + types: { + type: "string", + valueHint: "", + description: { + "en-US": "Comma-separated raw event types", + "zh-CN": "原始事件类型,多个以逗号分隔", + }, + }, + createdAtGte: { + type: "string", + valueHint: "", + description: { + "en-US": "Created at or after this timestamp", + "zh-CN": "创建时间不早于该时间戳", + }, + }, + createdAtLte: { + type: "string", + valueHint: "", + 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: "", + 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: "", + 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 [--types ] [--order asc|desc] [--limit ] [--page ] [--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 --event ", + 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 [--after-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, + ); + } + }), + ); + }, +}); diff --git a/packages/commands/src/commands/managed-agent/session-events.ts b/packages/commands/src/commands/managed-agent/session-events.ts index 84e15a6b5..e41d17970 100644 --- a/packages/commands/src/commands/managed-agent/session-events.ts +++ b/packages/commands/src/commands/managed-agent/session-events.ts @@ -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: "", - description: { "en-US": "Session ID (required)", "zh-CN": "Session ID(必填)" }, - required: true, - }, - file: { - type: "string", - valueHint: "", - description: { - "en-US": "Config file path (default: agents.yaml)", - "zh-CN": "配置文件路径(默认:agents.yaml)", - }, - }, - provider: { - type: "string", - valueHint: "", - description: { "en-US": "Target provider", "zh-CN": "目标 Provider" }, - }, - limit: { - type: "number", - valueHint: "", - 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 [--limit ] [--all] [--file ]", - 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"; diff --git a/packages/commands/src/commands/managed-agent/session-list.ts b/packages/commands/src/commands/managed-agent/session-list.ts index a87abd02f..b5a311ac8 100644 --- a/packages/commands/src/commands/managed-agent/session-list.ts +++ b/packages/commands/src/commands/managed-agent/session-list.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: "", + description: { + "en-US": "Comma-separated session statuses", + "zh-CN": "Session 状态,多个以逗号分隔", + }, + }, + createdAtGte: { + type: "string", + valueHint: "", + description: { + "en-US": "Created at or after this RFC 3339 timestamp", + "zh-CN": "创建时间不早于该 RFC 3339 时间戳", + }, + }, + createdAtLte: { + type: "string", + valueHint: "", + description: { + "en-US": "Created at or before this RFC 3339 timestamp", + "zh-CN": "创建时间不晚于该 RFC 3339 时间戳", + }, + }, provider: { type: "string", valueHint: "", @@ -40,36 +71,51 @@ export default defineCommand({ "zh-CN": "列出 Provider 中的 Session", }, auth: "apiKey", - usageArgs: "[--agent ] [--all] [--provider ] [--file ]", + usageArgs: "[--agent ] [--statuses ] [--limit ] [--page ] [--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) { diff --git a/packages/commands/src/commands/managed-agent/session-management.ts b/packages/commands/src/commands/managed-agent/session-management.ts new file mode 100644 index 000000000..12101ad3e --- /dev/null +++ b/packages/commands/src/commands/managed-agent/session-management.ts @@ -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: "", + 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: "", + description: { + "en-US": "Filter by configured agent name", + "zh-CN": "按配置中的 Agent 名称筛选", + }, + }, + statuses: { + type: "string", + valueHint: "", + description: { + "en-US": "Comma-separated session statuses", + "zh-CN": "Session 状态,多个以逗号分隔", + }, + }, + createdAtGte: { + type: "string", + valueHint: "", + description: { + "en-US": "Created at or after this timestamp", + "zh-CN": "创建时间不早于该时间戳", + }, + }, + createdAtLte: { + type: "string", + valueHint: "", + 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: "", + 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.`); + }, +}); diff --git a/packages/commands/src/commands/managed-agent/skill-list.ts b/packages/commands/src/commands/managed-agent/skill-list.ts index f8f77273b..ee42b96a4 100644 --- a/packages/commands/src/commands/managed-agent/skill-list.ts +++ b/packages/commands/src/commands/managed-agent/skill-list.ts @@ -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"; diff --git a/packages/commands/src/commands/managed-agent/skill.ts b/packages/commands/src/commands/managed-agent/skill.ts new file mode 100644 index 000000000..7a8b90a40 --- /dev/null +++ b/packages/commands/src/commands/managed-agent/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}`); + }, +}); diff --git a/packages/commands/src/commands/managed-agent/vault.ts b/packages/commands/src/commands/managed-agent/vault.ts new file mode 100644 index 000000000..38e916f47 --- /dev/null +++ b/packages/commands/src/commands/managed-agent/vault.ts @@ -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.", + }); + }, +}); diff --git a/packages/commands/src/index.ts b/packages/commands/src/index.ts index 79e311ebf..6ec38d97e 100644 --- a/packages/commands/src/index.ts +++ b/packages/commands/src/index.ts @@ -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"; diff --git a/packages/commands/tests/e2e/managed-agent.e2e.test.ts b/packages/commands/tests/e2e/managed-agent.e2e.test.ts index fc6b05881..6bb31e620 100644 --- a/packages/commands/tests/e2e/managed-agent.e2e.test.ts +++ b/packages/commands/tests/e2e/managed-agent.e2e.test.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", diff --git a/packages/commands/tests/e2e/topic-routes.ts b/packages/commands/tests/e2e/topic-routes.ts index fe249e8d7..5104072a9 100644 --- a/packages/commands/tests/e2e/topic-routes.ts +++ b/packages/commands/tests/e2e/topic-routes.ts @@ -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 = { diff --git a/packages/commands/tests/managed-agent-api-helpers.test.ts b/packages/commands/tests/managed-agent-api-helpers.test.ts new file mode 100644 index 000000000..ee468abd7 --- /dev/null +++ b/packages/commands/tests/managed-agent-api-helpers.test.ts @@ -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]); +}); diff --git a/skills/bailian-managed-agent/SKILL.md b/skills/bailian-managed-agent/SKILL.md index 7277707b3..cf5005930 100644 --- a/skills/bailian-managed-agent/SKILL.md +++ b/skills/bailian-managed-agent/SKILL.md @@ -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 | diff --git a/skills/bailian-managed-agent/reference/index.md b/skills/bailian-managed-agent/reference/index.md index a401eac0b..46f2a8721 100644 --- a/skills/bailian-managed-agent/reference/index.md +++ b/skills/bailian-managed-agent/reference/index.md @@ -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 diff --git a/skills/bailian-managed-agent/reference/managed-agent.md b/skills/bailian-managed-agent/reference/managed-agent.md index e3975b7e5..7836606b2 100644 --- a/skills/bailian-managed-agent/reference/managed-agent.md +++ b/skills/bailian-managed-agent/reference/managed-agent.md @@ -7,48 +7,742 @@ Index: [index.md](index.md) ## Commands in this group -| Command | Authentication | Description | -| --------------------------------- | -------------- | ------------------------------------------------------------- | -| `bl managed-agent apply` | API Key | Apply planned changes to create/update/delete agent resources | -| `bl managed-agent destroy` | API Key | Destroy all managed agent resources tracked in state | -| `bl managed-agent init` | No Auth | Create a new agents.yaml template | -| `bl managed-agent plan` | API Key | Show what changes would be applied to agent infrastructure | -| `bl managed-agent session create` | API Key | Create a new session for an agent | -| `bl managed-agent session delete` | API Key | Delete a session | -| `bl managed-agent session events` | API Key | List event history for a session | -| `bl managed-agent session get` | API Key | Get details of a session | -| `bl managed-agent session list` | API Key | List sessions from the provider | -| `bl managed-agent session run` | API Key | Create a session, send a message, and stream the response | -| `bl managed-agent session send` | API Key | Send a message to an existing session and stream the response | -| `bl managed-agent skill-list` | API Key | List skills from the provider's skill catalog | -| `bl managed-agent state import` | API Key | Import an existing remote resource into agents state | -| `bl managed-agent state list` | No Auth | List resources tracked in agents state | -| `bl managed-agent state rm` | No Auth | Remove a resource from state without destroying it remotely | -| `bl managed-agent state show` | No Auth | Show details of a resource in agents state | -| `bl managed-agent validate` | No Auth | Validate an agents.yaml configuration (offline) | +| Command | Authentication | Description | +| --------------------------------------- | -------------- | ------------------------------------------------------------- | +| `bl managed-agent agent get` | API Key | Get a Managed Agent | +| `bl managed-agent agent list` | API Key | List Managed Agents | +| `bl managed-agent agent search` | API Key | Search Managed Agents | +| `bl managed-agent agent versions` | API Key | List Managed Agent versions | +| `bl managed-agent apply` | API Key | Apply planned changes to create/update/delete agent resources | +| `bl managed-agent capabilities` | No Auth | Show operation-level Managed Agents API capabilities | +| `bl managed-agent deployment get` | API Key | Get a Managed Agent deployment | +| `bl managed-agent deployment list` | API Key | List Managed Agent deployments | +| `bl managed-agent deployment pause` | API Key | Pause a Managed Agent deployment | +| `bl managed-agent deployment run` | API Key | Run a Managed Agent deployment now | +| `bl managed-agent deployment runs get` | API Key | Get a Managed Agent deployment run | +| `bl managed-agent deployment runs list` | API Key | List runs for a Managed Agent deployment | +| `bl managed-agent deployment search` | API Key | Search Managed Agent deployments | +| `bl managed-agent deployment unpause` | API Key | Unpause a Managed Agent deployment | +| `bl managed-agent destroy` | API Key | Destroy all managed agent resources tracked in state | +| `bl managed-agent environment get` | API Key | Get a Managed Agent environment | +| `bl managed-agent environment list` | API Key | List Managed Agent environments | +| `bl managed-agent environment search` | API Key | Search Managed Agent environments | +| `bl managed-agent file delete` | API Key | Delete a Managed Agent file | +| `bl managed-agent file download` | API Key | Download Managed Agent file content | +| `bl managed-agent file get` | API Key | Get Managed Agent file metadata | +| `bl managed-agent file list` | API Key | List Managed Agent files | +| `bl managed-agent file search` | API Key | Search Managed Agent files | +| `bl managed-agent file upload` | API Key | Upload a Managed Agent file | +| `bl managed-agent init` | No Auth | Create a new agents.yaml template | +| `bl managed-agent plan` | API Key | Show what changes would be applied to agent infrastructure | +| `bl managed-agent session archive` | API Key | Archive a Managed Agent session | +| `bl managed-agent session create` | API Key | Create a new session for an agent | +| `bl managed-agent session debug` | API Key | Aggregate session diagnostics | +| `bl managed-agent session delete` | API Key | Delete a session | +| `bl managed-agent session event list` | API Key | List events for a Managed Agent session | +| `bl managed-agent session event send` | API Key | Send raw events to a Managed Agent session | +| `bl managed-agent session event stream` | API Key | Stream events from a Managed Agent session | +| `bl managed-agent session events` | API Key | List events for a Managed Agent session | +| `bl managed-agent session export` | API Key | Export session diagnostics as a ZIP | +| `bl managed-agent session get` | API Key | Get details of a session | +| `bl managed-agent session list` | API Key | List sessions from the provider | +| `bl managed-agent session run` | API Key | Create a session, send a message, and stream the response | +| `bl managed-agent session search` | API Key | Search Managed Agent sessions | +| `bl managed-agent session send` | API Key | Send a message to an existing session and stream the response | +| `bl managed-agent session update` | API Key | Update a Managed Agent session | +| `bl managed-agent skill download` | API Key | Download a Managed Agent skill version | +| `bl managed-agent skill get` | API Key | Get a Managed Agent skill | +| `bl managed-agent skill list` | API Key | List Managed Agent skills | +| `bl managed-agent skill search` | API Key | Search Managed Agent skills | +| `bl managed-agent skill versions` | API Key | List Managed Agent skill versions | +| `bl managed-agent skill-list` | API Key | List Managed Agent skills | +| `bl managed-agent state import` | API Key | Import an existing remote resource into agents state | +| `bl managed-agent state list` | No Auth | List resources tracked in agents state | +| `bl managed-agent state rm` | No Auth | Remove a resource from state without destroying it remotely | +| `bl managed-agent state show` | No Auth | Show details of a resource in agents state | +| `bl managed-agent validate` | No Auth | Validate an agents.yaml configuration (offline) | +| `bl managed-agent vault get` | API Key | Get a Managed Agent vault | +| `bl managed-agent vault list` | API Key | List Managed Agent vaults | +| `bl managed-agent vault search` | API Key | Search Managed Agent vaults | ## Command details +### `bl managed-agent agent get` + +| Field | Value | +| ------------------ | ---------------------------------------------------------------------------------- | +| **Name** | `managed-agent agent get` | +| **Description** | Get a Managed Agent | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent agent get --agent-id <id> [--agent-version <n>] [--file <path>]` | + +#### Flags + +| Flag | Type | Required | Description | +| --------------------- | ------ | -------- | --------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--agent-id <id>` | string | yes | Agent ID | +| `--agent-version <n>` | number | no | Specific agent version | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent agent get --agent-id agent_abc +``` + +```bash +bl managed-agent agent get --agent-id agent_abc --agent-version 3 --output json +``` + +### `bl managed-agent agent list` + +| Field | Value | +| ------------------ | ---------------------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent agent list` | +| **Description** | List Managed Agents | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent agent list [--limit <n>] [--page <cursor>] [--all] [--include-archived] [--file <path>]` | + +#### Flags + +| Flag | Type | Required | Description | +| -------------------- | ------ | -------- | ------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--limit <n>` | number | no | Page size (1-100) | +| `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | +| `--all` | switch | no | Fetch all pages by following opaque cursors | +| `--include-archived` | switch | no | Include archived resources | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent agent list +``` + +```bash +bl managed-agent agent list --limit 50 +``` + +```bash +bl managed-agent agent list --all --include-archived --output json +``` + +### `bl managed-agent agent search` + +| Field | Value | +| ------------------ | ---------------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent agent search` | +| **Description** | Search Managed Agents | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent agent search --query <text> [--limit <n>] [--page-limit <n>] [--include-archived]` | + +#### Flags + +| Flag | Type | Required | Description | +| -------------------- | ------ | -------- | ------------------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--limit <n>` | number | no | Page size (1-100) | +| `--query <text>` | string | yes | Case-insensitive text to find in IDs, names, and descriptions | +| `--page-limit <n>` | number | no | Maximum pages to scan for client-side search (default: 10) | +| `--include-archived` | switch | no | Include archived resources | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent agent search --query assistant +``` + +```bash +bl managed-agent agent search --query code --page-limit 20 --output json +``` + +### `bl managed-agent agent versions` + +| Field | Value | +| ------------------ | ----------------------------------------------------------------------------------------- | +| **Name** | `managed-agent agent versions` | +| **Description** | List Managed Agent versions | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent agent versions --agent-id <id> [--limit <n>] [--page <cursor>] [--all]` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------- | ------ | -------- | ------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--limit <n>` | number | no | Page size (1-100) | +| `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | +| `--all` | switch | no | Fetch all pages by following opaque cursors | +| `--agent-id <id>` | string | yes | Agent ID | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent agent versions --agent-id agent_abc +``` + +```bash +bl managed-agent agent versions --agent-id agent_abc --all --output json +``` + ### `bl managed-agent apply` -| Field | Value | -| ------------------ | ---------------------------------------------------------------------------------------- | -| **Name** | `managed-agent apply` | -| **Description** | Apply planned changes to create/update/delete agent resources | -| **Authentication** | API Key | -| **Usage** | `bl managed-agent apply [--file <path>] [--provider <name>] [--yes] [--concurrency <n>]` | +| Field | Value | +| ------------------ | ---------------------------------------------------------------------------------------- | +| **Name** | `managed-agent apply` | +| **Description** | Apply planned changes to create/update/delete agent resources | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent apply [--file <path>] [--provider <name>] [--yes] [--concurrency <n>]` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------- | ------ | -------- | -------------------------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider (default: all configured) | +| `--yes` | switch | no | Confirm and apply without an interactive prompt (required to mutate) | +| `--no-refresh` | switch | no | Skip refreshing state from remote before planning | +| `--concurrency <n>` | number | no | Max independent resources to apply in parallel (default 6, max 10) | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent apply --yes +``` + +```bash +bl managed-agent apply --provider bailian --yes +``` + +### `bl managed-agent capabilities` + +| Field | Value | +| ------------------ | ---------------------------------------------------- | +| **Name** | `managed-agent capabilities` | +| **Description** | Show operation-level Managed Agents API capabilities | +| **Authentication** | No Auth | +| **Usage** | `bl managed-agent capabilities [--provider <name>]` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------- | ------ | -------- | -------------------------------------- | +| `--provider <name>` | string | no | Provider to inspect (default: bailian) | + +#### Notes + +- Capabilities distinguish public Managed Agents APIs from client-side compositions and unsupported resources. + +#### Examples + +```bash +bl managed-agent capabilities +``` + +```bash +bl managed-agent capabilities --provider bailian --output json +``` + +### `bl managed-agent deployment get` + +| Field | Value | +| ------------------ | ------------------------------------------------------ | +| **Name** | `managed-agent deployment get` | +| **Description** | Get a Managed Agent deployment | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent deployment get --deployment-id <id>` | + +#### Flags + +| Flag | Type | Required | Description | +| ---------------------- | ------ | -------- | --------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--deployment-id <id>` | string | yes | Deployment ID | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent deployment get --deployment-id dep_abc +``` + +### `bl managed-agent deployment list` + +| Field | Value | +| ------------------ | ---------------------------------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent deployment list` | +| **Description** | List Managed Agent deployments | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent deployment list [--agent-id <id>] [--status active\|paused] [--limit <n>] [--page <cursor>] [--all]` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------------------ | ------ | -------- | ------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--limit <n>` | number | no | Page size (1-100) | +| `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | +| `--all` | switch | no | Fetch all pages by following opaque cursors | +| `--agent-id <id>` | string | no | Filter by agent ID | +| `--status <active\|paused>` | string | no | Filter by deployment status | +| `--include-archived` | switch | no | Include archived resources | +| `--created-at-gte <timestamp>` | string | no | Created at or after this timestamp | +| `--created-at-lte <timestamp>` | string | no | Created at or before this timestamp | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent deployment list +``` + +```bash +bl managed-agent deployment list --status active --all --output json +``` + +### `bl managed-agent deployment pause` + +| Field | Value | +| ------------------ | --------------------------------------------------------------------------------- | +| **Name** | `managed-agent deployment pause` | +| **Description** | Pause a Managed Agent deployment | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent deployment pause (--deployment <name> \| --deployment-id <id>)` | + +#### Flags + +| Flag | Type | Required | Description | +| ---------------------- | ------ | -------- | -------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--deployment <name>` | string | no | Logical deployment name in agents.yaml/state | +| `--deployment-id <id>` | string | no | Direct deployment ID | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent deployment pause --deployment daily-report --dry-run +``` + +```bash +bl managed-agent deployment pause --deployment-id dep_abc +``` + +### `bl managed-agent deployment run` + +| Field | Value | +| ------------------ | ------------------------------------------------------------------------------------- | +| **Name** | `managed-agent deployment run` | +| **Description** | Run a Managed Agent deployment now | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent deployment run (--deployment <name> \| --deployment-id <id>) --yes` | + +#### Flags + +| Flag | Type | Required | Description | +| ---------------------- | ------ | -------- | -------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--deployment <name>` | string | no | Logical deployment name in agents.yaml/state | +| `--deployment-id <id>` | string | no | Direct deployment ID | +| `--yes` | switch | no | Confirm deployment run | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent deployment run --deployment daily-report --dry-run +``` + +```bash +bl managed-agent deployment run --deployment-id dep_abc --yes +``` + +### `bl managed-agent deployment runs get` + +| Field | Value | +| ------------------ | ---------------------------------------------------- | +| **Name** | `managed-agent deployment runs get` | +| **Description** | Get a Managed Agent deployment run | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent deployment runs get --run-id <id>` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------- | ------ | -------- | --------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--run-id <id>` | string | yes | Deployment run ID | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent deployment runs get --run-id run_abc +``` + +### `bl managed-agent deployment runs list` + +| Field | Value | +| ------------------ | ---------------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent deployment runs list` | +| **Description** | List runs for a Managed Agent deployment | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent deployment runs list --deployment-id <id> [--limit <n>] [--page <cursor>] [--all]` | + +#### Flags + +| Flag | Type | Required | Description | +| ---------------------- | ------ | -------- | ------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--deployment-id <id>` | string | yes | Deployment ID | +| `--limit <n>` | number | no | Page size (1-100) | +| `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | +| `--all` | switch | no | Fetch all pages by following opaque cursors | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent deployment runs list --deployment-id dep_abc +``` + +```bash +bl managed-agent deployment runs list --deployment-id dep_abc --all --output json +``` + +### `bl managed-agent deployment search` + +| Field | Value | +| ------------------ | ------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent deployment search` | +| **Description** | Search Managed Agent deployments | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent deployment search --query <text> [--limit <n>] [--page <cursor>] [--all]` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------------------ | ------ | -------- | ------------------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--limit <n>` | number | no | Page size (1-100) | +| `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | +| `--all` | switch | no | Fetch all pages by following opaque cursors | +| `--query <text>` | string | yes | Case-insensitive text to find in IDs, names, and descriptions | +| `--agent-id <id>` | string | no | Filter by agent ID | +| `--status <active\|paused>` | string | no | Filter by deployment status | +| `--include-archived` | switch | no | Include archived resources | +| `--created-at-gte <timestamp>` | string | no | Created at or after this timestamp | +| `--created-at-lte <timestamp>` | string | no | Created at or before this timestamp | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. +- Deployment search maps --query to the provider's server-side keyword parameter. + +#### Examples + +```bash +bl managed-agent deployment search --query report +``` + +```bash +bl managed-agent deployment search --query nightly --all --output json +``` + +### `bl managed-agent deployment unpause` + +| Field | Value | +| ------------------ | ----------------------------------------------------------------------------------- | +| **Name** | `managed-agent deployment unpause` | +| **Description** | Unpause a Managed Agent deployment | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent deployment unpause (--deployment <name> \| --deployment-id <id>)` | + +#### Flags + +| Flag | Type | Required | Description | +| ---------------------- | ------ | -------- | -------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--deployment <name>` | string | no | Logical deployment name in agents.yaml/state | +| `--deployment-id <id>` | string | no | Direct deployment ID | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent deployment unpause --deployment daily-report --dry-run +``` + +```bash +bl managed-agent deployment unpause --deployment-id dep_abc +``` + +### `bl managed-agent destroy` + +| Field | Value | +| ------------------ | -------------------------------------------------------------- | +| **Name** | `managed-agent destroy` | +| **Description** | Destroy all managed agent resources tracked in state | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent destroy [--file <path>] [--yes] [--cascade]` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------ | ------ | -------- | -------------------------------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--yes` | switch | no | Confirm and destroy without an interactive prompt (required) | +| `--cascade` | switch | no | Auto-delete dependent resources (e.g. sessions referencing an environment) | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent destroy --yes +``` + +```bash +bl managed-agent destroy --yes --cascade +``` + +### `bl managed-agent environment get` + +| Field | Value | +| ------------------ | -------------------------------------------------------- | +| **Name** | `managed-agent environment get` | +| **Description** | Get a Managed Agent environment | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent environment get --environment-id <id>` | + +#### Flags + +| Flag | Type | Required | Description | +| ----------------------- | ------ | -------- | --------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--environment-id <id>` | string | yes | Environment ID | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent environment get --environment-id env_abc +``` + +### `bl managed-agent environment list` + +| Field | Value | +| ------------------ | ------------------------------------------------------------------------------------------------ | +| **Name** | `managed-agent environment list` | +| **Description** | List Managed Agent environments | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent environment list [--limit <n>] [--page <cursor>] [--all] [--include-archived]` | + +#### Flags + +| Flag | Type | Required | Description | +| -------------------- | ------ | -------- | ------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--limit <n>` | number | no | Page size (1-100) | +| `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | +| `--all` | switch | no | Fetch all pages by following opaque cursors | +| `--include-archived` | switch | no | Include archived resources | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent environment list +``` + +```bash +bl managed-agent environment list --all --output json +``` + +### `bl managed-agent environment search` + +| Field | Value | +| ------------------ | ---------------------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent environment search` | +| **Description** | Search Managed Agent environments | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent environment search --query <text> [--limit <n>] [--page-limit <n>] [--include-archived]` | + +#### Flags + +| Flag | Type | Required | Description | +| -------------------- | ------ | -------- | ------------------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--limit <n>` | number | no | Page size (1-100) | +| `--query <text>` | string | yes | Case-insensitive text to find in IDs, names, and descriptions | +| `--page-limit <n>` | number | no | Maximum pages to scan for client-side search (default: 10) | +| `--include-archived` | switch | no | Include archived resources | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent environment search --query sandbox +``` + +```bash +bl managed-agent environment search --query production --page-limit 20 --output json +``` + +### `bl managed-agent file delete` + +| Field | Value | +| ------------------ | --------------------------------------------------- | +| **Name** | `managed-agent file delete` | +| **Description** | Delete a Managed Agent file | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent file delete --file-id <id> --yes` | #### Flags -| Flag | Type | Required | Description | -| ------------------- | ------ | -------- | -------------------------------------------------------------------- | -| `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider (default: all configured) | -| `--yes` | switch | no | Confirm and apply without an interactive prompt (required to mutate) | -| `--no-refresh` | switch | no | Skip refreshing state from remote before planning | -| `--concurrency <n>` | number | no | Max independent resources to apply in parallel (default 6, max 10) | -| `--api-key <key>` | string | no | API key | -| `--base-url <url>` | string | no | API base URL | +| Flag | Type | Required | Description | +| ------------------- | ------ | -------- | --------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--file-id <id>` | string | yes | Remote file ID | +| `--yes` | switch | no | Confirm permanent file deletion | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | #### Notes @@ -59,31 +753,33 @@ Index: [index.md](index.md) #### Examples ```bash -bl managed-agent apply --yes +bl managed-agent file delete --file-id file_abc --dry-run ``` ```bash -bl managed-agent apply --provider bailian --yes +bl managed-agent file delete --file-id file_abc --yes ``` -### `bl managed-agent destroy` +### `bl managed-agent file download` -| Field | Value | -| ------------------ | -------------------------------------------------------------- | -| **Name** | `managed-agent destroy` | -| **Description** | Destroy all managed agent resources tracked in state | -| **Authentication** | API Key | -| **Usage** | `bl managed-agent destroy [--file <path>] [--yes] [--cascade]` | +| Field | Value | +| ------------------ | ------------------------------------------------------------------------------ | +| **Name** | `managed-agent file download` | +| **Description** | Download Managed Agent file content | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent file download --file-id <id> --output-file <path> [--force]` | #### Flags -| Flag | Type | Required | Description | -| ------------------ | ------ | -------- | -------------------------------------------------------------------------- | -| `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--yes` | switch | no | Confirm and destroy without an interactive prompt (required) | -| `--cascade` | switch | no | Auto-delete dependent resources (e.g. sessions referencing an environment) | -| `--api-key <key>` | string | no | API key | -| `--base-url <url>` | string | no | API base URL | +| Flag | Type | Required | Description | +| ---------------------- | ------ | -------- | --------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--file-id <id>` | string | yes | Remote file ID | +| `--output-file <path>` | string | yes | Destination path | +| `--force` | switch | no | Overwrite an existing output file | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | #### Notes @@ -94,11 +790,152 @@ bl managed-agent apply --provider bailian --yes #### Examples ```bash -bl managed-agent destroy --yes +bl managed-agent file download --file-id file_abc --output-file ./artifact.pdf +``` + +### `bl managed-agent file get` + +| Field | Value | +| ------------------ | ------------------------------------------ | +| **Name** | `managed-agent file get` | +| **Description** | Get Managed Agent file metadata | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent file get --file-id <id>` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------- | ------ | -------- | --------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--file-id <id>` | string | yes | Remote file ID | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent file get --file-id file_abc +``` + +### `bl managed-agent file list` + +| Field | Value | +| ------------------ | -------------------------------------------------------------------------------------- | +| **Name** | `managed-agent file list` | +| **Description** | List Managed Agent files | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent file list [--scope-id <id>] [--limit <n>] [--page <cursor>] [--all]` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------- | ------ | -------- | ------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--limit <n>` | number | no | Page size (1-100) | +| `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | +| `--all` | switch | no | Fetch all pages by following opaque cursors | +| `--scope-id <id>` | string | no | Filter by scope ID | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent file list ``` ```bash -bl managed-agent destroy --yes --cascade +bl managed-agent file list --scope-id sess_abc --all --output json +``` + +### `bl managed-agent file search` + +| Field | Value | +| ------------------ | ------------------------------------------------------------------------------------------------ | +| **Name** | `managed-agent file search` | +| **Description** | Search Managed Agent files | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent file search --query <text> [--scope-id <id>] [--limit <n>] [--page-limit <n>]` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------- | ------ | -------- | ------------------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--limit <n>` | number | no | Page size (1-100) | +| `--query <text>` | string | yes | Case-insensitive text to find in IDs, names, and descriptions | +| `--page-limit <n>` | number | no | Maximum pages to scan for client-side search (default: 10) | +| `--scope-id <id>` | string | no | Filter by scope ID | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent file search --query report +``` + +```bash +bl managed-agent file search --query pdf --scope-id sess_abc --output json +``` + +### `bl managed-agent file upload` + +| Field | Value | +| ------------------ | ----------------------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent file upload` | +| **Description** | Upload a Managed Agent file | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent file upload --path <path> [--filename <name>] [--mime-type <type>] [--purpose <purpose>]` | + +#### Flags + +| Flag | Type | Required | Description | +| --------------------- | ------ | -------- | --------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--path <path>` | string | yes | Local file path | +| `--filename <name>` | string | no | Remote filename override | +| `--mime-type <type>` | string | no | MIME type override | +| `--purpose <purpose>` | string | no | Provider upload purpose | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent file upload --path ./report.pdf +``` + +```bash +bl managed-agent file upload --path ./data.json --purpose assistants ``` ### `bl managed-agent init` @@ -174,6 +1011,42 @@ bl managed-agent plan --provider bailian bl managed-agent plan --no-refresh ``` +### `bl managed-agent session archive` + +| Field | Value | +| ------------------ | ---------------------------------------------------------- | +| **Name** | `managed-agent session archive` | +| **Description** | Archive a Managed Agent session | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent session archive --session-id <id> --yes` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------- | ------ | -------- | --------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--session-id <id>` | string | yes | Session ID | +| `--yes` | switch | no | Confirm session archive | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent session archive --session-id sess_abc --dry-run +``` + +```bash +bl managed-agent session archive --session-id sess_abc --yes +``` + ### `bl managed-agent session create` | Field | Value | @@ -206,83 +1079,275 @@ bl managed-agent plan --no-refresh #### Examples ```bash -bl managed-agent session create -``` - -```bash -bl managed-agent session create --agent assistant +bl managed-agent session create +``` + +```bash +bl managed-agent session create --agent assistant +``` + +```bash +bl managed-agent session create --agent assistant --title 'debug run' +``` + +### `bl managed-agent session debug` + +| Field | Value | +| ------------------ | -------------------------------------------------- | +| **Name** | `managed-agent session debug` | +| **Description** | Aggregate session diagnostics | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent session debug --session-id <id>` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------- | ------ | -------- | --------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--session-id <id>` | string | yes | Session ID | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent session debug --session-id sess_abc +``` + +```bash +bl managed-agent session debug --session-id sess_abc --output json +``` + +### `bl managed-agent session delete` + +| Field | Value | +| ------------------ | --------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent session delete` | +| **Description** | Delete a session | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent session delete --session-id <id> --yes [--provider <name>] [--file <path>]` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------- | ------ | -------- | --------------------------------------- | +| `--session-id <id>` | string | yes | Session ID (required) | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--yes` | switch | no | Confirm permanent session deletion | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent session delete --session-id sess_abc123 +``` + +### `bl managed-agent session event list` + +| Field | Value | +| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent session event list` | +| **Description** | List events for a Managed Agent session | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent session event list --session-id <id> [--types <types>] [--order asc\|desc] [--limit <n>] [--page <cursor>] [--all]` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------------------ | ------ | -------- | ------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--session-id <id>` | string | yes | Session ID | +| `--limit <n>` | number | no | Page size (1-100) | +| `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | +| `--all` | switch | no | Fetch all pages by following opaque cursors | +| `--order <asc\|desc>` | string | no | Event order: asc or desc | +| `--types <types>` | string | no | Comma-separated raw event types | +| `--created-at-gte <timestamp>` | string | no | Created at or after this timestamp | +| `--created-at-lte <timestamp>` | string | no | Created at or before this timestamp | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. +- --types is applied client-side to each page returned by the provider. + +#### Examples + +```bash +bl managed-agent session event list --session-id sess_abc +``` + +```bash +bl managed-agent session event list --session-id sess_abc --all --output json +``` + +### `bl managed-agent session event send` + +| Field | Value | +| ------------------ | ----------------------------------------------------------------------------- | +| **Name** | `managed-agent session event send` | +| **Description** | Send raw events to a Managed Agent session | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent session event send --session-id <id> --event <json\|@path>` | + +#### Flags + +| Flag | Type | Required | Description | +| ----------------------- | ------ | -------- | --------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--session-id <id>` | string | yes | Session ID | +| `--event <json\|@path>` | string | yes | Raw event object/array as JSON or @event.json | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent session event send --session-id sess_abc --event '{"type":"message","role":"user","content":"hello"}' ``` ```bash -bl managed-agent session create --agent assistant --title 'debug run' +bl managed-agent session event send --session-id sess_abc --event @event.json ``` -### `bl managed-agent session delete` +### `bl managed-agent session event stream` -| Field | Value | -| ------------------ | --------------------------------------------------------------------------------------- | -| **Name** | `managed-agent session delete` | -| **Description** | Delete a session | -| **Authentication** | API Key | -| **Usage** | `bl managed-agent session delete --session-id <id> [--provider <name>] [--file <path>]` | +| Field | Value | +| ------------------ | --------------------------------------------------------------------------------- | +| **Name** | `managed-agent session event stream` | +| **Description** | Stream events from a Managed Agent session | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent session event stream --session-id <id> [--after-id <event-id>]` | #### Flags -| Flag | Type | Required | Description | -| ------------------- | ------ | -------- | --------------------------------------- | -| `--session-id <id>` | string | yes | Session ID (required) | -| `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | -| `--api-key <key>` | string | no | API key | -| `--base-url <url>` | string | no | API base URL | +| Flag | Type | Required | Description | +| ----------------------- | ------ | -------- | --------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--session-id <id>` | string | yes | Session ID | +| `--after-id <event-id>` | string | no | Resume after this event ID | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | #### Notes - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). - Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. +- When the provider has no native event cursor, --after-id resumes through paginated history polling and event ID de-duplication. #### Examples ```bash -bl managed-agent session delete --session-id sess_abc123 +bl managed-agent session event stream --session-id sess_abc +``` + +```bash +bl managed-agent session event stream --session-id sess_abc --after-id evt_123 --output json ``` ### `bl managed-agent session events` -| Field | Value | -| ------------------ | ----------------------------------------------------------------------------------------- | -| **Name** | `managed-agent session events` | -| **Description** | List event history for a session | -| **Authentication** | API Key | -| **Usage** | `bl managed-agent session events --session-id <id> [--limit <n>] [--all] [--file <path>]` | +| Field | Value | +| ------------------ | --------------------------------------------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent session events` | +| **Description** | List events for a Managed Agent session | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent session events --session-id <id> [--types <types>] [--order asc\|desc] [--limit <n>] [--page <cursor>] [--all]` | #### Flags -| Flag | Type | Required | Description | -| ------------------- | ------ | -------- | --------------------------------------- | -| `--session-id <id>` | string | yes | Session ID (required) | -| `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | -| `--limit <n>` | number | no | Maximum number of events to fetch | -| `--all` | switch | no | Fetch all pages by following the cursor | -| `--api-key <key>` | string | no | API key | -| `--base-url <url>` | string | no | API base URL | +| Flag | Type | Required | Description | +| ------------------------------ | ------ | -------- | ------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--session-id <id>` | string | yes | Session ID | +| `--limit <n>` | number | no | Page size (1-100) | +| `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | +| `--all` | switch | no | Fetch all pages by following opaque cursors | +| `--order <asc\|desc>` | string | no | Event order: asc or desc | +| `--types <types>` | string | no | Comma-separated raw event types | +| `--created-at-gte <timestamp>` | string | no | Created at or after this timestamp | +| `--created-at-lte <timestamp>` | string | no | Created at or before this timestamp | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | #### Notes - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). - Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. +- --types is applied client-side to each page returned by the provider. #### Examples ```bash -bl managed-agent session events --session-id sess_abc123 +bl managed-agent session events --session-id sess_abc +``` + +```bash +bl managed-agent session events --session-id sess_abc --all --output json ``` +### `bl managed-agent session export` + +| Field | Value | +| ------------------ | ---------------------------------------------------------------------------------- | +| **Name** | `managed-agent session export` | +| **Description** | Export session diagnostics as a ZIP | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent session export --session-id <id> --output-file <path> [--force]` | + +#### Flags + +| Flag | Type | Required | Description | +| ---------------------- | ------ | -------- | --------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--session-id <id>` | string | yes | Session ID | +| `--output-file <path>` | string | yes | Destination ZIP path | +| `--force` | switch | no | Overwrite an existing output file | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. +- The ZIP contains metadata only; file bodies and credential-like values are excluded/redacted. + +#### Examples + ```bash -bl managed-agent session events --session-id sess_abc123 --all +bl managed-agent session export --session-id sess_abc --output-file ./session-debug.zip ``` ### `bl managed-agent session get` @@ -318,23 +1383,28 @@ bl managed-agent session get --session-id sess_abc123 ### `bl managed-agent session list` -| Field | Value | -| ------------------ | -------------------------------------------------------------------------------------------- | -| **Name** | `managed-agent session list` | -| **Description** | List sessions from the provider | -| **Authentication** | API Key | -| **Usage** | `bl managed-agent session list [--agent <name>] [--all] [--provider <name>] [--file <path>]` | +| Field | Value | +| ------------------ | ---------------------------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent session list` | +| **Description** | List sessions from the provider | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent session list [--agent <name>] [--statuses <statuses>] [--limit <n>] [--page <cursor>] [--all]` | #### Flags -| Flag | Type | Required | Description | -| ------------------- | ------ | -------- | --------------------------------------- | -| `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--agent <name>` | string | no | Filter by agent name | -| `--all` | switch | no | Fetch all pages by following the cursor | -| `--provider <name>` | string | no | Target provider | -| `--api-key <key>` | string | no | API key | -| `--base-url <url>` | string | no | API base URL | +| Flag | Type | Required | Description | +| ------------------------------ | ------ | -------- | ------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--agent <name>` | string | no | Filter by agent name | +| `--all` | switch | no | Fetch all pages by following the cursor | +| `--limit <n>` | number | no | Page size (1-100) | +| `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | +| `--statuses <statuses>` | string | no | Comma-separated session statuses | +| `--created-at-gte <timestamp>` | string | no | Created at or after this RFC 3339 timestamp | +| `--created-at-lte <timestamp>` | string | no | Created at or before this RFC 3339 timestamp | +| `--provider <name>` | string | no | Target provider | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | #### Notes @@ -398,6 +1468,47 @@ bl managed-agent session run --prompt "hello" bl managed-agent session run --agent assistant --prompt "summarize this repo" ``` +### `bl managed-agent session search` + +| Field | Value | +| ------------------ | -------------------------------------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent session search` | +| **Description** | Search Managed Agent sessions | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent session search --query <text> [--agent <name>] [--statuses <statuses>] [--limit <n>] [--page-limit <n>]` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------------------ | ------ | -------- | ------------------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--limit <n>` | number | no | Page size (1-100) | +| `--query <text>` | string | yes | Case-insensitive text to find in IDs, names, and descriptions | +| `--page-limit <n>` | number | no | Maximum pages to scan for client-side search (default: 10) | +| `--agent <name>` | string | no | Filter by configured agent name | +| `--statuses <statuses>` | string | no | Comma-separated session statuses | +| `--created-at-gte <timestamp>` | string | no | Created at or after this timestamp | +| `--created-at-lte <timestamp>` | string | no | Created at or before this timestamp | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent session search --query debug +``` + +```bash +bl managed-agent session search --query failed --statuses failed --output json +``` + ### `bl managed-agent session send` | Field | Value | @@ -431,50 +1542,268 @@ bl managed-agent session run --agent assistant --prompt "summarize this repo" bl managed-agent session send --session-id sess_abc123 --message "continue" ``` -### `bl managed-agent skill-list` +### `bl managed-agent session update` -| Field | Value | -| ------------------ | -------------------------------------------------------------------------------------------------- | -| **Name** | `managed-agent skill-list` | -| **Description** | List skills from the provider's skill catalog | -| **Authentication** | API Key | -| **Usage** | `bl managed-agent skill-list [--source custom\|official\|all] [--provider <name>] [--file <path>]` | +| Field | Value | +| ------------------ | ------------------------------------------------------------------------------------------------ | +| **Name** | `managed-agent session update` | +| **Description** | Update a Managed Agent session | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent session update --session-id <id> [--title <title>] [--metadata <json\|@path>]` | #### Flags -| Flag | Type | Required | Description | -| ------------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------ | -| `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--source <source>` | string | no | Skill catalog: custom (workspace-uploaded, default), official (built-in), or all (both catalogs in one call) | -| `--provider <name>` | string | no | Target provider | -| `--api-key <key>` | string | no | API key | -| `--base-url <url>` | string | no | API base URL | +| Flag | Type | Required | Description | +| -------------------------- | ------ | -------- | --------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--session-id <id>` | string | yes | Session ID | +| `--title <title>` | string | no | New session title | +| `--metadata <json\|@path>` | string | no | String-valued metadata JSON or @file | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | #### Notes - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). - Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. -- Providers without a skill listing API (e.g. ark) return an empty list. -- 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. -- When generating a task that needs a suitable skill, call this command to match official or custom skills before wiring them into the task. #### Examples ```bash -bl managed-agent skill-list +bl managed-agent session update --session-id sess_abc --title 'investigation' ``` ```bash -bl managed-agent skill-list --source official +bl managed-agent session update --session-id sess_abc --metadata @metadata.json +``` + +### `bl managed-agent skill download` + +| Field | Value | +| ------------------ | ---------------------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent skill download` | +| **Description** | Download a Managed Agent skill version | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent skill download --skill-id <id> --skill-version <version> --output-file <path> [--force]` | + +#### Flags + +| Flag | Type | Required | Description | +| --------------------------- | ------ | -------- | --------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--skill-id <id>` | string | yes | Skill ID | +| `--skill-version <version>` | string | yes | Skill version | +| `--output-file <path>` | string | yes | Destination ZIP path | +| `--force` | switch | no | Overwrite an existing output file | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent skill download --skill-id skill_abc --skill-version 3 --output-file ./skill.zip +``` + +### `bl managed-agent skill get` + +| Field | Value | +| ------------------ | -------------------------------------------- | +| **Name** | `managed-agent skill get` | +| **Description** | Get a Managed Agent skill | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent skill get --skill-id <id>` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------- | ------ | -------- | --------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--skill-id <id>` | string | yes | Skill ID | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent skill get --skill-id skill_abc +``` + +### `bl managed-agent skill list` + +| Field | Value | +| ------------------ | ------------------------------------------------------------------------------------------------------ | +| **Name** | `managed-agent skill list` | +| **Description** | List Managed Agent skills | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent skill list [--source custom\|official\|all] [--limit <n>] [--page <cursor>] [--all]` | + +#### Flags + +| Flag | Type | Required | Description | +| ---------------------------------- | ------ | -------- | ------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--limit <n>` | number | no | Page size (1-100) | +| `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | +| `--all` | switch | no | Fetch all pages by following opaque cursors | +| `--source <custom\|official\|all>` | string | no | Skill catalog: custom (default), official, or all | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. +- --source all combines one page from each catalog, or every page with --all; it does not accept --page. + +#### Examples + +```bash +bl managed-agent skill list +``` + +```bash +bl managed-agent skill list --source official +``` + +```bash +bl managed-agent skill list --source all --all --output json +``` + +### `bl managed-agent skill search` + +| Field | Value | +| ------------------ | ---------------------------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent skill search` | +| **Description** | Search Managed Agent skills | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent skill search --query <text> [--source custom\|official\|all] [--limit <n>] [--page-limit <n>]` | + +#### Flags + +| Flag | Type | Required | Description | +| ---------------------------------- | ------ | -------- | ------------------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--limit <n>` | number | no | Page size (1-100) | +| `--query <text>` | string | yes | Case-insensitive text to find in IDs, names, and descriptions | +| `--page-limit <n>` | number | no | Maximum pages to scan for client-side search (default: 10) | +| `--source <custom\|official\|all>` | string | no | Skill catalog: custom (default), official, or all | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent skill search --query browser --source official +``` + +```bash +bl managed-agent skill search --query report --source all --output json +``` + +### `bl managed-agent skill versions` + +| Field | Value | +| ------------------ | ----------------------------------------------------------------------------------------- | +| **Name** | `managed-agent skill versions` | +| **Description** | List Managed Agent skill versions | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent skill versions --skill-id <id> [--limit <n>] [--page <cursor>] [--all]` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------- | ------ | -------- | ------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--skill-id <id>` | string | yes | Skill ID | +| `--limit <n>` | number | no | Page size (1-100) | +| `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | +| `--all` | switch | no | Fetch all pages by following opaque cursors | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent skill versions --skill-id skill_abc +``` + +```bash +bl managed-agent skill versions --skill-id skill_abc --all --output json +``` + +### `bl managed-agent skill-list` + +| Field | Value | +| ------------------ | ------------------------------------------------------------------------------------------------------ | +| **Name** | `managed-agent skill-list` | +| **Description** | List Managed Agent skills | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent skill-list [--source custom\|official\|all] [--limit <n>] [--page <cursor>] [--all]` | + +#### Flags + +| Flag | Type | Required | Description | +| ---------------------------------- | ------ | -------- | ------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--limit <n>` | number | no | Page size (1-100) | +| `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | +| `--all` | switch | no | Fetch all pages by following opaque cursors | +| `--source <custom\|official\|all>` | string | no | Skill catalog: custom (default), official, or all | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. +- --source all combines one page from each catalog, or every page with --all; it does not accept --page. + +#### Examples + +```bash +bl managed-agent skill-list ``` ```bash -bl managed-agent skill-list --source all --output json +bl managed-agent skill-list --source official ``` ```bash -bl managed-agent skill-list --source custom --provider bailian +bl managed-agent skill-list --source all --all --output json ``` ### `bl managed-agent state import` @@ -618,3 +1947,110 @@ bl managed-agent validate ```bash bl managed-agent validate --file agents.yaml ``` + +### `bl managed-agent vault get` + +| Field | Value | +| ------------------ | -------------------------------------------- | +| **Name** | `managed-agent vault get` | +| **Description** | Get a Managed Agent vault | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent vault get --vault-id <id>` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------- | ------ | -------- | --------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--vault-id <id>` | string | yes | Vault ID | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent vault get --vault-id vault_abc +``` + +### `bl managed-agent vault list` + +| Field | Value | +| ------------------ | ------------------------------------------------------------------------------------------ | +| **Name** | `managed-agent vault list` | +| **Description** | List Managed Agent vaults | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent vault list [--limit <n>] [--page <cursor>] [--all] [--include-archived]` | + +#### Flags + +| Flag | Type | Required | Description | +| -------------------- | ------ | -------- | ------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--limit <n>` | number | no | Page size (1-100) | +| `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | +| `--all` | switch | no | Fetch all pages by following opaque cursors | +| `--include-archived` | switch | no | Include archived resources | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent vault list +``` + +```bash +bl managed-agent vault list --all --output json +``` + +### `bl managed-agent vault search` + +| Field | Value | +| ------------------ | ---------------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent vault search` | +| **Description** | Search Managed Agent vaults | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent vault search --query <text> [--limit <n>] [--page-limit <n>] [--include-archived]` | + +#### Flags + +| Flag | Type | Required | Description | +| -------------------- | ------ | -------- | ------------------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--limit <n>` | number | no | Page size (1-100) | +| `--query <text>` | string | yes | Case-insensitive text to find in IDs, names, and descriptions | +| `--page-limit <n>` | number | no | Maximum pages to scan for client-side search (default: 10) | +| `--include-archived` | switch | no | Include archived resources | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. + +#### Examples + +```bash +bl managed-agent vault search --query github +``` + +```bash +bl managed-agent vault search --query production --page-limit 20 --output json +``` From 4c7ef0c7a5d067cf9f1a1d0cf4c1e8eeea627eba Mon Sep 17 00:00:00 2001 From: chenanran555 <car534511@alibaba-inc.com> Date: Thu, 27 Aug 2026 11:44:00 +0800 Subject: [PATCH 02/10] feat(managed-agent): add scoped agent create command Add `bl managed-agent agent create` with automatic YAML key generation, atomic config updates, preview-by-default behavior, and create-only scoped apply that ignores unrelated resource drift. --- packages/cli/src/commands.ts | 2 + .../managed-agent/_engine/credentials.ts | 11 +- .../_engine/file-state-manager.ts | 4 +- .../commands/managed-agent/agent-create.ts | 443 ++++++++++++++++++ packages/commands/src/index.ts | 1 + .../commands/tests/credentials-bridge.test.ts | 12 + .../tests/e2e/managed-agent.e2e.test.ts | 163 +++++++ packages/commands/tests/e2e/topic-routes.ts | 1 + .../tests/managed-agent-create.test.ts | 94 ++++ skills/bailian-managed-agent/SKILL.md | 13 + .../bailian-managed-agent/reference/index.md | 121 ++--- .../reference/managed-agent.md | 159 ++++--- 12 files changed, 902 insertions(+), 122 deletions(-) create mode 100644 packages/commands/src/commands/managed-agent/agent-create.ts create mode 100644 packages/commands/tests/managed-agent-create.test.ts diff --git a/packages/cli/src/commands.ts b/packages/cli/src/commands.ts index f5cff6b69..68b3784ef 100644 --- a/packages/cli/src/commands.ts +++ b/packages/cli/src/commands.ts @@ -152,6 +152,7 @@ import { managedAgentSessionEvents, managedAgentSkillList, managedAgentCapabilities, + managedAgentAgentCreate, managedAgentAgentList, managedAgentAgentGet, managedAgentAgentSearch, @@ -350,6 +351,7 @@ export const commands: Record<string, AnyCommand> = { "managed-agent session events": managedAgentSessionEvents, "managed-agent skill-list": managedAgentSkillList, "managed-agent capabilities": managedAgentCapabilities, + "managed-agent agent create": managedAgentAgentCreate, "managed-agent agent list": managedAgentAgentList, "managed-agent agent get": managedAgentAgentGet, "managed-agent agent search": managedAgentAgentSearch, diff --git a/packages/commands/src/commands/managed-agent/_engine/credentials.ts b/packages/commands/src/commands/managed-agent/_engine/credentials.ts index 3dbfbaaea..63489f589 100644 --- a/packages/commands/src/commands/managed-agent/_engine/credentials.ts +++ b/packages/commands/src/commands/managed-agent/_engine/credentials.ts @@ -170,11 +170,16 @@ export function normalizeInterpolatedProviderBlocks(providers: Record<string, un * provider's `api_key` resolved empty (missing env var, or no bl login for * bailian). Replaces the SDK's raw `Environment variable '...' is not set` / * zod config error with a clean message plus a provider-specific hint. Validates - * every declared provider, so a project is only runnable once all its providers' - * keys are available; offline commands skip the check entirely. + * every declared provider by default. A scoped operation may pass its exact + * provider list so unrelated credentials cannot block it; offline commands skip + * the check entirely. */ -export function assertProviderCredentials(providers: Record<string, unknown>): void { +export function assertProviderCredentials( + providers: Record<string, unknown>, + targetProviders?: readonly string[], +): void { for (const [name, raw] of Object.entries(providers)) { + if (targetProviders && !targetProviders.includes(name)) continue; if (!raw || typeof raw !== "object") continue; const block = raw as Record<string, unknown>; if (!("api_key" in block)) continue; diff --git a/packages/commands/src/commands/managed-agent/_engine/file-state-manager.ts b/packages/commands/src/commands/managed-agent/_engine/file-state-manager.ts index 72a8fd066..8e1aff2b7 100644 --- a/packages/commands/src/commands/managed-agent/_engine/file-state-manager.ts +++ b/packages/commands/src/commands/managed-agent/_engine/file-state-manager.ts @@ -6,7 +6,7 @@ import { type StateScope, } from "@openagentpack/sdk"; -function createStateScope(configPath: string, projectName?: string): StateScope { +export function createFileStateScope(configPath: string, projectName?: string): StateScope { const resolved = resolve(configPath); return { projectId: projectName ?? basename(dirname(resolved)) }; } @@ -19,6 +19,6 @@ export async function loadFileState( ): Promise<IStateManager> { const resolved = resolve(configPath); const backend = new LocalFileStateBackend({ configPath: resolved, statePath }); - const path = backend.getStatePath(createStateScope(resolved, projectName)); + const path = backend.getStatePath(createFileStateScope(resolved, projectName)); return StateManager.load(path); } diff --git a/packages/commands/src/commands/managed-agent/agent-create.ts b/packages/commands/src/commands/managed-agent/agent-create.ts new file mode 100644 index 000000000..6af87cdf4 --- /dev/null +++ b/packages/commands/src/commands/managed-agent/agent-create.ts @@ -0,0 +1,443 @@ +import { randomUUID } from "node:crypto"; +import { readFile, rename, stat, unlink, writeFile } from "node:fs/promises"; +import { basename, dirname, resolve } from "node:path"; +import { + buildAgentDecl, + type IStateManager, + LocalFileStateBackend, + planAgentResourcesWithStateBackend, + type ResolvedProjectConfig, + type ResourceAddress, + resolveProjectConfigFromObject, + syncAgentResourcesWithStateBackend, +} from "@openagentpack/sdk"; +import { + BailianError, + defineCommand, + detectOutputFormat, + ExitCode, + type FlagsDef, +} from "bailian-cli-core"; +import { emitBare, emitResult } from "bailian-cli-runtime"; +import { parseDocument } from "yaml"; +import { formatResourceLabel } from "./_engine/address-utils.ts"; +import { CREDENTIALS_NOTE, resolveAgentProjectConfig } from "./_engine/config-loader.ts"; +import { assertProviderCredentials } from "./_engine/credentials.ts"; +import { withStdoutProtected } from "./_engine/console-capture.ts"; +import { withAgentErrors } from "./_engine/errors.ts"; +import { createFileStateScope } from "./_engine/file-state-manager.ts"; +import { installSdkTransport } from "./_engine/transport.ts"; + +const CREATE_FLAGS = { + name: { + type: "string", + valueHint: "<name>", + required: true, + description: { + "en-US": "Remote Agent display name; the YAML key is generated automatically", + "zh-CN": "远端 Agent 显示名称;YAML key 将自动生成", + }, + }, + model: { + type: "string", + valueHint: "<model>", + required: true, + description: { "en-US": "Model ID", "zh-CN": "模型 ID" }, + }, + instructions: { + type: "string", + valueHint: "<text|path>", + required: true, + description: { + "en-US": "Inline instructions or a ./, ../, or absolute file path", + "zh-CN": "内联指令,或以 ./、../、/ 开头的文件路径", + }, + }, + description: { + type: "string", + valueHint: "<text>", + description: { "en-US": "Agent description", "zh-CN": "Agent 描述" }, + }, + provider: { + type: "string", + valueHint: "<name>", + description: { + "en-US": "Target provider; inferred when the config has one effective provider", + "zh-CN": "目标 Provider;配置只有一个有效 Provider 时自动推断", + }, + }, + environment: { + type: "string", + valueHint: "<name>", + description: { + "en-US": "Existing environment key from agents.yaml", + "zh-CN": "agents.yaml 中已有的 Environment key", + }, + }, + vault: { + type: "string", + valueHint: "<name>", + description: { + "en-US": "Existing vault key from agents.yaml", + "zh-CN": "agents.yaml 中已有的 Vault key", + }, + }, + skill: { + type: "array", + valueHint: "<name>", + description: { + "en-US": "Existing custom Skill key from agents.yaml (repeatable)", + "zh-CN": "agents.yaml 中已有的自定义 Skill key(可重复)", + }, + }, + tool: { + type: "array", + valueHint: "<name>", + description: { + "en-US": "Builtin tool name (repeatable)", + "zh-CN": "内置工具名称(可重复)", + }, + }, + file: { + type: "string", + valueHint: "<path>", + description: { + "en-US": "Config file path (default: agents.yaml)", + "zh-CN": "配置文件路径(默认:agents.yaml)", + }, + }, + yes: { + type: "switch", + description: { + "en-US": "Write YAML and run the scoped remote create", + "zh-CN": "写入 YAML 并执行定向远端创建", + }, + }, +} satisfies FlagsDef; + +type BuiltAgentDecl = ReturnType<typeof buildAgentDecl>["agent"]; + +interface AgentKeySelection { + key: string; + reusedPending: boolean; +} + +function canonicalJson(value: unknown): string { + const normalize = (candidate: unknown): unknown => { + if (Array.isArray(candidate)) return candidate.map(normalize); + if (!candidate || typeof candidate !== "object") return candidate; + return Object.fromEntries( + Object.entries(candidate as Record<string, unknown>) + .filter(([, entry]) => entry !== undefined) + .sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey)) + .map(([key, entry]) => [key, normalize(entry)]), + ); + }; + return JSON.stringify(normalize(value)); +} + +export function normalizeAgentKey(displayName: string): string { + const normalized = displayName + .normalize("NFKC") + .trim() + .toLowerCase() + .replace(/[^\p{L}\p{N}]+/gu, "-") + .replace(/^-+|-+$/g, ""); + return normalized || "agent"; +} + +export function selectAgentKey(options: { + displayName: string; + provider: string; + agents: Record<string, BuiltAgentDecl>; + candidate: BuiltAgentDecl; + state: IStateManager; +}): AgentKeySelection { + const tracked = new Set( + options.state + .listResources() + .filter( + (resource) => + resource.address.provider === options.provider && + (resource.address.type === "agent" || resource.address.type === "template"), + ) + .map((resource) => resource.address.name), + ); + const candidateJson = canonicalJson(options.candidate); + for (const [key, declaration] of Object.entries(options.agents)) { + const effectiveName = declaration.name ?? key; + if (effectiveName !== options.displayName || tracked.has(key)) continue; + if (canonicalJson({ ...declaration, name: effectiveName }) === candidateJson) { + return { key, reusedPending: true }; + } + } + + const baseKey = normalizeAgentKey(options.displayName); + if (!(baseKey in options.agents)) return { key: baseKey, reusedPending: false }; + let suffix = 2; + while (`${baseKey}-${suffix}` in options.agents) suffix += 1; + return { key: `${baseKey}-${suffix}`, reusedPending: false }; +} + +export async function replaceConfigAtomically( + configPath: string, + expectedSource: string, + nextSource: string, +): Promise<void> { + const destination = resolve(configPath); + const currentSource = await readFile(destination, "utf8"); + if (currentSource !== expectedSource) { + throw new BailianError( + `${configPath} changed while Agent create was being prepared.`, + ExitCode.GENERAL, + "Review the latest YAML and re-run the command; no file was overwritten.", + ); + } + const currentStat = await stat(destination); + const temporary = resolve(dirname(destination), `.${basename(destination)}.${randomUUID()}.tmp`); + await writeFile(temporary, nextSource, { flag: "wx", mode: currentStat.mode }); + try { + await rename(temporary, destination); + } catch (error) { + await unlink(temporary).catch(() => undefined); + throw error; + } +} + +function resolveTargetProvider( + config: ResolvedProjectConfig, + requested: string | undefined, +): string { + if (requested === "all") { + throw new BailianError("--provider all is not valid for Agent create.", ExitCode.USAGE); + } + if (requested) { + if (requested in config.providers) return requested; + throw new BailianError( + `Provider '${requested}' is not configured in agents.yaml.`, + ExitCode.USAGE, + ); + } + const defaultProvider = config.defaults?.provider; + if (defaultProvider && defaultProvider !== "all") return defaultProvider; + const configuredProviders = Object.keys(config.providers); + if (configuredProviders.length === 1) return configuredProviders[0]!; + throw new BailianError( + "Agent create cannot infer one target provider.", + ExitCode.USAGE, + "Pass --provider <name> when defaults.provider is 'all' or multiple providers are configured.", + ); +} + +function rootAddress( + selectedAddresses: ResourceAddress[] | undefined, + agentKey: string, +): ResourceAddress | undefined { + return selectedAddresses?.find( + (address) => + address.name === agentKey && (address.type === "agent" || address.type === "template"), + ); +} + +export default defineCommand({ + description: { + "en-US": "Declare and create one Managed Agent through an isolated YAML apply", + "zh-CN": "通过隔离的 YAML Apply 声明并创建一个托管 Agent", + }, + auth: "apiKey", + usageArgs: + "--name <name> --model <model> --instructions <text|path> [--description <text>] [--provider <name>] [--environment <name>] [--vault <name>] [--skill <name>...] [--tool <name>...] [--file <path>] [--yes]", + flags: CREATE_FLAGS, + exampleArgs: [ + '--name assistant --model qwen3.8-max --instructions "You are helpful."', + "--name assistant --model qwen3.8-max --instructions ./prompts/assistant.md --environment dev --skill search --yes", + ], + notes: [ + ...CREDENTIALS_NOTE, + { + "en-US": + "Without --yes, previews the generated YAML key and scoped plan. --dry-run stays offline. Unrelated resources are not refreshed or drift-checked.", + "zh-CN": + "不带 --yes 时预览自动生成的 YAML key 和定向计划;--dry-run 完全离线。无关资源不会刷新或检测 Drift。", + }, + ], + validate: (flags) => { + if (!flags.name.trim()) return "--name must not be empty."; + if (!flags.model.trim()) return "--model must not be empty."; + if (!flags.instructions.trim()) return "--instructions must not be empty."; + return undefined; + }, + async run(ctx) { + const { flags, settings } = ctx; + const format = detectOutputFormat(settings.output); + const file = flags.file ?? "agents.yaml"; + const sourceBeforeLoad = await readFile(resolve(file), "utf8").catch((error) => { + const code = (error as NodeJS.ErrnoException).code; + if (code === "ENOENT") { + throw new BailianError( + `Config file not found: ${file}`, + ExitCode.USAGE, + "Run `bl managed-agent init` first.", + ); + } + throw error; + }); + const loaded = await withAgentErrors(() => + resolveAgentProjectConfig(ctx, file, { credentials: "none" }), + ); + const source = await readFile(loaded.configPath, "utf8"); + if (source !== sourceBeforeLoad) { + throw new BailianError( + `${file} changed while it was being loaded.`, + ExitCode.GENERAL, + "Re-run the command against the latest file.", + ); + } + + const provider = resolveTargetProvider(loaded.config, flags.provider); + if (!settings.dryRun) assertProviderCredentials(loaded.config.providers, [provider]); + installSdkTransport(ctx); + + const rawAgent = buildAgentDecl(undefined, { + name: flags.name.trim(), + description: flags.description, + model: flags.model, + instructions: flags.instructions, + provider, + environment: flags.environment, + vault: flags.vault, + builtinTools: flags.tool, + skills: flags.skill?.map((skillName) => ({ kind: "custom", name: skillName })), + }).agent; + const temporaryKey = "__bailian_cli_agent_create_candidate__"; + const resolvedCandidate = await withAgentErrors(() => + resolveProjectConfigFromObject( + { + ...loaded.config, + agents: { ...loaded.config.agents, [temporaryKey]: rawAgent }, + }, + { projectName: loaded.projectName, basePath: dirname(loaded.configPath) }, + ), + ); + const candidateAgent = resolvedCandidate.config.agents![temporaryKey]! as BuiltAgentDecl; + const stateBackend = new LocalFileStateBackend({ configPath: loaded.configPath }); + const stateScope = createFileStateScope(loaded.configPath, loaded.projectName); + const keySelection = await stateBackend.read(stateScope, (state) => + selectAgentKey({ + displayName: flags.name.trim(), + provider, + agents: (loaded.config.agents ?? {}) as Record<string, BuiltAgentDecl>, + candidate: candidateAgent, + state, + }), + ); + const agentKey = keySelection.key; + const candidateConfig = structuredClone(loaded.config); + candidateConfig.agents = { ...candidateConfig.agents, [agentKey]: candidateAgent }; + candidateConfig._resolved = true; + + const document = parseDocument(source); + if (document.errors.length > 0) { + throw new BailianError( + `YAML parse error: ${document.errors.map((error) => error.message).join("; ")}`, + ExitCode.USAGE, + ); + } + document.setIn(["agents", agentKey], rawAgent); + const nextSource = document.toString(); + const backendInput = { + projectName: loaded.projectName, + config: candidateConfig, + configPath: loaded.configPath, + providers: { [provider]: candidateConfig.providers[provider] }, + stateBackend, + stateScope, + }; + + if (settings.dryRun || !flags.yes) { + const planned = await withAgentErrors(() => + withStdoutProtected(() => + planAgentResourcesWithStateBackend(backendInput, agentKey, { + refresh: !settings.dryRun, + quiet: format === "json", + mode: "create-only", + }), + ), + ); + const readyToCreate = !planned.diagnostics.some( + (diagnostic) => diagnostic.severity === "error", + ); + const result = { + agent: { key: agentKey, name: flags.name.trim(), provider }, + config_file: loaded.configPath, + yaml_written: false, + reused_pending: keySelection.reusedPending, + requires_confirmation: !settings.dryRun, + ready_to_create: readyToCreate, + actions: planned.actions, + diagnostics: planned.diagnostics, + }; + if (format === "json") { + emitResult(result, format); + } else { + emitBare(`Generated YAML key: ${agentKey}`); + for (const diagnostic of planned.diagnostics) { + emitBare(`[${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`); + } + for (const action of planned.actions.filter((entry) => entry.action !== "no-op")) { + const icon = action.action === "create" ? "+" : action.action === "update" ? "~" : "-"; + emitBare(` ${icon} ${formatResourceLabel(action.address)}`); + } + emitBare( + settings.dryRun + ? "Dry run: YAML, State, and remote resources were not changed." + : "Preview only: re-run with --yes to write YAML and create this Agent.", + ); + } + return; + } + + await replaceConfigAtomically(loaded.configPath, source, nextSource); + const run = await withAgentErrors(() => + withStdoutProtected(() => + syncAgentResourcesWithStateBackend(backendInput, agentKey, { + refresh: true, + quiet: format === "json", + mode: "create-only", + policy: "block", + }), + ), + ); + const remoteId = await stateBackend.read(stateScope, (state) => { + const selectedRoot = rootAddress( + run.actions.map((action) => action.address), + agentKey, + ); + return selectedRoot ? state.getResource(selectedRoot)?.remote_id : undefined; + }); + const result = { + agent: { key: agentKey, name: flags.name.trim(), provider, remote_id: remoteId }, + config_file: loaded.configPath, + yaml_written: true, + reused_pending: keySelection.reusedPending, + status: run.status, + actions: run.actions, + diagnostics: run.diagnostics, + results: run.results, + error: run.error, + }; + if (format === "json") emitResult(result, format); + else { + emitBare(`Wrote ${loaded.configPath} with Agent key '${agentKey}'.`); + if (run.status === "completed") emitBare(`Created Agent '${flags.name.trim()}'.`); + else emitBare(`Scoped create ${run.status}: ${run.error ?? "unknown error"}`); + } + if (run.status !== "completed") { + throw new BailianError( + run.error ?? "Scoped Agent create failed.", + ExitCode.GENERAL, + "The YAML declaration was kept. Fix the related dependency or provider error, then re-run the same create command.", + ); + } + }, +}); diff --git a/packages/commands/src/index.ts b/packages/commands/src/index.ts index 6ec38d97e..a3655122a 100644 --- a/packages/commands/src/index.ts +++ b/packages/commands/src/index.ts @@ -149,6 +149,7 @@ export { default as managedAgentSessionSend } from "./commands/managed-agent/ses 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 { default as managedAgentAgentCreate } from "./commands/managed-agent/agent-create.ts"; export { managedAgentAgentGet, managedAgentAgentList, diff --git a/packages/commands/tests/credentials-bridge.test.ts b/packages/commands/tests/credentials-bridge.test.ts index 3a1fb982d..dde12cfaf 100644 --- a/packages/commands/tests/credentials-bridge.test.ts +++ b/packages/commands/tests/credentials-bridge.test.ts @@ -166,6 +166,18 @@ test("assert:所有已声明 provider 的 key 非空时通过", () => { ).not.toThrow(); }); +test("assert:定向检查不会被无关 provider 的空 key 阻塞", () => { + expect(() => + assertProviderCredentials( + { + bailian: { api_key: "x" }, + claude: { api_key: "" }, + }, + ["bailian"], + ), + ).not.toThrow(); +}); + test("assert:claude key 为空抛 AUTH 且 hint 指向 ANTHROPIC_API_KEY", () => { let thrown: unknown; try { diff --git a/packages/commands/tests/e2e/managed-agent.e2e.test.ts b/packages/commands/tests/e2e/managed-agent.e2e.test.ts index 6bb31e620..f082b5a0a 100644 --- a/packages/commands/tests/e2e/managed-agent.e2e.test.ts +++ b/packages/commands/tests/e2e/managed-agent.e2e.test.ts @@ -1,5 +1,10 @@ +import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import http from "node:http"; +import type { AddressInfo } from "node:net"; +import { tmpdir } from "node:os"; import { join } from "node:path"; import { describe, expect, test } from "vite-plus/test"; +import { parse } from "yaml"; import { e2eFixturesDir, parseStdoutJson, runCommandE2e } from "./helpers.ts"; import { MANAGED_AGENT_ROUTES } from "./topic-routes.ts"; @@ -9,6 +14,7 @@ const AGENTS_DEPLOYMENT_INVALID_YAML = join( "managed-agent", "agents-deployment-invalid.yaml", ); +const AGENTS_YAML = join(e2eFixturesDir, "managed-agent", "agents.yaml"); const DEPLOYMENT_SAFETY_DIAGNOSTIC_CODES = [ "bailian.deployment.initial_events.message_required", @@ -132,6 +138,125 @@ describe("e2e: managed-agent", () => { expect(stderr).toMatch(/--file|--provider|--yes/i); }); + test("managed-agent agent create --help 展示声明和确认参数", async () => { + const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [ + "managed-agent", + "agent", + "create", + "--help", + ]); + expect(exitCode, stderr).toBe(0); + expect(stderr).toMatch(/--name|--model|--instructions|--skill|--yes/i); + expect(stderr).not.toMatch(/--key/i); + }); + + test("managed-agent agent create 缺少 --name 时退出为用法错误 (2)", async () => { + const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [ + "managed-agent", + "agent", + "create", + "--model", + "qwen3.8-max", + "--instructions", + "help", + "--quiet", + ]); + expect(exitCode).toBe(2); + expect(stderr).toMatch(/--name|Missing required/i); + }); + + test("managed-agent agent create 默认只预览,不写 YAML", async () => { + const sourceBefore = await readFile(AGENTS_YAML, "utf8"); + const { stdout, stderr, exitCode } = await runCommandE2e( + MANAGED_AGENT_ROUTES, + [ + "managed-agent", + "agent", + "create", + "--name", + "Create Confirm", + "--model", + "qwen3.8-max", + "--instructions", + "Preview before create", + "--file", + AGENTS_YAML, + "--output", + "json", + ], + { DASHSCOPE_API_KEY: "sk-e2e-agent-create" }, + ); + expect(exitCode, stderr).toBe(0); + const data = parseStdoutJson<{ + agent?: { key?: string; name?: string }; + yaml_written?: boolean; + requires_confirmation?: boolean; + ready_to_create?: boolean; + }>(stdout); + expect(data.agent).toEqual( + expect.objectContaining({ key: "create-confirm", name: "Create Confirm" }), + ); + expect(data.yaml_written).toBe(false); + expect(data.requires_confirmation).toBe(true); + expect(data.ready_to_create).toBe(true); + expect(await readFile(AGENTS_YAML, "utf8")).toBe(sourceBefore); + }); + + test("managed-agent agent create 远端失败后保留 YAML,重试复用 key", async () => { + const directory = await mkdtemp(join(tmpdir(), "bl-agent-create-e2e-")); + const configPath = join(directory, "agents.yaml"); + await writeFile(configPath, await readFile(AGENTS_YAML, "utf8"), "utf8"); + const requestBodies: Array<Record<string, unknown>> = []; + const server = http.createServer((request, response) => { + const chunks: Buffer[] = []; + request.on("data", (chunk: Buffer) => chunks.push(chunk)); + request.on("end", () => { + const body = Buffer.concat(chunks).toString("utf8"); + if (body) requestBodies.push(JSON.parse(body) as Record<string, unknown>); + response.writeHead(500, { "Content-Type": "application/json" }); + response.end(JSON.stringify({ message: "intentional create failure" })); + }); + }); + await new Promise<void>((resolveListen) => server.listen(0, "127.0.0.1", resolveListen)); + const address = server.address() as AddressInfo; + const args = [ + "managed-agent", + "agent", + "create", + "--name", + "Retry Agent", + "--model", + "qwen3.8-max", + "--instructions", + "Retry safely", + "--file", + configPath, + "--yes", + "--output", + "json", + ]; + const env = { + DASHSCOPE_API_KEY: "sk-e2e-agent-create", + BAILIAN_BASE_URL: `http://127.0.0.1:${address.port}/api/v1/agentstudio`, + }; + try { + for (let attempt = 0; attempt < 2; attempt += 1) { + const result = await runCommandE2e(MANAGED_AGENT_ROUTES, args, env); + expect(result.exitCode, result.stderr).toBe(1); + } + const config = parse(await readFile(configPath, "utf8")) as { + agents: Record<string, { name?: string }>; + }; + expect(config.agents["retry-agent"]?.name).toBe("Retry Agent"); + expect(config.agents["retry-agent-2"]).toBeUndefined(); + expect(requestBodies).toHaveLength(2); + expect(requestBodies.every((body) => body.name === "Retry Agent")).toBe(true); + } finally { + await new Promise<void>((resolveClose) => server.close(() => resolveClose())); + await rm(directory, { recursive: true, force: true }); + } + }); + test("managed-agent session delete 缺少 --session-id 时退出为用法错误 (2)", async () => { const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [ "managed-agent", @@ -311,6 +436,44 @@ describe("e2e: managed-agent(--dry-run 短路,不联网不写盘)", () => expect(data.would_apply?.provider).toBe("all"); }); + test("agent create --dry-run 自动生成 key 且不改 YAML", async () => { + const sourceBefore = await readFile(AGENTS_YAML, "utf8"); + const { stdout, stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [ + "managed-agent", + "agent", + "create", + "--dry-run", + "--name", + "Create Preview", + "--model", + "qwen3.8-max", + "--instructions", + "Preview only", + "--file", + AGENTS_YAML, + "--output", + "json", + ]); + expect(exitCode, stderr).toBe(0); + const data = parseStdoutJson<{ + agent?: { key?: string; name?: string }; + yaml_written?: boolean; + actions?: Array<{ action?: string; address?: { name?: string } }>; + }>(stdout); + expect(data.agent).toEqual( + expect.objectContaining({ key: "create-preview", name: "Create Preview" }), + ); + expect(data.yaml_written).toBe(false); + expect(data.actions).toContainEqual( + expect.objectContaining({ + action: "create", + address: expect.objectContaining({ name: "create-preview" }), + }), + ); + expect(data.actions?.every((action) => action.address?.name === "create-preview")).toBe(true); + expect(await readFile(AGENTS_YAML, "utf8")).toBe(sourceBefore); + }); + test("destroy --dry-run 仅输出计划", async () => { const { stdout, stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [ "managed-agent", diff --git a/packages/commands/tests/e2e/topic-routes.ts b/packages/commands/tests/e2e/topic-routes.ts index 5104072a9..ab962b2cb 100644 --- a/packages/commands/tests/e2e/topic-routes.ts +++ b/packages/commands/tests/e2e/topic-routes.ts @@ -198,6 +198,7 @@ export const MANAGED_AGENT_ROUTES: E2eRouteExports = { "managed-agent session events": "managedAgentSessionEvents", "managed-agent skill-list": "managedAgentSkillList", "managed-agent capabilities": "managedAgentCapabilities", + "managed-agent agent create": "managedAgentAgentCreate", "managed-agent agent list": "managedAgentAgentList", "managed-agent agent get": "managedAgentAgentGet", "managed-agent agent search": "managedAgentAgentSearch", diff --git a/packages/commands/tests/managed-agent-create.test.ts b/packages/commands/tests/managed-agent-create.test.ts new file mode 100644 index 000000000..edbe39fd3 --- /dev/null +++ b/packages/commands/tests/managed-agent-create.test.ts @@ -0,0 +1,94 @@ +import { chmod, mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { buildAgentDecl, StateManager } from "@openagentpack/sdk"; +import { expect, test } from "vite-plus/test"; +import { + normalizeAgentKey, + replaceConfigAtomically, + selectAgentKey, +} from "../src/commands/managed-agent/agent-create.ts"; + +function candidate(instructions = "help") { + return buildAgentDecl(undefined, { + name: "Assistant", + model: "qwen3.8-max", + instructions, + provider: "bailian", + }).agent; +} + +test("Agent key 从显示名生成并保留 Unicode", () => { + expect(normalizeAgentKey(" Data Assistant ")).toBe("data-assistant"); + expect(normalizeAgentKey("数据分析 助手")).toBe("数据分析-助手"); + expect(normalizeAgentKey("***")).toBe("agent"); +}); + +test("同名已跟踪 Agent 分配递增 key", () => { + const state = StateManager.initialize("/tmp/bailian-cli-agent-create-state.json"); + state.setResource({ + address: { type: "agent", name: "assistant", provider: "bailian" }, + remote_id: "agent_existing", + content_hash: "hash", + }); + + expect( + selectAgentKey({ + displayName: "Assistant", + provider: "bailian", + agents: { + assistant: candidate(), + "assistant-2": { ...candidate(), instructions: "other" }, + }, + candidate: candidate(), + state, + }), + ).toEqual({ key: "assistant-3", reusedPending: false }); +}); + +test("远端失败后复用完全相同的待创建声明", () => { + const state = StateManager.initialize("/tmp/bailian-cli-agent-create-pending-state.json"); + + expect( + selectAgentKey({ + displayName: "Assistant", + provider: "bailian", + agents: { assistant: candidate() }, + candidate: candidate(), + state, + }), + ).toEqual({ key: "assistant", reusedPending: true }); +}); + +test("同名待创建声明配置不同则创建新的逻辑 key", () => { + const state = StateManager.initialize("/tmp/bailian-cli-agent-create-different-state.json"); + + expect( + selectAgentKey({ + displayName: "Assistant", + provider: "bailian", + agents: { assistant: candidate("old") }, + candidate: candidate("new"), + state, + }), + ).toEqual({ key: "assistant-2", reusedPending: false }); +}); + +test("YAML 替换校验原内容并保留文件权限", async () => { + const directory = await mkdtemp(join(tmpdir(), "bailian-agent-create-")); + const configPath = join(directory, "agents.yaml"); + try { + await writeFile(configPath, "version: old\n", "utf8"); + await chmod(configPath, 0o640); + await replaceConfigAtomically(configPath, "version: old\n", "version: new\n"); + expect(await readFile(configPath, "utf8")).toBe("version: new\n"); + expect((await stat(configPath)).mode & 0o777).toBe(0o640); + + await expect( + replaceConfigAtomically(configPath, "version: stale\n", "version: overwritten\n"), + ).rejects.toThrow(/changed while Agent create was being prepared/); + expect(await readFile(configPath, "utf8")).toBe("version: new\n"); + } finally { + await rm(directory, { recursive: true, force: true }); + } +}); diff --git a/skills/bailian-managed-agent/SKILL.md b/skills/bailian-managed-agent/SKILL.md index cf5005930..69b55f361 100644 --- a/skills/bailian-managed-agent/SKILL.md +++ b/skills/bailian-managed-agent/SKILL.md @@ -40,6 +40,19 @@ API-oriented commands do not replace IaC. Agent / Environment / Skill / Vault 5. Destroy bl managed-agent destroy --yes # only after user confirmation ``` +## Scoped single-Agent create + +`bl managed-agent agent create`仍然先把声明写入 `agents.yaml`,再通过 SDK 的定向 Plan/Apply 创建远端资源, +不是绕过 State 的命令式 API 调用: + +- 用户只提供 Agent `name`;CLI 自动生成稳定的 YAML 逻辑 key,同名 Agent 用递增后缀并存。 +- 默认只预览自动 key 和定向计划;`--dry-run` 完全离线,只有显式 `--yes` 才写 YAML 并创建远端 Agent。 +- 定向流程只刷新目标 Agent 及其传递依赖;无关资源不检测 Drift、不产生 action,也不阻塞。 +- 目标 Agent 必须是 `create`,相关依赖必须已经处于 `no-op`;项目级 Drift 和删除仍由全量 `plan/apply` 处理。 +- 远端创建失败时保留 YAML 声明;修复相关依赖或 Provider 错误后,重复相同命令会复用待创建 key。 + +具体 flags、usage 和 examples 以 `reference/` 或 `bl managed-agent agent create --help` 为准。 + ## Deployment as IaC Deployment 与 Agent 一样声明在 `agents.yaml` 中,并复用同一条 `validate → plan → apply → destroy` IaC 链路; diff --git a/skills/bailian-managed-agent/reference/index.md b/skills/bailian-managed-agent/reference/index.md index 46f2a8721..07f109f2b 100644 --- a/skills/bailian-managed-agent/reference/index.md +++ b/skills/bailian-managed-agent/reference/index.md @@ -9,69 +9,70 @@ Use this index for the skill-scoped quick index and global flags. ## Quick index -| 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) | +| Command | Authentication | Description | Detail | +| --------------------------------------- | -------------- | ------------------------------------------------------------------- | ------------------------------------ | +| `bl managed-agent agent create` | API Key | Declare and create one Managed Agent through an isolated YAML apply | [managed-agent.md](managed-agent.md) | +| `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` | `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) | +| Group | Commands | Reference | +| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------ | +| `managed-agent` | `agent create`, `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 diff --git a/skills/bailian-managed-agent/reference/managed-agent.md b/skills/bailian-managed-agent/reference/managed-agent.md index 7836606b2..93600bc71 100644 --- a/skills/bailian-managed-agent/reference/managed-agent.md +++ b/skills/bailian-managed-agent/reference/managed-agent.md @@ -7,66 +7,111 @@ Index: [index.md](index.md) ## Commands in this group -| Command | Authentication | Description | -| --------------------------------------- | -------------- | ------------------------------------------------------------- | -| `bl managed-agent agent get` | API Key | Get a Managed Agent | -| `bl managed-agent agent list` | API Key | List Managed Agents | -| `bl managed-agent agent search` | API Key | Search Managed Agents | -| `bl managed-agent agent versions` | API Key | List Managed Agent versions | -| `bl managed-agent apply` | API Key | Apply planned changes to create/update/delete agent resources | -| `bl managed-agent capabilities` | No Auth | Show operation-level Managed Agents API capabilities | -| `bl managed-agent deployment get` | API Key | Get a Managed Agent deployment | -| `bl managed-agent deployment list` | API Key | List Managed Agent deployments | -| `bl managed-agent deployment pause` | API Key | Pause a Managed Agent deployment | -| `bl managed-agent deployment run` | API Key | Run a Managed Agent deployment now | -| `bl managed-agent deployment runs get` | API Key | Get a Managed Agent deployment run | -| `bl managed-agent deployment runs list` | API Key | List runs for a Managed Agent deployment | -| `bl managed-agent deployment search` | API Key | Search Managed Agent deployments | -| `bl managed-agent deployment unpause` | API Key | Unpause a Managed Agent deployment | -| `bl managed-agent destroy` | API Key | Destroy all managed agent resources tracked in state | -| `bl managed-agent environment get` | API Key | Get a Managed Agent environment | -| `bl managed-agent environment list` | API Key | List Managed Agent environments | -| `bl managed-agent environment search` | API Key | Search Managed Agent environments | -| `bl managed-agent file delete` | API Key | Delete a Managed Agent file | -| `bl managed-agent file download` | API Key | Download Managed Agent file content | -| `bl managed-agent file get` | API Key | Get Managed Agent file metadata | -| `bl managed-agent file list` | API Key | List Managed Agent files | -| `bl managed-agent file search` | API Key | Search Managed Agent files | -| `bl managed-agent file upload` | API Key | Upload a Managed Agent file | -| `bl managed-agent init` | No Auth | Create a new agents.yaml template | -| `bl managed-agent plan` | API Key | Show what changes would be applied to agent infrastructure | -| `bl managed-agent session archive` | API Key | Archive a Managed Agent session | -| `bl managed-agent session create` | API Key | Create a new session for an agent | -| `bl managed-agent session debug` | API Key | Aggregate session diagnostics | -| `bl managed-agent session delete` | API Key | Delete a session | -| `bl managed-agent session event list` | API Key | List events for a Managed Agent session | -| `bl managed-agent session event send` | API Key | Send raw events to a Managed Agent session | -| `bl managed-agent session event stream` | API Key | Stream events from a Managed Agent session | -| `bl managed-agent session events` | API Key | List events for a Managed Agent session | -| `bl managed-agent session export` | API Key | Export session diagnostics as a ZIP | -| `bl managed-agent session get` | API Key | Get details of a session | -| `bl managed-agent session list` | API Key | List sessions from the provider | -| `bl managed-agent session run` | API Key | Create a session, send a message, and stream the response | -| `bl managed-agent session search` | API Key | Search Managed Agent sessions | -| `bl managed-agent session send` | API Key | Send a message to an existing session and stream the response | -| `bl managed-agent session update` | API Key | Update a Managed Agent session | -| `bl managed-agent skill download` | API Key | Download a Managed Agent skill version | -| `bl managed-agent skill get` | API Key | Get a Managed Agent skill | -| `bl managed-agent skill list` | API Key | List Managed Agent skills | -| `bl managed-agent skill search` | API Key | Search Managed Agent skills | -| `bl managed-agent skill versions` | API Key | List Managed Agent skill versions | -| `bl managed-agent skill-list` | API Key | List Managed Agent skills | -| `bl managed-agent state import` | API Key | Import an existing remote resource into agents state | -| `bl managed-agent state list` | No Auth | List resources tracked in agents state | -| `bl managed-agent state rm` | No Auth | Remove a resource from state without destroying it remotely | -| `bl managed-agent state show` | No Auth | Show details of a resource in agents state | -| `bl managed-agent validate` | No Auth | Validate an agents.yaml configuration (offline) | -| `bl managed-agent vault get` | API Key | Get a Managed Agent vault | -| `bl managed-agent vault list` | API Key | List Managed Agent vaults | -| `bl managed-agent vault search` | API Key | Search Managed Agent vaults | +| Command | Authentication | Description | +| --------------------------------------- | -------------- | ------------------------------------------------------------------- | +| `bl managed-agent agent create` | API Key | Declare and create one Managed Agent through an isolated YAML apply | +| `bl managed-agent agent get` | API Key | Get a Managed Agent | +| `bl managed-agent agent list` | API Key | List Managed Agents | +| `bl managed-agent agent search` | API Key | Search Managed Agents | +| `bl managed-agent agent versions` | API Key | List Managed Agent versions | +| `bl managed-agent apply` | API Key | Apply planned changes to create/update/delete agent resources | +| `bl managed-agent capabilities` | No Auth | Show operation-level Managed Agents API capabilities | +| `bl managed-agent deployment get` | API Key | Get a Managed Agent deployment | +| `bl managed-agent deployment list` | API Key | List Managed Agent deployments | +| `bl managed-agent deployment pause` | API Key | Pause a Managed Agent deployment | +| `bl managed-agent deployment run` | API Key | Run a Managed Agent deployment now | +| `bl managed-agent deployment runs get` | API Key | Get a Managed Agent deployment run | +| `bl managed-agent deployment runs list` | API Key | List runs for a Managed Agent deployment | +| `bl managed-agent deployment search` | API Key | Search Managed Agent deployments | +| `bl managed-agent deployment unpause` | API Key | Unpause a Managed Agent deployment | +| `bl managed-agent destroy` | API Key | Destroy all managed agent resources tracked in state | +| `bl managed-agent environment get` | API Key | Get a Managed Agent environment | +| `bl managed-agent environment list` | API Key | List Managed Agent environments | +| `bl managed-agent environment search` | API Key | Search Managed Agent environments | +| `bl managed-agent file delete` | API Key | Delete a Managed Agent file | +| `bl managed-agent file download` | API Key | Download Managed Agent file content | +| `bl managed-agent file get` | API Key | Get Managed Agent file metadata | +| `bl managed-agent file list` | API Key | List Managed Agent files | +| `bl managed-agent file search` | API Key | Search Managed Agent files | +| `bl managed-agent file upload` | API Key | Upload a Managed Agent file | +| `bl managed-agent init` | No Auth | Create a new agents.yaml template | +| `bl managed-agent plan` | API Key | Show what changes would be applied to agent infrastructure | +| `bl managed-agent session archive` | API Key | Archive a Managed Agent session | +| `bl managed-agent session create` | API Key | Create a new session for an agent | +| `bl managed-agent session debug` | API Key | Aggregate session diagnostics | +| `bl managed-agent session delete` | API Key | Delete a session | +| `bl managed-agent session event list` | API Key | List events for a Managed Agent session | +| `bl managed-agent session event send` | API Key | Send raw events to a Managed Agent session | +| `bl managed-agent session event stream` | API Key | Stream events from a Managed Agent session | +| `bl managed-agent session events` | API Key | List events for a Managed Agent session | +| `bl managed-agent session export` | API Key | Export session diagnostics as a ZIP | +| `bl managed-agent session get` | API Key | Get details of a session | +| `bl managed-agent session list` | API Key | List sessions from the provider | +| `bl managed-agent session run` | API Key | Create a session, send a message, and stream the response | +| `bl managed-agent session search` | API Key | Search Managed Agent sessions | +| `bl managed-agent session send` | API Key | Send a message to an existing session and stream the response | +| `bl managed-agent session update` | API Key | Update a Managed Agent session | +| `bl managed-agent skill download` | API Key | Download a Managed Agent skill version | +| `bl managed-agent skill get` | API Key | Get a Managed Agent skill | +| `bl managed-agent skill list` | API Key | List Managed Agent skills | +| `bl managed-agent skill search` | API Key | Search Managed Agent skills | +| `bl managed-agent skill versions` | API Key | List Managed Agent skill versions | +| `bl managed-agent skill-list` | API Key | List Managed Agent skills | +| `bl managed-agent state import` | API Key | Import an existing remote resource into agents state | +| `bl managed-agent state list` | No Auth | List resources tracked in agents state | +| `bl managed-agent state rm` | No Auth | Remove a resource from state without destroying it remotely | +| `bl managed-agent state show` | No Auth | Show details of a resource in agents state | +| `bl managed-agent validate` | No Auth | Validate an agents.yaml configuration (offline) | +| `bl managed-agent vault get` | API Key | Get a Managed Agent vault | +| `bl managed-agent vault list` | API Key | List Managed Agent vaults | +| `bl managed-agent vault search` | API Key | Search Managed Agent vaults | ## Command details +### `bl managed-agent agent create` + +| Field | Value | +| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent agent create` | +| **Description** | Declare and create one Managed Agent through an isolated YAML apply | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent agent create --name <name> --model <model> --instructions <text\|path> [--description <text>] [--provider <name>] [--environment <name>] [--vault <name>] [--skill <name>...] [--tool <name>...] [--file <path>] [--yes]` | + +#### Flags + +| Flag | Type | Required | Description | +| ----------------------------- | ------ | -------- | -------------------------------------------------------------------- | +| `--name <name>` | string | yes | Remote Agent display name; the YAML key is generated automatically | +| `--model <model>` | string | yes | Model ID | +| `--instructions <text\|path>` | string | yes | Inline instructions or a ./, ../, or absolute file path | +| `--description <text>` | string | no | Agent description | +| `--provider <name>` | string | no | Target provider; inferred when the config has one effective provider | +| `--environment <name>` | string | no | Existing environment key from agents.yaml | +| `--vault <name>` | string | no | Existing vault key from agents.yaml | +| `--skill <name>` | array | no | Existing custom Skill key from agents.yaml (repeatable) | +| `--tool <name>` | array | no | Builtin tool name (repeatable) | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--yes` | switch | no | Write YAML and run the scoped remote create | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. +- Without --yes, previews the generated YAML key and scoped plan. --dry-run stays offline. Unrelated resources are not refreshed or drift-checked. + +#### Examples + +```bash +bl managed-agent agent create --name assistant --model qwen3.8-max --instructions "You are helpful." +``` + +```bash +bl managed-agent agent create --name assistant --model qwen3.8-max --instructions ./prompts/assistant.md --environment dev --skill search --yes +``` + ### `bl managed-agent agent get` | Field | Value | From 47e9bcc634f54f5d0094f85457276926680b0bfd Mon Sep 17 00:00:00 2001 From: chenanran555 <car534511@alibaba-inc.com> Date: Thu, 27 Aug 2026 17:01:27 +0800 Subject: [PATCH 03/10] feat(managed-agent): add scoped resource create commands --- packages/cli/src/commands.ts | 10 + .../managed-agent/_engine/scoped-create.ts | 447 ++++++++++++++ .../src/commands/managed-agent/agent.ts | 236 -------- .../commands/managed-agent/agent/_shared.ts | 52 ++ .../{agent-create.ts => agent/create.ts} | 205 ++----- .../src/commands/managed-agent/agent/get.ts | 45 ++ .../src/commands/managed-agent/agent/list.ts | 53 ++ .../commands/managed-agent/agent/search.ts | 59 ++ .../commands/managed-agent/agent/versions.ts | 52 ++ .../src/commands/managed-agent/deployment.ts | 471 --------------- .../managed-agent/deployment/_set-paused.ts | 56 ++ .../managed-agent/deployment/_shared.ts | 160 +++++ .../managed-agent/deployment/create.ts | 288 +++++++++ .../commands/managed-agent/deployment/get.ts | 38 ++ .../commands/managed-agent/deployment/list.ts | 57 ++ .../managed-agent/deployment/pause.ts | 3 + .../commands/managed-agent/deployment/run.ts | 57 ++ .../managed-agent/deployment/runs/get.ts | 38 ++ .../managed-agent/deployment/runs/list.ts | 55 ++ .../managed-agent/deployment/search.ts | 67 +++ .../managed-agent/deployment/unpause.ts | 3 + .../src/commands/managed-agent/environment.ts | 174 ------ .../managed-agent/environment/_shared.ts | 41 ++ .../managed-agent/environment/create.ts | 157 +++++ .../commands/managed-agent/environment/get.ts | 41 ++ .../managed-agent/environment/list.ts | 53 ++ .../managed-agent/environment/search.ts | 68 +++ .../src/commands/managed-agent/skill-list.ts | 2 +- .../src/commands/managed-agent/skill.ts | 363 ------------ .../commands/managed-agent/skill/_shared.ts | 87 +++ .../commands/managed-agent/skill/create.ts | 124 ++++ .../commands/managed-agent/skill/download.ts | 49 ++ .../src/commands/managed-agent/skill/get.ts | 36 ++ .../src/commands/managed-agent/skill/list.ts | 94 +++ .../commands/managed-agent/skill/search.ts | 73 +++ .../commands/managed-agent/skill/versions.ts | 55 ++ .../src/commands/managed-agent/vault.ts | 165 ------ .../commands/managed-agent/vault/_shared.ts | 41 ++ .../commands/managed-agent/vault/create.ts | 108 ++++ .../managed-agent/vault/credential/create.ts | 344 +++++++++++ .../src/commands/managed-agent/vault/get.ts | 35 ++ .../src/commands/managed-agent/vault/list.ts | 53 ++ .../commands/managed-agent/vault/search.ts | 59 ++ packages/commands/src/index.ts | 61 +- .../tests/e2e/managed-agent.e2e.test.ts | 546 +++++++++++++++++- packages/commands/tests/e2e/topic-routes.ts | 5 + .../tests/managed-agent-create.test.ts | 19 +- skills/bailian-managed-agent/SKILL.md | 81 ++- .../bailian-managed-agent/reference/index.md | 127 ++-- .../reference/managed-agent.md | 336 +++++++++-- 50 files changed, 4096 insertions(+), 1753 deletions(-) create mode 100644 packages/commands/src/commands/managed-agent/_engine/scoped-create.ts delete mode 100644 packages/commands/src/commands/managed-agent/agent.ts create mode 100644 packages/commands/src/commands/managed-agent/agent/_shared.ts rename packages/commands/src/commands/managed-agent/{agent-create.ts => agent/create.ts} (58%) create mode 100644 packages/commands/src/commands/managed-agent/agent/get.ts create mode 100644 packages/commands/src/commands/managed-agent/agent/list.ts create mode 100644 packages/commands/src/commands/managed-agent/agent/search.ts create mode 100644 packages/commands/src/commands/managed-agent/agent/versions.ts delete mode 100644 packages/commands/src/commands/managed-agent/deployment.ts create mode 100644 packages/commands/src/commands/managed-agent/deployment/_set-paused.ts create mode 100644 packages/commands/src/commands/managed-agent/deployment/_shared.ts create mode 100644 packages/commands/src/commands/managed-agent/deployment/create.ts create mode 100644 packages/commands/src/commands/managed-agent/deployment/get.ts create mode 100644 packages/commands/src/commands/managed-agent/deployment/list.ts create mode 100644 packages/commands/src/commands/managed-agent/deployment/pause.ts create mode 100644 packages/commands/src/commands/managed-agent/deployment/run.ts create mode 100644 packages/commands/src/commands/managed-agent/deployment/runs/get.ts create mode 100644 packages/commands/src/commands/managed-agent/deployment/runs/list.ts create mode 100644 packages/commands/src/commands/managed-agent/deployment/search.ts create mode 100644 packages/commands/src/commands/managed-agent/deployment/unpause.ts delete mode 100644 packages/commands/src/commands/managed-agent/environment.ts create mode 100644 packages/commands/src/commands/managed-agent/environment/_shared.ts create mode 100644 packages/commands/src/commands/managed-agent/environment/create.ts create mode 100644 packages/commands/src/commands/managed-agent/environment/get.ts create mode 100644 packages/commands/src/commands/managed-agent/environment/list.ts create mode 100644 packages/commands/src/commands/managed-agent/environment/search.ts delete mode 100644 packages/commands/src/commands/managed-agent/skill.ts create mode 100644 packages/commands/src/commands/managed-agent/skill/_shared.ts create mode 100644 packages/commands/src/commands/managed-agent/skill/create.ts create mode 100644 packages/commands/src/commands/managed-agent/skill/download.ts create mode 100644 packages/commands/src/commands/managed-agent/skill/get.ts create mode 100644 packages/commands/src/commands/managed-agent/skill/list.ts create mode 100644 packages/commands/src/commands/managed-agent/skill/search.ts create mode 100644 packages/commands/src/commands/managed-agent/skill/versions.ts delete mode 100644 packages/commands/src/commands/managed-agent/vault.ts create mode 100644 packages/commands/src/commands/managed-agent/vault/_shared.ts create mode 100644 packages/commands/src/commands/managed-agent/vault/create.ts create mode 100644 packages/commands/src/commands/managed-agent/vault/credential/create.ts create mode 100644 packages/commands/src/commands/managed-agent/vault/get.ts create mode 100644 packages/commands/src/commands/managed-agent/vault/list.ts create mode 100644 packages/commands/src/commands/managed-agent/vault/search.ts diff --git a/packages/cli/src/commands.ts b/packages/cli/src/commands.ts index 68b3784ef..26bc75ef1 100644 --- a/packages/cli/src/commands.ts +++ b/packages/cli/src/commands.ts @@ -153,6 +153,11 @@ import { managedAgentSkillList, managedAgentCapabilities, managedAgentAgentCreate, + managedAgentEnvironmentCreate, + managedAgentSkillCreate, + managedAgentVaultCreate, + managedAgentVaultCredentialCreate, + managedAgentDeploymentCreate, managedAgentAgentList, managedAgentAgentGet, managedAgentAgentSearch, @@ -356,17 +361,22 @@ export const commands: Record<string, AnyCommand> = { "managed-agent agent get": managedAgentAgentGet, "managed-agent agent search": managedAgentAgentSearch, "managed-agent agent versions": managedAgentAgentVersions, + "managed-agent environment create": managedAgentEnvironmentCreate, "managed-agent environment list": managedAgentEnvironmentList, "managed-agent environment get": managedAgentEnvironmentGet, "managed-agent environment search": managedAgentEnvironmentSearch, + "managed-agent skill create": managedAgentSkillCreate, "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 create": managedAgentVaultCreate, + "managed-agent vault credential create": managedAgentVaultCredentialCreate, "managed-agent vault list": managedAgentVaultList, "managed-agent vault get": managedAgentVaultGet, "managed-agent vault search": managedAgentVaultSearch, + "managed-agent deployment create": managedAgentDeploymentCreate, "managed-agent deployment list": managedAgentDeploymentList, "managed-agent deployment get": managedAgentDeploymentGet, "managed-agent deployment search": managedAgentDeploymentSearch, diff --git a/packages/commands/src/commands/managed-agent/_engine/scoped-create.ts b/packages/commands/src/commands/managed-agent/_engine/scoped-create.ts new file mode 100644 index 000000000..5b78c634e --- /dev/null +++ b/packages/commands/src/commands/managed-agent/_engine/scoped-create.ts @@ -0,0 +1,447 @@ +import { randomUUID } from "node:crypto"; +import { readFile, rename, stat, unlink, writeFile } from "node:fs/promises"; +import { basename, dirname, resolve } from "node:path"; +import { + type BackendRuntimeInput, + type IStateManager, + LocalFileStateBackend, + planProjectWithStateBackend, + type ResolvedProjectConfig, + type ResourceAddress, + type ResourceSyncRun, + resolveProjectConfigFromObject, + syncProjectResourcesWithStateBackend, +} from "@openagentpack/sdk"; +import { BailianError, type CommandContext, detectOutputFormat, ExitCode } from "bailian-cli-core"; +import { emitBare, emitResult } from "bailian-cli-runtime"; +import { parseDocument } from "yaml"; +import { formatResourceLabel } from "./address-utils.ts"; +import { resolveAgentProjectConfig } from "./config-loader.ts"; +import { assertProviderCredentials } from "./credentials.ts"; +import { withStdoutProtected } from "./console-capture.ts"; +import { withAgentErrors } from "./errors.ts"; +import { createFileStateScope } from "./file-state-manager.ts"; +import { installSdkTransport } from "./transport.ts"; + +export type ScopedCreateHost = Pick<CommandContext, "client" | "identity" | "settings">; +export type ScopedCreateGroup = "agents" | "environments" | "skills" | "vaults" | "deployments"; + +export interface LoadedScopedCreateProject { + configPath: string; + projectName: string; + config: ResolvedProjectConfig; + source: string; + provider: string; + stateBackend: LocalFileStateBackend; + stateScope: ReturnType<typeof createFileStateScope>; +} + +export interface ResourceKeySelection { + key: string; + reusedPending: boolean; +} + +export const SCOPED_CREATE_NOTE = [ + { + "en-US": + "Without --yes, this command only previews. --dry-run is fully offline. The scoped flow checks only the target resource and its transitive dependencies; unrelated resources are not refreshed or drift-checked.", + "zh-CN": + "不带 --yes 时仅预览;--dry-run 完全离线。定向流程只检查目标资源及其传递依赖,不刷新或检测无关资源的 Drift。", + }, +]; + +export interface ScopedTopLevelCreateInput { + host: ScopedCreateHost; + project: LoadedScopedCreateProject; + group: Exclude<ScopedCreateGroup, "agents">; + resourceType: "environment" | "skill" | "vault" | "deployment"; + displayName: string; + rawDeclaration: Record<string, unknown>; + resolvedDeclaration: Record<string, unknown>; + existingDeclarations: Record<string, Record<string, unknown>>; + effectiveName: (key: string, declaration: Record<string, unknown>) => string; + fallbackKey: string; + yes: boolean; +} + +function canonicalJson(value: unknown): string { + const normalize = (candidate: unknown): unknown => { + if (Array.isArray(candidate)) return candidate.map(normalize); + if (!candidate || typeof candidate !== "object") return candidate; + return Object.fromEntries( + Object.entries(candidate as Record<string, unknown>) + .filter(([, entry]) => entry !== undefined) + .sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey)) + .map(([key, entry]) => [key, normalize(entry)]), + ); + }; + return JSON.stringify(normalize(value)); +} + +export function normalizeResourceKey(displayName: string, fallback = "resource"): string { + const normalized = displayName + .normalize("NFKC") + .trim() + .toLowerCase() + .replace(/[^\p{L}\p{N}]+/gu, "-") + .replace(/^-+|-+$/g, ""); + return normalized || fallback; +} + +export function selectResourceKey(options: { + displayName: string; + provider: string; + resourceTypes: ResourceAddress["type"][]; + declarations: Record<string, Record<string, unknown>>; + candidate: Record<string, unknown>; + effectiveName: (key: string, declaration: Record<string, unknown>) => string; + fallbackKey?: string; + state: IStateManager; +}): ResourceKeySelection { + const tracked = new Set( + options.state + .listResources() + .filter( + (resource) => + resource.address.provider === options.provider && + options.resourceTypes.includes(resource.address.type), + ) + .map((resource) => resource.address.name), + ); + const candidateJson = canonicalJson(options.candidate); + for (const [key, declaration] of Object.entries(options.declarations)) { + if (options.effectiveName(key, declaration) !== options.displayName || tracked.has(key)) + continue; + if (canonicalJson(declaration) === candidateJson) return { key, reusedPending: true }; + } + + const baseKey = normalizeResourceKey(options.displayName, options.fallbackKey); + if (!(baseKey in options.declarations)) return { key: baseKey, reusedPending: false }; + let suffix = 2; + while (`${baseKey}-${suffix}` in options.declarations) suffix += 1; + return { key: `${baseKey}-${suffix}`, reusedPending: false }; +} + +export async function loadScopedCreateProject( + host: ScopedCreateHost, + file: string, + requestedProvider: string | undefined, +): Promise<LoadedScopedCreateProject> { + const sourceBeforeLoad = await readFile(resolve(file), "utf8").catch((error) => { + if ((error as NodeJS.ErrnoException).code === "ENOENT") { + throw new BailianError( + `Config file not found: ${file}`, + ExitCode.USAGE, + "Run `bl managed-agent init` first.", + ); + } + throw error; + }); + const loaded = await withAgentErrors(() => + resolveAgentProjectConfig(host, file, { credentials: "none" }), + ); + const source = await readFile(loaded.configPath, "utf8"); + if (source !== sourceBeforeLoad) { + throw new BailianError( + `${file} changed while it was being loaded.`, + ExitCode.GENERAL, + "Re-run the command against the latest file.", + ); + } + const provider = resolveTargetProvider(loaded.config, requestedProvider); + if (!host.settings.dryRun) assertProviderCredentials(loaded.config.providers, [provider]); + installSdkTransport(host); + const stateBackend = new LocalFileStateBackend({ configPath: loaded.configPath }); + return { + ...loaded, + source, + provider, + stateBackend, + stateScope: createFileStateScope(loaded.configPath, loaded.projectName), + }; +} + +export function resolveTargetProvider( + config: ResolvedProjectConfig, + requested: string | undefined, +): string { + if (requested === "all") { + throw new BailianError( + "--provider all is not valid for a single-resource create.", + ExitCode.USAGE, + ); + } + if (requested) { + if (requested in config.providers) return requested; + throw new BailianError( + `Provider '${requested}' is not configured in agents.yaml.`, + ExitCode.USAGE, + ); + } + const defaultProvider = config.defaults?.provider; + if (defaultProvider && defaultProvider !== "all") return defaultProvider; + const configuredProviders = Object.keys(config.providers); + if (configuredProviders.length === 1) return configuredProviders[0]!; + throw new BailianError( + "Cannot infer one target provider for this create command.", + ExitCode.USAGE, + "Pass --provider <name> when defaults.provider is 'all' or multiple providers are configured.", + ); +} + +export async function resolveCandidateDeclaration(options: { + project: LoadedScopedCreateProject; + group: ScopedCreateGroup; + rawDeclaration: Record<string, unknown>; +}): Promise<Record<string, unknown>> { + const temporaryKey = "__bailian_cli_create_candidate__"; + const rawConfig = structuredClone(options.project.config) as unknown as Record<string, unknown>; + const declarations = (rawConfig[options.group] ?? {}) as Record<string, unknown>; + rawConfig[options.group] = { ...declarations, [temporaryKey]: options.rawDeclaration }; + const resolved = await withAgentErrors(() => + resolveProjectConfigFromObject(rawConfig, { + projectName: options.project.projectName, + basePath: dirname(options.project.configPath), + }), + ); + const resolvedGroup = resolved.config[options.group] as + | Record<string, Record<string, unknown>> + | undefined; + return resolvedGroup?.[temporaryKey] ?? options.rawDeclaration; +} + +export async function runScopedTopLevelCreate(input: ScopedTopLevelCreateInput): Promise<void> { + const { project, host } = input; + const format = detectOutputFormat(host.settings.output); + const keySelection = await project.stateBackend.read(project.stateScope, (state) => + selectResourceKey({ + displayName: input.displayName, + provider: project.provider, + resourceTypes: [input.resourceType], + declarations: input.existingDeclarations, + candidate: input.resolvedDeclaration, + effectiveName: input.effectiveName, + fallbackKey: input.fallbackKey, + state, + }), + ); + const resourceKey = keySelection.key; + const candidateConfig = structuredClone(project.config) as unknown as Record<string, unknown>; + const existingGroup = (candidateConfig[input.group] ?? {}) as Record<string, unknown>; + candidateConfig[input.group] = { ...existingGroup, [resourceKey]: input.resolvedDeclaration }; + candidateConfig._resolved = true; + + const document = parseDocument(project.source); + if (document.errors.length > 0) { + throw new BailianError( + `YAML parse error: ${document.errors.map((error) => error.message).join("; ")}`, + ExitCode.USAGE, + ); + } + document.setIn([input.group, resourceKey], input.rawDeclaration); + const nextSource = document.toString(); + const root: ResourceAddress = { + type: input.resourceType, + name: resourceKey, + provider: project.provider, + }; + const backendInput: BackendRuntimeInput = { + projectName: project.projectName, + config: candidateConfig as unknown as ResolvedProjectConfig, + configPath: project.configPath, + providers: { [project.provider]: project.config.providers[project.provider] }, + stateBackend: project.stateBackend, + stateScope: project.stateScope, + }; + + if (host.settings.dryRun || !input.yes) { + const planned = await withAgentErrors(() => + withStdoutProtected(() => + planProjectWithStateBackend(backendInput, { + provider: project.provider, + scope: { roots: [root] }, + mode: "create-only", + refresh: !host.settings.dryRun, + quiet: format === "json", + }), + ), + ); + const readyToCreate = !planned.plan.diagnostics.some( + (diagnostic) => diagnostic.severity === "error", + ); + const result = { + resource: { + type: input.resourceType, + key: resourceKey, + name: input.displayName, + provider: project.provider, + }, + config_file: project.configPath, + yaml_written: false, + reused_pending: keySelection.reusedPending, + requires_confirmation: !host.settings.dryRun, + ready_to_create: readyToCreate, + actions: planned.plan.actions, + diagnostics: planned.plan.diagnostics, + }; + if (format === "json") emitResult(result, format); + else { + emitBare(`Generated YAML key: ${resourceKey}`); + renderPlan(planned.plan.actions, planned.plan.diagnostics); + emitBare( + host.settings.dryRun + ? "Dry run: YAML, State, and remote resources were not changed." + : "Preview only: re-run with --yes to write YAML and create this resource.", + ); + } + return; + } + + await replaceConfigAtomically(project.configPath, project.source, nextSource); + let run: ResourceSyncRun; + try { + run = await withAgentErrors(() => + withStdoutProtected(() => + syncProjectResourcesWithStateBackend(backendInput, { + provider: project.provider, + scope: { roots: [root] }, + mode: "create-only", + refresh: true, + quiet: format === "json", + policy: "block", + }), + ), + ); + } catch (error) { + throw retainedDeclarationError(error, input.resourceType); + } + const remoteId = await project.stateBackend.read( + project.stateScope, + (state) => state.getResource(root)?.remote_id, + ); + const results = run.execution?.results ?? []; + const failed = results.find((result) => result.status === "failed"); + const result = { + resource: { + type: input.resourceType, + key: resourceKey, + name: input.displayName, + provider: project.provider, + remote_id: remoteId, + }, + config_file: project.configPath, + yaml_written: true, + reused_pending: keySelection.reusedPending, + actions: run.planned.plan.actions, + diagnostics: run.planned.plan.diagnostics, + results, + error: failed?.error, + }; + if (format === "json") emitResult(result, format); + else { + emitBare(`Wrote ${project.configPath} with ${input.resourceType} key '${resourceKey}'.`); + if (!failed) emitBare(`Created ${input.resourceType} '${input.displayName}'.`); + } + if (failed) { + throw new BailianError( + failed.error ?? `Scoped ${input.resourceType} create failed.`, + ExitCode.GENERAL, + "The YAML declaration was kept. Fix the provider error, then re-run the same create command.", + ); + } +} + +export async function replaceConfigAtomically( + configPath: string, + expectedSource: string, + nextSource: string, +): Promise<void> { + const destination = resolve(configPath); + const currentSource = await readFile(destination, "utf8"); + if (currentSource !== expectedSource) { + throw new BailianError( + `${configPath} changed while resource create was being prepared.`, + ExitCode.GENERAL, + "Review the latest YAML and re-run the command; no file was overwritten.", + ); + } + const currentStat = await stat(destination); + const temporary = resolve(dirname(destination), `.${basename(destination)}.${randomUUID()}.tmp`); + await writeFile(temporary, nextSource, { flag: "wx", mode: currentStat.mode }); + try { + await rename(temporary, destination); + } catch (error) { + await unlink(temporary).catch(() => undefined); + throw error; + } +} + +export function parseMetadata(values: string[] | undefined): Record<string, string> | undefined { + if (!values?.length) return undefined; + const metadata: Record<string, string> = {}; + for (const entry of values) { + const separator = entry.indexOf("="); + if (separator <= 0) { + throw new BailianError( + `Invalid metadata '${entry}'.`, + ExitCode.USAGE, + "Use --metadata key=value.", + ); + } + const key = entry.slice(0, separator).trim(); + if (!key) throw new BailianError("Metadata key must not be empty.", ExitCode.USAGE); + metadata[key] = entry.slice(separator + 1); + } + return metadata; +} + +export async function parseJsonInputs( + values: string[] | undefined, + label: string, +): Promise<unknown[]> { + const parsed: unknown[] = []; + for (const value of values ?? []) { + const raw = value.startsWith("@") ? await readFile(resolve(value.slice(1)), "utf8") : value; + let decoded: unknown; + try { + decoded = JSON.parse(raw); + } catch (error) { + throw new BailianError( + `Invalid ${label} JSON: ${error instanceof Error ? error.message : String(error)}`, + ExitCode.USAGE, + ); + } + if (Array.isArray(decoded)) parsed.push(...decoded); + else parsed.push(decoded); + } + return parsed; +} + +function renderPlan( + actions: Array<{ action: string; address: ResourceAddress }>, + diagnostics: Array<{ severity: string; code: string; message: string }>, +): void { + for (const diagnostic of diagnostics) { + emitBare(`[${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`); + } + for (const action of actions.filter((entry) => entry.action !== "no-op")) { + const icon = action.action === "create" ? "+" : action.action === "update" ? "~" : "-"; + emitBare(` ${icon} ${formatResourceLabel(action.address)}`); + } +} + +function retainedDeclarationError(error: unknown, resourceType: string): BailianError { + if (error instanceof BailianError) { + return new BailianError( + error.message, + error.exitCode, + "The YAML declaration was kept. Fix the related dependency or provider error, then re-run the same create command.", + { api: error.api, rawResponse: error.rawResponse, cause: error.cause }, + ); + } + return new BailianError( + error instanceof Error ? error.message : String(error), + ExitCode.GENERAL, + `The ${resourceType} YAML declaration was kept; re-run the same create command after fixing the error.`, + ); +} diff --git a/packages/commands/src/commands/managed-agent/agent.ts b/packages/commands/src/commands/managed-agent/agent.ts deleted file mode 100644 index 9289dee94..000000000 --- a/packages/commands/src/commands/managed-agent/agent.ts +++ /dev/null @@ -1,236 +0,0 @@ -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.", - }); - }, -}); diff --git a/packages/commands/src/commands/managed-agent/agent/_shared.ts b/packages/commands/src/commands/managed-agent/agent/_shared.ts new file mode 100644 index 000000000..a8fb0639f --- /dev/null +++ b/packages/commands/src/commands/managed-agent/agent/_shared.ts @@ -0,0 +1,52 @@ +import type { CloudAgent } from "@openagentpack/sdk"; +import { + API_TARGET_FLAGS, + CURSOR_FLAGS, + displayValue, + INCLUDE_ARCHIVED_FLAG, + SEARCH_FLAGS, +} from "../_engine/api-helpers.ts"; + +export const AGENT_LIST_FLAGS = { + ...API_TARGET_FLAGS, + ...CURSOR_FLAGS, + ...INCLUDE_ARCHIVED_FLAG, +}; + +export const AGENT_SEARCH_FLAGS = { + ...API_TARGET_FLAGS, + limit: CURSOR_FLAGS.limit, + ...SEARCH_FLAGS, + ...INCLUDE_ARCHIVED_FLAG, +}; + +export 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; + +export const AGENT_VERSIONS_FLAGS = { + ...API_TARGET_FLAGS, + ...CURSOR_FLAGS, + agentId: AGENT_GET_FLAGS.agentId, +}; + +export function agentRows(agents: CloudAgent[]): string[][] { + return agents.map((agent) => [ + agent.id, + displayValue(agent.name), + displayValue(agent.version), + displayValue(agent.type), + displayValue(agent.updated_at), + ]); +} diff --git a/packages/commands/src/commands/managed-agent/agent-create.ts b/packages/commands/src/commands/managed-agent/agent/create.ts similarity index 58% rename from packages/commands/src/commands/managed-agent/agent-create.ts rename to packages/commands/src/commands/managed-agent/agent/create.ts index 6af87cdf4..571e2aec4 100644 --- a/packages/commands/src/commands/managed-agent/agent-create.ts +++ b/packages/commands/src/commands/managed-agent/agent/create.ts @@ -1,14 +1,8 @@ -import { randomUUID } from "node:crypto"; -import { readFile, rename, stat, unlink, writeFile } from "node:fs/promises"; -import { basename, dirname, resolve } from "node:path"; import { buildAgentDecl, type IStateManager, - LocalFileStateBackend, planAgentResourcesWithStateBackend, - type ResolvedProjectConfig, type ResourceAddress, - resolveProjectConfigFromObject, syncAgentResourcesWithStateBackend, } from "@openagentpack/sdk"; import { @@ -20,13 +14,19 @@ import { } from "bailian-cli-core"; import { emitBare, emitResult } from "bailian-cli-runtime"; import { parseDocument } from "yaml"; -import { formatResourceLabel } from "./_engine/address-utils.ts"; -import { CREDENTIALS_NOTE, resolveAgentProjectConfig } from "./_engine/config-loader.ts"; -import { assertProviderCredentials } from "./_engine/credentials.ts"; -import { withStdoutProtected } from "./_engine/console-capture.ts"; -import { withAgentErrors } from "./_engine/errors.ts"; -import { createFileStateScope } from "./_engine/file-state-manager.ts"; -import { installSdkTransport } from "./_engine/transport.ts"; +import { formatResourceLabel } from "../_engine/address-utils.ts"; +import { CREDENTIALS_NOTE } from "../_engine/config-loader.ts"; +import { withStdoutProtected } from "../_engine/console-capture.ts"; +import { withAgentErrors } from "../_engine/errors.ts"; +import { + loadScopedCreateProject, + normalizeResourceKey, + replaceConfigAtomically, + resolveCandidateDeclaration, + selectResourceKey, +} from "../_engine/scoped-create.ts"; + +export { replaceConfigAtomically } from "../_engine/scoped-create.ts"; const CREATE_FLAGS = { name: { @@ -122,28 +122,8 @@ interface AgentKeySelection { reusedPending: boolean; } -function canonicalJson(value: unknown): string { - const normalize = (candidate: unknown): unknown => { - if (Array.isArray(candidate)) return candidate.map(normalize); - if (!candidate || typeof candidate !== "object") return candidate; - return Object.fromEntries( - Object.entries(candidate as Record<string, unknown>) - .filter(([, entry]) => entry !== undefined) - .sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey)) - .map(([key, entry]) => [key, normalize(entry)]), - ); - }; - return JSON.stringify(normalize(value)); -} - export function normalizeAgentKey(displayName: string): string { - const normalized = displayName - .normalize("NFKC") - .trim() - .toLowerCase() - .replace(/[^\p{L}\p{N}]+/gu, "-") - .replace(/^-+|-+$/g, ""); - return normalized || "agent"; + return normalizeResourceKey(displayName, "agent"); } export function selectAgentKey(options: { @@ -153,80 +133,17 @@ export function selectAgentKey(options: { candidate: BuiltAgentDecl; state: IStateManager; }): AgentKeySelection { - const tracked = new Set( - options.state - .listResources() - .filter( - (resource) => - resource.address.provider === options.provider && - (resource.address.type === "agent" || resource.address.type === "template"), - ) - .map((resource) => resource.address.name), - ); - const candidateJson = canonicalJson(options.candidate); - for (const [key, declaration] of Object.entries(options.agents)) { - const effectiveName = declaration.name ?? key; - if (effectiveName !== options.displayName || tracked.has(key)) continue; - if (canonicalJson({ ...declaration, name: effectiveName }) === candidateJson) { - return { key, reusedPending: true }; - } - } - - const baseKey = normalizeAgentKey(options.displayName); - if (!(baseKey in options.agents)) return { key: baseKey, reusedPending: false }; - let suffix = 2; - while (`${baseKey}-${suffix}` in options.agents) suffix += 1; - return { key: `${baseKey}-${suffix}`, reusedPending: false }; -} - -export async function replaceConfigAtomically( - configPath: string, - expectedSource: string, - nextSource: string, -): Promise<void> { - const destination = resolve(configPath); - const currentSource = await readFile(destination, "utf8"); - if (currentSource !== expectedSource) { - throw new BailianError( - `${configPath} changed while Agent create was being prepared.`, - ExitCode.GENERAL, - "Review the latest YAML and re-run the command; no file was overwritten.", - ); - } - const currentStat = await stat(destination); - const temporary = resolve(dirname(destination), `.${basename(destination)}.${randomUUID()}.tmp`); - await writeFile(temporary, nextSource, { flag: "wx", mode: currentStat.mode }); - try { - await rename(temporary, destination); - } catch (error) { - await unlink(temporary).catch(() => undefined); - throw error; - } -} - -function resolveTargetProvider( - config: ResolvedProjectConfig, - requested: string | undefined, -): string { - if (requested === "all") { - throw new BailianError("--provider all is not valid for Agent create.", ExitCode.USAGE); - } - if (requested) { - if (requested in config.providers) return requested; - throw new BailianError( - `Provider '${requested}' is not configured in agents.yaml.`, - ExitCode.USAGE, - ); - } - const defaultProvider = config.defaults?.provider; - if (defaultProvider && defaultProvider !== "all") return defaultProvider; - const configuredProviders = Object.keys(config.providers); - if (configuredProviders.length === 1) return configuredProviders[0]!; - throw new BailianError( - "Agent create cannot infer one target provider.", - ExitCode.USAGE, - "Pass --provider <name> when defaults.provider is 'all' or multiple providers are configured.", - ); + return selectResourceKey({ + displayName: options.displayName, + provider: options.provider, + resourceTypes: ["agent", "template"], + declarations: options.agents as unknown as Record<string, Record<string, unknown>>, + candidate: options.candidate as unknown as Record<string, unknown>, + effectiveName: (key, declaration) => + typeof declaration.name === "string" ? declaration.name : key, + fallbackKey: "agent", + state: options.state, + }); } function rootAddress( @@ -271,32 +188,8 @@ export default defineCommand({ const { flags, settings } = ctx; const format = detectOutputFormat(settings.output); const file = flags.file ?? "agents.yaml"; - const sourceBeforeLoad = await readFile(resolve(file), "utf8").catch((error) => { - const code = (error as NodeJS.ErrnoException).code; - if (code === "ENOENT") { - throw new BailianError( - `Config file not found: ${file}`, - ExitCode.USAGE, - "Run `bl managed-agent init` first.", - ); - } - throw error; - }); - const loaded = await withAgentErrors(() => - resolveAgentProjectConfig(ctx, file, { credentials: "none" }), - ); - const source = await readFile(loaded.configPath, "utf8"); - if (source !== sourceBeforeLoad) { - throw new BailianError( - `${file} changed while it was being loaded.`, - ExitCode.GENERAL, - "Re-run the command against the latest file.", - ); - } - - const provider = resolveTargetProvider(loaded.config, flags.provider); - if (!settings.dryRun) assertProviderCredentials(loaded.config.providers, [provider]); - installSdkTransport(ctx); + const project = await loadScopedCreateProject(ctx, file, flags.provider); + const provider = project.provider; const rawAgent = buildAgentDecl(undefined, { name: flags.name.trim(), @@ -309,34 +202,26 @@ export default defineCommand({ builtinTools: flags.tool, skills: flags.skill?.map((skillName) => ({ kind: "custom", name: skillName })), }).agent; - const temporaryKey = "__bailian_cli_agent_create_candidate__"; - const resolvedCandidate = await withAgentErrors(() => - resolveProjectConfigFromObject( - { - ...loaded.config, - agents: { ...loaded.config.agents, [temporaryKey]: rawAgent }, - }, - { projectName: loaded.projectName, basePath: dirname(loaded.configPath) }, - ), - ); - const candidateAgent = resolvedCandidate.config.agents![temporaryKey]! as BuiltAgentDecl; - const stateBackend = new LocalFileStateBackend({ configPath: loaded.configPath }); - const stateScope = createFileStateScope(loaded.configPath, loaded.projectName); - const keySelection = await stateBackend.read(stateScope, (state) => + const candidateAgent = (await resolveCandidateDeclaration({ + project, + group: "agents", + rawDeclaration: rawAgent as unknown as Record<string, unknown>, + })) as unknown as BuiltAgentDecl; + const keySelection = await project.stateBackend.read(project.stateScope, (state) => selectAgentKey({ displayName: flags.name.trim(), provider, - agents: (loaded.config.agents ?? {}) as Record<string, BuiltAgentDecl>, + agents: (project.config.agents ?? {}) as Record<string, BuiltAgentDecl>, candidate: candidateAgent, state, }), ); const agentKey = keySelection.key; - const candidateConfig = structuredClone(loaded.config); + const candidateConfig = structuredClone(project.config); candidateConfig.agents = { ...candidateConfig.agents, [agentKey]: candidateAgent }; candidateConfig._resolved = true; - const document = parseDocument(source); + const document = parseDocument(project.source); if (document.errors.length > 0) { throw new BailianError( `YAML parse error: ${document.errors.map((error) => error.message).join("; ")}`, @@ -346,12 +231,12 @@ export default defineCommand({ document.setIn(["agents", agentKey], rawAgent); const nextSource = document.toString(); const backendInput = { - projectName: loaded.projectName, + projectName: project.projectName, config: candidateConfig, - configPath: loaded.configPath, + configPath: project.configPath, providers: { [provider]: candidateConfig.providers[provider] }, - stateBackend, - stateScope, + stateBackend: project.stateBackend, + stateScope: project.stateScope, }; if (settings.dryRun || !flags.yes) { @@ -369,7 +254,7 @@ export default defineCommand({ ); const result = { agent: { key: agentKey, name: flags.name.trim(), provider }, - config_file: loaded.configPath, + config_file: project.configPath, yaml_written: false, reused_pending: keySelection.reusedPending, requires_confirmation: !settings.dryRun, @@ -397,7 +282,7 @@ export default defineCommand({ return; } - await replaceConfigAtomically(loaded.configPath, source, nextSource); + await replaceConfigAtomically(project.configPath, project.source, nextSource); const run = await withAgentErrors(() => withStdoutProtected(() => syncAgentResourcesWithStateBackend(backendInput, agentKey, { @@ -408,7 +293,7 @@ export default defineCommand({ }), ), ); - const remoteId = await stateBackend.read(stateScope, (state) => { + const remoteId = await project.stateBackend.read(project.stateScope, (state) => { const selectedRoot = rootAddress( run.actions.map((action) => action.address), agentKey, @@ -417,7 +302,7 @@ export default defineCommand({ }); const result = { agent: { key: agentKey, name: flags.name.trim(), provider, remote_id: remoteId }, - config_file: loaded.configPath, + config_file: project.configPath, yaml_written: true, reused_pending: keySelection.reusedPending, status: run.status, @@ -428,7 +313,7 @@ export default defineCommand({ }; if (format === "json") emitResult(result, format); else { - emitBare(`Wrote ${loaded.configPath} with Agent key '${agentKey}'.`); + emitBare(`Wrote ${project.configPath} with Agent key '${agentKey}'.`); if (run.status === "completed") emitBare(`Created Agent '${flags.name.trim()}'.`); else emitBare(`Scoped create ${run.status}: ${run.error ?? "unknown error"}`); } diff --git a/packages/commands/src/commands/managed-agent/agent/get.ts b/packages/commands/src/commands/managed-agent/agent/get.ts new file mode 100644 index 000000000..bed98c70d --- /dev/null +++ b/packages/commands/src/commands/managed-agent/agent/get.ts @@ -0,0 +1,45 @@ +import { getRemoteAgent } from "@openagentpack/sdk"; +import { defineCommand, detectOutputFormat } from "bailian-cli-core"; +import { emitBare, emitResult } from "bailian-cli-runtime"; +import { displayValue } 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 { AGENT_GET_FLAGS } from "./_shared.ts"; + +export default 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)}`); + }, +}); diff --git a/packages/commands/src/commands/managed-agent/agent/list.ts b/packages/commands/src/commands/managed-agent/agent/list.ts new file mode 100644 index 000000000..638df7977 --- /dev/null +++ b/packages/commands/src/commands/managed-agent/agent/list.ts @@ -0,0 +1,53 @@ +import { listRemoteAgents } from "@openagentpack/sdk"; +import { defineCommand, detectOutputFormat } from "bailian-cli-core"; +import { emitCollection, 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"; +import { AGENT_LIST_FLAGS, agentRows } from "./_shared.ts"; + +export default 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.", + }); + }, +}); diff --git a/packages/commands/src/commands/managed-agent/agent/search.ts b/packages/commands/src/commands/managed-agent/agent/search.ts new file mode 100644 index 000000000..555181c1a --- /dev/null +++ b/packages/commands/src/commands/managed-agent/agent/search.ts @@ -0,0 +1,59 @@ +import { listRemoteAgents } from "@openagentpack/sdk"; +import { defineCommand, detectOutputFormat } from "bailian-cli-core"; +import { + emitCollection, + matchesQuery, + 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 { AGENT_SEARCH_FLAGS, agentRows } from "./_shared.ts"; + +export default 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.", + }); + }, +}); diff --git a/packages/commands/src/commands/managed-agent/agent/versions.ts b/packages/commands/src/commands/managed-agent/agent/versions.ts new file mode 100644 index 000000000..e4f3d80d5 --- /dev/null +++ b/packages/commands/src/commands/managed-agent/agent/versions.ts @@ -0,0 +1,52 @@ +import { listRemoteAgentVersions } from "@openagentpack/sdk"; +import { defineCommand, detectOutputFormat } from "bailian-cli-core"; +import { emitCollection, 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"; +import { AGENT_VERSIONS_FLAGS, agentRows } from "./_shared.ts"; + +export default 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.", + }); + }, +}); diff --git a/packages/commands/src/commands/managed-agent/deployment.ts b/packages/commands/src/commands/managed-agent/deployment.ts deleted file mode 100644 index f74cc2262..000000000 --- a/packages/commands/src/commands/managed-agent/deployment.ts +++ /dev/null @@ -1,471 +0,0 @@ -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); diff --git a/packages/commands/src/commands/managed-agent/deployment/_set-paused.ts b/packages/commands/src/commands/managed-agent/deployment/_set-paused.ts new file mode 100644 index 000000000..4e3b4b68a --- /dev/null +++ b/packages/commands/src/commands/managed-agent/deployment/_set-paused.ts @@ -0,0 +1,56 @@ +import { setRemoteDeploymentPaused } from "@openagentpack/sdk"; +import { defineCommand, detectOutputFormat } from "bailian-cli-core"; +import { emitBare, emitResult } from "bailian-cli-runtime"; +import { buildAgentRuntime, CREDENTIALS_NOTE } from "../_engine/config-loader.ts"; +import { withStdoutProtected } from "../_engine/console-capture.ts"; +import { withAgentErrors } from "../_engine/errors.ts"; +import { + DEPLOYMENT_ACTION_TARGET_FLAGS, + resolveDeploymentTarget, + validateDeploymentActionTarget, +} from "./_shared.ts"; + +export function createSetDeploymentPausedCommand(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: DEPLOYMENT_ACTION_TARGET_FLAGS, + exampleArgs: [`--deployment daily-report --dry-run`, `--deployment-id dep_abc`], + notes: CREDENTIALS_NOTE, + validate: validateDeploymentActionTarget, + 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"}.`); + } + }, + }); +} diff --git a/packages/commands/src/commands/managed-agent/deployment/_shared.ts b/packages/commands/src/commands/managed-agent/deployment/_shared.ts new file mode 100644 index 000000000..a641d104c --- /dev/null +++ b/packages/commands/src/commands/managed-agent/deployment/_shared.ts @@ -0,0 +1,160 @@ +import type { DeploymentInfo, DeploymentRunInfo, ProjectRuntimeContext } from "@openagentpack/sdk"; +import { UserError } from "@openagentpack/sdk"; +import { + API_TARGET_FLAGS, + CURSOR_FLAGS, + displayValue, + INCLUDE_ARCHIVED_FLAG, + SEARCH_FLAGS, +} from "../_engine/api-helpers.ts"; + +export const DEPLOYMENT_ID_FLAG = { + deploymentId: { + type: "string", + valueHint: "<id>", + required: true, + description: { "en-US": "Deployment ID", "zh-CN": "Deployment ID" }, + }, +} as const; + +const DEPLOYMENT_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; + +export const DEPLOYMENT_LIST_FLAGS = { + ...API_TARGET_FLAGS, + ...CURSOR_FLAGS, + ...DEPLOYMENT_LIST_FILTER_FLAGS, +}; + +export const DEPLOYMENT_GET_FLAGS = { ...API_TARGET_FLAGS, ...DEPLOYMENT_ID_FLAG }; + +export const DEPLOYMENT_SEARCH_FLAGS = { + ...API_TARGET_FLAGS, + ...CURSOR_FLAGS, + query: SEARCH_FLAGS.query, + ...DEPLOYMENT_LIST_FILTER_FLAGS, +}; + +export const DEPLOYMENT_RUNS_LIST_FLAGS = { + ...API_TARGET_FLAGS, + ...DEPLOYMENT_ID_FLAG, + ...CURSOR_FLAGS, +}; + +export const DEPLOYMENT_RUNS_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; + +export const DEPLOYMENT_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; + +export const DEPLOYMENT_RUN_ACTION_FLAGS = { + ...DEPLOYMENT_ACTION_TARGET_FLAGS, + yes: { + type: "switch", + description: { "en-US": "Confirm deployment run", "zh-CN": "确认运行 Deployment" }, + }, +} as const; + +export function validateDeploymentActionTarget(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; +} + +export function deploymentRows(deployments: DeploymentInfo[]): string[][] { + return deployments.map((deployment) => [ + displayValue(deployment.id), + deployment.status, + displayValue(deployment.schedule?.expression), + displayValue(deployment.paused_reason?.type), + ]); +} + +export function deploymentRunRows(runs: DeploymentRunInfo[]): string[][] { + return runs.map((run) => [ + run.id, + displayValue(run.deployment_id), + displayValue(run.session_id), + displayValue(run.status), + displayValue(run.created_at), + ]); +} + +export 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; +} diff --git a/packages/commands/src/commands/managed-agent/deployment/create.ts b/packages/commands/src/commands/managed-agent/deployment/create.ts new file mode 100644 index 000000000..c0446d39a --- /dev/null +++ b/packages/commands/src/commands/managed-agent/deployment/create.ts @@ -0,0 +1,288 @@ +import type { DeploymentDecl } from "@openagentpack/sdk"; +import { BailianError, defineCommand, ExitCode, type FlagsDef } from "bailian-cli-core"; +import { CREDENTIALS_NOTE } from "../_engine/config-loader.ts"; +import { + loadScopedCreateProject, + parseJsonInputs, + parseMetadata, + resolveCandidateDeclaration, + runScopedTopLevelCreate, + SCOPED_CREATE_NOTE, +} from "../_engine/scoped-create.ts"; + +const FLAGS = { + name: { + type: "string", + valueHint: "<name>", + required: true, + description: { + "en-US": "Remote Deployment display name; the YAML key is generated automatically", + "zh-CN": "远端 Deployment 显示名称;YAML key 将自动生成", + }, + }, + agent: { + type: "string", + valueHint: "<yaml-key>", + required: true, + description: { + "en-US": "Existing Agent key from agents.yaml", + "zh-CN": "agents.yaml 中已有的 Agent key", + }, + }, + agentVersion: { + type: "number", + valueHint: "<number>", + description: { "en-US": "Agent version", "zh-CN": "Agent 版本" }, + }, + environment: { + type: "string", + valueHint: "<yaml-key>", + description: { + "en-US": "Existing Environment key from agents.yaml", + "zh-CN": "agents.yaml 中已有的 Environment key", + }, + }, + vault: { + type: "array", + valueHint: "<yaml-key>", + description: { + "en-US": "Existing Vault key from agents.yaml (repeatable)", + "zh-CN": "agents.yaml 中已有的 Vault key(可重复)", + }, + }, + message: { + type: "array", + valueHint: "<text>", + description: { + "en-US": "Initial user message (repeatable)", + "zh-CN": "初始用户消息(可重复)", + }, + }, + event: { + type: "array", + valueHint: "<json|@path>", + description: { + "en-US": "Initial user.message or system.message JSON (repeatable)", + "zh-CN": "初始 user.message 或 system.message JSON(可重复)", + }, + }, + resource: { + type: "array", + valueHint: "<json|@path>", + description: { + "en-US": "File Resource JSON with source or file_id (repeatable)", + "zh-CN": "包含 source 或 file_id 的 File Resource JSON(可重复)", + }, + }, + schedule: { + type: "string", + valueHint: "<cron>", + description: { "en-US": "Five-field cron expression", "zh-CN": "五段式 Cron 表达式" }, + }, + timezone: { + type: "string", + valueHint: "<timezone>", + description: { "en-US": "IANA schedule timezone", "zh-CN": "Schedule 的 IANA 时区" }, + }, + description: { + type: "string", + valueHint: "<text>", + description: { "en-US": "Deployment description", "zh-CN": "Deployment 描述" }, + }, + metadata: { + type: "array", + valueHint: "<key=value>", + description: { + "en-US": "Metadata entry (repeatable)", + "zh-CN": "Metadata 条目(可重复)", + }, + }, + provider: { + type: "string", + valueHint: "<name>", + description: { + "en-US": "Target provider; inferred when unambiguous", + "zh-CN": "目标 Provider;可唯一确定时自动推断", + }, + }, + file: { + type: "string", + valueHint: "<path>", + description: { + "en-US": "Config file path (default: agents.yaml)", + "zh-CN": "配置文件路径(默认:agents.yaml)", + }, + }, + yes: { + type: "switch", + description: { + "en-US": "Write YAML and run the scoped remote create", + "zh-CN": "写入 YAML 并执行定向远端创建", + }, + }, +} satisfies FlagsDef; + +export default defineCommand({ + description: { + "en-US": "Declare and create one Managed Agent Deployment through a scoped YAML apply", + "zh-CN": "通过定向 YAML Apply 声明并创建一个托管 Agent Deployment", + }, + auth: "apiKey", + usageArgs: + "--name <name> --agent <yaml-key> (--message <text>... | --event <json|@path>...) [--agent-version <number>] [--environment <yaml-key>] [--vault <yaml-key>...] [--resource <json|@path>...] [--schedule <cron> --timezone <timezone>] [--description <text>] [--metadata <key=value>...] [--provider <name>] [--file <path>] [--yes]", + flags: FLAGS, + exampleArgs: [ + { + "en-US": '--name Daily --agent assistant --message "Generate the report"', + "zh-CN": '--name Daily --agent assistant --message "生成报告"', + }, + { + "en-US": + '--name Daily --agent assistant --event \'{"type":"system.message","content":"Be concise"}\' --message "Run" --yes', + "zh-CN": + '--name Daily --agent assistant --event \'{"type":"system.message","content":"保持简洁"}\' --message "执行" --yes', + }, + ], + notes: [ + ...CREDENTIALS_NOTE, + ...SCOPED_CREATE_NOTE, + { + "en-US": + "Initial Events must contain 1-50 user.message/system.message entries. --resource accepts only File Resources in this release.", + "zh-CN": + "Initial Events 必须包含 1–50 条 user.message/system.message;本期 --resource 仅接受 File Resource。", + }, + ], + validate: (flags) => { + if (!flags.name.trim()) return "--name must not be empty."; + if (!flags.agent.trim()) return "--agent must not be empty."; + if (!flags.message?.length && !flags.event?.length) + return "Pass at least one --message or --event."; + if (Boolean(flags.schedule) !== Boolean(flags.timezone)) { + return "--schedule and --timezone must be provided together."; + } + if ( + flags.agentVersion !== undefined && + (!Number.isInteger(flags.agentVersion) || flags.agentVersion < 1) + ) { + return "--agent-version must be a positive integer."; + } + return undefined; + }, + async run(ctx) { + const project = await loadScopedCreateProject( + ctx, + ctx.flags.file ?? "agents.yaml", + ctx.flags.provider, + ); + const initialEvents = [ + ...(ctx.flags.message ?? []).map((content) => ({ type: "user.message" as const, content })), + ...validateEvents(await parseJsonInputs(ctx.flags.event, "event")), + ]; + if (initialEvents.length < 1 || initialEvents.length > 50) { + throw new BailianError("Initial Events must contain 1-50 entries.", ExitCode.USAGE); + } + const resources = validateResources(await parseJsonInputs(ctx.flags.resource, "resource")); + if (ctx.flags.schedule && ctx.flags.schedule.trim().split(/\s+/).length !== 5) { + throw new BailianError("--schedule must be a five-field cron expression.", ExitCode.USAGE); + } + if (ctx.flags.timezone) validateTimezone(ctx.flags.timezone); + + const rawDeclaration: DeploymentDecl = { + name: ctx.flags.name.trim(), + agent: ctx.flags.agent, + agent_version: ctx.flags.agentVersion, + environment: ctx.flags.environment, + vaults: ctx.flags.vault, + resources, + initial_events: initialEvents, + schedule: + ctx.flags.schedule && ctx.flags.timezone + ? { expression: ctx.flags.schedule, timezone: ctx.flags.timezone } + : undefined, + description: ctx.flags.description, + provider: project.provider, + metadata: parseMetadata(ctx.flags.metadata), + }; + const resolvedDeclaration = await resolveCandidateDeclaration({ + project, + group: "deployments", + rawDeclaration: rawDeclaration as unknown as Record<string, unknown>, + }); + await runScopedTopLevelCreate({ + host: ctx, + project, + group: "deployments", + resourceType: "deployment", + displayName: ctx.flags.name.trim(), + rawDeclaration: rawDeclaration as unknown as Record<string, unknown>, + resolvedDeclaration, + existingDeclarations: (project.config.deployments ?? {}) as unknown as Record< + string, + Record<string, unknown> + >, + effectiveName: (key, declaration) => + typeof declaration.name === "string" ? declaration.name : key, + fallbackKey: "deployment", + yes: ctx.flags.yes, + }); + }, +}); + +function validateEvents( + values: unknown[], +): Array<{ type: "user.message" | "system.message"; content: string }> { + return values.map((value) => { + if (!value || typeof value !== "object" || Array.isArray(value)) { + throw new BailianError("Each --event must be a JSON object.", ExitCode.USAGE); + } + const event = value as Record<string, unknown>; + if (event.type !== "user.message" && event.type !== "system.message") { + throw new BailianError( + "--event type must be user.message or system.message.", + ExitCode.USAGE, + ); + } + if (typeof event.content !== "string" || !event.content.trim()) { + throw new BailianError("--event content must be a non-empty string.", ExitCode.USAGE); + } + return { type: event.type, content: event.content }; + }); +} + +function validateResources(values: unknown[]): NonNullable<DeploymentDecl["resources"]> { + return values.map((value) => { + if (!value || typeof value !== "object" || Array.isArray(value)) { + throw new BailianError("Each --resource must be a JSON object.", ExitCode.USAGE); + } + const resource = value as Record<string, unknown>; + if (resource.type !== "file") { + throw new BailianError("--resource currently accepts only type=file.", ExitCode.USAGE); + } + const source = typeof resource.source === "string" ? resource.source : undefined; + const fileId = typeof resource.file_id === "string" ? resource.file_id : undefined; + if (Boolean(source) === Boolean(fileId)) { + throw new BailianError( + "A File Resource must provide exactly one of source or file_id.", + ExitCode.USAGE, + ); + } + if (resource.mount_path !== undefined && typeof resource.mount_path !== "string") { + throw new BailianError("File Resource mount_path must be a string.", ExitCode.USAGE); + } + return { + type: "file" as const, + source, + file_id: fileId, + mount_path: resource.mount_path as string | undefined, + }; + }); +} + +function validateTimezone(timezone: string): void { + try { + new Intl.DateTimeFormat("en-US", { timeZone: timezone }).format(); + } catch { + throw new BailianError(`Invalid IANA timezone: ${timezone}`, ExitCode.USAGE); + } +} diff --git a/packages/commands/src/commands/managed-agent/deployment/get.ts b/packages/commands/src/commands/managed-agent/deployment/get.ts new file mode 100644 index 000000000..59f244a52 --- /dev/null +++ b/packages/commands/src/commands/managed-agent/deployment/get.ts @@ -0,0 +1,38 @@ +import { getRemoteDeployment } from "@openagentpack/sdk"; +import { defineCommand, detectOutputFormat } from "bailian-cli-core"; +import { emitBare, emitResult } from "bailian-cli-runtime"; +import { displayValue } 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 { DEPLOYMENT_GET_FLAGS } from "./_shared.ts"; + +export default defineCommand({ + description: { + "en-US": "Get a Managed Agent deployment", + "zh-CN": "获取托管 Agent Deployment 详情", + }, + auth: "apiKey", + usageArgs: "--deployment-id <id>", + flags: DEPLOYMENT_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)}`); + } + }, +}); diff --git a/packages/commands/src/commands/managed-agent/deployment/list.ts b/packages/commands/src/commands/managed-agent/deployment/list.ts new file mode 100644 index 000000000..7c898c9d3 --- /dev/null +++ b/packages/commands/src/commands/managed-agent/deployment/list.ts @@ -0,0 +1,57 @@ +import { listRemoteDeployments } from "@openagentpack/sdk"; +import { defineCommand, detectOutputFormat } from "bailian-cli-core"; +import { emitCollection, 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"; +import { DEPLOYMENT_LIST_FLAGS, deploymentRows } from "./_shared.ts"; + +export default 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: DEPLOYMENT_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.", + }); + }, +}); diff --git a/packages/commands/src/commands/managed-agent/deployment/pause.ts b/packages/commands/src/commands/managed-agent/deployment/pause.ts new file mode 100644 index 000000000..2b639d92c --- /dev/null +++ b/packages/commands/src/commands/managed-agent/deployment/pause.ts @@ -0,0 +1,3 @@ +import { createSetDeploymentPausedCommand } from "./_set-paused.ts"; + +export default createSetDeploymentPausedCommand(true); diff --git a/packages/commands/src/commands/managed-agent/deployment/run.ts b/packages/commands/src/commands/managed-agent/deployment/run.ts new file mode 100644 index 000000000..a6ebd7210 --- /dev/null +++ b/packages/commands/src/commands/managed-agent/deployment/run.ts @@ -0,0 +1,57 @@ +import { runRemoteDeployment } from "@openagentpack/sdk"; +import { BailianError, defineCommand, detectOutputFormat, ExitCode } from "bailian-cli-core"; +import { emitBare, emitResult } from "bailian-cli-runtime"; +import { buildAgentRuntime, CREDENTIALS_NOTE } from "../_engine/config-loader.ts"; +import { withStdoutProtected } from "../_engine/console-capture.ts"; +import { withAgentErrors } from "../_engine/errors.ts"; +import { + DEPLOYMENT_RUN_ACTION_FLAGS, + resolveDeploymentTarget, + validateDeploymentActionTarget, +} from "./_shared.ts"; + +export default defineCommand({ + description: { + "en-US": "Run a Managed Agent deployment now", + "zh-CN": "立即运行托管 Agent Deployment", + }, + auth: "apiKey", + usageArgs: "(--deployment <name> | --deployment-id <id>) --yes", + flags: DEPLOYMENT_RUN_ACTION_FLAGS, + exampleArgs: ["--deployment daily-report --dry-run", "--deployment-id dep_abc --yes"], + notes: CREDENTIALS_NOTE, + validate: validateDeploymentActionTarget, + 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 ?? "-"}`); + }, +}); diff --git a/packages/commands/src/commands/managed-agent/deployment/runs/get.ts b/packages/commands/src/commands/managed-agent/deployment/runs/get.ts new file mode 100644 index 000000000..59ea3146b --- /dev/null +++ b/packages/commands/src/commands/managed-agent/deployment/runs/get.ts @@ -0,0 +1,38 @@ +import { getRemoteDeploymentRun } from "@openagentpack/sdk"; +import { defineCommand, detectOutputFormat } from "bailian-cli-core"; +import { emitBare, emitResult } from "bailian-cli-runtime"; +import { displayValue } 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 { DEPLOYMENT_RUNS_GET_FLAGS } from "../_shared.ts"; + +export default defineCommand({ + description: { + "en-US": "Get a Managed Agent deployment run", + "zh-CN": "获取托管 Agent Deployment Run 详情", + }, + auth: "apiKey", + usageArgs: "--run-id <id>", + flags: DEPLOYMENT_RUNS_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)}`); + } + }, +}); diff --git a/packages/commands/src/commands/managed-agent/deployment/runs/list.ts b/packages/commands/src/commands/managed-agent/deployment/runs/list.ts new file mode 100644 index 000000000..face4cbf5 --- /dev/null +++ b/packages/commands/src/commands/managed-agent/deployment/runs/list.ts @@ -0,0 +1,55 @@ +import { listRemoteDeploymentRuns } from "@openagentpack/sdk"; +import { defineCommand, detectOutputFormat } from "bailian-cli-core"; +import { emitCollection, 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"; +import { DEPLOYMENT_RUNS_LIST_FLAGS, deploymentRunRows } from "../_shared.ts"; + +export default 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: DEPLOYMENT_RUNS_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: deploymentRunRows(result.items), + hasMore: result.hasMore, + nextPage: result.nextPage, + emptyMessage: "No deployment runs found.", + }); + }, +}); diff --git a/packages/commands/src/commands/managed-agent/deployment/search.ts b/packages/commands/src/commands/managed-agent/deployment/search.ts new file mode 100644 index 000000000..d7c286148 --- /dev/null +++ b/packages/commands/src/commands/managed-agent/deployment/search.ts @@ -0,0 +1,67 @@ +import { listRemoteDeployments } from "@openagentpack/sdk"; +import { defineCommand, detectOutputFormat } from "bailian-cli-core"; +import { emitCollection, 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"; +import { DEPLOYMENT_SEARCH_FLAGS, deploymentRows } from "./_shared.ts"; + +export default defineCommand({ + description: { + "en-US": "Search Managed Agent deployments", + "zh-CN": "搜索托管 Agent Deployment", + }, + auth: "apiKey", + usageArgs: "--query <text> [--limit <n>] [--page <cursor>] [--all]", + flags: DEPLOYMENT_SEARCH_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.", + }); + }, +}); diff --git a/packages/commands/src/commands/managed-agent/deployment/unpause.ts b/packages/commands/src/commands/managed-agent/deployment/unpause.ts new file mode 100644 index 000000000..ac36bf29f --- /dev/null +++ b/packages/commands/src/commands/managed-agent/deployment/unpause.ts @@ -0,0 +1,3 @@ +import { createSetDeploymentPausedCommand } from "./_set-paused.ts"; + +export default createSetDeploymentPausedCommand(false); diff --git a/packages/commands/src/commands/managed-agent/environment.ts b/packages/commands/src/commands/managed-agent/environment.ts deleted file mode 100644 index 8ef2caa4d..000000000 --- a/packages/commands/src/commands/managed-agent/environment.ts +++ /dev/null @@ -1,174 +0,0 @@ -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.", - }); - }, -}); diff --git a/packages/commands/src/commands/managed-agent/environment/_shared.ts b/packages/commands/src/commands/managed-agent/environment/_shared.ts new file mode 100644 index 000000000..8c3bfc9ab --- /dev/null +++ b/packages/commands/src/commands/managed-agent/environment/_shared.ts @@ -0,0 +1,41 @@ +import type { CloudEnvironment } from "@openagentpack/sdk"; +import { + API_TARGET_FLAGS, + CURSOR_FLAGS, + displayValue, + INCLUDE_ARCHIVED_FLAG, + SEARCH_FLAGS, +} from "../_engine/api-helpers.ts"; + +export const ENVIRONMENT_LIST_FLAGS = { + ...API_TARGET_FLAGS, + ...CURSOR_FLAGS, + ...INCLUDE_ARCHIVED_FLAG, +}; + +export const ENVIRONMENT_SEARCH_FLAGS = { + ...API_TARGET_FLAGS, + limit: CURSOR_FLAGS.limit, + ...SEARCH_FLAGS, + ...INCLUDE_ARCHIVED_FLAG, +}; + +export const ENVIRONMENT_GET_FLAGS = { + ...API_TARGET_FLAGS, + environmentId: { + type: "string", + valueHint: "<id>", + required: true, + description: { "en-US": "Environment ID", "zh-CN": "Environment ID" }, + }, +} as const; + +export function environmentRows(environments: CloudEnvironment[]): string[][] { + return environments.map((environment) => [ + environment.id, + displayValue(environment.name), + displayValue(environment.scope), + displayValue(environment.version), + displayValue(environment.updated_at), + ]); +} diff --git a/packages/commands/src/commands/managed-agent/environment/create.ts b/packages/commands/src/commands/managed-agent/environment/create.ts new file mode 100644 index 000000000..813890aff --- /dev/null +++ b/packages/commands/src/commands/managed-agent/environment/create.ts @@ -0,0 +1,157 @@ +import { defineCommand, type FlagsDef } from "bailian-cli-core"; +import { CREDENTIALS_NOTE } from "../_engine/config-loader.ts"; +import { + loadScopedCreateProject, + parseMetadata, + resolveCandidateDeclaration, + runScopedTopLevelCreate, + SCOPED_CREATE_NOTE, +} from "../_engine/scoped-create.ts"; + +const FLAGS = { + name: { + type: "string", + valueHint: "<name>", + required: true, + description: { + "en-US": "Remote Environment display name; the YAML key is generated automatically", + "zh-CN": "远端 Environment 显示名称;YAML key 将自动生成", + }, + }, + description: { + type: "string", + valueHint: "<text>", + description: { "en-US": "Environment description", "zh-CN": "Environment 描述" }, + }, + metadata: { + type: "array", + valueHint: "<key=value>", + description: { + "en-US": "Metadata entry (repeatable)", + "zh-CN": "Metadata 条目(可重复)", + }, + }, + apt: packageFlag("APT"), + pip: packageFlag("pip"), + npm: packageFlag("npm"), + cargo: packageFlag("Cargo"), + gem: packageFlag("Ruby gem"), + go: packageFlag("Go"), + provider: providerFlag(), + file: fileFlag(), + yes: yesFlag(), +} satisfies FlagsDef; + +export default defineCommand({ + description: { + "en-US": "Declare and create one Managed Agent Environment through a scoped YAML apply", + "zh-CN": "通过定向 YAML Apply 声明并创建一个托管 Agent Environment", + }, + auth: "apiKey", + usageArgs: + "--name <name> [--description <text>] [--metadata <key=value>...] [--apt <package>...] [--pip <package>...] [--npm <package>...] [--cargo <package>...] [--gem <package>...] [--go <package>...] [--provider <name>] [--file <path>] [--yes]", + flags: FLAGS, + exampleArgs: [ + "--name Development", + "--name Development --pip pandas --npm typescript --metadata owner=platform --yes", + ], + notes: [ + ...CREDENTIALS_NOTE, + ...SCOPED_CREATE_NOTE, + { + "en-US": + "Creates a cloud Environment with unrestricted networking. Without --yes, only previews the generated YAML key and scoped plan.", + "zh-CN": + "创建使用 unrestricted 网络的 cloud Environment。不带 --yes 时仅预览自动生成的 YAML key 和定向计划。", + }, + ], + validate: (flags) => (!flags.name.trim() ? "--name must not be empty." : undefined), + async run(ctx) { + const project = await loadScopedCreateProject( + ctx, + ctx.flags.file ?? "agents.yaml", + ctx.flags.provider, + ); + const packages = Object.fromEntries( + ["apt", "pip", "npm", "cargo", "gem", "go"] + .map((manager) => [manager, ctx.flags[manager as keyof typeof ctx.flags]]) + .filter((entry) => Array.isArray(entry[1]) && entry[1].length > 0), + ); + const rawDeclaration: Record<string, unknown> = { + name: ctx.flags.name.trim(), + description: ctx.flags.description, + provider: project.provider, + config: { + type: "cloud", + networking: { type: "unrestricted" }, + ...(Object.keys(packages).length > 0 ? { packages } : {}), + }, + metadata: parseMetadata(ctx.flags.metadata), + }; + const resolvedDeclaration = await resolveCandidateDeclaration({ + project, + group: "environments", + rawDeclaration, + }); + await runScopedTopLevelCreate({ + host: ctx, + project, + group: "environments", + resourceType: "environment", + displayName: ctx.flags.name.trim(), + rawDeclaration, + resolvedDeclaration, + existingDeclarations: (project.config.environments ?? {}) as unknown as Record< + string, + Record<string, unknown> + >, + effectiveName: (key, declaration) => + typeof declaration.name === "string" ? declaration.name : key, + fallbackKey: "environment", + yes: ctx.flags.yes, + }); + }, +}); + +function packageFlag(manager: string) { + return { + type: "array" as const, + valueHint: "<package>", + description: { + "en-US": `${manager} package (repeatable)`, + "zh-CN": `${manager} 软件包(可重复)`, + }, + }; +} + +function providerFlag() { + return { + type: "string" as const, + valueHint: "<name>", + description: { + "en-US": "Target provider; inferred when unambiguous", + "zh-CN": "目标 Provider;可唯一确定时自动推断", + }, + }; +} + +function fileFlag() { + return { + type: "string" as const, + valueHint: "<path>", + description: { + "en-US": "Config file path (default: agents.yaml)", + "zh-CN": "配置文件路径(默认:agents.yaml)", + }, + }; +} + +function yesFlag() { + return { + type: "switch" as const, + description: { + "en-US": "Write YAML and run the scoped remote create", + "zh-CN": "写入 YAML 并执行定向远端创建", + }, + }; +} diff --git a/packages/commands/src/commands/managed-agent/environment/get.ts b/packages/commands/src/commands/managed-agent/environment/get.ts new file mode 100644 index 000000000..53de6a98f --- /dev/null +++ b/packages/commands/src/commands/managed-agent/environment/get.ts @@ -0,0 +1,41 @@ +import { getRemoteEnvironment } from "@openagentpack/sdk"; +import { defineCommand, detectOutputFormat } from "bailian-cli-core"; +import { emitBare, emitResult } from "bailian-cli-runtime"; +import { displayValue } 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 { ENVIRONMENT_GET_FLAGS } from "./_shared.ts"; + +export default defineCommand({ + description: { + "en-US": "Get a Managed Agent environment", + "zh-CN": "获取托管 Agent 环境详情", + }, + auth: "apiKey", + usageArgs: "--environment-id <id>", + flags: ENVIRONMENT_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)}`); + }, +}); diff --git a/packages/commands/src/commands/managed-agent/environment/list.ts b/packages/commands/src/commands/managed-agent/environment/list.ts new file mode 100644 index 000000000..b143f7a5d --- /dev/null +++ b/packages/commands/src/commands/managed-agent/environment/list.ts @@ -0,0 +1,53 @@ +import { listRemoteEnvironments } from "@openagentpack/sdk"; +import { defineCommand, detectOutputFormat } from "bailian-cli-core"; +import { emitCollection, 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"; +import { ENVIRONMENT_LIST_FLAGS, environmentRows } from "./_shared.ts"; + +export default defineCommand({ + description: { "en-US": "List Managed Agent environments", "zh-CN": "列出托管 Agent 环境" }, + auth: "apiKey", + usageArgs: "[--limit <n>] [--page <cursor>] [--all] [--include-archived]", + flags: ENVIRONMENT_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.", + }); + }, +}); diff --git a/packages/commands/src/commands/managed-agent/environment/search.ts b/packages/commands/src/commands/managed-agent/environment/search.ts new file mode 100644 index 000000000..aa1a7fedc --- /dev/null +++ b/packages/commands/src/commands/managed-agent/environment/search.ts @@ -0,0 +1,68 @@ +import { listRemoteEnvironments } from "@openagentpack/sdk"; +import { defineCommand, detectOutputFormat } from "bailian-cli-core"; +import { + emitCollection, + matchesQuery, + 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 { ENVIRONMENT_SEARCH_FLAGS, environmentRows } from "./_shared.ts"; + +export default defineCommand({ + description: { + "en-US": "Search Managed Agent environments", + "zh-CN": "搜索托管 Agent 环境", + }, + auth: "apiKey", + usageArgs: "--query <text> [--limit <n>] [--page-limit <n>] [--include-archived]", + flags: ENVIRONMENT_SEARCH_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.", + }); + }, +}); diff --git a/packages/commands/src/commands/managed-agent/skill-list.ts b/packages/commands/src/commands/managed-agent/skill-list.ts index ee42b96a4..8e6a19fd5 100644 --- a/packages/commands/src/commands/managed-agent/skill-list.ts +++ b/packages/commands/src/commands/managed-agent/skill-list.ts @@ -1,2 +1,2 @@ // Backward-compatible alias for the API-oriented `managed-agent skill list` command. -export { managedAgentSkillList as default } from "./skill.ts"; +export { default } from "./skill/list.ts"; diff --git a/packages/commands/src/commands/managed-agent/skill.ts b/packages/commands/src/commands/managed-agent/skill.ts deleted file mode 100644 index 7a8b90a40..000000000 --- a/packages/commands/src/commands/managed-agent/skill.ts +++ /dev/null @@ -1,363 +0,0 @@ -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}`); - }, -}); diff --git a/packages/commands/src/commands/managed-agent/skill/_shared.ts b/packages/commands/src/commands/managed-agent/skill/_shared.ts new file mode 100644 index 000000000..b952fe536 --- /dev/null +++ b/packages/commands/src/commands/managed-agent/skill/_shared.ts @@ -0,0 +1,87 @@ +import type { ProviderSkillInfo, SkillVersionInfo } from "@openagentpack/sdk"; +import { + API_TARGET_FLAGS, + CURSOR_FLAGS, + displayValue, + SEARCH_FLAGS, +} from "../_engine/api-helpers.ts"; + +export const SKILL_SOURCES = ["custom", "official", "all"] as const; +export 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; + +export const SKILL_LIST_FLAGS = { ...API_TARGET_FLAGS, ...CURSOR_FLAGS, ...SOURCE_FLAG }; + +export const SKILL_SEARCH_FLAGS = { + ...API_TARGET_FLAGS, + limit: CURSOR_FLAGS.limit, + ...SEARCH_FLAGS, + ...SOURCE_FLAG, +}; + +export const SKILL_GET_FLAGS = { + ...API_TARGET_FLAGS, + skillId: { + type: "string", + valueHint: "<id>", + required: true, + description: { "en-US": "Skill ID", "zh-CN": "Skill ID" }, + }, +} as const; + +export const SKILL_VERSIONS_FLAGS = { + ...SKILL_GET_FLAGS, + ...CURSOR_FLAGS, +}; + +export const SKILL_DOWNLOAD_FLAGS = { + ...SKILL_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; + +export 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), + ]); +} + +export 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), + ]); +} diff --git a/packages/commands/src/commands/managed-agent/skill/create.ts b/packages/commands/src/commands/managed-agent/skill/create.ts new file mode 100644 index 000000000..2ad512530 --- /dev/null +++ b/packages/commands/src/commands/managed-agent/skill/create.ts @@ -0,0 +1,124 @@ +import { dirname, relative, sep } from "node:path"; +import { inspectSkillSource } from "@openagentpack/sdk"; +import { BailianError, defineCommand, ExitCode, type FlagsDef } from "bailian-cli-core"; +import { CREDENTIALS_NOTE } from "../_engine/config-loader.ts"; +import { + loadScopedCreateProject, + resolveCandidateDeclaration, + runScopedTopLevelCreate, + SCOPED_CREATE_NOTE, +} from "../_engine/scoped-create.ts"; + +const FLAGS = { + source: { + type: "string", + valueHint: "<directory|zip|SKILL.md>", + required: true, + description: { + "en-US": "Local Skill directory, ZIP archive, or single SKILL.md", + "zh-CN": "本地 Skill 目录、ZIP 压缩包或单个 SKILL.md", + }, + }, + provider: { + type: "string", + valueHint: "<name>", + description: { + "en-US": "Target provider; inferred when unambiguous", + "zh-CN": "目标 Provider;可唯一确定时自动推断", + }, + }, + file: { + type: "string", + valueHint: "<path>", + description: { + "en-US": "Config file path (default: agents.yaml)", + "zh-CN": "配置文件路径(默认:agents.yaml)", + }, + }, + yes: { + type: "switch", + description: { + "en-US": "Write YAML and upload the Skill through the scoped create", + "zh-CN": "写入 YAML 并通过定向创建上传 Skill", + }, + }, +} satisfies FlagsDef; + +export default defineCommand({ + description: { + "en-US": "Declare and create one custom Managed Agent Skill from a local source", + "zh-CN": "从本地来源声明并创建一个自定义托管 Agent Skill", + }, + auth: "apiKey", + usageArgs: "--source <directory|zip|SKILL.md> [--provider <name>] [--file <path>] [--yes]", + flags: FLAGS, + exampleArgs: ["--source ./skills/code-review", "--source ./skill.zip --yes"], + notes: [ + ...CREDENTIALS_NOTE, + ...SCOPED_CREATE_NOTE, + { + "en-US": + "The YAML key is derived from SKILL.md frontmatter name. Remote URLs remain available through handwritten YAML plus full apply.", + "zh-CN": + "YAML key 从 SKILL.md frontmatter 的 name 生成。远程 URL 仍可手工写入 YAML 后执行全量 Apply。", + }, + ], + validate: (flags) => (!flags.source.trim() ? "--source must not be empty." : undefined), + async run(ctx) { + const project = await loadScopedCreateProject( + ctx, + ctx.flags.file ?? "agents.yaml", + ctx.flags.provider, + ); + const inspected = await inspectSkillSource(ctx.flags.source, { basePath: process.cwd() }); + const source = + relative(dirname(project.configPath), inspected.sourcePath).split(sep).join("/") || "."; + const existingSkills = (project.config.skills ?? {}) as unknown as Record< + string, + Record<string, unknown> + >; + const trackedSameName = await project.stateBackend.read(project.stateScope, (state) => + state.listResources().some((resource) => { + if (resource.address.provider !== project.provider || resource.address.type !== "skill") { + return false; + } + const declaration = existingSkills[resource.address.name]; + const canonicalName = + typeof declaration?.name === "string" ? declaration.name : resource.address.name; + return canonicalName === inspected.name; + }), + ); + if (trackedSameName) { + throw new BailianError( + `Skill '${inspected.name}' is already tracked.`, + ExitCode.USAGE, + "Modify its existing YAML declaration and run full `bl managed-agent apply` to create a new version.", + ); + } + const rawDeclaration: Record<string, unknown> = { + name: inspected.name, + source, + origin: "custom", + provider: project.provider, + }; + const resolvedDeclaration = await resolveCandidateDeclaration({ + project, + group: "skills", + rawDeclaration, + }); + await runScopedTopLevelCreate({ + host: ctx, + project, + group: "skills", + resourceType: "skill", + displayName: inspected.name, + rawDeclaration, + resolvedDeclaration, + existingDeclarations: existingSkills, + effectiveName: (key, declaration) => + typeof declaration.name === "string" ? declaration.name : key, + fallbackKey: "skill", + yes: ctx.flags.yes, + }); + }, +}); diff --git a/packages/commands/src/commands/managed-agent/skill/download.ts b/packages/commands/src/commands/managed-agent/skill/download.ts new file mode 100644 index 000000000..109ab37b8 --- /dev/null +++ b/packages/commands/src/commands/managed-agent/skill/download.ts @@ -0,0 +1,49 @@ +import { downloadRemoteSkill } from "@openagentpack/sdk"; +import { defineCommand, detectOutputFormat } from "bailian-cli-core"; +import { emitBare, emitResult } from "bailian-cli-runtime"; +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 { SKILL_DOWNLOAD_FLAGS } from "./_shared.ts"; + +export default 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: SKILL_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}`); + }, +}); diff --git a/packages/commands/src/commands/managed-agent/skill/get.ts b/packages/commands/src/commands/managed-agent/skill/get.ts new file mode 100644 index 000000000..f8cd69f04 --- /dev/null +++ b/packages/commands/src/commands/managed-agent/skill/get.ts @@ -0,0 +1,36 @@ +import { getRemoteSkill } from "@openagentpack/sdk"; +import { defineCommand, detectOutputFormat } from "bailian-cli-core"; +import { emitBare, emitResult } from "bailian-cli-runtime"; +import { displayValue } 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 { SKILL_GET_FLAGS } from "./_shared.ts"; + +export default defineCommand({ + description: { "en-US": "Get a Managed Agent skill", "zh-CN": "获取托管 Agent Skill 详情" }, + auth: "apiKey", + usageArgs: "--skill-id <id>", + flags: SKILL_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)}`); + }, +}); diff --git a/packages/commands/src/commands/managed-agent/skill/list.ts b/packages/commands/src/commands/managed-agent/skill/list.ts new file mode 100644 index 000000000..3221ae624 --- /dev/null +++ b/packages/commands/src/commands/managed-agent/skill/list.ts @@ -0,0 +1,94 @@ +import { listRemoteSkills } from "@openagentpack/sdk"; +import { defineCommand, detectOutputFormat } from "bailian-cli-core"; +import { emitCollection, 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"; +import { SKILL_LIST_FLAGS, skillRows, type SkillSource } from "./_shared.ts"; + +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 default 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: SKILL_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.", + }); + }, +}); diff --git a/packages/commands/src/commands/managed-agent/skill/search.ts b/packages/commands/src/commands/managed-agent/skill/search.ts new file mode 100644 index 000000000..c11158080 --- /dev/null +++ b/packages/commands/src/commands/managed-agent/skill/search.ts @@ -0,0 +1,73 @@ +import { listRemoteSkills } from "@openagentpack/sdk"; +import { defineCommand, detectOutputFormat } from "bailian-cli-core"; +import { + emitCollection, + matchesQuery, + 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 { SKILL_SEARCH_FLAGS, skillRows, type SkillSource } from "./_shared.ts"; + +export default 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: SKILL_SEARCH_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.", + }); + }, +}); diff --git a/packages/commands/src/commands/managed-agent/skill/versions.ts b/packages/commands/src/commands/managed-agent/skill/versions.ts new file mode 100644 index 000000000..bd11d0e5f --- /dev/null +++ b/packages/commands/src/commands/managed-agent/skill/versions.ts @@ -0,0 +1,55 @@ +import { listRemoteSkillVersions } from "@openagentpack/sdk"; +import { defineCommand, detectOutputFormat } from "bailian-cli-core"; +import { emitCollection, 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"; +import { SKILL_VERSIONS_FLAGS, versionRows } from "./_shared.ts"; + +export default 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: SKILL_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.", + }); + }, +}); diff --git a/packages/commands/src/commands/managed-agent/vault.ts b/packages/commands/src/commands/managed-agent/vault.ts deleted file mode 100644 index 38e916f47..000000000 --- a/packages/commands/src/commands/managed-agent/vault.ts +++ /dev/null @@ -1,165 +0,0 @@ -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.", - }); - }, -}); diff --git a/packages/commands/src/commands/managed-agent/vault/_shared.ts b/packages/commands/src/commands/managed-agent/vault/_shared.ts new file mode 100644 index 000000000..4383329dd --- /dev/null +++ b/packages/commands/src/commands/managed-agent/vault/_shared.ts @@ -0,0 +1,41 @@ +import type { CloudVault } from "@openagentpack/sdk"; +import { + API_TARGET_FLAGS, + CURSOR_FLAGS, + displayValue, + INCLUDE_ARCHIVED_FLAG, + SEARCH_FLAGS, +} from "../_engine/api-helpers.ts"; + +export const VAULT_LIST_FLAGS = { + ...API_TARGET_FLAGS, + ...CURSOR_FLAGS, + ...INCLUDE_ARCHIVED_FLAG, +}; + +export const VAULT_SEARCH_FLAGS = { + ...API_TARGET_FLAGS, + limit: CURSOR_FLAGS.limit, + ...SEARCH_FLAGS, + ...INCLUDE_ARCHIVED_FLAG, +}; + +export const VAULT_GET_FLAGS = { + ...API_TARGET_FLAGS, + vaultId: { + type: "string", + valueHint: "<id>", + required: true, + description: { "en-US": "Vault ID", "zh-CN": "Vault ID" }, + }, +} as const; + +export 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), + ]); +} diff --git a/packages/commands/src/commands/managed-agent/vault/create.ts b/packages/commands/src/commands/managed-agent/vault/create.ts new file mode 100644 index 000000000..bb1133a5b --- /dev/null +++ b/packages/commands/src/commands/managed-agent/vault/create.ts @@ -0,0 +1,108 @@ +import { defineCommand, type FlagsDef } from "bailian-cli-core"; +import { CREDENTIALS_NOTE } from "../_engine/config-loader.ts"; +import { + loadScopedCreateProject, + parseMetadata, + resolveCandidateDeclaration, + runScopedTopLevelCreate, + SCOPED_CREATE_NOTE, +} from "../_engine/scoped-create.ts"; + +const FLAGS = { + name: { + type: "string", + valueHint: "<name>", + required: true, + description: { + "en-US": "Remote Vault display name; the YAML key is generated automatically", + "zh-CN": "远端 Vault 显示名称;YAML key 将自动生成", + }, + }, + metadata: { + type: "array", + valueHint: "<key=value>", + description: { + "en-US": "Metadata entry (repeatable)", + "zh-CN": "Metadata 条目(可重复)", + }, + }, + provider: { + type: "string", + valueHint: "<name>", + description: { + "en-US": "Target provider; inferred when unambiguous", + "zh-CN": "目标 Provider;可唯一确定时自动推断", + }, + }, + file: { + type: "string", + valueHint: "<path>", + description: { + "en-US": "Config file path (default: agents.yaml)", + "zh-CN": "配置文件路径(默认:agents.yaml)", + }, + }, + yes: { + type: "switch", + description: { + "en-US": "Write YAML and run the scoped remote create", + "zh-CN": "写入 YAML 并执行定向远端创建", + }, + }, +} satisfies FlagsDef; + +export default defineCommand({ + description: { + "en-US": "Declare and create one empty Managed Agent Vault through a scoped YAML apply", + "zh-CN": "通过定向 YAML Apply 声明并创建一个空的托管 Agent Vault", + }, + auth: "apiKey", + usageArgs: + "--name <name> [--metadata <key=value>...] [--provider <name>] [--file <path>] [--yes]", + flags: FLAGS, + exampleArgs: ["--name Production", "--name Production --metadata owner=platform --yes"], + notes: [ + ...CREDENTIALS_NOTE, + ...SCOPED_CREATE_NOTE, + { + "en-US": "Creates an empty Vault. Add secrets later with `vault credential create`.", + "zh-CN": "创建空 Vault;随后使用 `vault credential create` 添加 Secret。", + }, + ], + validate: (flags) => (!flags.name.trim() ? "--name must not be empty." : undefined), + async run(ctx) { + const project = await loadScopedCreateProject( + ctx, + ctx.flags.file ?? "agents.yaml", + ctx.flags.provider, + ); + const rawDeclaration: Record<string, unknown> = { + display_name: ctx.flags.name.trim(), + provider: project.provider, + credentials: [], + metadata: parseMetadata(ctx.flags.metadata), + }; + const resolvedDeclaration = await resolveCandidateDeclaration({ + project, + group: "vaults", + rawDeclaration, + }); + await runScopedTopLevelCreate({ + host: ctx, + project, + group: "vaults", + resourceType: "vault", + displayName: ctx.flags.name.trim(), + rawDeclaration, + resolvedDeclaration, + existingDeclarations: (project.config.vaults ?? {}) as unknown as Record< + string, + Record<string, unknown> + >, + effectiveName: (key, declaration) => + typeof declaration.display_name === "string" ? declaration.display_name : key, + fallbackKey: "vault", + yes: ctx.flags.yes, + }); + }, +}); diff --git a/packages/commands/src/commands/managed-agent/vault/credential/create.ts b/packages/commands/src/commands/managed-agent/vault/credential/create.ts new file mode 100644 index 000000000..146474471 --- /dev/null +++ b/packages/commands/src/commands/managed-agent/vault/credential/create.ts @@ -0,0 +1,344 @@ +import { dirname } from "node:path"; +import { + createVaultCredentialWithStateBackend, + planVaultCredentialCreateWithStateBackend, + type ResolvedProjectConfig, + resolveProjectConfigFromObject, +} from "@openagentpack/sdk"; +import { + BailianError, + defineCommand, + detectOutputFormat, + ExitCode, + type FlagsDef, +} from "bailian-cli-core"; +import { emitBare, emitResult } from "bailian-cli-runtime"; +import { parseDocument } from "yaml"; +import { CREDENTIALS_NOTE } from "../../_engine/config-loader.ts"; +import { withStdoutProtected } from "../../_engine/console-capture.ts"; +import { withAgentErrors } from "../../_engine/errors.ts"; +import { + loadScopedCreateProject, + parseMetadata, + replaceConfigAtomically, + SCOPED_CREATE_NOTE, +} from "../../_engine/scoped-create.ts"; + +const FLAGS = { + vault: { + type: "string", + valueHint: "<yaml-key>", + required: true, + description: { + "en-US": "Existing tracked Vault key from agents.yaml", + "zh-CN": "agents.yaml 中已跟踪的 Vault key", + }, + }, + name: { + type: "string", + valueHint: "<name>", + required: true, + description: { + "en-US": "Credential display name", + "zh-CN": "Credential 显示名称", + }, + }, + secretName: { + type: "string", + valueHint: "<name>", + required: true, + description: { + "en-US": "Environment variable name exposed to the Agent", + "zh-CN": "提供给 Agent 的环境变量名", + }, + }, + secretEnv: { + type: "string", + valueHint: "<env-name>", + required: true, + description: { + "en-US": "Local environment variable containing the secret value", + "zh-CN": "保存真实 Secret 的本地环境变量名", + }, + }, + metadata: { + type: "array", + valueHint: "<key=value>", + description: { + "en-US": "Metadata entry (repeatable)", + "zh-CN": "Metadata 条目(可重复)", + }, + }, + provider: { + type: "string", + valueHint: "<name>", + description: { + "en-US": "Target provider; inferred when unambiguous", + "zh-CN": "目标 Provider;可唯一确定时自动推断", + }, + }, + file: { + type: "string", + valueHint: "<path>", + description: { + "en-US": "Config file path (default: agents.yaml)", + "zh-CN": "配置文件路径(默认:agents.yaml)", + }, + }, + yes: { + type: "switch", + description: { + "en-US": "Write YAML and create the remote Credential", + "zh-CN": "写入 YAML 并创建远端 Credential", + }, + }, +} satisfies FlagsDef; + +export default defineCommand({ + description: { + "en-US": "Append and create one environment-variable Credential in a tracked Vault", + "zh-CN": "在已跟踪 Vault 中追加并创建一个环境变量 Credential", + }, + auth: "apiKey", + usageArgs: + "--vault <yaml-key> --name <name> --secret-name <name> --secret-env <env-name> [--metadata <key=value>...] [--provider <name>] [--file <path>] [--yes]", + flags: FLAGS, + exampleArgs: [ + "--vault production --name api-token --secret-name API_TOKEN --secret-env PROD_API_TOKEN", + "--vault production --name api-token --secret-name API_TOKEN --secret-env PROD_API_TOKEN --yes", + ], + notes: [ + ...CREDENTIALS_NOTE, + ...SCOPED_CREATE_NOTE, + { + "en-US": + "--secret-env is an environment variable name, not the secret itself. The CLI auto-loads the nearest .env from the current directory upward; shell exports and CI secret injection also work.", + "zh-CN": + "--secret-env 接收环境变量名,不接收 Secret 本身。CLI 会从当前目录向上自动加载最近的 .env,也支持 Shell export 和 CI Secret 注入。", + }, + { + "en-US": + "YAML stores only ${ENV_NAME}. Never commit .env; subsequent full apply runs must provide the same environment variable.", + "zh-CN": + "YAML 只保存 ${ENV_NAME}。不要提交 .env;后续执行全量 Apply 时仍需提供同名环境变量。", + }, + ], + validate: (flags) => { + if (!flags.vault.trim()) return "--vault must not be empty."; + if (!flags.name.trim()) return "--name must not be empty."; + if (!flags.secretName.trim()) return "--secret-name must not be empty."; + if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(flags.secretEnv)) { + return "--secret-env must be a valid environment variable name."; + } + return undefined; + }, + async run(ctx) { + const format = detectOutputFormat(ctx.settings.output); + const project = await loadScopedCreateProject( + ctx, + ctx.flags.file ?? "agents.yaml", + ctx.flags.provider, + ); + const vault = project.config.vaults?.[ctx.flags.vault]; + if (!vault) { + throw new BailianError( + `Vault '${ctx.flags.vault}' is not declared in agents.yaml.`, + ExitCode.USAGE, + ); + } + const secretValue = process.env[ctx.flags.secretEnv]; + if (ctx.flags.yes && !ctx.settings.dryRun && !secretValue) { + throw new BailianError( + `Environment variable '${ctx.flags.secretEnv}' is not set or is empty.`, + ExitCode.USAGE, + `Set it in your shell, CI secret store, or a local .env file, then re-run with --secret-env ${ctx.flags.secretEnv}.`, + ); + } + const metadata = parseMetadata(ctx.flags.metadata); + const rawCredential = { + name: ctx.flags.name.trim(), + type: "environment_variable" as const, + secret_name: ctx.flags.secretName.trim(), + secret_value: `\${${ctx.flags.secretEnv}}`, + networking: { type: "unrestricted" as const }, + ...(metadata ? { metadata } : {}), + }; + const runtimeCredential = { + ...rawCredential, + secret_value: secretValue ?? "__dry_run_secret__", + }; + const document = parseDocument(project.source); + if (document.errors.length > 0) { + throw new BailianError( + `YAML parse error: ${document.errors.map((error) => error.message).join("; ")}`, + ExitCode.USAGE, + ); + } + const rawConfig = document.toJS() as { + vaults?: Record<string, { credentials?: unknown[] }>; + }; + const rawCredentials = rawConfig.vaults?.[ctx.flags.vault]?.credentials ?? []; + const sameNameCredentials = rawCredentials.filter( + (credential): credential is Record<string, unknown> => + Boolean( + credential && + typeof credential === "object" && + !Array.isArray(credential) && + (credential as Record<string, unknown>).name === rawCredential.name, + ), + ); + let reusedPending = false; + if (sameNameCredentials.length > 0) { + const lastCredential = rawCredentials.at(-1); + const existing = sameNameCredentials[0]!; + if ( + sameNameCredentials.length !== 1 || + existing !== lastCredential || + !sameCredentialDeclaration(existing, rawCredential) + ) { + throw new BailianError( + `Vault '${ctx.flags.vault}' already declares a different credential named '${rawCredential.name}'.`, + ExitCode.USAGE, + "Modify the existing YAML declaration and run full `bl managed-agent apply`; create never overwrites it.", + ); + } + reusedPending = true; + } + const rawCandidate = structuredClone(project.config) as unknown as Record<string, unknown>; + const rawVaults = rawCandidate.vaults as Record<string, Record<string, unknown>>; + const rawVault = rawVaults[ctx.flags.vault]!; + if (!reusedPending) { + rawVaults[ctx.flags.vault] = { + ...rawVault, + credentials: [ + ...((rawVault.credentials as unknown[] | undefined) ?? []), + runtimeCredential, + ], + }; + } + const resolvedCandidate = await withAgentErrors(() => + resolveProjectConfigFromObject(rawCandidate, { + projectName: project.projectName, + basePath: dirname(project.configPath), + }), + ); + const candidateConfig = resolvedCandidate.config; + if (!reusedPending) { + document.addIn(["vaults", ctx.flags.vault, "credentials"], rawCredential); + } + const nextSource = reusedPending ? project.source : document.toString(); + const backendInput = { + projectName: project.projectName, + config: candidateConfig as ResolvedProjectConfig, + configPath: project.configPath, + providers: { [project.provider]: candidateConfig.providers[project.provider] }, + stateBackend: project.stateBackend, + stateScope: project.stateScope, + }; + + if (ctx.settings.dryRun || !ctx.flags.yes) { + const planned = await withAgentErrors(() => + withStdoutProtected(() => + planVaultCredentialCreateWithStateBackend( + backendInput, + ctx.flags.vault, + ctx.flags.name.trim(), + { + provider: project.provider, + refresh: !ctx.settings.dryRun, + quiet: format === "json", + checkRemote: !ctx.settings.dryRun, + }, + ), + ), + ); + const result = { + vault: { key: ctx.flags.vault, remote_id: planned.vaultRemoteId }, + credential: { + name: ctx.flags.name.trim(), + secret_name: ctx.flags.secretName.trim(), + secret_env: ctx.flags.secretEnv, + }, + config_file: project.configPath, + yaml_written: false, + reused_pending: reusedPending, + requires_confirmation: !ctx.settings.dryRun, + ready_to_create: true, + remote_checked: planned.remoteChecked, + reuse_remote: planned.reuseRemote, + }; + if (format === "json") emitResult(result, format); + else { + emitBare(`Vault: ${ctx.flags.vault}`); + emitBare(`Credential: ${ctx.flags.name.trim()} (secret from ${ctx.flags.secretEnv})`); + emitBare( + ctx.settings.dryRun + ? "Dry run: YAML, State, and remote resources were not changed." + : "Preview only: re-run with --yes to append YAML and create this Credential.", + ); + } + return; + } + + await replaceConfigAtomically(project.configPath, project.source, nextSource); + let created; + try { + created = await withAgentErrors(() => + withStdoutProtected(() => + createVaultCredentialWithStateBackend( + backendInput, + ctx.flags.vault, + ctx.flags.name.trim(), + { provider: project.provider, refresh: true, quiet: format === "json" }, + ), + ), + ); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new BailianError( + message, + error instanceof BailianError ? error.exitCode : ExitCode.GENERAL, + "The YAML declaration was kept. Restore the same secret environment variable and re-run this command; an already-created matching Credential will be adopted.", + ); + } + const result = { + vault: { key: ctx.flags.vault, remote_id: created.vaultRemoteId }, + credential: { + id: created.credentialId, + name: ctx.flags.name.trim(), + secret_name: ctx.flags.secretName.trim(), + secret_env: ctx.flags.secretEnv, + }, + config_file: project.configPath, + yaml_written: true, + reused_pending: reusedPending, + adopted: created.adopted, + status: "completed", + }; + if (format === "json") emitResult(result, format); + else { + emitBare(`Wrote ${project.configPath} and created Credential '${ctx.flags.name.trim()}'.`); + } + }, +}); + +function sameCredentialDeclaration( + existing: Record<string, unknown>, + candidate: Record<string, unknown>, +): boolean { + return canonicalJson(existing) === canonicalJson(candidate); +} + +function canonicalJson(value: unknown): string { + const normalize = (candidate: unknown): unknown => { + if (Array.isArray(candidate)) return candidate.map(normalize); + if (!candidate || typeof candidate !== "object") return candidate; + return Object.fromEntries( + Object.entries(candidate as Record<string, unknown>) + .filter(([, entry]) => entry !== undefined) + .sort(([left], [right]) => left.localeCompare(right)) + .map(([key, entry]) => [key, normalize(entry)]), + ); + }; + return JSON.stringify(normalize(value)); +} diff --git a/packages/commands/src/commands/managed-agent/vault/get.ts b/packages/commands/src/commands/managed-agent/vault/get.ts new file mode 100644 index 000000000..a4763187e --- /dev/null +++ b/packages/commands/src/commands/managed-agent/vault/get.ts @@ -0,0 +1,35 @@ +import { getRemoteVault } from "@openagentpack/sdk"; +import { defineCommand, detectOutputFormat } from "bailian-cli-core"; +import { emitBare, emitResult } from "bailian-cli-runtime"; +import { displayValue } 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 { VAULT_GET_FLAGS } from "./_shared.ts"; + +export default defineCommand({ + description: { "en-US": "Get a Managed Agent vault", "zh-CN": "获取托管 Agent Vault 详情" }, + auth: "apiKey", + usageArgs: "--vault-id <id>", + flags: VAULT_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)}`); + }, +}); diff --git a/packages/commands/src/commands/managed-agent/vault/list.ts b/packages/commands/src/commands/managed-agent/vault/list.ts new file mode 100644 index 000000000..95c237ef3 --- /dev/null +++ b/packages/commands/src/commands/managed-agent/vault/list.ts @@ -0,0 +1,53 @@ +import { listRemoteVaults } from "@openagentpack/sdk"; +import { defineCommand, detectOutputFormat } from "bailian-cli-core"; +import { emitCollection, 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"; +import { VAULT_LIST_FLAGS, vaultRows } from "./_shared.ts"; + +export default defineCommand({ + description: { "en-US": "List Managed Agent vaults", "zh-CN": "列出托管 Agent Vault" }, + auth: "apiKey", + usageArgs: "[--limit <n>] [--page <cursor>] [--all] [--include-archived]", + flags: VAULT_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.", + }); + }, +}); diff --git a/packages/commands/src/commands/managed-agent/vault/search.ts b/packages/commands/src/commands/managed-agent/vault/search.ts new file mode 100644 index 000000000..2ade13496 --- /dev/null +++ b/packages/commands/src/commands/managed-agent/vault/search.ts @@ -0,0 +1,59 @@ +import { listRemoteVaults } from "@openagentpack/sdk"; +import { defineCommand, detectOutputFormat } from "bailian-cli-core"; +import { + emitCollection, + matchesQuery, + 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 { VAULT_SEARCH_FLAGS, vaultRows } from "./_shared.ts"; + +export default 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: VAULT_SEARCH_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.", + }); + }, +}); diff --git a/packages/commands/src/index.ts b/packages/commands/src/index.ts index a3655122a..4b296aed9 100644 --- a/packages/commands/src/index.ts +++ b/packages/commands/src/index.ts @@ -149,39 +149,34 @@ export { default as managedAgentSessionSend } from "./commands/managed-agent/ses 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 { default as managedAgentAgentCreate } from "./commands/managed-agent/agent-create.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 { default as managedAgentAgentCreate } from "./commands/managed-agent/agent/create.ts"; +export { default as managedAgentEnvironmentCreate } from "./commands/managed-agent/environment/create.ts"; +export { default as managedAgentSkillCreate } from "./commands/managed-agent/skill/create.ts"; +export { default as managedAgentVaultCreate } from "./commands/managed-agent/vault/create.ts"; +export { default as managedAgentVaultCredentialCreate } from "./commands/managed-agent/vault/credential/create.ts"; +export { default as managedAgentDeploymentCreate } from "./commands/managed-agent/deployment/create.ts"; +export { default as managedAgentAgentGet } from "./commands/managed-agent/agent/get.ts"; +export { default as managedAgentAgentList } from "./commands/managed-agent/agent/list.ts"; +export { default as managedAgentAgentSearch } from "./commands/managed-agent/agent/search.ts"; +export { default as managedAgentAgentVersions } from "./commands/managed-agent/agent/versions.ts"; +export { default as managedAgentEnvironmentGet } from "./commands/managed-agent/environment/get.ts"; +export { default as managedAgentEnvironmentList } from "./commands/managed-agent/environment/list.ts"; +export { default as managedAgentEnvironmentSearch } from "./commands/managed-agent/environment/search.ts"; +export { default as managedAgentSkillDownload } from "./commands/managed-agent/skill/download.ts"; +export { default as managedAgentSkillGet } from "./commands/managed-agent/skill/get.ts"; +export { default as managedAgentSkillSearch } from "./commands/managed-agent/skill/search.ts"; +export { default as managedAgentSkillVersions } from "./commands/managed-agent/skill/versions.ts"; +export { default as managedAgentVaultGet } from "./commands/managed-agent/vault/get.ts"; +export { default as managedAgentVaultList } from "./commands/managed-agent/vault/list.ts"; +export { default as managedAgentVaultSearch } from "./commands/managed-agent/vault/search.ts"; +export { default as managedAgentDeploymentGet } from "./commands/managed-agent/deployment/get.ts"; +export { default as managedAgentDeploymentList } from "./commands/managed-agent/deployment/list.ts"; +export { default as managedAgentDeploymentPause } from "./commands/managed-agent/deployment/pause.ts"; +export { default as managedAgentDeploymentRun } from "./commands/managed-agent/deployment/run.ts"; +export { default as managedAgentDeploymentRunsGet } from "./commands/managed-agent/deployment/runs/get.ts"; +export { default as managedAgentDeploymentRunsList } from "./commands/managed-agent/deployment/runs/list.ts"; +export { default as managedAgentDeploymentSearch } from "./commands/managed-agent/deployment/search.ts"; +export { default as managedAgentDeploymentUnpause } from "./commands/managed-agent/deployment/unpause.ts"; export { managedAgentSessionArchive, managedAgentSessionSearch, diff --git a/packages/commands/tests/e2e/managed-agent.e2e.test.ts b/packages/commands/tests/e2e/managed-agent.e2e.test.ts index f082b5a0a..ff1a56060 100644 --- a/packages/commands/tests/e2e/managed-agent.e2e.test.ts +++ b/packages/commands/tests/e2e/managed-agent.e2e.test.ts @@ -2,7 +2,13 @@ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import http from "node:http"; import type { AddressInfo } from "node:net"; import { tmpdir } from "node:os"; -import { join } from "node:path"; +import { basename, dirname, join } from "node:path"; +import { + LocalFileStateBackend, + planProjectWithStateBackend, + resolveProjectConfig, + type ResourceAddress, +} from "@openagentpack/sdk"; import { describe, expect, test } from "vite-plus/test"; import { parse } from "yaml"; import { e2eFixturesDir, parseStdoutJson, runCommandE2e } from "./helpers.ts"; @@ -22,6 +28,48 @@ const DEPLOYMENT_SAFETY_DIAGNOSTIC_CODES = [ "bailian.deployment.file.mount_path.duplicate", ]; +async function seedTrackedResources( + configPath: string, + addresses: ResourceAddress[], +): Promise<void> { + const loaded = await resolveProjectConfig(configPath); + const stateBackend = new LocalFileStateBackend({ configPath }); + const stateScope = { projectId: loaded.projectName ?? basename(dirname(configPath)) }; + const backendInput = { + projectName: loaded.projectName, + config: loaded.config, + configPath: loaded.configPath, + providers: loaded.config.providers, + stateBackend, + stateScope, + }; + const planned = await planProjectWithStateBackend(backendInput, { + provider: "bailian", + refresh: false, + quiet: true, + }); + await stateBackend.write(stateScope, (state) => { + for (const address of addresses) { + const action = planned.plan.actions.find( + (entry) => + entry.address.provider === address.provider && + entry.address.type === address.type && + entry.address.name === address.name, + ); + const contentHash = (action?.after as { content_hash?: string } | undefined)?.content_hash; + if (action?.action !== "create" || !contentHash) { + throw new Error(`Cannot seed tracked resource ${address.type}.${address.name}.`); + } + state.setResource({ + address, + remote_id: `${address.type}_${address.name}_e2e`, + content_hash: contentHash, + desired_hash: contentHash, + }); + } + }); +} + /** * managed-agent:help / 缺参不依赖密钥;所有 mutation 命令的 --dry-run * 必须在构建 SDK runtime(凭证注入 / 联网 / 写盘)之前短路,因此同样不需要密钥。 @@ -165,6 +213,43 @@ describe("e2e: managed-agent", () => { expect(stderr).toMatch(/--name|Missing required/i); }); + test.each([ + ["environment create", ["environment", "create"], /--name|--pip|--yes/i], + ["skill create", ["skill", "create"], /--source|--yes/i], + ["vault create", ["vault", "create"], /--name|--yes/i], + [ + "vault credential create", + ["vault", "credential", "create"], + /--vault|--secret-name|--secret-env|--yes/i, + ], + ["deployment create", ["deployment", "create"], /--name|--agent|--message|--yes/i], + ])("managed-agent %s --help 展示资源参数", async (_label, commandArgs, expected) => { + const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [ + "managed-agent", + ...commandArgs, + "--help", + ]); + expect(exitCode, stderr).toBe(0); + expect(stderr).toMatch(expected); + expect(stderr).not.toMatch(/--key/i); + }); + + test.each([ + [["environment", "create"], /--name|Missing required/i], + [["skill", "create"], /--source|Missing required/i], + [["vault", "create"], /--name|Missing required/i], + [["vault", "credential", "create"], /--vault|Missing required/i], + [["deployment", "create"], /--name|Missing required/i], + ])("managed-agent %s 缺少必填参数时退出为用法错误 (2)", async (commandArgs, expected) => { + const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [ + "managed-agent", + ...commandArgs, + "--quiet", + ]); + expect(exitCode).toBe(2); + expect(stderr).toMatch(expected); + }); + test("managed-agent agent create 默认只预览,不写 YAML", async () => { const sourceBefore = await readFile(AGENTS_YAML, "utf8"); const { stdout, stderr, exitCode } = await runCommandE2e( @@ -257,6 +342,243 @@ describe("e2e: managed-agent", () => { } }); + test("managed-agent environment create 远端失败后保留 YAML,重试复用 key", async () => { + const directory = await mkdtemp(join(tmpdir(), "bl-environment-create-e2e-")); + const configPath = join(directory, "agents.yaml"); + await writeFile(configPath, await readFile(AGENTS_YAML, "utf8"), "utf8"); + const requestBodies: Array<Record<string, unknown>> = []; + const server = http.createServer((request, response) => { + const chunks: Buffer[] = []; + request.on("data", (chunk: Buffer) => chunks.push(chunk)); + request.on("end", () => { + const body = Buffer.concat(chunks).toString("utf8"); + if (body) requestBodies.push(JSON.parse(body) as Record<string, unknown>); + response.writeHead(500, { "Content-Type": "application/json" }); + response.end(JSON.stringify({ message: "intentional environment create failure" })); + }); + }); + await new Promise<void>((resolveListen) => server.listen(0, "127.0.0.1", resolveListen)); + const address = server.address() as AddressInfo; + const args = [ + "managed-agent", + "environment", + "create", + "--name", + "Retry Environment", + "--pip", + "pandas", + "--file", + configPath, + "--yes", + "--output", + "json", + ]; + const env = { + DASHSCOPE_API_KEY: "sk-e2e-environment-create", + BAILIAN_BASE_URL: `http://127.0.0.1:${address.port}/api/v1/agentstudio`, + }; + try { + for (let attempt = 0; attempt < 2; attempt += 1) { + const result = await runCommandE2e(MANAGED_AGENT_ROUTES, args, env); + expect(result.exitCode, result.stderr).toBe(1); + } + const config = parse(await readFile(configPath, "utf8")) as { + environments: Record<string, { name?: string }>; + }; + expect(config.environments["retry-environment"]?.name).toBe("Retry Environment"); + expect(config.environments["retry-environment-2"]).toBeUndefined(); + expect(requestBodies).toHaveLength(2); + expect(requestBodies.every((body) => body.name === "Retry Environment")).toBe(true); + } finally { + await new Promise<void>((resolveClose) => server.close(() => resolveClose())); + await rm(directory, { recursive: true, force: true }); + } + }); + + test("vault credential create --yes 缺少 Secret env 时不写 YAML", async () => { + const directory = await mkdtemp(join(tmpdir(), "bl-vault-credential-secret-e2e-")); + const configPath = join(directory, "agents.yaml"); + const configSource = `version: "1" +providers: + bailian: + api_key: \${DASHSCOPE_API_KEY} + base_url: \${BAILIAN_BASE_URL} +defaults: + provider: bailian +vaults: + production: + display_name: Production + credentials: [] +`; + await writeFile(configPath, configSource, "utf8"); + try { + const { stderr, exitCode } = await runCommandE2e( + MANAGED_AGENT_ROUTES, + [ + "managed-agent", + "vault", + "credential", + "create", + "--vault", + "production", + "--name", + "api-token", + "--secret-name", + "API_TOKEN", + "--secret-env", + "E2E_SECRET_THAT_MUST_NOT_EXIST", + "--file", + configPath, + "--yes", + ], + { DASHSCOPE_API_KEY: "sk-e2e-vault-credential" }, + ); + expect(exitCode).toBe(2); + expect(stderr).toMatch(/E2E_SECRET_THAT_MUST_NOT_EXIST.*not set or is empty/i); + expect(await readFile(configPath, "utf8")).toBe(configSource); + } finally { + await rm(directory, { recursive: true, force: true }); + } + }); + + test("vault credential create 远端失败后复用 YAML 声明且不泄露 Secret", async () => { + const directory = await mkdtemp(join(tmpdir(), "bl-vault-credential-retry-e2e-")); + const configPath = join(directory, "agents.yaml"); + const requestBodies: Array<Record<string, unknown>> = []; + const server = http.createServer((request, response) => { + const requestUrl = new URL(request.url ?? "/", "http://127.0.0.1"); + if (request.method === "GET" && requestUrl.pathname === "/vaults/vault_production_e2e") { + response.writeHead(200, { "Content-Type": "application/json" }); + response.end( + JSON.stringify({ id: "vault_production_e2e", type: "vault", display_name: "Production" }), + ); + return; + } + if ( + request.method === "GET" && + requestUrl.pathname === "/vaults/vault_production_e2e/credentials" + ) { + response.writeHead(200, { "Content-Type": "application/json" }); + response.end(JSON.stringify({ data: [], next_page: null })); + return; + } + if ( + request.method === "POST" && + requestUrl.pathname === "/vaults/vault_production_e2e/credentials" + ) { + const chunks: Buffer[] = []; + request.on("data", (chunk: Buffer) => chunks.push(chunk)); + request.on("end", () => { + requestBodies.push(JSON.parse(Buffer.concat(chunks).toString("utf8"))); + response.writeHead(500, { "Content-Type": "application/json" }); + response.end(JSON.stringify({ message: "intentional credential create failure" })); + }); + return; + } + response.writeHead(404, { "Content-Type": "application/json" }); + response.end(JSON.stringify({ message: "unexpected mock route" })); + }); + await new Promise<void>((resolveListen) => server.listen(0, "127.0.0.1", resolveListen)); + const address = server.address() as AddressInfo; + const configSource = `version: "1" +providers: + bailian: + api_key: test + base_url: http://127.0.0.1:${address.port} +defaults: + provider: bailian +vaults: + production: + display_name: Production + credentials: [] +`; + await writeFile(configPath, configSource, "utf8"); + const args = [ + "managed-agent", + "vault", + "credential", + "create", + "--vault", + "production", + "--name", + "api-token", + "--secret-name", + "API_TOKEN", + "--secret-env", + "E2E_RETRY_SECRET", + "--file", + configPath, + "--yes", + "--output", + "json", + ]; + const env = { + DASHSCOPE_API_KEY: "sk-e2e-vault-credential", + E2E_RETRY_SECRET: "credential-secret-must-not-leak", + }; + try { + await seedTrackedResources(configPath, [ + { type: "vault", name: "production", provider: "bailian" }, + ]); + for (let attempt = 0; attempt < 2; attempt += 1) { + const result = await runCommandE2e(MANAGED_AGENT_ROUTES, args, env); + expect(result.exitCode).toBe(1); + expect(`${result.stdout}\n${result.stderr}`).not.toContain( + "credential-secret-must-not-leak", + ); + } + const config = parse(await readFile(configPath, "utf8")) as { + vaults: { production: { credentials: Array<{ name?: string; secret_value?: string }> } }; + }; + expect(config.vaults.production.credentials).toEqual([ + expect.objectContaining({ + name: "api-token", + secret_value: "${E2E_RETRY_SECRET}", + }), + ]); + expect(requestBodies).toHaveLength(2); + } finally { + await new Promise<void>((resolveClose) => server.close(() => resolveClose())); + await rm(directory, { recursive: true, force: true }); + } + }); + + test("deployment create 拒绝非法 Event 与不完整 Schedule", async () => { + const invalidEvent = await runCommandE2e(MANAGED_AGENT_ROUTES, [ + "managed-agent", + "deployment", + "create", + "--dry-run", + "--name", + "Invalid Event", + "--agent", + "assistant", + "--event", + '{"type":"tool.call","content":"x"}', + "--file", + AGENTS_YAML, + ]); + expect(invalidEvent.exitCode).toBe(2); + expect(invalidEvent.stderr).toMatch(/user\.message or system\.message/i); + + const incompleteSchedule = await runCommandE2e(MANAGED_AGENT_ROUTES, [ + "managed-agent", + "deployment", + "create", + "--name", + "Invalid Schedule", + "--agent", + "assistant", + "--message", + "Run", + "--schedule", + "0 9 * * *", + "--quiet", + ]); + expect(incompleteSchedule.exitCode).toBe(2); + expect(incompleteSchedule.stderr).toMatch(/--schedule and --timezone/i); + }); + test("managed-agent session delete 缺少 --session-id 时退出为用法错误 (2)", async () => { const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [ "managed-agent", @@ -347,8 +669,20 @@ describe("e2e: managed-agent(--dry-run 短路,不联网不写盘)", () => ]); expect(exitCode, stderr).toBe(0); const data = parseStdoutJson<{ - operations?: Record<string, { supported?: boolean; reason?: string }>; + operations?: Record<string, { supported?: boolean; auth?: string; reason?: string }>; }>(stdout); + for (const operation of [ + "agent.create", + "environment.create", + "skill.create", + "vault.create", + "vault.credential.create", + "deployment.create", + ]) { + expect(data.operations?.[operation]).toEqual( + expect.objectContaining({ supported: true, auth: "api_key" }), + ); + } 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); @@ -474,6 +808,214 @@ describe("e2e: managed-agent(--dry-run 短路,不联网不写盘)", () => expect(await readFile(AGENTS_YAML, "utf8")).toBe(sourceBefore); }); + test.each([ + [ + "environment", + ["environment", "create", "--name", "Create Environment", "--pip", "pandas"], + "environment", + "create-environment", + ], + ["vault", ["vault", "create", "--name", "Create Vault"], "vault", "create-vault"], + ])("%s create --dry-run 只规划目标资源且不改 YAML", async (_label, commandArgs, type, key) => { + const sourceBefore = await readFile(AGENTS_YAML, "utf8"); + const { stdout, stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [ + "managed-agent", + ...commandArgs, + "--dry-run", + "--file", + AGENTS_YAML, + "--output", + "json", + ]); + expect(exitCode, stderr).toBe(0); + const data = parseStdoutJson<{ + resource?: { type?: string; key?: string }; + yaml_written?: boolean; + actions?: Array<{ action?: string; address?: { type?: string; name?: string } }>; + }>(stdout); + expect(data.resource).toEqual(expect.objectContaining({ type, key })); + expect(data.yaml_written).toBe(false); + expect(data.actions).toContainEqual( + expect.objectContaining({ + action: "create", + address: expect.objectContaining({ type, name: key }), + }), + ); + expect( + data.actions?.every( + (action) => action.address?.type === type && action.address?.name === key, + ), + ).toBe(true); + expect(await readFile(AGENTS_YAML, "utf8")).toBe(sourceBefore); + }); + + test("skill create --dry-run 从 SKILL.md 取 name 且不改 YAML", async () => { + const directory = await mkdtemp(join(tmpdir(), "bl-skill-create-e2e-")); + const skillPath = join(directory, "SKILL.md"); + const sourceBefore = await readFile(AGENTS_YAML, "utf8"); + await writeFile( + skillPath, + "---\nname: create-skill\ndescription: E2E Skill\n---\n\n# Create Skill\n", + "utf8", + ); + try { + const { stdout, stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [ + "managed-agent", + "skill", + "create", + "--dry-run", + "--source", + skillPath, + "--file", + AGENTS_YAML, + "--output", + "json", + ]); + expect(exitCode, stderr).toBe(0); + const data = parseStdoutJson<{ + resource?: { type?: string; key?: string; name?: string }; + yaml_written?: boolean; + actions?: Array<{ action?: string; address?: { type?: string; name?: string } }>; + }>(stdout); + expect(data.resource).toEqual( + expect.objectContaining({ type: "skill", key: "create-skill", name: "create-skill" }), + ); + expect(data.yaml_written).toBe(false); + expect(data.actions).toContainEqual( + expect.objectContaining({ + action: "create", + address: expect.objectContaining({ type: "skill", name: "create-skill" }), + }), + ); + expect(await readFile(AGENTS_YAML, "utf8")).toBe(sourceBefore); + } finally { + await rm(directory, { recursive: true, force: true }); + } + }); + + test("deployment create --dry-run 只要求依赖 no-op,不检查无关资源", async () => { + const directory = await mkdtemp(join(tmpdir(), "bl-deployment-create-e2e-")); + const configPath = join(directory, "agents.yaml"); + const configSource = `version: "1" +providers: + bailian: + api_key: test + workspace_id: ws_e2e +defaults: + provider: bailian +agents: + assistant: + name: Assistant + model: qwen3.8-max + instructions: Help the user. + provider: bailian +`; + await writeFile(configPath, configSource, "utf8"); + try { + await seedTrackedResources(configPath, [ + { type: "agent", name: "assistant", provider: "bailian" }, + ]); + const { stdout, stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [ + "managed-agent", + "deployment", + "create", + "--dry-run", + "--name", + "Daily Report", + "--agent", + "assistant", + "--message", + "Generate the report", + "--file", + configPath, + "--output", + "json", + ]); + expect(exitCode, stderr).toBe(0); + const data = parseStdoutJson<{ + resource?: { type?: string; key?: string }; + yaml_written?: boolean; + ready_to_create?: boolean; + actions?: Array<{ action?: string; address?: { type?: string; name?: string } }>; + }>(stdout); + expect(data.resource).toEqual( + expect.objectContaining({ type: "deployment", key: "daily-report" }), + ); + expect(data.yaml_written).toBe(false); + expect(data.ready_to_create).toBe(true); + expect(data.actions?.filter((action) => action.action !== "no-op")).toEqual([ + expect.objectContaining({ + action: "create", + address: expect.objectContaining({ type: "deployment", name: "daily-report" }), + }), + ]); + expect(await readFile(configPath, "utf8")).toBe(configSource); + } finally { + await rm(directory, { recursive: true, force: true }); + } + }); + + test("vault credential create --dry-run 不读取远端、不要求 Secret、不写 YAML", async () => { + const directory = await mkdtemp(join(tmpdir(), "bl-vault-credential-create-e2e-")); + const configPath = join(directory, "agents.yaml"); + const configSource = `version: "1" +providers: + bailian: + api_key: test + workspace_id: ws_e2e +defaults: + provider: bailian +vaults: + production: + display_name: Production + credentials: [] +`; + await writeFile(configPath, configSource, "utf8"); + try { + await seedTrackedResources(configPath, [ + { type: "vault", name: "production", provider: "bailian" }, + ]); + const { stdout, stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [ + "managed-agent", + "vault", + "credential", + "create", + "--dry-run", + "--vault", + "production", + "--name", + "api-token", + "--secret-name", + "API_TOKEN", + "--secret-env", + "E2E_SECRET_THAT_IS_NOT_SET", + "--file", + configPath, + "--output", + "json", + ]); + expect(exitCode, stderr).toBe(0); + const data = parseStdoutJson<{ + credential?: { name?: string; secret_name?: string; secret_env?: string }; + yaml_written?: boolean; + remote_checked?: boolean; + reuse_remote?: boolean; + }>(stdout); + expect(data.credential).toEqual({ + name: "api-token", + secret_name: "API_TOKEN", + secret_env: "E2E_SECRET_THAT_IS_NOT_SET", + }); + expect(data.yaml_written).toBe(false); + expect(data.remote_checked).toBe(false); + expect(data.reuse_remote).toBe(false); + expect(stdout).not.toContain("__dry_run_secret__"); + expect(await readFile(configPath, "utf8")).toBe(configSource); + } finally { + await rm(directory, { recursive: true, force: true }); + } + }); + test("destroy --dry-run 仅输出计划", async () => { const { stdout, stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [ "managed-agent", diff --git a/packages/commands/tests/e2e/topic-routes.ts b/packages/commands/tests/e2e/topic-routes.ts index ab962b2cb..d9b4d6776 100644 --- a/packages/commands/tests/e2e/topic-routes.ts +++ b/packages/commands/tests/e2e/topic-routes.ts @@ -203,17 +203,22 @@ export const MANAGED_AGENT_ROUTES: E2eRouteExports = { "managed-agent agent get": "managedAgentAgentGet", "managed-agent agent search": "managedAgentAgentSearch", "managed-agent agent versions": "managedAgentAgentVersions", + "managed-agent environment create": "managedAgentEnvironmentCreate", "managed-agent environment list": "managedAgentEnvironmentList", "managed-agent environment get": "managedAgentEnvironmentGet", "managed-agent environment search": "managedAgentEnvironmentSearch", + "managed-agent skill create": "managedAgentSkillCreate", "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 create": "managedAgentVaultCreate", + "managed-agent vault credential create": "managedAgentVaultCredentialCreate", "managed-agent vault list": "managedAgentVaultList", "managed-agent vault get": "managedAgentVaultGet", "managed-agent vault search": "managedAgentVaultSearch", + "managed-agent deployment create": "managedAgentDeploymentCreate", "managed-agent deployment list": "managedAgentDeploymentList", "managed-agent deployment get": "managedAgentDeploymentGet", "managed-agent deployment search": "managedAgentDeploymentSearch", diff --git a/packages/commands/tests/managed-agent-create.test.ts b/packages/commands/tests/managed-agent-create.test.ts index edbe39fd3..48d23a3a7 100644 --- a/packages/commands/tests/managed-agent-create.test.ts +++ b/packages/commands/tests/managed-agent-create.test.ts @@ -7,7 +7,11 @@ import { normalizeAgentKey, replaceConfigAtomically, selectAgentKey, -} from "../src/commands/managed-agent/agent-create.ts"; +} from "../src/commands/managed-agent/agent/create.ts"; +import { + normalizeResourceKey, + parseMetadata, +} from "../src/commands/managed-agent/_engine/scoped-create.ts"; function candidate(instructions = "help") { return buildAgentDecl(undefined, { @@ -24,6 +28,17 @@ test("Agent key 从显示名生成并保留 Unicode", () => { expect(normalizeAgentKey("***")).toBe("agent"); }); +test("通用资源 key 与 metadata 输入保持稳定", () => { + expect(normalizeResourceKey(" Production Vault ", "vault")).toBe("production-vault"); + expect(normalizeResourceKey("生产 环境", "environment")).toBe("生产-环境"); + expect(normalizeResourceKey("***", "vault")).toBe("vault"); + expect(parseMetadata(["owner=platform", "empty="])).toEqual({ + owner: "platform", + empty: "", + }); + expect(() => parseMetadata(["missing-separator"])).toThrow(/Invalid metadata/); +}); + test("同名已跟踪 Agent 分配递增 key", () => { const state = StateManager.initialize("/tmp/bailian-cli-agent-create-state.json"); state.setResource({ @@ -86,7 +101,7 @@ test("YAML 替换校验原内容并保留文件权限", async () => { await expect( replaceConfigAtomically(configPath, "version: stale\n", "version: overwritten\n"), - ).rejects.toThrow(/changed while Agent create was being prepared/); + ).rejects.toThrow(/changed while resource create was being prepared/); expect(await readFile(configPath, "utf8")).toBe("version: new\n"); } finally { await rm(directory, { recursive: true, force: true }); diff --git a/skills/bailian-managed-agent/SKILL.md b/skills/bailian-managed-agent/SKILL.md index 69b55f361..3efbf9931 100644 --- a/skills/bailian-managed-agent/SKILL.md +++ b/skills/bailian-managed-agent/SKILL.md @@ -20,14 +20,15 @@ description: >- ## Safety guardrail (the most important rule) -`apply` / `destroy` **mutate persistent remote resources** and only execute when `--yes` is passed: +`apply` / `destroy` and single-resource `create --yes` **mutate persistent remote resources**: 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. +2. A single-resource create command previews its scoped plan when `--yes` is absent; show that preview before retrying it with `--yes`. +3. Only after explicit user confirmation, retry `apply` / `destroy` / single-resource create with `--yes`. +4. 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 运行时动作。 +API-oriented commands do not replace IaC. Agent / Environment / Skill / Vault / Deployment 的 create 命令仍通过 +`agents.yaml → scoped plan → scoped apply` 管理;查询命令和 Session、Event、File、Deployment 运行时动作直接调用 API。 `session archive|delete`、`file delete`、`deployment run` 也需要先 `--dry-run`,确认后才传 `--yes`。 ## IaC lifecycle @@ -40,23 +41,49 @@ API-oriented commands do not replace IaC. Agent / Environment / Skill / Vault 5. Destroy bl managed-agent destroy --yes # only after user confirmation ``` -## Scoped single-Agent create +## Scoped single-resource create -`bl managed-agent agent create`仍然先把声明写入 `agents.yaml`,再通过 SDK 的定向 Plan/Apply 创建远端资源, -不是绕过 State 的命令式 API 调用: +以下命令都先构造 `agents.yaml` 声明,再通过 SDK 的定向 Plan/Apply 创建远端资源,不绕过 State: -- 用户只提供 Agent `name`;CLI 自动生成稳定的 YAML 逻辑 key,同名 Agent 用递增后缀并存。 -- 默认只预览自动 key 和定向计划;`--dry-run` 完全离线,只有显式 `--yes` 才写 YAML 并创建远端 Agent。 -- 定向流程只刷新目标 Agent 及其传递依赖;无关资源不检测 Drift、不产生 action,也不阻塞。 -- 目标 Agent 必须是 `create`,相关依赖必须已经处于 `no-op`;项目级 Drift 和删除仍由全量 `plan/apply` 处理。 +| Resource | Command | +| --------------------- | ------------------------------------------ | +| Agent | `bl managed-agent agent create` | +| Environment | `bl managed-agent environment create` | +| Custom Skill | `bl managed-agent skill create` | +| Empty Vault | `bl managed-agent vault create` | +| Credential in a Vault | `bl managed-agent vault credential create` | +| Deployment | `bl managed-agent deployment create` | + +- 用户只提供资源 `name`;CLI 自动生成稳定的 YAML 逻辑 key,同名资源用递增后缀并存。Credential 追加到指定 Vault,不单独生成 key。 +- 默认只预览自动 key 和定向计划;`--dry-run` 完全离线,只有显式 `--yes` 才写 YAML 并创建远端资源。 +- 定向流程只刷新目标资源及其传递依赖;无关资源不检测 Drift、不产生 action,也不阻塞。 +- 目标资源必须是 `create`,相关依赖必须已经处于 `no-op`;项目级 Drift 和删除仍由全量 `plan/apply` 处理。 - 远端创建失败时保留 YAML 声明;修复相关依赖或 Provider 错误后,重复相同命令会复用待创建 key。 +- `skill create` 接受本地目录、ZIP 或单个 `SKILL.md`;远程 URL 仍需手工声明到 YAML,再执行全量 Apply。 + +### Credential secret input + +`vault credential create --secret-env <ENV_NAME>` 中的参数是变量名,不是 Secret 明文。Secret 可来自 Shell export、CI Secret 注入或可选的 `.env`;CLI 会从当前目录向上自动加载最近的 `.env`,用户不必创建该文件。 + +```bash +export PROD_API_TOKEN="..." +bl managed-agent vault credential create \ + --vault production \ + --name api-token \ + --secret-name API_TOKEN \ + --secret-env PROD_API_TOKEN +``` + +- YAML 只保存 `secret_value: ${PROD_API_TOKEN}`,输出、诊断和 State 都不保存明文。 +- 不要提交 `.env`;若使用 `.env`,先确认项目 `.gitignore` 已忽略它。 +- 预览后再带 `--yes` 重试。后续执行全量 Apply 时也必须提供同名环境变量。 -具体 flags、usage 和 examples 以 `reference/` 或 `bl managed-agent agent create --help` 为准。 +具体 flags、usage 和 examples 以 `reference/` 或对应命令的 `--help` 为准。 ## Deployment as IaC Deployment 与 Agent 一样声明在 `agents.yaml` 中,并复用同一条 `validate → plan → apply → destroy` IaC 链路; -CLI 不提供绕过 state 的命令式 Deployment CRUD。最小配置: +`deployment create` 可追加一条声明并走定向 Apply;CLI 不提供绕过 state 的 Deployment create/update/delete。最小配置: ```yaml deployments: @@ -89,19 +116,19 @@ Managed Agents 的子线程通过 Event 中的 `session_thread_id` 暴露;公 ## 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` | +| Intent | Command family | +| -------------------------------------- | ------------------------------------------------------------------------------------------------------- | +| Check exact API support/auth/reason | `bl managed-agent capabilities` | +| Discover agents and versions | `agent list`, `agent get`, `agent search`, `agent versions` | +| Discover environments | `environment list`, `environment get`, `environment search` | +| Discover skills and download a version | `skill list`, `skill get`, `skill search`, `skill versions`, `skill download` | +| Inspect vault envelopes | `vault list`, `vault get`, `vault search` | +| Inspect deployments and run history | `deployment list`, `deployment get`, `deployment search`, `deployment runs list`, `deployment runs get` | +| Run or pause deployments | `deployment run`, `deployment pause`, `deployment unpause` | +| Manage session metadata/lifecycle | `session list`, `session get`, `session search`, `session update`, `session archive`, `session delete` | +| Work with raw events | `session event send`, `session event list`, `session event stream` | +| Diagnose/export a session | `session debug`, `session export` | +| Work with files | `file upload`, `file list`, `file get`, `file search`, `file download`, `file delete` | - 所有 Cursor 都是不透明字符串:只回传 `next_page`,不得转换为数字页码。 - 客户端搜索默认最多扫描 10 页;需要扩大范围时显式传 `--page-limit`。Deployment 搜索直接映射服务端 `keyword`。 diff --git a/skills/bailian-managed-agent/reference/index.md b/skills/bailian-managed-agent/reference/index.md index 07f109f2b..d019fdf6d 100644 --- a/skills/bailian-managed-agent/reference/index.md +++ b/skills/bailian-managed-agent/reference/index.md @@ -9,70 +9,75 @@ Use this index for the skill-scoped quick index and global flags. ## Quick index -| Command | Authentication | Description | Detail | -| --------------------------------------- | -------------- | ------------------------------------------------------------------- | ------------------------------------ | -| `bl managed-agent agent create` | API Key | Declare and create one Managed Agent through an isolated YAML apply | [managed-agent.md](managed-agent.md) | -| `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) | +| Command | Authentication | Description | Detail | +| ------------------------------------------ | -------------- | ---------------------------------------------------------------------------- | ------------------------------------ | +| `bl managed-agent agent create` | API Key | Declare and create one Managed Agent through an isolated YAML apply | [managed-agent.md](managed-agent.md) | +| `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 create` | API Key | Declare and create one Managed Agent Deployment through a scoped YAML apply | [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 create` | API Key | Declare and create one Managed Agent Environment through a scoped YAML apply | [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 create` | API Key | Declare and create one custom Managed Agent Skill from a local source | [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 create` | API Key | Declare and create one empty Managed Agent Vault through a scoped YAML apply | [managed-agent.md](managed-agent.md) | +| `bl managed-agent vault credential create` | API Key | Append and create one environment-variable Credential in a tracked Vault | [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` | `agent create`, `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) | +| Group | Commands | Reference | +| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------ | +| `managed-agent` | `agent create`, `agent get`, `agent list`, `agent search`, `agent versions`, `apply`, `capabilities`, `deployment create`, `deployment get`, `deployment list`, `deployment pause`, `deployment run`, `deployment runs get`, `deployment runs list`, `deployment search`, `deployment unpause`, `destroy`, `environment create`, `environment get`, `environment list`, `environment search`, `file delete`, `file download`, `file get`, `file list`, `file search`, `file upload`, `init`, `plan`, `session archive`, `session create`, `session debug`, `session delete`, `session event list`, `session event send`, `session event stream`, `session events`, `session export`, `session get`, `session list`, `session run`, `session search`, `session send`, `session update`, `skill create`, `skill download`, `skill get`, `skill list`, `skill search`, `skill versions`, `skill-list`, `state import`, `state list`, `state rm`, `state show`, `validate`, `vault create`, `vault credential create`, `vault get`, `vault list`, `vault search` | [managed-agent.md](managed-agent.md) | ## Global flags diff --git a/skills/bailian-managed-agent/reference/managed-agent.md b/skills/bailian-managed-agent/reference/managed-agent.md index 93600bc71..25cc4fe84 100644 --- a/skills/bailian-managed-agent/reference/managed-agent.md +++ b/skills/bailian-managed-agent/reference/managed-agent.md @@ -7,64 +7,69 @@ Index: [index.md](index.md) ## Commands in this group -| Command | Authentication | Description | -| --------------------------------------- | -------------- | ------------------------------------------------------------------- | -| `bl managed-agent agent create` | API Key | Declare and create one Managed Agent through an isolated YAML apply | -| `bl managed-agent agent get` | API Key | Get a Managed Agent | -| `bl managed-agent agent list` | API Key | List Managed Agents | -| `bl managed-agent agent search` | API Key | Search Managed Agents | -| `bl managed-agent agent versions` | API Key | List Managed Agent versions | -| `bl managed-agent apply` | API Key | Apply planned changes to create/update/delete agent resources | -| `bl managed-agent capabilities` | No Auth | Show operation-level Managed Agents API capabilities | -| `bl managed-agent deployment get` | API Key | Get a Managed Agent deployment | -| `bl managed-agent deployment list` | API Key | List Managed Agent deployments | -| `bl managed-agent deployment pause` | API Key | Pause a Managed Agent deployment | -| `bl managed-agent deployment run` | API Key | Run a Managed Agent deployment now | -| `bl managed-agent deployment runs get` | API Key | Get a Managed Agent deployment run | -| `bl managed-agent deployment runs list` | API Key | List runs for a Managed Agent deployment | -| `bl managed-agent deployment search` | API Key | Search Managed Agent deployments | -| `bl managed-agent deployment unpause` | API Key | Unpause a Managed Agent deployment | -| `bl managed-agent destroy` | API Key | Destroy all managed agent resources tracked in state | -| `bl managed-agent environment get` | API Key | Get a Managed Agent environment | -| `bl managed-agent environment list` | API Key | List Managed Agent environments | -| `bl managed-agent environment search` | API Key | Search Managed Agent environments | -| `bl managed-agent file delete` | API Key | Delete a Managed Agent file | -| `bl managed-agent file download` | API Key | Download Managed Agent file content | -| `bl managed-agent file get` | API Key | Get Managed Agent file metadata | -| `bl managed-agent file list` | API Key | List Managed Agent files | -| `bl managed-agent file search` | API Key | Search Managed Agent files | -| `bl managed-agent file upload` | API Key | Upload a Managed Agent file | -| `bl managed-agent init` | No Auth | Create a new agents.yaml template | -| `bl managed-agent plan` | API Key | Show what changes would be applied to agent infrastructure | -| `bl managed-agent session archive` | API Key | Archive a Managed Agent session | -| `bl managed-agent session create` | API Key | Create a new session for an agent | -| `bl managed-agent session debug` | API Key | Aggregate session diagnostics | -| `bl managed-agent session delete` | API Key | Delete a session | -| `bl managed-agent session event list` | API Key | List events for a Managed Agent session | -| `bl managed-agent session event send` | API Key | Send raw events to a Managed Agent session | -| `bl managed-agent session event stream` | API Key | Stream events from a Managed Agent session | -| `bl managed-agent session events` | API Key | List events for a Managed Agent session | -| `bl managed-agent session export` | API Key | Export session diagnostics as a ZIP | -| `bl managed-agent session get` | API Key | Get details of a session | -| `bl managed-agent session list` | API Key | List sessions from the provider | -| `bl managed-agent session run` | API Key | Create a session, send a message, and stream the response | -| `bl managed-agent session search` | API Key | Search Managed Agent sessions | -| `bl managed-agent session send` | API Key | Send a message to an existing session and stream the response | -| `bl managed-agent session update` | API Key | Update a Managed Agent session | -| `bl managed-agent skill download` | API Key | Download a Managed Agent skill version | -| `bl managed-agent skill get` | API Key | Get a Managed Agent skill | -| `bl managed-agent skill list` | API Key | List Managed Agent skills | -| `bl managed-agent skill search` | API Key | Search Managed Agent skills | -| `bl managed-agent skill versions` | API Key | List Managed Agent skill versions | -| `bl managed-agent skill-list` | API Key | List Managed Agent skills | -| `bl managed-agent state import` | API Key | Import an existing remote resource into agents state | -| `bl managed-agent state list` | No Auth | List resources tracked in agents state | -| `bl managed-agent state rm` | No Auth | Remove a resource from state without destroying it remotely | -| `bl managed-agent state show` | No Auth | Show details of a resource in agents state | -| `bl managed-agent validate` | No Auth | Validate an agents.yaml configuration (offline) | -| `bl managed-agent vault get` | API Key | Get a Managed Agent vault | -| `bl managed-agent vault list` | API Key | List Managed Agent vaults | -| `bl managed-agent vault search` | API Key | Search Managed Agent vaults | +| Command | Authentication | Description | +| ------------------------------------------ | -------------- | ---------------------------------------------------------------------------- | +| `bl managed-agent agent create` | API Key | Declare and create one Managed Agent through an isolated YAML apply | +| `bl managed-agent agent get` | API Key | Get a Managed Agent | +| `bl managed-agent agent list` | API Key | List Managed Agents | +| `bl managed-agent agent search` | API Key | Search Managed Agents | +| `bl managed-agent agent versions` | API Key | List Managed Agent versions | +| `bl managed-agent apply` | API Key | Apply planned changes to create/update/delete agent resources | +| `bl managed-agent capabilities` | No Auth | Show operation-level Managed Agents API capabilities | +| `bl managed-agent deployment create` | API Key | Declare and create one Managed Agent Deployment through a scoped YAML apply | +| `bl managed-agent deployment get` | API Key | Get a Managed Agent deployment | +| `bl managed-agent deployment list` | API Key | List Managed Agent deployments | +| `bl managed-agent deployment pause` | API Key | Pause a Managed Agent deployment | +| `bl managed-agent deployment run` | API Key | Run a Managed Agent deployment now | +| `bl managed-agent deployment runs get` | API Key | Get a Managed Agent deployment run | +| `bl managed-agent deployment runs list` | API Key | List runs for a Managed Agent deployment | +| `bl managed-agent deployment search` | API Key | Search Managed Agent deployments | +| `bl managed-agent deployment unpause` | API Key | Unpause a Managed Agent deployment | +| `bl managed-agent destroy` | API Key | Destroy all managed agent resources tracked in state | +| `bl managed-agent environment create` | API Key | Declare and create one Managed Agent Environment through a scoped YAML apply | +| `bl managed-agent environment get` | API Key | Get a Managed Agent environment | +| `bl managed-agent environment list` | API Key | List Managed Agent environments | +| `bl managed-agent environment search` | API Key | Search Managed Agent environments | +| `bl managed-agent file delete` | API Key | Delete a Managed Agent file | +| `bl managed-agent file download` | API Key | Download Managed Agent file content | +| `bl managed-agent file get` | API Key | Get Managed Agent file metadata | +| `bl managed-agent file list` | API Key | List Managed Agent files | +| `bl managed-agent file search` | API Key | Search Managed Agent files | +| `bl managed-agent file upload` | API Key | Upload a Managed Agent file | +| `bl managed-agent init` | No Auth | Create a new agents.yaml template | +| `bl managed-agent plan` | API Key | Show what changes would be applied to agent infrastructure | +| `bl managed-agent session archive` | API Key | Archive a Managed Agent session | +| `bl managed-agent session create` | API Key | Create a new session for an agent | +| `bl managed-agent session debug` | API Key | Aggregate session diagnostics | +| `bl managed-agent session delete` | API Key | Delete a session | +| `bl managed-agent session event list` | API Key | List events for a Managed Agent session | +| `bl managed-agent session event send` | API Key | Send raw events to a Managed Agent session | +| `bl managed-agent session event stream` | API Key | Stream events from a Managed Agent session | +| `bl managed-agent session events` | API Key | List events for a Managed Agent session | +| `bl managed-agent session export` | API Key | Export session diagnostics as a ZIP | +| `bl managed-agent session get` | API Key | Get details of a session | +| `bl managed-agent session list` | API Key | List sessions from the provider | +| `bl managed-agent session run` | API Key | Create a session, send a message, and stream the response | +| `bl managed-agent session search` | API Key | Search Managed Agent sessions | +| `bl managed-agent session send` | API Key | Send a message to an existing session and stream the response | +| `bl managed-agent session update` | API Key | Update a Managed Agent session | +| `bl managed-agent skill create` | API Key | Declare and create one custom Managed Agent Skill from a local source | +| `bl managed-agent skill download` | API Key | Download a Managed Agent skill version | +| `bl managed-agent skill get` | API Key | Get a Managed Agent skill | +| `bl managed-agent skill list` | API Key | List Managed Agent skills | +| `bl managed-agent skill search` | API Key | Search Managed Agent skills | +| `bl managed-agent skill versions` | API Key | List Managed Agent skill versions | +| `bl managed-agent skill-list` | API Key | List Managed Agent skills | +| `bl managed-agent state import` | API Key | Import an existing remote resource into agents state | +| `bl managed-agent state list` | No Auth | List resources tracked in agents state | +| `bl managed-agent state rm` | No Auth | Remove a resource from state without destroying it remotely | +| `bl managed-agent state show` | No Auth | Show details of a resource in agents state | +| `bl managed-agent validate` | No Auth | Validate an agents.yaml configuration (offline) | +| `bl managed-agent vault create` | API Key | Declare and create one empty Managed Agent Vault through a scoped YAML apply | +| `bl managed-agent vault credential create` | API Key | Append and create one environment-variable Credential in a tracked Vault | +| `bl managed-agent vault get` | API Key | Get a Managed Agent vault | +| `bl managed-agent vault list` | API Key | List Managed Agent vaults | +| `bl managed-agent vault search` | API Key | Search Managed Agent vaults | ## Command details @@ -332,6 +337,55 @@ bl managed-agent capabilities bl managed-agent capabilities --provider bailian --output json ``` +### `bl managed-agent deployment create` + +| Field | Value | +| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent deployment create` | +| **Description** | Declare and create one Managed Agent Deployment through a scoped YAML apply | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent deployment create --name <name> --agent <yaml-key> (--message <text>... \| --event <json\|@path>...) [--agent-version <number>] [--environment <yaml-key>] [--vault <yaml-key>...] [--resource <json\|@path>...] [--schedule <cron> --timezone <timezone>] [--description <text>] [--metadata <key=value>...] [--provider <name>] [--file <path>] [--yes]` | + +#### Flags + +| Flag | Type | Required | Description | +| -------------------------- | ------ | -------- | ----------------------------------------------------------------------- | +| `--name <name>` | string | yes | Remote Deployment display name; the YAML key is generated automatically | +| `--agent <yaml-key>` | string | yes | Existing Agent key from agents.yaml | +| `--agent-version <number>` | number | no | Agent version | +| `--environment <yaml-key>` | string | no | Existing Environment key from agents.yaml | +| `--vault <yaml-key>` | array | no | Existing Vault key from agents.yaml (repeatable) | +| `--message <text>` | array | no | Initial user message (repeatable) | +| `--event <json\|@path>` | array | no | Initial user.message or system.message JSON (repeatable) | +| `--resource <json\|@path>` | array | no | File Resource JSON with source or file_id (repeatable) | +| `--schedule <cron>` | string | no | Five-field cron expression | +| `--timezone <timezone>` | string | no | IANA schedule timezone | +| `--description <text>` | string | no | Deployment description | +| `--metadata <key=value>` | array | no | Metadata entry (repeatable) | +| `--provider <name>` | string | no | Target provider; inferred when unambiguous | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--yes` | switch | no | Write YAML and run the scoped remote create | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. +- Without --yes, this command only previews. --dry-run is fully offline. The scoped flow checks only the target resource and its transitive dependencies; unrelated resources are not refreshed or drift-checked. +- Initial Events must contain 1-50 user.message/system.message entries. --resource accepts only File Resources in this release. + +#### Examples + +```bash +bl managed-agent deployment create --name Daily --agent assistant --message "Generate the report" +``` + +```bash +bl managed-agent deployment create --name Daily --agent assistant --event '{"type":"system.message","content":"Be concise"}' --message "Run" --yes +``` + ### `bl managed-agent deployment get` | Field | Value | @@ -662,6 +716,52 @@ bl managed-agent destroy --yes bl managed-agent destroy --yes --cascade ``` +### `bl managed-agent environment create` + +| Field | Value | +| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent environment create` | +| **Description** | Declare and create one Managed Agent Environment through a scoped YAML apply | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent environment create --name <name> [--description <text>] [--metadata <key=value>...] [--apt <package>...] [--pip <package>...] [--npm <package>...] [--cargo <package>...] [--gem <package>...] [--go <package>...] [--provider <name>] [--file <path>] [--yes]` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------------ | ------ | -------- | ------------------------------------------------------------------------ | +| `--name <name>` | string | yes | Remote Environment display name; the YAML key is generated automatically | +| `--description <text>` | string | no | Environment description | +| `--metadata <key=value>` | array | no | Metadata entry (repeatable) | +| `--apt <package>` | array | no | APT package (repeatable) | +| `--pip <package>` | array | no | pip package (repeatable) | +| `--npm <package>` | array | no | npm package (repeatable) | +| `--cargo <package>` | array | no | Cargo package (repeatable) | +| `--gem <package>` | array | no | Ruby gem package (repeatable) | +| `--go <package>` | array | no | Go package (repeatable) | +| `--provider <name>` | string | no | Target provider; inferred when unambiguous | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--yes` | switch | no | Write YAML and run the scoped remote create | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. +- Without --yes, this command only previews. --dry-run is fully offline. The scoped flow checks only the target resource and its transitive dependencies; unrelated resources are not refreshed or drift-checked. +- Creates a cloud Environment with unrestricted networking. Without --yes, only previews the generated YAML key and scoped plan. + +#### Examples + +```bash +bl managed-agent environment create --name Development +``` + +```bash +bl managed-agent environment create --name Development --pip pandas --npm typescript --metadata owner=platform --yes +``` + ### `bl managed-agent environment get` | Field | Value | @@ -1624,6 +1724,44 @@ bl managed-agent session update --session-id sess_abc --title 'investigation' bl managed-agent session update --session-id sess_abc --metadata @metadata.json ``` +### `bl managed-agent skill create` + +| Field | Value | +| ------------------ | --------------------------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent skill create` | +| **Description** | Declare and create one custom Managed Agent Skill from a local source | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent skill create --source <directory\|zip\|SKILL.md> [--provider <name>] [--file <path>] [--yes]` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------------------------- | ------ | -------- | --------------------------------------------------------- | +| `--source <directory\|zip\|SKILL.md>` | string | yes | Local Skill directory, ZIP archive, or single SKILL.md | +| `--provider <name>` | string | no | Target provider; inferred when unambiguous | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--yes` | switch | no | Write YAML and upload the Skill through the scoped create | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. +- Without --yes, this command only previews. --dry-run is fully offline. The scoped flow checks only the target resource and its transitive dependencies; unrelated resources are not refreshed or drift-checked. +- The YAML key is derived from SKILL.md frontmatter name. Remote URLs remain available through handwritten YAML plus full apply. + +#### Examples + +```bash +bl managed-agent skill create --source ./skills/code-review +``` + +```bash +bl managed-agent skill create --source ./skill.zip --yes +``` + ### `bl managed-agent skill download` | Field | Value | @@ -1993,6 +2131,88 @@ bl managed-agent validate bl managed-agent validate --file agents.yaml ``` +### `bl managed-agent vault create` + +| Field | Value | +| ------------------ | --------------------------------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent vault create` | +| **Description** | Declare and create one empty Managed Agent Vault through a scoped YAML apply | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent vault create --name <name> [--metadata <key=value>...] [--provider <name>] [--file <path>] [--yes]` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------------ | ------ | -------- | ------------------------------------------------------------------ | +| `--name <name>` | string | yes | Remote Vault display name; the YAML key is generated automatically | +| `--metadata <key=value>` | array | no | Metadata entry (repeatable) | +| `--provider <name>` | string | no | Target provider; inferred when unambiguous | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--yes` | switch | no | Write YAML and run the scoped remote create | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. +- Without --yes, this command only previews. --dry-run is fully offline. The scoped flow checks only the target resource and its transitive dependencies; unrelated resources are not refreshed or drift-checked. +- Creates an empty Vault. Add secrets later with `vault credential create`. + +#### Examples + +```bash +bl managed-agent vault create --name Production +``` + +```bash +bl managed-agent vault create --name Production --metadata owner=platform --yes +``` + +### `bl managed-agent vault credential create` + +| Field | Value | +| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **Name** | `managed-agent vault credential create` | +| **Description** | Append and create one environment-variable Credential in a tracked Vault | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent vault credential create --vault <yaml-key> --name <name> --secret-name <name> --secret-env <env-name> [--metadata <key=value>...] [--provider <name>] [--file <path>] [--yes]` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------------- | ------ | -------- | ------------------------------------------------------ | +| `--vault <yaml-key>` | string | yes | Existing tracked Vault key from agents.yaml | +| `--name <name>` | string | yes | Credential display name | +| `--secret-name <name>` | string | yes | Environment variable name exposed to the Agent | +| `--secret-env <env-name>` | string | yes | Local environment variable containing the secret value | +| `--metadata <key=value>` | array | no | Metadata entry (repeatable) | +| `--provider <name>` | string | no | Target provider; inferred when unambiguous | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--yes` | switch | no | Write YAML and create the remote Credential | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | + +#### Notes + +- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). +- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. +- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. +- Without --yes, this command only previews. --dry-run is fully offline. The scoped flow checks only the target resource and its transitive dependencies; unrelated resources are not refreshed or drift-checked. +- --secret-env is an environment variable name, not the secret itself. The CLI auto-loads the nearest .env from the current directory upward; shell exports and CI secret injection also work. +- YAML stores only ${ENV_NAME}. Never commit .env; subsequent full apply runs must provide the same environment variable. + +#### Examples + +```bash +bl managed-agent vault credential create --vault production --name api-token --secret-name API_TOKEN --secret-env PROD_API_TOKEN +``` + +```bash +bl managed-agent vault credential create --vault production --name api-token --secret-name API_TOKEN --secret-env PROD_API_TOKEN --yes +``` + ### `bl managed-agent vault get` | Field | Value | From 449eede2375eb6573ecac3c7c937e05e6e193372 Mon Sep 17 00:00:00 2001 From: chenanran555 <car534511@alibaba-inc.com> Date: Thu, 27 Aug 2026 20:47:51 +0800 Subject: [PATCH 04/10] fix(managed-agent): upgrade OpenAgentPack SDK to 0.4.0 beta --- packages/commands/package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/commands/package.json b/packages/commands/package.json index 88eefb5d8..d3d165669 100644 --- a/packages/commands/package.json +++ b/packages/commands/package.json @@ -40,7 +40,7 @@ "check": "vp check" }, "dependencies": { - "@openagentpack/sdk": "0.3.2", + "@openagentpack/sdk": "0.4.0-beta-2472720-20260827", "bailian-cli-core": "workspace:*", "bailian-cli-runtime": "workspace:*", "boxen": "catalog:", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6f4e769de..fb6fb22c2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -189,8 +189,8 @@ importers: packages/commands: dependencies: '@openagentpack/sdk': - specifier: 0.3.2 - version: 0.3.2 + specifier: 0.4.0-beta-2472720-20260827 + version: 0.4.0-beta-2472720-20260827 bailian-cli-core: specifier: workspace:* version: link:../core @@ -1124,8 +1124,8 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 - '@openagentpack/sdk@0.3.2': - resolution: {integrity: sha512-FvMHtIMIVLASGlt5q4kYSpD7BK+LWwN8vwnsckmD1SvPlWc9wayzA04DXU98IM74Ep5/HAqIKmNL8JAMQoeX1g==} + '@openagentpack/sdk@0.4.0-beta-2472720-20260827': + resolution: {integrity: sha512-Ms9xeYKllgaeeBKvlb5O6rvSn3mHw2PWJ6SEY+08Vayti3Fi7yhRqb4vOGfC1L3+vQtl0qRf2Bqsr3b8dyx4zw==} engines: {node: '>=18.17.0'} '@oxc-project/runtime@0.129.0': @@ -4287,7 +4287,7 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@openagentpack/sdk@0.3.2': + '@openagentpack/sdk@0.4.0-beta-2472720-20260827': dependencies: jszip: 3.10.1 yaml: 2.9.0 From 83d7982ecb84a1b42cdada466955cde13cc6390d Mon Sep 17 00:00:00 2001 From: chenanran555 <car534511@alibaba-inc.com> Date: Wed, 2 Sep 2026 14:36:22 +0800 Subject: [PATCH 05/10] feat(managed-agent): support creating agents with remote and local skills --- .../commands/managed-agent/_engine/errors.ts | 42 ++ .../managed-agent/_engine/scoped-create.ts | 24 +- .../commands/managed-agent/agent/create.ts | 405 ++++++++++++--- .../src/commands/managed-agent/apply.ts | 27 +- .../src/commands/managed-agent/plan.ts | 7 +- .../src/commands/managed-agent/validate.ts | 7 +- .../managed-agent/vault/credential/create.ts | 9 +- .../fixtures/managed-agent/agents.state.json | 83 ++++ .../e2e/fixtures/managed-agent/agents.yaml | 13 + .../tests/e2e/managed-agent.e2e.test.ts | 465 +++++++++++++++++- .../tests/managed-agent-create.test.ts | 70 +++ .../tests/managed-agent-errors.test.ts | 48 +- skills/bailian-managed-agent/SKILL.md | 3 + .../reference/managed-agent.md | 54 +- 14 files changed, 1120 insertions(+), 137 deletions(-) create mode 100644 packages/commands/tests/e2e/fixtures/managed-agent/agents.state.json diff --git a/packages/commands/src/commands/managed-agent/_engine/errors.ts b/packages/commands/src/commands/managed-agent/_engine/errors.ts index f22571ce4..bd531903d 100644 --- a/packages/commands/src/commands/managed-agent/_engine/errors.ts +++ b/packages/commands/src/commands/managed-agent/_engine/errors.ts @@ -12,6 +12,12 @@ interface SdkApiErrorLike extends Error { responseBody: string; } +interface AgentDiagnosticLike { + severity: string; + code?: string; + message: string; +} + function isSdkApiError(error: Error): error is SdkApiErrorLike { const candidate = error as Partial<SdkApiErrorLike>; return typeof candidate.statusCode === "number" && typeof candidate.responseBody === "string"; @@ -45,6 +51,42 @@ function isSdkPollingTimeout(error: UserError): boolean { return /did not complete within the timeout/i.test(error.message); } +/** + * Keep the final CLI error useful for hosts that retain stderr's terminal + * BailianError but discard the diagnostics emitted before it. The complete + * diagnostic collection remains on stdout/stderr; the terminal error carries + * the first actionable reason and signals when more errors are available. + */ +export function formatAgentDiagnosticFailure( + diagnostics: readonly AgentDiagnosticLike[], + fallback: string, +): string { + const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error"); + const firstError = errors[0]; + if (!firstError) return fallback; + + const codePrefix = firstError.code?.trim() ? `[${firstError.code}] ` : ""; + const remaining = errors.length - 1; + const remainingSuffix = remaining > 0 ? ` (+${remaining} more errors)` : ""; + return `${codePrefix}${firstError.message}${remainingSuffix}`; +} + +/** Add a recovery hint without replacing the original error or its metadata. */ +export function retainAgentError(error: unknown, hint: string, fallback: string): BailianError { + if (error instanceof BailianError) { + return new BailianError(error.message, error.exitCode, hint, { + api: error.api, + rawResponse: error.rawResponse, + cause: error.cause, + }); + } + if (error instanceof Error) { + return new BailianError(error.message, ExitCode.GENERAL, hint, { cause: error }); + } + const message = typeof error === "string" && error.trim() ? error : fallback; + return new BailianError(message, ExitCode.GENERAL, hint); +} + /** * Run an SDK-backed operation, translating SDK error types into BailianError so * bl's error handler produces the right exit code and hint formatting. diff --git a/packages/commands/src/commands/managed-agent/_engine/scoped-create.ts b/packages/commands/src/commands/managed-agent/_engine/scoped-create.ts index 5b78c634e..1ffccd592 100644 --- a/packages/commands/src/commands/managed-agent/_engine/scoped-create.ts +++ b/packages/commands/src/commands/managed-agent/_engine/scoped-create.ts @@ -19,7 +19,7 @@ import { formatResourceLabel } from "./address-utils.ts"; import { resolveAgentProjectConfig } from "./config-loader.ts"; import { assertProviderCredentials } from "./credentials.ts"; import { withStdoutProtected } from "./console-capture.ts"; -import { withAgentErrors } from "./errors.ts"; +import { retainAgentError, withAgentErrors } from "./errors.ts"; import { createFileStateScope } from "./file-state-manager.ts"; import { installSdkTransport } from "./transport.ts"; @@ -313,7 +313,11 @@ export async function runScopedTopLevelCreate(input: ScopedTopLevelCreateInput): ), ); } catch (error) { - throw retainedDeclarationError(error, input.resourceType); + throw retainAgentError( + error, + "The YAML declaration was kept. Fix the related dependency or provider error, then re-run the same create command.", + `Scoped ${input.resourceType} create failed.`, + ); } const remoteId = await project.stateBackend.read( project.stateScope, @@ -429,19 +433,3 @@ function renderPlan( emitBare(` ${icon} ${formatResourceLabel(action.address)}`); } } - -function retainedDeclarationError(error: unknown, resourceType: string): BailianError { - if (error instanceof BailianError) { - return new BailianError( - error.message, - error.exitCode, - "The YAML declaration was kept. Fix the related dependency or provider error, then re-run the same create command.", - { api: error.api, rawResponse: error.rawResponse, cause: error.cause }, - ); - } - return new BailianError( - error instanceof Error ? error.message : String(error), - ExitCode.GENERAL, - `The ${resourceType} YAML declaration was kept; re-run the same create command after fixing the error.`, - ); -} diff --git a/packages/commands/src/commands/managed-agent/agent/create.ts b/packages/commands/src/commands/managed-agent/agent/create.ts index 571e2aec4..67a2ba517 100644 --- a/packages/commands/src/commands/managed-agent/agent/create.ts +++ b/packages/commands/src/commands/managed-agent/agent/create.ts @@ -1,9 +1,16 @@ +import { stat } from "node:fs/promises"; +import { dirname, extname, relative, sep } from "node:path"; import { + type BackendRuntimeInput, buildAgentDecl, type IStateManager, - planAgentResourcesWithStateBackend, + inspectSkillSource, + planProjectWithStateBackend, + type ResolvedProjectConfig, type ResourceAddress, - syncAgentResourcesWithStateBackend, + type ResourceSyncRun, + type SkillDecl, + syncProjectResourcesWithStateBackend, } from "@openagentpack/sdk"; import { BailianError, @@ -17,8 +24,9 @@ import { parseDocument } from "yaml"; import { formatResourceLabel } from "../_engine/address-utils.ts"; import { CREDENTIALS_NOTE } from "../_engine/config-loader.ts"; import { withStdoutProtected } from "../_engine/console-capture.ts"; -import { withAgentErrors } from "../_engine/errors.ts"; +import { retainAgentError, withAgentErrors } from "../_engine/errors.ts"; import { + type LoadedScopedCreateProject, loadScopedCreateProject, normalizeResourceKey, replaceConfigAtomically, @@ -66,28 +74,30 @@ const CREATE_FLAGS = { "zh-CN": "目标 Provider;配置只有一个有效 Provider 时自动推断", }, }, - environment: { - type: "string", - valueHint: "<name>", + skill: { + type: "array", + valueHint: "<id>", description: { - "en-US": "Existing environment key from agents.yaml", - "zh-CN": "agents.yaml 中已有的 Environment key", + "en-US": "Existing remote Skill ID (repeatable)", + "zh-CN": "已存在的远端 Skill ID(可重复)", }, }, - vault: { - type: "string", - valueHint: "<name>", + skillDir: { + type: "array", + valueHint: "<path>", description: { - "en-US": "Existing vault key from agents.yaml", - "zh-CN": "agents.yaml 中已有的 Vault key", + "en-US": + "Local Skill directory or ZIP to declare, upload, and attach through the same scoped create (repeatable)", + "zh-CN": "通过同一次定向创建声明、上传并绑定的本地 Skill 目录或 ZIP(可重复)", }, }, - skill: { - type: "array", - valueHint: "<name>", + type: { + type: "string", + valueHint: "<custom|official>", + choices: ["custom", "official"], description: { - "en-US": "Existing custom Skill key from agents.yaml (repeatable)", - "zh-CN": "agents.yaml 中已有的自定义 Skill key(可重复)", + "en-US": "Type applied to every --skill value (default: custom)", + "zh-CN": "应用于所有 --skill 的类型(默认:custom)", }, }, tool: { @@ -116,12 +126,28 @@ const CREATE_FLAGS = { } satisfies FlagsDef; type BuiltAgentDecl = ReturnType<typeof buildAgentDecl>["agent"]; +type AgentSkillType = "custom" | "official"; +type AgentSkillDecl = NonNullable<BuiltAgentDecl["skills"]>[number]; interface AgentKeySelection { key: string; reusedPending: boolean; } +interface LocalSkillKeySelection { + key: string; + needsCreate: boolean; + reusedPending: boolean; + reusedTracked: boolean; +} + +interface PreparedLocalSkill extends LocalSkillKeySelection { + name: string; + source: string; + rawDeclaration: Record<string, unknown>; + resolvedDeclaration: SkillDecl; +} + export function normalizeAgentKey(displayName: string): string { return normalizeResourceKey(displayName, "agent"); } @@ -146,14 +172,169 @@ export function selectAgentKey(options: { }); } -function rootAddress( - selectedAddresses: ResourceAddress[] | undefined, - agentKey: string, -): ResourceAddress | undefined { - return selectedAddresses?.find( - (address) => - address.name === agentKey && (address.type === "agent" || address.type === "template"), +function agentSkillType(value: string | undefined): AgentSkillType { + if (value === undefined || value === "custom") return "custom"; + if (value === "official") return "official"; + throw new BailianError("--type must be either custom or official.", ExitCode.USAGE); +} + +export function buildAgentSkillRefs( + skillIds: string[] | undefined, + type: AgentSkillType = "custom", +): NonNullable<BuiltAgentDecl["skills"]> { + const seen = new Set<string>(); + const refs: NonNullable<BuiltAgentDecl["skills"]> = []; + for (const rawId of skillIds ?? []) { + const skillId = rawId.trim(); + if (!skillId) { + throw new BailianError("--skill values must not be empty.", ExitCode.USAGE); + } + if (seen.has(skillId)) continue; + seen.add(skillId); + refs.push({ type, skill_id: skillId }); + } + return refs; +} + +function canonicalJson(value: unknown): string { + const normalize = (candidate: unknown): unknown => { + if (Array.isArray(candidate)) return candidate.map(normalize); + if (!candidate || typeof candidate !== "object") return candidate; + return Object.fromEntries( + Object.entries(candidate as Record<string, unknown>) + .filter(([, entry]) => entry !== undefined) + .sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey)) + .map(([key, entry]) => [key, normalize(entry)]), + ); + }; + return JSON.stringify(normalize(value)); +} + +export function selectLocalSkillKey(options: { + displayName: string; + provider: string; + declarations: Record<string, Record<string, unknown>>; + candidate: Record<string, unknown>; + state: IStateManager; +}): LocalSkillKeySelection { + const trackedKeys = new Set( + options.state + .listResources() + .filter( + (resource) => + resource.address.provider === options.provider && resource.address.type === "skill", + ) + .map((resource) => resource.address.name), ); + const sameName = Object.entries(options.declarations).filter(([key, declaration]) => { + const effectiveName = typeof declaration.name === "string" ? declaration.name : key; + return effectiveName === options.displayName; + }); + const matchingTracked = sameName.find( + ([key, declaration]) => + trackedKeys.has(key) && canonicalJson(declaration) === canonicalJson(options.candidate), + ); + if (matchingTracked) { + return { + key: matchingTracked[0], + needsCreate: false, + reusedPending: false, + reusedTracked: true, + }; + } + const differentTracked = sameName.find(([key]) => trackedKeys.has(key)); + if (differentTracked) { + throw new BailianError( + `Skill '${options.displayName}' is already tracked with a different declaration.`, + ExitCode.USAGE, + "Modify its existing YAML declaration and run full `bl managed-agent apply`, or bind its remote ID with --skill.", + ); + } + const selected = selectResourceKey({ + displayName: options.displayName, + provider: options.provider, + resourceTypes: ["skill"], + declarations: options.declarations, + candidate: options.candidate, + effectiveName: (key, declaration) => + typeof declaration.name === "string" ? declaration.name : key, + fallbackKey: "skill", + state: options.state, + }); + return { + ...selected, + needsCreate: true, + reusedTracked: false, + }; +} + +async function prepareLocalSkillSources( + project: LoadedScopedCreateProject, + sources: string[] | undefined, +): Promise<PreparedLocalSkill[]> { + const declarations = { + ...((project.config.skills ?? {}) as unknown as Record<string, Record<string, unknown>>), + }; + const preparedByPath = new Map<string, PreparedLocalSkill>(); + const preparedByKey = new Map<string, PreparedLocalSkill>(); + for (const rawSource of sources ?? []) { + const requestedSource = rawSource.trim(); + if (!requestedSource) { + throw new BailianError("--skill-dir values must not be empty.", ExitCode.USAGE); + } + const inspected = await withAgentErrors(() => + inspectSkillSource(requestedSource, { basePath: process.cwd() }), + ); + const sourceStat = await stat(inspected.sourcePath); + const supportedSource = + sourceStat.isDirectory() || + (sourceStat.isFile() && extname(inspected.sourcePath).toLowerCase() === ".zip"); + if (!supportedSource) { + throw new BailianError( + `--skill-dir requires a Skill directory or .zip file: ${requestedSource}`, + ExitCode.USAGE, + ); + } + if (preparedByPath.has(inspected.sourcePath)) continue; + const source = + relative(dirname(project.configPath), inspected.sourcePath).split(sep).join("/") || "."; + const rawDeclaration: Record<string, unknown> = { + name: inspected.name, + source, + origin: "custom", + provider: project.provider, + }; + const resolvedDeclaration = (await resolveCandidateDeclaration({ + project, + group: "skills", + rawDeclaration, + })) as unknown as SkillDecl; + const keySelection = await project.stateBackend.read(project.stateScope, (state) => + selectLocalSkillKey({ + displayName: inspected.name, + provider: project.provider, + declarations, + candidate: resolvedDeclaration as unknown as Record<string, unknown>, + state, + }), + ); + const existingPrepared = preparedByKey.get(keySelection.key); + if (existingPrepared) { + preparedByPath.set(inspected.sourcePath, existingPrepared); + continue; + } + const prepared: PreparedLocalSkill = { + ...keySelection, + name: inspected.name, + source, + rawDeclaration, + resolvedDeclaration, + }; + declarations[keySelection.key] = resolvedDeclaration as unknown as Record<string, unknown>; + preparedByPath.set(inspected.sourcePath, prepared); + preparedByKey.set(keySelection.key, prepared); + } + return [...preparedByKey.values()]; } export default defineCommand({ @@ -163,11 +344,13 @@ export default defineCommand({ }, auth: "apiKey", usageArgs: - "--name <name> --model <model> --instructions <text|path> [--description <text>] [--provider <name>] [--environment <name>] [--vault <name>] [--skill <name>...] [--tool <name>...] [--file <path>] [--yes]", + "--name <name> --model <model> --instructions <text|path> [--description <text>] [--provider <name>] [--skill <id>...] [--type custom|official] [--skill-dir <path>...] [--tool <name>...] [--file <path>] [--yes]", flags: CREATE_FLAGS, exampleArgs: [ '--name assistant --model qwen3.8-max --instructions "You are helpful."', - "--name assistant --model qwen3.8-max --instructions ./prompts/assistant.md --environment dev --skill search --yes", + "--name assistant --model qwen3.8-max --instructions ./prompts/assistant.md --skill skill_abc --yes", + "--name slides --model qwen3.8-max --instructions ./prompts/slides.md --skill skill_pptx --type official --yes", + "--name reviewer --model qwen3.8-max --instructions ./prompts/reviewer.md --skill-dir ./skills/code-review --yes", ], notes: [ ...CREDENTIALS_NOTE, @@ -177,11 +360,30 @@ export default defineCommand({ "zh-CN": "不带 --yes 时预览自动生成的 YAML key 和定向计划;--dry-run 完全离线。无关资源不会刷新或检测 Drift。", }, + { + "en-US": + "--skill writes an external Skill reference directly into the Agent declaration. --type defaults to custom; use --type official for platform Skills. These Skills are not managed through the top-level skills map.", + "zh-CN": + "--skill 会把外部 Skill 引用直接写入 Agent 声明。--type 默认为 custom;平台 Skill 使用 --type official。这些 Skill 不通过顶层 skills 资源管理。", + }, + { + "en-US": + "--skill-dir accepts a local Skill directory or ZIP, writes it as a top-level custom Skill declaration, and writes its generated YAML key into the Agent skills list. Skill and Agent are created together in dependency order. --type applies only to --skill IDs.", + "zh-CN": + "--skill-dir 接收本地 Skill 目录或 ZIP,将其写为顶层 custom Skill 声明,并把生成的 YAML key 写入 Agent 的 skills 列表;Skill 与 Agent 按依赖顺序一并创建。--type 只作用于 --skill ID。", + }, ], validate: (flags) => { if (!flags.name.trim()) return "--name must not be empty."; if (!flags.model.trim()) return "--model must not be empty."; if (!flags.instructions.trim()) return "--instructions must not be empty."; + if (flags.type && !flags.skill?.length) return "--type requires at least one --skill."; + if (flags.skill?.some((skillId) => !skillId.trim())) { + return "--skill values must not be empty."; + } + if (flags.skillDir?.some((source) => !source.trim())) { + return "--skill-dir values must not be empty."; + } return undefined; }, async run(ctx) { @@ -190,6 +392,12 @@ export default defineCommand({ const file = flags.file ?? "agents.yaml"; const project = await loadScopedCreateProject(ctx, file, flags.provider); const provider = project.provider; + const externalSkillRefs = buildAgentSkillRefs(flags.skill, agentSkillType(flags.type)); + const localSkills = await prepareLocalSkillSources(project, flags.skillDir); + const skillRefs: AgentSkillDecl[] = [ + ...externalSkillRefs, + ...localSkills.map((skill) => skill.key), + ]; const rawAgent = buildAgentDecl(undefined, { name: flags.name.trim(), @@ -197,13 +405,17 @@ export default defineCommand({ model: flags.model, instructions: flags.instructions, provider, - environment: flags.environment, - vault: flags.vault, builtinTools: flags.tool, - skills: flags.skill?.map((skillName) => ({ kind: "custom", name: skillName })), }).agent; + rawAgent.skills = skillRefs; + const candidateConfig = structuredClone(project.config); + candidateConfig.skills = { ...candidateConfig.skills }; + for (const skill of localSkills) { + candidateConfig.skills[skill.key] = skill.resolvedDeclaration; + } + candidateConfig._resolved = true; const candidateAgent = (await resolveCandidateDeclaration({ - project, + project: { ...project, config: candidateConfig }, group: "agents", rawDeclaration: rawAgent as unknown as Record<string, unknown>, })) as unknown as BuiltAgentDecl; @@ -217,7 +429,6 @@ export default defineCommand({ }), ); const agentKey = keySelection.key; - const candidateConfig = structuredClone(project.config); candidateConfig.agents = { ...candidateConfig.agents, [agentKey]: candidateAgent }; candidateConfig._resolved = true; @@ -228,11 +439,21 @@ export default defineCommand({ ExitCode.USAGE, ); } + for (const skill of localSkills) { + document.setIn(["skills", skill.key], skill.rawDeclaration); + } document.setIn(["agents", agentKey], rawAgent); const nextSource = document.toString(); - const backendInput = { + const agentRoot: ResourceAddress = { type: "agent", name: agentKey, provider }; + const roots: ResourceAddress[] = [ + ...localSkills + .filter((skill) => skill.needsCreate) + .map((skill) => ({ type: "skill" as const, name: skill.key, provider })), + agentRoot, + ]; + const backendInput: BackendRuntimeInput = { projectName: project.projectName, - config: candidateConfig, + config: candidateConfig as ResolvedProjectConfig, configPath: project.configPath, providers: { [provider]: candidateConfig.providers[provider] }, stateBackend: project.stateBackend, @@ -242,34 +463,46 @@ export default defineCommand({ if (settings.dryRun || !flags.yes) { const planned = await withAgentErrors(() => withStdoutProtected(() => - planAgentResourcesWithStateBackend(backendInput, agentKey, { + planProjectWithStateBackend(backendInput, { + provider, + scope: { roots }, refresh: !settings.dryRun, quiet: format === "json", mode: "create-only", }), ), ); - const readyToCreate = !planned.diagnostics.some( + const readyToCreate = !planned.plan.diagnostics.some( (diagnostic) => diagnostic.severity === "error", ); const result = { - agent: { key: agentKey, name: flags.name.trim(), provider }, + agent: { key: agentKey, name: flags.name.trim(), provider, skills: skillRefs }, + local_skills: localSkills.map((skill) => ({ + key: skill.key, + name: skill.name, + source: skill.source, + reused_pending: skill.reusedPending, + reused_tracked: skill.reusedTracked, + })), config_file: project.configPath, yaml_written: false, reused_pending: keySelection.reusedPending, requires_confirmation: !settings.dryRun, ready_to_create: readyToCreate, - actions: planned.actions, - diagnostics: planned.diagnostics, + actions: planned.plan.actions, + diagnostics: planned.plan.diagnostics, }; if (format === "json") { emitResult(result, format); } else { - emitBare(`Generated YAML key: ${agentKey}`); - for (const diagnostic of planned.diagnostics) { + for (const skill of localSkills) { + emitBare(`Generated Skill YAML key: ${skill.key}`); + } + emitBare(`Generated Agent YAML key: ${agentKey}`); + for (const diagnostic of planned.plan.diagnostics) { emitBare(`[${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`); } - for (const action of planned.actions.filter((entry) => entry.action !== "no-op")) { + for (const action of planned.plan.actions.filter((entry) => entry.action !== "no-op")) { const icon = action.action === "create" ? "+" : action.action === "update" ? "~" : "-"; emitBare(` ${icon} ${formatResourceLabel(action.address)}`); } @@ -283,45 +516,77 @@ export default defineCommand({ } await replaceConfigAtomically(project.configPath, project.source, nextSource); - const run = await withAgentErrors(() => - withStdoutProtected(() => - syncAgentResourcesWithStateBackend(backendInput, agentKey, { - refresh: true, - quiet: format === "json", - mode: "create-only", - policy: "block", - }), - ), - ); - const remoteId = await project.stateBackend.read(project.stateScope, (state) => { - const selectedRoot = rootAddress( - run.actions.map((action) => action.address), - agentKey, + let run: ResourceSyncRun; + try { + run = await withAgentErrors(() => + withStdoutProtected(() => + syncProjectResourcesWithStateBackend(backendInput, { + provider, + scope: { roots }, + refresh: true, + quiet: format === "json", + mode: "create-only", + policy: "block", + }), + ), ); - return selectedRoot ? state.getResource(selectedRoot)?.remote_id : undefined; - }); + } catch (error) { + throw retainAgentError( + error, + "The YAML declarations were kept. Fix the related dependency or provider error, then re-run the same create command.", + "Scoped Agent and Skill create failed.", + ); + } + const remoteResources = await project.stateBackend.read(project.stateScope, (state) => ({ + agentId: state.getResource(agentRoot)?.remote_id, + skillIds: Object.fromEntries( + localSkills.map((skill) => [ + skill.key, + state.getResource({ type: "skill", name: skill.key, provider })?.remote_id, + ]), + ), + })); + const results = run.execution?.results ?? []; + const failed = results.find((entry) => entry.status === "failed"); + const status = failed ? "failed" : "completed"; const result = { - agent: { key: agentKey, name: flags.name.trim(), provider, remote_id: remoteId }, + agent: { + key: agentKey, + name: flags.name.trim(), + provider, + remote_id: remoteResources.agentId, + skills: skillRefs, + }, + local_skills: localSkills.map((skill) => ({ + key: skill.key, + name: skill.name, + source: skill.source, + remote_id: remoteResources.skillIds[skill.key], + reused_pending: skill.reusedPending, + reused_tracked: skill.reusedTracked, + })), config_file: project.configPath, yaml_written: true, reused_pending: keySelection.reusedPending, - status: run.status, - actions: run.actions, - diagnostics: run.diagnostics, - results: run.results, - error: run.error, + status, + actions: run.planned.plan.actions, + diagnostics: run.planned.plan.diagnostics, + results, + error: failed?.error, }; if (format === "json") emitResult(result, format); else { - emitBare(`Wrote ${project.configPath} with Agent key '${agentKey}'.`); - if (run.status === "completed") emitBare(`Created Agent '${flags.name.trim()}'.`); - else emitBare(`Scoped create ${run.status}: ${run.error ?? "unknown error"}`); + emitBare( + `Wrote ${project.configPath} with Agent key '${agentKey}'${localSkills.length ? ` and ${localSkills.length} local Skill declaration(s)` : ""}.`, + ); + if (status === "completed") emitBare(`Created Agent '${flags.name.trim()}'.`); + else emitBare(`Scoped create ${status}: ${failed?.error ?? "unknown error"}`); } - if (run.status !== "completed") { + if (failed) { throw new BailianError( - run.error ?? "Scoped Agent create failed.", + failed.error ?? "Scoped Agent create failed.", ExitCode.GENERAL, - "The YAML declaration was kept. Fix the related dependency or provider error, then re-run the same create command.", + "The YAML declarations were kept. Fix the related dependency or provider error, then re-run the same create command.", ); } }, diff --git a/packages/commands/src/commands/managed-agent/apply.ts b/packages/commands/src/commands/managed-agent/apply.ts index 6040a0add..8dce8fdb7 100644 --- a/packages/commands/src/commands/managed-agent/apply.ts +++ b/packages/commands/src/commands/managed-agent/apply.ts @@ -14,7 +14,7 @@ import { CREDENTIALS_NOTE, } from "./_engine/config-loader.ts"; import { withStdoutProtected } from "./_engine/console-capture.ts"; -import { withAgentErrors } from "./_engine/errors.ts"; +import { formatAgentDiagnosticFailure, withAgentErrors } from "./_engine/errors.ts"; import { renderAgentFeedback } from "./_engine/feedback.ts"; const APPLY_FLAGS = { @@ -109,11 +109,20 @@ export default defineCommand({ if (format === "json") process.stderr.write(`${line}\n`); else emitBare(line); }; - if (plan.diagnostics.some((diag) => diag.severity === "error")) { - for (const diag of plan.diagnostics) { - if (diag.severity === "error") emitProgress(`[error] ${diag.code}: ${diag.message}`); + const errorDiagnostics = plan.diagnostics.filter( + (diagnostic) => diagnostic.severity === "error", + ); + if (errorDiagnostics.length > 0) { + for (const diagnostic of errorDiagnostics) { + emitProgress(`[error] ${diagnostic.code}: ${diagnostic.message}`); } - throw new BailianError("Cannot apply: resolve the errors above first.", ExitCode.GENERAL); + throw new BailianError( + formatAgentDiagnosticFailure( + errorDiagnostics, + "Cannot apply: resolve the errors above first.", + ), + ExitCode.GENERAL, + ); } const actionable = plan.actions.filter((action) => action.action !== "no-op"); @@ -152,7 +161,8 @@ export default defineCommand({ ); const succeeded = result.results.filter((entry) => entry.status === "success").length; - const failed = result.results.filter((entry) => entry.status === "failed").length; + const failedResults = result.results.filter((entry) => entry.status === "failed"); + const failed = failedResults.length; const skipped = result.results.filter((entry) => entry.status === "skipped").length; if (format === "json") { @@ -161,6 +171,9 @@ export default defineCommand({ emitBare(`\nApply finished: ${succeeded} succeeded, ${failed} failed, ${skipped} skipped.`); } - if (failed > 0) throw new BailianError("Apply failed.", ExitCode.GENERAL); + if (failed > 0) { + const firstFailure = failedResults.find((entry) => entry.error); + throw new BailianError(firstFailure?.error ?? "Apply failed.", ExitCode.GENERAL); + } }, }); diff --git a/packages/commands/src/commands/managed-agent/plan.ts b/packages/commands/src/commands/managed-agent/plan.ts index 1a5dda851..72130baa3 100644 --- a/packages/commands/src/commands/managed-agent/plan.ts +++ b/packages/commands/src/commands/managed-agent/plan.ts @@ -14,7 +14,7 @@ import { CREDENTIALS_NOTE, } from "./_engine/config-loader.ts"; import { withStdoutProtected } from "./_engine/console-capture.ts"; -import { withAgentErrors } from "./_engine/errors.ts"; +import { formatAgentDiagnosticFailure, withAgentErrors } from "./_engine/errors.ts"; import { renderAgentFeedback } from "./_engine/feedback.ts"; const PLAN_FLAGS = { @@ -95,17 +95,18 @@ export default defineCommand({ const plan = planned.plan; const hasErrors = plan.diagnostics.some((diag) => diag.severity === "error"); + const failureMessage = formatAgentDiagnosticFailure(plan.diagnostics, "Plan contains errors."); if (format === "json") { emitResult(plan, format); - if (hasErrors) throw new BailianError("Plan contains errors.", ExitCode.GENERAL); + if (hasErrors) throw new BailianError(failureMessage, ExitCode.GENERAL); return; } for (const diag of plan.diagnostics) { emitBare(`[${diag.severity}] ${diag.code}: ${diag.message}`); } - if (hasErrors) throw new BailianError("Plan contains errors.", ExitCode.GENERAL); + if (hasErrors) throw new BailianError(failureMessage, ExitCode.GENERAL); const creates = plan.actions.filter((action) => action.action === "create"); const updates = plan.actions.filter((action) => action.action === "update"); diff --git a/packages/commands/src/commands/managed-agent/validate.ts b/packages/commands/src/commands/managed-agent/validate.ts index 32fe5eafc..08abdd922 100644 --- a/packages/commands/src/commands/managed-agent/validate.ts +++ b/packages/commands/src/commands/managed-agent/validate.ts @@ -8,7 +8,7 @@ import { import { emitBare, emitResult } from "bailian-cli-runtime"; import { validateProjectConfig } from "@openagentpack/sdk"; import { OFFLINE_NOTE, resolveAgentProjectConfig } from "./_engine/config-loader.ts"; -import { withAgentErrors } from "./_engine/errors.ts"; +import { formatAgentDiagnosticFailure, withAgentErrors } from "./_engine/errors.ts"; const VALIDATE_FLAGS = { file: { @@ -56,7 +56,10 @@ export default defineCommand({ } if (errorCount > 0) { - throw new BailianError(`Validation failed with ${errorCount} error(s).`, ExitCode.GENERAL); + throw new BailianError( + formatAgentDiagnosticFailure(diagnostics, `Validation failed with ${errorCount} error(s).`), + ExitCode.GENERAL, + ); } }, }); diff --git a/packages/commands/src/commands/managed-agent/vault/credential/create.ts b/packages/commands/src/commands/managed-agent/vault/credential/create.ts index 146474471..e028a5864 100644 --- a/packages/commands/src/commands/managed-agent/vault/credential/create.ts +++ b/packages/commands/src/commands/managed-agent/vault/credential/create.ts @@ -16,7 +16,7 @@ import { emitBare, emitResult } from "bailian-cli-runtime"; import { parseDocument } from "yaml"; import { CREDENTIALS_NOTE } from "../../_engine/config-loader.ts"; import { withStdoutProtected } from "../../_engine/console-capture.ts"; -import { withAgentErrors } from "../../_engine/errors.ts"; +import { retainAgentError, withAgentErrors } from "../../_engine/errors.ts"; import { loadScopedCreateProject, parseMetadata, @@ -294,11 +294,10 @@ export default defineCommand({ ), ); } catch (error) { - const message = error instanceof Error ? error.message : String(error); - throw new BailianError( - message, - error instanceof BailianError ? error.exitCode : ExitCode.GENERAL, + throw retainAgentError( + error, "The YAML declaration was kept. Restore the same secret environment variable and re-run this command; an already-created matching Credential will be adopted.", + "Vault Credential create failed.", ); } const result = { diff --git a/packages/commands/tests/e2e/fixtures/managed-agent/agents.state.json b/packages/commands/tests/e2e/fixtures/managed-agent/agents.state.json new file mode 100644 index 000000000..ae974fbfc --- /dev/null +++ b/packages/commands/tests/e2e/fixtures/managed-agent/agents.state.json @@ -0,0 +1,83 @@ +{ + "resources": [ + { + "address": { + "type": "environment", + "name": "dev", + "provider": "bailian" + }, + "remote_id": "env_ZjQxMzg5NDkwYjcxNGJmMj", + "content_hash": "d7744af584d39dad40371b0abeefd15f595f9b252401d56b1695eff11c34745a", + "desired_hash": "d7744af584d39dad40371b0abeefd15f595f9b252401d56b1695eff11c34745a", + "desired_comparable_hash": "df4636ee00f9b91880ffb65051bd58bc840be71f9bb13d4e64129b5e46dcddfc", + "desired_readiness_baseline": { + "operational_hash": "d7744af584d39dad40371b0abeefd15f595f9b252401d56b1695eff11c34745a", + "description_hash": "74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b", + "metadata_hash": "74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b" + }, + "remote_hash": "df4636ee00f9b91880ffb65051bd58bc840be71f9bb13d4e64129b5e46dcddfc", + "remote_snapshot": { + "description": null, + "config": { + "type": "cloud", + "networking": { + "type": "unrestricted" + } + } + }, + "drift_paths": [], + "drift_status": "in_sync" + }, + { + "address": { + "type": "agent", + "name": "assistant", + "provider": "bailian" + }, + "remote_id": "agent_01M1E06GQ4KR9QJFX9MQJMBA4H", + "version": 1, + "content_hash": "c173a2dafaf0039b9ddfb8b342f58876b1c9510d12debe765e82b586dcd30168", + "desired_hash": "c173a2dafaf0039b9ddfb8b342f58876b1c9510d12debe765e82b586dcd30168", + "desired_comparable_hash": "429c090d7f12c014aa2a15ac0ec4a8f89555694b4acc36049b66bb2c1d7d5d54", + "desired_readiness_baseline": { + "operational_hash": "3f3cfd0953464b338b23b1e753c6698ef2567133ee6eded15b500d8df717f9ec", + "description_hash": "d97348b869d0aef8d26e4f06bba719d6f57864d7c1eef68c433a11aaa6e2b244", + "metadata_hash": "74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b" + }, + "remote_hash": "429c090d7f12c014aa2a15ac0ec4a8f89555694b4acc36049b66bb2c1d7d5d54", + "remote_snapshot": { + "description": "E2E auth-chain fixture", + "model": "qwen3.7-max", + "instructions": "You are a helpful assistant.\n" + }, + "drift_paths": [], + "drift_status": "in_sync" + }, + { + "address": { + "type": "agent", + "name": "test-create", + "provider": "bailian" + }, + "remote_id": "agent_01M1EXETGHNV6QBGQZ4PZRAEQ5", + "version": 1, + "content_hash": "5084a95feef59412ac07d99a500bbfca801c0a86143706d14bb209c4ccfda073", + "desired_hash": "5084a95feef59412ac07d99a500bbfca801c0a86143706d14bb209c4ccfda073", + "desired_comparable_hash": "5f381045d839d5ef97751407123a3ad27e15a00976d198cfe2359ce14ac127a0", + "desired_readiness_baseline": { + "operational_hash": "5a16d288fb20b4ebc5d9f5c1b3d6d6eae6d072bcdcf3d069161946c709d09a69", + "description_hash": "74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b", + "metadata_hash": "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a" + }, + "remote_hash": "5f381045d839d5ef97751407123a3ad27e15a00976d198cfe2359ce14ac127a0", + "remote_snapshot": { + "name": "test-create", + "description": null, + "model": "qwen3.8-max", + "instructions": "111" + }, + "drift_paths": [], + "drift_status": "in_sync" + } + ] +} diff --git a/packages/commands/tests/e2e/fixtures/managed-agent/agents.yaml b/packages/commands/tests/e2e/fixtures/managed-agent/agents.yaml index 7def72693..b6056ac72 100644 --- a/packages/commands/tests/e2e/fixtures/managed-agent/agents.yaml +++ b/packages/commands/tests/e2e/fixtures/managed-agent/agents.yaml @@ -22,3 +22,16 @@ agents: instructions: | You are a helpful assistant. environment: dev + test-create: + name: test-create + model: qwen3.8-max + instructions: "111" + provider: bailian + tools: + builtin: [] + mcp: [] + mcp_servers: [] + skills: + - type: custom + skill_id: skill_M2I0ZTZhOWJiOWY5NDFkMzk2YT + metadata: {} diff --git a/packages/commands/tests/e2e/managed-agent.e2e.test.ts b/packages/commands/tests/e2e/managed-agent.e2e.test.ts index ff1a56060..d00d2528b 100644 --- a/packages/commands/tests/e2e/managed-agent.e2e.test.ts +++ b/packages/commands/tests/e2e/managed-agent.e2e.test.ts @@ -1,4 +1,4 @@ -import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import http from "node:http"; import type { AddressInfo } from "node:net"; import { tmpdir } from "node:os"; @@ -21,6 +21,8 @@ const AGENTS_DEPLOYMENT_INVALID_YAML = join( "agents-deployment-invalid.yaml", ); const AGENTS_YAML = join(e2eFixturesDir, "managed-agent", "agents.yaml"); +const LOCAL_SKILL_ZIP_BASE64 = + "UEsDBAoAAAAIAGBoIV3X45MGOwAAAEoAAAAIAAAAU0tJTEwubWTT1dXlykvMTbVSyMlPTszRLUotyC8q4UpJLU4uyiwoyczPs1LwAckoQGQUirMzc3K4dIHalKESQRAtAFBLAwQKAAAAAABgaCFdAAAAAAAAAAAAAAAACAAAAHNjcmlwdHMvUEsDBAoAAAAIAGBoIV3vn4RoIQAAAB8AAAAOAAAAc2NyaXB0cy9ydW4uanNLrSjILypRSM7PKy5RKCrNU7BV0NBUsLVTKCkqTbXmAgBQSwECFAAKAAAACABgaCFd1+OTBjsAAABKAAAACAAAAAAAAAAAAAAAAAAAAAAAU0tJTEwubWRQSwECFAAKAAAAAABgaCFdAAAAAAAAAAAAAAAACAAAAAAAAAAAABAAAABhAAAAc2NyaXB0cy9QSwECFAAKAAAACABgaCFd75+EaCEAAAAfAAAADgAAAAAAAAAAAAAAAACHAAAAc2NyaXB0cy9ydW4uanNQSwUGAAAAAAMAAwCoAAAA1AAAAAAA"; const DEPLOYMENT_SAFETY_DIAGNOSTIC_CODES = [ "bailian.deployment.initial_events.message_required", @@ -28,6 +30,29 @@ const DEPLOYMENT_SAFETY_DIAGNOSTIC_CODES = [ "bailian.deployment.file.mount_path.duplicate", ]; +async function writeLocalSkillDirectory(parentDirectory: string): Promise<string> { + const skillDirectory = join(parentDirectory, "local-report"); + await mkdir(join(skillDirectory, "scripts"), { recursive: true }); + await writeFile( + join(skillDirectory, "SKILL.md"), + `--- +name: local-report +description: Local report skill +--- +# Local Report +`, + "utf8", + ); + await writeFile(join(skillDirectory, "scripts", "run.js"), "export const run = true;\n", "utf8"); + return skillDirectory; +} + +async function writeLocalSkillZip(parentDirectory: string): Promise<string> { + const skillZipPath = join(parentDirectory, "local-report.zip"); + await writeFile(skillZipPath, Buffer.from(LOCAL_SKILL_ZIP_BASE64, "base64")); + return skillZipPath; +} + async function seedTrackedResources( configPath: string, addresses: ResourceAddress[], @@ -92,7 +117,7 @@ describe("e2e: managed-agent", () => { expect(exitCode, stderr).toBe(0); const data = parseStdoutJson<{ valid?: boolean; - diagnostics?: Array<{ code?: string; severity?: string }>; + diagnostics?: Array<{ code?: string; message?: string; severity?: string }>; }>(stdout); expect(data.valid).toBe(true); expect(data.diagnostics).not.toContainEqual( @@ -134,7 +159,7 @@ describe("e2e: managed-agent", () => { }); test("validate 拒绝会产生空消息或无效挂载路径的 Bailian Deployment", async () => { - const { stdout, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [ + const { stdout, stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [ "managed-agent", "validate", "--file", @@ -145,9 +170,17 @@ describe("e2e: managed-agent", () => { expect(exitCode).toBe(1); const data = parseStdoutJson<{ valid?: boolean; - diagnostics?: Array<{ code?: string; severity?: string }>; + diagnostics?: Array<{ code?: string; message?: string; severity?: string }>; }>(stdout); expect(data.valid).toBe(false); + const errorDiagnostics = + data.diagnostics?.filter((diagnostic) => diagnostic.severity === "error") ?? []; + const firstError = errorDiagnostics[0]; + expect(firstError?.code).toBeTruthy(); + expect(firstError?.message).toBeTruthy(); + expect(stderr).toContain(`[${firstError?.code}] ${firstError?.message}`); + expect(stderr).toContain(`(+${errorDiagnostics.length - 1} more errors)`); + expect(stderr).not.toMatch(/Validation failed with \d+ error/); for (const diagnosticCode of DEPLOYMENT_SAFETY_DIAGNOSTIC_CODES) { expect(data.diagnostics).toContainEqual( expect.objectContaining({ code: diagnosticCode, severity: "error" }), @@ -156,7 +189,7 @@ describe("e2e: managed-agent", () => { }); test("plan --dry-run 在 Deployment 安全诊断失败时阻断计划", async () => { - const { stdout, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [ + const { stdout, stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [ "managed-agent", "plan", "--dry-run", @@ -167,8 +200,16 @@ describe("e2e: managed-agent", () => { ]); expect(exitCode).toBe(1); const data = parseStdoutJson<{ - diagnostics?: Array<{ code?: string; severity?: string }>; + diagnostics?: Array<{ code?: string; message?: string; severity?: string }>; }>(stdout); + const errorDiagnostics = + data.diagnostics?.filter((diagnostic) => diagnostic.severity === "error") ?? []; + const firstError = errorDiagnostics[0]; + expect(firstError?.code).toBeTruthy(); + expect(firstError?.message).toBeTruthy(); + expect(stderr).toContain(`[${firstError?.code}] ${firstError?.message}`); + expect(stderr).toContain(`(+${errorDiagnostics.length - 1} more errors)`); + expect(stderr).not.toContain("Plan contains errors."); for (const diagnosticCode of DEPLOYMENT_SAFETY_DIAGNOSTIC_CODES) { expect(data.diagnostics).toContainEqual( expect.objectContaining({ code: diagnosticCode, severity: "error" }), @@ -186,6 +227,99 @@ describe("e2e: managed-agent", () => { expect(stderr).toMatch(/--file|--provider|--yes/i); }); + test("managed-agent apply 计划失败时在最终错误中保留首条诊断和剩余数量", async () => { + const { stderr, exitCode } = await runCommandE2e( + MANAGED_AGENT_ROUTES, + [ + "managed-agent", + "apply", + "--file", + AGENTS_DEPLOYMENT_INVALID_YAML, + "--no-refresh", + "--yes", + "--output", + "json", + ], + { + DASHSCOPE_API_KEY: "sk-e2e-apply-diagnostics", + BAILIAN_BASE_URL: "http://127.0.0.1:1", + }, + ); + + expect(exitCode).toBe(1); + for (const diagnosticCode of DEPLOYMENT_SAFETY_DIAGNOSTIC_CODES) { + expect(stderr).toContain(`[error] ${diagnosticCode}:`); + } + expect(stderr).toContain(`[${DEPLOYMENT_SAFETY_DIAGNOSTIC_CODES[0]}]`); + expect(stderr).toContain(`(+${DEPLOYMENT_SAFETY_DIAGNOSTIC_CODES.length - 1} more errors)`); + expect(stderr).not.toContain("Cannot apply: resolve the errors above first."); + }); + + test("managed-agent apply 失败时在 text 和 JSON 最终错误中保留服务端原因", async () => { + const directory = await mkdtemp(join(tmpdir(), "bl-apply-error-e2e-")); + const configPath = join(directory, "agents.yaml"); + const server = http.createServer((_request, response) => { + response.writeHead(500, { "Content-Type": "application/json" }); + response.end( + JSON.stringify({ + code: "AGENT_E2E_FAILURE", + message: "intentional apply failure", + request_id: "req_apply_error_e2e", + }), + ); + }); + await new Promise<void>((resolveListen) => server.listen(0, "127.0.0.1", resolveListen)); + const address = server.address() as AddressInfo; + const configSource = `version: "1" +providers: + bailian: + api_key: \${DASHSCOPE_API_KEY} + base_url: \${BAILIAN_BASE_URL} +defaults: + provider: bailian +agents: + apply-error: + name: Apply Error + model: qwen3.8-max + instructions: Exercise the apply error path. +`; + await writeFile(configPath, configSource, "utf8"); + const baseArgs = [ + "managed-agent", + "apply", + "--file", + configPath, + "--provider", + "bailian", + "--yes", + ]; + const env = { + DASHSCOPE_API_KEY: "sk-e2e-apply-error", + BAILIAN_BASE_URL: `http://127.0.0.1:${address.port}`, + }; + + try { + const textResult = await runCommandE2e(MANAGED_AGENT_ROUTES, baseArgs, env); + expect(textResult.exitCode).toBe(1); + expect(textResult.stderr).toMatch(/Error:\s+Bailian API 500:.*intentional apply failure/); + expect(textResult.stderr).not.toMatch(/Error:\s+Apply failed\./); + + const jsonResult = await runCommandE2e( + MANAGED_AGENT_ROUTES, + [...baseArgs, "--output", "json"], + env, + ); + expect(jsonResult.exitCode).toBe(1); + expect(jsonResult.stderr).toMatch( + /"message":\s*"Bailian API 500:.*intentional apply failure/, + ); + expect(jsonResult.stderr).not.toMatch(/"message":\s*"Apply failed\."/); + } finally { + await new Promise<void>((resolveClose) => server.close(() => resolveClose())); + await rm(directory, { recursive: true, force: true }); + } + }); + test("managed-agent agent create --help 展示声明和确认参数", async () => { const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [ "managed-agent", @@ -194,8 +328,10 @@ describe("e2e: managed-agent", () => { "--help", ]); expect(exitCode, stderr).toBe(0); - expect(stderr).toMatch(/--name|--model|--instructions|--skill|--yes/i); - expect(stderr).not.toMatch(/--key/i); + expect(stderr).toMatch(/--name|--model|--instructions|--skill|--skill-dir|--type|--yes/i); + expect(stderr).toMatch(/remote Skill ID/i); + expect(stderr).toMatch(/local Skill directory/i); + expect(stderr).not.toMatch(/--key|--environment|--vault/i); }); test("managed-agent agent create 缺少 --name 时退出为用法错误 (2)", async () => { @@ -213,6 +349,305 @@ describe("e2e: managed-agent", () => { expect(stderr).toMatch(/--name|Missing required/i); }); + test.each([ + ["--environment", "dev"], + ["--vault", "secrets"], + ])("managed-agent agent create 不接受运行时参数 %s", async (flag, value) => { + const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [ + "managed-agent", + "agent", + "create", + "--name", + "Runtime Binding Agent", + "--model", + "qwen3.8-max", + "--instructions", + "help", + flag, + value, + "--quiet", + ]); + expect(exitCode).toBe(2); + expect(stderr).toMatch(/Unknown option|unknown flag|unexpected/i); + }); + + test("managed-agent agent create 将 official Skill ID 写为外部引用", async () => { + const directory = await mkdtemp(join(tmpdir(), "bl-agent-official-skill-e2e-")); + const configPath = join(directory, "agents.yaml"); + await writeFile( + configPath, + `version: "1" +providers: + bailian: + api_key: \${DASHSCOPE_API_KEY} + base_url: \${BAILIAN_BASE_URL} +defaults: + provider: bailian +`, + "utf8", + ); + + try { + const { exitCode } = await runCommandE2e( + MANAGED_AGENT_ROUTES, + [ + "managed-agent", + "agent", + "create", + "--name", + "Report Agent", + "--model", + "qwen3.8-max", + "--instructions", + "Create a report.", + "--skill", + "skill_pptx", + "--type", + "official", + "--file", + configPath, + "--yes", + ], + { + DASHSCOPE_API_KEY: "sk-e2e-agent-official-skill", + BAILIAN_BASE_URL: "http://127.0.0.1:1/api/v1/agentstudio", + }, + ); + expect(exitCode).toBe(1); + const config = parse(await readFile(configPath, "utf8")) as { + agents?: Record<string, { skills?: Array<{ type?: string; skill_id?: string }> }>; + }; + expect(config.agents?.["report-agent"]?.skills).toEqual([ + { type: "official", skill_id: "skill_pptx" }, + ]); + } finally { + await rm(directory, { recursive: true, force: true }); + } + }); + + test("managed-agent agent create 的 Skill type 默认是 custom", async () => { + const sourceBefore = await readFile(AGENTS_YAML, "utf8"); + const { stdout, stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [ + "managed-agent", + "agent", + "create", + "--dry-run", + "--name", + "Custom Skill Agent", + "--model", + "qwen3.8-max", + "--instructions", + "Use the custom Skill.", + "--skill", + "skill_custom_abc", + "--file", + AGENTS_YAML, + "--output", + "json", + ]); + expect(exitCode, stderr).toBe(0); + const data = parseStdoutJson<{ + agent?: { skills?: Array<{ type?: string; skill_id?: string }> }; + yaml_written?: boolean; + }>(stdout); + expect(data.agent?.skills).toEqual([{ type: "custom", skill_id: "skill_custom_abc" }]); + expect(data.yaml_written).toBe(false); + expect(await readFile(AGENTS_YAML, "utf8")).toBe(sourceBefore); + }); + + test("managed-agent agent create --skill-dir 离线规划 Skill 与 Agent 且不写 YAML", async () => { + const directory = await mkdtemp(join(tmpdir(), "bl-agent-skill-dir-preview-e2e-")); + const configPath = join(directory, "agents.yaml"); + const skillDirectory = await writeLocalSkillDirectory(directory); + const configSource = `version: "1" +providers: + bailian: + api_key: \${DASHSCOPE_API_KEY} + base_url: \${BAILIAN_BASE_URL} +defaults: + provider: bailian + `; + await writeFile(configPath, configSource, "utf8"); + + try { + const { stdout, stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [ + "managed-agent", + "agent", + "create", + "--dry-run", + "--name", + "ZIP Agent", + "--model", + "qwen3.8-max", + "--instructions", + "Use the local Skill.", + "--skill-dir", + skillDirectory, + "--file", + configPath, + "--output", + "json", + ]); + expect(exitCode, stderr).toBe(0); + const data = parseStdoutJson<{ + agent?: { key?: string; skills?: Array<string | Record<string, unknown>> }; + local_skills?: Array<{ key?: string; name?: string; source?: string }>; + yaml_written?: boolean; + actions?: Array<{ + action?: string; + address?: { type?: string; name?: string; provider?: string }; + }>; + }>(stdout); + expect(data.agent).toEqual( + expect.objectContaining({ key: "zip-agent", skills: ["local-report"] }), + ); + expect(data.local_skills).toEqual([ + expect.objectContaining({ + key: "local-report", + name: "local-report", + source: "local-report", + }), + ]); + expect(data.yaml_written).toBe(false); + expect(data.actions).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + action: "create", + address: expect.objectContaining({ type: "skill", name: "local-report" }), + }), + expect.objectContaining({ + action: "create", + address: expect.objectContaining({ type: "agent", name: "zip-agent" }), + }), + ]), + ); + expect(data.actions).toHaveLength(2); + expect(await readFile(configPath, "utf8")).toBe(configSource); + } finally { + await rm(directory, { recursive: true, force: true }); + } + }); + + test("managed-agent agent create --skill-dir 兼容本地 ZIP", async () => { + const directory = await mkdtemp(join(tmpdir(), "bl-agent-skill-dir-zip-e2e-")); + const configPath = join(directory, "agents.yaml"); + const skillZipPath = await writeLocalSkillZip(directory); + const configSource = `version: "1" +providers: + bailian: + api_key: \${DASHSCOPE_API_KEY} + base_url: \${BAILIAN_BASE_URL} +defaults: + provider: bailian +`; + await writeFile(configPath, configSource, "utf8"); + + try { + const { stdout, stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [ + "managed-agent", + "agent", + "create", + "--dry-run", + "--name", + "ZIP Agent", + "--model", + "qwen3.8-max", + "--instructions", + "Use the ZIP Skill.", + "--skill-dir", + skillZipPath, + "--file", + configPath, + "--output", + "json", + ]); + expect(exitCode, stderr).toBe(0); + const data = parseStdoutJson<{ + agent?: { skills?: Array<string | Record<string, unknown>> }; + local_skills?: Array<{ key?: string; source?: string }>; + yaml_written?: boolean; + }>(stdout); + expect(data.agent?.skills).toEqual(["local-report"]); + expect(data.local_skills).toEqual([ + expect.objectContaining({ key: "local-report", source: "local-report.zip" }), + ]); + expect(data.yaml_written).toBe(false); + expect(await readFile(configPath, "utf8")).toBe(configSource); + } finally { + await rm(directory, { recursive: true, force: true }); + } + }); + + test("managed-agent agent create --skill-dir 写入顶层 Skill 并在 Agent 中引用 key", async () => { + const directory = await mkdtemp(join(tmpdir(), "bl-agent-skill-dir-write-e2e-")); + const configPath = join(directory, "agents.yaml"); + const skillDirectory = await writeLocalSkillDirectory(directory); + await writeFile( + configPath, + `version: "1" +providers: + bailian: + api_key: \${DASHSCOPE_API_KEY} + base_url: \${BAILIAN_BASE_URL} +defaults: + provider: bailian +`, + "utf8", + ); + const server = http.createServer((_request, response) => { + response.writeHead(500, { "Content-Type": "application/json" }); + response.end(JSON.stringify({ message: "intentional Skill directory upload failure" })); + }); + await new Promise<void>((resolveListen) => server.listen(0, "127.0.0.1", resolveListen)); + const address = server.address() as AddressInfo; + const args = [ + "managed-agent", + "agent", + "create", + "--name", + "ZIP Agent", + "--model", + "qwen3.8-max", + "--instructions", + "Use the local Skill.", + "--skill-dir", + skillDirectory, + "--file", + configPath, + "--yes", + "--output", + "json", + ]; + const env = { + DASHSCOPE_API_KEY: "sk-e2e-agent-skill-zip", + BAILIAN_BASE_URL: `http://127.0.0.1:${address.port}/api/v1/agentstudio`, + }; + + try { + for (let attempt = 0; attempt < 2; attempt += 1) { + const result = await runCommandE2e(MANAGED_AGENT_ROUTES, args, env); + expect(result.exitCode, result.stderr).toBe(1); + expect(result.stderr).toContain("intentional Skill directory upload failure"); + } + const config = parse(await readFile(configPath, "utf8")) as { + skills?: Record<string, Record<string, unknown>>; + agents?: Record<string, { skills?: Array<string | Record<string, unknown>> }>; + }; + expect(config.skills?.["local-report"]).toEqual({ + name: "local-report", + source: "local-report", + origin: "custom", + provider: "bailian", + }); + expect(config.agents?.["zip-agent"]?.skills).toEqual(["local-report"]); + expect(config.skills?.["local-report-2"]).toBeUndefined(); + expect(config.agents?.["zip-agent-2"]).toBeUndefined(); + } finally { + await new Promise<void>((resolveClose) => server.close(() => resolveClose())); + await rm(directory, { recursive: true, force: true }); + } + }); + test.each([ ["environment create", ["environment", "create"], /--name|--pip|--yes/i], ["skill create", ["skill", "create"], /--source|--yes/i], @@ -328,6 +763,7 @@ describe("e2e: managed-agent", () => { for (let attempt = 0; attempt < 2; attempt += 1) { const result = await runCommandE2e(MANAGED_AGENT_ROUTES, args, env); expect(result.exitCode, result.stderr).toBe(1); + expect(result.stderr).toContain("intentional create failure"); } const config = parse(await readFile(configPath, "utf8")) as { agents: Record<string, { name?: string }>; @@ -381,6 +817,7 @@ describe("e2e: managed-agent", () => { for (let attempt = 0; attempt < 2; attempt += 1) { const result = await runCommandE2e(MANAGED_AGENT_ROUTES, args, env); expect(result.exitCode, result.stderr).toBe(1); + expect(result.stderr).toContain("intentional environment create failure"); } const config = parse(await readFile(configPath, "utf8")) as { environments: Record<string, { name?: string }>; @@ -471,7 +908,13 @@ vaults: request.on("end", () => { requestBodies.push(JSON.parse(Buffer.concat(chunks).toString("utf8"))); response.writeHead(500, { "Content-Type": "application/json" }); - response.end(JSON.stringify({ message: "intentional credential create failure" })); + response.end( + JSON.stringify({ + code: "CREDENTIAL_CREATE_FAILURE", + message: "intentional credential create failure", + request_id: "req_credential_create_e2e", + }), + ); }); return; } @@ -523,6 +966,10 @@ vaults: for (let attempt = 0; attempt < 2; attempt += 1) { const result = await runCommandE2e(MANAGED_AGENT_ROUTES, args, env); expect(result.exitCode).toBe(1); + expect(result.stderr).toMatch(/"message":\s*"intentional credential create failure"/); + expect(result.stderr).toMatch(/"http_status":\s*500/); + expect(result.stderr).toMatch(/"api_code":\s*"CREDENTIAL_CREATE_FAILURE"/); + expect(result.stderr).toMatch(/"request_id":\s*"req_credential_create_e2e"/); expect(`${result.stdout}\n${result.stderr}`).not.toContain( "credential-secret-must-not-leak", ); diff --git a/packages/commands/tests/managed-agent-create.test.ts b/packages/commands/tests/managed-agent-create.test.ts index 48d23a3a7..c2f81989b 100644 --- a/packages/commands/tests/managed-agent-create.test.ts +++ b/packages/commands/tests/managed-agent-create.test.ts @@ -4,9 +4,11 @@ import { join } from "node:path"; import { buildAgentDecl, StateManager } from "@openagentpack/sdk"; import { expect, test } from "vite-plus/test"; import { + buildAgentSkillRefs, normalizeAgentKey, replaceConfigAtomically, selectAgentKey, + selectLocalSkillKey, } from "../src/commands/managed-agent/agent/create.ts"; import { normalizeResourceKey, @@ -89,6 +91,74 @@ test("同名待创建声明配置不同则创建新的逻辑 key", () => { ).toEqual({ key: "assistant-2", reusedPending: false }); }); +test("Agent Skill ID 默认生成 custom 外部引用并去重", () => { + expect(buildAgentSkillRefs(["skill_report", "skill_report", "skill_chart"])).toEqual([ + { type: "custom", skill_id: "skill_report" }, + { type: "custom", skill_id: "skill_chart" }, + ]); +}); + +test("Agent Skill ID 可生成 official 外部引用", () => { + expect(buildAgentSkillRefs(["skill_pptx"], "official")).toEqual([ + { type: "official", skill_id: "skill_pptx" }, + ]); + expect(() => buildAgentSkillRefs([" "])).toThrow(/must not be empty/); +}); + +test("本地 Skill 来源复用同声明的待创建或已跟踪 key", () => { + const declaration = { + name: "local-report", + source: "skills/local-report.zip", + origin: "custom", + provider: "bailian", + }; + const pendingState = StateManager.initialize("/tmp/bailian-cli-local-skill-pending.json"); + expect( + selectLocalSkillKey({ + displayName: "local-report", + provider: "bailian", + declarations: { "local-report": declaration }, + candidate: declaration, + state: pendingState, + }), + ).toEqual({ + key: "local-report", + needsCreate: true, + reusedPending: true, + reusedTracked: false, + }); + + const trackedState = StateManager.initialize("/tmp/bailian-cli-local-skill-tracked.json"); + trackedState.setResource({ + address: { type: "skill", name: "local-report", provider: "bailian" }, + remote_id: "skill_local_report", + content_hash: "hash", + }); + expect( + selectLocalSkillKey({ + displayName: "local-report", + provider: "bailian", + declarations: { "local-report": declaration }, + candidate: declaration, + state: trackedState, + }), + ).toEqual({ + key: "local-report", + needsCreate: false, + reusedPending: false, + reusedTracked: true, + }); + expect(() => + selectLocalSkillKey({ + displayName: "local-report", + provider: "bailian", + declarations: { "local-report": declaration }, + candidate: { ...declaration, source: "skills/new-local-report.zip" }, + state: trackedState, + }), + ).toThrow(/already tracked with a different declaration/); +}); + test("YAML 替换校验原内容并保留文件权限", async () => { const directory = await mkdtemp(join(tmpdir(), "bailian-agent-create-")); const configPath = join(directory, "agents.yaml"); diff --git a/packages/commands/tests/managed-agent-errors.test.ts b/packages/commands/tests/managed-agent-errors.test.ts index 8af71b7ef..c9ed4d4ca 100644 --- a/packages/commands/tests/managed-agent-errors.test.ts +++ b/packages/commands/tests/managed-agent-errors.test.ts @@ -1,7 +1,11 @@ import { UserError } from "@openagentpack/sdk"; import { BailianError, ExitCode } from "bailian-cli-core"; import { expect, test } from "vite-plus/test"; -import { withAgentErrors } from "../src/commands/managed-agent/_engine/errors.ts"; +import { + formatAgentDiagnosticFailure, + retainAgentError, + withAgentErrors, +} from "../src/commands/managed-agent/_engine/errors.ts"; /** * Structural stand-in for the SDK's internal `ApiError` (not exported by the @@ -116,3 +120,45 @@ test("fetch transport TypeError is rethrown untouched for the runtime NETWORK ma expect(error).toBe(transportError); } }); + +test("diagnostic failure surfaces the first error and reports the remaining count", () => { + const message = formatAgentDiagnosticFailure( + [ + { severity: "warning", code: "warning.code", message: "warning" }, + { severity: "error", code: "first.code", message: "first failure" }, + { severity: "error", code: "second.code", message: "second failure" }, + { severity: "error", code: "third.code", message: "third failure" }, + ], + "fallback", + ); + + expect(message).toBe("[first.code] first failure (+2 more errors)"); +}); + +test("diagnostic failure uses its fallback only when no error detail exists", () => { + expect( + formatAgentDiagnosticFailure( + [{ severity: "warning", code: "warning.code", message: "warning" }], + "fallback", + ), + ).toBe("fallback"); +}); + +test("retaining an Agent error adds a hint without losing structured context", () => { + const cause = new Error("provider cause"); + const original = new BailianError("provider failure", ExitCode.GENERAL, undefined, { + api: { httpStatus: 500, apiCode: "PROVIDER_FAILURE", requestId: "req-retain" }, + rawResponse: "raw provider response", + cause, + }); + + const retained = retainAgentError(original, "retry the create", "fallback"); + + expect(retained).not.toBe(original); + expect(retained.message).toBe("provider failure"); + expect(retained.exitCode).toBe(ExitCode.GENERAL); + expect(retained.hint).toBe("retry the create"); + expect(retained.api).toEqual(original.api); + expect(retained.rawResponse).toBe("raw provider response"); + expect(retained.cause).toBe(cause); +}); diff --git a/skills/bailian-managed-agent/SKILL.md b/skills/bailian-managed-agent/SKILL.md index fa313f5e6..f0ebbd982 100644 --- a/skills/bailian-managed-agent/SKILL.md +++ b/skills/bailian-managed-agent/SKILL.md @@ -55,6 +55,9 @@ API-oriented commands do not replace IaC. Agent / Environment / Skill / Vault / | Deployment | `bl managed-agent deployment create` | - 用户只提供资源 `name`;CLI 自动生成稳定的 YAML 逻辑 key,同名资源用递增后缀并存。Credential 追加到指定 Vault,不单独生成 key。 +- `agent create --skill <id>` 直接绑定已存在的远端 Skill,不要求顶层 `skills` 声明;默认写为 `type: custom`,平台 Skill 显式增加 `--type official`。同一次命令中的全部 `--skill` 共用该类型。 +- `agent create --skill-dir <path>` 接收可重复的本地 Skill 目录或 ZIP:CLI 从其中的 `SKILL.md` 读取 name,自动生成顶层 `skills.<key>` 声明,并把该 key 写入 Agent 的 `skills` 列表;定向 Apply 会先上传 custom Skill,再创建 Agent。`--type` 只作用于 `--skill <id>`,不改变本地 Skill 的 custom 类型。 +- Environment 和 Vault 属于 Session/Deployment 运行时绑定,不是 Agent 创建参数;在 `session create|run` 或 `deployment create` 中传入。 - 默认只预览自动 key 和定向计划;`--dry-run` 完全离线,只有显式 `--yes` 才写 YAML 并创建远端资源。 - 定向流程只刷新目标资源及其传递依赖;无关资源不检测 Drift、不产生 action,也不阻塞。 - 目标资源必须是 `create`,相关依赖必须已经处于 `no-op`;项目级 Drift 和删除仍由全量 `plan/apply` 处理。 diff --git a/skills/bailian-managed-agent/reference/managed-agent.md b/skills/bailian-managed-agent/reference/managed-agent.md index 25cc4fe84..081f64448 100644 --- a/skills/bailian-managed-agent/reference/managed-agent.md +++ b/skills/bailian-managed-agent/reference/managed-agent.md @@ -75,30 +75,30 @@ Index: [index.md](index.md) ### `bl managed-agent agent create` -| Field | Value | -| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Name** | `managed-agent agent create` | -| **Description** | Declare and create one Managed Agent through an isolated YAML apply | -| **Authentication** | API Key | -| **Usage** | `bl managed-agent agent create --name <name> --model <model> --instructions <text\|path> [--description <text>] [--provider <name>] [--environment <name>] [--vault <name>] [--skill <name>...] [--tool <name>...] [--file <path>] [--yes]` | +| Field | Value | +| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent agent create` | +| **Description** | Declare and create one Managed Agent through an isolated YAML apply | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent agent create --name <name> --model <model> --instructions <text\|path> [--description <text>] [--provider <name>] [--skill <id>...] [--type custom\|official] [--skill-dir <path>...] [--tool <name>...] [--file <path>] [--yes]` | #### Flags -| Flag | Type | Required | Description | -| ----------------------------- | ------ | -------- | -------------------------------------------------------------------- | -| `--name <name>` | string | yes | Remote Agent display name; the YAML key is generated automatically | -| `--model <model>` | string | yes | Model ID | -| `--instructions <text\|path>` | string | yes | Inline instructions or a ./, ../, or absolute file path | -| `--description <text>` | string | no | Agent description | -| `--provider <name>` | string | no | Target provider; inferred when the config has one effective provider | -| `--environment <name>` | string | no | Existing environment key from agents.yaml | -| `--vault <name>` | string | no | Existing vault key from agents.yaml | -| `--skill <name>` | array | no | Existing custom Skill key from agents.yaml (repeatable) | -| `--tool <name>` | array | no | Builtin tool name (repeatable) | -| `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--yes` | switch | no | Write YAML and run the scoped remote create | -| `--api-key <key>` | string | no | API key | -| `--base-url <url>` | string | no | API base URL | +| Flag | Type | Required | Description | +| ----------------------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------- | +| `--name <name>` | string | yes | Remote Agent display name; the YAML key is generated automatically | +| `--model <model>` | string | yes | Model ID | +| `--instructions <text\|path>` | string | yes | Inline instructions or a ./, ../, or absolute file path | +| `--description <text>` | string | no | Agent description | +| `--provider <name>` | string | no | Target provider; inferred when the config has one effective provider | +| `--skill <id>` | array | no | Existing remote Skill ID (repeatable) | +| `--skill-dir <path>` | array | no | Local Skill directory or ZIP to declare, upload, and attach through the same scoped create (repeatable) | +| `--type <custom\|official>` | string | no | Type applied to every --skill value (default: custom) | +| `--tool <name>` | array | no | Builtin tool name (repeatable) | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--yes` | switch | no | Write YAML and run the scoped remote create | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | #### Notes @@ -106,6 +106,8 @@ Index: [index.md](index.md) - Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. - Without --yes, previews the generated YAML key and scoped plan. --dry-run stays offline. Unrelated resources are not refreshed or drift-checked. +- --skill writes an external Skill reference directly into the Agent declaration. --type defaults to custom; use --type official for platform Skills. These Skills are not managed through the top-level skills map. +- --skill-dir accepts a local Skill directory or ZIP, writes it as a top-level custom Skill declaration, and writes its generated YAML key into the Agent skills list. Skill and Agent are created together in dependency order. --type applies only to --skill IDs. #### Examples @@ -114,7 +116,15 @@ bl managed-agent agent create --name assistant --model qwen3.8-max --instruction ``` ```bash -bl managed-agent agent create --name assistant --model qwen3.8-max --instructions ./prompts/assistant.md --environment dev --skill search --yes +bl managed-agent agent create --name assistant --model qwen3.8-max --instructions ./prompts/assistant.md --skill skill_abc --yes +``` + +```bash +bl managed-agent agent create --name slides --model qwen3.8-max --instructions ./prompts/slides.md --skill skill_pptx --type official --yes +``` + +```bash +bl managed-agent agent create --name reviewer --model qwen3.8-max --instructions ./prompts/reviewer.md --skill-dir ./skills/code-review --yes ``` ### `bl managed-agent agent get` From 9d19558645ddff22ce284ca6a1a874d21258b48a Mon Sep 17 00:00:00 2001 From: chenanran555 <car534511@alibaba-inc.com> Date: Wed, 2 Sep 2026 15:07:22 +0800 Subject: [PATCH 06/10] feat(managed-agent): enforce high-risk confirmation for runtime operations --- .../cli/tests/skill-risk-confirmation.test.ts | 28 +++++- .../managed-agent/deployment/_set-paused.ts | 17 +++- .../managed-agent/deployment/_shared.ts | 8 -- .../commands/managed-agent/deployment/run.ts | 23 ++--- .../src/commands/managed-agent/file.ts | 22 ++--- .../managed-agent/session-management.ts | 20 ++-- .../tests/e2e/managed-agent.e2e.test.ts | 10 ++ skills/bailian-managed-agent/SKILL.md | 7 +- .../reference/managed-agent.md | 97 ++++++++++++------- 9 files changed, 146 insertions(+), 86 deletions(-) diff --git a/packages/cli/tests/skill-risk-confirmation.test.ts b/packages/cli/tests/skill-risk-confirmation.test.ts index a7bca904c..8843f7864 100644 --- a/packages/cli/tests/skill-risk-confirmation.test.ts +++ b/packages/cli/tests/skill-risk-confirmation.test.ts @@ -5,9 +5,18 @@ import { expect, test } from "vite-plus/test"; const repositoryRoot = join(dirname(fileURLToPath(import.meta.url)), "../../.."); const skillsRoot = join(repositoryRoot, "skills"); +const scopedCreateCommands = new Set([ + "bl managed-agent agent create", + "bl managed-agent deployment create", + "bl managed-agent environment create", + "bl managed-agent skill create", + "bl managed-agent vault create", + "bl managed-agent vault credential create", +]); -test("every generated high-risk command reference requires user confirmation before --yes", () => { +test("generated references distinguish runtime high-risk confirmation from scoped create execution", () => { let highRiskCommandCount = 0; + const seenScopedCreateCommands = new Set<string>(); for (const skillDirectory of readdirSync(skillsRoot, { withFileTypes: true })) { if (!skillDirectory.isDirectory()) continue; @@ -27,9 +36,21 @@ test("every generated high-risk command reference requires user confirmation bef const commandSections = markdown.split(/(?=^### `bl )/m).slice(1); for (const commandSection of commandSections) { - if (!commandSection.includes("`--yes`")) continue; + const commandName = commandSection.match(/^### `([^`]+)`/m)?.[1]; + const hasConfirmationFlag = commandSection.includes("`--yes`"); + const hasHighRiskMetadata = /\|\s+\*\*Risk\*\*\s+\|\s+`high`\s+\|/.test(commandSection); + + if (!hasHighRiskMetadata) { + if (!hasConfirmationFlag) continue; + expect(commandName).toBeDefined(); + expect(scopedCreateCommands.has(commandName ?? "")).toBe(true); + expect(commandSection).toMatch(/Without --yes, .*preview/i); + seenScopedCreateCommands.add(commandName ?? ""); + continue; + } + highRiskCommandCount += 1; - expect(commandSection).toMatch(/\|\s+\*\*Risk\*\*\s+\|\s+`high`\s+\|/); + expect(hasConfirmationFlag).toBe(true); expect(commandSection).toMatch(/\|\s+\*\*Risk message\*\*\s+\|\s+.+\|/); expect(commandSection).toMatch(/type=.*requires_confirmation/); const agentSafetyLine = commandSection @@ -44,4 +65,5 @@ test("every generated high-risk command reference requires user confirmation bef } expect(highRiskCommandCount).toBeGreaterThan(0); + expect([...seenScopedCreateCommands].sort()).toEqual([...scopedCreateCommands].sort()); }); diff --git a/packages/commands/src/commands/managed-agent/deployment/_set-paused.ts b/packages/commands/src/commands/managed-agent/deployment/_set-paused.ts index 4e3b4b68a..b19b8f2be 100644 --- a/packages/commands/src/commands/managed-agent/deployment/_set-paused.ts +++ b/packages/commands/src/commands/managed-agent/deployment/_set-paused.ts @@ -16,9 +16,24 @@ export function createSetDeploymentPausedCommand(paused: boolean) { ? { "en-US": "Pause a Managed Agent deployment", "zh-CN": "暂停托管 Agent Deployment" } : { "en-US": "Unpause a Managed Agent deployment", "zh-CN": "恢复托管 Agent Deployment" }, auth: "apiKey", + risk: { + level: "high", + message: paused + ? { + "en-US": + "This pauses the specified Managed Agent deployment and stops its scheduled executions until resumed.", + "zh-CN": "该操作会暂停指定的托管 Agent Deployment,并停止其定时执行直至恢复。", + } + : { + "en-US": + "This resumes the specified Managed Agent deployment and may restart scheduled executions and related usage.", + "zh-CN": + "该操作会恢复指定的托管 Agent Deployment,可能重新开始定时执行并产生相关用量。", + }, + }, usageArgs: "(--deployment <name> | --deployment-id <id>)", flags: DEPLOYMENT_ACTION_TARGET_FLAGS, - exampleArgs: [`--deployment daily-report --dry-run`, `--deployment-id dep_abc`], + exampleArgs: [`--deployment daily-report --dry-run`, `--deployment-id dep_abc --yes`], notes: CREDENTIALS_NOTE, validate: validateDeploymentActionTarget, async run(ctx) { diff --git a/packages/commands/src/commands/managed-agent/deployment/_shared.ts b/packages/commands/src/commands/managed-agent/deployment/_shared.ts index a641d104c..ea6bfc498 100644 --- a/packages/commands/src/commands/managed-agent/deployment/_shared.ts +++ b/packages/commands/src/commands/managed-agent/deployment/_shared.ts @@ -96,14 +96,6 @@ export const DEPLOYMENT_ACTION_TARGET_FLAGS = { }, } as const; -export const DEPLOYMENT_RUN_ACTION_FLAGS = { - ...DEPLOYMENT_ACTION_TARGET_FLAGS, - yes: { - type: "switch", - description: { "en-US": "Confirm deployment run", "zh-CN": "确认运行 Deployment" }, - }, -} as const; - export function validateDeploymentActionTarget(flags: { deployment?: string; deploymentId?: string; diff --git a/packages/commands/src/commands/managed-agent/deployment/run.ts b/packages/commands/src/commands/managed-agent/deployment/run.ts index a6ebd7210..c1d55736d 100644 --- a/packages/commands/src/commands/managed-agent/deployment/run.ts +++ b/packages/commands/src/commands/managed-agent/deployment/run.ts @@ -1,11 +1,11 @@ import { runRemoteDeployment } from "@openagentpack/sdk"; -import { BailianError, defineCommand, detectOutputFormat, ExitCode } from "bailian-cli-core"; +import { defineCommand, detectOutputFormat } from "bailian-cli-core"; import { emitBare, emitResult } from "bailian-cli-runtime"; import { buildAgentRuntime, CREDENTIALS_NOTE } from "../_engine/config-loader.ts"; import { withStdoutProtected } from "../_engine/console-capture.ts"; import { withAgentErrors } from "../_engine/errors.ts"; import { - DEPLOYMENT_RUN_ACTION_FLAGS, + DEPLOYMENT_ACTION_TARGET_FLAGS, resolveDeploymentTarget, validateDeploymentActionTarget, } from "./_shared.ts"; @@ -16,8 +16,16 @@ export default defineCommand({ "zh-CN": "立即运行托管 Agent Deployment", }, auth: "apiKey", - usageArgs: "(--deployment <name> | --deployment-id <id>) --yes", - flags: DEPLOYMENT_RUN_ACTION_FLAGS, + risk: { + level: "high", + message: { + "en-US": + "This immediately starts a run for the specified Managed Agent deployment and may incur usage or trigger configured actions.", + "zh-CN": "该操作会立即运行指定的托管 Agent Deployment,可能产生用量或触发已配置的动作。", + }, + }, + usageArgs: "(--deployment <name> | --deployment-id <id>)", + flags: DEPLOYMENT_ACTION_TARGET_FLAGS, exampleArgs: ["--deployment daily-report --dry-run", "--deployment-id dep_abc --yes"], notes: CREDENTIALS_NOTE, validate: validateDeploymentActionTarget, @@ -33,13 +41,6 @@ export default defineCommand({ ); 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"); diff --git a/packages/commands/src/commands/managed-agent/file.ts b/packages/commands/src/commands/managed-agent/file.ts index 0e3037b51..913c35d71 100644 --- a/packages/commands/src/commands/managed-agent/file.ts +++ b/packages/commands/src/commands/managed-agent/file.ts @@ -7,7 +7,7 @@ import { listRemoteFiles, uploadFile, } from "@openagentpack/sdk"; -import { BailianError, defineCommand, detectOutputFormat, ExitCode } from "bailian-cli-core"; +import { defineCommand, detectOutputFormat } from "bailian-cli-core"; import { emitBare, emitResult } from "bailian-cli-runtime"; import { API_TARGET_FLAGS, @@ -90,10 +90,6 @@ const DOWNLOAD_FLAGS = { } 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[][] { @@ -298,7 +294,14 @@ export const managedAgentFileDownload = defineCommand({ export const managedAgentFileDelete = defineCommand({ description: { "en-US": "Delete a Managed Agent file", "zh-CN": "删除托管 Agent 文件" }, auth: "apiKey", - usageArgs: "--file-id <id> --yes", + risk: { + level: "high", + message: { + "en-US": "This permanently deletes the specified remote Managed Agent file.", + "zh-CN": "该操作会永久删除指定的远端托管 Agent 文件。", + }, + }, + usageArgs: "--file-id <id>", flags: DELETE_FLAGS, exampleArgs: ["--file-id file_abc --dry-run", "--file-id file_abc --yes"], notes: CREDENTIALS_NOTE, @@ -308,13 +311,6 @@ export const managedAgentFileDelete = defineCommand({ 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"); diff --git a/packages/commands/src/commands/managed-agent/session-management.ts b/packages/commands/src/commands/managed-agent/session-management.ts index 12101ad3e..5750bf632 100644 --- a/packages/commands/src/commands/managed-agent/session-management.ts +++ b/packages/commands/src/commands/managed-agent/session-management.ts @@ -90,10 +90,6 @@ const UPDATE_FLAGS = { 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[][] { @@ -217,7 +213,14 @@ export const managedAgentSessionUpdate = defineCommand({ export const managedAgentSessionArchive = defineCommand({ description: { "en-US": "Archive a Managed Agent session", "zh-CN": "归档托管 Agent Session" }, auth: "apiKey", - usageArgs: "--session-id <id> --yes", + risk: { + level: "high", + message: { + "en-US": "This archives the specified remote Managed Agent Session.", + "zh-CN": "该操作会归档指定的远端托管 Agent Session。", + }, + }, + usageArgs: "--session-id <id>", flags: ARCHIVE_FLAGS, exampleArgs: ["--session-id sess_abc --dry-run", "--session-id sess_abc --yes"], notes: CREDENTIALS_NOTE, @@ -227,13 +230,6 @@ export const managedAgentSessionArchive = defineCommand({ 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"); diff --git a/packages/commands/tests/e2e/managed-agent.e2e.test.ts b/packages/commands/tests/e2e/managed-agent.e2e.test.ts index 9ce9ff0b3..022f7b576 100644 --- a/packages/commands/tests/e2e/managed-agent.e2e.test.ts +++ b/packages/commands/tests/e2e/managed-agent.e2e.test.ts @@ -1046,6 +1046,11 @@ vaults: test.each([ ["state rm", ["state", "rm"]], ["session delete", ["session", "delete"]], + ["session archive", ["session", "archive"]], + ["file delete", ["file", "delete"]], + ["deployment run", ["deployment", "run"]], + ["deployment pause", ["deployment", "pause"]], + ["deployment unpause", ["deployment", "unpause"]], ])("managed-agent %s --help 展示 runtime 注入的 --yes", async (_commandName, commandPath) => { const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [ "managed-agent", @@ -1062,6 +1067,11 @@ vaults: "session delete", ["session", "delete", "--session-id", "sess_e2e", "--api-key", "e2e-dummy-key"], ], + ["session archive", ["session", "archive", "--session-id", "sess_e2e"]], + ["file delete", ["file", "delete", "--file-id", "file_e2e"]], + ["deployment run", ["deployment", "run", "--deployment-id", "dep_e2e"]], + ["deployment pause", ["deployment", "pause", "--deployment-id", "dep_e2e"]], + ["deployment unpause", ["deployment", "unpause", "--deployment-id", "dep_e2e"]], ])("managed-agent %s 无 --yes 返回确认请求 (7)", async (_commandName, commandArgs) => { const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [ "managed-agent", diff --git a/skills/bailian-managed-agent/SKILL.md b/skills/bailian-managed-agent/SKILL.md index 35e111209..76eefeb70 100644 --- a/skills/bailian-managed-agent/SKILL.md +++ b/skills/bailian-managed-agent/SKILL.md @@ -30,10 +30,11 @@ description: >- API-oriented commands do not replace IaC. Agent / Environment / Skill / Vault / Deployment 的 create 命令仍通过 `agents.yaml → scoped plan → scoped apply` 管理;查询命令和 Session、Event、File、Deployment 运行时动作直接调用 API。 -`session archive|delete`、`file delete`、`deployment run` 也需要先 `--dry-run`,确认后才传 `--yes`。 +`session archive|delete`、`file delete`、`deployment run|pause|unpause` 也需要先 `--dry-run`,确认后才传 `--yes`。 -`state rm`, `session delete`, and future `risk: high` commands follow the shared protocol: show the -risk message and exact scope, then wait for explicit confirmation before re-running with `--yes`. +`state rm`, `session archive|delete`, `file delete`, `deployment run|pause|unpause`, and future +`risk: high` commands follow the shared protocol: show the risk message and exact scope, then wait +for explicit confirmation before re-running with `--yes`. ## IaC lifecycle diff --git a/skills/bailian-managed-agent/reference/managed-agent.md b/skills/bailian-managed-agent/reference/managed-agent.md index 2cd26d9a1..aca615d12 100644 --- a/skills/bailian-managed-agent/reference/managed-agent.md +++ b/skills/bailian-managed-agent/reference/managed-agent.md @@ -477,12 +477,16 @@ bl managed-agent deployment list --status active --all --output json ### `bl managed-agent deployment pause` -| Field | Value | -| ------------------ | --------------------------------------------------------------------------------- | -| **Name** | `managed-agent deployment pause` | -| **Description** | Pause a Managed Agent deployment | -| **Authentication** | API Key | -| **Usage** | `bl managed-agent deployment pause (--deployment <name> \| --deployment-id <id>)` | +| Field | Value | +| ------------------ | ---------------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent deployment pause` | +| **Description** | Pause a Managed Agent deployment | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent deployment pause (--deployment <name> \| --deployment-id <id>)` | +| **Risk** | `high` | +| **Risk message** | This pauses the specified Managed Agent deployment and stops its scheduled executions until resumed. | + +> **Agent safety:** Never add `--yes` automatically. On `type="requires_confirmation"`, stop and ask for explicit user confirmation of the same action and scope. #### Flags @@ -492,6 +496,7 @@ bl managed-agent deployment list --status active --all --output json | `--provider <name>` | string | no | Target provider | | `--deployment <name>` | string | no | Logical deployment name in agents.yaml/state | | `--deployment-id <id>` | string | no | Direct deployment ID | +| `--yes` | switch | no | Confirm this high-risk operation | | `--api-key <key>` | string | no | API key | | `--base-url <url>` | string | no | API base URL | @@ -508,17 +513,22 @@ bl managed-agent deployment pause --deployment daily-report --dry-run ``` ```bash -bl managed-agent deployment pause --deployment-id dep_abc +# Only after explicit user confirmation: +bl managed-agent deployment pause --deployment-id dep_abc --yes ``` ### `bl managed-agent deployment run` -| Field | Value | -| ------------------ | ------------------------------------------------------------------------------------- | -| **Name** | `managed-agent deployment run` | -| **Description** | Run a Managed Agent deployment now | -| **Authentication** | API Key | -| **Usage** | `bl managed-agent deployment run (--deployment <name> \| --deployment-id <id>) --yes` | +| Field | Value | +| ------------------ | --------------------------------------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent deployment run` | +| **Description** | Run a Managed Agent deployment now | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent deployment run (--deployment <name> \| --deployment-id <id>)` | +| **Risk** | `high` | +| **Risk message** | This immediately starts a run for the specified Managed Agent deployment and may incur usage or trigger configured actions. | + +> **Agent safety:** Never add `--yes` automatically. On `type="requires_confirmation"`, stop and ask for explicit user confirmation of the same action and scope. #### Flags @@ -528,7 +538,7 @@ bl managed-agent deployment pause --deployment-id dep_abc | `--provider <name>` | string | no | Target provider | | `--deployment <name>` | string | no | Logical deployment name in agents.yaml/state | | `--deployment-id <id>` | string | no | Direct deployment ID | -| `--yes` | switch | no | Confirm deployment run | +| `--yes` | switch | no | Confirm this high-risk operation | | `--api-key <key>` | string | no | API key | | `--base-url <url>` | string | no | API base URL | @@ -545,6 +555,7 @@ bl managed-agent deployment run --deployment daily-report --dry-run ``` ```bash +# Only after explicit user confirmation: bl managed-agent deployment run --deployment-id dep_abc --yes ``` @@ -663,12 +674,16 @@ bl managed-agent deployment search --query nightly --all --output json ### `bl managed-agent deployment unpause` -| Field | Value | -| ------------------ | ----------------------------------------------------------------------------------- | -| **Name** | `managed-agent deployment unpause` | -| **Description** | Unpause a Managed Agent deployment | -| **Authentication** | API Key | -| **Usage** | `bl managed-agent deployment unpause (--deployment <name> \| --deployment-id <id>)` | +| Field | Value | +| ------------------ | ----------------------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent deployment unpause` | +| **Description** | Unpause a Managed Agent deployment | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent deployment unpause (--deployment <name> \| --deployment-id <id>)` | +| **Risk** | `high` | +| **Risk message** | This resumes the specified Managed Agent deployment and may restart scheduled executions and related usage. | + +> **Agent safety:** Never add `--yes` automatically. On `type="requires_confirmation"`, stop and ask for explicit user confirmation of the same action and scope. #### Flags @@ -678,6 +693,7 @@ bl managed-agent deployment search --query nightly --all --output json | `--provider <name>` | string | no | Target provider | | `--deployment <name>` | string | no | Logical deployment name in agents.yaml/state | | `--deployment-id <id>` | string | no | Direct deployment ID | +| `--yes` | switch | no | Confirm this high-risk operation | | `--api-key <key>` | string | no | API key | | `--base-url <url>` | string | no | API base URL | @@ -694,7 +710,8 @@ bl managed-agent deployment unpause --deployment daily-report --dry-run ``` ```bash -bl managed-agent deployment unpause --deployment-id dep_abc +# Only after explicit user confirmation: +bl managed-agent deployment unpause --deployment-id dep_abc --yes ``` ### `bl managed-agent destroy` @@ -893,12 +910,16 @@ bl managed-agent environment search --query production --page-limit 20 --output ### `bl managed-agent file delete` -| Field | Value | -| ------------------ | --------------------------------------------------- | -| **Name** | `managed-agent file delete` | -| **Description** | Delete a Managed Agent file | -| **Authentication** | API Key | -| **Usage** | `bl managed-agent file delete --file-id <id> --yes` | +| Field | Value | +| ------------------ | ----------------------------------------------------------------- | +| **Name** | `managed-agent file delete` | +| **Description** | Delete a Managed Agent file | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent file delete --file-id <id>` | +| **Risk** | `high` | +| **Risk message** | This permanently deletes the specified remote Managed Agent file. | + +> **Agent safety:** Never add `--yes` automatically. On `type="requires_confirmation"`, stop and ask for explicit user confirmation of the same action and scope. #### Flags @@ -907,7 +928,7 @@ bl managed-agent environment search --query production --page-limit 20 --output | `--file <path>` | string | no | Config file path (default: agents.yaml) | | `--provider <name>` | string | no | Target provider | | `--file-id <id>` | string | yes | Remote file ID | -| `--yes` | switch | no | Confirm permanent file deletion | +| `--yes` | switch | no | Confirm this high-risk operation | | `--api-key <key>` | string | no | API key | | `--base-url <url>` | string | no | API base URL | @@ -924,6 +945,7 @@ bl managed-agent file delete --file-id file_abc --dry-run ``` ```bash +# Only after explicit user confirmation: bl managed-agent file delete --file-id file_abc --yes ``` @@ -1180,12 +1202,16 @@ bl managed-agent plan --no-refresh ### `bl managed-agent session archive` -| Field | Value | -| ------------------ | ---------------------------------------------------------- | -| **Name** | `managed-agent session archive` | -| **Description** | Archive a Managed Agent session | -| **Authentication** | API Key | -| **Usage** | `bl managed-agent session archive --session-id <id> --yes` | +| Field | Value | +| ------------------ | --------------------------------------------------------- | +| **Name** | `managed-agent session archive` | +| **Description** | Archive a Managed Agent session | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent session archive --session-id <id>` | +| **Risk** | `high` | +| **Risk message** | This archives the specified remote Managed Agent Session. | + +> **Agent safety:** Never add `--yes` automatically. On `type="requires_confirmation"`, stop and ask for explicit user confirmation of the same action and scope. #### Flags @@ -1194,7 +1220,7 @@ bl managed-agent plan --no-refresh | `--file <path>` | string | no | Config file path (default: agents.yaml) | | `--provider <name>` | string | no | Target provider | | `--session-id <id>` | string | yes | Session ID | -| `--yes` | switch | no | Confirm session archive | +| `--yes` | switch | no | Confirm this high-risk operation | | `--api-key <key>` | string | no | API key | | `--base-url <url>` | string | no | API base URL | @@ -1211,6 +1237,7 @@ bl managed-agent session archive --session-id sess_abc --dry-run ``` ```bash +# Only after explicit user confirmation: bl managed-agent session archive --session-id sess_abc --yes ``` From 176961dc56a5fd48e8bf68dbae36cc1ecd3704f0 Mon Sep 17 00:00:00 2001 From: chenanran555 <car534511@alibaba-inc.com> Date: Wed, 2 Sep 2026 15:33:50 +0800 Subject: [PATCH 07/10] feat(managed-agent): hide capabilities command from public CLI --- packages/cli/src/commands.ts | 2 - .../commands/managed-agent/capabilities.ts | 49 ------------------- packages/commands/src/index.ts | 1 - .../tests/e2e/managed-agent.e2e.test.ts | 28 ----------- packages/commands/tests/e2e/topic-routes.ts | 1 - skills/bailian-managed-agent/SKILL.md | 3 +- .../bailian-managed-agent/reference/index.md | 7 ++- .../reference/managed-agent.md | 30 ------------ 8 files changed, 4 insertions(+), 117 deletions(-) delete mode 100644 packages/commands/src/commands/managed-agent/capabilities.ts diff --git a/packages/cli/src/commands.ts b/packages/cli/src/commands.ts index 40978fe2b..309b5d68d 100644 --- a/packages/cli/src/commands.ts +++ b/packages/cli/src/commands.ts @@ -152,7 +152,6 @@ import { managedAgentSessionSend, managedAgentSessionEvents, managedAgentSkillList, - managedAgentCapabilities, managedAgentAgentCreate, managedAgentEnvironmentCreate, managedAgentSkillCreate, @@ -357,7 +356,6 @@ 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 create": managedAgentAgentCreate, "managed-agent agent list": managedAgentAgentList, "managed-agent agent get": managedAgentAgentGet, diff --git a/packages/commands/src/commands/managed-agent/capabilities.ts b/packages/commands/src/commands/managed-agent/capabilities.ts deleted file mode 100644 index 77e3167ae..000000000 --- a/packages/commands/src/commands/managed-agent/capabilities.ts +++ /dev/null @@ -1,49 +0,0 @@ -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); - } - }, -}); diff --git a/packages/commands/src/index.ts b/packages/commands/src/index.ts index ccdebf579..0eb21c415 100644 --- a/packages/commands/src/index.ts +++ b/packages/commands/src/index.ts @@ -149,7 +149,6 @@ 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 { default as managedAgentAgentCreate } from "./commands/managed-agent/agent/create.ts"; export { default as managedAgentEnvironmentCreate } from "./commands/managed-agent/environment/create.ts"; export { default as managedAgentSkillCreate } from "./commands/managed-agent/skill/create.ts"; diff --git a/packages/commands/tests/e2e/managed-agent.e2e.test.ts b/packages/commands/tests/e2e/managed-agent.e2e.test.ts index 022f7b576..6b925b38c 100644 --- a/packages/commands/tests/e2e/managed-agent.e2e.test.ts +++ b/packages/commands/tests/e2e/managed-agent.e2e.test.ts @@ -1166,34 +1166,6 @@ vaults: }); 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; auth?: string; reason?: string }>; - }>(stdout); - for (const operation of [ - "agent.create", - "environment.create", - "skill.create", - "vault.create", - "vault.credential.create", - "deployment.create", - ]) { - expect(data.operations?.[operation]).toEqual( - expect.objectContaining({ supported: true, auth: "api_key" }), - ); - } - 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"]], diff --git a/packages/commands/tests/e2e/topic-routes.ts b/packages/commands/tests/e2e/topic-routes.ts index 89ac236db..18e6279c4 100644 --- a/packages/commands/tests/e2e/topic-routes.ts +++ b/packages/commands/tests/e2e/topic-routes.ts @@ -199,7 +199,6 @@ export const MANAGED_AGENT_ROUTES: E2eRouteExports = { "managed-agent session list": "managedAgentSessionList", "managed-agent session events": "managedAgentSessionEvents", "managed-agent skill-list": "managedAgentSkillList", - "managed-agent capabilities": "managedAgentCapabilities", "managed-agent agent create": "managedAgentAgentCreate", "managed-agent agent list": "managedAgentAgentList", "managed-agent agent get": "managedAgentAgentGet", diff --git a/skills/bailian-managed-agent/SKILL.md b/skills/bailian-managed-agent/SKILL.md index 76eefeb70..81441d96e 100644 --- a/skills/bailian-managed-agent/SKILL.md +++ b/skills/bailian-managed-agent/SKILL.md @@ -127,7 +127,6 @@ Managed Agents 的子线程通过 Event 中的 `session_thread_id` 暴露;公 | Intent | Command family | | -------------------------------------- | ------------------------------------------------------------------------------------------------------- | -| Check exact API support/auth/reason | `bl managed-agent capabilities` | | Discover agents and versions | `agent list`, `agent get`, `agent search`, `agent versions` | | Discover environments | `environment list`, `environment get`, `environment search` | | Discover skills and download a version | `skill list`, `skill get`, `skill search`, `skill versions`, `skill download` | @@ -143,7 +142,7 @@ Managed Agents 的子线程通过 Event 中的 `session_thread_id` 暴露;公 - 客户端搜索默认最多扫描 10 页;需要扩大范围时显式传 `--page-limit`。Deployment 搜索直接映射服务端 `keyword`。 - 下载必须给出 `--output-file`;默认不覆盖已有文件,只有用户确认后才可加 `--force`。 - `session export` 只导出诊断元数据,不含 File 正文,并会脱敏凭证类字段。 -- 公开 Managed Agents API 没有模型 Catalog,也没有 MCP OAuth Login;以 `capabilities` 返回的 unsupported 原因为准。 +- 公开 Managed Agents API 没有模型 Catalog,也没有 MCP OAuth Login;CLI 不注册对应命令。 ## Local state management diff --git a/skills/bailian-managed-agent/reference/index.md b/skills/bailian-managed-agent/reference/index.md index d019fdf6d..6d2d37348 100644 --- a/skills/bailian-managed-agent/reference/index.md +++ b/skills/bailian-managed-agent/reference/index.md @@ -17,7 +17,6 @@ Use this index for the skill-scoped quick index and global flags. | `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 create` | API Key | Declare and create one Managed Agent Deployment through a scoped YAML apply | [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) | @@ -75,9 +74,9 @@ Use this index for the skill-scoped quick index and global flags. ## By group -| Group | Commands | Reference | -| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------ | -| `managed-agent` | `agent create`, `agent get`, `agent list`, `agent search`, `agent versions`, `apply`, `capabilities`, `deployment create`, `deployment get`, `deployment list`, `deployment pause`, `deployment run`, `deployment runs get`, `deployment runs list`, `deployment search`, `deployment unpause`, `destroy`, `environment create`, `environment get`, `environment list`, `environment search`, `file delete`, `file download`, `file get`, `file list`, `file search`, `file upload`, `init`, `plan`, `session archive`, `session create`, `session debug`, `session delete`, `session event list`, `session event send`, `session event stream`, `session events`, `session export`, `session get`, `session list`, `session run`, `session search`, `session send`, `session update`, `skill create`, `skill download`, `skill get`, `skill list`, `skill search`, `skill versions`, `skill-list`, `state import`, `state list`, `state rm`, `state show`, `validate`, `vault create`, `vault credential create`, `vault get`, `vault list`, `vault search` | [managed-agent.md](managed-agent.md) | +| Group | Commands | Reference | +| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | +| `managed-agent` | `agent create`, `agent get`, `agent list`, `agent search`, `agent versions`, `apply`, `deployment create`, `deployment get`, `deployment list`, `deployment pause`, `deployment run`, `deployment runs get`, `deployment runs list`, `deployment search`, `deployment unpause`, `destroy`, `environment create`, `environment get`, `environment list`, `environment search`, `file delete`, `file download`, `file get`, `file list`, `file search`, `file upload`, `init`, `plan`, `session archive`, `session create`, `session debug`, `session delete`, `session event list`, `session event send`, `session event stream`, `session events`, `session export`, `session get`, `session list`, `session run`, `session search`, `session send`, `session update`, `skill create`, `skill download`, `skill get`, `skill list`, `skill search`, `skill versions`, `skill-list`, `state import`, `state list`, `state rm`, `state show`, `validate`, `vault create`, `vault credential create`, `vault get`, `vault list`, `vault search` | [managed-agent.md](managed-agent.md) | ## Global flags diff --git a/skills/bailian-managed-agent/reference/managed-agent.md b/skills/bailian-managed-agent/reference/managed-agent.md index aca615d12..7a9d53c92 100644 --- a/skills/bailian-managed-agent/reference/managed-agent.md +++ b/skills/bailian-managed-agent/reference/managed-agent.md @@ -15,7 +15,6 @@ Index: [index.md](index.md) | `bl managed-agent agent search` | API Key | Search Managed Agents | | `bl managed-agent agent versions` | API Key | List Managed Agent versions | | `bl managed-agent apply` | API Key | Apply planned changes to create/update/delete agent resources | -| `bl managed-agent capabilities` | No Auth | Show operation-level Managed Agents API capabilities | | `bl managed-agent deployment create` | API Key | Declare and create one Managed Agent Deployment through a scoped YAML apply | | `bl managed-agent deployment get` | API Key | Get a Managed Agent deployment | | `bl managed-agent deployment list` | API Key | List Managed Agent deployments | @@ -324,35 +323,6 @@ bl managed-agent apply --yes bl managed-agent apply --provider bailian --yes ``` -### `bl managed-agent capabilities` - -| Field | Value | -| ------------------ | ---------------------------------------------------- | -| **Name** | `managed-agent capabilities` | -| **Description** | Show operation-level Managed Agents API capabilities | -| **Authentication** | No Auth | -| **Usage** | `bl managed-agent capabilities [--provider <name>]` | - -#### Flags - -| Flag | Type | Required | Description | -| ------------------- | ------ | -------- | -------------------------------------- | -| `--provider <name>` | string | no | Provider to inspect (default: bailian) | - -#### Notes - -- Capabilities distinguish public Managed Agents APIs from client-side compositions and unsupported resources. - -#### Examples - -```bash -bl managed-agent capabilities -``` - -```bash -bl managed-agent capabilities --provider bailian --output json -``` - ### `bl managed-agent deployment create` | Field | Value | From 4c5688ad4abc041b2817085452bb1574adc6bbd6 Mon Sep 17 00:00:00 2001 From: chenanran555 <car534511@alibaba-inc.com> Date: Wed, 2 Sep 2026 19:23:40 +0800 Subject: [PATCH 08/10] feat(managed-agent)!: restrict CLI to Bailian provider Remove multi-provider CLI flags and reject non-Bailian provider configs while preserving SDK support. --- docs/agents/auth-change.md | 12 +- .../managed-agent/_engine/api-helpers.ts | 5 - .../managed-agent/_engine/config-loader.ts | 40 +- .../managed-agent/_engine/credentials.ts | 31 +- .../managed-agent/_engine/scoped-create.ts | 31 +- .../commands/managed-agent/agent/create.ts | 12 +- .../src/commands/managed-agent/agent/get.ts | 2 +- .../src/commands/managed-agent/agent/list.ts | 2 +- .../commands/managed-agent/agent/search.ts | 2 +- .../commands/managed-agent/agent/versions.ts | 2 +- .../src/commands/managed-agent/apply.ts | 23 +- .../managed-agent/deployment/_set-paused.ts | 2 +- .../managed-agent/deployment/_shared.ts | 12 +- .../managed-agent/deployment/create.ts | 28 +- .../commands/managed-agent/deployment/get.ts | 2 +- .../commands/managed-agent/deployment/list.ts | 2 +- .../commands/managed-agent/deployment/run.ts | 2 +- .../managed-agent/deployment/runs/get.ts | 2 +- .../managed-agent/deployment/runs/list.ts | 2 +- .../managed-agent/deployment/search.ts | 2 +- .../src/commands/managed-agent/destroy.ts | 11 +- .../managed-agent/environment/create.ts | 20 +- .../commands/managed-agent/environment/get.ts | 2 +- .../managed-agent/environment/list.ts | 2 +- .../managed-agent/environment/search.ts | 2 +- .../src/commands/managed-agent/file.ts | 23 +- .../src/commands/managed-agent/init.ts | 72 +-- .../src/commands/managed-agent/plan.ts | 21 +- .../commands/managed-agent/session-create.ts | 12 +- .../commands/managed-agent/session-debug.ts | 39 +- .../commands/managed-agent/session-delete.ts | 11 +- .../commands/managed-agent/session-event.ts | 19 +- .../src/commands/managed-agent/session-get.ts | 9 +- .../commands/managed-agent/session-list.ts | 7 +- .../managed-agent/session-management.ts | 6 +- .../src/commands/managed-agent/session-run.ts | 9 +- .../commands/managed-agent/session-send.ts | 11 +- .../commands/managed-agent/skill/create.ts | 16 +- .../commands/managed-agent/skill/download.ts | 2 +- .../src/commands/managed-agent/skill/get.ts | 2 +- .../src/commands/managed-agent/skill/list.ts | 24 +- .../commands/managed-agent/skill/search.ts | 2 +- .../commands/managed-agent/skill/versions.ts | 2 +- .../commands/managed-agent/state-import.ts | 29 +- .../commands/managed-agent/vault/create.ts | 17 +- .../managed-agent/vault/credential/create.ts | 22 +- .../src/commands/managed-agent/vault/get.ts | 2 +- .../src/commands/managed-agent/vault/list.ts | 2 +- .../commands/managed-agent/vault/search.ts | 2 +- .../fixtures/managed-agent/agents.state.json | 82 +-- .../e2e/managed-agent-auth-chain.e2e.test.ts | 60 +- .../tests/e2e/managed-agent.e2e.test.ts | 99 +++- .../tests/managed-agent-api-helpers.test.ts | 37 +- .../reference/managed-agent.md | 533 ++++++++---------- 54 files changed, 615 insertions(+), 810 deletions(-) diff --git a/docs/agents/auth-change.md b/docs/agents/auth-change.md index 837a5e984..696279a87 100644 --- a/docs/agents/auth-change.md +++ b/docs/agents/auth-change.md @@ -62,17 +62,19 @@ defineCommand({ auth }) → runtime/authStage → ctx.client → command.run(ctx `bl managed-agent *` 按调用链分两层: - **离线命令** — `init`、`validate`、`state list/show/rm`:`auth: "none"`,只读写本地文件,无需登录;引擎侧传 `credentials: "none"` 跳过凭证断言 -- **联网命令** — `plan`、`apply`、`destroy`、`state import`、`skill-list`、全部 `session *`:统一声明 `auth: "apiKey"` 硬门禁 —— 无论目标 provider 是谁,authStage 都经 `resolveApiKey(sources)` 解析 bailian 凭证(flag > env > active profile config),缺失报统一 AUTH;引擎层 `assertProviderCredentials` 再对 agents.yaml 里**全部已声明 provider** 的空 key 拦截并给 provider 专属 hint。例外:`plan --no-refresh` / `plan --dry-run` 传 `credentials: "none"` 并强制 `refresh: false`(不联网、不回写 state,不查 provider key),其中 `--dry-run` 连登录也不要求(authStage 的 dry-run 豁免),`--no-refresh` 仍需登录。 +- **联网命令** — `plan`、`apply`、`destroy`、`state import`、`skill-list`、全部 `session *`:统一声明 `auth: "apiKey"` 硬门禁,authStage 经 `resolveApiKey(sources)` 解析 Bailian 凭证(flag > env > active profile config),缺失报统一 AUTH;引擎层再断言 Bailian key 非空。例外:`plan --no-refresh` / `plan --dry-run` 传 `credentials: "none"` 并强制 `refresh: false`(不联网、不回写 state,不查 provider key),其中 `--dry-run` 连登录也不要求(authStage 的 dry-run 豁免),`--no-refresh` 仍需登录。 + +`bl managed-agent` 是 Bailian-only 产品入口:命令不暴露 `--provider`,`init` 只生成 `providers.bailian`,所有远端调用固定传 `provider: "bailian"`。`resolveAgentProjectConfig` 在创建 SDK runtime 前通过 `assertBailianOnlyProviders` 拒绝包含非 Bailian Provider 的手写配置;共享 `@openagentpack/sdk` 仍可保留多 Provider 能力。 凭证不以真实值写入 `process.env`,而是经 `packages/commands/src/commands/managed-agent/_engine/` 的**内存注入管道**(`resolveAgentProjectConfig`)注入 SDK,管道五步: -1. `prepareProviderEnv()` — 先 `bootstrapRuntimeCredentialsSync()`(SDK 把 `.env` / `~/.agents/config.json` 灌进 env,服务 claude/ark/qoder 等非 bailian provider),再把全部凭证类 env(`CREDENTIAL_ENV_KEYS`,含别名)中仍为 undefined 的占位为 `""`,使 agents.yaml 插值不因缺变量抛错 -2. `resolveProjectConfig` — 插值发生:bailian 插值拿到占位空串,claude/ark 拿到真实 env 值;随后 `normalizeInterpolatedProviderBlocks()` 把插值为空导致的 YAML `null` 归一为 `""`(避免离线命令下空 key 在 SDK zod 层报 "received null") +1. `prepareProviderEnv()` — 调用 SDK 的凭证 bootstrap,再把凭证类 env(`CREDENTIAL_ENV_KEYS`,含兼容别名)中仍为 undefined 的项占位为 `""`,使 agents.yaml 插值阶段能够完成并由 CLI 输出明确的 Bailian-only 配置错误 +2. `resolveProjectConfig` — 完成插值;随后 `normalizeInterpolatedProviderBlocks()` 把插值为空导致的 YAML `null` 归一为 `""`,避免空 key 在 SDK zod 层提前报 "received null" 3. `injectProviderCredentials()` — 用 `ctx.client.exportApiCredential()`(lint 限定 `managed-agent/_engine/**` 可用)覆写内存 config 对象的 bailian 块:有凭证时 `api_key` 无条件覆写;`base_url`(拼 `/api/v1/agentstudio` 后缀,无凭证时用 client 默认域名补齐以满足 schema)/`workspace_id`(取 `settings.workspaceId`)仅在引用且为空时填充 4. `scrubCredentialEnv()` — 从 `process.env` 删除全部凭证变量(真实凭证此后只存于 config 对象 → provider adapter 实例内存,不驻留 env / 不被子进程继承) -5. `assertProviderCredentials(providers)` — 任一已声明 provider 的 `api_key` 为空 → CLI 权威 `AUTH` 错误 + provider 专属 hint(取代 SDK 原始插值/zod 报错);离线命令传 `credentials: "none"` 整体跳过 +5. `assertBailianOnlyProviders(providers)` — 拒绝非 Bailian Provider;随后 `assertProviderCredentials(providers)` 在 Bailian `api_key` 为空时给出 CLI 权威 `AUTH` 错误和登录 hint;离线命令传 `credentials: "none"` 跳过 key 断言,但仍执行 Bailian-only 配置校验 -`bl auth login` 仅管理 bailian(DashScope)凭证;claude/ark/qoder 的 key 从 env(shell / `.env` / `~/.agents/config.json`)经插值进入 config 对象,同样被清扫。禁止命令层直接 `readConfigFile` 裸读凭证;bailian 字段以 CLI 鉴权链为唯一信源。 +禁止命令层直接 `readConfigFile` 裸读凭证;Bailian 字段以 CLI 鉴权链为唯一信源。SDK bootstrap 期间读取到的兼容凭证变量也会在配置解析后统一清扫。 ## 必查清单 diff --git a/packages/commands/src/commands/managed-agent/_engine/api-helpers.ts b/packages/commands/src/commands/managed-agent/_engine/api-helpers.ts index 5fce1c7e7..9fe3bd829 100644 --- a/packages/commands/src/commands/managed-agent/_engine/api-helpers.ts +++ b/packages/commands/src/commands/managed-agent/_engine/api-helpers.ts @@ -11,11 +11,6 @@ export const API_TARGET_FLAGS = { "zh-CN": "配置文件路径(默认:agents.yaml)", }, }, - provider: { - type: "string", - valueHint: "<name>", - description: { "en-US": "Target provider", "zh-CN": "目标 Provider" }, - }, } satisfies FlagsDef; export const CURSOR_FLAGS = { diff --git a/packages/commands/src/commands/managed-agent/_engine/config-loader.ts b/packages/commands/src/commands/managed-agent/_engine/config-loader.ts index 0457fda8e..7c83101da 100644 --- a/packages/commands/src/commands/managed-agent/_engine/config-loader.ts +++ b/packages/commands/src/commands/managed-agent/_engine/config-loader.ts @@ -3,8 +3,8 @@ import { type LoadedProjectConfig, type ProjectRuntimeContext, resolveProjectConfig, - UserError, } from "@openagentpack/sdk"; +import { BailianError, ExitCode } from "bailian-cli-core"; import { assertProviderCredentials, type CredentialHost, @@ -20,12 +20,14 @@ export { CREDENTIALS_NOTE, OFFLINE_NOTE } from "./credentials.ts"; /** * Whether this run requires provider keys: - * - "all" (default) — online command: every provider declared in agents.yaml - * must have a non-empty key after injection + * - "all" (default) — online command: the Bailian provider must have a + * non-empty key after injection * - "none" — offline command (local config/state only), skip the check */ export type CredentialScope = "all" | "none"; +export const BAILIAN_PROVIDER = "bailian"; + interface AgentConfigOptions { resolveEnv?: boolean; projectName?: string; @@ -42,7 +44,8 @@ interface AgentConfigOptions { * 3. override the bailian block with the CLI auth chain's credential (in-memory) * 4. scrub all credential vars from process.env (real values now live only in * the config object → provider adapters, never the environment) - * 5. fail with a CLI-authoritative AUTH error if any provider's key is empty + * 5. enforce Bailian-only config and fail with a CLI-authoritative AUTH error + * if its key is empty * (offline commands pass `credentials: "none"` to skip the check) */ export async function resolveAgentProjectConfig( @@ -55,6 +58,7 @@ export async function resolveAgentProjectConfig( normalizeInterpolatedProviderBlocks(resolved.config.providers); injectProviderCredentials(resolved.config.providers, host); scrubCredentialEnv(); + assertBailianOnlyProviders(resolved.config.providers); if ((options.credentials ?? "all") !== "none") { assertProviderCredentials(resolved.config.providers); } @@ -91,15 +95,23 @@ export async function buildAgentRuntime( return { ...ctx, configPath }; } -/** Ensure a user-supplied --provider value is actually configured in agents.yaml. */ -export function assertProviderConfigured( - ctx: ProjectRuntimeContext, - provider: string | undefined, -): void { - if (!provider || provider === "all") return; - if (ctx.providers.has(provider)) return; - const available = Array.from(ctx.providers.keys()).join(", ") || "none"; - throw new UserError( - `Provider '${provider}' is not configured. Available providers: ${available}.`, +/** Enforce the bl product boundary while the shared SDK remains multi-provider capable. */ +export function assertBailianOnlyProviders(providers: Record<string, unknown>): void { + const configuredProviders = Object.keys(providers); + const unsupportedProviders = configuredProviders.filter( + (provider) => provider !== BAILIAN_PROVIDER, ); + if (unsupportedProviders.length > 0) { + const names = unsupportedProviders.join(", "); + throw new BailianError( + `bl managed-agent only supports provider '${BAILIAN_PROVIDER}'; remove unsupported providers: ${names}. / bl managed-agent 仅支持 Provider '${BAILIAN_PROVIDER}';请移除不支持的 Provider:${names}。`, + ExitCode.USAGE, + ); + } + if (!(BAILIAN_PROVIDER in providers)) { + throw new BailianError( + `bl managed-agent requires a '${BAILIAN_PROVIDER}' provider configuration. / bl managed-agent 需要配置 Provider '${BAILIAN_PROVIDER}'。`, + ExitCode.USAGE, + ); + } } diff --git a/packages/commands/src/commands/managed-agent/_engine/credentials.ts b/packages/commands/src/commands/managed-agent/_engine/credentials.ts index 63489f589..934637b42 100644 --- a/packages/commands/src/commands/managed-agent/_engine/credentials.ts +++ b/packages/commands/src/commands/managed-agent/_engine/credentials.ts @@ -34,6 +34,12 @@ const CREDENTIAL_HINTS: Record<string, string> = { qoder: "Set QODER_PAT (or QODER_API_KEY) in your shell or .env.", }; +const BAILIAN_ONLY_NOTE = { + "en-US": + "bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected.", + "zh-CN": "bl managed-agent 仅支持百炼 Provider;包含其他 Provider 的配置会被拒绝。", +} as const; + /** The slice of CommandContext the credential pipeline needs: authStage-resolved client + settings. */ export interface CredentialHost { client: Client; @@ -43,24 +49,18 @@ export interface CredentialHost { /** * Shared `--help` note documenting where agent commands get provider * credentials. Bailian goes through bl's own auth chain (commands declare - * `auth: "apiKey"`); other providers come from env. Either way the resolved - * credential is injected into the SDK in-memory and scrubbed from the - * environment. Attach to every command that loads agents.yaml. `bl` prefix is - * safe: agent commands ship on `bl` only. + * `auth: "apiKey"`). The resolved credential is injected into the SDK in-memory + * and scrubbed from the environment. Attach to every command that loads + * agents.yaml. `bl` prefix is safe: agent commands ship on `bl` only. */ export const CREDENTIALS_NOTE = [ + BAILIAN_ONLY_NOTE, { "en-US": "Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile).", "zh-CN": "百炼凭证来自 bl 鉴权链:--api-key > DASHSCOPE_API_KEY > `bl auth login`(当前激活的配置 Profile)。", }, - { - "en-US": - "Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json.", - "zh-CN": - "其他 Provider 读取 agents.yaml 中引用的环境变量(例如 ${ANTHROPIC_API_KEY}),包括 .env 和 ~/.agents/config.json。", - }, { "en-US": "Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env.", @@ -73,6 +73,7 @@ export const CREDENTIALS_NOTE = [ * agents.yaml / local state only, so no login or provider key is required. */ export const OFFLINE_NOTE = [ + BAILIAN_ONLY_NOTE, { "en-US": "Runs fully offline against local files: no login or provider credentials required.", "zh-CN": "完全离线处理本地文件:无需登录或提供 Provider 凭证。", @@ -166,13 +167,9 @@ export function normalizeInterpolatedProviderBlocks(providers: Record<string, un } /** - * After injection, fail with a CLI-authoritative AUTH error if any configured - * provider's `api_key` resolved empty (missing env var, or no bl login for - * bailian). Replaces the SDK's raw `Environment variable '...' is not set` / - * zod config error with a clean message plus a provider-specific hint. Validates - * every declared provider by default. A scoped operation may pass its exact - * provider list so unrelated credentials cannot block it; offline commands skip - * the check entirely. + * After injection, fail with a CLI-authoritative AUTH error if Bailian's + * `api_key` resolved empty. Replaces the SDK's raw config error with a clean + * message and hint. Offline commands skip the check entirely. */ export function assertProviderCredentials( providers: Record<string, unknown>, diff --git a/packages/commands/src/commands/managed-agent/_engine/scoped-create.ts b/packages/commands/src/commands/managed-agent/_engine/scoped-create.ts index 1ffccd592..21d877eda 100644 --- a/packages/commands/src/commands/managed-agent/_engine/scoped-create.ts +++ b/packages/commands/src/commands/managed-agent/_engine/scoped-create.ts @@ -125,7 +125,6 @@ export function selectResourceKey(options: { export async function loadScopedCreateProject( host: ScopedCreateHost, file: string, - requestedProvider: string | undefined, ): Promise<LoadedScopedCreateProject> { const sourceBeforeLoad = await readFile(resolve(file), "utf8").catch((error) => { if ((error as NodeJS.ErrnoException).code === "ENOENT") { @@ -148,7 +147,7 @@ export async function loadScopedCreateProject( "Re-run the command against the latest file.", ); } - const provider = resolveTargetProvider(loaded.config, requestedProvider); + const provider = "bailian"; if (!host.settings.dryRun) assertProviderCredentials(loaded.config.providers, [provider]); installSdkTransport(host); const stateBackend = new LocalFileStateBackend({ configPath: loaded.configPath }); @@ -161,34 +160,6 @@ export async function loadScopedCreateProject( }; } -export function resolveTargetProvider( - config: ResolvedProjectConfig, - requested: string | undefined, -): string { - if (requested === "all") { - throw new BailianError( - "--provider all is not valid for a single-resource create.", - ExitCode.USAGE, - ); - } - if (requested) { - if (requested in config.providers) return requested; - throw new BailianError( - `Provider '${requested}' is not configured in agents.yaml.`, - ExitCode.USAGE, - ); - } - const defaultProvider = config.defaults?.provider; - if (defaultProvider && defaultProvider !== "all") return defaultProvider; - const configuredProviders = Object.keys(config.providers); - if (configuredProviders.length === 1) return configuredProviders[0]!; - throw new BailianError( - "Cannot infer one target provider for this create command.", - ExitCode.USAGE, - "Pass --provider <name> when defaults.provider is 'all' or multiple providers are configured.", - ); -} - export async function resolveCandidateDeclaration(options: { project: LoadedScopedCreateProject; group: ScopedCreateGroup; diff --git a/packages/commands/src/commands/managed-agent/agent/create.ts b/packages/commands/src/commands/managed-agent/agent/create.ts index 67a2ba517..a38dd8697 100644 --- a/packages/commands/src/commands/managed-agent/agent/create.ts +++ b/packages/commands/src/commands/managed-agent/agent/create.ts @@ -66,14 +66,6 @@ const CREATE_FLAGS = { valueHint: "<text>", description: { "en-US": "Agent description", "zh-CN": "Agent 描述" }, }, - provider: { - type: "string", - valueHint: "<name>", - description: { - "en-US": "Target provider; inferred when the config has one effective provider", - "zh-CN": "目标 Provider;配置只有一个有效 Provider 时自动推断", - }, - }, skill: { type: "array", valueHint: "<id>", @@ -344,7 +336,7 @@ export default defineCommand({ }, auth: "apiKey", usageArgs: - "--name <name> --model <model> --instructions <text|path> [--description <text>] [--provider <name>] [--skill <id>...] [--type custom|official] [--skill-dir <path>...] [--tool <name>...] [--file <path>] [--yes]", + "--name <name> --model <model> --instructions <text|path> [--description <text>] [--skill <id>...] [--type custom|official] [--skill-dir <path>...] [--tool <name>...] [--file <path>] [--yes]", flags: CREATE_FLAGS, exampleArgs: [ '--name assistant --model qwen3.8-max --instructions "You are helpful."', @@ -390,7 +382,7 @@ export default defineCommand({ const { flags, settings } = ctx; const format = detectOutputFormat(settings.output); const file = flags.file ?? "agents.yaml"; - const project = await loadScopedCreateProject(ctx, file, flags.provider); + const project = await loadScopedCreateProject(ctx, file); const provider = project.provider; const externalSkillRefs = buildAgentSkillRefs(flags.skill, agentSkillType(flags.type)); const localSkills = await prepareLocalSkillSources(project, flags.skillDir); diff --git a/packages/commands/src/commands/managed-agent/agent/get.ts b/packages/commands/src/commands/managed-agent/agent/get.ts index bed98c70d..e7dc50971 100644 --- a/packages/commands/src/commands/managed-agent/agent/get.ts +++ b/packages/commands/src/commands/managed-agent/agent/get.ts @@ -25,7 +25,7 @@ export default defineCommand({ withStdoutProtected(async () => { const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml"); return getRemoteAgent(runtime, ctx.flags.agentId, { - provider: ctx.flags.provider, + provider: "bailian", version: ctx.flags.agentVersion, }); }), diff --git a/packages/commands/src/commands/managed-agent/agent/list.ts b/packages/commands/src/commands/managed-agent/agent/list.ts index 638df7977..0bda7cd98 100644 --- a/packages/commands/src/commands/managed-agent/agent/list.ts +++ b/packages/commands/src/commands/managed-agent/agent/list.ts @@ -23,7 +23,7 @@ export default defineCommand({ return fetchAllPages( async (page) => { const response = await listRemoteAgents(runtime, { - provider: ctx.flags.provider, + provider: "bailian", limit: ctx.flags.limit, page, include_archived: ctx.flags.includeArchived, diff --git a/packages/commands/src/commands/managed-agent/agent/search.ts b/packages/commands/src/commands/managed-agent/agent/search.ts index 555181c1a..ff7f2436b 100644 --- a/packages/commands/src/commands/managed-agent/agent/search.ts +++ b/packages/commands/src/commands/managed-agent/agent/search.ts @@ -27,7 +27,7 @@ export default defineCommand({ return searchCursorPages( async (page) => { const response = await listRemoteAgents(runtime, { - provider: ctx.flags.provider, + provider: "bailian", limit: ctx.flags.limit ?? 100, page, include_archived: ctx.flags.includeArchived, diff --git a/packages/commands/src/commands/managed-agent/agent/versions.ts b/packages/commands/src/commands/managed-agent/agent/versions.ts index e4f3d80d5..974bbf06a 100644 --- a/packages/commands/src/commands/managed-agent/agent/versions.ts +++ b/packages/commands/src/commands/managed-agent/agent/versions.ts @@ -23,7 +23,7 @@ export default defineCommand({ return fetchAllPages( async (page) => { const response = await listRemoteAgentVersions(runtime, ctx.flags.agentId, { - provider: ctx.flags.provider, + provider: "bailian", limit: ctx.flags.limit, page, }); diff --git a/packages/commands/src/commands/managed-agent/apply.ts b/packages/commands/src/commands/managed-agent/apply.ts index a1f193a92..4d4a940ae 100644 --- a/packages/commands/src/commands/managed-agent/apply.ts +++ b/packages/commands/src/commands/managed-agent/apply.ts @@ -8,11 +8,7 @@ import { import { emitBare, emitResult } from "bailian-cli-runtime"; import { executePlannedProject, planProjectContext } from "@openagentpack/sdk"; import { formatResourceLabel } from "./_engine/address-utils.ts"; -import { - assertProviderConfigured, - buildAgentRuntime, - CREDENTIALS_NOTE, -} from "./_engine/config-loader.ts"; +import { buildAgentRuntime, CREDENTIALS_NOTE } from "./_engine/config-loader.ts"; import { withStdoutProtected } from "./_engine/console-capture.ts"; import { formatAgentDiagnosticFailure, withAgentErrors } from "./_engine/errors.ts"; import { renderAgentFeedback } from "./_engine/feedback.ts"; @@ -26,14 +22,6 @@ const APPLY_FLAGS = { "zh-CN": "配置文件路径(默认:agents.yaml)", }, }, - provider: { - type: "string", - valueHint: "<name>", - description: { - "en-US": "Target provider (default: all configured)", - "zh-CN": "目标 Provider(默认:全部已配置项)", - }, - }, noRefresh: { type: "switch", description: { @@ -65,9 +53,9 @@ export default defineCommand({ "zh-CN": "该操作会应用当前计划,可能创建、更新或删除远端托管 Agent 资源。", }, }, - usageArgs: "[--file <path>] [--provider <name>] [--concurrency <n>]", + usageArgs: "[--file <path>] [--concurrency <n>]", flags: APPLY_FLAGS, - exampleArgs: ["--yes", "--provider bailian --yes"], + exampleArgs: ["--yes"], notes: CREDENTIALS_NOTE, async run(ctx) { const { settings, flags } = ctx; @@ -78,7 +66,7 @@ export default defineCommand({ emitResult( { would_apply: { - provider: flags.provider ?? "all", + provider: "bailian", refresh: !flags.noRefresh, concurrency: flags.concurrency, }, @@ -93,9 +81,8 @@ export default defineCommand({ const planned = await withAgentErrors(() => withStdoutProtected(async () => { const runtime = await buildAgentRuntime(ctx, file); - assertProviderConfigured(runtime, flags.provider); return planProjectContext(runtime, { - provider: flags.provider, + provider: "bailian", refresh: !flags.noRefresh, quiet: true, onFeedback: renderAgentFeedback, diff --git a/packages/commands/src/commands/managed-agent/deployment/_set-paused.ts b/packages/commands/src/commands/managed-agent/deployment/_set-paused.ts index b19b8f2be..97e926dd3 100644 --- a/packages/commands/src/commands/managed-agent/deployment/_set-paused.ts +++ b/packages/commands/src/commands/managed-agent/deployment/_set-paused.ts @@ -56,7 +56,7 @@ export function createSetDeploymentPausedCommand(paused: boolean) { return { deploymentId, deployment: await setRemoteDeploymentPaused(runtime, deploymentId, paused, { - provider: ctx.flags.provider, + provider: "bailian", }), }; }), diff --git a/packages/commands/src/commands/managed-agent/deployment/_shared.ts b/packages/commands/src/commands/managed-agent/deployment/_shared.ts index ea6bfc498..62dede788 100644 --- a/packages/commands/src/commands/managed-agent/deployment/_shared.ts +++ b/packages/commands/src/commands/managed-agent/deployment/_shared.ts @@ -127,19 +127,11 @@ export function deploymentRunRows(runs: DeploymentRunInfo[]): string[][] { export async function resolveDeploymentTarget( runtime: ProjectRuntimeContext, - options: { provider?: string; deployment?: string; deploymentId?: string }, + options: { 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, + provider: "bailian", type: "deployment", name: options.deployment!, }); diff --git a/packages/commands/src/commands/managed-agent/deployment/create.ts b/packages/commands/src/commands/managed-agent/deployment/create.ts index c0446d39a..e25b847a2 100644 --- a/packages/commands/src/commands/managed-agent/deployment/create.ts +++ b/packages/commands/src/commands/managed-agent/deployment/create.ts @@ -97,14 +97,6 @@ const FLAGS = { "zh-CN": "Metadata 条目(可重复)", }, }, - provider: { - type: "string", - valueHint: "<name>", - description: { - "en-US": "Target provider; inferred when unambiguous", - "zh-CN": "目标 Provider;可唯一确定时自动推断", - }, - }, file: { type: "string", valueHint: "<path>", @@ -129,7 +121,7 @@ export default defineCommand({ }, auth: "apiKey", usageArgs: - "--name <name> --agent <yaml-key> (--message <text>... | --event <json|@path>...) [--agent-version <number>] [--environment <yaml-key>] [--vault <yaml-key>...] [--resource <json|@path>...] [--schedule <cron> --timezone <timezone>] [--description <text>] [--metadata <key=value>...] [--provider <name>] [--file <path>] [--yes]", + "--name <name> --agent <yaml-key> (--message <text>... | --event <json|@path>...) [--agent-version <number>] [--environment <yaml-key>] [--vault <yaml-key>...] [--resource <json|@path>...] [--schedule <cron> --timezone <timezone>] [--description <text>] [--metadata <key=value>...] [--file <path>] [--yes]", flags: FLAGS, exampleArgs: [ { @@ -138,9 +130,9 @@ export default defineCommand({ }, { "en-US": - '--name Daily --agent assistant --event \'{"type":"system.message","content":"Be concise"}\' --message "Run" --yes', + '--name Daily --agent assistant --event \'{"type":"system.message","content":"Be concise"}\' --yes', "zh-CN": - '--name Daily --agent assistant --event \'{"type":"system.message","content":"保持简洁"}\' --message "执行" --yes', + '--name Daily --agent assistant --event \'{"type":"system.message","content":"保持简洁"}\' --yes', }, ], notes: [ @@ -152,10 +144,18 @@ export default defineCommand({ "zh-CN": "Initial Events 必须包含 1–50 条 user.message/system.message;本期 --resource 仅接受 File Resource。", }, + { + "en-US": + "Use either repeatable --message values or repeatable --event values; the two input forms cannot be mixed.", + "zh-CN": "重复使用 --message 或重复使用 --event;两种输入形式不能混用。", + }, ], validate: (flags) => { if (!flags.name.trim()) return "--name must not be empty."; if (!flags.agent.trim()) return "--agent must not be empty."; + if (flags.message?.length && flags.event?.length) { + return "--message and --event cannot be used together."; + } if (!flags.message?.length && !flags.event?.length) return "Pass at least one --message or --event."; if (Boolean(flags.schedule) !== Boolean(flags.timezone)) { @@ -170,11 +170,7 @@ export default defineCommand({ return undefined; }, async run(ctx) { - const project = await loadScopedCreateProject( - ctx, - ctx.flags.file ?? "agents.yaml", - ctx.flags.provider, - ); + const project = await loadScopedCreateProject(ctx, ctx.flags.file ?? "agents.yaml"); const initialEvents = [ ...(ctx.flags.message ?? []).map((content) => ({ type: "user.message" as const, content })), ...validateEvents(await parseJsonInputs(ctx.flags.event, "event")), diff --git a/packages/commands/src/commands/managed-agent/deployment/get.ts b/packages/commands/src/commands/managed-agent/deployment/get.ts index 59f244a52..fd2841984 100644 --- a/packages/commands/src/commands/managed-agent/deployment/get.ts +++ b/packages/commands/src/commands/managed-agent/deployment/get.ts @@ -23,7 +23,7 @@ export default defineCommand({ withStdoutProtected(async () => { const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml"); return getRemoteDeployment(runtime, ctx.flags.deploymentId, { - provider: ctx.flags.provider, + provider: "bailian", }); }), ); diff --git a/packages/commands/src/commands/managed-agent/deployment/list.ts b/packages/commands/src/commands/managed-agent/deployment/list.ts index 7c898c9d3..0ec8c9cb8 100644 --- a/packages/commands/src/commands/managed-agent/deployment/list.ts +++ b/packages/commands/src/commands/managed-agent/deployment/list.ts @@ -23,7 +23,7 @@ export default defineCommand({ return fetchAllPages( async (page) => { const response = await listRemoteDeployments(runtime, { - provider: ctx.flags.provider, + provider: "bailian", agent_id: ctx.flags.agentId, status: ctx.flags.status, include_archived: ctx.flags.includeArchived, diff --git a/packages/commands/src/commands/managed-agent/deployment/run.ts b/packages/commands/src/commands/managed-agent/deployment/run.ts index c1d55736d..6f4af5a52 100644 --- a/packages/commands/src/commands/managed-agent/deployment/run.ts +++ b/packages/commands/src/commands/managed-agent/deployment/run.ts @@ -47,7 +47,7 @@ export default defineCommand({ const deploymentId = await resolveDeploymentTarget(runtime, ctx.flags); return { deploymentId, - run: await runRemoteDeployment(runtime, deploymentId, { provider: ctx.flags.provider }), + run: await runRemoteDeployment(runtime, deploymentId, { provider: "bailian" }), }; }), ); diff --git a/packages/commands/src/commands/managed-agent/deployment/runs/get.ts b/packages/commands/src/commands/managed-agent/deployment/runs/get.ts index 59ea3146b..2b52512e7 100644 --- a/packages/commands/src/commands/managed-agent/deployment/runs/get.ts +++ b/packages/commands/src/commands/managed-agent/deployment/runs/get.ts @@ -22,7 +22,7 @@ export default defineCommand({ 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 }); + return getRemoteDeploymentRun(runtime, ctx.flags.runId, { provider: "bailian" }); }), ); if (format === "json") emitResult(run, format); diff --git a/packages/commands/src/commands/managed-agent/deployment/runs/list.ts b/packages/commands/src/commands/managed-agent/deployment/runs/list.ts index face4cbf5..de9ac1367 100644 --- a/packages/commands/src/commands/managed-agent/deployment/runs/list.ts +++ b/packages/commands/src/commands/managed-agent/deployment/runs/list.ts @@ -26,7 +26,7 @@ export default defineCommand({ return fetchAllPages( async (page) => { const response = await listRemoteDeploymentRuns(runtime, ctx.flags.deploymentId, { - provider: ctx.flags.provider, + provider: "bailian", limit: ctx.flags.limit, page, }); diff --git a/packages/commands/src/commands/managed-agent/deployment/search.ts b/packages/commands/src/commands/managed-agent/deployment/search.ts index d7c286148..d25e9549e 100644 --- a/packages/commands/src/commands/managed-agent/deployment/search.ts +++ b/packages/commands/src/commands/managed-agent/deployment/search.ts @@ -32,7 +32,7 @@ export default defineCommand({ return fetchAllPages( async (page) => { const response = await listRemoteDeployments(runtime, { - provider: ctx.flags.provider, + provider: "bailian", keyword: ctx.flags.query, agent_id: ctx.flags.agentId, status: ctx.flags.status, diff --git a/packages/commands/src/commands/managed-agent/destroy.ts b/packages/commands/src/commands/managed-agent/destroy.ts index 7cafc4469..87eb8b5ee 100644 --- a/packages/commands/src/commands/managed-agent/destroy.ts +++ b/packages/commands/src/commands/managed-agent/destroy.ts @@ -68,7 +68,16 @@ export default defineCommand({ const planned = await withAgentErrors(() => withStdoutProtected(async () => { const runtime = await buildAgentRuntime(ctx, file); - return planDestroyProjectContext(runtime); + const planned = planDestroyProjectContext(runtime); + return { + ...planned, + resources: planned.resources.filter( + (resource) => resource.address.provider === "bailian", + ), + defaultMemoryStores: (planned.defaultMemoryStores ?? []).filter( + (memoryStore) => memoryStore.provider === "bailian", + ), + }; }), ); diff --git a/packages/commands/src/commands/managed-agent/environment/create.ts b/packages/commands/src/commands/managed-agent/environment/create.ts index 813890aff..b655d49ea 100644 --- a/packages/commands/src/commands/managed-agent/environment/create.ts +++ b/packages/commands/src/commands/managed-agent/environment/create.ts @@ -37,7 +37,6 @@ const FLAGS = { cargo: packageFlag("Cargo"), gem: packageFlag("Ruby gem"), go: packageFlag("Go"), - provider: providerFlag(), file: fileFlag(), yes: yesFlag(), } satisfies FlagsDef; @@ -49,7 +48,7 @@ export default defineCommand({ }, auth: "apiKey", usageArgs: - "--name <name> [--description <text>] [--metadata <key=value>...] [--apt <package>...] [--pip <package>...] [--npm <package>...] [--cargo <package>...] [--gem <package>...] [--go <package>...] [--provider <name>] [--file <path>] [--yes]", + "--name <name> [--description <text>] [--metadata <key=value>...] [--apt <package>...] [--pip <package>...] [--npm <package>...] [--cargo <package>...] [--gem <package>...] [--go <package>...] [--file <path>] [--yes]", flags: FLAGS, exampleArgs: [ "--name Development", @@ -67,11 +66,7 @@ export default defineCommand({ ], validate: (flags) => (!flags.name.trim() ? "--name must not be empty." : undefined), async run(ctx) { - const project = await loadScopedCreateProject( - ctx, - ctx.flags.file ?? "agents.yaml", - ctx.flags.provider, - ); + const project = await loadScopedCreateProject(ctx, ctx.flags.file ?? "agents.yaml"); const packages = Object.fromEntries( ["apt", "pip", "npm", "cargo", "gem", "go"] .map((manager) => [manager, ctx.flags[manager as keyof typeof ctx.flags]]) @@ -124,17 +119,6 @@ function packageFlag(manager: string) { }; } -function providerFlag() { - return { - type: "string" as const, - valueHint: "<name>", - description: { - "en-US": "Target provider; inferred when unambiguous", - "zh-CN": "目标 Provider;可唯一确定时自动推断", - }, - }; -} - function fileFlag() { return { type: "string" as const, diff --git a/packages/commands/src/commands/managed-agent/environment/get.ts b/packages/commands/src/commands/managed-agent/environment/get.ts index 53de6a98f..67898770d 100644 --- a/packages/commands/src/commands/managed-agent/environment/get.ts +++ b/packages/commands/src/commands/managed-agent/environment/get.ts @@ -23,7 +23,7 @@ export default defineCommand({ withStdoutProtected(async () => { const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml"); return getRemoteEnvironment(runtime, ctx.flags.environmentId, { - provider: ctx.flags.provider, + provider: "bailian", }); }), ); diff --git a/packages/commands/src/commands/managed-agent/environment/list.ts b/packages/commands/src/commands/managed-agent/environment/list.ts index b143f7a5d..ebe2d2538 100644 --- a/packages/commands/src/commands/managed-agent/environment/list.ts +++ b/packages/commands/src/commands/managed-agent/environment/list.ts @@ -23,7 +23,7 @@ export default defineCommand({ return fetchAllPages( async (page) => { const response = await listRemoteEnvironments(runtime, { - provider: ctx.flags.provider, + provider: "bailian", limit: ctx.flags.limit, page, include_archived: ctx.flags.includeArchived, diff --git a/packages/commands/src/commands/managed-agent/environment/search.ts b/packages/commands/src/commands/managed-agent/environment/search.ts index aa1a7fedc..0ce32de61 100644 --- a/packages/commands/src/commands/managed-agent/environment/search.ts +++ b/packages/commands/src/commands/managed-agent/environment/search.ts @@ -30,7 +30,7 @@ export default defineCommand({ return searchCursorPages( async (page) => { const response = await listRemoteEnvironments(runtime, { - provider: ctx.flags.provider, + provider: "bailian", limit: ctx.flags.limit ?? 100, page, include_archived: ctx.flags.includeArchived, diff --git a/packages/commands/src/commands/managed-agent/file.ts b/packages/commands/src/commands/managed-agent/file.ts index 913c35d71..df4a93573 100644 --- a/packages/commands/src/commands/managed-agent/file.ts +++ b/packages/commands/src/commands/managed-agent/file.ts @@ -60,11 +60,6 @@ const UPLOAD_FLAGS = { 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 }; @@ -106,9 +101,9 @@ function fileRows(files: ProviderFileInfo[]): string[][] { 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>]", + usageArgs: "--path <path> [--filename <name>] [--mime-type <type>]", flags: UPLOAD_FLAGS, - exampleArgs: ["--path ./report.pdf", "--path ./data.json --purpose assistants"], + exampleArgs: ["--path ./report.pdf"], notes: CREDENTIALS_NOTE, async run(ctx) { const format = detectOutputFormat(ctx.settings.output); @@ -118,7 +113,6 @@ export const managedAgentFileUpload = defineCommand({ 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, ); @@ -129,9 +123,8 @@ export const managedAgentFileUpload = defineCommand({ 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, + provider: "bailian", mimeType: ctx.flags.mimeType ?? inferMimeType(ctx.flags.path), - purpose: ctx.flags.purpose, }); }), ); @@ -156,7 +149,7 @@ export const managedAgentFileList = defineCommand({ return fetchAllPages( async (page) => { const response = await listRemoteFiles(runtime, { - provider: ctx.flags.provider, + provider: "bailian", scope_id: ctx.flags.scopeId, limit: ctx.flags.limit, page, @@ -197,7 +190,7 @@ export const managedAgentFileGet = defineCommand({ 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 }); + return getFileInfo(runtime, ctx.flags.fileId, { provider: "bailian" }); }), ); if (format === "json") emitResult(file, format); @@ -228,7 +221,7 @@ export const managedAgentFileSearch = defineCommand({ return searchCursorPages( async (page) => { const response = await listRemoteFiles(runtime, { - provider: ctx.flags.provider, + provider: "bailian", scope_id: ctx.flags.scopeId, limit: ctx.flags.limit ?? 100, page, @@ -281,7 +274,7 @@ export const managedAgentFileDownload = defineCommand({ 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 }); + return downloadRemoteFile(runtime, ctx.flags.fileId, { provider: "bailian" }); }), ); const outputFile = await writeOutputFile(ctx.flags.outputFile, content, ctx.flags.force); @@ -314,7 +307,7 @@ export const managedAgentFileDelete = defineCommand({ await withAgentErrors(() => withStdoutProtected(async () => { const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml"); - await deleteFile(runtime, ctx.flags.fileId, { provider: ctx.flags.provider }); + await deleteFile(runtime, ctx.flags.fileId, { provider: "bailian" }); }), ); if (format === "json") emitResult({ deleted: ctx.flags.fileId }, format); diff --git a/packages/commands/src/commands/managed-agent/init.ts b/packages/commands/src/commands/managed-agent/init.ts index ac3c83caa..4305b1e3e 100644 --- a/packages/commands/src/commands/managed-agent/init.ts +++ b/packages/commands/src/commands/managed-agent/init.ts @@ -15,45 +15,17 @@ agents.state.json .env `; -const PROVIDERS = ["bailian", "claude", "qoder", "ark", "all"] as const; - -const PROVIDER_BLOCKS: Record<string, string> = { - bailian: ` bailian:\n # bl auth login --api-key <key> sets DASHSCOPE_API_KEY; --base-url <url> sets BAILIAN_BASE_URL\n api_key: \${DASHSCOPE_API_KEY}\n base_url: \${BAILIAN_BASE_URL}`, - claude: ` claude:\n api_key: \${ANTHROPIC_API_KEY}`, - qoder: ` qoder:\n api_key: \${QODER_PAT}\n gateway: "https://api.qoder.com/api/v1/cloud"`, - ark: ` ark:\n api_key: \${ARK_API_KEY}`, -}; - -const SINGLE_MODEL: Record<string, string> = { - bailian: ` model: qwen3.8-max`, - claude: ` model: claude-sonnet-4-6`, - qoder: ` model: ultimate`, - ark: ` model: doubao-seed-2-1-pro-260628`, -}; - -function buildTemplate(options: { provider: string; agentName: string }): string { - const providerBlock = - options.provider === "all" - ? `${PROVIDER_BLOCKS.bailian}\n${PROVIDER_BLOCKS.claude}\n${PROVIDER_BLOCKS.qoder}\n${PROVIDER_BLOCKS.ark}` - : PROVIDER_BLOCKS[options.provider]!; - - const modelBlock = - options.provider === "all" - ? ` model:\n bailian: qwen3.8-max\n claude: claude-sonnet-4-6\n qoder: ultimate\n ark: doubao-seed-2-1-pro-260628` - : SINGLE_MODEL[options.provider]!; - - const toolBlock = - options.provider === "bailian" - ? "[bash, read, glob, grep]" - : "[read, glob, grep, web_search, web_fetch]"; - +function buildTemplate(options: { agentName: string }): string { return `version: "1" providers: -${providerBlock} + bailian: + # bl auth login --api-key <key> sets DASHSCOPE_API_KEY; --base-url <url> sets BAILIAN_BASE_URL + api_key: \${DASHSCOPE_API_KEY} + base_url: \${BAILIAN_BASE_URL} defaults: - provider: ${options.provider === "all" ? "all" : options.provider} + provider: bailian environments: dev: @@ -65,25 +37,16 @@ environments: agents: ${options.agentName}: description: "General-purpose assistant" -${modelBlock} + model: qwen3.8-max instructions: | You are a helpful assistant. environment: dev tools: - builtin: ${toolBlock} + builtin: [bash, read, glob, grep] `; } const INIT_FLAGS = { - provider: { - type: "string", - valueHint: "<name>", - description: { - "en-US": "Provider: bailian, claude, qoder, ark, all (default: bailian)", - "zh-CN": "Provider:bailian、claude、qoder、ark、all(默认:bailian)", - }, - choices: PROVIDERS, - }, agentName: { type: "string", valueHint: "<name>", @@ -112,13 +75,12 @@ export default defineCommand({ "zh-CN": "创建新的 agents.yaml 模板", }, auth: "none", - usageArgs: "[--provider <name>] [--agent-name <name>] [--file <path>] [--force]", + usageArgs: "[--agent-name <name>] [--file <path>] [--force]", flags: INIT_FLAGS, - exampleArgs: ["", "--provider bailian --agent-name assistant", "--provider all"], + exampleArgs: ["", "--agent-name assistant"], async run(ctx) { const { settings, flags } = ctx; const format = detectOutputFormat(settings.output); - const provider = flags.provider ?? "bailian"; const agentName = flags.agentName ?? "assistant"; const file = flags.file ?? "agents.yaml"; @@ -141,7 +103,7 @@ export default defineCommand({ emitResult( { would_create: file, - provider, + provider: "bailian", agent: agentName, would_update_gitignore: wouldUpdateGitignore, }, @@ -150,7 +112,7 @@ export default defineCommand({ return; } - const template = buildTemplate({ provider, agentName }); + const template = buildTemplate({ agentName }); await writeFile(file, template, "utf8"); if (existsSync(gitignorePath)) { @@ -163,14 +125,12 @@ export default defineCommand({ } if (format === "json") { - emitResult({ created: file, provider, agent: agentName }, format); + emitResult({ created: file, provider: "bailian", agent: agentName }, format); } else { emitBare(`Created ${file}`); - if (provider === "bailian" || provider === "all") { - emitBare( - "Credentials: run `bl auth login --api-key <key> --base-url <url>`, or set DASHSCOPE_API_KEY / BAILIAN_BASE_URL.", - ); - } + emitBare( + "Credentials: run `bl auth login --api-key <key> --base-url <url>`, or set DASHSCOPE_API_KEY / BAILIAN_BASE_URL.", + ); emitBare("Next: edit agents.yaml, then run `bl managed-agent plan`."); } }, diff --git a/packages/commands/src/commands/managed-agent/plan.ts b/packages/commands/src/commands/managed-agent/plan.ts index 72130baa3..b08039894 100644 --- a/packages/commands/src/commands/managed-agent/plan.ts +++ b/packages/commands/src/commands/managed-agent/plan.ts @@ -8,11 +8,7 @@ import { import { emitBare, emitResult } from "bailian-cli-runtime"; import { planProjectContext } from "@openagentpack/sdk"; import { formatResourceLabel } from "./_engine/address-utils.ts"; -import { - assertProviderConfigured, - buildAgentRuntime, - CREDENTIALS_NOTE, -} from "./_engine/config-loader.ts"; +import { buildAgentRuntime, CREDENTIALS_NOTE } from "./_engine/config-loader.ts"; import { withStdoutProtected } from "./_engine/console-capture.ts"; import { formatAgentDiagnosticFailure, withAgentErrors } from "./_engine/errors.ts"; import { renderAgentFeedback } from "./_engine/feedback.ts"; @@ -26,14 +22,6 @@ const PLAN_FLAGS = { "zh-CN": "配置文件路径(默认:agents.yaml)", }, }, - provider: { - type: "string", - valueHint: "<name>", - description: { - "en-US": "Target provider (default: all configured)", - "zh-CN": "目标 Provider(默认:全部已配置项)", - }, - }, noRefresh: { type: "switch", description: { @@ -56,9 +44,9 @@ export default defineCommand({ "zh-CN": "显示将应用到 Agent 基础设施的变更", }, auth: "apiKey", - usageArgs: "[--file <path>] [--provider <name>] [--no-refresh] [--refresh-only]", + usageArgs: "[--file <path>] [--no-refresh] [--refresh-only]", flags: PLAN_FLAGS, - exampleArgs: ["", "--provider bailian", "--no-refresh"], + exampleArgs: ["", "--no-refresh"], notes: [ ...CREDENTIALS_NOTE, { @@ -83,9 +71,8 @@ export default defineCommand({ const runtime = await buildAgentRuntime(ctx, file, { credentials: offline ? "none" : "all", }); - assertProviderConfigured(runtime, flags.provider); return planProjectContext(runtime, { - provider: flags.provider, + provider: "bailian", refresh: !offline, quiet: format === "json", onFeedback: format === "json" ? undefined : renderAgentFeedback, diff --git a/packages/commands/src/commands/managed-agent/session-create.ts b/packages/commands/src/commands/managed-agent/session-create.ts index 3efba5c25..ce921cd80 100644 --- a/packages/commands/src/commands/managed-agent/session-create.ts +++ b/packages/commands/src/commands/managed-agent/session-create.ts @@ -49,14 +49,6 @@ const SESSION_CREATE_FLAGS = { valueHint: "<title>", description: { "en-US": "Session title", "zh-CN": "Session 标题" }, }, - provider: { - type: "string", - valueHint: "<name>", - description: { - "en-US": "Target provider (multi-provider agents)", - "zh-CN": "目标 Provider(多 Provider Agent)", - }, - }, } satisfies FlagsDef; export default defineCommand({ @@ -83,7 +75,7 @@ export default defineCommand({ { would_create_session: { agent: flags.agent ?? "auto", - provider: flags.provider ?? "auto", + provider: "bailian", environment: flags.environment, vault: flags.vault, memory_stores: parseMemoryStores(flags.memoryStores), @@ -101,7 +93,7 @@ export default defineCommand({ const runtime = await buildAgentRuntime(ctx, file); return createSessionForAgent(runtime, { agent: flags.agent, - provider: flags.provider, + provider: "bailian", environment: flags.environment, vault: flags.vault, memoryStores: parseMemoryStores(flags.memoryStores), diff --git a/packages/commands/src/commands/managed-agent/session-debug.ts b/packages/commands/src/commands/managed-agent/session-debug.ts index fc6d9c143..c41af4d13 100644 --- a/packages/commands/src/commands/managed-agent/session-debug.ts +++ b/packages/commands/src/commands/managed-agent/session-debug.ts @@ -107,11 +107,42 @@ async function collectSessionDiagnostics( }; } -const SENSITIVE_KEY = - /(api[_-]?key|access[_-]?key|secret|token|authorization|credential|password)/i; +const SENSITIVE_MARKER = String.raw`(?:api[_-]?key|access[_-]?key|private[_-]?key|secret|token|authorization|credential|password|signature|signed[_-]?url)`; +const SENSITIVE_KEY = new RegExp(`${SENSITIVE_MARKER}|tool[_-]?input`, "i"); +const SENSITIVE_URL_PARAMETER = new RegExp( + `([?&][^=&#\\s]*(?:${SENSITIVE_MARKER})[^=&#\\s]*=)[^&#\\s"'<>),}\\]]*`, + "gi", +); +const SENSITIVE_DOUBLE_QUOTED_VALUE = new RegExp( + `("[^"]*(?:${SENSITIVE_MARKER})[^"]*"\\s*:\\s*")[^"]*(")`, + "gi", +); +const SENSITIVE_SINGLE_QUOTED_VALUE = new RegExp( + `('[^']*(?:${SENSITIVE_MARKER})[^']*'\\s*:\\s*')[^']*(')`, + "gi", +); +const SENSITIVE_ASSIGNMENT = new RegExp( + `(\\b[^\\s=:,;]*(?:${SENSITIVE_MARKER})[^\\s=:,;]*\\s*=\\s*)(?!\\[REDACTED\\])(?:"[^"]*"|'[^']*'|[^\\s,;&]+)`, + "gi", +); +const AUTHORIZATION_VALUE = /\b(authorization\s*:\s*)(?:bearer\s+)?[^\s,;]+/gi; +const BEARER_TOKEN = /\b(bearer\s+)[A-Za-z0-9._~+/=-]+/gi; +const API_KEY_LITERAL = /\bsk-[A-Za-z0-9_-]{8,}\b/g; + +function redactSensitiveText(value: string): string { + return value + .replace(SENSITIVE_URL_PARAMETER, "$1[REDACTED]") + .replace(SENSITIVE_DOUBLE_QUOTED_VALUE, "$1[REDACTED]$2") + .replace(SENSITIVE_SINGLE_QUOTED_VALUE, "$1[REDACTED]$2") + .replace(SENSITIVE_ASSIGNMENT, "$1[REDACTED]") + .replace(AUTHORIZATION_VALUE, "$1[REDACTED]") + .replace(BEARER_TOKEN, "$1[REDACTED]") + .replace(API_KEY_LITERAL, "[REDACTED]"); +} export function redactSensitiveValues(value: unknown): unknown { if (Array.isArray(value)) return value.map(redactSensitiveValues); + if (typeof value === "string") return redactSensitiveText(value); if (!value || typeof value !== "object") return value; return Object.fromEntries( Object.entries(value).map(([key, entry]) => [ @@ -233,7 +264,7 @@ export const managedAgentSessionDebug = defineCommand({ 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); + return collectSessionDiagnostics(runtime, ctx.flags.sessionId, "bailian"); }), ); const redacted = redactSensitiveValues(bundle); @@ -278,7 +309,7 @@ export const managedAgentSessionExport = defineCommand({ 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); + return collectSessionDiagnostics(runtime, ctx.flags.sessionId, "bailian"); }), ); const manifest = { diff --git a/packages/commands/src/commands/managed-agent/session-delete.ts b/packages/commands/src/commands/managed-agent/session-delete.ts index fc56363ba..f7f3f147d 100644 --- a/packages/commands/src/commands/managed-agent/session-delete.ts +++ b/packages/commands/src/commands/managed-agent/session-delete.ts @@ -20,11 +20,6 @@ const SESSION_DELETE_FLAGS = { "zh-CN": "配置文件路径(默认:agents.yaml)", }, }, - provider: { - type: "string", - valueHint: "<name>", - description: { "en-US": "Target provider", "zh-CN": "目标 Provider" }, - }, } satisfies FlagsDef; export default defineCommand({ @@ -37,7 +32,7 @@ export default defineCommand({ "zh-CN": "该操作会删除指定的远端托管 Agent Session。", }, }, - usageArgs: "--session-id <id> [--provider <name>] [--file <path>]", + usageArgs: "--session-id <id> [--file <path>]", flags: SESSION_DELETE_FLAGS, exampleArgs: ["--session-id sess_abc123 --yes"], notes: CREDENTIALS_NOTE, @@ -50,7 +45,7 @@ export default defineCommand({ emitResult( { would_delete_session: flags.sessionId, - provider: flags.provider ?? "auto", + provider: "bailian", config_file: file, }, format, @@ -61,7 +56,7 @@ export default defineCommand({ await withAgentErrors(() => withStdoutProtected(async () => { const runtime = await buildAgentRuntime(ctx, file); - await deleteSession(runtime, flags.sessionId, flags.provider); + await deleteSession(runtime, flags.sessionId, "bailian"); }), ); diff --git a/packages/commands/src/commands/managed-agent/session-event.ts b/packages/commands/src/commands/managed-agent/session-event.ts index 21cd25544..111b3fb16 100644 --- a/packages/commands/src/commands/managed-agent/session-event.ts +++ b/packages/commands/src/commands/managed-agent/session-event.ts @@ -3,6 +3,7 @@ import { isTerminalSessionStatus, listSessionEvents, sendRemoteSessionEvents, + SessionEventSchema, streamSessionEvents, } from "@openagentpack/sdk"; import { sanitizeSessionEvents } from "@openagentpack/sdk/session-events"; @@ -111,6 +112,16 @@ function normalizeEventInput(value: unknown): SessionEventInput[] { ExitCode.USAGE, ); } + for (const [index, entry] of values.entries()) { + const validation = SessionEventSchema.safeParse(entry); + if (validation.success) continue; + const issue = validation.error.issues[0]; + const path = issue?.path.length ? ` at ${issue.path.join(".")}` : ""; + throw new BailianError( + `Invalid event at index ${index}${path}: ${issue?.message ?? "does not match the Session event schema"}.`, + ExitCode.USAGE, + ); + } return values as SessionEventInput[]; } @@ -140,7 +151,7 @@ export const managedAgentSessionEventList = defineCommand({ return fetchAllPages( async (page) => { const response = await listSessionEvents(runtime, ctx.flags.sessionId, { - provider: ctx.flags.provider, + provider: "bailian", limit: ctx.flags.limit, page_token: page, order: ctx.flags.order, @@ -181,7 +192,7 @@ export const managedAgentSessionEventSend = defineCommand({ 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 \'{"type":"message","role":"user","content":[{"type":"text","text":"hello"}]}\'', "--session-id sess_abc --event @event.json", ], notes: CREDENTIALS_NOTE, @@ -196,7 +207,7 @@ export const managedAgentSessionEventSend = defineCommand({ withStdoutProtected(async () => { const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml"); return sendRemoteSessionEvents(runtime, ctx.flags.sessionId, events, { - provider: ctx.flags.provider, + provider: "bailian", }); }), ); @@ -230,7 +241,7 @@ export const managedAgentSessionEventStream = defineCommand({ 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, + provider: "bailian", after_id: ctx.flags.afterId, })) { events.push(event); diff --git a/packages/commands/src/commands/managed-agent/session-get.ts b/packages/commands/src/commands/managed-agent/session-get.ts index fcbb43a99..be6a8e19f 100644 --- a/packages/commands/src/commands/managed-agent/session-get.ts +++ b/packages/commands/src/commands/managed-agent/session-get.ts @@ -20,17 +20,12 @@ const SESSION_GET_FLAGS = { "zh-CN": "配置文件路径(默认:agents.yaml)", }, }, - provider: { - type: "string", - valueHint: "<name>", - description: { "en-US": "Target provider", "zh-CN": "目标 Provider" }, - }, } satisfies FlagsDef; export default defineCommand({ description: { "en-US": "Get details of a session", "zh-CN": "获取 Session 详情" }, auth: "apiKey", - usageArgs: "--session-id <id> [--provider <name>] [--file <path>]", + usageArgs: "--session-id <id> [--file <path>]", flags: SESSION_GET_FLAGS, exampleArgs: ["--session-id sess_abc123"], notes: CREDENTIALS_NOTE, @@ -42,7 +37,7 @@ export default defineCommand({ const session = await withAgentErrors(() => withStdoutProtected(async () => { const runtime = await buildAgentRuntime(ctx, file); - return getSession(runtime, flags.sessionId, flags.provider); + return getSession(runtime, flags.sessionId, "bailian"); }), ); diff --git a/packages/commands/src/commands/managed-agent/session-list.ts b/packages/commands/src/commands/managed-agent/session-list.ts index b5a311ac8..8be8fa1c3 100644 --- a/packages/commands/src/commands/managed-agent/session-list.ts +++ b/packages/commands/src/commands/managed-agent/session-list.ts @@ -58,11 +58,6 @@ const SESSION_LIST_FLAGS = { "zh-CN": "创建时间不晚于该 RFC 3339 时间戳", }, }, - provider: { - type: "string", - valueHint: "<name>", - description: { "en-US": "Target provider", "zh-CN": "目标 Provider" }, - }, } satisfies FlagsDef; export default defineCommand({ @@ -92,7 +87,7 @@ export default defineCommand({ async (page) => { const result = await listSessionSummaries(runtime, { agent: flags.agent, - provider: flags.provider, + provider: "bailian", filter: { page, limit: flags.limit, diff --git a/packages/commands/src/commands/managed-agent/session-management.ts b/packages/commands/src/commands/managed-agent/session-management.ts index 5750bf632..d7e6ce395 100644 --- a/packages/commands/src/commands/managed-agent/session-management.ts +++ b/packages/commands/src/commands/managed-agent/session-management.ts @@ -134,7 +134,7 @@ export const managedAgentSessionSearch = defineCommand({ async (page) => { const response = await listSessionSummaries(runtime, { agent: ctx.flags.agent, - provider: ctx.flags.provider, + provider: "bailian", filter: { page, limit: ctx.flags.limit ?? 100, @@ -201,7 +201,7 @@ export const managedAgentSessionUpdate = defineCommand({ withStdoutProtected(async () => { const runtime = await buildAgentRuntime(ctx, ctx.flags.file ?? "agents.yaml"); return updateRemoteSession(runtime, ctx.flags.sessionId, input, { - provider: ctx.flags.provider, + provider: "bailian", }); }), ); @@ -233,7 +233,7 @@ export const managedAgentSessionArchive = defineCommand({ 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 }); + return archiveRemoteSession(runtime, ctx.flags.sessionId, { provider: "bailian" }); }), ); if (format === "json") emitResult({ archived: ctx.flags.sessionId, session }, format); diff --git a/packages/commands/src/commands/managed-agent/session-run.ts b/packages/commands/src/commands/managed-agent/session-run.ts index c261d3d22..95bded48c 100644 --- a/packages/commands/src/commands/managed-agent/session-run.ts +++ b/packages/commands/src/commands/managed-agent/session-run.ts @@ -59,11 +59,6 @@ const SESSION_RUN_FLAGS = { valueHint: "<title>", description: { "en-US": "Session title", "zh-CN": "Session 标题" }, }, - provider: { - type: "string", - valueHint: "<name>", - description: { "en-US": "Target provider", "zh-CN": "目标 Provider" }, - }, noStream: { type: "switch", description: { @@ -108,7 +103,7 @@ export default defineCommand({ const runOptions = { agent: flags.agent, - provider: flags.provider, + provider: "bailian", environment: flags.environment, vault: flags.vault, memoryStores: parseMemoryStores(flags.memoryStores), @@ -121,7 +116,7 @@ export default defineCommand({ would_run: { prompt: flags.prompt, agent: flags.agent ?? "auto", - provider: flags.provider ?? "auto", + provider: "bailian", environment: flags.environment, vault: flags.vault, memory_stores: runOptions.memoryStores, diff --git a/packages/commands/src/commands/managed-agent/session-send.ts b/packages/commands/src/commands/managed-agent/session-send.ts index 9037bf39e..30afe8e59 100644 --- a/packages/commands/src/commands/managed-agent/session-send.ts +++ b/packages/commands/src/commands/managed-agent/session-send.ts @@ -27,11 +27,6 @@ const SESSION_SEND_FLAGS = { "zh-CN": "配置文件路径(默认:agents.yaml)", }, }, - provider: { - type: "string", - valueHint: "<name>", - description: { "en-US": "Target provider", "zh-CN": "目标 Provider" }, - }, noStream: { type: "switch", description: { @@ -68,7 +63,7 @@ export default defineCommand({ would_send: { session_id: flags.sessionId, message: flags.message, - provider: flags.provider ?? "auto", + provider: "bailian", mode: flags.noStream ? "polling" : "streaming", }, config_file: file, @@ -83,7 +78,7 @@ export default defineCommand({ const runtime = await buildAgentRuntime(ctx, file); if (flags.noStream) { const result = await sendSessionMessagePolling(runtime, flags.sessionId, flags.message, { - provider: flags.provider, + provider: "bailian", }); renderCollectedEvents(result, asJson, { session_id: flags.sessionId, @@ -94,7 +89,7 @@ export default defineCommand({ flags.sessionId, flags.message, { - provider: flags.provider, + provider: "bailian", }, ); await streamAndRenderEvents(events, asJson, { diff --git a/packages/commands/src/commands/managed-agent/skill/create.ts b/packages/commands/src/commands/managed-agent/skill/create.ts index 2ad512530..02b352aa6 100644 --- a/packages/commands/src/commands/managed-agent/skill/create.ts +++ b/packages/commands/src/commands/managed-agent/skill/create.ts @@ -19,14 +19,6 @@ const FLAGS = { "zh-CN": "本地 Skill 目录、ZIP 压缩包或单个 SKILL.md", }, }, - provider: { - type: "string", - valueHint: "<name>", - description: { - "en-US": "Target provider; inferred when unambiguous", - "zh-CN": "目标 Provider;可唯一确定时自动推断", - }, - }, file: { type: "string", valueHint: "<path>", @@ -50,7 +42,7 @@ export default defineCommand({ "zh-CN": "从本地来源声明并创建一个自定义托管 Agent Skill", }, auth: "apiKey", - usageArgs: "--source <directory|zip|SKILL.md> [--provider <name>] [--file <path>] [--yes]", + usageArgs: "--source <directory|zip|SKILL.md> [--file <path>] [--yes]", flags: FLAGS, exampleArgs: ["--source ./skills/code-review", "--source ./skill.zip --yes"], notes: [ @@ -65,11 +57,7 @@ export default defineCommand({ ], validate: (flags) => (!flags.source.trim() ? "--source must not be empty." : undefined), async run(ctx) { - const project = await loadScopedCreateProject( - ctx, - ctx.flags.file ?? "agents.yaml", - ctx.flags.provider, - ); + const project = await loadScopedCreateProject(ctx, ctx.flags.file ?? "agents.yaml"); const inspected = await inspectSkillSource(ctx.flags.source, { basePath: process.cwd() }); const source = relative(dirname(project.configPath), inspected.sourcePath).split(sep).join("/") || "."; diff --git a/packages/commands/src/commands/managed-agent/skill/download.ts b/packages/commands/src/commands/managed-agent/skill/download.ts index 109ab37b8..d752a6772 100644 --- a/packages/commands/src/commands/managed-agent/skill/download.ts +++ b/packages/commands/src/commands/managed-agent/skill/download.ts @@ -34,7 +34,7 @@ export default defineCommand({ 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, + provider: "bailian", }); }), ); diff --git a/packages/commands/src/commands/managed-agent/skill/get.ts b/packages/commands/src/commands/managed-agent/skill/get.ts index f8cd69f04..88f9b7c07 100644 --- a/packages/commands/src/commands/managed-agent/skill/get.ts +++ b/packages/commands/src/commands/managed-agent/skill/get.ts @@ -19,7 +19,7 @@ export default defineCommand({ 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 }); + return getRemoteSkill(runtime, ctx.flags.skillId, { provider: "bailian" }); }), ); if (format === "json") { diff --git a/packages/commands/src/commands/managed-agent/skill/list.ts b/packages/commands/src/commands/managed-agent/skill/list.ts index 3221ae624..0049f3069 100644 --- a/packages/commands/src/commands/managed-agent/skill/list.ts +++ b/packages/commands/src/commands/managed-agent/skill/list.ts @@ -1,5 +1,6 @@ import { listRemoteSkills } from "@openagentpack/sdk"; import { defineCommand, detectOutputFormat } from "bailian-cli-core"; +import { emitResult } from "bailian-cli-runtime"; import { emitCollection, validateLimitAndPageLimit } from "../_engine/api-helpers.ts"; import { buildAgentRuntime, CREDENTIALS_NOTE } from "../_engine/config-loader.ts"; import { withStdoutProtected } from "../_engine/console-capture.ts"; @@ -7,15 +8,27 @@ import { withAgentErrors } from "../_engine/errors.ts"; import { fetchAllPages } from "../_engine/pagination.ts"; import { SKILL_LIST_FLAGS, skillRows, type SkillSource } from "./_shared.ts"; +export function buildSkillListJsonResult<T>( + source: SkillSource, + result: { items: T[]; hasMore: boolean; nextPage?: string }, +) { + return { + source, + skills: result.items, + has_more: result.hasMore, + next_page: result.nextPage, + }; +} + async function listOneCatalog( runtime: Parameters<typeof listRemoteSkills>[0], source: Exclude<SkillSource, "all">, - options: { provider?: string; limit?: number; page?: string; all?: boolean }, + options: { limit?: number; page?: string; all?: boolean }, ) { return fetchAllPages( async (page) => { const response = await listRemoteSkills(runtime, { - provider: options.provider, + provider: "bailian", source, limit: options.limit, page, @@ -55,7 +68,6 @@ export default defineCommand({ 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, @@ -63,12 +75,10 @@ export default defineCommand({ } 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, }), @@ -80,6 +90,10 @@ export default defineCommand({ }; }), ); + if (format === "json") { + emitResult(buildSkillListJsonResult(source, result), format); + return; + } emitCollection({ format, key: "skills", diff --git a/packages/commands/src/commands/managed-agent/skill/search.ts b/packages/commands/src/commands/managed-agent/skill/search.ts index c11158080..0359d4027 100644 --- a/packages/commands/src/commands/managed-agent/skill/search.ts +++ b/packages/commands/src/commands/managed-agent/skill/search.ts @@ -29,7 +29,7 @@ export default defineCommand({ searchCursorPages( async (page) => { const response = await listRemoteSkills(runtime, { - provider: ctx.flags.provider, + provider: "bailian", source: catalog, limit: ctx.flags.limit ?? 100, page, diff --git a/packages/commands/src/commands/managed-agent/skill/versions.ts b/packages/commands/src/commands/managed-agent/skill/versions.ts index bd11d0e5f..00b1c1c19 100644 --- a/packages/commands/src/commands/managed-agent/skill/versions.ts +++ b/packages/commands/src/commands/managed-agent/skill/versions.ts @@ -26,7 +26,7 @@ export default defineCommand({ return fetchAllPages( async (page) => { const response = await listRemoteSkillVersions(runtime, ctx.flags.skillId, { - provider: ctx.flags.provider, + provider: "bailian", limit: ctx.flags.limit, page, }); diff --git a/packages/commands/src/commands/managed-agent/state-import.ts b/packages/commands/src/commands/managed-agent/state-import.ts index 91841c57d..5754238ea 100644 --- a/packages/commands/src/commands/managed-agent/state-import.ts +++ b/packages/commands/src/commands/managed-agent/state-import.ts @@ -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 { importResource, parseStateAddress } from "@openagentpack/sdk"; import { buildAgentRuntime, CREDENTIALS_NOTE } from "./_engine/config-loader.ts"; @@ -8,7 +14,7 @@ import { withAgentErrors } from "./_engine/errors.ts"; const STATE_IMPORT_FLAGS = { address: { type: "string", - valueHint: "<provider.type.name>", + valueHint: "<bailian.type.name>", description: { "en-US": "Resource state address (required)", "zh-CN": "资源 State 地址(必填)", @@ -42,6 +48,17 @@ const STATE_IMPORT_FLAGS = { }, } satisfies FlagsDef; +function parseBailianStateAddress(address: string) { + const parsed = parseStateAddress(address, { requireProvider: true }); + if (parsed.provider !== "bailian") { + throw new BailianError( + `bl managed-agent can import only Bailian resources; address provider was '${parsed.provider}'. / bl managed-agent 只能导入百炼资源;地址中的 Provider 为 '${parsed.provider}'。`, + ExitCode.USAGE, + ); + } + return parsed; +} + export default defineCommand({ description: { "en-US": "Import an existing remote resource into agents state", @@ -49,7 +66,7 @@ export default defineCommand({ }, auth: "apiKey", usageArgs: - "--address <provider.type.name> --remote-id <id> [--resource-version <n>] [--file <path>]", + "--address <bailian.type.name> --remote-id <id> [--resource-version <n>] [--file <path>]", flags: STATE_IMPORT_FLAGS, exampleArgs: ["--address bailian.agent.assistant --remote-id agent-abc123"], notes: CREDENTIALS_NOTE, @@ -61,7 +78,7 @@ export default defineCommand({ if (settings.dryRun) { // Validate the address shape locally so dry-run still catches usage errors. await withAgentErrors(async () => { - parseStateAddress(flags.address, { requireProvider: true }); + parseBailianStateAddress(flags.address); }); emitResult( { @@ -78,9 +95,7 @@ export default defineCommand({ await withAgentErrors(() => withStdoutProtected(async () => { // Parse first so a malformed address fails fast, before any config I/O. - const parsed = parseStateAddress(flags.address, { - requireProvider: true, - }); + const parsed = parseBailianStateAddress(flags.address); const runtime = await buildAgentRuntime(ctx, file); await importResource(runtime, parsed, flags.remoteId, { resourceVersion: flags.resourceVersion, diff --git a/packages/commands/src/commands/managed-agent/vault/create.ts b/packages/commands/src/commands/managed-agent/vault/create.ts index bb1133a5b..d6c76b5d7 100644 --- a/packages/commands/src/commands/managed-agent/vault/create.ts +++ b/packages/commands/src/commands/managed-agent/vault/create.ts @@ -26,14 +26,6 @@ const FLAGS = { "zh-CN": "Metadata 条目(可重复)", }, }, - provider: { - type: "string", - valueHint: "<name>", - description: { - "en-US": "Target provider; inferred when unambiguous", - "zh-CN": "目标 Provider;可唯一确定时自动推断", - }, - }, file: { type: "string", valueHint: "<path>", @@ -57,8 +49,7 @@ export default defineCommand({ "zh-CN": "通过定向 YAML Apply 声明并创建一个空的托管 Agent Vault", }, auth: "apiKey", - usageArgs: - "--name <name> [--metadata <key=value>...] [--provider <name>] [--file <path>] [--yes]", + usageArgs: "--name <name> [--metadata <key=value>...] [--file <path>] [--yes]", flags: FLAGS, exampleArgs: ["--name Production", "--name Production --metadata owner=platform --yes"], notes: [ @@ -71,11 +62,7 @@ export default defineCommand({ ], validate: (flags) => (!flags.name.trim() ? "--name must not be empty." : undefined), async run(ctx) { - const project = await loadScopedCreateProject( - ctx, - ctx.flags.file ?? "agents.yaml", - ctx.flags.provider, - ); + const project = await loadScopedCreateProject(ctx, ctx.flags.file ?? "agents.yaml"); const rawDeclaration: Record<string, unknown> = { display_name: ctx.flags.name.trim(), provider: project.provider, diff --git a/packages/commands/src/commands/managed-agent/vault/credential/create.ts b/packages/commands/src/commands/managed-agent/vault/credential/create.ts index e028a5864..c8b9c8fca 100644 --- a/packages/commands/src/commands/managed-agent/vault/credential/create.ts +++ b/packages/commands/src/commands/managed-agent/vault/credential/create.ts @@ -1,5 +1,6 @@ import { dirname } from "node:path"; import { + bootstrapRuntimeCredentialsSync, createVaultCredentialWithStateBackend, planVaultCredentialCreateWithStateBackend, type ResolvedProjectConfig, @@ -69,14 +70,6 @@ const FLAGS = { "zh-CN": "Metadata 条目(可重复)", }, }, - provider: { - type: "string", - valueHint: "<name>", - description: { - "en-US": "Target provider; inferred when unambiguous", - "zh-CN": "目标 Provider;可唯一确定时自动推断", - }, - }, file: { type: "string", valueHint: "<path>", @@ -101,7 +94,7 @@ export default defineCommand({ }, auth: "apiKey", usageArgs: - "--vault <yaml-key> --name <name> --secret-name <name> --secret-env <env-name> [--metadata <key=value>...] [--provider <name>] [--file <path>] [--yes]", + "--vault <yaml-key> --name <name> --secret-name <name> --secret-env <env-name> [--metadata <key=value>...] [--file <path>] [--yes]", flags: FLAGS, exampleArgs: [ "--vault production --name api-token --secret-name API_TOKEN --secret-env PROD_API_TOKEN", @@ -134,10 +127,12 @@ export default defineCommand({ }, async run(ctx) { const format = detectOutputFormat(ctx.settings.output); - const project = await loadScopedCreateProject( - ctx, - ctx.flags.file ?? "agents.yaml", - ctx.flags.provider, + bootstrapRuntimeCredentialsSync(); + const secretValue = process.env[ctx.flags.secretEnv]; + const project = await loadScopedCreateProject(ctx, ctx.flags.file ?? "agents.yaml").finally( + () => { + delete process.env[ctx.flags.secretEnv]; + }, ); const vault = project.config.vaults?.[ctx.flags.vault]; if (!vault) { @@ -146,7 +141,6 @@ export default defineCommand({ ExitCode.USAGE, ); } - const secretValue = process.env[ctx.flags.secretEnv]; if (ctx.flags.yes && !ctx.settings.dryRun && !secretValue) { throw new BailianError( `Environment variable '${ctx.flags.secretEnv}' is not set or is empty.`, diff --git a/packages/commands/src/commands/managed-agent/vault/get.ts b/packages/commands/src/commands/managed-agent/vault/get.ts index a4763187e..f9f577177 100644 --- a/packages/commands/src/commands/managed-agent/vault/get.ts +++ b/packages/commands/src/commands/managed-agent/vault/get.ts @@ -19,7 +19,7 @@ export default defineCommand({ 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 }); + return getRemoteVault(runtime, ctx.flags.vaultId, { provider: "bailian" }); }), ); if (format === "json") { diff --git a/packages/commands/src/commands/managed-agent/vault/list.ts b/packages/commands/src/commands/managed-agent/vault/list.ts index 95c237ef3..dd3f8a4f0 100644 --- a/packages/commands/src/commands/managed-agent/vault/list.ts +++ b/packages/commands/src/commands/managed-agent/vault/list.ts @@ -23,7 +23,7 @@ export default defineCommand({ return fetchAllPages( async (page) => { const response = await listRemoteVaults(runtime, { - provider: ctx.flags.provider, + provider: "bailian", limit: ctx.flags.limit, page, include_archived: ctx.flags.includeArchived, diff --git a/packages/commands/src/commands/managed-agent/vault/search.ts b/packages/commands/src/commands/managed-agent/vault/search.ts index 2ade13496..7055759a8 100644 --- a/packages/commands/src/commands/managed-agent/vault/search.ts +++ b/packages/commands/src/commands/managed-agent/vault/search.ts @@ -27,7 +27,7 @@ export default defineCommand({ return searchCursorPages( async (page) => { const response = await listRemoteVaults(runtime, { - provider: ctx.flags.provider, + provider: "bailian", limit: ctx.flags.limit ?? 100, page, include_archived: ctx.flags.includeArchived, diff --git a/packages/commands/tests/e2e/fixtures/managed-agent/agents.state.json b/packages/commands/tests/e2e/fixtures/managed-agent/agents.state.json index ae974fbfc..d02ef58d6 100644 --- a/packages/commands/tests/e2e/fixtures/managed-agent/agents.state.json +++ b/packages/commands/tests/e2e/fixtures/managed-agent/agents.state.json @@ -1,83 +1,3 @@ { - "resources": [ - { - "address": { - "type": "environment", - "name": "dev", - "provider": "bailian" - }, - "remote_id": "env_ZjQxMzg5NDkwYjcxNGJmMj", - "content_hash": "d7744af584d39dad40371b0abeefd15f595f9b252401d56b1695eff11c34745a", - "desired_hash": "d7744af584d39dad40371b0abeefd15f595f9b252401d56b1695eff11c34745a", - "desired_comparable_hash": "df4636ee00f9b91880ffb65051bd58bc840be71f9bb13d4e64129b5e46dcddfc", - "desired_readiness_baseline": { - "operational_hash": "d7744af584d39dad40371b0abeefd15f595f9b252401d56b1695eff11c34745a", - "description_hash": "74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b", - "metadata_hash": "74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b" - }, - "remote_hash": "df4636ee00f9b91880ffb65051bd58bc840be71f9bb13d4e64129b5e46dcddfc", - "remote_snapshot": { - "description": null, - "config": { - "type": "cloud", - "networking": { - "type": "unrestricted" - } - } - }, - "drift_paths": [], - "drift_status": "in_sync" - }, - { - "address": { - "type": "agent", - "name": "assistant", - "provider": "bailian" - }, - "remote_id": "agent_01M1E06GQ4KR9QJFX9MQJMBA4H", - "version": 1, - "content_hash": "c173a2dafaf0039b9ddfb8b342f58876b1c9510d12debe765e82b586dcd30168", - "desired_hash": "c173a2dafaf0039b9ddfb8b342f58876b1c9510d12debe765e82b586dcd30168", - "desired_comparable_hash": "429c090d7f12c014aa2a15ac0ec4a8f89555694b4acc36049b66bb2c1d7d5d54", - "desired_readiness_baseline": { - "operational_hash": "3f3cfd0953464b338b23b1e753c6698ef2567133ee6eded15b500d8df717f9ec", - "description_hash": "d97348b869d0aef8d26e4f06bba719d6f57864d7c1eef68c433a11aaa6e2b244", - "metadata_hash": "74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b" - }, - "remote_hash": "429c090d7f12c014aa2a15ac0ec4a8f89555694b4acc36049b66bb2c1d7d5d54", - "remote_snapshot": { - "description": "E2E auth-chain fixture", - "model": "qwen3.7-max", - "instructions": "You are a helpful assistant.\n" - }, - "drift_paths": [], - "drift_status": "in_sync" - }, - { - "address": { - "type": "agent", - "name": "test-create", - "provider": "bailian" - }, - "remote_id": "agent_01M1EXETGHNV6QBGQZ4PZRAEQ5", - "version": 1, - "content_hash": "5084a95feef59412ac07d99a500bbfca801c0a86143706d14bb209c4ccfda073", - "desired_hash": "5084a95feef59412ac07d99a500bbfca801c0a86143706d14bb209c4ccfda073", - "desired_comparable_hash": "5f381045d839d5ef97751407123a3ad27e15a00976d198cfe2359ce14ac127a0", - "desired_readiness_baseline": { - "operational_hash": "5a16d288fb20b4ebc5d9f5c1b3d6d6eae6d072bcdcf3d069161946c709d09a69", - "description_hash": "74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b", - "metadata_hash": "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a" - }, - "remote_hash": "5f381045d839d5ef97751407123a3ad27e15a00976d198cfe2359ce14ac127a0", - "remote_snapshot": { - "name": "test-create", - "description": null, - "model": "qwen3.8-max", - "instructions": "111" - }, - "drift_paths": [], - "drift_status": "in_sync" - } - ] + "resources": [] } diff --git a/packages/commands/tests/e2e/managed-agent-auth-chain.e2e.test.ts b/packages/commands/tests/e2e/managed-agent-auth-chain.e2e.test.ts index fce243ca9..4af56f708 100644 --- a/packages/commands/tests/e2e/managed-agent-auth-chain.e2e.test.ts +++ b/packages/commands/tests/e2e/managed-agent-auth-chain.e2e.test.ts @@ -9,8 +9,8 @@ import { MANAGED_AGENT_ROUTES } from "./topic-routes.ts"; /** * managed-agent 凭证链 e2e:验证 bl 自有配置体系(config 写入 / 命名 Profile / * logout)与错误映射如何流入 SDK 引擎。全部离线:凭证门禁用 `managed-agent plan` - * 验证(空 state 不发网络请求,但 auth: "apiKey" 硬门禁 + 引擎全量 provider key - * 断言照常生效);`validate` / `state list` 属离线命令,无凭证也必须可用。 + * 验证(空 state 不发网络请求,但 auth: "apiKey" 硬门禁 + Bailian key 断言照常 + * 生效);`validate` / `state list` 属离线命令,无凭证也必须可用。 * 配置一律通过 BAILIAN_CONFIG_DIR 指向临时目录,绝不触碰真实用户配置。 */ @@ -52,11 +52,6 @@ function planArgs(file: string): string[] { return ["managed-agent", "plan", "--file", file, "--quiet"]; } -/** 隔离宿主机的 ~/.agents/config.json,避免它强制覆盖 provider 凭证 env。 */ -function isolatedAgentsConfigEnv(): NodeJS.ProcessEnv { - return { AGENTS_CONFIG_PATH: join(tmpdir(), "bl-e2e-no-agents-config.json") }; -} - /** * 在临时目录里搭一套非空 state 的项目:agents.yaml 复用单 provider fixture, * agents.state.json 预置一条已追踪资源 —— 非空 state 是触发 plan 默认 refresh @@ -214,51 +209,24 @@ describe("e2e: managed-agent 鉴权分层(离线命令免登录 / 联网命令 expect(stderr).toMatch(/auth login|API key/i); }); - test("已登录 bailian 时,多 provider 配置下 plan --no-refresh 离线通过,不查其他 provider key (0)", async () => { - const env = { - ...makeConfigEnv({ api_key: "sk-e2e-no-refresh" }), - ...isolatedAgentsConfigEnv(), - ANTHROPIC_API_KEY: "", - CLAUDE_API_KEY: "", - }; - const { stderr, exitCode } = await runCommandE2e( - ROUTES, - [...planArgs(AGENTS_YAML_MULTI), "--no-refresh"], - env, - ); - expect(exitCode, stderr).toBe(0); - }); - - test("统一登录门禁:只配 claude key 未登录 bailian 时,plan --provider claude 仍报 AUTH (3)", async () => { - const env = { - ...makeConfigEnv({}), - ...isolatedAgentsConfigEnv(), - ANTHROPIC_API_KEY: "sk-ant-e2e-scope", - CLAUDE_API_KEY: "", - }; - const { stderr, exitCode } = await runCommandE2e( - ROUTES, - [...planArgs(AGENTS_YAML_MULTI), "--provider", "claude"], - env, - ); - expect(exitCode).toBe(3); - expect(stderr).toMatch(/auth login|API key/i); + test("CLI 边界拒绝 agents.yaml 中的非 Bailian Provider (2)", async () => { + const env = makeConfigEnv({}); + const { stderr, exitCode } = await runCommandE2e(ROUTES, validateArgs(AGENTS_YAML_MULTI), env); + expect(exitCode).toBe(2); + expect(stderr).toMatch(/only supports provider 'bailian'/i); + expect(stderr).toContain("claude"); }); - test("已登录但缺 claude key 时,全量断言拦住并给 ANTHROPIC_API_KEY hint (3)", async () => { - const env = { - ...makeConfigEnv({ api_key: "sk-e2e-bailian-present" }), - ...isolatedAgentsConfigEnv(), - ANTHROPIC_API_KEY: "", - CLAUDE_API_KEY: "", - }; + test("agent list 在发请求前拒绝多 Provider 配置,不再校验 Claude Key", async () => { + const env = makeConfigEnv({ api_key: "sk-e2e-bailian-only" }); const { stderr, exitCode } = await runCommandE2e( ROUTES, - [...planArgs(AGENTS_YAML_MULTI), "--provider", "claude"], + ["managed-agent", "agent", "list", "--file", AGENTS_YAML_MULTI], env, ); - expect(exitCode).toBe(3); - expect(stderr).toMatch(/ANTHROPIC_API_KEY/); + expect(exitCode).toBe(2); + expect(stderr).toMatch(/only supports provider 'bailian'/i); + expect(stderr).not.toContain("ANTHROPIC_API_KEY"); }); }); diff --git a/packages/commands/tests/e2e/managed-agent.e2e.test.ts b/packages/commands/tests/e2e/managed-agent.e2e.test.ts index 6b925b38c..8c2b16434 100644 --- a/packages/commands/tests/e2e/managed-agent.e2e.test.ts +++ b/packages/commands/tests/e2e/managed-agent.e2e.test.ts @@ -224,7 +224,8 @@ describe("e2e: managed-agent", () => { "--help", ]); expect(exitCode, stderr).toBe(0); - expect(stderr).toMatch(/--file|--provider|--yes/i); + expect(stderr).toMatch(/--file|--yes/i); + expect(stderr).not.toContain("--provider"); }); test("managed-agent apply 计划失败时在最终错误中保留首条诊断和剩余数量", async () => { @@ -284,15 +285,7 @@ agents: instructions: Exercise the apply error path. `; await writeFile(configPath, configSource, "utf8"); - const baseArgs = [ - "managed-agent", - "apply", - "--file", - configPath, - "--provider", - "bailian", - "--yes", - ]; + const baseArgs = ["managed-agent", "apply", "--file", configPath, "--yes"]; const env = { DASHSCOPE_API_KEY: "sk-e2e-apply-error", BAILIAN_BASE_URL: `http://127.0.0.1:${address.port}`, @@ -948,7 +941,7 @@ vaults: "--secret-name", "API_TOKEN", "--secret-env", - "E2E_RETRY_SECRET", + "ANTHROPIC_API_KEY", "--file", configPath, "--yes", @@ -957,7 +950,7 @@ vaults: ]; const env = { DASHSCOPE_API_KEY: "sk-e2e-vault-credential", - E2E_RETRY_SECRET: "credential-secret-must-not-leak", + ANTHROPIC_API_KEY: "credential-secret-must-not-leak", }; try { await seedTrackedResources(configPath, [ @@ -980,7 +973,7 @@ vaults: expect(config.vaults.production.credentials).toEqual([ expect.objectContaining({ name: "api-token", - secret_value: "${E2E_RETRY_SECRET}", + secret_value: "${ANTHROPIC_API_KEY}", }), ]); expect(requestBodies).toHaveLength(2); @@ -991,6 +984,23 @@ vaults: }); test("deployment create 拒绝非法 Event 与不完整 Schedule", async () => { + const mixedEventInputs = await runCommandE2e(MANAGED_AGENT_ROUTES, [ + "managed-agent", + "deployment", + "create", + "--dry-run", + "--name", + "Mixed Events", + "--agent", + "assistant", + "--message", + "Run", + "--event", + '{"type":"system.message","content":"Be concise"}', + ]); + expect(mixedEventInputs.exitCode).toBe(2); + expect(mixedEventInputs.stderr).toMatch(/--message and --event cannot be used together/i); + const invalidEvent = await runCommandE2e(MANAGED_AGENT_ROUTES, [ "managed-agent", "deployment", @@ -1116,7 +1126,8 @@ vaults: "--help", ]); expect(exitCode, stderr).toBe(0); - expect(stderr).toMatch(/--source|--provider|--file/i); + expect(stderr).toMatch(/--source|--file/i); + expect(stderr).not.toContain("--provider"); }); test("managed-agent skill-list 非法 --source 时退出为用法错误 (2)", async () => { @@ -1163,6 +1174,50 @@ vaults: expect(exitCode, stderr).toBe(0); expect(stderr).toMatch(/--skill-id|--limit|--page|--all/i); }); + + test("session event send 拒绝字符串 content", async () => { + const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [ + "managed-agent", + "session", + "event", + "send", + "--dry-run", + "--session-id", + "sess_e2e", + "--event", + '{"type":"message","role":"user","content":"hello"}', + ]); + expect(exitCode).toBe(2); + expect(stderr).toMatch(/invalid event.*content.*expected array/i); + }); + + test("file upload 不再暴露多 Provider 专属选项", async () => { + const providerResult = await runCommandE2e(MANAGED_AGENT_ROUTES, [ + "managed-agent", + "file", + "upload", + "--dry-run", + "--path", + "missing.txt", + "--provider", + "bailian", + ]); + expect(providerResult.exitCode).toBe(2); + expect(providerResult.stderr).toMatch(/Unknown flag.*--provider/i); + + const purposeResult = await runCommandE2e(MANAGED_AGENT_ROUTES, [ + "managed-agent", + "file", + "upload", + "--dry-run", + "--path", + "missing.txt", + "--purpose", + "agent", + ]); + expect(purposeResult.exitCode).toBe(2); + expect(purposeResult.stderr).toMatch(/Unknown flag.*--purpose/i); + }); }); describe("e2e: managed-agent(--dry-run 短路,不联网不写盘)", () => { @@ -1178,7 +1233,7 @@ describe("e2e: managed-agent(--dry-run 短路,不联网不写盘)", () => "--session-id", "sess_e2e", "--event", - '{"type":"message","content":"hello"}', + '{"type":"message","role":"user","content":[{"type":"text","text":"hello"}]}', ], ], [ @@ -1234,6 +1289,18 @@ describe("e2e: managed-agent(--dry-run 短路,不联网不写盘)", () => expect(data.provider).toBe("bailian"); }); + test("init 不再接受 --provider", async () => { + const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [ + "managed-agent", + "init", + "--dry-run", + "--provider", + "claude", + ]); + expect(exitCode).toBe(2); + expect(stderr).toMatch(/Unknown flag.*--provider/i); + }); + test("apply --dry-run 仅输出计划", async () => { const { stdout, stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [ "managed-agent", @@ -1244,7 +1311,7 @@ describe("e2e: managed-agent(--dry-run 短路,不联网不写盘)", () => ]); expect(exitCode, stderr).toBe(0); const data = parseStdoutJson<{ would_apply?: { provider?: string } }>(stdout); - expect(data.would_apply?.provider).toBe("all"); + expect(data.would_apply?.provider).toBe("bailian"); }); test("agent create --dry-run 自动生成 key 且不改 YAML", async () => { diff --git a/packages/commands/tests/managed-agent-api-helpers.test.ts b/packages/commands/tests/managed-agent-api-helpers.test.ts index ee468abd7..4d1eec9ac 100644 --- a/packages/commands/tests/managed-agent-api-helpers.test.ts +++ b/packages/commands/tests/managed-agent-api-helpers.test.ts @@ -1,6 +1,7 @@ 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"; +import { buildSkillListJsonResult } from "../src/commands/managed-agent/skill/list.ts"; test("searchCursorPages preserves opaque cursors and marks truncated scans", async () => { const cursors: Array<string | undefined> = []; @@ -19,14 +20,46 @@ test("searchCursorPages preserves opaque cursors and marks truncated scans", asy expect(result.nextPage).toBe("opaque-B"); }); +test("legacy skill-list JSON keeps source alongside pagination fields", () => { + expect( + buildSkillListJsonResult("official", { + items: [{ id: "skill-official" }], + hasMore: true, + nextPage: "opaque-next", + }), + ).toEqual({ + source: "official", + skills: [{ id: "skill-official" }], + has_more: true, + next_page: "opaque-next", + }); +}); + test("session export ZIP is valid-shaped and redacts credential-like values", () => { const redacted = redactSensitiveValues({ api_key: "secret", - nested: { authorization: "Bearer secret", normal: "kept" }, + nested: { + authorization: "Bearer secret", + normal: "kept", + tool_input: "https://example.test/private?X-Amz-Signature=must-not-leak", + download_url: + "https://example.test/private?X-Amz-Credential=credential&X-Amz-Signature=must-not-leak", + json_url: + '{"url":"https://example.test/private?X-Amz-Signature=must-not-leak","status":"ok"}', + message: 'request={"token":"must-not-leak"} Authorization: Bearer must-not-leak', + }, }); expect(redacted).toEqual({ api_key: "[REDACTED]", - nested: { authorization: "[REDACTED]", normal: "kept" }, + nested: { + authorization: "[REDACTED]", + normal: "kept", + tool_input: "[REDACTED]", + download_url: + "https://example.test/private?X-Amz-Credential=[REDACTED]&X-Amz-Signature=[REDACTED]", + json_url: '{"url":"https://example.test/private?X-Amz-Signature=[REDACTED]","status":"ok"}', + message: 'request={"token":"[REDACTED]"} Authorization: [REDACTED]', + }, }); const zip = createZip([ diff --git a/skills/bailian-managed-agent/reference/managed-agent.md b/skills/bailian-managed-agent/reference/managed-agent.md index 7a9d53c92..a094cef19 100644 --- a/skills/bailian-managed-agent/reference/managed-agent.md +++ b/skills/bailian-managed-agent/reference/managed-agent.md @@ -74,12 +74,12 @@ Index: [index.md](index.md) ### `bl managed-agent agent create` -| Field | Value | -| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Name** | `managed-agent agent create` | -| **Description** | Declare and create one Managed Agent through an isolated YAML apply | -| **Authentication** | API Key | -| **Usage** | `bl managed-agent agent create --name <name> --model <model> --instructions <text\|path> [--description <text>] [--provider <name>] [--skill <id>...] [--type custom\|official] [--skill-dir <path>...] [--tool <name>...] [--file <path>] [--yes]` | +| Field | Value | +| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent agent create` | +| **Description** | Declare and create one Managed Agent through an isolated YAML apply | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent agent create --name <name> --model <model> --instructions <text\|path> [--description <text>] [--skill <id>...] [--type custom\|official] [--skill-dir <path>...] [--tool <name>...] [--file <path>] [--yes]` | #### Flags @@ -89,7 +89,6 @@ Index: [index.md](index.md) | `--model <model>` | string | yes | Model ID | | `--instructions <text\|path>` | string | yes | Inline instructions or a ./, ../, or absolute file path | | `--description <text>` | string | no | Agent description | -| `--provider <name>` | string | no | Target provider; inferred when the config has one effective provider | | `--skill <id>` | array | no | Existing remote Skill ID (repeatable) | | `--skill-dir <path>` | array | no | Local Skill directory or ZIP to declare, upload, and attach through the same scoped create (repeatable) | | `--type <custom\|official>` | string | no | Type applied to every --skill value (default: custom) | @@ -101,8 +100,8 @@ Index: [index.md](index.md) #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. - Without --yes, previews the generated YAML key and scoped plan. --dry-run stays offline. Unrelated resources are not refreshed or drift-checked. - --skill writes an external Skill reference directly into the Agent declaration. --type defaults to custom; use --type official for platform Skills. These Skills are not managed through the top-level skills map. @@ -140,7 +139,6 @@ bl managed-agent agent create --name reviewer --model qwen3.8-max --instructions | Flag | Type | Required | Description | | --------------------- | ------ | -------- | --------------------------------------- | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--agent-id <id>` | string | yes | Agent ID | | `--agent-version <n>` | number | no | Specific agent version | | `--api-key <key>` | string | no | API key | @@ -148,8 +146,8 @@ bl managed-agent agent create --name reviewer --model qwen3.8-max --instructions #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -176,7 +174,6 @@ bl managed-agent agent get --agent-id agent_abc --agent-version 3 --output json | Flag | Type | Required | Description | | -------------------- | ------ | -------- | ------------------------------------------------- | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--limit <n>` | number | no | Page size (1-100) | | `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | | `--all` | switch | no | Fetch all pages by following opaque cursors | @@ -186,8 +183,8 @@ bl managed-agent agent get --agent-id agent_abc --agent-version 3 --output json #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -218,7 +215,6 @@ bl managed-agent agent list --all --include-archived --output json | Flag | Type | Required | Description | | -------------------- | ------ | -------- | ------------------------------------------------------------- | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--limit <n>` | number | no | Page size (1-100) | | `--query <text>` | string | yes | Case-insensitive text to find in IDs, names, and descriptions | | `--page-limit <n>` | number | no | Maximum pages to scan for client-side search (default: 10) | @@ -228,8 +224,8 @@ bl managed-agent agent list --all --include-archived --output json #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -253,21 +249,20 @@ bl managed-agent agent search --query code --page-limit 20 --output json #### Flags -| Flag | Type | Required | Description | -| ------------------- | ------ | -------- | ------------------------------------------------- | -| `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | -| `--limit <n>` | number | no | Page size (1-100) | -| `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | -| `--all` | switch | no | Fetch all pages by following opaque cursors | -| `--agent-id <id>` | string | yes | Agent ID | -| `--api-key <key>` | string | no | API key | -| `--base-url <url>` | string | no | API base URL | +| Flag | Type | Required | Description | +| ------------------ | ------ | -------- | ------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--limit <n>` | number | no | Page size (1-100) | +| `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | +| `--all` | switch | no | Fetch all pages by following opaque cursors | +| `--agent-id <id>` | string | yes | Agent ID | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -287,7 +282,7 @@ bl managed-agent agent versions --agent-id agent_abc --all --output json | **Name** | `managed-agent apply` | | **Description** | Apply planned changes to create/update/delete agent resources | | **Authentication** | API Key | -| **Usage** | `bl managed-agent apply [--file <path>] [--provider <name>] [--concurrency <n>]` | +| **Usage** | `bl managed-agent apply [--file <path>] [--concurrency <n>]` | | **Risk** | `high` | | **Risk message** | This applies the current plan and may create, update, or delete remote managed Agent resources. | @@ -298,7 +293,6 @@ bl managed-agent agent versions --agent-id agent_abc --all --output json | Flag | Type | Required | Description | | ------------------- | ------ | -------- | ------------------------------------------------------------------ | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider (default: all configured) | | `--no-refresh` | switch | no | Skip refreshing state from remote before planning | | `--concurrency <n>` | number | no | Max independent resources to apply in parallel (default 6, max 10) | | `--yes` | switch | no | Confirm this high-risk operation | @@ -307,8 +301,8 @@ bl managed-agent agent versions --agent-id agent_abc --all --output json #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -318,19 +312,14 @@ bl managed-agent agent versions --agent-id agent_abc --all --output json bl managed-agent apply --yes ``` -```bash -# Only after explicit user confirmation: -bl managed-agent apply --provider bailian --yes -``` - ### `bl managed-agent deployment create` -| Field | Value | -| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Name** | `managed-agent deployment create` | -| **Description** | Declare and create one Managed Agent Deployment through a scoped YAML apply | -| **Authentication** | API Key | -| **Usage** | `bl managed-agent deployment create --name <name> --agent <yaml-key> (--message <text>... \| --event <json\|@path>...) [--agent-version <number>] [--environment <yaml-key>] [--vault <yaml-key>...] [--resource <json\|@path>...] [--schedule <cron> --timezone <timezone>] [--description <text>] [--metadata <key=value>...] [--provider <name>] [--file <path>] [--yes]` | +| Field | Value | +| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent deployment create` | +| **Description** | Declare and create one Managed Agent Deployment through a scoped YAML apply | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent deployment create --name <name> --agent <yaml-key> (--message <text>... \| --event <json\|@path>...) [--agent-version <number>] [--environment <yaml-key>] [--vault <yaml-key>...] [--resource <json\|@path>...] [--schedule <cron> --timezone <timezone>] [--description <text>] [--metadata <key=value>...] [--file <path>] [--yes]` | #### Flags @@ -348,7 +337,6 @@ bl managed-agent apply --provider bailian --yes | `--timezone <timezone>` | string | no | IANA schedule timezone | | `--description <text>` | string | no | Deployment description | | `--metadata <key=value>` | array | no | Metadata entry (repeatable) | -| `--provider <name>` | string | no | Target provider; inferred when unambiguous | | `--file <path>` | string | no | Config file path (default: agents.yaml) | | `--yes` | switch | no | Write YAML and run the scoped remote create | | `--api-key <key>` | string | no | API key | @@ -356,11 +344,12 @@ bl managed-agent apply --provider bailian --yes #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. - Without --yes, this command only previews. --dry-run is fully offline. The scoped flow checks only the target resource and its transitive dependencies; unrelated resources are not refreshed or drift-checked. - Initial Events must contain 1-50 user.message/system.message entries. --resource accepts only File Resources in this release. +- Use either repeatable --message values or repeatable --event values; the two input forms cannot be mixed. #### Examples @@ -369,7 +358,7 @@ bl managed-agent deployment create --name Daily --agent assistant --message "Gen ``` ```bash -bl managed-agent deployment create --name Daily --agent assistant --event '{"type":"system.message","content":"Be concise"}' --message "Run" --yes +bl managed-agent deployment create --name Daily --agent assistant --event '{"type":"system.message","content":"Be concise"}' --yes ``` ### `bl managed-agent deployment get` @@ -386,15 +375,14 @@ bl managed-agent deployment create --name Daily --agent assistant --event '{"typ | Flag | Type | Required | Description | | ---------------------- | ------ | -------- | --------------------------------------- | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--deployment-id <id>` | string | yes | Deployment ID | | `--api-key <key>` | string | no | API key | | `--base-url <url>` | string | no | API base URL | #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -417,7 +405,6 @@ bl managed-agent deployment get --deployment-id dep_abc | Flag | Type | Required | Description | | ------------------------------ | ------ | -------- | ------------------------------------------------- | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--limit <n>` | number | no | Page size (1-100) | | `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | | `--all` | switch | no | Fetch all pages by following opaque cursors | @@ -431,8 +418,8 @@ bl managed-agent deployment get --deployment-id dep_abc #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -463,7 +450,6 @@ bl managed-agent deployment list --status active --all --output json | Flag | Type | Required | Description | | ---------------------- | ------ | -------- | -------------------------------------------- | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--deployment <name>` | string | no | Logical deployment name in agents.yaml/state | | `--deployment-id <id>` | string | no | Direct deployment ID | | `--yes` | switch | no | Confirm this high-risk operation | @@ -472,8 +458,8 @@ bl managed-agent deployment list --status active --all --output json #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -505,7 +491,6 @@ bl managed-agent deployment pause --deployment-id dep_abc --yes | Flag | Type | Required | Description | | ---------------------- | ------ | -------- | -------------------------------------------- | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--deployment <name>` | string | no | Logical deployment name in agents.yaml/state | | `--deployment-id <id>` | string | no | Direct deployment ID | | `--yes` | switch | no | Confirm this high-risk operation | @@ -514,8 +499,8 @@ bl managed-agent deployment pause --deployment-id dep_abc --yes #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -540,18 +525,17 @@ bl managed-agent deployment run --deployment-id dep_abc --yes #### Flags -| Flag | Type | Required | Description | -| ------------------- | ------ | -------- | --------------------------------------- | -| `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | -| `--run-id <id>` | string | yes | Deployment run ID | -| `--api-key <key>` | string | no | API key | -| `--base-url <url>` | string | no | API base URL | +| Flag | Type | Required | Description | +| ------------------ | ------ | -------- | --------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--run-id <id>` | string | yes | Deployment run ID | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -574,7 +558,6 @@ bl managed-agent deployment runs get --run-id run_abc | Flag | Type | Required | Description | | ---------------------- | ------ | -------- | ------------------------------------------------- | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--deployment-id <id>` | string | yes | Deployment ID | | `--limit <n>` | number | no | Page size (1-100) | | `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | @@ -584,8 +567,8 @@ bl managed-agent deployment runs get --run-id run_abc #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -612,7 +595,6 @@ bl managed-agent deployment runs list --deployment-id dep_abc --all --output jso | Flag | Type | Required | Description | | ------------------------------ | ------ | -------- | ------------------------------------------------------------- | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--limit <n>` | number | no | Page size (1-100) | | `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | | `--all` | switch | no | Fetch all pages by following opaque cursors | @@ -627,8 +609,8 @@ bl managed-agent deployment runs list --deployment-id dep_abc --all --output jso #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. - Deployment search maps --query to the provider's server-side keyword parameter. @@ -660,7 +642,6 @@ bl managed-agent deployment search --query nightly --all --output json | Flag | Type | Required | Description | | ---------------------- | ------ | -------- | -------------------------------------------- | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--deployment <name>` | string | no | Logical deployment name in agents.yaml/state | | `--deployment-id <id>` | string | no | Direct deployment ID | | `--yes` | switch | no | Confirm this high-risk operation | @@ -669,8 +650,8 @@ bl managed-agent deployment search --query nightly --all --output json #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -709,8 +690,8 @@ bl managed-agent deployment unpause --deployment-id dep_abc --yes #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -727,12 +708,12 @@ bl managed-agent destroy --yes --cascade ### `bl managed-agent environment create` -| Field | Value | -| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Name** | `managed-agent environment create` | -| **Description** | Declare and create one Managed Agent Environment through a scoped YAML apply | -| **Authentication** | API Key | -| **Usage** | `bl managed-agent environment create --name <name> [--description <text>] [--metadata <key=value>...] [--apt <package>...] [--pip <package>...] [--npm <package>...] [--cargo <package>...] [--gem <package>...] [--go <package>...] [--provider <name>] [--file <path>] [--yes]` | +| Field | Value | +| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent environment create` | +| **Description** | Declare and create one Managed Agent Environment through a scoped YAML apply | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent environment create --name <name> [--description <text>] [--metadata <key=value>...] [--apt <package>...] [--pip <package>...] [--npm <package>...] [--cargo <package>...] [--gem <package>...] [--go <package>...] [--file <path>] [--yes]` | #### Flags @@ -747,7 +728,6 @@ bl managed-agent destroy --yes --cascade | `--cargo <package>` | array | no | Cargo package (repeatable) | | `--gem <package>` | array | no | Ruby gem package (repeatable) | | `--go <package>` | array | no | Go package (repeatable) | -| `--provider <name>` | string | no | Target provider; inferred when unambiguous | | `--file <path>` | string | no | Config file path (default: agents.yaml) | | `--yes` | switch | no | Write YAML and run the scoped remote create | | `--api-key <key>` | string | no | API key | @@ -755,8 +735,8 @@ bl managed-agent destroy --yes --cascade #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. - Without --yes, this command only previews. --dry-run is fully offline. The scoped flow checks only the target resource and its transitive dependencies; unrelated resources are not refreshed or drift-checked. - Creates a cloud Environment with unrestricted networking. Without --yes, only previews the generated YAML key and scoped plan. @@ -785,15 +765,14 @@ bl managed-agent environment create --name Development --pip pandas --npm typesc | Flag | Type | Required | Description | | ----------------------- | ------ | -------- | --------------------------------------- | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--environment-id <id>` | string | yes | Environment ID | | `--api-key <key>` | string | no | API key | | `--base-url <url>` | string | no | API base URL | #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -816,7 +795,6 @@ bl managed-agent environment get --environment-id env_abc | Flag | Type | Required | Description | | -------------------- | ------ | -------- | ------------------------------------------------- | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--limit <n>` | number | no | Page size (1-100) | | `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | | `--all` | switch | no | Fetch all pages by following opaque cursors | @@ -826,8 +804,8 @@ bl managed-agent environment get --environment-id env_abc #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -854,7 +832,6 @@ bl managed-agent environment list --all --output json | Flag | Type | Required | Description | | -------------------- | ------ | -------- | ------------------------------------------------------------- | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--limit <n>` | number | no | Page size (1-100) | | `--query <text>` | string | yes | Case-insensitive text to find in IDs, names, and descriptions | | `--page-limit <n>` | number | no | Maximum pages to scan for client-side search (default: 10) | @@ -864,8 +841,8 @@ bl managed-agent environment list --all --output json #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -893,19 +870,18 @@ bl managed-agent environment search --query production --page-limit 20 --output #### Flags -| Flag | Type | Required | Description | -| ------------------- | ------ | -------- | --------------------------------------- | -| `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | -| `--file-id <id>` | string | yes | Remote file ID | -| `--yes` | switch | no | Confirm this high-risk operation | -| `--api-key <key>` | string | no | API key | -| `--base-url <url>` | string | no | API base URL | +| Flag | Type | Required | Description | +| ------------------ | ------ | -------- | --------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--file-id <id>` | string | yes | Remote file ID | +| `--yes` | switch | no | Confirm this high-risk operation | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -933,7 +909,6 @@ bl managed-agent file delete --file-id file_abc --yes | Flag | Type | Required | Description | | ---------------------- | ------ | -------- | --------------------------------------- | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--file-id <id>` | string | yes | Remote file ID | | `--output-file <path>` | string | yes | Destination path | | `--force` | switch | no | Overwrite an existing output file | @@ -942,8 +917,8 @@ bl managed-agent file delete --file-id file_abc --yes #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -963,18 +938,17 @@ bl managed-agent file download --file-id file_abc --output-file ./artifact.pdf #### Flags -| Flag | Type | Required | Description | -| ------------------- | ------ | -------- | --------------------------------------- | -| `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | -| `--file-id <id>` | string | yes | Remote file ID | -| `--api-key <key>` | string | no | API key | -| `--base-url <url>` | string | no | API base URL | +| Flag | Type | Required | Description | +| ------------------ | ------ | -------- | --------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--file-id <id>` | string | yes | Remote file ID | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -994,21 +968,20 @@ bl managed-agent file get --file-id file_abc #### Flags -| Flag | Type | Required | Description | -| ------------------- | ------ | -------- | ------------------------------------------------- | -| `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | -| `--limit <n>` | number | no | Page size (1-100) | -| `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | -| `--all` | switch | no | Fetch all pages by following opaque cursors | -| `--scope-id <id>` | string | no | Filter by scope ID | -| `--api-key <key>` | string | no | API key | -| `--base-url <url>` | string | no | API base URL | +| Flag | Type | Required | Description | +| ------------------ | ------ | -------- | ------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--limit <n>` | number | no | Page size (1-100) | +| `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | +| `--all` | switch | no | Fetch all pages by following opaque cursors | +| `--scope-id <id>` | string | no | Filter by scope ID | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -1032,21 +1005,20 @@ bl managed-agent file list --scope-id sess_abc --all --output json #### Flags -| Flag | Type | Required | Description | -| ------------------- | ------ | -------- | ------------------------------------------------------------- | -| `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | -| `--limit <n>` | number | no | Page size (1-100) | -| `--query <text>` | string | yes | Case-insensitive text to find in IDs, names, and descriptions | -| `--page-limit <n>` | number | no | Maximum pages to scan for client-side search (default: 10) | -| `--scope-id <id>` | string | no | Filter by scope ID | -| `--api-key <key>` | string | no | API key | -| `--base-url <url>` | string | no | API base URL | +| Flag | Type | Required | Description | +| ------------------ | ------ | -------- | ------------------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--limit <n>` | number | no | Page size (1-100) | +| `--query <text>` | string | yes | Case-insensitive text to find in IDs, names, and descriptions | +| `--page-limit <n>` | number | no | Maximum pages to scan for client-side search (default: 10) | +| `--scope-id <id>` | string | no | Filter by scope ID | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -1061,30 +1033,28 @@ bl managed-agent file search --query pdf --scope-id sess_abc --output json ### `bl managed-agent file upload` -| Field | Value | -| ------------------ | ----------------------------------------------------------------------------------------------------------- | -| **Name** | `managed-agent file upload` | -| **Description** | Upload a Managed Agent file | -| **Authentication** | API Key | -| **Usage** | `bl managed-agent file upload --path <path> [--filename <name>] [--mime-type <type>] [--purpose <purpose>]` | +| Field | Value | +| ------------------ | ------------------------------------------------------------------------------------- | +| **Name** | `managed-agent file upload` | +| **Description** | Upload a Managed Agent file | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent file upload --path <path> [--filename <name>] [--mime-type <type>]` | #### Flags -| Flag | Type | Required | Description | -| --------------------- | ------ | -------- | --------------------------------------- | -| `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | -| `--path <path>` | string | yes | Local file path | -| `--filename <name>` | string | no | Remote filename override | -| `--mime-type <type>` | string | no | MIME type override | -| `--purpose <purpose>` | string | no | Provider upload purpose | -| `--api-key <key>` | string | no | API key | -| `--base-url <url>` | string | no | API base URL | +| Flag | Type | Required | Description | +| -------------------- | ------ | -------- | --------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--path <path>` | string | yes | Local file path | +| `--filename <name>` | string | no | Remote filename override | +| `--mime-type <type>` | string | no | MIME type override | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -1093,27 +1063,22 @@ bl managed-agent file search --query pdf --scope-id sess_abc --output json bl managed-agent file upload --path ./report.pdf ``` -```bash -bl managed-agent file upload --path ./data.json --purpose assistants -``` - ### `bl managed-agent init` -| Field | Value | -| ------------------ | ------------------------------------------------------------------------------------------- | -| **Name** | `managed-agent init` | -| **Description** | Create a new agents.yaml template | -| **Authentication** | No Auth | -| **Usage** | `bl managed-agent init [--provider <name>] [--agent-name <name>] [--file <path>] [--force]` | +| Field | Value | +| ------------------ | ----------------------------------------------------------------------- | +| **Name** | `managed-agent init` | +| **Description** | Create a new agents.yaml template | +| **Authentication** | No Auth | +| **Usage** | `bl managed-agent init [--agent-name <name>] [--file <path>] [--force]` | #### Flags -| Flag | Type | Required | Description | -| ----------------------------------------------- | ------ | -------- | ------------------------------------------------------------- | -| `--provider <bailian\|claude\|qoder\|ark\|all>` | string | no | Provider: bailian, claude, qoder, ark, all (default: bailian) | -| `--agent-name <name>` | string | no | Name of the first agent (default: assistant) | -| `--file <path>` | string | no | Output config path (default: agents.yaml) | -| `--force` | switch | no | Overwrite an existing config file | +| Flag | Type | Required | Description | +| --------------------- | ------ | -------- | -------------------------------------------- | +| `--agent-name <name>` | string | no | Name of the first agent (default: assistant) | +| `--file <path>` | string | no | Output config path (default: agents.yaml) | +| `--force` | switch | no | Overwrite an existing config file | #### Examples @@ -1122,37 +1087,32 @@ bl managed-agent init ``` ```bash -bl managed-agent init --provider bailian --agent-name assistant -``` - -```bash -bl managed-agent init --provider all +bl managed-agent init --agent-name assistant ``` ### `bl managed-agent plan` -| Field | Value | -| ------------------ | ------------------------------------------------------------------------------------------- | -| **Name** | `managed-agent plan` | -| **Description** | Show what changes would be applied to agent infrastructure | -| **Authentication** | API Key | -| **Usage** | `bl managed-agent plan [--file <path>] [--provider <name>] [--no-refresh] [--refresh-only]` | +| Field | Value | +| ------------------ | ----------------------------------------------------------------------- | +| **Name** | `managed-agent plan` | +| **Description** | Show what changes would be applied to agent infrastructure | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent plan [--file <path>] [--no-refresh] [--refresh-only]` | #### Flags -| Flag | Type | Required | Description | -| ------------------- | ------ | -------- | -------------------------------------------------------------- | -| `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider (default: all configured) | -| `--no-refresh` | switch | no | Skip refreshing state from remote before planning | -| `--refresh-only` | switch | no | Refresh state and show drift without planning remote mutations | -| `--api-key <key>` | string | no | API key | -| `--base-url <url>` | string | no | API base URL | +| Flag | Type | Required | Description | +| ------------------ | ------ | -------- | -------------------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--no-refresh` | switch | no | Skip refreshing state from remote before planning | +| `--refresh-only` | switch | no | Refresh state and show drift without planning remote mutations | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. - --no-refresh and --dry-run plan offline from local config and state: no remote requests, no state writes, provider keys are not checked. @@ -1162,10 +1122,6 @@ bl managed-agent init --provider all bl managed-agent plan ``` -```bash -bl managed-agent plan --provider bailian -``` - ```bash bl managed-agent plan --no-refresh ``` @@ -1188,7 +1144,6 @@ bl managed-agent plan --no-refresh | Flag | Type | Required | Description | | ------------------- | ------ | -------- | --------------------------------------- | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--session-id <id>` | string | yes | Session ID | | `--yes` | switch | no | Confirm this high-risk operation | | `--api-key <key>` | string | no | API key | @@ -1196,8 +1151,8 @@ bl managed-agent plan --no-refresh #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -1230,14 +1185,13 @@ bl managed-agent session archive --session-id sess_abc --yes | `--vault <name>` | string | no | Override agent's declared vault | | `--memory-stores <names>` | string | no | Override agent's memory stores (comma-separated) | | `--title <title>` | string | no | Session title | -| `--provider <name>` | string | no | Target provider (multi-provider agents) | | `--api-key <key>` | string | no | API key | | `--base-url <url>` | string | no | API base URL | #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -1268,15 +1222,14 @@ bl managed-agent session create --agent assistant --title 'debug run' | Flag | Type | Required | Description | | ------------------- | ------ | -------- | --------------------------------------- | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--session-id <id>` | string | yes | Session ID | | `--api-key <key>` | string | no | API key | | `--base-url <url>` | string | no | API base URL | #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -1291,14 +1244,14 @@ bl managed-agent session debug --session-id sess_abc --output json ### `bl managed-agent session delete` -| Field | Value | -| ------------------ | --------------------------------------------------------------------------------------- | -| **Name** | `managed-agent session delete` | -| **Description** | Delete a session | -| **Authentication** | API Key | -| **Usage** | `bl managed-agent session delete --session-id <id> [--provider <name>] [--file <path>]` | -| **Risk** | `high` | -| **Risk message** | This deletes the specified remote managed Agent Session. | +| Field | Value | +| ------------------ | ------------------------------------------------------------------- | +| **Name** | `managed-agent session delete` | +| **Description** | Delete a session | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent session delete --session-id <id> [--file <path>]` | +| **Risk** | `high` | +| **Risk message** | This deletes the specified remote managed Agent Session. | > **Agent safety:** Never add `--yes` automatically. On `type="requires_confirmation"`, stop and ask for explicit user confirmation of the same action and scope. @@ -1308,15 +1261,14 @@ bl managed-agent session debug --session-id sess_abc --output json | ------------------- | ------ | -------- | --------------------------------------- | | `--session-id <id>` | string | yes | Session ID (required) | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--yes` | switch | no | Confirm this high-risk operation | | `--api-key <key>` | string | no | API key | | `--base-url <url>` | string | no | API base URL | #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -1340,7 +1292,6 @@ bl managed-agent session delete --session-id sess_abc123 --yes | Flag | Type | Required | Description | | ------------------------------ | ------ | -------- | ------------------------------------------------- | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--session-id <id>` | string | yes | Session ID | | `--limit <n>` | number | no | Page size (1-100) | | `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | @@ -1354,8 +1305,8 @@ bl managed-agent session delete --session-id sess_abc123 --yes #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. - --types is applied client-side to each page returned by the provider. @@ -1383,7 +1334,6 @@ bl managed-agent session event list --session-id sess_abc --all --output json | Flag | Type | Required | Description | | ----------------------- | ------ | -------- | --------------------------------------------- | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--session-id <id>` | string | yes | Session ID | | `--event <json\|@path>` | string | yes | Raw event object/array as JSON or @event.json | | `--api-key <key>` | string | no | API key | @@ -1391,14 +1341,14 @@ bl managed-agent session event list --session-id sess_abc --all --output json #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples ```bash -bl managed-agent session event send --session-id sess_abc --event '{"type":"message","role":"user","content":"hello"}' +bl managed-agent session event send --session-id sess_abc --event '{"type":"message","role":"user","content":[{"type":"text","text":"hello"}]}' ``` ```bash @@ -1419,7 +1369,6 @@ bl managed-agent session event send --session-id sess_abc --event @event.json | Flag | Type | Required | Description | | ----------------------- | ------ | -------- | --------------------------------------- | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--session-id <id>` | string | yes | Session ID | | `--after-id <event-id>` | string | no | Resume after this event ID | | `--api-key <key>` | string | no | API key | @@ -1427,8 +1376,8 @@ bl managed-agent session event send --session-id sess_abc --event @event.json #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. - When the provider has no native event cursor, --after-id resumes through paginated history polling and event ID de-duplication. @@ -1456,7 +1405,6 @@ bl managed-agent session event stream --session-id sess_abc --after-id evt_123 - | Flag | Type | Required | Description | | ------------------------------ | ------ | -------- | ------------------------------------------------- | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--session-id <id>` | string | yes | Session ID | | `--limit <n>` | number | no | Page size (1-100) | | `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | @@ -1470,8 +1418,8 @@ bl managed-agent session event stream --session-id sess_abc --after-id evt_123 - #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. - --types is applied client-side to each page returned by the provider. @@ -1499,7 +1447,6 @@ bl managed-agent session events --session-id sess_abc --all --output json | Flag | Type | Required | Description | | ---------------------- | ------ | -------- | --------------------------------------- | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--session-id <id>` | string | yes | Session ID | | `--output-file <path>` | string | yes | Destination ZIP path | | `--force` | switch | no | Overwrite an existing output file | @@ -1508,8 +1455,8 @@ bl managed-agent session events --session-id sess_abc --all --output json #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. - The ZIP contains metadata only; file bodies and credential-like values are excluded/redacted. @@ -1521,12 +1468,12 @@ bl managed-agent session export --session-id sess_abc --output-file ./session-de ### `bl managed-agent session get` -| Field | Value | -| ------------------ | ------------------------------------------------------------------------------------ | -| **Name** | `managed-agent session get` | -| **Description** | Get details of a session | -| **Authentication** | API Key | -| **Usage** | `bl managed-agent session get --session-id <id> [--provider <name>] [--file <path>]` | +| Field | Value | +| ------------------ | ---------------------------------------------------------------- | +| **Name** | `managed-agent session get` | +| **Description** | Get details of a session | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent session get --session-id <id> [--file <path>]` | #### Flags @@ -1534,14 +1481,13 @@ bl managed-agent session export --session-id sess_abc --output-file ./session-de | ------------------- | ------ | -------- | --------------------------------------- | | `--session-id <id>` | string | yes | Session ID (required) | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--api-key <key>` | string | no | API key | | `--base-url <url>` | string | no | API base URL | #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -1571,14 +1517,13 @@ bl managed-agent session get --session-id sess_abc123 | `--statuses <statuses>` | string | no | Comma-separated session statuses | | `--created-at-gte <timestamp>` | string | no | Created at or after this RFC 3339 timestamp | | `--created-at-lte <timestamp>` | string | no | Created at or before this RFC 3339 timestamp | -| `--provider <name>` | string | no | Target provider | | `--api-key <key>` | string | no | API key | | `--base-url <url>` | string | no | API base URL | #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -1615,15 +1560,14 @@ bl managed-agent session list --all | `--vault <name>` | string | no | Override agent's declared vault | | `--memory-stores <names>` | string | no | Override agent's memory stores (comma-separated) | | `--title <title>` | string | no | Session title | -| `--provider <name>` | string | no | Target provider | | `--no-stream` | switch | no | Use polling instead of SSE streaming | | `--api-key <key>` | string | no | API key | | `--base-url <url>` | string | no | API base URL | #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. - --output json emits one envelope: { session_id, provider, agent, events } — read session_id to chain `session send/get/events/delete`. @@ -1651,7 +1595,6 @@ bl managed-agent session run --agent assistant --prompt "summarize this repo" | Flag | Type | Required | Description | | ------------------------------ | ------ | -------- | ------------------------------------------------------------- | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--limit <n>` | number | no | Page size (1-100) | | `--query <text>` | string | yes | Case-insensitive text to find in IDs, names, and descriptions | | `--page-limit <n>` | number | no | Maximum pages to scan for client-side search (default: 10) | @@ -1664,8 +1607,8 @@ bl managed-agent session run --agent assistant --prompt "summarize this repo" #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -1694,15 +1637,14 @@ bl managed-agent session search --query failed --statuses failed --output json | `--session-id <id>` | string | yes | Session ID (required) | | `--message <text>` | string | yes | Message to send (required) | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--no-stream` | switch | no | Use polling instead of SSE streaming | | `--api-key <key>` | string | no | API key | | `--base-url <url>` | string | no | API base URL | #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -1725,7 +1667,6 @@ bl managed-agent session send --session-id sess_abc123 --message "continue" | Flag | Type | Required | Description | | -------------------------- | ------ | -------- | --------------------------------------- | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--session-id <id>` | string | yes | Session ID | | `--title <title>` | string | no | New session title | | `--metadata <json\|@path>` | string | no | String-valued metadata JSON or @file | @@ -1734,8 +1675,8 @@ bl managed-agent session send --session-id sess_abc123 --message "continue" #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -1750,19 +1691,18 @@ bl managed-agent session update --session-id sess_abc --metadata @metadata.json ### `bl managed-agent skill create` -| Field | Value | -| ------------------ | --------------------------------------------------------------------------------------------------------------- | -| **Name** | `managed-agent skill create` | -| **Description** | Declare and create one custom Managed Agent Skill from a local source | -| **Authentication** | API Key | -| **Usage** | `bl managed-agent skill create --source <directory\|zip\|SKILL.md> [--provider <name>] [--file <path>] [--yes]` | +| Field | Value | +| ------------------ | ------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent skill create` | +| **Description** | Declare and create one custom Managed Agent Skill from a local source | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent skill create --source <directory\|zip\|SKILL.md> [--file <path>] [--yes]` | #### Flags | Flag | Type | Required | Description | | ------------------------------------- | ------ | -------- | --------------------------------------------------------- | | `--source <directory\|zip\|SKILL.md>` | string | yes | Local Skill directory, ZIP archive, or single SKILL.md | -| `--provider <name>` | string | no | Target provider; inferred when unambiguous | | `--file <path>` | string | no | Config file path (default: agents.yaml) | | `--yes` | switch | no | Write YAML and upload the Skill through the scoped create | | `--api-key <key>` | string | no | API key | @@ -1770,8 +1710,8 @@ bl managed-agent session update --session-id sess_abc --metadata @metadata.json #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. - Without --yes, this command only previews. --dry-run is fully offline. The scoped flow checks only the target resource and its transitive dependencies; unrelated resources are not refreshed or drift-checked. - The YAML key is derived from SKILL.md frontmatter name. Remote URLs remain available through handwritten YAML plus full apply. @@ -1800,7 +1740,6 @@ bl managed-agent skill create --source ./skill.zip --yes | Flag | Type | Required | Description | | --------------------------- | ------ | -------- | --------------------------------------- | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--skill-id <id>` | string | yes | Skill ID | | `--skill-version <version>` | string | yes | Skill version | | `--output-file <path>` | string | yes | Destination ZIP path | @@ -1810,8 +1749,8 @@ bl managed-agent skill create --source ./skill.zip --yes #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -1831,18 +1770,17 @@ bl managed-agent skill download --skill-id skill_abc --skill-version 3 --output- #### Flags -| Flag | Type | Required | Description | -| ------------------- | ------ | -------- | --------------------------------------- | -| `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | -| `--skill-id <id>` | string | yes | Skill ID | -| `--api-key <key>` | string | no | API key | -| `--base-url <url>` | string | no | API base URL | +| Flag | Type | Required | Description | +| ------------------ | ------ | -------- | --------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--skill-id <id>` | string | yes | Skill ID | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -1865,7 +1803,6 @@ bl managed-agent skill get --skill-id skill_abc | Flag | Type | Required | Description | | ---------------------------------- | ------ | -------- | ------------------------------------------------- | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--limit <n>` | number | no | Page size (1-100) | | `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | | `--all` | switch | no | Fetch all pages by following opaque cursors | @@ -1875,8 +1812,8 @@ bl managed-agent skill get --skill-id skill_abc #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. - --source all combines one page from each catalog, or every page with --all; it does not accept --page. @@ -1908,7 +1845,6 @@ bl managed-agent skill list --source all --all --output json | Flag | Type | Required | Description | | ---------------------------------- | ------ | -------- | ------------------------------------------------------------- | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--limit <n>` | number | no | Page size (1-100) | | `--query <text>` | string | yes | Case-insensitive text to find in IDs, names, and descriptions | | `--page-limit <n>` | number | no | Maximum pages to scan for client-side search (default: 10) | @@ -1918,8 +1854,8 @@ bl managed-agent skill list --source all --all --output json #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -1943,21 +1879,20 @@ bl managed-agent skill search --query report --source all --output json #### Flags -| Flag | Type | Required | Description | -| ------------------- | ------ | -------- | ------------------------------------------------- | -| `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | -| `--skill-id <id>` | string | yes | Skill ID | -| `--limit <n>` | number | no | Page size (1-100) | -| `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | -| `--all` | switch | no | Fetch all pages by following opaque cursors | -| `--api-key <key>` | string | no | API key | -| `--base-url <url>` | string | no | API base URL | +| Flag | Type | Required | Description | +| ------------------ | ------ | -------- | ------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--skill-id <id>` | string | yes | Skill ID | +| `--limit <n>` | number | no | Page size (1-100) | +| `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | +| `--all` | switch | no | Fetch all pages by following opaque cursors | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -1984,7 +1919,6 @@ bl managed-agent skill versions --skill-id skill_abc --all --output json | Flag | Type | Required | Description | | ---------------------------------- | ------ | -------- | ------------------------------------------------- | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--limit <n>` | number | no | Page size (1-100) | | `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | | `--all` | switch | no | Fetch all pages by following opaque cursors | @@ -1994,8 +1928,8 @@ bl managed-agent skill versions --skill-id skill_abc --all --output json #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. - --source all combines one page from each catalog, or every page with --all; it does not accept --page. @@ -2015,28 +1949,28 @@ bl managed-agent skill-list --source all --all --output json ### `bl managed-agent state import` -| Field | Value | -| ------------------ | ------------------------------------------------------------------------------------------------------------------------ | -| **Name** | `managed-agent state import` | -| **Description** | Import an existing remote resource into agents state | -| **Authentication** | API Key | -| **Usage** | `bl managed-agent state import --address <provider.type.name> --remote-id <id> [--resource-version <n>] [--file <path>]` | +| Field | Value | +| ------------------ | ----------------------------------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent state import` | +| **Description** | Import an existing remote resource into agents state | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent state import --address <bailian.type.name> --remote-id <id> [--resource-version <n>] [--file <path>]` | #### Flags -| Flag | Type | Required | Description | -| -------------------------------- | ------ | -------- | ------------------------------------------------------ | -| `--address <provider.type.name>` | string | yes | Resource state address (required) | -| `--remote-id <id>` | string | yes | Existing remote resource ID to import (required) | -| `--resource-version <n>` | number | no | Resource version (for versioned resources like agents) | -| `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--api-key <key>` | string | no | API key | -| `--base-url <url>` | string | no | API base URL | +| Flag | Type | Required | Description | +| ------------------------------- | ------ | -------- | ------------------------------------------------------ | +| `--address <bailian.type.name>` | string | yes | Resource state address (required) | +| `--remote-id <id>` | string | yes | Existing remote resource ID to import (required) | +| `--resource-version <n>` | number | no | Resource version (for versioned resources like agents) | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -2062,6 +1996,7 @@ bl managed-agent state import --address bailian.agent.assistant --remote-id agen #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Runs fully offline against local files: no login or provider credentials required. #### Examples @@ -2097,6 +2032,7 @@ bl managed-agent state list --file agents.yaml #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Runs fully offline against local files: no login or provider credentials required. #### Examples @@ -2124,6 +2060,7 @@ bl managed-agent state rm --address bailian.agent.assistant --yes #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Runs fully offline against local files: no login or provider credentials required. #### Examples @@ -2149,6 +2086,7 @@ bl managed-agent state show --address bailian.agent.assistant #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Runs fully offline against local files: no login or provider credentials required. #### Examples @@ -2163,12 +2101,12 @@ bl managed-agent validate --file agents.yaml ### `bl managed-agent vault create` -| Field | Value | -| ------------------ | --------------------------------------------------------------------------------------------------------------------- | -| **Name** | `managed-agent vault create` | -| **Description** | Declare and create one empty Managed Agent Vault through a scoped YAML apply | -| **Authentication** | API Key | -| **Usage** | `bl managed-agent vault create --name <name> [--metadata <key=value>...] [--provider <name>] [--file <path>] [--yes]` | +| Field | Value | +| ------------------ | ------------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent vault create` | +| **Description** | Declare and create one empty Managed Agent Vault through a scoped YAML apply | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent vault create --name <name> [--metadata <key=value>...] [--file <path>] [--yes]` | #### Flags @@ -2176,7 +2114,6 @@ bl managed-agent validate --file agents.yaml | ------------------------ | ------ | -------- | ------------------------------------------------------------------ | | `--name <name>` | string | yes | Remote Vault display name; the YAML key is generated automatically | | `--metadata <key=value>` | array | no | Metadata entry (repeatable) | -| `--provider <name>` | string | no | Target provider; inferred when unambiguous | | `--file <path>` | string | no | Config file path (default: agents.yaml) | | `--yes` | switch | no | Write YAML and run the scoped remote create | | `--api-key <key>` | string | no | API key | @@ -2184,8 +2121,8 @@ bl managed-agent validate --file agents.yaml #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. - Without --yes, this command only previews. --dry-run is fully offline. The scoped flow checks only the target resource and its transitive dependencies; unrelated resources are not refreshed or drift-checked. - Creates an empty Vault. Add secrets later with `vault credential create`. @@ -2202,12 +2139,12 @@ bl managed-agent vault create --name Production --metadata owner=platform --yes ### `bl managed-agent vault credential create` -| Field | Value | -| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| **Name** | `managed-agent vault credential create` | -| **Description** | Append and create one environment-variable Credential in a tracked Vault | -| **Authentication** | API Key | -| **Usage** | `bl managed-agent vault credential create --vault <yaml-key> --name <name> --secret-name <name> --secret-env <env-name> [--metadata <key=value>...] [--provider <name>] [--file <path>] [--yes]` | +| Field | Value | +| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Name** | `managed-agent vault credential create` | +| **Description** | Append and create one environment-variable Credential in a tracked Vault | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent vault credential create --vault <yaml-key> --name <name> --secret-name <name> --secret-env <env-name> [--metadata <key=value>...] [--file <path>] [--yes]` | #### Flags @@ -2218,7 +2155,6 @@ bl managed-agent vault create --name Production --metadata owner=platform --yes | `--secret-name <name>` | string | yes | Environment variable name exposed to the Agent | | `--secret-env <env-name>` | string | yes | Local environment variable containing the secret value | | `--metadata <key=value>` | array | no | Metadata entry (repeatable) | -| `--provider <name>` | string | no | Target provider; inferred when unambiguous | | `--file <path>` | string | no | Config file path (default: agents.yaml) | | `--yes` | switch | no | Write YAML and create the remote Credential | | `--api-key <key>` | string | no | API key | @@ -2226,8 +2162,8 @@ bl managed-agent vault create --name Production --metadata owner=platform --yes #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. - Without --yes, this command only previews. --dry-run is fully offline. The scoped flow checks only the target resource and its transitive dependencies; unrelated resources are not refreshed or drift-checked. - --secret-env is an environment variable name, not the secret itself. The CLI auto-loads the nearest .env from the current directory upward; shell exports and CI secret injection also work. @@ -2254,18 +2190,17 @@ bl managed-agent vault credential create --vault production --name api-token --s #### Flags -| Flag | Type | Required | Description | -| ------------------- | ------ | -------- | --------------------------------------- | -| `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | -| `--vault-id <id>` | string | yes | Vault ID | -| `--api-key <key>` | string | no | API key | -| `--base-url <url>` | string | no | API base URL | +| Flag | Type | Required | Description | +| ------------------ | ------ | -------- | --------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--vault-id <id>` | string | yes | Vault ID | +| `--api-key <key>` | string | no | API key | +| `--base-url <url>` | string | no | API base URL | #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -2288,7 +2223,6 @@ bl managed-agent vault get --vault-id vault_abc | Flag | Type | Required | Description | | -------------------- | ------ | -------- | ------------------------------------------------- | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--limit <n>` | number | no | Page size (1-100) | | `--page <cursor>` | string | no | Opaque page cursor returned by a previous request | | `--all` | switch | no | Fetch all pages by following opaque cursors | @@ -2298,8 +2232,8 @@ bl managed-agent vault get --vault-id vault_abc #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples @@ -2326,7 +2260,6 @@ bl managed-agent vault list --all --output json | Flag | Type | Required | Description | | -------------------- | ------ | -------- | ------------------------------------------------------------- | | `--file <path>` | string | no | Config file path (default: agents.yaml) | -| `--provider <name>` | string | no | Target provider | | `--limit <n>` | number | no | Page size (1-100) | | `--query <text>` | string | yes | Case-insensitive text to find in IDs, names, and descriptions | | `--page-limit <n>` | number | no | Maximum pages to scan for client-side search (default: 10) | @@ -2336,8 +2269,8 @@ bl managed-agent vault list --all --output json #### Notes +- bl managed-agent supports the Bailian provider only; configurations containing other providers are rejected. - Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile). -- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json. - Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env. #### Examples From e1634c40ed7952ed13d57f873b41198f8801a0a9 Mon Sep 17 00:00:00 2001 From: chenanran555 <car534511@alibaba-inc.com> Date: Wed, 2 Sep 2026 22:26:33 +0800 Subject: [PATCH 09/10] chore(deps): upgrade @openagentpack/sdk to 0.5.0 --- packages/commands/package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/commands/package.json b/packages/commands/package.json index 120a88934..590b11db3 100644 --- a/packages/commands/package.json +++ b/packages/commands/package.json @@ -40,7 +40,7 @@ "check": "vp check" }, "dependencies": { - "@openagentpack/sdk": "0.4.0-beta-2472720-20260827", + "@openagentpack/sdk": "0.5.0", "bailian-cli-core": "workspace:*", "bailian-cli-runtime": "workspace:*", "boxen": "catalog:", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fb6fb22c2..27333d153 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -189,8 +189,8 @@ importers: packages/commands: dependencies: '@openagentpack/sdk': - specifier: 0.4.0-beta-2472720-20260827 - version: 0.4.0-beta-2472720-20260827 + specifier: 0.5.0 + version: 0.5.0 bailian-cli-core: specifier: workspace:* version: link:../core @@ -1124,8 +1124,8 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 - '@openagentpack/sdk@0.4.0-beta-2472720-20260827': - resolution: {integrity: sha512-Ms9xeYKllgaeeBKvlb5O6rvSn3mHw2PWJ6SEY+08Vayti3Fi7yhRqb4vOGfC1L3+vQtl0qRf2Bqsr3b8dyx4zw==} + '@openagentpack/sdk@0.5.0': + resolution: {integrity: sha512-9mNMvPWuoiK5NQVFN5i2qAhUAFSmw9jO8xrMDXXfa6eSu0XlEAPZzR6DDg4HdhW+ex60KgA+fcNA0h0LRF552A==} engines: {node: '>=18.17.0'} '@oxc-project/runtime@0.129.0': @@ -4287,7 +4287,7 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@openagentpack/sdk@0.4.0-beta-2472720-20260827': + '@openagentpack/sdk@0.5.0': dependencies: jszip: 3.10.1 yaml: 2.9.0 From 4f41eaabc20bd5fdf3a402d5f9349fa84c0c7bca Mon Sep 17 00:00:00 2001 From: chenanran555 <car534511@alibaba-inc.com> Date: Thu, 3 Sep 2026 10:47:23 +0800 Subject: [PATCH 10/10] chore(release): prepare 1.20.0 --- CHANGELOG.md | 23 +++++++++++++++++++++++ CHANGELOG.zh.md | 23 +++++++++++++++++++++++ packages/cli/package.json | 2 +- packages/commands/package.json | 2 +- packages/core/package.json | 2 +- packages/kscli/package.json | 2 +- packages/runtime/package.json | 2 +- skills/bailian-cli/SKILL.md | 2 +- skills/bailian-finetune/SKILL.md | 2 +- skills/bailian-gen/SKILL.md | 2 +- skills/bailian-managed-agent/SKILL.md | 2 +- skills/bailian-protocol/SKILL.md | 2 +- skills/bailian-web-search/SKILL.md | 2 +- 13 files changed, 57 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88456f183..881232187 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,29 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and [中文版](CHANGELOG.zh.md) · [README](README.md) · [Contributing](CONTRIBUTING.md) +## [1.20.0] - 2026-09-03 + +> Managed Agents now combines YAML-first infrastructure management with direct Bailian AgentStudio resource and runtime operations. + +### Added + +- **Managed Agent API commands** — added direct list, get, search, version, upload, download, run, pause, archive, event, and diagnostic operations for Agents, Environments, Skills, Vaults, Deployments, Sessions, and Files. +- **Scoped YAML-backed resource creation** — `agent create`, `environment create`, `skill create`, `vault create`, `vault credential create`, and `deployment create` update `agents.yaml` and apply only the target resource without unrelated drift blocking the operation. +- **Agent Skill attachment** — Agent creation supports existing custom or official Skill IDs as well as local Skill directories and ZIP archives. + +### Changed + +- **Bailian-only Managed Agent CLI** — `bl managed-agent` now targets the Bailian provider exclusively; provider-selection flags were removed and configurations containing other providers are rejected. +- **Runtime mutation confirmation** — Deployment run/pause/unpause, Session archive/delete, and File delete operations require explicit high-risk confirmation. + +### Fixed + +- **Managed Agent error reporting** — Apply and scoped-create failures preserve the underlying provider diagnostic instead of ending with only `Apply failed.`. + +### Security + +- Credentials are resolved in memory and removed from the process environment; Vault credential declarations reference environment variables without persisting plaintext secrets. + ## [1.19.0] - 2026-09-01 ### Added diff --git a/CHANGELOG.zh.md b/CHANGELOG.zh.md index 2d89af459..dcfbba248 100644 --- a/CHANGELOG.zh.md +++ b/CHANGELOG.zh.md @@ -6,6 +6,29 @@ [English](CHANGELOG.md) · [README](README.zh.md) · [参与贡献](CONTRIBUTING.zh.md) +## [1.20.0] - 2026-09-03 + +> Managed Agent 现在同时提供 YAML-first 基础设施管理与百炼 AgentStudio 资源、运行时 API 操作。 + +### 新增 + +- **Managed Agent API 命令** —— 新增 Agent、Environment、Skill、Vault、Deployment、Session 和 File 的列表、详情、搜索、版本、上传、下载、运行、暂停、归档、事件及诊断等操作。 +- **基于 YAML 的单资源创建** —— `agent create`、`environment create`、`skill create`、`vault create`、`vault credential create` 和 `deployment create` 会更新 `agents.yaml`,并且只 Apply 目标资源,不受无关资源 Drift 阻塞。 +- **Agent Skill 挂载** —— 创建 Agent 时支持引用已有的自定义或官方 Skill ID,也支持本地 Skill 目录和 ZIP 文件。 + +### 变更 + +- **Managed Agent CLI 限定为百炼 Provider** —— `bl managed-agent` 现在只面向百炼,移除 Provider 选择参数,并拒绝包含其他 Provider 的配置。 +- **运行时变更增加确认** —— Deployment 运行/暂停/恢复、Session 归档/删除以及 File 删除操作需要显式进行高风险确认。 + +### 修复 + +- **Managed Agent 错误输出** —— Apply 和单资源创建失败时会保留底层 Provider 的具体诊断,不再只显示 `Apply failed.`。 + +### 安全 + +- 凭证仅在内存中解析并从进程环境清除;Vault Credential 声明通过环境变量引用 Secret,不会持久化明文。 + ## [1.19.0] - 2026-09-01 ### 新增 diff --git a/packages/cli/package.json b/packages/cli/package.json index cecb9dbe1..5c8ee4d5a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "bailian-cli", - "version": "1.19.0", + "version": "1.20.0", "description": "CLI for Aliyun Model Studio (DashScope) AI Platform.", "keywords": [ "agent", diff --git a/packages/commands/package.json b/packages/commands/package.json index 590b11db3..937b58741 100644 --- a/packages/commands/package.json +++ b/packages/commands/package.json @@ -1,6 +1,6 @@ { "name": "bailian-cli-commands", - "version": "1.19.0", + "version": "1.20.0", "description": "Command library for bailian-cli products (knowledge, memory, media, …). See https://www.npmjs.com/package/bailian-cli for usage.", "homepage": "https://bailian.console.aliyun.com/cli", "bugs": { diff --git a/packages/core/package.json b/packages/core/package.json index 0542e6267..6c3db390c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "bailian-cli-core", - "version": "1.19.0", + "version": "1.20.0", "description": "Core SDK for bailian-cli. See https://www.npmjs.com/package/bailian-cli for usage.", "homepage": "https://bailian.console.aliyun.com/cli", "bugs": { diff --git a/packages/kscli/package.json b/packages/kscli/package.json index b9d52cd89..fc8dad481 100644 --- a/packages/kscli/package.json +++ b/packages/kscli/package.json @@ -1,6 +1,6 @@ { "name": "knowledge-studio-cli", - "version": "1.19.0", + "version": "1.20.0", "description": "Lightweight RAG CLI for Aliyun Model Studio — focused on knowledge-base retrieval.", "keywords": [ "alibaba-cloud", diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 289f33d42..e86a528c8 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "bailian-cli-runtime", - "version": "1.19.0", + "version": "1.20.0", "description": "Runtime framework for bailian-cli (createCli, registry, args, output, pipeline). See https://www.npmjs.com/package/bailian-cli for usage.", "homepage": "https://bailian.console.aliyun.com/cli", "bugs": { diff --git a/skills/bailian-cli/SKILL.md b/skills/bailian-cli/SKILL.md index b31b0a9b9..7efd2e9ef 100644 --- a/skills/bailian-cli/SKILL.md +++ b/skills/bailian-cli/SKILL.md @@ -1,7 +1,7 @@ --- name: bailian-cli metadata: - version: "1.19.0" + version: "1.20.0" requires: bins: ["bl"] description: >- diff --git a/skills/bailian-finetune/SKILL.md b/skills/bailian-finetune/SKILL.md index 0f5e1a855..d9336e856 100644 --- a/skills/bailian-finetune/SKILL.md +++ b/skills/bailian-finetune/SKILL.md @@ -1,7 +1,7 @@ --- name: bailian-finetune metadata: - version: "1.19.0" + version: "1.20.0" requires: bins: ["bl"] description: >- diff --git a/skills/bailian-gen/SKILL.md b/skills/bailian-gen/SKILL.md index 946b04f66..29dea9d59 100644 --- a/skills/bailian-gen/SKILL.md +++ b/skills/bailian-gen/SKILL.md @@ -1,7 +1,7 @@ --- name: bailian-gen metadata: - version: "1.19.0" + version: "1.20.0" requires: bins: ["bl"] description: >- diff --git a/skills/bailian-managed-agent/SKILL.md b/skills/bailian-managed-agent/SKILL.md index 81441d96e..deb524622 100644 --- a/skills/bailian-managed-agent/SKILL.md +++ b/skills/bailian-managed-agent/SKILL.md @@ -1,7 +1,7 @@ --- name: bailian-managed-agent metadata: - version: "1.19.0" + version: "1.20.0" requires: bins: ["bl"] description: >- diff --git a/skills/bailian-protocol/SKILL.md b/skills/bailian-protocol/SKILL.md index 37b0fb635..8e1bb5a70 100644 --- a/skills/bailian-protocol/SKILL.md +++ b/skills/bailian-protocol/SKILL.md @@ -1,7 +1,7 @@ --- name: bailian-protocol metadata: - version: "1.19.0" + version: "1.20.0" requires: bins: ["bl"] description: >- diff --git a/skills/bailian-web-search/SKILL.md b/skills/bailian-web-search/SKILL.md index 39cf0947b..0030e3d3f 100644 --- a/skills/bailian-web-search/SKILL.md +++ b/skills/bailian-web-search/SKILL.md @@ -1,7 +1,7 @@ --- name: bailian-web-search metadata: - version: "1.19.0" + version: "1.20.0" requires: bins: ["bl"] description: >-