diff --git a/CHANGELOG.md b/CHANGELOG.md index e4ac357a..263ecb19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,23 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and [中文版](CHANGELOG.zh.md) · [README](README.md) · [Contributing](CONTRIBUTING.md) +## [1.22.0] - 2026-09-08 + +### Changed + +- **Project initialization** — `managed-agent project init` now creates `./managed-agent` by default. Use `--project .` to initialize in place. **(BREAKING)** +- **Build confirmation** — `managed-agent project build` no longer requires confirmation and rejects `--yes`. Use `--dry-run` for a read-only preview; Publish still requires confirmation. **(BREAKING)** +- **Managed Agent SDK** — upgrade to `0.7.1`. Build automatically associates active Agent-local resources while preserving explicit bindings, Skill versions, and File mount paths. Ambiguous Environment or Vault selections are rejected before writing. + +### Fixed + +- **Project diagnostics** — provide actionable project-root guidance and surface the underlying Build validation error. +- **YAML initialization paths** — show the absolute YAML path in creation messages and existing-file errors. + +### Internal + +- Expand project initialization and Build regression coverage, and remove the obsolete Build confirmation flag from the local lifecycle E2E test. + ## [1.21.0] - 2026-09-07 ### Added diff --git a/CHANGELOG.zh.md b/CHANGELOG.zh.md index 4e08c3eb..5c82a3f7 100644 --- a/CHANGELOG.zh.md +++ b/CHANGELOG.zh.md @@ -6,6 +6,23 @@ [English](CHANGELOG.md) · [README](README.zh.md) · [参与贡献](CONTRIBUTING.zh.md) +## [1.22.0] - 2026-09-08 + +### 变更 + +- **项目初始化** —— `managed-agent project init` 默认创建 `./managed-agent` 子目录;如需原地初始化,请使用 `--project .`。**(BREAKING)** +- **Build 确认机制** —— `managed-agent project build` 无需确认,并且不再接受 `--yes`。使用 `--dry-run` 可只读预览;Publish 仍需显式确认。**(BREAKING)** +- **Managed Agent SDK** —— 升级至 `0.7.1`。Build 自动关联 Agent 目录下已启用的资源,保留显式引用、Skill 版本和 File 挂载路径;Environment 或 Vault 选择存在歧义时,在写入前报错。 + +### 修复 + +- **项目诊断** —— 提供可操作的项目根目录提示,并展示 Build 校验失败的具体原因。 +- **YAML 初始化路径** —— 创建成功及文件已存在的错误信息均展示 YAML 绝对路径。 + +### 内部 + +- 补充项目初始化和 Build 回归覆盖,移除本地闭环 E2E 测试中过时的 Build 确认参数。 + ## [1.21.0] - 2026-09-07 ### 新增 diff --git a/docs/agents/command-flag-change.md b/docs/agents/command-flag-change.md index d65805ac..a8ff6531 100644 --- a/docs/agents/command-flag-change.md +++ b/docs/agents/command-flag-change.md @@ -19,6 +19,7 @@ - 类型由 `ParsedFlags` 推导;避免手写 `flags.x as number` 这类断言 - 单 flag 必填用 `required: true`;跨 flag / 值相关校验放 `validate` - 默认值 fallback 写在命令实现或 `Settings` 解析层,不要重复解析 env/config + - 需要在高风险确认前检查本地路径时,可用异步 `validate`;runtime 会在鉴权和确认前等待它完成。这里只允许本地只读检查,不写文件、不请求远端。非缺参的环境错误应抛出 `BailianError`,避免裸命令调用被当成缺参而仅显示 help。 ### B. 鉴权 / 全局选项 diff --git a/packages/cli/package.json b/packages/cli/package.json index aa42cacf..08d84fc8 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "bailian-cli", - "version": "1.21.0", + "version": "1.22.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 040d3b66..574f19b9 100644 --- a/packages/commands/package.json +++ b/packages/commands/package.json @@ -1,6 +1,6 @@ { "name": "bailian-cli-commands", - "version": "1.21.0", + "version": "1.22.0", "description": "Command library for bailian-cli products (knowledge, memory, media, …). See https://www.npmjs.com/package/bailian-cli for usage.", "homepage": "https://bailian.console.aliyun.com/cli", "bugs": { @@ -40,7 +40,7 @@ "check": "vp check" }, "dependencies": { - "@openagentpack/sdk": "0.7.0", + "@openagentpack/sdk": "0.7.1", "bailian-cli-core": "workspace:*", "bailian-cli-runtime": "workspace:*", "boxen": "catalog:", diff --git a/packages/commands/src/commands/managed-agent/init.ts b/packages/commands/src/commands/managed-agent/init.ts index bd6ac714..210cbf23 100644 --- a/packages/commands/src/commands/managed-agent/init.ts +++ b/packages/commands/src/commands/managed-agent/init.ts @@ -1,5 +1,6 @@ import { existsSync } from "node:fs"; import { readFile, writeFile } from "node:fs/promises"; +import { resolve } from "node:path"; import { BailianError, defineCommand, @@ -87,7 +88,7 @@ export default defineCommand({ if (existsSync(file) && !flags.force) { throw new BailianError( - `${file} already exists.`, + `${resolve(file)} already exists.`, ExitCode.USAGE, "Pass --force to overwrite.", ); @@ -128,7 +129,7 @@ export default defineCommand({ if (format === "json") { emitResult({ created: file, provider: "bailian", agent: agentName }, format); } else { - emitBare(`Created ${file}`); + emitBare(`Created ${resolve(file)}`); emitBare( "Credentials: run `bl auth login --api-key --base-url `, or set DASHSCOPE_API_KEY / BAILIAN_BASE_URL.", ); diff --git a/packages/commands/src/commands/managed-agent/project.ts b/packages/commands/src/commands/managed-agent/project.ts index 50330c4d..452c6b03 100644 --- a/packages/commands/src/commands/managed-agent/project.ts +++ b/packages/commands/src/commands/managed-agent/project.ts @@ -1,3 +1,5 @@ +import { stat } from "node:fs/promises"; +import { join } from "node:path"; import { BailianError, defineCommand, @@ -14,6 +16,7 @@ import { planProjectPublish, previewProjectBuild, type ProjectBuildResolver, + resolveDirectoryProjectRoot, validateDirectoryProject, } from "@openagentpack/sdk/project-workspace"; import { CREDENTIALS_NOTE, resolveAgentProjectConfig } from "./_engine/config-loader.ts"; @@ -44,9 +47,23 @@ export const managedAgentProjectInit = defineCommand({ }, auth: "none", usageArgs: "[--project ]", - flags: PROJECT_FLAG, - exampleArgs: ["", "--project ./my-agent"], + flags: { + project: { + ...PROJECT_FLAG.project, + description: { + "en-US": "Directory project root (default: ./managed-agent under the current directory)", + "zh-CN": "目录项目根路径(默认:当前目录下的 ./managed-agent)", + }, + }, + }, + exampleArgs: ["", "--project ./my-agent", "--project ."], notes: [ + { + "en-US": + "Without --project, creates a managed-agent/ subdirectory. Enter it before running other project commands. Use --project . to initialize in place or convert the current agents.yaml; existing project files are not overwritten.", + "zh-CN": + "不传 --project 时创建 managed-agent/ 子目录;后续项目操作请先进入该目录。使用 --project . 可在当前目录初始化或转换 agents.yaml;不会覆盖已有项目文件。", + }, { "en-US": "New projects include Skill, File, Vault, and Environment examples under each resource directory's _examples/. They are not referenced by agent.json and are excluded from Build/Publish. Copy an example outside _examples/ to enable it, then configure its Agent reference.", @@ -55,16 +72,17 @@ export const managedAgentProjectInit = defineCommand({ }, ], async run(ctx) { + const projectRoot = ctx.flags.project ?? "./managed-agent"; if (ctx.settings.dryRun) { emitResult( { - would_initialize_project: ctx.flags.project ?? ".", + would_initialize_project: projectRoot, }, detectOutputFormat(ctx.settings.output), ); return; } - const result = await initializeDirectoryProject({ projectRoot: ctx.flags.project ?? "." }); + const result = await initializeDirectoryProject({ projectRoot }); emitResult(result, detectOutputFormat(ctx.settings.output)); }, }); @@ -99,18 +117,34 @@ export const managedAgentProjectBuild = defineCommand({ "zh-CN": "整理目录源文件并生成不可变的发布 Build", }, auth: "none", - risk: { - level: "high", - message: { + notes: [ + { "en-US": - "This organizes project source, moves literal Vault secrets into the local .env, and writes the previewed immutable Build.", + "Build writes local project files without confirmation, including inferred resource associations and migration of plaintext Vault secrets into .env. Use --dry-run to preview without writing. Publish still requires explicit confirmation before remote changes.", "zh-CN": - "该操作会整理项目源文件,将 Vault 明文密钥移入本地 .env,并写入已预览的不可变 Build。", + "Build 无需确认即可写入本地项目文件,包括推断的资源关联及将 Vault 明文密钥移入 .env。使用 --dry-run 可只预览不写入。Publish 变更远端资源前仍需显式确认。", }, - }, + ], usageArgs: "[--project ]", flags: PROJECT_FLAG, - exampleArgs: ["--dry-run", "--yes", "--project ./my-agent --yes"], + exampleArgs: ["", "--dry-run", "--project ./my-agent"], + async validate(flags) { + await withAgentErrors(async () => { + const root = await resolveDirectoryProjectRoot(flags.project ?? "."); + const metadata = await stat(join(root, "project.json")).catch((error: unknown) => { + if ((error as NodeJS.ErrnoException).code === "ENOENT") return null; + throw error; + }); + if (!metadata?.isFile()) { + throw new BailianError( + `Not a project root: ${root} (project.json is missing).`, + ExitCode.USAGE, + "Run from the directory containing project.json, or pass --project .", + ); + } + }); + return undefined; + }, async run(ctx) { const root = ctx.flags.project ?? "."; const preview = await previewProjectBuild(root); @@ -120,7 +154,11 @@ export const managedAgentProjectBuild = defineCommand({ return; } if (!preview.can_build) - throw new BailianError("Directory project is invalid and cannot be built.", ExitCode.GENERAL); + throw new BailianError( + preview.diagnostics.find((diagnostic) => diagnostic.severity === "error")?.message ?? + "Directory project is invalid and cannot be built.", + ExitCode.GENERAL, + ); const built = await commitProjectBuild({ projectRoot: root, baseRevision: preview.project_revision, diff --git a/packages/commands/tests/e2e/managed-agent-init.e2e.test.ts b/packages/commands/tests/e2e/managed-agent-init.e2e.test.ts new file mode 100644 index 00000000..0f8daba7 --- /dev/null +++ b/packages/commands/tests/e2e/managed-agent-init.e2e.test.ts @@ -0,0 +1,112 @@ +import { mkdir, mkdtemp, readFile, realpath, rm, stat, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { runNodeMain } from "e2e/runner"; +import { afterEach, describe, expect, test } from "vite-plus/test"; +import { parseStdoutJson } from "./helpers.ts"; + +const directories: string[] = []; + +async function temporaryDirectory() { + const directory = await realpath(await mkdtemp(join(tmpdir(), "bailian-yaml-init-"))); + directories.push(directory); + return directory; +} + +afterEach(async () => { + for (const directory of directories.splice(0)) { + await rm(directory, { recursive: true, force: true }); + } +}); + +async function runInit(directory: string, args: string[] = []) { + return runNodeMain( + fileURLToPath(new URL("./harness/main.ts", import.meta.url)), + ["managed-agent", "init", ...args], + { + cwd: directory, + env: { + BAILIAN_CONFIG_DIR: await temporaryDirectory(), + BAILIAN_E2E_ROUTES: JSON.stringify([ + { path: "managed-agent init", export: "managedAgentInit" }, + ]), + }, + }, + ); +} + +describe("e2e: managed-agent init output path", () => { + test.each(["default", "relative", "absolute"] as const)( + "reports the absolute YAML path for a %s output path", + async (pathKind) => { + const directory = await temporaryDirectory(); + const relativePath = + pathKind === "default" ? "agents.yaml" : join("config files", "custom agents.yaml"); + const outputPath = join(directory, relativePath); + await mkdir(dirname(outputPath), { recursive: true }); + const args = + pathKind === "default" + ? [] + : ["--file", pathKind === "absolute" ? outputPath : relativePath]; + + const result = await runInit(directory, args); + + expect(result.exitCode, result.stderr).toBe(0); + expect(result.stdout.split(/\r?\n/)[0]).toBe(`Created ${outputPath}`); + expect(await readFile(outputPath, "utf8")).toContain("agents:"); + }, + ); + + test("preserves the JSON output contract", async () => { + const directory = await temporaryDirectory(); + const result = await runInit(directory, ["--file", "custom.yaml", "--output", "json"]); + + expect(result.exitCode, result.stderr).toBe(0); + expect(parseStdoutJson(result.stdout)).toEqual({ + created: "custom.yaml", + provider: "bailian", + agent: "assistant", + }); + expect((await stat(join(directory, "custom.yaml"))).isFile()).toBe(true); + }); + + test("keeps dry-run read-only without reporting a created file", async () => { + const directory = await temporaryDirectory(); + const result = await runInit(directory, ["--dry-run", "--output", "json"]); + + expect(result.exitCode, result.stderr).toBe(0); + expect(parseStdoutJson(result.stdout)).toEqual({ + would_create: "agents.yaml", + provider: "bailian", + agent: "assistant", + would_update_gitignore: true, + }); + expect(await stat(join(directory, "agents.yaml")).catch(() => null)).toBeNull(); + expect(await stat(join(directory, ".gitignore")).catch(() => null)).toBeNull(); + }); + + test.each(["default", "relative", "absolute"] as const)( + "reports the absolute existing %s path without overwriting the YAML", + async (pathKind) => { + const directory = await temporaryDirectory(); + const relativePath = + pathKind === "default" ? "agents.yaml" : join("config files", "custom agents.yaml"); + const outputPath = join(directory, relativePath); + await mkdir(dirname(outputPath), { recursive: true }); + await writeFile(outputPath, "Keep user configuration.\n"); + const args = + pathKind === "default" + ? [] + : ["--file", pathKind === "absolute" ? outputPath : relativePath]; + + const result = await runInit(directory, args); + + expect(result.exitCode).toBe(2); + expect(result.stderr).toContain(`${outputPath} already exists.`); + expect(result.stderr).toContain("Pass --force to overwrite."); + expect(result.stdout).not.toContain("Created "); + expect(await readFile(outputPath, "utf8")).toBe("Keep user configuration.\n"); + }, + ); +}); diff --git a/packages/commands/tests/e2e/managed-agent-project-init.e2e.test.ts b/packages/commands/tests/e2e/managed-agent-project-init.e2e.test.ts new file mode 100644 index 00000000..855e3df5 --- /dev/null +++ b/packages/commands/tests/e2e/managed-agent-project-init.e2e.test.ts @@ -0,0 +1,247 @@ +import { + cp, + mkdir, + mkdtemp, + readFile, + realpath, + rm, + stat, + symlink, + writeFile, +} from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { runNodeMain } from "e2e/runner"; +import { afterEach, describe, expect, test } from "vite-plus/test"; +import { parseStdoutJson, runCommandHelp } from "./helpers.ts"; + +const routes = { + "managed-agent project init": "managedAgentProjectInit", + "managed-agent project build": "managedAgentProjectBuild", + "managed-agent project publish": "managedAgentProjectPublish", + "managed-agent project validate": "managedAgentProjectValidate", +} as const; +const directories: string[] = []; + +async function temporaryDirectory() { + const directory = await realpath(await mkdtemp(join(tmpdir(), "bailian-project-init-"))); + directories.push(directory); + return directory; +} + +afterEach(async () => { + for (const directory of directories.splice(0)) { + await rm(directory, { recursive: true, force: true }); + } +}); + +function runInit(directory: string, args: string[] = []) { + return runProject(directory, "init", args); +} + +async function runProject(directory: string, subcommand: string, args: string[] = [], json = true) { + const configRoot = await temporaryDirectory(); + return runNodeMain( + fileURLToPath(new URL("./harness/main.ts", import.meta.url)), + ["managed-agent", "project", subcommand, ...args, ...(json ? ["--output", "json"] : [])], + { + cwd: directory, + env: { + BAILIAN_CONFIG_DIR: configRoot, + BAILIAN_E2E_ROUTES: JSON.stringify( + Object.entries(routes).map(([path, exportName]) => ({ path, export: exportName })), + ), + }, + }, + ); +} + +describe("e2e: managed-agent project init directory defaults", () => { + test("build links copied resources and reports ambiguous environment bindings without writing", async () => { + const directory = await temporaryDirectory(); + const initialized = await runInit(directory); + expect(initialized.exitCode, initialized.stderr).toBe(0); + const root = join(directory, "managed-agent"); + for (const [section, id] of [ + ["skills", "example-skill"], + ["files", "example-file"], + ["environments", "example-env"], + ["vaults", "example-vault"], + ] as const) { + await cp( + join(root, "agents/assistant", section, "_examples", id), + join(root, "agents/assistant", section, id), + { recursive: true }, + ); + } + const agentPath = join(root, "agents/assistant/agent.json"); + const original = await readFile(agentPath, "utf8"); + const preview = await runProject(root, "build", ["--dry-run"]); + expect(preview.exitCode, preview.stderr).toBe(0); + expect(parseStdoutJson<{ can_build: boolean }>(preview.stdout).can_build).toBe(true); + expect(await readFile(agentPath, "utf8")).toBe(original); + const built = await runProject(root, "build"); + expect(built.exitCode, built.stderr).toBe(0); + const agent = JSON.parse(await readFile(agentPath, "utf8")); + expect(agent).toMatchObject({ + environment: "example-env", + vault: "example-vault", + skills: ["example-skill"], + files: [{ file: "example-file", mount_path: "/mnt/example.md" }], + }); + const alternatePath = join(root, "agents/assistant/environments/alternate"); + await mkdir(alternatePath); + await writeFile( + join(alternatePath, "environment.json"), + JSON.stringify({ id: "alternate", config: { type: "cloud" } }), + ); + delete agent.environment; + await writeFile(agentPath, JSON.stringify(agent)); + const beforeConflict = await readFile(agentPath, "utf8"); + const buildPath = join(root, ".openagentpack/build/agents.yaml"); + const beforeBuild = await readFile(buildPath, "utf8"); + for (const json of [false, true]) { + const conflict = await runProject(root, "build", [], json); + expect(conflict.exitCode).toBe(1); + expect(conflict.stderr).toContain("multiple local environment resources"); + expect(conflict.stderr).toContain("Set 'environment' explicitly"); + expect(conflict.stderr).not.toMatch(/\p{Script=Han}/u); + } + expect(await readFile(agentPath, "utf8")).toBe(beforeConflict); + expect(await readFile(buildPath, "utf8")).toBe(beforeBuild); + }); + + test("build checks directories and writes without confirmation while publish stays gated", async () => { + const directory = await temporaryDirectory(); + const initialized = await runInit(directory); + expect(initialized.exitCode, initialized.stderr).toBe(0); + const root = join(directory, "managed-agent"); + const nested = join(root, "agents/assistant/skills"); + for (const args of [[], ["--dry-run"]]) { + const result = await runProject(nested, "build", args, false); + expect(result.exitCode, result.stderr).toBe(2); + expect(result.stderr).toContain("Not a project root:"); + expect(result.stderr).not.toMatch(/\p{Script=Han}/u); + expect(result.stderr).toContain(`cd '${root}'`); + expect(result.stderr).not.toContain("high-risk"); + expect(result.stderr).not.toContain("Usage:"); + } + const explicit = await runProject(root, "build", ["--project", nested]); + expect(explicit.exitCode).toBe(2); + expect(explicit.stderr).toContain(`--project '${root}'`); + const noMarker = await runProject(directory, "build", [], false); + expect(noMarker.exitCode).toBe(2); + expect(noMarker.stderr).toContain("project.json"); + expect(noMarker.stderr).not.toMatch(/\p{Script=Han}/u); + expect(noMarker.stderr).not.toContain("high-risk"); + expect(await stat(join(nested, ".openagentpack")).catch(() => null)).toBeNull(); + + const preview = await runProject(root, "build", ["--dry-run"]); + expect(preview.exitCode, preview.stderr).toBe(0); + expect(await stat(join(root, ".openagentpack/build")).catch(() => null)).toBeNull(); + const built = await runProject(root, "build"); + expect(built.exitCode, built.stderr).toBe(0); + expect(built.stderr).not.toContain("requires_confirmation"); + expect((await stat(join(root, ".openagentpack/build/agents.yaml"))).isFile()).toBe(true); + const explicitValid = await runProject(nested, "build", ["--project", root]); + expect(explicitValid.exitCode, explicitValid.stderr).toBe(0); + const storePath = join(root, ".openagentpack/versions/project/store.json"); + const beforePublish = await readFile(storePath, "utf8"); + const publish = await runProject(root, "publish"); + expect(publish.exitCode).toBe(7); + expect(publish.stderr).toContain("requires_confirmation"); + expect(await readFile(storePath, "utf8")).toBe(beforePublish); + }); + + test("only publish help includes confirmation; build keeps dry-run", async () => { + const build = await runCommandHelp(routes, ["managed-agent", "project", "build", "--help"]); + expect(build.stderr).not.toContain("--yes"); + expect(build.stderr).toContain("--dry-run"); + const publish = await runCommandHelp(routes, ["managed-agent", "project", "publish", "--help"]); + expect(publish.stderr).toContain("--yes"); + const directory = await temporaryDirectory(); + const removedFlag = await runProject(directory, "build", ["--yes"]); + expect(removedFlag.exitCode).toBe(2); + expect(removedFlag.stderr).toMatch(/Unknown flag.*--yes/); + }); + + test("nested build and validate explain the project root without changing directories", async () => { + const directory = await temporaryDirectory(); + const initialized = await runInit(directory); + expect(initialized.exitCode, initialized.stderr).toBe(0); + const root = join(directory, "managed-agent"); + const nested = join(root, "agents/assistant/skills"); + for (const command of ["build", "validate"]) { + const result = await runProject(nested, command, ["--dry-run"]); + expect(result.exitCode).not.toBe(0); + expect(result.stderr).toContain("Not a project root:"); + expect(result.stderr).not.toMatch(/\p{Script=Han}/u); + expect(result.stderr).toContain(`cd '${root}'`); + expect(result.stderr).toContain(`--project '${root}'`); + expect(result.stderr).not.toContain("ERR_MODULE_NOT_FOUND"); + } + expect(await stat(join(nested, ".openagentpack")).catch(() => null)).toBeNull(); + const corrected = await runProject(nested, "build", ["--project", root, "--dry-run"]); + expect(corrected.exitCode, corrected.stderr).toBe(0); + }); + + test("help describes the subdirectory default and explicit in-place initialization", async () => { + const result = await runCommandHelp(routes, ["managed-agent", "project", "init", "--help"]); + expect(result.exitCode).toBe(0); + expect(result.stderr).toContain("./managed-agent"); + expect(result.stderr).toContain("--project ."); + }); + + test("default dry-run reports the child directory without creating it", async () => { + const directory = await temporaryDirectory(); + const result = await runInit(directory, ["--dry-run"]); + expect(result.exitCode, result.stderr).toBe(0); + expect(parseStdoutJson(result.stdout)).toEqual({ would_initialize_project: "./managed-agent" }); + expect(await stat(join(directory, "managed-agent")).catch(() => null)).toBeNull(); + }); + + test("initializes only the child, ignores parent source, and rejects repeated init without overwriting", async () => { + const directory = await temporaryDirectory(); + const parentYaml = "not a valid project declaration"; + await writeFile(join(directory, "agents.yaml"), parentYaml); + if (process.platform !== "win32") { + await symlink("missing-instructions.md", join(directory, "CLAUDE.md")); + } + const result = await runInit(directory); + expect(result.exitCode, result.stderr).toBe(0); + const projectRoot = join(directory, "managed-agent"); + const initialized = parseStdoutJson<{ + project_root: string; + baseline_version: string; + converted_from_yaml: boolean; + }>(result.stdout); + expect(initialized.project_root).toBe(projectRoot); + expect(initialized.baseline_version).toHaveLength(64); + expect(initialized.converted_from_yaml).toBe(false); + expect(await stat(join(directory, "project.json")).catch(() => null)).toBeNull(); + expect(await readFile(join(directory, "agents.yaml"), "utf8")).toBe(parentYaml); + expect((await stat(join(projectRoot, "agents/assistant/agent.json"))).isFile()).toBe(true); + expect( + (await stat(join(projectRoot, ".openagentpack/versions/project/store.json"))).isFile(), + ).toBe(true); + const instructions = join(projectRoot, "agents/assistant/instructions.md"); + await writeFile(instructions, "user changes"); + const repeated = await runInit(directory); + expect(repeated.exitCode).not.toBe(0); + expect(repeated.stderr).toContain("already exists"); + expect(await readFile(instructions, "utf8")).toBe("user changes"); + }); + + test("explicit paths, including dot, remain exact project roots", async () => { + for (const target of ["custom-agent", "."]) { + const directory = await temporaryDirectory(); + const result = await runInit(directory, ["--project", target]); + expect(result.exitCode, result.stderr).toBe(0); + expect(parseStdoutJson<{ project_root: string }>(result.stdout).project_root).toBe( + join(directory, target), + ); + expect(await stat(join(directory, "managed-agent")).catch(() => null)).toBeNull(); + } + }); +}); diff --git a/packages/commands/tests/e2e/managed-agent.e2e.test.ts b/packages/commands/tests/e2e/managed-agent.e2e.test.ts index 30f57770..6d6dd407 100644 --- a/packages/commands/tests/e2e/managed-agent.e2e.test.ts +++ b/packages/commands/tests/e2e/managed-agent.e2e.test.ts @@ -368,7 +368,6 @@ describe("e2e: managed-agent", () => { "build", "--project", projectRoot, - "--yes", "--output", "json", ]); diff --git a/packages/core/package.json b/packages/core/package.json index 3b17cafd..d15b40e8 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "bailian-cli-core", - "version": "1.21.0", + "version": "1.22.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/core/src/types/command.ts b/packages/core/src/types/command.ts index 66b62681..0a19de9c 100644 --- a/packages/core/src/types/command.ts +++ b/packages/core/src/types/command.ts @@ -287,8 +287,10 @@ export interface Command { * Cross-flag validation, after parsing and before run. Return an error message * → UsageError; undefined to pass. Single-flag `required` is enforced by the * parser — use this for rules spanning flags or depending on a flag's *value*. + * May be async for read-only local preflight. Runtime awaits it before auth + * and confirmation. Do not perform remote requests or local writes here. */ - validate?: (flags: ParsedFlags) => string | undefined; + validate?: (flags: ParsedFlags) => string | undefined | Promise; run: (ctx: CommandContext) => Promise; } diff --git a/packages/kscli/package.json b/packages/kscli/package.json index 84fbdb05..325c9ab0 100644 --- a/packages/kscli/package.json +++ b/packages/kscli/package.json @@ -1,6 +1,6 @@ { "name": "knowledge-studio-cli", - "version": "1.21.0", + "version": "1.22.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 75962887..5b7a6ca1 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "bailian-cli-runtime", - "version": "1.21.0", + "version": "1.22.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/packages/runtime/src/create-cli.ts b/packages/runtime/src/create-cli.ts index 1cf98e3c..3a7f5dfa 100644 --- a/packages/runtime/src/create-cli.ts +++ b/packages/runtime/src/create-cli.ts @@ -217,7 +217,7 @@ export function createCli(commands: Record, opts: CliOptions parsedFlags, Object.keys(res.command.flags ?? {}), ) as ParsedFlags; - const invalid = res.command.validate?.(ownFlags); + const invalid = await res.command.validate?.(ownFlags); if (invalid) throw new UsageError(invalid); // 校验通过 → 建源、解析 settings、组 ctx,进中间件执行命令。 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5c4626f3..56306c92 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -189,8 +189,8 @@ importers: packages/commands: dependencies: '@openagentpack/sdk': - specifier: 0.7.0 - version: 0.7.0 + specifier: 0.7.1 + version: 0.7.1 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.7.0': - resolution: {integrity: sha512-MjyKIFvPi4aT1TIcvGUB0bnD4Tly9aMCncjI9+EYG6CPWpuGO4DGj+4zlfBCx1fvtD1G1w4Zi0m8A8ZodqkK9Q==} + '@openagentpack/sdk@0.7.1': + resolution: {integrity: sha512-OENks8UdfWFanvhJIVbqyUX1+XGavHnrxDiFx4SoJoGRXi45cOgyYtWWbG5VdDI2I5Dpo9eGNaw/JtDnkdxUcg==} 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.7.0': + '@openagentpack/sdk@0.7.1': dependencies: jszip: 3.10.1 yaml: 2.9.0 diff --git a/skills/bailian-cli/SKILL.md b/skills/bailian-cli/SKILL.md index 57fa84e7..6b905c20 100644 --- a/skills/bailian-cli/SKILL.md +++ b/skills/bailian-cli/SKILL.md @@ -1,7 +1,7 @@ --- name: bailian-cli metadata: - version: "1.21.0" + version: "1.22.0" requires: bins: ["bl"] description: >- diff --git a/skills/bailian-finetune/SKILL.md b/skills/bailian-finetune/SKILL.md index d6458120..a3340ec4 100644 --- a/skills/bailian-finetune/SKILL.md +++ b/skills/bailian-finetune/SKILL.md @@ -1,7 +1,7 @@ --- name: bailian-finetune metadata: - version: "1.21.0" + version: "1.22.0" requires: bins: ["bl"] description: >- diff --git a/skills/bailian-gen/SKILL.md b/skills/bailian-gen/SKILL.md index 7addc20e..c0d47a35 100644 --- a/skills/bailian-gen/SKILL.md +++ b/skills/bailian-gen/SKILL.md @@ -1,7 +1,7 @@ --- name: bailian-gen metadata: - version: "1.21.0" + version: "1.22.0" requires: bins: ["bl"] description: >- diff --git a/skills/bailian-managed-agent/SKILL.md b/skills/bailian-managed-agent/SKILL.md index 9b437fe9..97035e1e 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.21.0" + version: "1.22.0" requires: bins: ["bl"] description: >- diff --git a/skills/bailian-managed-agent/reference/managed-agent.md b/skills/bailian-managed-agent/reference/managed-agent.md index a933746c..da7a1dd0 100644 --- a/skills/bailian-managed-agent/reference/managed-agent.md +++ b/skills/bailian-managed-agent/reference/managed-agent.md @@ -1181,38 +1181,35 @@ bl managed-agent playground --file agents.yaml --no-open ### `bl managed-agent project build` -| Field | Value | -| ------------------ | ------------------------------------------------------------------------------------------------------------------------- | -| **Name** | `managed-agent project build` | -| **Description** | Organize directory source and generate the immutable Publish Build | -| **Authentication** | No Auth | -| **Usage** | `bl managed-agent project build [--project ]` | -| **Risk** | `high` | -| **Risk message** | This organizes project source, moves literal Vault secrets into the local .env, and writes the previewed immutable Build. | - -> **Agent safety:** Never add `--yes` automatically. On `type="requires_confirmation"`, stop and ask for explicit user confirmation of the same action and scope. +| Field | Value | +| ------------------ | ------------------------------------------------------------------ | +| **Name** | `managed-agent project build` | +| **Description** | Organize directory source and generate the immutable Publish Build | +| **Authentication** | No Auth | +| **Usage** | `bl managed-agent project build [--project ]` | #### Flags | Flag | Type | Required | Description | | ----------------------- | ------ | -------- | --------------------------------------------------- | | `--project ` | string | no | Directory project root (default: current directory) | -| `--yes` | switch | no | Confirm this high-risk operation | + +#### Notes + +- Build writes local project files without confirmation, including inferred resource associations and migration of plaintext Vault secrets into .env. Use --dry-run to preview without writing. Publish still requires explicit confirmation before remote changes. #### Examples ```bash -bl managed-agent project build --dry-run +bl managed-agent project build ``` ```bash -# Only after explicit user confirmation: -bl managed-agent project build --yes +bl managed-agent project build --dry-run ``` ```bash -# Only after explicit user confirmation: -bl managed-agent project build --project ./my-agent --yes +bl managed-agent project build --project ./my-agent ``` ### `bl managed-agent project init` @@ -1226,12 +1223,13 @@ bl managed-agent project build --project ./my-agent --yes #### Flags -| Flag | Type | Required | Description | -| ----------------------- | ------ | -------- | --------------------------------------------------- | -| `--project ` | string | no | Directory project root (default: current directory) | +| Flag | Type | Required | Description | +| ----------------------- | ------ | -------- | ----------------------------------------------------------------------------- | +| `--project ` | string | no | Directory project root (default: ./managed-agent under the current directory) | #### Notes +- Without --project, creates a managed-agent/ subdirectory. Enter it before running other project commands. Use --project . to initialize in place or convert the current agents.yaml; existing project files are not overwritten. - New projects include Skill, File, Vault, and Environment examples under each resource directory's \_examples/. They are not referenced by agent.json and are excluded from Build/Publish. Copy an example outside \_examples/ to enable it, then configure its Agent reference. #### Examples @@ -1244,6 +1242,10 @@ bl managed-agent project init bl managed-agent project init --project ./my-agent ``` +```bash +bl managed-agent project init --project . +``` + ### `bl managed-agent project publish` | Field | Value | diff --git a/skills/bailian-protocol/SKILL.md b/skills/bailian-protocol/SKILL.md index 0da22e46..c7a2780b 100644 --- a/skills/bailian-protocol/SKILL.md +++ b/skills/bailian-protocol/SKILL.md @@ -1,7 +1,7 @@ --- name: bailian-protocol metadata: - version: "1.21.0" + version: "1.22.0" requires: bins: ["bl"] description: >- diff --git a/skills/bailian-web-search/SKILL.md b/skills/bailian-web-search/SKILL.md index 89d09a23..6e3923a8 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.21.0" + version: "1.22.0" requires: bins: ["bl"] description: >-