Skip to content

feat(operator-queue): approvals get a "Something else" answer (#3242) - #3250

Merged
vybe merged 8 commits into
devfrom
feature/3242-approval-something-else
Oct 6, 2026
Merged

vybe merged 8 commits into
devfrom
feature/3242-approval-something-else

Conversation

@vybe

@vybe vybe commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #3242 — first of the chain #3242 → #3243 → #3247 (ask contract), with #3246 alongside. Base is dev. Draft until the chain is in (operator, 2026-10-05).

What

An approval is no longer a closed menu. A person can answer "Something else" and type an instruction; the agent reads back one reserved decision and the instruction.

  • Sink (services/operator_queue_choices.py, services/ask_service.py): the reserved literal (something else) is checked before membership. On an approval with a non-blank instruction it is accepted; blank → 422 instruction_required; on any other ask type → 422 reserved_value; on a gate / platform-minted approval → 422 not_off_menu. Every other unoffered string is still refused (bug: POST /api/operator-queue/{id}/respond accepts any string as an approval decision — no layer checks response ∈ options #2376 stays closed). An ask that lists the literal as its own option is refused at raise (invalid_options). response_text is bounded at 4000 on both routes.
  • Agent contract: one sentence in both authored copies of the platform prompt and in the agent guide, sentinel-pinned; ask_operator, get_my_ask and respond_to_operator_queue descriptions name the value; the respond tool returns a structured refusal beside error; the resume message explains the value above the data block.
  • Screens (utils/operatorQueue.js, QueueCard.vue, QueueItemDetail.vue, MobileAdmin.vue, PortalAsks.vue, ResolvedCard.vue): a "Something else" chip after the agent's options. On desktop and in the Workspace, typing with no pick selects it and the button reads "Send instruction"; Enter never sends an auto-selected state. Ended asks show "Something else" with the instruction, never the raw literal.
  • Gate approvals: the chip is hidden on every surface. The Workspace projection and the operator list / item projections carry one boolean, decided_by_options, from the same backend predicate; the gate- id prefix is only the fallback.
  • Docs: requirements §26, the operating-room and mobile-admin flows, the backend catalog line, and the user-docs sentences this change made false.

Rulings carried (orchestrator, on the operator's behalf — plan file)

  • T1 literal (something else), one constant per language with a parity test · T2 instruction required · T3 chip + auto-select on typing, "Send instruction" · T4 approvals without options unchanged · T5 no new MCP parameter · T6 no schema change · T7 /m decomposition (refactor: decompose oversized Vue views (Settings 3061 lines, MobileAdmin, Agents, AgentDetail, ...) #1030) not bundled.
  • T8 deviates from the plan's recommendation (operator, 2026-10-05): no surface shows a chip that always fails, so the Workspace is told which approvals are decided by their options, and the review fix extended the same boolean to the operator projections.

Review + security

/review (claude-fable-5-1, report-only): MERGEABLE AFTER FIXES. One sink holds on every route tried; the four named 422s come from both writers; gates are closed on every route; the new specs assert the body that is sent (reverting the rules turns 17 new tests red). Fixed in ad85e9f6 (C1: a source-text pin of the old Send rule was red; I1), 10dc0480 (I2 read-back contract tests, I4, I5, I6, I7, I12 learnings fragment), 210fc6b4 (I3: one predicate on both sinks). /cso --diff: nothing supported.

Tests

Reported by the builder on the fix tip: frontend npm run test:unit 265 files / 4653 tests passed; pytest on the five affected suites 307 passed; mcp-server 687 passed, tsc --noEmit clean. Not run here: the full Python unit island (CI) and any live tier.

Before merge

Handoffs

🤖 Generated with Claude Code

Trinity Agent (trinity) and others added 8 commits October 6, 2026 10:48
…se) answer (#3242)

The #2376 sink now handles SOMETHING_ELSE = "(something else)" before
membership: accepted on any approval with an instruction in response_text,
refused by name otherwise (instruction_required, reserved_value), and refused
on platform-minted / gate approvals (not_off_menu). Every other unoffered
string stays refused. validate_response_choice takes response_text
keyword-only and required.

- ask_service: gate refusal; ask_operator refuses the literal as an option
- both writers map ReservedAnswerError to a named 422
- OperatorResponse.response_text bounded at 4000
- Workspace projection: decided_by_options boolean (T8 ruling)
- recent answers render "Something else: <instruction>"
- resume frame: platform sentence above the data fence

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e) (#3242)

- platform prompt (both authored copies) + agent guide: the approval bullet
  and the queue-file write-back say what the reserved value means and that
  the instruction is in response_text; never list it as an option
- test_1402 SENTINELS += "(something else)"
- MCP: types.ts exports SOMETHING_ELSE; ask_operator, get_my_ask and
  respond_to_operator_queue descriptions name it; the respond tool keeps
  `error` and adds the backend's {status, code, message, offered_options?}
  (T5, additive)
- py <-> ts parity test for the literal

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- utils/operatorQueue.js: SOMETHING_ELSE (mirrored, parity-tested),
  offeredChips (drops the literal and the size-cap marker), decisionLabel,
  decidedByOptions; buildQueueResponse needs an instruction with the literal
- QueueCard + QueueItemDetail: chips from offeredChips, a 'Something else'
  chip outside the v-for (hidden on gate approvals); typing with no pick arms
  it, the label and Send copy flip; Enter sends only after an explicit pick;
  maxlength 4000; Send rule is the shared builder
- respondToItem returns true/false; the detail panel clears only on success
- ResolvedCard + detail resolved view render 'Something else', never the literal

Raw-colour counts unchanged (QueueCard 28/59, QueueItemDetail 26/55).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- /m: a 'Something else' chip in the options group (reuses ops-option-btn,
  hidden on gate approvals) opens the form; the consequence line says none of
  the options will run; Send reads 'Send instruction' and stays disabled until
  the instruction is typed; maxlength 4000
- PortalAsks: the chip after the offered chips, hidden when the projection
  says decided_by_options (T8 ruling); typing with no pick arms it, placeholder
  and Send aria-label flip; Enter never sends the auto-armed state; submit()
  sends the armed option through the shared builder. Diff kept to the approval
  template, helpers beside pick(), the body of submit() and one import line
  (open PR #3181 edits other hunks)
- portalAsksTestidPrefix: the id inventory gains the chip's id

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Document what #3242 built: the reserved (something else) decision an
approval accepts besides its own options, with the instruction in
response_text; the named 422s (instruction_required, reserved_value,
not_off_menu, invalid_options at raise); the Something else chip on the
desktop card and detail, /m and the Workspace, hidden on gate approvals
on every surface (decided_by_options on the Workspace projection); the
resume frame and contract text. Corrects every doc that still said an
approval's answer must be one of its options.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dead optionsOf import (C1, I1) (#3242)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-miss literals; public decided_by_options; learnings (I2 I4 I5 I6 I7 I12) (#3242)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tor projections; gate- is only the fallback (I3) (#3242)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vybe
vybe force-pushed the feature/3242-approval-something-else branch from 210fc6b to efedeaa Compare October 6, 2026 10:10
@vybe
vybe marked this pull request as ready for review October 6, 2026 10:15

@trinity-ability trinity-ability left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merge-train: batch validated on train/20261006-1038 (#3270)

@vybe
vybe merged commit 4a0c065 into dev Oct 6, 2026
28 checks passed
vybe pushed a commit that referenced this pull request Oct 6, 2026
…sh-landed (#3253) — mechanical, per the merge-train note on the PR

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ui PR touches the frontend UI — triggers Playwright e2e tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants