dcg 统一 Robot Mode API 设计解析:为 AI Agent 打造稳定、可解析的命令行接口
【免费下载链接】destructive_command_guardThe Destructive Command Guard (dcg) is for blocking dangerous git and shell commands from being executed by agents.项目地址: https://gitcode.com/GitHub_Trending/de/destructive_command_guard
dcg(Destructive Command Guard)以 hook 形式嵌入 Claude Code、Gemini CLI 等 AI 编码代理,拦截危险的 git 与 shell 命令。本文基于仓库 docs/adr-002-robot-mode-api.md 展开,剖析其提出的统一Robot Mode(机器人模式)API设计:全局--robot标志、标准化退出码、统一OutputFormat枚举与纯 JSON 输出契约。读完你将掌握 dcg 为机器消费设计的完整接口约定、各子命令在 robot 模式下的行为差异,以及如何在自己的脚本或 Agent 工作流中可靠地消费 dcg 的输出。
背景:Agent 集成为何需要统一接口
dcg 作为 hook 被 AI 编码代理调用时,代理需要以编程方式解析 dcg 的输出。但在 ADR-002 提出之前,CLI 存在一系列不一致,使集成变得复杂:
- 格式标志不统一:不同命令混用
-f、-F、-o或--json布尔标志; - 默认值不统一:部分命令默认
pretty,部分默认json或text; - 缺少统一机器人模式:每条命令都要单独配置机器输出;
- 退出码不统一:各命令没有文档化的标准退出码;
- stderr 行为混杂:即使在 CI 环境,某些命令也会输出装饰性内容;
- JSON 字段命名不一致:hook 输出使用 camelCase(协议要求),其他输出使用 snake_case。
Agent 的核心需求
AI 代理需要的是:
- stdout 上是纯 JSON(无 ANSI 码、无装饰文本);
- stderr保持静默(至少不干扰 stdout 解析);
- 可预测的退出码用于决策;
- 稳定的 JSON schema,版本升级不破坏;
- 单一标志即可在所有命令上启用“机器模式”。
ADR-002 的决策正是围绕这六点展开,并已在仓库中落地实现。
全局--robot标志:一份配置、处处生效
ADR-002 设计了一个全局--robot布尔标志,并支持通过环境变量DCG_ROBOT启用。在 src/cli.rs 中可以看到它的实际定义:
/// Enable robot/machine mode for AI agent integration /// /// When enabled: /// - All output is JSON on stdout /// - stderr is completely silent (no rich output, no human messages) /// - Exit codes follow standardized values (see docs/adr-002-robot-mode-api.md) /// - Human-friendly decorations are suppressed #[arg(long, global = true)] pub robot: bool,注意实现细节:ADR 草案中曾设计env = "DCG_ROBOT"直接挂在 clap 参数上,而落地实现将其移到了输出层统一处理。在 src/output/mod.rs 的robot_mode_enabled中:
pub fn robot_mode_enabled(explicit_robot_flag: bool) -> bool { explicit_robot_flag || env_flag_enabled("DCG_ROBOT") }关键语义:显式--robot始终优先;DCG_ROBOT环境变量遵循与其他输出标志一致的布尔解析——0、false、no、n、off及空值都视为未启用,而非“只要设置了就算启用”。env_flag_value_enabled的实现验证了这一点(src/output/mod.rs 中test_env_flag_value_enabled_boolean_semantics测试覆盖了这些取值)。
在 src/main.rs 中,robot 模式被纳入“强制纯文本输出”的判定:
let robot_mode = destructive_command_guard::output::robot_mode_enabled(cli.robot); let force_plain_output = cli.legacy_output || cli.no_color || robot_mode;即 robot 模式下自动禁用颜色渲染,并同时禁用建议(suggestion)输出(init_suggestions(!cli.no_suggestions && !robot_mode))。
启用方式
# 方式一:命令行标志 dcg --robot test "rm -rf /" # 方式二:环境变量(支持 true/false 语义) DCG_ROBOT=1 dcg test "rm -rf /"行为对照表
| 维度 | 普通模式 | Robot 模式 |
|---|---|---|
| stdout | JSON 或 pretty | 始终 JSON |
| stderr | Rich 彩色输出 | 静默 |
| 退出码 | 各命令各异 | 标准化 |
| ANSI 码 | 视 TTY | 从不输出 |
| 进度指示 | 显示 | 隐藏 |
| 建议(suggestions) | 显示 | 仅在 JSON 中 |
| 警告 | 输出到 stderr | 编码进 JSON |
标准化退出码:让$?直接可决策
ADR-002 提议创建独立的退出码模块。仓库中 src/exit_codes.rs 已完整落地,且比 ADR 草案增加了两个实战常量:
| 码 | 常量 | 含义 |
|---|---|---|
| 0 | EXIT_SUCCESS | 成功 / 放行(allowed、passed、healthy) |
| 1 | EXIT_DENIED | 命令被安全规则拒绝/拦截 |
| 2 | EXIT_WARNING | 警告(配合--fail-on warn) |
| 3 | EXIT_CONFIG_ERROR | 配置错误(配置文件无效、缺少必需配置) |
| 4 | EXIT_PARSE_ERROR | 解析/输入错误(无效 JSON、畸形命令) |
| 5 | EXIT_IO_ERROR | IO 错误(文件未找到、权限拒绝、网络错误) |
| 141 | EXIT_BROKEN_PIPE | stdout/stderr 读取方提前离开(EPIPE),是干净退出而非信号死亡 |
| 2 | EXIT_HOOK_BLOCK | 仅 hook 模式:deny/ask/indeterminate 裁决无法写入 stdout 时,以退出码承载拦截 |
EXIT_BROKEN_PIPE(141)的由来
EXIT_BROKEN_PIPE对应128 + SIGPIPE(13),让dcg … | head -1在set -o pipefail下与cat … | head -1表现完全一致。dcg 通过 broken-pipe panic 兜底(issue #389)走干净的process::exit到达该码——永远不会因信号死亡,因此无 core dump、无SIGABRT,且SIGPIPE本身保持忽略(详见output::emit的注释:hook 二进制不能在 stderr 写入时死亡,因为其 stdout 上的裁决可能仍有读者)。
EXIT_HOOK_BLOCK(2)与EXIT_WARNING(2)的共存
两个常量数值相同(都是 2),但永远不会出现在同一进程中:
EXIT_WARNING属于 robot 模式子命令(如dcg --robot test --fail-on warn);EXIT_HOOK_BLOCK属于 hook 模式(无子命令)。当 stdout 写入本身失败(EPIPE,宿主在裁决写入前关闭了管道)时,退出 0 且无 JSON 会被宿主解读为“放行”,从而把 deny 变成 allow——这是不可接受的,因此拦截结果改由退出码 2 承载,理由写到 stderr。每个 hook 协议的“裁决无法送达时的退出码”映射表定义在HookProtocol::undeliverable_block_exit_code上。
各协议对“退出 2 + 空 stdout”的解读(来自 docs/agents.md):
| 协议 | 效果 |
|---|---|
| Claude Code 及兼容宿主(Posit Assistant、Augment) | 拦截;stderr 作为理由反馈给模型 |
| Gemini CLI | 拦截(退出 2 即其阻断错误) |
| Copilot CLI | 拦截(preToolUse钩子退出 2 拒绝调用) |
| Crush | 拦截;stderr 即理由 |
| Grok | 拦截(退出 2 是文档化的显式拒绝) |
Codex CLI、Hermes、Antigravity(agy) | 记为 hook 失败后 fail-open——与退出 0 无 JSON 效果相同,但可见 |
hook 模式绝不会因其他原因退出 2,也从不退出 141(hook 模式的每次写入都容忍关闭的管道)。
辅助 API
exit_codes模块还提供to_exit_code(i32) -> ExitCode(供main()返回)、exit_with(code) -> !(便捷退出包装)以及ToExitCodetrait(将求值结果转换为退出码)。模块内测试(exit_codes_are_distinct、exit_codes_are_valid_range、success_is_zero等)保证这些常量互不冲突且落在 0–255 合法区间。
统一OutputFormat枚举:消灭各自为政的格式类型
ADR-002 设计了跨命令共享的OutputFormat枚举,落地实现位于 src/cli.rs:
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, clap::ValueEnum, serde::Serialize)] #[serde(rename_all = "lowercase")] pub enum OutputFormat { /// Human-readable colored output (default for interactive use) #[default] #[value(alias = "text", alias = "human")] Pretty, /// Structured JSON output (for agents and scripting) #[value(alias = "sarif", alias = "structured")] Json, /// JSON Lines format (one JSON object per line, for streaming) #[value(name = "jsonl")] Jsonl, /// Compact single-line output (for specific commands) Compact, }该枚举提供两个实用谓词:
is_json():Json或Jsonl均为真——用于判断输出是否可被 JSON 解析器消费;is_human_readable():Pretty或Compact均为真。
Robot 模式下的格式默认值:当--robot启用时,格式强制默认Json,无论命令自身的默认值是什么。因此下面两条命令等价:
dcg --robot test "cmd" dcg --robot --format json test "cmd"可用的格式别名
pretty/text/human—— 人类可读(无--robot时的默认);json/sarif/structured—— JSON 输出(--robot时的默认);jsonl—— JSON Lines(每行一个对象,适合流式处理);compact—— 紧凑单行输出(用于explain等特定命令)。
从源码结构看,除统一枚举外,各子命令仍保留了自己的强类型格式枚举(TestFormat、CorpusFormat、StatsFormat、ClassifyFormat、ConfigFormat、PacksFormat、DoctorFormat等),它们大多以Pretty/Json二元结构为主,并通过DCG_FORMAT环境变量统一驱动默认值——这印证了 ADR 中“先加统一枚举、逐步淘汰零散类型”的迁移思路。
Robot 模式 JSON 输出:稳定、可解析的响应契约
ADR-002 设想的响应信封包含dcg_version、schema_version、command_name、success、data、metadata等字段。实际落地为每个子命令输出稳定的 JSON,例如dcg test的 deny 结果(tests/golden/robot/deny_filesystem.json):
{ "schema_version": 1, "dcg_version": "<DYNAMIC_VERSION>", "robot_mode": true, "command": "rm -rf /", "decision": "deny", "mode": "deny", "rule_id": "core.filesystem:rm-rf-root-home", "pack_id": "core.filesystem", "pattern_name": "rm-rf-root-home", "reason": "rm -rf on root or home paths is EXTREMELY DANGEROUS. ...", "explanation": "This command would recursively delete files starting from the root filesystem (/) ...", "source": "pack", "matched_span": [3, 6], "severity": "critical", "agent": { "detected": "unknown", "trust_level": "medium", "detection_method": "none" } }allow 结果的字段更精简(tests/golden/robot/allow_git_status.json):
{ "schema_version": 1, "dcg_version": "0.4.1", "robot_mode": true, "command": "git status", "decision": "allow", "agent": { "detected": "unknown", "trust_level": "medium", "detection_method": "none" } }Agent 元数据字段
输出中的agent对象承载代理检测信息:detected(代理标识,如claude-code)、trust_level(high/medium/low,默认medium)、detection_method(如environment_variable)。检测优先级为:显式--agent标志 > 环境变量 > 父进程检查 > unknown(详见 docs/agents.md)。trust_level是建议性标签,本身不改变规则触发,行为差异全部来自disabled_packs、extra_packs、additional_allowlist、disabled_allowlist等配置项。
Golden 文件测试保障 schema 稳定
仓库用 golden 文件锁定 robot 模式 JSON,路径 tests/golden/robot/ 下含allow_git_status.json、allow_simple.json、deny_filesystem.json、deny_git_force_push.json、deny_git_reset.json五个快照。配合dcg_version字段支持版本占位符(如<DYNAMIC_VERSION>),测试可在版本更新时动态替换,从而验证“schema 不因版本而破坏”这一 ADR 目标。
Hook 模式 vs Robot 模式:两条契约的分工
这是理解 dcg 接口的关键——hook 模式与 robot 模式是两套不同的退出码/输出契约(docs/agents.md):
- Hook 模式(无子命令时默认):裁决写在 stdout JSON 上,allow 时为空 stdout;进程退出 0,无论命令是允许、警告、送审还是拒绝。仅当阻断裁决无法送达时退出 2(
EXIT_HOOK_BLOCK)。Codex CLI 使用严格 hook 解析,dcg 输出最小化的hookSpecificOutput拒绝负载并退出 0。 - Robot 模式(带子命令,如
dcg --robot test):deny 时退出 1(便于脚本直接判断$?),stdout 纯 JSON,stderr 静默。
stdout / stderr 分离原则
dcg 将面向 Agent 的输出与面向人类的输出放在不同流上,这是 rich 格式化兼容性的根基:
| 流 | 用途 | Hook 模式内容 | Robot 模式内容 |
|---|---|---|---|
| stdout | Agent 与脚本解析 | 拒绝时为协议 JSON,放行为空 | 仅 JSON |
| stderr | 人类可见诊断 | Rich 或纯文本警告框 | 静默 |
Rich 输出纯属展示用途,绝不允许被 Agent 解析,也绝不允许写入 stdout。Unicode 边框、颜色、高亮命令、建议面板全部归属 stderr。
实战:在 Agent 工作流中消费 robot 模式
场景一:脚本按退出码决策
#!/bin/bash # Script for AI agent to check commands before execution check_command() { local cmd="$1" local result # Use robot mode for predictable output result=$(dcg --robot test "$cmd" 2>/dev/null) local exit_code=$? if [ $exit_code -eq 0 ]; then echo "Command allowed: $cmd" return 0 elif [ $exit_code -eq 1 ]; then echo "Command BLOCKED: $cmd" echo "Reason: $(echo "$result" | jq -r '.reason')" return 1 else echo "Error checking command (exit code: $exit_code)" return $exit_code fi } # Usage check_command "git status" # Allowed check_command "rm -rf /" # Blocked场景二:包装器同时保留两条流
# Hook integration: preserve both streams. dcg < hook-input.json >hook-stdout.json 2>human-warning.txt # Scripting integration: use robot mode and parse stdout only. dcg --robot test "rm -rf /" >decision.json 2>/dev/null对于 Codex 和 Claude 兼容的 hook 集成:stdout 非空时解析 stdout;stdout 为空且退出 0 视为 allow。Codex 的拒绝负载是最小化的,且有意省略 dcg 专属元数据。
场景三:作为其他工具的底层调用
从源码结构看,dcg 自身的集成组件也依赖 robot 模式:OMP 扩展通过dcg --robot test --stdin --agent omp --format json做每命令预检(见 docs/agents.md),并以--dialect posix指定 shell 方言。这说明 robot 模式是 dcg 生态内部组件的通用机器接口,而不仅是外部脚本的便利开关。
Rich 输出降级控制:机器人环境的兜底保障
除--robot外,以下任一条件都会让 dcg 回退到纯文本/静默输出(src/output/mod.rs 的should_use_rich_output_with_env):
| 控制项 | 效果 |
|---|---|
DCG_NO_RICH=1 | 禁用 rich 格式化,保持正常命令行为 |
--legacy-output/DCG_LEGACY_OUTPUT=1 | 强制 legacy/纯文本渲染路径 |
NO_COLOR=1/DCG_NO_COLOR=1 | 禁用彩色输出 |
TERM=dumb | 使用 dumb 终端安全输出 |
CI=1 | CI 下抑制 rich 交互格式化 |
| stdout 非 TTY | 偏好纯文本,利于管道 |
--robot/DCG_ROBOT=1 | 机器可读 stdout + stderr 静默 |
判定的完整顺序是:显式强制纯文本 →DCG_NO_RICH/NO_COLOR/DCG_NO_COLOR→CI环境变量 → stdout 是否为 TTY →TERM=dumb。这些开关共同保证:无论 Agent 宿主环境如何(CI、管道、无 TTY 子进程),stdout 始终可安全解析。
迁移策略与影响评估
ADR-002 规划的迁移分三个阶段推进:
- Phase 1(非破坏):新增
--robot标志与DCG_ROBOT环境变量、新增exit_codes模块、新增OutputFormat枚举、在 AGENTS.md 中记录 robot 模式——均已落地; - Phase 2(弃用期):弃用命令各自的
--json布尔标志与不一致的格式枚举;旧标志继续工作并发出弃用警告; - Phase 3(未来):在下一个大版本移除已弃用标志;考虑 gRPC/MCP 原生协议(注:仓库已提供
dcg mcp-server子命令,以 stdio 方式暴露check_command、scan_file、explain_pattern工具,是这一方向的先行实现)。
影响总结
正面:单一--robot标志即可配置一切,Agent 集成更简单;行为可预测;JSON schema 版本化防止破坏性变更;golden 文件测试可验证 robot 输出;Agent 开发者有集中、清晰的文档。
负面:CLI 新增一个全局标志;现有使用--json的脚本最终需要更新;需要同时维护人类与机器两条输出路径。
中性:完全向后兼容,所有既有行为继续工作;robot 模式是 opt-in,默认仍是人类友好的 pretty 输出。
最佳实践清单
- 脚本集成一律使用 robot 模式:需要稳定可解析输出时,用
--robot而非命令各自的--format json; - 简单判定只看退出码:allow/deny 直接读
$?(0/1),无需解析 JSON;需要理由再jq -r '.reason'; - 永远丢弃 stderr:robot 模式下 stderr 应为空;脚本中建议
2>/dev/null防御性隔离; - 不要解析 rich 输出:人类可读的装饰内容属于 stderr,只用于展示;
- CI 中审计 Agent 行为:用
--format json审计哪些 Agent 在访问代码库(配合agent元数据字段); - 检查
schema_version:消费方应对输出中的schema_version做校验,防止 schema 变更时静默破坏; - hook 集成遵循协议语义:hook 模式退出 0 携带裁决 JSON,空 stdout + 0 = allow;阻断裁决无法送达时退出 2,切勿把退出 2 误读为“警告”。
延伸阅读
- 设计原文:docs/adr-002-robot-mode-api.md
- Agent 配置与集成指南:docs/agents.md
- 退出码常量实现:src/exit_codes.rs
--robot标志与OutputFormat枚举:src/cli.rs- robot 模式启用判定:src/output/mod.rs
- Robot 模式 golden 输出快照:tests/golden/robot/
- 相关测试:tests/robot_mode.rs、tests/golden_json_tests.rs
- 相邻设计:MCP 服务器集成见 docs/pi-integration.md 与
dcg mcp-server子命令
【免费下载链接】destructive_command_guardThe Destructive Command Guard (dcg) is for blocking dangerous git and shell commands from being executed by agents.项目地址: https://gitcode.com/GitHub_Trending/de/destructive_command_guard
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考