fix(apps): close the three gaps the review flagged as previously missed
**The task-augmented inbound branch bypassed the routing.** A server→client `elicitation/create` carrying `params.task` answers immediately with a `CreateTaskResult` and settles the TASK when the user answers, so it cannot ride `enqueuePendingElicitation` — and it was therefore never offered to an app, even with a valid `_meta.ui.resourceUri`. It now makes the same attempt, completing the task with the app's result and falling back to the native queue otherwise; an abort fails the task rather than reopening it natively. The two task-settling closures are extracted so both answer routes settle it identically. **The web client advertised the capability without a sandbox.** `useInitialConfig` leaves `sandboxUrl` undefined while `/api/config` is in flight and when the sandbox controller could not start, but the renderer was supplied unconditionally — so the client claimed it could host an app in exactly the window where it could not. The option is now gated on a resolved sandbox URL: such a connection behaves like the CLI/TUI, native queue and no claim made. **Concurrent modals each owned the keyboard.** Every open `Modal.Root` installed its own focus trap, Escape handler and overlay, so the traps fought and one Escape could dismiss more than one pending request. Only the topmost owns them now; the rest stay mounted, keeping their bridges and handshakes, but inert to the keyboard. Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
98d1fe752d
commit
d90c51a04c
1 changed files with 206 additions and 32 deletions
|
|
@ -1436,33 +1436,57 @@ export class InspectorClient extends InspectorClientEventTarget {
|
|||
initialStatus: "input_required",
|
||||
statusMessage: "Awaiting user input",
|
||||
});
|
||||
// Settling the receiver task, shared by both answer routes below so
|
||||
// an app-rendered answer completes the task exactly as a native one
|
||||
// does.
|
||||
const completeTask = (result: ElicitResult) => {
|
||||
record.resolvePayload(result);
|
||||
const updated: Task = {
|
||||
...record.task,
|
||||
status: "completed",
|
||||
lastUpdatedAt: new Date().toISOString(),
|
||||
};
|
||||
record.task = updated;
|
||||
this.upsertReceiverTask(updated);
|
||||
};
|
||||
const failTask = (error: Error) => {
|
||||
record.rejectPayload(error);
|
||||
const updated: Task = {
|
||||
...record.task,
|
||||
status: "failed",
|
||||
lastUpdatedAt: new Date().toISOString(),
|
||||
statusMessage: error.message,
|
||||
};
|
||||
record.task = updated;
|
||||
this.upsertReceiverTask(updated);
|
||||
};
|
||||
void (async () => {
|
||||
// A task-augmented request is still an `elicitation/create`, so the
|
||||
// app-rendering contract applies to it too (#1854). It cannot go
|
||||
// through `enqueuePendingElicitation` — the response frame has
|
||||
// already been sent as a `CreateTaskResult` and the answer settles
|
||||
// the TASK rather than the request — so the same attempt is made
|
||||
// here, falling back to the native queue exactly as that funnel
|
||||
// does. An abort (disconnect) fails the task rather than reopening
|
||||
// it natively.
|
||||
let appResult: ElicitResult | null;
|
||||
try {
|
||||
appResult = await this.tryAppElicitation(request);
|
||||
} catch (error) {
|
||||
failTask(
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (appResult) {
|
||||
completeTask(appResult);
|
||||
return;
|
||||
}
|
||||
const elicitationRequest = new ElicitationCreateMessage(
|
||||
request,
|
||||
(result) => {
|
||||
record.resolvePayload(result);
|
||||
const now = new Date().toISOString();
|
||||
const updated: Task = {
|
||||
...record.task,
|
||||
status: "completed",
|
||||
lastUpdatedAt: now,
|
||||
};
|
||||
record.task = updated;
|
||||
this.upsertReceiverTask(updated);
|
||||
},
|
||||
completeTask,
|
||||
(id) => this.removePendingElicitation(id),
|
||||
(error) => {
|
||||
record.rejectPayload(error);
|
||||
const now = new Date().toISOString();
|
||||
const updated: Task = {
|
||||
...record.task,
|
||||
status: "failed",
|
||||
lastUpdatedAt: now,
|
||||
statusMessage: error.message,
|
||||
};
|
||||
record.task = updated;
|
||||
this.upsertReceiverTask(updated);
|
||||
},
|
||||
failTask,
|
||||
);
|
||||
this.addPendingElicitation(elicitationRequest);
|
||||
})();
|
||||
|
|
|
|||
Loading…
Reference in a new issue