Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
29 changes: 29 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# ACECode

ACECode is a local coding agent with a TUI and a desktop/web GUI, sharing one agent loop and one tool set across ends.

## Language

**AskUserQuestion**:
A cross-end tool that asks the user multiple-choice questions mid-task. TUI (overlay) and GUI (browser modal) are only different transports; the tool logic and its limits are shared and single-sourced.
_Avoid_: question tool, 提问工具 (ambiguous with unrelated prompts)

**Question budget**:
The maximum number of questions one AskUserQuestion call may contain. Configured by `ask.max_questions`; shared by all ends.
_Avoid_: 问题上限 (vague, could mean the text length limit)

**Option budget**:
The maximum number of selectable options a single question may carry. Configured by `ask.max_options`; shared by all ends.
_Avoid_: 选项个数限制 (vague), 选项上限 (collides with option text limits)

**Option floor**:
The minimum number of options a question must have. Fixed at 2; not configurable.
_Avoid_: 最少选项 (unclear whether user-facing or validation-facing)

**Over-limit request**:
A model request whose question count or option count exceeds the current configured budget. Always rejected with a hard error naming the dynamic limit; never silently truncated.
_Avoid_: 超量 (unclear), truncation (that is the rejected alternative, not the term)

**Out-of-range config**:
A config value outside the legal range of a budget key. Clamped to the nearest boundary with a warning; the app still starts.
_Avoid_: 非法配置 (implies rejection)
20 changes: 20 additions & 0 deletions docs/adr/0001-configurable-ask-option-limit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Configurable AskUserQuestion option count limit

**Status**: accepted

AskUserQuestion's per-question option count was hardcoded to `kMinOptions=2` / `kMaxOptions=4` in the shared tool code. We decided to make the upper bound configurable via `ask.max_options` (legal range 4–8, default 6, clamped with a warning when out of range), while the lower bound stays fixed at 2. The tool schema (`minItems`/`maxItems`) and the validation error message are built from the configured limit, so they stay in sync automatically. When the model submits more options than the configured budget, the call is rejected with a hard error naming the current limit.

The existing `ask.max_questions` (question budget) established the config pattern — same section, same clamp-and-warn behavior — and this change extends that pattern rather than introducing a new mechanism.

**Considered Options**

- **Make the lower bound configurable too** — rejected: narrower change surface; nothing has needed a floor other than 2.
- **Silently truncate over-limit options** — rejected: masks model errors and makes the conversation transcript diverge from what the user actually saw.
- **Reject the config file on out-of-range values** — rejected: `ask.max_questions` already clamps with a warning; failing startup for a cosmetic knob is worse.

**Consequences**

- Default 6 is a deliberate behavior change from the previous fixed 4: models may now submit up to 6 options without any configuration. Set `ask.max_options: 4` to restore the old cap.
- Config `ask.max_options` is read once at startup, like `max_questions`; changing it requires a restart.
- Error messages and tool description previously hardcoded "2-4" must be derived from the configured value.
- TUI and GUI render option lists dynamically (`options.size()`), so no layout work is needed for up to 8 options.
10 changes: 8 additions & 2 deletions docs/help/configuration.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
<main id="main-content" class="article" tabindex="-1">
<div class="breadcrumb"><a href="index.html">文档</a><span aria-hidden="true">›</span><span>配置与扩展</span></div>
<header class="article-header"><h1>配置文件与生效范围</h1><p class="lead">优先通过界面修改对应设置;需要手动编辑时,先确认数据目录、字段范围和保存方式。</p></header>
<details class="mobile-contents"><summary>本页内容<svg class="icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><path d="m8 5 7 7-7 7"/></svg></summary><ul><li><a href="#locations" data-toc-link>配置保存在哪里</a></li><li><a href="#save" data-toc-link>修改与生效</a></li><li><a href="#tui-ask-user-question" data-toc-link>TUI 问答配置</a></li><li><a href="#recovery" data-toc-link>手动编辑与错误恢复</a></li></ul></details>
<details class="mobile-contents"><summary>本页内容<svg class="icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><path d="m8 5 7 7-7 7"/></svg></summary><ul><li><a href="#locations" data-toc-link>配置保存在哪里</a></li><li><a href="#save" data-toc-link>修改与生效</a></li><li><a href="#tui-ask-user-question" data-toc-link>TUI 问答配置</a></li><li><a href="#ask-user-question" data-toc-link>AskUserQuestion 跨端配置</a></li><li><a href="#recovery" data-toc-link>手动编辑与错误恢复</a></li></ul></details>

<section aria-labelledby="locations"><h2 id="locations">配置保存在哪里</h2><p>个人安装的主配置是 <code>~/.acecode/config.json</code>,Windows 对应 <code>%USERPROFILE%\.acecode\config.json</code>。模型预设、默认模型以及网络、技能、MCP 等全局选项保存在这套用户配置中。</p><p>Windows 服务模式使用 <code>%PROGRAMDATA%\acecode</code>,与个人安装的数据目录分开。连接远端后台时,配置属于远端用户或服务身份;编辑本机文件不会自动修改远端配置。</p>
<div class="table-scroll"><table><thead><tr><th scope="col">配置层</th><th scope="col">适合保存的内容</th></tr></thead><tbody><tr><td>全局配置</td><td>服务商连接、默认值、扩展连接与运行选项。</td></tr><tr><td>工作目录覆盖</td><td>例如 TUI 用 /model --cwd 保存的项目模型选择。</td></tr><tr><td>项目文件</td><td>项目规则、项目技能和项目 Hooks。</td></tr><tr><td>当前任务</td><td>任务选择的模型、权限与对话上下文。</td></tr></tbody></table></div>
Expand All @@ -48,11 +48,17 @@
"question_selection_feedback_ms": 200
}
}</code></pre><p>这两个字段会在读取配置时限制在有效范围内。超出范围的整数会自动限制到边界,并记录警告;非整数值会被忽略并继续使用默认值。省略字段时使用默认值,配置保存采用稀疏写入,不会强制写出默认值。</p></section>
<section aria-labelledby="ask-user-question"><h2 id="ask-user-question">AskUserQuestion 跨端配置</h2><p>TUI 与 Web/Desktop 使用同一个 AskUserQuestion 工具,题目数量和单个问题的选项数量上限由 <code>ask</code> 对象统一控制,对所有运行端生效。以下字段位于配置文件的 <code>ask</code> 对象中:</p><div class="table-scroll"><table><thead><tr><th scope="col">字段</th><th scope="col">默认值</th><th scope="col">有效范围</th><th scope="col">说明</th></tr></thead><tbody><tr><td><code>max_questions</code></td><td>10</td><td>1–50</td><td>单次 AskUserQuestion 调用允许的题目数量。</td></tr><tr><td><code>max_options</code></td><td>6</td><td>4–8</td><td>单个问题允许的选项数量上限。默认 6,可在 4 到 8 之间调整。</td></tr></tbody></table></div><p>例如:</p><pre><code>{
"ask": {
"max_questions": 10,
"max_options": 6
}
}</code></pre><p>超出有效范围的整数会自动限制到边界,并记录警告;非整数值会被忽略并继续使用默认值。省略字段时使用默认值,配置保存采用稀疏写入,不会强制写出默认值。</p></section>
<section aria-labelledby="recovery"><h2 id="recovery">手动编辑与错误恢复</h2><ol><li>先备份当前有效配置,使用支持 UTF-8 的编辑器打开。</li><li>只修改目标字段,保持正确的 JSON 类型,不加入注释或尾随逗号。</li><li>重新加载相关功能,或按该功能要求重启。</li><li>检查界面实际值和一次小操作,确认修改已生效。</li></ol><p>当前版本会保存有效配置快照。配置损坏且存在有效快照时,会备份错误文件并尝试自动恢复;Web/Desktop 会显示一次<strong>配置已自动回滚</strong>提示。没有可用快照时仍会报告配置错误。按提示查看备份位置并修复目标字段,备份可能含密钥,不要直接公开。</p></section>
<nav class="page-navigation" aria-label="文章翻页"><a class="page-link previous" href="model-capabilities.html"><span>上一篇</span><strong><svg class="icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><path d="M20 12H5m6-6-6 6 6 6"/></svg>模型能力与参数</strong></a><a class="page-link next" href="permissions.html"><span>下一篇</span><strong>权限模式与操作确认<svg class="icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><path d="M4 12h15m-6-6 6 6-6 6"/></svg></strong></a></nav>
<footer class="article-footer"><span>ACECode 帮助文档</span><a href="https://github.com/tmoonlight/acecode/issues" target="_blank" rel="noopener noreferrer">反馈文档问题</a></footer>
</main>
<aside class="contents-rail" aria-label="本页内容"><div class="contents-sticky"><p class="contents-title">本页内容</p><nav aria-label="文章小节"><ul><li><a href="#locations" data-toc-link>配置保存在哪里</a></li><li><a href="#save" data-toc-link>修改与生效</a></li><li><a href="#tui-ask-user-question" data-toc-link>TUI 问答配置</a></li><li><a href="#recovery" data-toc-link>手动编辑与错误恢复</a></li></ul></nav><div class="rail-note"><span>配置与扩展</span><p>循着目录逐步了解 ACECode。</p><a href="quick-start.html">前往快速开始 →</a></div></div></aside>
<aside class="contents-rail" aria-label="本页内容"><div class="contents-sticky"><p class="contents-title">本页内容</p><nav aria-label="文章小节"><ul><li><a href="#locations" data-toc-link>配置保存在哪里</a></li><li><a href="#save" data-toc-link>修改与生效</a></li><li><a href="#tui-ask-user-question" data-toc-link>TUI 问答配置</a></li><li><a href="#ask-user-question" data-toc-link>AskUserQuestion 跨端配置</a></li><li><a href="#recovery" data-toc-link>手动编辑与错误恢复</a></li></ul></nav><div class="rail-note"><span>配置与扩展</span><p>循着目录逐步了解 ACECode。</p><a href="quick-start.html">前往快速开始 →</a></div></div></aside>
</div>
<dialog class="search-dialog" aria-labelledby="search-title">
<div class="search-dialog-header"><svg class="icon " width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><circle cx="10.5" cy="10.5" r="5.5"/><path d="m15 15 4 4"/></svg><label class="sr-only" id="search-title" for="docs-search">搜索文档</label><input id="docs-search" type="search" placeholder="搜索文档、命令或操作…" autocomplete="off" spellcheck="false"><button class="search-close icon-button" type="button" aria-label="关闭搜索"><svg class="icon " width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><path d="m6 6 12 12M18 6 6 18"/></svg></button></div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-09-15
26 changes: 26 additions & 0 deletions openspec/changes/add-configurable-ask-option-limit/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
## Context

See proposal.md. The option budget lives in `src/tool/ask_user_question_tool.cpp` as anonymous-namespace constants (`kMinOptions=2`, `kMaxOptions=4`) used by validation, the error message, and the tool-definition schema. The config layer already has the exact pattern we need: `AskConfig.max_questions` (default 10, clamped to [1,50] with a warning on load) flows into `create_ask_user_question_tool_async(max_questions)` at three registration sites.

## Goals / Non-Goals

**Goals:** Make the per-question option upper bound configurable (4–8, default 6); keep every limit the model sees (schema, error text) in sync with the configured value; preserve the fixed lower bound of 2; keep behavior identical for existing configs.

**Non-Goals:** Configuring the lower bound; changing over-limit behavior (still a hard error); adding UI to edit the setting (the existing `ask.max_questions` is file-config-only and stays that way); touching TUI/Web rendering.

## Decisions

- Add `AskConfig.max_options` (default 6), parse it from the `ask` object as an integer, clamp to [4,8] with a warning, and mirror it in config dump/validation like `max_questions`.
- Thread the option budget through the tool factories: `validate_ask_user_question_args(args, err, max_questions, max_options)` and `build_ask_user_question_def(max_questions, max_options)`. Effective bounds are clamped once at the boundary of the tool layer so callers cannot bypass the legal range.
- Keep `kMinOptions=2` fixed. Replace the `kMaxOptions=4` constant with a default (`kDefaultAskMaxOptions=6`) plus the legal range constants (`kMinAskMaxOptions=4`, `kMaxAskMaxOptions=8`); the effective max used in validation and schema comes from the config.
- Over-limit requests keep failing with a hard error whose message names the current dynamic upper bound, and the schema `maxItems` reflects the same value, so the model is told the real limit before it can fail.

## Risks / Trade-offs

- Default 6 is a deliberate behavior change from the previous fixed 4: with no configuration, models may now submit up to 6 options. Restoring the old cap requires `ask.max_options: 4`. Recorded in ADR-0001.
- A config file written by a newer build (e.g. `max_options: 8`) read by an older build would be ignored by the older build's schema. Acceptable: the old build still enforces 4, which is a subset of what the newer config allows; this matches how unknown config keys already behave.
- The clamp range [4,8] is enforced in two layers (config load and tool factory). This is intentional defense in depth, mirroring the existing `max_questions` treatment; the two layers agree by construction.

## Migration Plan

No storage migration. New default applies on next startup for configs that omit the key. Users wanting the old cap set `ask.max_options: 4`. Deliver the change as a normal scoped commit on master.
24 changes: 24 additions & 0 deletions openspec/changes/add-configurable-ask-option-limit/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
## Why

AskUserQuestion's per-question option count is hardcoded to 2–4 (`kMinOptions`/`kMaxOptions` in the shared tool code). Users cannot widen the choice set without editing source, and the limit is invisible in configuration. The user wants the upper bound to be configurable between 4 and 8.

## What Changes

- Add `ask.max_options` to the config: default 6, legal range 4–8, out-of-range values clamped to the nearest boundary with a warning (same pattern as `ask.max_questions`).
- Make the AskUserQuestion tool take the option budget from configuration: validation, error messages, and the tool schema (`minItems`/`maxItems`) are all derived from the configured value.
- Keep the lower bound fixed at 2.
- Update the config tests, the tool tests, and the official configuration documentation.

## Capabilities

### New Capabilities

- `configurable-ask-option-limit`: Let `ask.max_options` control the maximum number of options a single AskUserQuestion question may carry.

### Modified Capabilities

None.

## Impact

The shared `AskConfig` struct and its JSON parsing/dumping, `validate_ask_user_question_args` and `build_ask_user_question_def`, the three tool-registration call sites (TUI main, headless runner, daemon worker), config and tool unit tests, and `docs/help/configuration.html`. No TUI/Web rendering changes are needed: both render option lists dynamically from `options.size()`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
## Purpose

Make the maximum number of options a single AskUserQuestion question may carry configurable, while keeping every model-visible limit derived from the configured value.

## ADDED Requirements

### Requirement: Option budget is configurable

The system SHALL expose `ask.max_options` in the configuration with a default of 6. The legal range SHALL be 4 through 8 inclusive. All run ends (TUI, GUI, headless) SHALL enforce the same configured budget.

#### Scenario: Default budget
- **WHEN** no `ask.max_options` is present in the configuration
- **THEN** a question with 6 options is accepted and a question with 7 options is rejected

#### Scenario: Widen the budget
- **WHEN** `ask.max_options` is set to 8
- **THEN** a question with 8 options is accepted and a question with 9 options is rejected

#### Scenario: Narrow the budget
- **WHEN** `ask.max_options` is set to 4
- **THEN** a question with 4 options is accepted and a question with 5 options is rejected

### Requirement: Out-of-range configuration is clamped

A configured `ask.max_options` outside 4–8 SHALL be clamped to the nearest boundary with a warning, and the application SHALL still start.

#### Scenario: Clamp above the range
- **WHEN** `ask.max_options` is set to 10
- **THEN** the effective budget is 8 and a warning is recorded

#### Scenario: Clamp below the range
- **WHEN** `ask.max_options` is set to 3
- **THEN** the effective budget is 4 and a warning is recorded

### Requirement: Over-limit requests are rejected with the dynamic limit

When a model submits more options than the current budget, the tool SHALL reject the call with a hard error naming the current upper bound. The tool schema (`maxItems`) SHALL reflect the same configured value so the model sees the real limit in advance.

#### Scenario: Rejection message names the limit
- **WHEN** the budget is 6 and a question carries 7 options
- **THEN** the error states the limit as 6 and the call fails

#### Scenario: Schema follows the configuration
- **WHEN** `ask.max_options` is set to 8
- **THEN** the tool definition's options schema advertises a maximum of 8 items

### Requirement: Lower bound stays fixed

The minimum number of options per question SHALL remain 2 regardless of configuration.

#### Scenario: Two options still valid
- **WHEN** a question carries 2 options and the budget is 6
- **THEN** the question is accepted

#### Scenario: One option still invalid
- **WHEN** a question carries 1 option and the budget is 6
- **THEN** the question is rejected
29 changes: 29 additions & 0 deletions openspec/changes/add-configurable-ask-option-limit/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
## 1. Configuration

- [ ] 1.1 Add `int max_options = 6` to `AskConfig` in `src/config/config.hpp`, with a comment mirroring `max_questions`.
- [ ] 1.2 Parse `ask.max_options` in `src/config/config.cpp` load path: integer-only, clamp to [4,8] with a warning on out-of-range values.
- [ ] 1.3 Add `max_options` to config validation (out-of-range check) and to the config JSON dump so the value persists and round-trips.

## 2. Tool layer

- [ ] 2.1 In `src/tool/ask_user_question_tool.hpp/.cpp`, replace the fixed `kMaxOptions=4` with `kDefaultAskMaxOptions=6` plus range constants `kMinAskMaxOptions=4` / `kMaxAskMaxOptions=8`; keep `kMinOptions=2` fixed.
- [ ] 2.2 Thread `max_options` through `validate_ask_user_question_args` and `build_ask_user_question_def`; clamp at the tool boundary; derive the error message ("between 2 and N") and the schema `maxItems` from the effective value.
- [ ] 2.3 Keep the overloads used by independent callers working with defaults (6).

## 3. Registration sites

- [ ] 3.1 Pass `cfg.ask.max_options` at `src/main.cpp` (TUI), `src/headless/headless_runner.cpp`, and `src/daemon/worker.cpp`.

## 4. Tests

- [ ] 4.1 `tests/config/config_ask_test.cpp`: default 6; parse of 4/8; clamp of 3→4 and 10→8 with warning.
- [ ] 4.2 `tests/tool/ask_user_question_tool_test.cpp`: dynamic acceptance/rejection at the configured bound; 8 accepts / 9 rejects; rejection message names the current limit; schema `maxItems` follows configuration; lower bound 2 still enforced.

## 5. Documentation

- [ ] 5.1 `docs/help/configuration.html`: add an "AskUserQuestion 跨端配置" section documenting `ask.max_questions` and `ask.max_options` (defaults, ranges, notes).

## 6. Verification

- [ ] 6.1 Build the C++ targets and run the focused config and tool test suites; fix any failures.
- [ ] 6.2 Write `verification.md` with the exact commands and results; run `git diff --check`.
Loading
Loading