☰
GSD Headless 答案注入完全指南:用 `--answers` 预置答案与密钥,实现零交互的全自动软件构建
2026/10/6 2:04:10 网站建设 项目流程
  • 人工智能
  • AI Agent
  • 代码智能体
  • Agent 编排
  • CLI
  • AI 应用

【免费下载链接】gsd-2

A powerful meta-prompting, context engineering and spec-driven development system that enables agents to work for long periods of time autonomously without losing track of the big picture

项目地址:https://gitcode.com/gh_mirrors/gs/gsd-2
点击查看免费下载

导读:本文围绕 GSD(gsd-2)开源仓库中 gsd-orchestrator/references/answer-injection.md 所定义的Answer Injection(答案注入)机制展开,系统讲解在 headless(无头)执行模式下如何通过--answers参数预置问题答案与敏感密钥,从而消除交互式提示,让 Agent 可以长时间无人值守地自主完成从 spec 到可运行软件的完整构建流程。读完本文,你将掌握答案文件的完整 JSON Schema 与校验约束、secrets 注入的环境变量链路、两阶段问题关联的底层实现,以及它如何与--supervised监督模式分层协作。

为什么需要答案注入:headless 模式的"最后一公里"问题

GSD 的 headless 模式面向的是完全自主的软件交付场景:Agent 以子进程方式调用gsd headless,GSD 在内部负责规划、编码、测试、提交的全流程(见 gsd-orchestrator/SKILL.md 的 mental model)。但构建过程中 Agent 不可避免地会遇到需要"人"来拍板的时刻——比如 LLM 调用ask_user_questions工具向用户询问部署目标、技术选型,或者secure_env_collect工具请求用户输入 API Key。

在无人值守的 CI 或长时运行场景里,任何一次交互式弹窗都可能让整个流程卡死。Answer Injection 正是为解决这一问题而设计:在启动 headless 会话之前,把所有可能被问到的答案和需要用到的密钥写进一个 JSON 文件,通过--answers参数预置,GSD 在运行中自动"代答",让整个构建过程零人工介入。

从源码结构看,该能力由 src/headless-answers.ts 独立模块实现,并由 src/headless.ts 的 headless 主流程集成。它同时被打包进 GSD 扩展技能文档 src/resources/extensions/gsd/skills/gsd-headless/references/answer-injection.md,说明这套机制既服务 CLI 用户,也面向使用该技能的自主 Agent。

CLI 用法:--answers与命令的组合方式

答案注入通过 headless 的--answers标志启用,其值指向一个包含预置答案与密钥的 JSON 文件路径:

# 直接进入 auto 模式(自动执行全部排队的单元直至里程碑完成) gsd headless --answers answers.json auto # 从 spec 文件创建里程碑,并链式进入 auto 模式 gsd headless --answers answers.json new-milestone --context spec.md --auto

两条命令展示了两种典型场景:前者用于在已初始化的项目上继续/恢复自动构建,后者用于"写 spec → 建里程碑 → 立即自动执行"的全新构建链路。注意一个细节:new-milestone --auto会在里程碑创建成功后自动链入 auto 阶段,此时--answers文件会在两个阶段都持续生效(详见后文 headless.ts 的milestoneReady链式逻辑)。

从 src/headless.ts 可以看到启动时的加载逻辑:headless 入口解析options.answers路径后调用loadAndValidateAnswerFile,若文件不存在、JSON 非法或 schema 不满足约束,会在启动阶段直接向 stderr 输出错误并以退出码 1 终止——答案文件的问题会被快速失败,而不是等构建跑到一半才暴露。

另外需要注意 headless 的一个通用约束:标志必须放在命令之前(gsd headless [--flags] [command] [args]),放在命令之后的标志会被忽略(见 gsd-orchestrator/SKILL.md 的 critical rules)。

答案文件 Schema 详解

答案文件是一个顶层 JSON 对象,包含questions、secrets、defaults三个可选字段:

{ "questions": { "question_id": "selected_option_label", "multi_select_question": ["option_a", "option_b"] }, "secrets": { "API_KEY": "sk-...", "DATABASE_URL": "postgres://..." }, "defaults": { "strategy": "first_option" } }

各字段语义如下:

字段类型说明
questionsRecord<string, string \| string[]>问题 ID → 答案的映射。单选框用字符串(选项 label),多选框用字符串数组(多个选项 label)。
secretsRecord<string, string>环境变量名 → 值的映射。会被注入到子进程的环境变量中。
defaults.strategy"first_option" \| "cancel"未匹配问题的兜底策略。"first_option"为默认值(自动选择第一个可用选项),"cancel"表示直接取消该请求。

其中questions的 key 是问题的稳定标识符(question ID)。这个 ID 由调用ask_user_questions工具的 LLM 生成,规范要求使用 snake_case(见 src/resources/extensions/ask-user-questions.ts 中QuestionSchema对id的约束:"Stable identifier for mapping answers (snake_case)")。要精确命中某个问题,ID 必须与运行中实际出现的一致。

从源码看 Schema 的严格校验

loadAndValidateAnswerFile(src/headless-answers.ts)对答案文件执行逐字段校验,任何一项不满足都会抛出明确错误并终止启动:

  • 文件内容必须是合法 JSON,否则报Invalid JSON in answer file: <path>;
  • 顶层必须是纯对象(非数组、非 null),否则报Answer file must be a JSON object;
  • questions必须是对象,且每个值必须是字符串或"全部为字符串的数组",否则报Answer file "questions.<key>" must be a string or string[];
  • secrets必须是对象,且每个值必须是字符串,否则报Answer file "secrets.<key>" must be a string;
  • defaults.strategy只能是"first_option"或"cancel",否则报对应错误。

这些校验规则与 src/resources/extensions/gsd/tests/headless-answers.test.ts 中的测试一一对应(如invalid JSON、wrong types用例),说明这是一条被测试覆盖的稳定契约。

Secrets 注入机制:密钥如何直达子进程

答案文件中secrets字段的作用是在构建开始前把密钥"预灌"进子进程的环境变量。文档给出了完整链路:

  1. Orchestrator(或用户)通过--answers传递答案文件;
  2. GSD 读取文件,将 secret 值设为子进程的环境变量;
  3. Agent 内部运行secure_env_collect时,checkExistingEnvKeys()发现 key 已存在于process.env;
  4. 该工具跳过交互式输入,将 key 报告为 "already configured"(已配置)。

源码级链路验证

在 src/headless.ts 中,headless 入口创建 RPC 客户端时把 secrets 作为env选项传入:

if (injector) { clientOptions.env = injector.getSecretEnvVars() } // 同时注入 headless 模式标记 clientOptions.env = { ...(clientOptions.env || {}), GSD_HEADLESS: '1' }

getSecretEnvVars()(src/headless-answers.ts)直接返回answerFile.secrets ?? {},即答案文件中的全部 secrets 原样并入子进程环境。

在子进程一侧,secure_env_collect工具(src/resources/extensions/get-secrets-from-user.ts)执行时会调用checkExistingEnvKeys(allKeys, envPath)检测 key 是否已存在;对已存在的 key,汇总界面会标注already set,且不会进入逐页掩码输入流程。这正是"skip the interactive prompt"的实现来源。

安全特性:文档明确强调 secrets 从不被记录日志、也不进入事件流。这一点与secure_env_collect的 promptGuidelines 一致——工具在输出中只报告 key 名与 applied/skipped 状态,绝不回显值(src/resources/extensions/get-secrets-from-user.ts)。因此在答案文件中写入真实密钥时,务必确保该文件本身不被纳入版本控制、不被事件流捕获。

问题匹配的两阶段关联机制

Answer Injection 的核心难点在于:headless 进程以事件流方式工作,问题不是"预先知道"的,而是运行中动态出现的。因此注入器采用两阶段关联:

  1. Observe(观察)— GSD 监听tool_execution_start事件中工具名为ask_user_questions的调用,从中提取问题元数据(ID、选项列表、allowMultiple标志);
  2. Match(匹配)— 后续到达的extension_ui_request事件被关联到对应元数据,并以预置答案回复。

实现细节:以 title 为关联键

在 src/headless-answers.ts 的observeEvent中,注入器从event.input?.questions(或event.args?.questions)解析每个问题,把header与question拼接成<header>: <question>形式的 title,连同id、options、allowMultiple一起存入questionMetaByTitleMap。

而在tryHandle(src/headless-answers.ts)中,extension_ui_request事件通过其title字段反查元数据——与 src/resources/extensions/ask-user-questions.ts 中ctx.ui.select(\${q.header}: ${q.question}`, ...)` 的拼装方式保持一致,这就是"问题 ID → 答案"得以匹配的底层桥梁。

乱序事件与 500ms 延迟队列

在 RPC 模式下,事件顺序并不总是严格:extension_ui_request可能先于tool_execution_start到达。此时元数据尚未建立,注入器无法立即匹配。处理方式是延迟处理队列:

  • 若查不到元数据,事件先被放入deferredEvents队列,并设置 500ms 定时器;
  • 若在 500ms 内observeEvent带入了对应元数据,定时器被清除,事件立即按预置答案处理(processWithMeta);
  • 若 500ms 内仍无元数据,则按defaults.strategy兜底:"first_option"回复第一个选项,"cancel"发送取消响应。

src/resources/extensions/gsd/tests/headless-answers.test.ts 中的tryHandle deferred resolution — observeEvent after tryHandle用例专门验证了"先收到 UI 请求、后收到元数据"的乱序场景:事件先被延迟,observeEvent到达后立刻同步解析并发出正确应答。

匹配与校验逻辑:答案必须落在选项内

processWithMeta(src/headless-answers.ts)是最终应答的决策点:

  • 单选(allowMultiple为假):答案取字符串(若配置成数组则取首元素),且必须存在于事件的 options 列表中才被接受;否则走兜底策略;
  • 多选:答案必须是数组,且每个值都必须存在于 options 中,才会以values字段整体回复;
  • 兜底策略为"first_option"且答案无效时,processWithMeta返回false,把处理权交还给内置自动应答器(见下文)。

这保证了注入器不会发送一个不在选项中的非法答案——预置答案与运行时选项不一致时,系统会优雅降级而不是中断。

与--supervised监督模式的共存优先级

答案注入不是唯一的自动应答手段。headless 还支持--supervised模式:把交互式 UI 请求通过 stdout/stdin 转发给外部 orchestrator 处理。两者可以同时启用,构成三层优先级:

  1. Answer injector 先尝试— 若有匹配的预置答案,直接回复;
  2. 若无答案,交给 supervised 模式— 转发给 orchestrator 等待其响应;
  3. 若超过--response-timeout仍未收到 orchestrator 响应,内置自动应答器接管。

在 src/headless.ts 的事件处理中可以看到这一顺序的实现:extension_ui_request到达后先调用injector.tryHandle(...),只有返回false(未处理)且处于 supervised 模式时,才设置responseTimeout定时器等待外部响应;超时后回落到handleExtensionUIRequest(内置自动应答器)。

--response-timeout的默认值为 30000ms(见 gsd-orchestrator/SKILL.md 的 flags 表)。另外,注入器只会处理method === 'select'的事件,confirm、input、editor等方法直接交给自动应答器或 supervised 链路处理。

无答案注入时的内置自动应答

即使完全不使用--answers,headless 模式也内置了针对所有提示类型的自动应答器,保证流程不会卡死:

提示类型默认行为
Select选择第一个选项
Confirm自动确认
Input返回空字符串
Editor返回预填内容或空

答案注入的价值在于:当"自动选择第一个选项"这种默认行为不够精确时(例如部署目标必须是 GCP 而不是列表首位的 AWS),用具体答案覆盖默认行为,实现精确决策。二者是"默认兜底 + 精准覆盖"的关系。

诊断统计与未使用警告

为了便于排查,注入器会跟踪三类统计信息,并打印在会话结束的摘要中:

统计项说明
questionsAnswered从答案文件中命中的问题数
questionsDefaulted由兜底策略处理的问题数
secretsProvided注入的密钥数量

在 src/headless.ts 中,会话收尾时会输出:

[headless] Answers: 3 answered, 1 defaulted, 2 secrets

此外,未使用的 question ID 与 secret key 会在退出时给出警告。getUnusedWarnings()(src/headless-answers.ts)会逐一检查答案文件中配置的每个问题 ID 与密钥是否实际被消费,未匹配的生成类似以下警告:

[answers] Warning: question ID 'deploy_target' was never matched [answers] Warning: secret 'OPENAI_API_KEY' was provided but never requested

这套机制非常实用:它能在运行结束后帮你发现"答案文件写错了 ID/键名"或"多余的密钥配置",避免长期带病运行。对应测试getUnusedWarnings reports unused question IDs and secret keys(src/resources/extensions/gsd/tests/headless-answers.test.ts)验证了已用项不告警、未用项必告警的行为。

实战示例:Orchestrator 全自动构建 + 结果解析

将以上所有机制组合起来,就是一个完整的"答案注入 + JSON 输出 + 结果解析"实战流程。文档给出了可直接运行的示例:

# 创建答案文件 cat > answers.json << 'EOF' { "questions": { "test_framework": "vitest", "package_manager": "pnpm" }, "secrets": { "OPENAI_API_KEY": "sk-...", "DATABASE_URL": "postgres://localhost:5432/mydb" }, "defaults": { "strategy": "first_option" } } EOF # 使用预置答案运行(--output-format json 下结构化结果走 stdout,进度走 stderr) gsd headless --answers answers.json --output-format json auto 2>/dev/null # 解析结果 RESULT=$(gsd headless --answers answers.json --output-format json next 2>/dev/null) echo "$RESULT" | jq '{status: .status, cost: .cost.total}'

几个实践要点:

  • 2>/dev/null是必须的:JSON 结构化结果输出到 stdout,而进度、统计、警告都输出到 stderr;解析 JSON 时务必重定向 stderr,否则会污染管道(这是 gsd-orchestrator/SKILL.md 的 critical rules 之一)。
  • 配合query轮询:运行auto后,用gsd headless query(约 50ms、无 LLM 成本)检查状态,而不是反复调用auto。
  • 配合退出码判断:0=成功,1=错误/超时,10=阻塞(需要介入),11=取消。当构建以 10 阻塞时,检查.gsd/STATE.md与最后的阻塞通知,决定是补充答案还是人工介入。
  • next单步执行:--answers同样适用于next单步命令,适合编排者逐步推进并逐次解析结果。

关于构建状态的完整字段说明,可参考 gsd-orchestrator/references/json-result.md;完整命令与标志参考见 gsd-orchestrator/references/commands.md;Orchestrator 的轮询与分步工作流见 gsd-orchestrator/workflows/monitor-and-poll.md 与 gsd-orchestrator/workflows/step-by-step.md。

小结

Answer Injection 是 GSD headless 自主构建体系中的关键拼图:questions解决"决策"的自动化,secrets解决"凭证"的自动化,defaults.strategy解决"未知情况"的兜底,而严格的 schema 校验、两阶段事件关联、500ms 乱序补偿、统计与未使用警告共同保证了这套机制在无人值守环境下的可靠性与可诊断性。对任何希望把 GSD 接入 CI、或由外部 Agent 长时间自主编排构建的团队而言,--answers都是最值得优先掌握的 headless 能力之一。

  • 人工智能
  • AI Agent
  • 代码智能体
  • Agent 编排
  • CLI
  • AI 应用

【免费下载链接】gsd-2

A powerful meta-prompting, context engineering and spec-driven development system that enables agents to work for long periods of time autonomously without losing track of the big picture

项目地址:https://gitcode.com/gh_mirrors/gs/gsd-2
点击查看免费下载

相关推荐

上一篇:check-if-email-exists 的 `is_reachable` 字段完全解读:从四种投递状态到完整响应 JSON
下一篇:devops-exercises 实战指南:AWS EC2 Elastic IP 静态公网 IP 分配、关联与最佳实践

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询