Skip to content

Commit c6b9931

Browse files
committed
feat(managed-agent): integrate directory resource scaffolds and safe builds
1 parent a92c584 commit c6b9931

5 files changed

Lines changed: 126 additions & 12 deletions

File tree

packages/commands/src/commands/managed-agent/_engine/config-loader.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export const BAILIAN_PROVIDER = "bailian";
3030

3131
interface AgentConfigOptions {
3232
resolveEnv?: boolean;
33+
environment?: Record<string, string>;
3334
projectName?: string;
3435
statePath?: string;
3536
credentials?: CredentialScope;

packages/commands/src/commands/managed-agent/project.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ export const managedAgentProjectInit = defineCommand({
4646
usageArgs: "[--project <directory>]",
4747
flags: PROJECT_FLAG,
4848
exampleArgs: ["", "--project ./my-agent"],
49+
notes: [
50+
{
51+
"en-US":
52+
"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.",
53+
"zh-CN":
54+
"新项目在四类资源目录的 _examples/ 下生成配置示例,不写入 agent.json 引用,也不参与 Build/Publish。需要使用时,将示例复制到 _examples/ 外,再配置 Agent 引用。",
55+
},
56+
],
4957
async run(ctx) {
5058
if (ctx.settings.dryRun) {
5159
emitResult(
@@ -95,8 +103,9 @@ export const managedAgentProjectBuild = defineCommand({
95103
level: "high",
96104
message: {
97105
"en-US":
98-
"This organizes the directory project source and writes the previewed immutable Build.",
99-
"zh-CN": "该操作会整理目录项目源文件,并写入已预览的不可变 Build。",
106+
"This organizes project source, moves literal Vault secrets into the local .env, and writes the previewed immutable Build.",
107+
"zh-CN":
108+
"该操作会整理项目源文件,将 Vault 明文密钥移入本地 .env,并写入已预览的不可变 Build。",
100109
},
101110
},
102111
usageArgs: "[--project <directory>]",
@@ -161,8 +170,9 @@ export const managedAgentProjectPublish = defineCommand({
161170
async run(ctx) {
162171
installSdkTransport(ctx);
163172
const root = ctx.flags.project ?? ".";
164-
const resolveBuild: ProjectBuildResolver = async (buildPath) =>
173+
const resolveBuild: ProjectBuildResolver = async (buildPath, options) =>
165174
(await resolveAgentProjectConfig(ctx, buildPath, {
175+
environment: options?.environment,
166176
overrideBailianBaseUrl: true,
167177
})) as unknown as Awaited<ReturnType<ProjectBuildResolver>>;
168178
const planned = await withAgentErrors(() =>

packages/commands/tests/e2e/managed-agent.e2e.test.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,32 @@ describe("e2e: managed-agent", () => {
290290
expect(await stat(join(projectRoot, ".env")).catch(() => null)).toBeNull();
291291
expect(await stat(join(projectRoot, ".gitignore")).catch(() => null)).toBeNull();
292292

293+
expect(
294+
JSON.parse(await readFile(join(projectRoot, "agents/assistant/agent.json"), "utf8")),
295+
).toEqual({ name: "Assistant", model: "qwen3.7-max" });
296+
for (const relativePath of [
297+
"skills/_examples/example-skill/skill.json",
298+
"skills/_examples/example-skill/SKILL.md",
299+
"files/_examples/example-file/file.json",
300+
"files/_examples/example-file/example.md",
301+
"vaults/_examples/example-vault/vault.json",
302+
"environments/_examples/example-env/environment.json",
303+
]) {
304+
expect((await stat(join(projectRoot, "agents/assistant", relativePath))).isFile()).toBe(true);
305+
expect(
306+
await readFile(join(projectRoot, "agents/assistant", relativePath, "../README.md"), "utf8"),
307+
).toContain("_examples/");
308+
}
309+
310+
const skillDirectory = join(projectRoot, "agents/assistant/skills/writer");
311+
const filesDirectory = join(projectRoot, "agents/assistant/files");
312+
const nestedFileDirectory = join(filesDirectory, "mount");
313+
await mkdir(skillDirectory, { recursive: true });
314+
await mkdir(nestedFileDirectory, { recursive: true });
315+
await writeFile(join(skillDirectory, "SKILL.md"), "# Writer\n", "utf8");
316+
await writeFile(join(filesDirectory, "brief.txt"), "Build this brief.\n", "utf8");
317+
await writeFile(join(nestedFileDirectory, "mount.md"), "Mount this file.\n", "utf8");
318+
293319
const validated = await runCommandE2e(MANAGED_AGENT_ROUTES, [
294320
"managed-agent",
295321
"project",
@@ -304,6 +330,38 @@ describe("e2e: managed-agent", () => {
304330
parseStdoutJson<{ diagnostics?: Array<{ severity?: string }> }>(validated.stdout).diagnostics,
305331
).not.toContainEqual(expect.objectContaining({ severity: "error" }));
306332

333+
const vaultDirectory = join(projectRoot, "agents/assistant/vaults/secrets");
334+
await mkdir(vaultDirectory, { recursive: true });
335+
await writeFile(
336+
join(vaultDirectory, "vault.json"),
337+
JSON.stringify({
338+
id: "secrets",
339+
display_name: "Secrets",
340+
credentials: [
341+
{
342+
name: "service",
343+
type: "environment_variable",
344+
secret_name: "SERVICE_TOKEN",
345+
secret_value: "e2e-vault-private-value",
346+
},
347+
],
348+
}),
349+
);
350+
const dryBuild = await runCommandE2e(MANAGED_AGENT_ROUTES, [
351+
"managed-agent",
352+
"project",
353+
"build",
354+
"--project",
355+
projectRoot,
356+
"--dry-run",
357+
"--output",
358+
"json",
359+
]);
360+
expect(dryBuild.exitCode, dryBuild.stderr).toBe(0);
361+
expect(dryBuild.stdout + dryBuild.stderr).not.toContain("e2e-vault-private-value");
362+
expect(dryBuild.stdout).toContain("AGENTS_VAULT_");
363+
expect(await stat(join(projectRoot, ".env")).catch(() => null)).toBeNull();
364+
307365
const built = await runCommandE2e(MANAGED_AGENT_ROUTES, [
308366
"managed-agent",
309367
"project",
@@ -315,12 +373,50 @@ describe("e2e: managed-agent", () => {
315373
"json",
316374
]);
317375
expect(built.exitCode, built.stderr).toBe(0);
376+
const generatedYaml = await readFile(
377+
join(projectRoot, ".openagentpack/build/agents.yaml"),
378+
"utf8",
379+
);
380+
expect(generatedYaml).not.toContain("example-");
381+
expect(generatedYaml).not.toContain("_examples");
382+
expect(generatedYaml).not.toContain("$" + "{SERVICE_TOKEN}");
383+
expect(built.stdout + built.stderr).not.toContain("e2e-vault-private-value");
384+
expect(await readFile(join(projectRoot, ".env"), "utf8")).toContain("e2e-vault-private-value");
385+
expect(await readFile(join(vaultDirectory, "vault.json"), "utf8")).not.toContain(
386+
"e2e-vault-private-value",
387+
);
388+
expect(
389+
await readFile(join(projectRoot, ".openagentpack/build/agents.yaml"), "utf8"),
390+
).not.toContain("e2e-vault-private-value");
318391
expect(await readFile(join(projectRoot, ".openagentpack/build/agents.yaml"), "utf8")).toContain(
319392
"assistant",
320393
);
321394
expect(await readFile(join(projectRoot, ".openagentpack/build/agents.yaml"), "utf8")).toContain(
322395
"provider: bailian",
323396
);
397+
expect(JSON.parse(await readFile(join(skillDirectory, "skill.json"), "utf8"))).toEqual({
398+
id: "writer",
399+
});
400+
expect(JSON.parse(await readFile(join(filesDirectory, "brief/file.json"), "utf8"))).toEqual({
401+
id: "brief",
402+
name: "brief.txt",
403+
source: "./brief.txt",
404+
});
405+
expect(JSON.parse(await readFile(join(nestedFileDirectory, "file.json"), "utf8"))).toEqual({
406+
id: "mount",
407+
name: "mount.md",
408+
source: "./mount.md",
409+
});
410+
const builtAgent = JSON.parse(
411+
await readFile(join(projectRoot, "agents/assistant/agent.json"), "utf8"),
412+
);
413+
expect(builtAgent.skills).toEqual(["writer"]);
414+
expect(builtAgent.files).toEqual(
415+
expect.arrayContaining([
416+
{ file: "brief", mount_path: "/mnt/brief.txt" },
417+
{ file: "mount", mount_path: "/mnt/mount.md" },
418+
]),
419+
);
324420

325421
const status = await runCommandE2e(MANAGED_AGENT_ROUTES, [
326422
"managed-agent",

skills/bailian-managed-agent/SKILL.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,12 @@ for explicit confirmation before re-running with `--yes`.
6363

6464
- Bailian CLI and Workbench use the same `.openagentpack/versions/project` store and enable switch. Git is not required.
6565
- Directory projects always use Bailian. `project.json` does not declare a Provider; Build supplies the Bailian Provider configuration automatically.
66+
- Fresh `project init` includes complete Skill/File/Vault/Environment examples with bilingual README files under `agents/assistant/<resource-type>/_examples/`. They are not linked in `agent.json` and are excluded from Build discovery, Workbench declarations, and remote Publish. Copy a resource outside `_examples/` and configure its Agent reference to use it. Examples remain local versioned source; never put real secrets into them. / 新项目的四类资源示例默认不启用、不发布;请按 README 复制到 `_examples/` 外再配置引用,不要向示例写入真实密钥。
6667
- `project init`, `validate`, `build`, and version commands are local-only. Publish and Workbench resolve credentials from Bailian CLI flags, shell environment, or the active Profile; project initialization does not write credentials into the project directory.
6768
- Build is local-only. Publish never runs Build implicitly and consumes only a current `.openagentpack/build/agents.yaml` plus manifest.
68-
- Agent-owned Files live under `agents/<agent>/files/<id>/`. Add `{ "file": "<id>", "mount_path": "/mnt/<name>" }` to `agent.json.files`; Publish uploads the File and every later Session mounts it automatically. A File referenced by multiple Agents is promoted to `resources/files/<id>/` during Build.
69+
- Build moves literal Vault `secret_value` / `access_token` values from Agent-local or shared `vault.json` into project-root `.env`, replacing them with generated environment references. Existing references and `.env` entries are preserved; conflicts receive suffixed variable names. Preview/dry-run never write or print secrets. Publish and Workbench read the selected project's root `.env` as a fallback to inherited environment variables, even when invoked elsewhere. `.env` is private plaintext storage, excluded from local versions but not automatically ignored by Git; keep it backed up securely.
70+
- Build 会将 Agent 本地或共享 `vault.json` 中的明文密钥移入项目根目录 `.env`,再写回环境变量引用;保留已有引用和变量,重名时生成后缀。预览不写文件或输出密钥。`.env` 不进入版本快照,也不加密;请自行备份并加入 Git 忽略规则。
71+
- Agent-local File and Skill content supports Build-time association. A File may be copied directly into `agents/<agent>/files/`, or placed in `agents/<agent>/files/<id>/` when that directory contains exactly one content file; Build generates `file.json` and a `/mnt/<filename>` entry in `agent.json.files`. A directory under `agents/<agent>/skills/<id>/` containing `SKILL.md` generates `skill.json` and its `agent.json.skills` entry. Explicit JSON always wins; shared root resources remain explicit. Resources referenced by multiple Agents are promoted to the corresponding root shared directory during Build.
6972
- A successful Publish versions the canonical YAML and the complete project source tree, including Skill scripts/assets and binary files. Remote State is never versioned or restored.
7073
- `project version restore` restores source files to the working directory, invalidates Build, and does not move version history or remote State.
7174
- `managed-agent playground` remains the standalone `agents.yaml` Session Preview path; directory Workbench is only under `managed-agent project workbench`.

skills/bailian-managed-agent/reference/managed-agent.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1181,14 +1181,14 @@ bl managed-agent playground --file agents.yaml --no-open
11811181

11821182
### `bl managed-agent project build`
11831183

1184-
| Field | Value |
1185-
| ------------------ | ------------------------------------------------------------------------------------- |
1186-
| **Name** | `managed-agent project build` |
1187-
| **Description** | Organize directory source and generate the immutable Publish Build |
1188-
| **Authentication** | No Auth |
1189-
| **Usage** | `bl managed-agent project build [--project <directory>]` |
1190-
| **Risk** | `high` |
1191-
| **Risk message** | This organizes the directory project source and writes the previewed immutable Build. |
1184+
| Field | Value |
1185+
| ------------------ | ------------------------------------------------------------------------------------------------------------------------- |
1186+
| **Name** | `managed-agent project build` |
1187+
| **Description** | Organize directory source and generate the immutable Publish Build |
1188+
| **Authentication** | No Auth |
1189+
| **Usage** | `bl managed-agent project build [--project <directory>]` |
1190+
| **Risk** | `high` |
1191+
| **Risk message** | This organizes project source, moves literal Vault secrets into the local .env, and writes the previewed immutable Build. |
11921192

11931193
> **Agent safety:** Never add `--yes` automatically. On `type="requires_confirmation"`, stop and ask for explicit user confirmation of the same action and scope.
11941194
@@ -1230,6 +1230,10 @@ bl managed-agent project build --project ./my-agent --yes
12301230
| ----------------------- | ------ | -------- | --------------------------------------------------- |
12311231
| `--project <directory>` | string | no | Directory project root (default: current directory) |
12321232

1233+
#### Notes
1234+
1235+
- 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.
1236+
12331237
#### Examples
12341238

12351239
```bash

0 commit comments

Comments
 (0)