Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6329427
feat(agent): add agent command group with session and state management
chenanran555 Jul 21, 2026
d6bd38a
feat(agent): agent相关cli命令的client层功能,对齐cli client的基础能力
chenanran555 Jul 22, 2026
1c9dac2
feat(cma): login时初始化agent相关的baseUrl
chenanran555 Jul 22, 2026
7cbd61d
Merge remote-tracking branch 'origin/main' into feat/cma
chenanran555 Jul 22, 2026
9e59b01
feat: update openagentpack sdk
chenanran555 Jul 22, 2026
1da3367
feat: fix ci
chenanran555 Jul 22, 2026
64335a6
feat(agent): rename cli command to managed-agent
chenanran555 Jul 23, 2026
9cad199
feat(agent): validate by client apiKey auth type
chenanran555 Jul 24, 2026
1d589c5
Merge remote-tracking branch 'origin/main' into feat/cma
chenanran555 Jul 24, 2026
1bf4fec
feat(agent): support --dry-run for all local and remote mutations
chenanran555 Jul 24, 2026
1e6165d
fix(agent): guarantee single valid JSON on stdout for --output json
chenanran555 Jul 24, 2026
247bb82
test(agent): cover config-write, profile, logout and error-mapping au…
chenanran555 Jul 24, 2026
32c497d
feat(agent): add skill-list command and fix pr issues
chenanran555 Jul 26, 2026
5f0966e
fix(agent): ci issues
chenanran555 Jul 27, 2026
8a0fb87
feat(agent): update openagentpack sdk version
chenanran555 Jul 27, 2026
e22058b
feat: update openagentpack sdk
chenanran555 Jul 27, 2026
6f9e006
feat(agent): add skills for skill-list command
chenanran555 Jul 27, 2026
93c9149
feat(agent): 鉴权分离线命令和在线命令,仅对bailian provider鉴权
chenanran555 Jul 27, 2026
63ee5aa
fix(agent): plan command dry-run
chenanran555 Jul 27, 2026
05860b3
fix(agent): session output json with session_id
chenanran555 Jul 27, 2026
a03ee0c
fix(agent): timeout error
chenanran555 Jul 27, 2026
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
17 changes: 17 additions & 0 deletions docs/agents/auth-change.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,23 @@ defineCommand({ auth }) → runtime/authStage → ctx.client → command.run(ctx

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

### 例外:agent 命令的分层鉴权与 SDK 凭证内存注入

`bl managed-agent *` 按调用链分两层,不再全命令硬门禁:

- **离线命令** — `init`、`validate`、`state list/show/rm`:`auth: "none"`,只读写本地文件,无需登录;引擎侧传 `credentials: "none"` 跳过凭证断言(`plan --no-refresh` 与 `plan --dry-run` 同样传 `"none"` 并强制 `refresh: false`:不联网、不回写 state)
- **provider-aware 命令** — `plan`(默认)、`apply`、`destroy`、`state import`、`skill-list`、全部 `session *`:仍声明 `auth: "apiKey"` 但加 `authOptional: true` —— authStage 照常经 `resolveApiKey(sources)` 解析 bailian 凭证(flag > env > active profile config)并注入 `ctx.client`,但缺失不在 authStage 抛;真正的门禁在引擎层 `assertProviderCredentials`,只校验本次运行涉及的 provider(`CredentialScope`:`--provider` / state 地址里的 provider / 配置默认 provider 链)。配了四个 provider 只跑 claude 时,缺 bailian key 不阻塞。

凭证不以真实值写入 `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 值;随后 `normalizeInterpolatedProviderBlocks()` 把插值为空导致的 YAML `null` 归一为 `""`(避免范围外 provider 在 SDK zod 层报 "received null")
3. `injectProviderCredentials()` — 用 `ctx.client.exportApiCredential()`(lint 限定 `managed-agent/_engine/**` 可用)覆写内存 config 对象的 bailian 块:有凭证时 `api_key` 无条件覆写;`base_url`(拼 `/api/v1/agentstudio` 后缀,无凭证时用 client 默认域名补齐以满足 schema)/`workspace_id`(取 `settings.workspaceId`)仅在引用且为空时填充
4. `scrubCredentialEnv()` — 从 `process.env` 删除全部凭证变量(真实凭证此后只存于 config 对象 → provider adapter 实例内存,不驻留 env / 不被子进程继承)
5. `assertProviderCredentials(providers, required)` — 按 `CredentialScope` 算出的 `required` 范围校验:范围内 provider 的 `api_key` 为空 → CLI 权威 `AUTH` 错误 + provider 专属 hint(取代 SDK 原始插值/zod 报错);范围外 provider 允许空 key

`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]
34 changes: 34 additions & 0 deletions packages/cli/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,23 @@ import {
pluginLink,
pluginList,
pluginRemove,
managedAgentInit,
managedAgentValidate,
managedAgentPlan,
managedAgentApply,
managedAgentDestroy,
managedAgentStateList,
managedAgentStateShow,
managedAgentStateRm,
managedAgentStateImport,
managedAgentSessionCreate,
managedAgentSessionList,
managedAgentSessionGet,
managedAgentSessionDelete,
managedAgentSessionRun,
managedAgentSessionSend,
managedAgentSessionEvents,
managedAgentSkillList,
} from "bailian-cli-commands";

// Full bailian-cli product: every command, exposed under the `bl` binary.
Expand Down Expand Up @@ -186,4 +203,21 @@ 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,
"managed-agent skill-list": managedAgentSkillList,
};
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.1",
"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})`;
}
117 changes: 117 additions & 0 deletions packages/commands/src/commands/managed-agent/_engine/config-loader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import {
createProjectRuntime,
type LoadedProjectConfig,
type ProjectRuntimeContext,
resolveProjectConfig,
UserError,
} from "@openagentpack/sdk";
import {
assertProviderCredentials,
type CredentialHost,
injectProviderCredentials,
normalizeInterpolatedProviderBlocks,
prepareProviderEnv,
resolveTargetProviderNames,
scrubCredentialEnv,
} from "./credentials.ts";
import { loadFileState } from "./file-state-manager.ts";
import { type HostContext, installSdkTransport } from "./transport.ts";

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

/**
* Which providers this run requires a non-empty key for:
* - "targets" (default) — the run's target providers per the config's
* default provider chain (mirrors the SDK's plan/apply targeting)
* - "none" — offline command (local config/state only), skip the check
* - "all" — every configured provider (`--provider all`)
* - any other name — the run was narrowed to that provider
* (`--provider <name>` / a provider-qualified state address)
*/
export type CredentialScope = "targets" | "none" | "all" | (string & {});

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

/**
* 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 a provider within this run's
* {@link CredentialScope} has an empty key (offline commands pass "none")
*/
export async function resolveAgentProjectConfig(
host: CredentialHost,
filePath: string,
options: AgentConfigOptions = {},
): Promise<LoadedProjectConfig> {
prepareProviderEnv();
const resolved = await resolveProjectConfig(filePath, options);
normalizeInterpolatedProviderBlocks(resolved.config.providers);
injectProviderCredentials(resolved.config.providers, host);
scrubCredentialEnv();
const scope = options.credentials ?? "targets";
if (scope !== "none") {
assertProviderCredentials(
resolved.config.providers,
scope === "targets"
? resolveTargetProviderNames(resolved.config)
: scope === "all"
? Object.keys(resolved.config.providers)
: [scope],
);
}
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