Repository navigation
Description
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.
Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: 64cc0994fa0b439f80667b03c5b45fd2
Branch: dev/3-ask-user-option-count
Base: f2ef4df
At: 2026-11-10T03:25:23Z
PR is opened within 24h; merge waits behind the current review queue.
Bot: FlyBot Product
Base: main f2ef4dfaccedd40315bd585474d0902118dc0ba8 (readings taken 2026-11-10; red first will be re-taken on the live main at claim time)
What is wrong
core/src/host/tools.js declares the option count of ask_user and then does something else with it.
:626— theoptionsparameter saysdescription: 'Two to four answers'(required: true).:646-653— after normalising the labels and dropping empty ones, the host applies.slice(0, 6)and keeps whatever length is left.:654— the only size check isif (options.length === 0) return 'Give at least one option.'
So the host accepts 1 to 6 options. A seventh is discarded with no word, and the tool still answers The card is on screen; the user's answer arrives as their next message. The group path askInGroup (:688-) receives the same array, so a question handed out from a group behaves identically.
Reproduction (measured on f2ef4dfa)
Probe built on core/test/questions.test.js's start() fixture, calling the registered ask_user tool directly and reading state.questions back through /api/bot state:
ONE -> {"returned":"The card is on screen; the user's answer arrives as their next message.","storedOptions":1}
FIVE -> {"returned":"The card is on screen; the user's answer arrives as their next message.","storedOptions":5}
SEVEN -> {"storedOptions":6,"labels":["A","B","C","D","E","F"]}
G never reaches the card and nothing in the answer says it was dropped.
Why it matters
- A one-option "multiple-choice" card offers no choice: the user can only tap the single option, and the host hands that back as the user's answer. A decision that hangs off that card then reads as a decision the user never made.
- A dropped seventh option is invisible to both sides, in the one tool whose job is to lay the choices out.
- A tool whose description and behaviour disagree cannot be used safely by a model: the model trusts the description, and the description is not what runs (same family as chat: read_own_chat must say how many hits it did not show #1906 — a capped read that does not say what it left out).
Expected
The host accepts exactly what it documents: fewer than two or more than four is refused with a sentence that names the rule (for example Give two to four answers.), and nothing is stored. Two to four options keep today's card and today's answer string, byte for byte.
Change scope
core/src/host/tools.js:646-655: keep the normalisation and the empty-label filter, replace.slice(0, 6)with an explicit 2–4 check placed where:654is (after normalisation, before thehandedFromGroupbranch, soaskInGroupinherits it). Count valid labels, not the raw array: 5 given with 2 empty labels is a 3-button card and must pass.core/test/questions.test.js: add the four cases below next to the existingstart()fixture.- Generated mirrors:
core/test/questions.test.jsis not in thepatches/ds-bot/files/**payload, so adding cases there needs no regeneration. - No client change, no state-format change, no new endpoint, no change to any read path.
Acceptance (red first: 2 red / 1 green on f2ef4dfa)
- one option → the answer is the refusal naming the rule, and no card is stored (base: a one-option card is stored — red).
- six options → refused the same way, and no card (base: six are stored — red).
- seven options → refused with the rule, not cut to six (base: six stored, the seventh gone — red).
- two and four options → card and answer string identical to today (guard, green on the base).
Out of scope by decision: read paths (cardText, the questions renderer, answer-question, the inbox model) are not validated. The stored-state and render fixtures that model legacy one-option cards must keep rendering and answering, and at least one single-option fixture stays as the legacy case.
Dedup (checked 2026-11-10, before opening)
- Backlog: no
ask_user+ option-count entry. - GitHub:
"ask_user" options→ 2, both closed (Inbox: approve / answer Needs you items directly in the inbox #1140 inbox answering, Main Bot can create and change Bots but has no tool to remove or retire one #638 archive_bot);"two to four"→ 1 closed (A room envelope's hold rule is only in code: widening autoSendRoomEnvelope reds no test #882 room envelope);"question card" options→ 0;option card limit→ 8, none on this (the only open one, Proposal: AI-led Project governance, unified board and Handoff-driven Workflow (five-stage plan) #1550, is a Project-governance proposal). No duplicate.
Verification command
cd core && TZ=UTC node --import ./test/harness-resolve.mjs --test --test-timeout=120000 test/questions.test.js
Activity
fix(host): refuse an ask_user card outside two to four answers (Think…