Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/agents/auth-change.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,19 @@ defineCommand({ auth }) → runtime/authStage → ctx.client → command.run(ctx

命令不要直接解析 token、env 或 config。业务请求统一走 `ctx.client`;登录/配置命令通过 `ctx.authStore` / `ctx.configStore` 的窄接口操作落盘。

### 例外:agent 命令的 SDK 凭证内存注入

`bl managed-agent *` 的全部命令声明 `auth: "apiKey"`(含纯本地脚手架 `init` —— 统一登录门槛,无例外),bailian 凭证由 authStage 经 `resolveApiKey(sources)` 权威解析(flag > env > active profile config,缺失时抛统一 AUTH 错误)。

凭证不再以真实值写入 `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 值
3. `injectProviderCredentials()` — 用 `ctx.client.exportApiCredential()`(lint 限定 `managed-agent/_engine/**` 可用)覆写内存 config 对象的 bailian 块:`api_key` 无条件覆写;`base_url`(拼 `/api/v1/agentstudio` 后缀)/`workspace_id`(取 `settings.workspaceId`)仅在引用且为空时填充
4. `scrubCredentialEnv()` + `assertProviderCredentials()` — 从 `process.env` 删除全部凭证变量(真实凭证此后只存于 config 对象 → provider adapter 实例内存,不驻留 env / 不被子进程继承);任一已声明 provider 的 `api_key` 为空 → CLI 权威 `AUTH` 错误 + provider 专属 hint(取代 SDK 原始插值/zod 报错)

`bl auth login` 仅管理 bailian(DashScope)凭证;claude/ark/qoder 的 key 从 env(shell / `.env` / `~/.agents/config.json`)经插值进入 config 对象,同样被清扫。禁止命令层直接 `readConfigFile` 裸读凭证;bailian 字段以 CLI 鉴权链为唯一信源。

## 必查清单

### A. core 层(类型 + 解析)
Expand Down
5 changes: 4 additions & 1 deletion packages/cli/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@ node_modules
dist
*.log
.DS_Store
outputs/
outputs/
# agents
agents.state.json
.env
27 changes: 27 additions & 0 deletions packages/cli/agents.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
version: "1"

providers:
bailian:
# bl auth login --api-key <key> sets DASHSCOPE_API_KEY; --agentstudio-base-url <url> sets BAILIAN_BASE_URL
api_key: ${DASHSCOPE_API_KEY}
base_url: ${BAILIAN_BASE_URL}

defaults:
provider: bailian

environments:
dev:
config:
type: cloud
networking:
type: unrestricted

agents:
assistant:
description: "General-purpose assistant"
model: qwen3.7-max
instructions: |
You are a helpful assistant.
environment: dev
tools:
builtin: [bash, read, glob, grep]
32 changes: 32 additions & 0 deletions packages/cli/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,22 @@ import {
pluginLink,
pluginList,
pluginRemove,
managedAgentInit,
managedAgentValidate,
managedAgentPlan,
managedAgentApply,
managedAgentDestroy,
managedAgentStateList,
managedAgentStateShow,
managedAgentStateRm,
managedAgentStateImport,
managedAgentSessionCreate,
managedAgentSessionList,
managedAgentSessionGet,
managedAgentSessionDelete,
managedAgentSessionRun,
managedAgentSessionSend,
managedAgentSessionEvents,
} from "bailian-cli-commands";

// Full bailian-cli product: every command, exposed under the `bl` binary.
Expand Down Expand Up @@ -186,4 +202,20 @@ export const commands: Record<string, AnyCommand> = {
"plugin link": pluginLink,
"plugin list": pluginList,
"plugin remove": pluginRemove,
"managed-agent init": managedAgentInit,
"managed-agent validate": managedAgentValidate,
"managed-agent plan": managedAgentPlan,
"managed-agent apply": managedAgentApply,
"managed-agent destroy": managedAgentDestroy,
"managed-agent state list": managedAgentStateList,
"managed-agent state show": managedAgentStateShow,
"managed-agent state rm": managedAgentStateRm,
"managed-agent state import": managedAgentStateImport,
"managed-agent session create": managedAgentSessionCreate,
"managed-agent session list": managedAgentSessionList,
"managed-agent session get": managedAgentSessionGet,
"managed-agent session delete": managedAgentSessionDelete,
"managed-agent session run": managedAgentSessionRun,
"managed-agent session send": managedAgentSessionSend,
"managed-agent session events": managedAgentSessionEvents,
};
1 change: 1 addition & 0 deletions packages/commands/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"check": "vp check"
},
"dependencies": {
"@openagentpack/sdk": "0.3.0-beta-8d9edcd-20260722",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] SDK 的 Node 版本破坏现有发布契约

当前引入的 @openagentpack/sdk 声明 engines.node >=22,但 bailian-clibailian-cli-commands 和 README 仍承诺 Node >=18.17。Agent commands 又通过 commands index 静态导出,SDK 没有被安全隔离到 bl cma 执行时才加载,因此 Node 18/20 下可能影响整个 bl 的启动。

请先统一运行时契约:要么让 SDK 支持并验证现有 Node 范围,要么同步提升所有发布包、README、CI 和发布检查的 Node 下限。需要增加最低支持 Node 版本下的安装及 bl --help smoke。

"bailian-cli-core": "workspace:*",
"bailian-cli-runtime": "workspace:*",
"boxen": "catalog:",
Expand Down
6 changes: 5 additions & 1 deletion packages/commands/src/commands/auth/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ export default defineCommand({
usageArgs:
"--api-key <key> | --console | --open-api --access-key-id <id> --access-key-secret <secret>",
flags: {
apiKey: { type: "string", valueHint: "<key>", description: "Model API key to store" },
apiKey: {
type: "string",
valueHint: "<key>",
description: "Model API key to store",
},
baseUrl: {
type: "string",
valueHint: "<url>",
Expand Down
11 changes: 9 additions & 2 deletions packages/commands/src/commands/config/set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ export default defineCommand({
"Config key (base_url, output, output_dir, timeout, api_key, access_token, access_key_id, access_key_secret, security_token, default_*_model, workspace_id)",
required: true,
},
value: { type: "string", valueHint: "<value>", description: "Value to set", required: true },
value: {
type: "string",
valueHint: "<value>",
description: "Value to set",
required: true,
},
},
exampleArgs: [
"--key output --value json",
Expand Down Expand Up @@ -44,7 +49,9 @@ export default defineCommand({
return;
}

await ctx.configStore.write({ [resolvedKey]: coerced } as Partial<ConfigFile>);
await ctx.configStore.write({
[resolvedKey]: coerced,
} as Partial<ConfigFile>);

if (!settings.quiet) {
const shown = SECRET_KEYS.has(resolvedKey) ? maskToken(String(coerced)) : coerced;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { ResourceAddress } from "@openagentpack/sdk";

/** Full state address: provider.type.name */
export function formatResourceAddress(address: ResourceAddress): string {
return `${address.provider}.${address.type}.${address.name}`;
}

/** CLI display short label: type.name (provider) */
export function formatResourceLabel(address: ResourceAddress): string {
return `${address.type}.${address.name} (${address.provider})`;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import {
createProjectRuntime,
type LoadedProjectConfig,
type ProjectRuntimeContext,
resolveProjectConfig,
UserError,
} from "@openagentpack/sdk";
import {
assertProviderCredentials,
type CredentialHost,
injectProviderCredentials,
prepareProviderEnv,
scrubCredentialEnv,
} from "./credentials.ts";
import { loadFileState } from "./file-state-manager.ts";
import { type HostContext, installSdkTransport } from "./transport.ts";

export { CREDENTIALS_NOTE } from "./credentials.ts";

interface AgentConfigOptions {
resolveEnv?: boolean;
projectName?: string;
statePath?: string;
}

/**
* Resolve agents.yaml with credentials injected the bl way and scrubbed from the
* environment — the shared credential spine for every SDK-engine command:
* 1. prepare env (SDK bootstrap for non-bailian + placeholders so interpolation
* never throws on a value we're about to supply/reject)
* 2. resolve + interpolate the config
* 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
*/
export async function resolveAgentProjectConfig(
host: CredentialHost,
filePath: string,
options: AgentConfigOptions = {},
): Promise<LoadedProjectConfig> {
prepareProviderEnv();
const resolved = await resolveProjectConfig(filePath, options);
injectProviderCredentials(resolved.config.providers, host);
scrubCredentialEnv();
assertProviderCredentials(resolved.config.providers);
return resolved;
}

/**
* Build a full ProjectRuntimeContext from a config file path — the standard
* entry point for agent commands that need the SDK engine. Mirrors OpenAgentPack
* CLI's buildCliRuntime: resolve config → load local state → assemble runtime.
* Takes the host context first so every SDK-engine command wires the
* instrumented transport (UA / tracking headers / verbose) and the bl-resolved,
* in-memory-injected credential ({@link resolveAgentProjectConfig}) by construction.
*/
export async function buildAgentRuntime(
host: HostContext & CredentialHost,
filePath: string,
options: AgentConfigOptions = {},
): Promise<ProjectRuntimeContext & { configPath: string }> {
installSdkTransport(host);
const { config, configPath, projectName } = await resolveAgentProjectConfig(
host,
filePath,
options,
);
const state = await loadFileState(configPath, options.statePath, projectName);
const ctx = createProjectRuntime({
projectName,
config,
state,
configPath,
providers: config.providers,
});
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}.`,
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Redirect `console.log` / `console.info` to stderr while `fn` runs.
*
* The OpenAgentPack SDK's provider adapters emit progress/debug logging via
* `console.log` (e.g. `[skill-upload]`), which would corrupt bl's stdout data
* channel in `--output json` mode. Wrapping SDK calls that may log keeps stdout
* a clean data channel. Restores the originals on completion.
*/
export async function withStdoutProtected<T>(fn: () => Promise<T>): Promise<T> {
const originalLog = console.log;
const originalInfo = console.info;
const toStderr = (...args: unknown[]): void => {
process.stderr.write(`${args.map((arg) => String(arg)).join(" ")}\n`);
};
console.log = toStderr;
console.info = toStderr;
try {
return await fn();
} finally {
console.log = originalLog;
console.info = originalInfo;
}
}
Loading