fix(harness): gate widget round-trip via native ctx.ui.input (no sendRaw); no-limbo
This commit is contained in:
@@ -133,46 +133,30 @@ function advanceIfReady(ctx, session) {
|
||||
}
|
||||
}
|
||||
|
||||
// --- widget emission + wait (NEW: replaces ctx.ui.* blocking primitives) -------
|
||||
// --- widget emission + wait — usa l'API UI NATIVA di Pi (ctx.ui.input) --------
|
||||
//
|
||||
// emitAndWait(ctx, descriptor) sends a ui_request widget and awaits the correlated
|
||||
// ui_response by id. This is the correlation-by-id layer the reference implementation never had (its
|
||||
// native TUI primitives handled it implicitly). The descriptor is built by the pure
|
||||
// ./gate/builders.js (L1-tested).
|
||||
// emitAndWait(ctx, descriptor) sends the widget-descriptor as JSON in the `title`
|
||||
// of ctx.ui.input and awaits the response as a parsed string value. No ctx.sendRaw,
|
||||
// no pi.on("extension_ui_response"): Pi routes the response via pendingExtensionRequests.
|
||||
//
|
||||
// The no-limbo invariant: a control response of "cancel"/undefined is NEVER accepted
|
||||
// as a final answer -- the widget is re-presented. Real escapes (Back/Exit/Other) are
|
||||
// always present as selectable options in the descriptor (reserved field), so cancel
|
||||
// has no legitimate meaning.
|
||||
let _pending = new Map(); // id -> {resolve, reject}
|
||||
|
||||
async function emitAndWait(ctx, descriptor) {
|
||||
// The no-limbo invariant: undefined/null/invalid-JSON/control:"cancel" are NEVER
|
||||
// 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.
|
||||
export async function emitAndWait(ctx, descriptor) {
|
||||
for (;;) {
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
_pending.set(descriptor.id, { resolve, reject });
|
||||
});
|
||||
ctx.sendRaw({ type: "extension_ui_request", ui_request: descriptor });
|
||||
const resp = await promise;
|
||||
// no-limbo: a cancel/undefined response re-presents the same widget
|
||||
if (resp && resp.control !== "cancel" && resp.id === descriptor.id) {
|
||||
return resp;
|
||||
}
|
||||
if (ctx.hasUI) {
|
||||
await ctx.ui.notify(
|
||||
"Esc non chiude il gate: usa Torna indietro / Esci / Altro dalle opzioni.",
|
||||
"warning",
|
||||
);
|
||||
}
|
||||
const value = await ctx.ui.input(JSON.stringify(descriptor), "");
|
||||
if (value === undefined || value === null) { await reLoop(ctx); continue; }
|
||||
let resp;
|
||||
try { resp = JSON.parse(value); } catch { await reLoop(ctx); continue; }
|
||||
if (resp && resp.control !== "cancel" && resp.id === descriptor.id) return resp;
|
||||
await reLoop(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
// The runtime invokes this when a ui_response arrives (RPC reply or SSE from FE).
|
||||
function handleUiResponse(event) {
|
||||
const slot = _pending.get(event.id);
|
||||
if (slot) {
|
||||
_pending.delete(event.id);
|
||||
slot.resolve(event);
|
||||
}
|
||||
async function reLoop(ctx) {
|
||||
if (ctx.hasUI) await ctx.ui.notify(
|
||||
"Esc non chiude il gate: usa Torna indietro / Esci / Altro dalle opzioni.", "warning");
|
||||
}
|
||||
|
||||
// --- the extension ------------------------------------------------------------
|
||||
@@ -284,9 +268,6 @@ export default function (pi) {
|
||||
}
|
||||
});
|
||||
|
||||
// 6) UI_RESPONSE dispatch: correlate an incoming response to its awaiting widget.
|
||||
pi.on("extension_ui_response", handleUiResponse);
|
||||
|
||||
// --- the four reviewer tools (widget-descriptor emit + await) ---------------
|
||||
|
||||
pi.registerTool({
|
||||
|
||||
Reference in New Issue
Block a user