fix(gate): read reviewer_select/confirm choice from the choices[] array
The frontend's reviewer widgets uniformly send the picked option in a `choices`
array (SelectWidget/ArtifactGateWidget: `choices: [optionId]`), but the gate's
reviewer_select and reviewer_confirm(reject) handlers read `resp.choice`
(singular). Result: every single-select gate saw an undefined choice, answered
"Nessuna scelta ricevuta", and re-presented forever — the workflow could never
pass F1. (reviewer_decide/multiselect already read `resp.choices`, so it worked.)
Add a shared selectedChoice(resp) helper reading choices[0] (falling back to the
legacy singular choice); both handlers use it.
TDD: gate/__tests__/gate_choice.test.js RED->GREEN; full gate suite 28/28.
Verified LIVE (Playwright -> real Pi -> GLM 5.2): a single-select F1 answer is
now accepted and the workflow advances (clarification 2/4 -> 3/4). The same run
also live-verified the F1 hang fix (418187a).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert");
|
||||
const { selectedChoice } = require("../../tht-gate.js");
|
||||
|
||||
// The frontend's reviewer widgets (SelectWidget, ArtifactGateWidget) always send the
|
||||
// picked option in a `choices` ARRAY: { id, kind, choices: [optionId] }. The gate's
|
||||
// select / artifact-gate handlers must read that array — reading a singular `choice`
|
||||
// yields undefined, so the gate answers "no choice received" and re-presents forever.
|
||||
|
||||
test("selectedChoice reads the frontend's choices[] array (select widget)", () => {
|
||||
assert.equal(selectedChoice({ id: "u1", kind: "select", choices: ["old75_frozen"] }), "old75_frozen");
|
||||
});
|
||||
|
||||
test("selectedChoice reads choices[] for the artifact-gate reject", () => {
|
||||
assert.equal(selectedChoice({ id: "u1", kind: "artifact-gate", choices: ["reject"] }), "reject");
|
||||
});
|
||||
|
||||
test("selectedChoice falls back to the legacy singular choice", () => {
|
||||
assert.equal(selectedChoice({ id: "u1", choice: "a" }), "a");
|
||||
});
|
||||
|
||||
test("selectedChoice returns undefined when nothing is selected", () => {
|
||||
assert.equal(selectedChoice({ id: "u1", choices: [] }), undefined);
|
||||
assert.equal(selectedChoice({ id: "u1" }), undefined);
|
||||
});
|
||||
@@ -207,6 +207,14 @@ function advanceIfReady(ctx, session) {
|
||||
// accepted as a final answer — the widget is re-presented. Real escapes (Back/Exit/Other)
|
||||
// are always present as selectable options in the descriptor, so cancel has no
|
||||
// legitimate meaning.
|
||||
// The frontend's reviewer widgets always carry the picked option(s) in a `choices`
|
||||
// ARRAY (SelectWidget/ArtifactGateWidget send `choices: [optionId]`). Single-pick
|
||||
// handlers read the first element; `choice` (singular) is accepted as a legacy form.
|
||||
export function selectedChoice(resp) {
|
||||
if (Array.isArray(resp?.choices)) return resp.choices[0];
|
||||
return resp?.choice;
|
||||
}
|
||||
|
||||
export async function emitAndWait(ctx, descriptor) {
|
||||
for (;;) {
|
||||
const value = await ctx.ui.input(JSON.stringify(descriptor), "");
|
||||
@@ -398,9 +406,10 @@ export default function (pi) {
|
||||
return textResult("Il reviewer vuole tornare indietro.");
|
||||
if (resp.control === "exit")
|
||||
return textResult("Il reviewer vuole uscire.");
|
||||
const chosen = opts.find((o) => o.id === resp.choice);
|
||||
const choice = selectedChoice(resp);
|
||||
const chosen = opts.find((o) => o.id === choice);
|
||||
return textResult(
|
||||
`Scelta del reviewer: ${chosen ? chosen.label : resp.choice}`,
|
||||
`Scelta del reviewer: ${chosen ? chosen.label : choice}`,
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -522,7 +531,7 @@ export default function (pi) {
|
||||
action: { kind: "approve_reject", prompt: "Approvi o rifiuti?" },
|
||||
});
|
||||
const resp = await emitAndWait(ctx, widget);
|
||||
if (resp.control === "freetext" || resp.choice === "reject") {
|
||||
if (resp.control === "freetext" || selectedChoice(resp) === "reject") {
|
||||
return textResult(
|
||||
`Rifiutato${resp.text ? ` (motivo: ${resp.text})` : ""}: rivedi e riprova.`,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user