Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 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
9819eb6
feat(agent): 非bailian provider也走鉴权逻辑
chenanran555 Jul 27, 2026
2dce9fe
feat(agent): session and destroy failed 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`、`apply`、`destroy`、`state import`、`skill-list`、全部 `session *`:统一声明 `auth: "apiKey"` 硬门禁 —— 无论目标 provider 是谁,authStage 都经 `resolveApiKey(sources)` 解析 bailian 凭证(flag > env > active profile config),缺失报统一 AUTH;引擎层 `assertProviderCredentials` 再对 agents.yaml 里**全部已声明 provider** 的空 key 拦截并给 provider 专属 hint。例外:`plan --no-refresh` / `plan --dry-run` 传 `credentials: "none"` 并强制 `refresh: false`(不联网、不回写 state,不查 provider key),其中 `--dry-run` 连登录也不要求(authStage 的 dry-run 豁免),`--no-refresh` 仍需登录。

凭证不以真实值写入 `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` 归一为 `""`(避免离线命令下空 key 在 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)` — 任一已声明 provider 的 `api_key` 为空 → CLI 权威 `AUTH` 错误 + provider 专属 hint(取代 SDK 原始插值/zod 报错);离线命令传 `credentials: "none"` 整体跳过

`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
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})`;
}
105 changes: 105 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,105 @@
import {
createProjectRuntime,
type LoadedProjectConfig,
type ProjectRuntimeContext,
resolveProjectConfig,
UserError,
} from "@openagentpack/sdk";
import {
assertProviderCredentials,
type CredentialHost,
injectProviderCredentials,
normalizeInterpolatedProviderBlocks,
prepareProviderEnv,
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";

/**
* Whether this run requires provider keys:
* - "all" (default) — online command: every provider declared in agents.yaml
* must have a non-empty key after injection
* - "none" — offline command (local config/state only), skip the check
*/
export type CredentialScope = "all" | "none";

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 any provider's key is empty
* (offline commands pass `credentials: "none"` to skip the check)
*/
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();
if ((options.credentials ?? "all") !== "none") {
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