Commit c03d4bfc5ce7
Changed files (1)
dots
pi
agent
extensions
dots/pi/agent/extensions/ask-user.ts
@@ -16,6 +16,30 @@ import { DynamicBorder, getSettingsListTheme } from "@mariozechner/pi-coding-age
import { Container, type SettingItem, SettingsList, Text } from "@mariozechner/pi-tui";
import { Type } from "@sinclair/typebox";
+/**
+ * Serializes TUI access across concurrent tool calls.
+ *
+ * The harness may execute several tool calls from a single assistant turn
+ * concurrently. Since AskUserQuestion grabs the single TUI input focus
+ * (ctx.ui.custom/select/input), running more than one at once deadlocks:
+ * the overlays fight for focus and none ever resolves, so no tool results
+ * are produced and the turn hangs forever.
+ *
+ * This mutex chains executions so each question is presented (and answered)
+ * before the next one starts.
+ */
+let uiLock: Promise<void> = Promise.resolve();
+
+function acquireUILock(): Promise<() => void> {
+ let release!: () => void;
+ const next = new Promise<void>((resolve) => {
+ release = resolve;
+ });
+ const prev = uiLock;
+ uiLock = uiLock.then(() => next);
+ return prev.then(() => release);
+}
+
export default function (pi: ExtensionAPI) {
pi.registerTool({
name: "AskUserQuestion",
@@ -57,6 +81,10 @@ export default function (pi: ExtensionAPI) {
};
}
+ // Serialize: if the model asked several questions in one turn, present
+ // them sequentially instead of letting the overlays deadlock.
+ const release = await acquireUILock();
+ try {
const { question, suggestions, multiSelect } = params;
const hasSuggestions = suggestions && suggestions.length > 0;
@@ -158,6 +186,9 @@ export default function (pi: ExtensionAPI) {
content: [{ type: "text", text: `User answered: ${answer}` }],
details: { question, suggestions, answer },
};
+ } finally {
+ release();
+ }
},
});
}