Skip to content

Commit 69c1fb9

Browse files
committed
feat(managed-agent): 增加托管 Agent 全面管理命令集及功能实现
- 扩展 CLI 命令,新增托管 Agent 相关全部操作命令包括能力、Agent、环境、技能、保管库、部署、会话及文件管理 - 实现 Agent 列表、详情、版本、搜索等功能 - 实现环境列表、详情及搜索功能 - 实现部署列表、详情、搜索、运行、暂停与恢复功能 - 实现会话的搜索、更新、归档及事件操作功能 - 实现文件上传、列表、下载、删除及搜索功能 - 添加托管 Agent API 操作能力展示命令 - 统一异常处理及输出格式支持,支持 JSON 与表格展示 - 完善分页、查询参数及权限验证逻辑,支持按需筛选与翻页 - 新增输入输出文件支持与控制台保护机制,提升命令行体验和稳定性
1 parent e76ebee commit 69c1fb9

25 files changed

Lines changed: 4900 additions & 384 deletions

packages/cli/src/commands.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,43 @@ import {
151151
managedAgentSessionSend,
152152
managedAgentSessionEvents,
153153
managedAgentSkillList,
154+
managedAgentCapabilities,
155+
managedAgentAgentList,
156+
managedAgentAgentGet,
157+
managedAgentAgentSearch,
158+
managedAgentAgentVersions,
159+
managedAgentEnvironmentList,
160+
managedAgentEnvironmentGet,
161+
managedAgentEnvironmentSearch,
162+
managedAgentSkillGet,
163+
managedAgentSkillSearch,
164+
managedAgentSkillVersions,
165+
managedAgentSkillDownload,
166+
managedAgentVaultList,
167+
managedAgentVaultGet,
168+
managedAgentVaultSearch,
169+
managedAgentDeploymentList,
170+
managedAgentDeploymentGet,
171+
managedAgentDeploymentSearch,
172+
managedAgentDeploymentRunsList,
173+
managedAgentDeploymentRunsGet,
174+
managedAgentDeploymentRun,
175+
managedAgentDeploymentPause,
176+
managedAgentDeploymentUnpause,
177+
managedAgentSessionSearch,
178+
managedAgentSessionUpdate,
179+
managedAgentSessionArchive,
180+
managedAgentSessionEventList,
181+
managedAgentSessionEventSend,
182+
managedAgentSessionEventStream,
183+
managedAgentSessionDebug,
184+
managedAgentSessionExport,
185+
managedAgentFileUpload,
186+
managedAgentFileList,
187+
managedAgentFileGet,
188+
managedAgentFileSearch,
189+
managedAgentFileDownload,
190+
managedAgentFileDelete,
154191
} from "bailian-cli-commands";
155192

156193
// Full bailian-cli product: every command, exposed under the `bl` binary.
@@ -312,6 +349,44 @@ export const commands: Record<string, AnyCommand> = {
312349
"managed-agent session send": managedAgentSessionSend,
313350
"managed-agent session events": managedAgentSessionEvents,
314351
"managed-agent skill-list": managedAgentSkillList,
352+
"managed-agent capabilities": managedAgentCapabilities,
353+
"managed-agent agent list": managedAgentAgentList,
354+
"managed-agent agent get": managedAgentAgentGet,
355+
"managed-agent agent search": managedAgentAgentSearch,
356+
"managed-agent agent versions": managedAgentAgentVersions,
357+
"managed-agent environment list": managedAgentEnvironmentList,
358+
"managed-agent environment get": managedAgentEnvironmentGet,
359+
"managed-agent environment search": managedAgentEnvironmentSearch,
360+
"managed-agent skill list": managedAgentSkillList,
361+
"managed-agent skill get": managedAgentSkillGet,
362+
"managed-agent skill search": managedAgentSkillSearch,
363+
"managed-agent skill versions": managedAgentSkillVersions,
364+
"managed-agent skill download": managedAgentSkillDownload,
365+
"managed-agent vault list": managedAgentVaultList,
366+
"managed-agent vault get": managedAgentVaultGet,
367+
"managed-agent vault search": managedAgentVaultSearch,
368+
"managed-agent deployment list": managedAgentDeploymentList,
369+
"managed-agent deployment get": managedAgentDeploymentGet,
370+
"managed-agent deployment search": managedAgentDeploymentSearch,
371+
"managed-agent deployment runs list": managedAgentDeploymentRunsList,
372+
"managed-agent deployment runs get": managedAgentDeploymentRunsGet,
373+
"managed-agent deployment run": managedAgentDeploymentRun,
374+
"managed-agent deployment pause": managedAgentDeploymentPause,
375+
"managed-agent deployment unpause": managedAgentDeploymentUnpause,
376+
"managed-agent session search": managedAgentSessionSearch,
377+
"managed-agent session update": managedAgentSessionUpdate,
378+
"managed-agent session archive": managedAgentSessionArchive,
379+
"managed-agent session event list": managedAgentSessionEventList,
380+
"managed-agent session event send": managedAgentSessionEventSend,
381+
"managed-agent session event stream": managedAgentSessionEventStream,
382+
"managed-agent session debug": managedAgentSessionDebug,
383+
"managed-agent session export": managedAgentSessionExport,
384+
"managed-agent file upload": managedAgentFileUpload,
385+
"managed-agent file list": managedAgentFileList,
386+
"managed-agent file get": managedAgentFileGet,
387+
"managed-agent file search": managedAgentFileSearch,
388+
"managed-agent file download": managedAgentFileDownload,
389+
"managed-agent file delete": managedAgentFileDelete,
315390
};
316391

317392
/**
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import type { FlagsDef } from "bailian-cli-core";
2+
import { emitBare, emitResult, formatTable } from "bailian-cli-runtime";
3+
import type { PagedResult } from "./pagination.ts";
4+
5+
export const API_TARGET_FLAGS = {
6+
file: {
7+
type: "string",
8+
valueHint: "<path>",
9+
description: {
10+
"en-US": "Config file path (default: agents.yaml)",
11+
"zh-CN": "配置文件路径(默认:agents.yaml)",
12+
},
13+
},
14+
provider: {
15+
type: "string",
16+
valueHint: "<name>",
17+
description: { "en-US": "Target provider", "zh-CN": "目标 Provider" },
18+
},
19+
} satisfies FlagsDef;
20+
21+
export const CURSOR_FLAGS = {
22+
limit: {
23+
type: "number",
24+
valueHint: "<n>",
25+
description: {
26+
"en-US": "Page size (1-100)",
27+
"zh-CN": "单页数量(1-100)",
28+
},
29+
},
30+
page: {
31+
type: "string",
32+
valueHint: "<cursor>",
33+
description: {
34+
"en-US": "Opaque page cursor returned by a previous request",
35+
"zh-CN": "上一次请求返回的不透明分页 Cursor",
36+
},
37+
},
38+
all: {
39+
type: "switch",
40+
description: {
41+
"en-US": "Fetch all pages by following opaque cursors",
42+
"zh-CN": "跟随不透明 Cursor 获取全部分页",
43+
},
44+
},
45+
} satisfies FlagsDef;
46+
47+
export const SEARCH_FLAGS = {
48+
query: {
49+
type: "string",
50+
valueHint: "<text>",
51+
required: true,
52+
description: {
53+
"en-US": "Case-insensitive text to find in IDs, names, and descriptions",
54+
"zh-CN": "在 ID、名称和描述中进行不区分大小写的文本搜索",
55+
},
56+
},
57+
pageLimit: {
58+
type: "number",
59+
valueHint: "<n>",
60+
description: {
61+
"en-US": "Maximum pages to scan for client-side search (default: 10)",
62+
"zh-CN": "客户端搜索最多扫描的页数(默认:10)",
63+
},
64+
},
65+
} satisfies FlagsDef;
66+
67+
export const INCLUDE_ARCHIVED_FLAG = {
68+
includeArchived: {
69+
type: "switch",
70+
description: {
71+
"en-US": "Include archived resources",
72+
"zh-CN": "包含已归档资源",
73+
},
74+
},
75+
} satisfies FlagsDef;
76+
77+
export function validateLimitAndPageLimit(flags: {
78+
limit?: number;
79+
pageLimit?: number;
80+
}): string | undefined {
81+
if (
82+
flags.limit !== undefined &&
83+
(!Number.isInteger(flags.limit) || flags.limit < 1 || flags.limit > 100)
84+
) {
85+
return "--limit must be an integer between 1 and 100.";
86+
}
87+
if (
88+
flags.pageLimit !== undefined &&
89+
(!Number.isInteger(flags.pageLimit) || flags.pageLimit < 1 || flags.pageLimit > 100)
90+
) {
91+
return "--page-limit must be an integer between 1 and 100.";
92+
}
93+
return undefined;
94+
}
95+
96+
export function splitCommaSeparated(value?: string): string[] | undefined {
97+
if (!value) return undefined;
98+
const values = value
99+
.split(",")
100+
.map((entry) => entry.trim())
101+
.filter(Boolean);
102+
return values.length > 0 ? values : undefined;
103+
}
104+
105+
export function matchesQuery(query: string, ...values: unknown[]): boolean {
106+
const normalized = query.trim().toLocaleLowerCase();
107+
if (!normalized) return true;
108+
return values.some((value) => {
109+
if (value === undefined || value === null) return false;
110+
const text = typeof value === "string" ? value : JSON.stringify(value);
111+
return text.toLocaleLowerCase().includes(normalized);
112+
});
113+
}
114+
115+
export interface SearchResult<T> extends PagedResult<T> {
116+
scannedPages: number;
117+
truncated: boolean;
118+
}
119+
120+
export async function searchCursorPages<T>(
121+
fetchPage: (page?: string) => Promise<PagedResult<T>>,
122+
matches: (item: T) => boolean,
123+
pageLimit = 10,
124+
): Promise<SearchResult<T>> {
125+
const items: T[] = [];
126+
let page: string | undefined;
127+
let hasMore = false;
128+
let nextPage: string | undefined;
129+
let scannedPages = 0;
130+
131+
do {
132+
const result = await fetchPage(page);
133+
scannedPages += 1;
134+
items.push(...result.items.filter(matches));
135+
hasMore = result.hasMore;
136+
nextPage = result.nextPage;
137+
page = result.nextPage;
138+
} while (hasMore && page && scannedPages < pageLimit);
139+
140+
return {
141+
items,
142+
hasMore,
143+
nextPage,
144+
scannedPages,
145+
truncated: Boolean(hasMore && nextPage),
146+
};
147+
}
148+
149+
export function emitCollection<T>(options: {
150+
format: "json" | "text";
151+
key: string;
152+
items: T[];
153+
headers: string[];
154+
rows: string[][];
155+
hasMore?: boolean;
156+
nextPage?: string;
157+
truncated?: boolean;
158+
scannedPages?: number;
159+
emptyMessage?: string;
160+
}): void {
161+
const {
162+
format,
163+
key,
164+
items,
165+
headers,
166+
rows,
167+
hasMore = false,
168+
nextPage,
169+
truncated,
170+
scannedPages,
171+
emptyMessage = "No resources found.",
172+
} = options;
173+
if (format === "json") {
174+
emitResult(
175+
{
176+
[key]: items,
177+
has_more: hasMore,
178+
next_page: nextPage,
179+
...(truncated === undefined ? {} : { truncated }),
180+
...(scannedPages === undefined ? {} : { scanned_pages: scannedPages }),
181+
},
182+
format,
183+
);
184+
return;
185+
}
186+
if (items.length === 0) {
187+
emitBare(emptyMessage);
188+
return;
189+
}
190+
for (const line of formatTable(headers, rows)) emitBare(line);
191+
emitBare(`\nTotal: ${items.length}`);
192+
if (truncated) emitBare("Search stopped at --page-limit; more pages remain.");
193+
else if (hasMore)
194+
emitBare(`More results are available.${nextPage ? ` Next page: ${nextPage}` : ""}`);
195+
}
196+
197+
export function displayValue(value: unknown, maxLength = 40): string {
198+
if (value === undefined || value === null || value === "") return "-";
199+
const text = typeof value === "string" ? value : JSON.stringify(value);
200+
return text.length > maxLength ? `${text.slice(0, maxLength - 3)}...` : text;
201+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { randomUUID } from "node:crypto";
2+
import { link, mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
3+
import { basename, dirname, resolve } from "node:path";
4+
import { BailianError, ExitCode } from "bailian-cli-core";
5+
6+
export async function readInputFile(path: string): Promise<Uint8Array> {
7+
return new Uint8Array(await readFile(resolve(path)));
8+
}
9+
10+
export async function readJsonInput(argument: string): Promise<unknown> {
11+
const source = argument.startsWith("@")
12+
? await readFile(resolve(argument.slice(1)), "utf8")
13+
: argument;
14+
try {
15+
return JSON.parse(source) as unknown;
16+
} catch (error) {
17+
const message = error instanceof Error ? error.message : String(error);
18+
throw new BailianError(`Invalid JSON input: ${message}`, ExitCode.USAGE);
19+
}
20+
}
21+
22+
export async function writeOutputFile(
23+
outputPath: string,
24+
content: Uint8Array,
25+
force = false,
26+
): Promise<string> {
27+
const destination = resolve(outputPath);
28+
const parent = dirname(destination);
29+
await mkdir(parent, { recursive: true });
30+
const temporary = `${destination}.${randomUUID()}.tmp`;
31+
await writeFile(temporary, content, { flag: "wx" });
32+
try {
33+
if (force) {
34+
await rename(temporary, destination);
35+
} else {
36+
try {
37+
await link(temporary, destination);
38+
} catch (error) {
39+
const code = (error as NodeJS.ErrnoException).code;
40+
if (code === "EEXIST") {
41+
throw new BailianError(
42+
`Output file already exists: ${destination}`,
43+
ExitCode.USAGE,
44+
"Choose another --output-file path or re-run with --force.",
45+
);
46+
}
47+
throw error;
48+
}
49+
await unlink(temporary);
50+
}
51+
return destination;
52+
} catch (error) {
53+
await unlink(temporary).catch(() => undefined);
54+
throw error;
55+
}
56+
}
57+
58+
export function inferMimeType(path: string): string {
59+
const filename = basename(path).toLocaleLowerCase();
60+
if (filename.endsWith(".json")) return "application/json";
61+
if (filename.endsWith(".md")) return "text/markdown";
62+
if (filename.endsWith(".txt")) return "text/plain";
63+
if (filename.endsWith(".pdf")) return "application/pdf";
64+
if (filename.endsWith(".png")) return "image/png";
65+
if (filename.endsWith(".jpg") || filename.endsWith(".jpeg")) return "image/jpeg";
66+
if (filename.endsWith(".zip")) return "application/zip";
67+
return "application/octet-stream";
68+
}

packages/commands/src/commands/managed-agent/_engine/pagination.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ export interface PagedResult<T> {
88
export async function fetchAllPages<T>(
99
fetchPage: (page?: string) => Promise<PagedResult<T>>,
1010
all?: boolean,
11+
initialPage?: string,
1112
): Promise<PagedResult<T>> {
12-
const first = await fetchPage();
13+
const first = await fetchPage(initialPage);
1314
const items = [...first.items];
1415
let hasMore = first.hasMore;
1516
let nextPage = first.nextPage;

0 commit comments

Comments
 (0)