Skip to content

Commit a92c584

Browse files
committed
fix(managed-agent): use CLI credentials for directory projects
Make project init fully offline, stop generating project-local credential files, bind Publish to the reviewed plan fingerprint, remove the unsafe Apply refresh-only mode, and document Agent-owned File mounts.
1 parent bcf68a3 commit a92c584

8 files changed

Lines changed: 78 additions & 24 deletions

File tree

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ interface AgentConfigOptions {
3333
projectName?: string;
3434
statePath?: string;
3535
credentials?: CredentialScope;
36+
overrideBailianBaseUrl?: boolean;
3637
}
3738

3839
/**
@@ -56,7 +57,9 @@ export async function resolveAgentProjectConfig(
5657
prepareProviderEnv();
5758
const resolved = await resolveProjectConfig(filePath, options);
5859
normalizeInterpolatedProviderBlocks(resolved.config.providers);
59-
injectProviderCredentials(resolved.config.providers, host);
60+
injectProviderCredentials(resolved.config.providers, host, {
61+
overrideBaseUrl: options.overrideBailianBaseUrl,
62+
});
6063
scrubCredentialEnv();
6164
assertBailianOnlyProviders(resolved.config.providers);
6265
if ((options.credentials ?? "all") !== "none") {

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,10 @@ export function prepareProviderEnv(): void {
9999
* Override the bailian provider block with bl's authStage-resolved credential, so
100100
* the bailian API key is authoritatively the CLI auth chain's — never a config
101101
* file bare-read or a stale env value. `api_key` is replaced unconditionally
102-
* when a credential resolved; `base_url` / `workspace_id` are filled only when
103-
* the block references them and the interpolated value is empty (a literal in
104-
* agents.yaml is respected).
102+
* when a credential resolved. `base_url` is normally filled only when empty so
103+
* a literal in agents.yaml remains supported; directory projects pass
104+
* `overrideBaseUrl` so their connection always follows the Bailian CLI auth
105+
* chain. `workspace_id` is filled only when empty.
105106
*
106107
* `base_url` carries {@link AGENTSTUDIO_API_PATH} because the SDK appends resource
107108
* paths onto it verbatim; a value already ending in the suffix is left as-is.
@@ -114,14 +115,15 @@ export function prepareProviderEnv(): void {
114115
export function injectProviderCredentials(
115116
providers: Record<string, unknown>,
116117
host: CredentialHost,
118+
options: { overrideBaseUrl?: boolean } = {},
117119
): void {
118120
const bailian = providers.bailian;
119121
if (!bailian || typeof bailian !== "object") return;
120122
const block = bailian as Record<string, unknown>;
121123

122124
const cred = host.client.exportApiCredential();
123125
if (cred) block.api_key = cred.token;
124-
if ("base_url" in block && !block.base_url) {
126+
if ("base_url" in block && (options.overrideBaseUrl || !block.base_url)) {
125127
// Defensive normalization: the auth chain already normalizes base_url to
126128
// an origin, but never let a trailing slash produce "//api/v1/agentstudio".
127129
const origin = host.client.baseUrl.replace(/\/+$/, "");

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

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,6 @@ const APPLY_FLAGS = {
2929
"zh-CN": "规划前跳过从远端刷新状态",
3030
},
3131
},
32-
refreshOnly: {
33-
type: "switch",
34-
description: {
35-
"en-US": "Refresh state without mutating remote resources",
36-
"zh-CN": "仅刷新 State,不修改远端资源",
37-
},
38-
},
3932
concurrency: {
4033
type: "number",
4134
valueHint: "<n>",
@@ -76,7 +69,6 @@ export default defineCommand({
7669
provider: "bailian",
7770
refresh: !flags.noRefresh,
7871
concurrency: flags.concurrency,
79-
refresh_only: flags.refreshOnly,
8072
},
8173
config_file: file,
8274
hint: "Run `managed-agent plan` to preview the exact resource changes.",

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,14 @@ export const managedAgentProjectInit = defineCommand({
4949
async run(ctx) {
5050
if (ctx.settings.dryRun) {
5151
emitResult(
52-
{ would_initialize_project: ctx.flags.project ?? "." },
52+
{
53+
would_initialize_project: ctx.flags.project ?? ".",
54+
},
5355
detectOutputFormat(ctx.settings.output),
5456
);
5557
return;
5658
}
57-
const result = await initializeDirectoryProject({
58-
projectRoot: ctx.flags.project ?? ".",
59-
provider: "bailian",
60-
});
59+
const result = await initializeDirectoryProject({ projectRoot: ctx.flags.project ?? "." });
6160
emitResult(result, detectOutputFormat(ctx.settings.output));
6261
},
6362
});
@@ -163,9 +162,9 @@ export const managedAgentProjectPublish = defineCommand({
163162
installSdkTransport(ctx);
164163
const root = ctx.flags.project ?? ".";
165164
const resolveBuild: ProjectBuildResolver = async (buildPath) =>
166-
(await resolveAgentProjectConfig(ctx, buildPath)) as unknown as Awaited<
167-
ReturnType<ProjectBuildResolver>
168-
>;
165+
(await resolveAgentProjectConfig(ctx, buildPath, {
166+
overrideBailianBaseUrl: true,
167+
})) as unknown as Awaited<ReturnType<ProjectBuildResolver>>;
169168
const planned = await withAgentErrors(() =>
170169
withStdoutProtected(() =>
171170
planProjectPublish(root, {
@@ -199,6 +198,7 @@ export const managedAgentProjectPublish = defineCommand({
199198
projectRoot: planned.project_root,
200199
expectedProjectRevision: planned.project_revision,
201200
expectedYamlHash: planned.build_manifest.yaml_hash,
201+
expectedPlanFingerprint: planned.plan_fingerprint,
202202
provider: "bailian",
203203
refresh: !ctx.flags.noRefresh,
204204
concurrency: ctx.flags.concurrency,

packages/commands/tests/credentials-bridge.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,20 @@ test("inject:base_url 已带后缀不重复拼;非空字面量 base_url 保留",
104104
expect(literal.bailian.base_url).toBe("https://custom.example.com/api/v1/agentstudio");
105105
});
106106

107+
test("inject:目录项目可强制使用 Bailian CLI 解析出的 base_url", () => {
108+
const providers = {
109+
bailian: {
110+
api_key: "from-project-env",
111+
base_url: "https://project-env.example.com/api/v1/agentstudio",
112+
},
113+
};
114+
injectProviderCredentials(providers, makeHost({ apiCred: bailianCred() }), {
115+
overrideBaseUrl: true,
116+
});
117+
expect(providers.bailian.api_key).toBe("sk-auth-chain");
118+
expect(providers.bailian.base_url).toBe("https://dashscope.aliyuncs.com/api/v1/agentstudio");
119+
});
120+
107121
test("inject:base_url 尾斜杠被规范化,不产生双斜杠", () => {
108122
const providers = { bailian: { api_key: "", base_url: "" } };
109123
injectProviderCredentials(

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

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ async function seedTrackedResources(
105105
/**
106106
* managed-agent:help / 缺参不依赖密钥;所有 mutation 命令的 --dry-run
107107
* 必须在构建 SDK runtime(凭证注入 / 联网 / 写盘)之前短路,因此同样不需要密钥。
108-
* 鉴权分层:离线命令(init/validate/state list|show|rm)auth: "none";联网命令
109-
* 统一 auth: "apiKey" 硬门禁(见 managed-agent-auth-chain e2e)。
108+
* 鉴权分层:离线命令(init/project init/validate/state list|show|rm)auth: "none";
109+
* 联网命令统一 auth: "apiKey" 硬门禁(见 managed-agent-auth-chain e2e)。
110110
* 真实集成(apply/destroy/session 流程)依赖工作区内的 agents.yaml 与远端资源,
111111
* 属批量场景,暂仅覆盖 dry-run 契约。
112112
*/
@@ -234,6 +234,18 @@ describe("e2e: managed-agent", () => {
234234
expect(stderr).toMatch(/--file|--yes/i);
235235
expect(stderr).not.toContain("--provider");
236236
expect(stderr).not.toContain("--ci");
237+
expect(stderr).not.toContain("--refresh-only");
238+
});
239+
240+
test("managed-agent apply 不再接受 --refresh-only", async () => {
241+
const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
242+
"managed-agent",
243+
"apply",
244+
"--dry-run",
245+
"--refresh-only",
246+
]);
247+
expect(exitCode).toBe(2);
248+
expect(stderr).toMatch(/Unknown flag.*--refresh-only/i);
237249
});
238250

239251
test("managed-agent init 不再暴露 Git 仓库脚手架", async () => {
@@ -272,6 +284,11 @@ describe("e2e: managed-agent", () => {
272284
expect(
273285
parseStdoutJson<{ baseline_version?: string }>(initialized.stdout).baseline_version,
274286
).toMatch(/^[a-f0-9]{64}$/);
287+
expect(JSON.parse(await readFile(join(projectRoot, "project.json"), "utf8"))).toEqual({
288+
version: "1",
289+
});
290+
expect(await stat(join(projectRoot, ".env")).catch(() => null)).toBeNull();
291+
expect(await stat(join(projectRoot, ".gitignore")).catch(() => null)).toBeNull();
275292

276293
const validated = await runCommandE2e(MANAGED_AGENT_ROUTES, [
277294
"managed-agent",
@@ -301,6 +318,9 @@ describe("e2e: managed-agent", () => {
301318
expect(await readFile(join(projectRoot, ".openagentpack/build/agents.yaml"), "utf8")).toContain(
302319
"assistant",
303320
);
321+
expect(await readFile(join(projectRoot, ".openagentpack/build/agents.yaml"), "utf8")).toContain(
322+
"provider: bailian",
323+
);
304324

305325
const status = await runCommandE2e(MANAGED_AGENT_ROUTES, [
306326
"managed-agent",
@@ -1425,6 +1445,27 @@ describe("e2e: managed-agent(--dry-run 短路,不联网不写盘)", () =>
14251445
expect(stderr).toMatch(/Unknown flag.*--provider/i);
14261446
});
14271447

1448+
test("project init --dry-run 不需要密钥且不创建目录", async () => {
1449+
const parent = await mkdtemp(join(tmpdir(), "bailian-managed-agent-project-dry-run-"));
1450+
projectDirectories.push(parent);
1451+
const projectRoot = join(parent, "project");
1452+
const { stdout, stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
1453+
"managed-agent",
1454+
"project",
1455+
"init",
1456+
"--dry-run",
1457+
"--project",
1458+
projectRoot,
1459+
"--output",
1460+
"json",
1461+
]);
1462+
expect(exitCode, stderr).toBe(0);
1463+
expect(
1464+
parseStdoutJson<{ would_initialize_project?: string }>(stdout).would_initialize_project,
1465+
).toBe(projectRoot);
1466+
expect(await stat(projectRoot).catch(() => null)).toBeNull();
1467+
});
1468+
14281469
test("apply --dry-run 仅输出计划", async () => {
14291470
const { stdout, stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
14301471
"managed-agent",

skills/bailian-managed-agent/SKILL.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,10 @@ for explicit confirmation before re-running with `--yes`.
6262
| Preview or restore project source | `bl managed-agent project version preview` / `restore` |
6363

6464
- Bailian CLI and Workbench use the same `.openagentpack/versions/project` store and enable switch. Git is not required.
65+
- Directory projects always use Bailian. `project.json` does not declare a Provider; Build supplies the Bailian Provider configuration automatically.
66+
- `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.
6567
- 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.
6669
- 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.
6770
- `project version restore` restores source files to the working directory, invalidates Build, and does not move version history or remote State.
6871
- `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: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,6 @@ bl managed-agent agent versions --agent-id agent_abc --all --output json
306306
| ------------------- | ------ | -------- | ------------------------------------------------------------------ |
307307
| `--file <path>` | string | no | Config file path (default: agents.yaml) |
308308
| `--no-refresh` | switch | no | Skip refreshing state from remote before planning |
309-
| `--refresh-only` | switch | no | Refresh state without mutating remote resources |
310309
| `--concurrency <n>` | number | no | Max independent resources to apply in parallel (default 6, max 10) |
311310
| `--yes` | switch | no | Confirm this high-risk operation |
312311
| `--api-key <key>` | string | no | API key |

0 commit comments

Comments
 (0)