Skip to content

Commit 0872ff6

Browse files
committed
feat(cli): expose high-risk confirmation guidance in help and skills
1 parent afb547e commit 0872ff6

14 files changed

Lines changed: 288 additions & 114 deletions

File tree

docs/agents/skill-change.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ bailian-gen bailian-finetune bailian-managed-agent bailian-web-search
4040

4141
- [ ] **整包装齐**:安装/升级文案主推 `bl skill init`;业务 skill ****声明 `companions`
4242
- [ ] **协议读取**:CRITICAL / references 可链 `../bailian-protocol/…`;若读不到 → 停止执行 `bl`,提示 `bl skill init`
43+
- [ ] **高风险确认**:统一由 `bailian-protocol` 定义;reference / leaf help 以 `risk: high` 明示风险,业务 skill 不得引导 Agent 自动补 `--yes`。遇到 exit code 7 / `requires_confirmation` 时停止执行并请求确认;目标或范围变化后重新确认
44+
- [ ] **正常控制流**`requires_confirmation` 不是 CLI bug,`assets/issue-reporting.md` 必须将 exit code 7 保持在 EXCLUDE 范围
4345
- [ ] **软 hand-off**:兄弟业务 skill **只写 skill 名**;已安装则 Read,未安装则 `bl … --help` 或提示整包安装;**不要**`../bailian-gen/…` 等写成执行前提
4446
- [ ] **Hub vs 领域**`bailian-cli` 的「When to use which command」只列 hub 拥有的意图;媒体 / 精调 / managed-agent 各留 hand-off 行,**不抄**领域默认模型与子命令明细
4547
- [ ] **渐进披露**:SKILL 写意图路由与领域硬规则;flags / usage / examples 以 `reference/``bl <command> --help` 为准,表后保留「勿猜 flag」指向句
@@ -55,6 +57,7 @@ bailian-gen bailian-finetune bailian-managed-agent bailian-web-search
5557

5658
- [ ] 新一级命令组归属领域时:改 `tools/generate-reference.ts``GROUP_OWNER_SKILL`,并更新**拥有方** skill 的路由表;hub 最多加一行 hand-off
5759
- [ ]`pnpm run sync:skill-assets`(或 commit 走 pre-commit),提交生成的 `reference/` 与 version 同步结果
60+
- [ ] 高风险命令生成的 reference 必须包含 `Risk` / `Risk message` 和简短 Agent safety 提示;带 `--yes` 的示例必须标注只能在确认后执行,不要手改生成物
5861
- [ ] 默认模型若写在领域路由表(如 `bailian-gen`):与命令 default / [model-add-remove.md](model-add-remove.md) 一并核对
5962

6063
## 完成后自查
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { readFileSync, readdirSync } from "node:fs";
2+
import { dirname, join } from "node:path";
3+
import { fileURLToPath } from "node:url";
4+
import { expect, test } from "vite-plus/test";
5+
6+
const repositoryRoot = join(dirname(fileURLToPath(import.meta.url)), "../../..");
7+
const skillsRoot = join(repositoryRoot, "skills");
8+
9+
test("every generated high-risk command reference requires user confirmation before --yes", () => {
10+
let highRiskCommandCount = 0;
11+
12+
for (const skillDirectory of readdirSync(skillsRoot, { withFileTypes: true })) {
13+
if (!skillDirectory.isDirectory()) continue;
14+
const referenceDirectory = join(skillsRoot, skillDirectory.name, "reference");
15+
16+
let referenceFiles: string[];
17+
try {
18+
referenceFiles = readdirSync(referenceDirectory).filter(
19+
(fileName) => fileName.endsWith(".md") && fileName !== "index.md",
20+
);
21+
} catch {
22+
continue;
23+
}
24+
25+
for (const referenceFile of referenceFiles) {
26+
const markdown = readFileSync(join(referenceDirectory, referenceFile), "utf8");
27+
const commandSections = markdown.split(/(?=^### `bl )/m).slice(1);
28+
29+
for (const commandSection of commandSections) {
30+
if (!commandSection.includes("`--yes`")) continue;
31+
highRiskCommandCount += 1;
32+
expect(commandSection).toMatch(/\|\s+\*\*Risk\*\*\s+\|\s+`high`\s+\|/);
33+
expect(commandSection).toMatch(/\|\s+\*\*Risk message\*\*\s+\|\s+.+\|/);
34+
expect(commandSection).toMatch(/type=.*requires_confirmation/);
35+
const agentSafetyLine = commandSection
36+
.split("\n")
37+
.find((line) => line.startsWith("> **Agent safety:**"));
38+
expect(agentSafetyLine).toBeDefined();
39+
expect(agentSafetyLine).toMatch(/never add `--yes` automatically/i);
40+
expect(agentSafetyLine).toMatch(/explicit user confirmation/i);
41+
expect(agentSafetyLine).not.toContain("`--dry-run`");
42+
}
43+
}
44+
}
45+
46+
expect(highRiskCommandCount).toBeGreaterThan(0);
47+
});

packages/runtime/src/registry.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type {
22
AnyCommand,
33
AuthRequirement,
4+
CommandRiskLevel,
45
FlagDef,
56
FlagsDef,
67
LocalizedText,
@@ -41,10 +42,16 @@ const AUTH_LABELS = {
4142
none: { "en-US": "No Auth", "zh-CN": "无需鉴权" },
4243
} satisfies Record<AuthRequirement, LocalizedText>;
4344

45+
const RISK_LEVEL_LABELS = {
46+
high: { "en-US": "high", "zh-CN": "高风险" },
47+
} satisfies Record<CommandRiskLevel, LocalizedText>;
48+
4449
const HELP_TEXT = {
4550
usage: { "en-US": "Usage:", "zh-CN": "用法:" },
4651
commands: { "en-US": "Commands:", "zh-CN": "命令:" },
4752
authentication: { "en-US": "Authentication:", "zh-CN": "鉴权方式:" },
53+
risk: { "en-US": "Risk:", "zh-CN": "风险等级:" },
54+
riskMessage: { "en-US": "Risk message:", "zh-CN": "风险说明:" },
4855
flags: { "en-US": "Flags:", "zh-CN": "选项:" },
4956
globalFlags: { "en-US": "Global Flags:", "zh-CN": "全局选项:" },
5057
modelAuthFlags: { "en-US": "Model Auth Flags:", "zh-CN": "模型鉴权选项:" },
@@ -64,6 +71,10 @@ const HELP_TEXT = {
6471
},
6572
notes: { "en-US": "Notes:", "zh-CN": "说明:" },
6673
examples: { "en-US": "Examples:", "zh-CN": "示例:" },
74+
confirmedExample: {
75+
"en-US": "# Only after explicit confirmation:",
76+
"zh-CN": "# 仅在明确确认后执行:",
77+
},
6778
minimalWorkflow: { "en-US": "Minimal workflow.yaml:", "zh-CN": "最小 workflow.yaml:" },
6879
tryIt: { "en-US": "Try it:", "zh-CN": "试一试:" },
6980
} satisfies Record<string, LocalizedText>;
@@ -432,6 +443,12 @@ ${authFlagSections ? `${authFlagSections}\n\n` : ""}${b(this.localize(HELP_TEXT.
432443
out.write(
433444
`${b(this.localize(HELP_TEXT.authentication))} ${a(this.localize(AUTH_LABELS[cmd.auth]))}\n`,
434445
);
446+
if (cmd.risk !== undefined) {
447+
out.write(
448+
`${b(this.localize(HELP_TEXT.risk))} ${a(this.localize(RISK_LEVEL_LABELS[cmd.risk.level]))}\n`,
449+
);
450+
out.write(`${b(this.localize(HELP_TEXT.riskMessage))} ${this.localize(cmd.risk.message)}\n`);
451+
}
435452
const flagEntries = [
436453
...Object.entries(cmd.flags ?? {}),
437454
...Object.entries(confirmationFlagDefs(cmd)),
@@ -460,6 +477,9 @@ ${authFlagSections ? `${authFlagSections}\n\n` : ""}${b(this.localize(HELP_TEXT.
460477
out.write(`\n${b(this.localize(HELP_TEXT.examples))}\n`);
461478
for (const example of cmd.exampleArgs) {
462479
const localizedExample = this.localize(example);
480+
if (cmd.risk !== undefined && /(?:^|\s)--yes(?:\s|$)/.test(localizedExample)) {
481+
out.write(` ${d(this.localize(HELP_TEXT.confirmedExample))}\n`);
482+
}
463483
const line = localizedExample.startsWith("#")
464484
? localizedExample
465485
: localizedExample

packages/runtime/tests/i18n.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ test("registry renders runtime help copy with the selected language", async () =
4747
},
4848
],
4949
auth: "none",
50+
risk: {
51+
level: "high",
52+
message: {
53+
"en-US": "This operation is permanent.",
54+
"zh-CN": "该操作无法撤销。",
55+
},
56+
},
5057
run: async () => {},
5158
});
5259
const registry = new CommandRegistry({ test: command }, "bl", translator);
@@ -69,6 +76,8 @@ test("registry renders runtime help copy with the selected language", async () =
6976

7077
output = "";
7178
registry.printHelp(["test"], stream);
79+
expect(output).toContain("风险等级: 高风险");
80+
expect(output).toContain("风险说明: 该操作无法撤销。");
7281
expect(output).toContain("测试说明");
7382
expect(output).toContain('bl test --message "你好"');
7483
expect(output).toContain(" # 流式输出响应");

packages/runtime/tests/registry-guard.test.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,12 @@ test("high risk 命令不能自行声明 runtime 保留的 yes", () => {
5353
expect(() => new CommandRegistry({ "x normal": normal }, "bl")).not.toThrow();
5454
});
5555

56-
test("命令 help 只为 high risk 展示 runtime 注入的 --yes", () => {
56+
test("命令 help 只为 high risk 展示风险信息和 runtime 注入的 --yes", () => {
5757
const high = defineCommand({
5858
description: "danger",
5959
auth: "none",
6060
risk: { level: "high", message: "dangerous operation" },
61+
exampleArgs: ["--dry-run", "--yes"],
6162
run: noopRun,
6263
});
6364
const normal = defineCommand({
@@ -77,5 +78,10 @@ test("命令 help 只为 high risk 展示 runtime 注入的 --yes", () => {
7778
} as unknown as NodeJS.WriteStream);
7879

7980
expect(highHelp).toContain("--yes");
81+
expect(highHelp).toContain("Risk: high");
82+
expect(highHelp).toContain("Risk message: dangerous operation");
83+
expect(highHelp).toMatch(/# Only after explicit confirmation:\n\s+bl asset delete --yes/);
8084
expect(normalHelp).not.toContain("--yes");
85+
expect(normalHelp).not.toContain("Risk:");
86+
expect(normalHelp).not.toContain("Risk message:");
8187
});

skills/bailian-cli/SKILL.md

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -56,30 +56,30 @@ Do not guess flags — use the reference files or `--help`.
5656

5757
Use this table only after the decision table in [`bailian-protocol`](../bailian-protocol/SKILL.md#provider-selection-and-consent) has routed the request to `bl` (class 4, or class 2 after the user picks Bailian). Hub-owned intents only — for media / fine-tune / agents.yaml, soft hand-off to the domain skill.
5858

59-
| User intent | Command | Notes |
60-
| ------------------------------------------------ | --------------------------------------------- | -------------------------------------------------------------------------------- |
61-
| Explicit Bailian model chat / text execution | `bl text chat` | Default `qwen3.8-max` |
62-
| Bailian agent / workflow | `bl app call` | Needs `--app-id` |
63-
| Find app by name | `bl app list` then `bl app call` | Console auth |
64-
| Bailian app memory CRUD (not host-agent memory) | `bl memory *` | [`reference/memory.md`](reference/memory.md) |
65-
| Bailian knowledge base RAG | `bl knowledge search` / `chat` | API key + agent/workspace IDs |
66-
| Upload a file as a step of a Bailian workflow | `bl file upload` | When you need `oss://` URL explicitly; not for generic hosting |
67-
| Bailian model selection / recommendation | `bl advisor recommend` | Intent → candidate recall → LLM ranking |
68-
| Bailian model catalog / pricing / params | `bl model list` | Console auth; `--model <family>` for detail, `--enrich` for input params |
69-
| Install / list / update / remove registry skills | `bl skill add` / `list` / `update` / `remove` | Bailian skill registry; see [`reference/skill.md`](reference/skill.md) |
70-
| Bailian MCP marketplace discovery / call | `bl mcp list` / `tools` / `call` | |
71-
| Bailian pipeline workflow (a step in a bl flow) | `bl pipeline run` / `validate` | JSON/YAML workflow definitions |
72-
| Bailian rate limits / quota | `bl quota list` / `check` / `request` | Console auth; class 2 — ask which product first if unnamed |
73-
| Bailian free tier / usage stats | `bl usage free` / `stats` / `freetier` | Console auth; class 2 — ask which product first if unnamed |
74-
| Bailian Token Plan quota usage | `bl usage token-plan` | Console auth; class 2 — ask which product first if unnamed |
75-
| Bailian Coding Plan quota usage | `bl usage coding-plan` | Console auth; class 2 — ask which product first if unnamed |
76-
| Console API (advanced) | `bl console call` | Console auth |
77-
| Bailian workspace listing | `bl workspace list` | Console auth |
78-
| Switch CLI Help / Quick Start language | `bl config set --key language --value zh-CN` | Use `en-US` to switch back; follows the active config profile |
79-
| Image / video / speech / omni / vision | → skill `bailian-gen` | Fallback: `bl image\|video\|speech\|omni\|vision --help` |
80-
| Dataset / fine-tune / deploy | → skill `bailian-finetune` | Fallback: `bl dataset\|finetune\|deploy --help` |
81-
| agents.yaml IaC / managed-agent sessions | → skill `bailian-managed-agent` | Fallback: `bl managed-agent --help`; `apply`/`destroy` need `--yes` after `plan` |
82-
| Web search (model-aware routing) | → skill `bailian-web-search` | Token Plan vs MCP path + fallback; fallback: `bl search web --help` |
59+
| User intent | Command | Notes |
60+
| ------------------------------------------------ | --------------------------------------------- | -------------------------------------------------------------------------- |
61+
| Explicit Bailian model chat / text execution | `bl text chat` | Default `qwen3.8-max` |
62+
| Bailian agent / workflow | `bl app call` | Needs `--app-id` |
63+
| Find app by name | `bl app list` then `bl app call` | Console auth |
64+
| Bailian app memory CRUD (not host-agent memory) | `bl memory *` | [`reference/memory.md`](reference/memory.md) |
65+
| Bailian knowledge base RAG | `bl knowledge search` / `chat` | API key + agent/workspace IDs |
66+
| Upload a file as a step of a Bailian workflow | `bl file upload` | When you need `oss://` URL explicitly; not for generic hosting |
67+
| Bailian model selection / recommendation | `bl advisor recommend` | Intent → candidate recall → LLM ranking |
68+
| Bailian model catalog / pricing / params | `bl model list` | Console auth; `--model <family>` for detail, `--enrich` for input params |
69+
| Install / list / update / remove registry skills | `bl skill add` / `list` / `update` / `remove` | Bailian skill registry; see [`reference/skill.md`](reference/skill.md) |
70+
| Bailian MCP marketplace discovery / call | `bl mcp list` / `tools` / `call` ||
71+
| Bailian pipeline workflow (a step in a bl flow) | `bl pipeline run` / `validate` | JSON/YAML workflow definitions |
72+
| Bailian rate limits / quota | `bl quota list` / `check` / `request` | Console auth; class 2 — ask which product first if unnamed |
73+
| Bailian free tier / usage stats | `bl usage free` / `stats` / `freetier` | Console auth; class 2 — ask which product first if unnamed |
74+
| Bailian Token Plan quota usage | `bl usage token-plan` | Console auth; class 2 — ask which product first if unnamed |
75+
| Bailian Coding Plan quota usage | `bl usage coding-plan` | Console auth; class 2 — ask which product first if unnamed |
76+
| Console API (advanced) | `bl console call` | Console auth |
77+
| Bailian workspace listing | `bl workspace list` | Console auth |
78+
| Switch CLI Help / Quick Start language | `bl config set --key language --value zh-CN` | Use `en-US` to switch back; follows the active config profile |
79+
| Image / video / speech / omni / vision | → skill `bailian-gen` | Fallback: `bl image\|video\|speech\|omni\|vision --help` |
80+
| Dataset / fine-tune / deploy | → skill `bailian-finetune` | Fallback: `bl dataset\|finetune\|deploy --help` |
81+
| agents.yaml IaC / managed-agent sessions | → skill `bailian-managed-agent` | Fallback: `bl managed-agent --help`; `apply`/`destroy` also require `plan` |
82+
| Web search (model-aware routing) | → skill `bailian-web-search` | Token Plan vs MCP path + fallback; fallback: `bl search web --help` |
8383

8484
Flags, usage, and examples: see hub [`reference/`](reference/index.md) or `bl <command> --help` — do not guess flags. Domain command details live in the owning skill's `reference/`.
8585

@@ -123,6 +123,7 @@ schema-export commands.
123123
- Usage / quota / credits questions that do not name a product → ask which product (Bailian or another AI service) first; run `bl usage` / `bl quota` only after the user picks Bailian or Bailian context is already established.
124124
- "Remember this" and memory requests default to the host agent's own memory; `bl memory *` is only for Bailian app memory resources.
125125
- `bl file upload` and `bl pipeline run` are steps inside a Bailian workflow; do not use them to capture generic "upload this file" or "run a pipeline" requests.
126-
- `bl managed-agent apply` / `destroy` mutate remote resources and only execute with `--yes`; run `plan` first and show the diff before confirming a mutation.
126+
- For `risk: high` commands or `requires_confirmation`, follow the shared protocol; never add `--yes` automatically.
127+
- `bl managed-agent apply` / `destroy` have an additional domain rule: run `plan` first and show the diff before asking for confirmation.
127128
- When a matched `bl` command accepts a file URL, pass local paths directly; never require the user to host the file first.
128129
- Console login → always `--console-site domestic|international`; see [`../bailian-protocol/assets/setup.md`](../bailian-protocol/assets/setup.md#console-site-selection).

0 commit comments

Comments
 (0)