From e567d43042e5506851c7c77c56b32c82cd134947 Mon Sep 17 00:00:00 2001
From: Federico Jaramillo Martinez
Date: Mon, 27 Jul 2026 12:39:40 +0200
Subject: [PATCH] feat(settings): add Ask Questions toggle
---
.changeset/ask-user-question-forms.md | 2 +-
docs/config.html | 6 +-
docs/config.md | 4 +-
.../SettingsSessiondPanel.askUser.test.ts | 78 +++++++++++++++++++
.../settings/SettingsSessiondPanel.ts | 32 +++++++-
.../settings/settingsSessiondConfig.test.ts | 3 +-
.../settings/settingsSessiondConfig.ts | 4 +
7 files changed, 124 insertions(+), 5 deletions(-)
create mode 100644 src/client/src/components/settings/SettingsSessiondPanel.askUser.test.ts
diff --git a/.changeset/ask-user-question-forms.md b/.changeset/ask-user-question-forms.md
index 3eab8ae..d59a062 100644
--- a/.changeset/ask-user-question-forms.md
+++ b/.changeset/ask-user-question-forms.md
@@ -2,4 +2,4 @@
"@jmfederico/pi-web": patch
---
-Add an `ask_user` session tool that lets agents post structured question sets as one chat-native browser form. The form uses the transcript's single scroll area, keeps its header visible, and always gives every question a Custom free-text answer. Agents end their run while the form waits; users can submit full or partial answers, unanswered questions are reported explicitly, pending forms survive browser and web/API reconnects, and closed forms remain readable in the transcript. Disable the tool with `askUser: false` or `PI_WEB_ASK_USER=false`.
+Add an `ask_user` session tool that lets agents post structured question sets as one chat-native browser form. The form uses the transcript's single scroll area, keeps its header visible, and always gives every question a Custom free-text answer. Agents end their run while the form waits; users can submit full or partial answers, unanswered questions are reported explicitly, pending forms survive browser and web/API reconnects, and closed forms remain readable in the transcript. Disable the tool from **Settings → Session daemon**, with `askUser: false`, or with `PI_WEB_ASK_USER=false`.
diff --git a/docs/config.html b/docs/config.html
index 44d2105..da45276 100644
--- a/docs/config.html
+++ b/docs/config.html
@@ -270,7 +270,7 @@
it, and whether project-local config overrides or merges with global config. Rows with JSON key
— are runtime-only environment variables, not config-file keys. Global means
machine-global. In Settings, selected-machine-safe global keys (pathAccess, uploads,
- maxUploadBytes, agent, spawnSessions, subsessions, and plugins)
+ maxUploadBytes, agent, spawnSessions, subsessions, askUser, and plugins)
are edited for the selected machine; gateway host/port/allowed-hosts, keyboard shortcuts, and machine
registry/tokens stay local.
@@ -807,6 +807,10 @@
true; set it to false, or set PI_WEB_ASK_USER=false, to remove the
tool. The environment override accepts 0|1|true|false and takes precedence over the config file.
+
+ Use Settings → Session daemon → Allow agents to ask questions to change
+ askUser on the selected machine. An environment override makes the toggle read-only.
+
The tool accepts one set of 1–20 questions. Each question has a unique id, its
question text, optional supporting detail, up to 12 options with stable values and
diff --git a/docs/config.md b/docs/config.md
index 76ebfcb..f28cf05 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -102,7 +102,7 @@ Plugins may own separate project files, such as `.pi-web/tasks.json` for the bui
## Configuration matrix
-Rows with JSON key `—` are runtime-only environment variables, not config-file keys. `Global` means machine-global. In Settings, selected-machine-safe global keys (`pathAccess`, `uploads`, `maxUploadBytes`, `agent`, `spawnSessions`, `subsessions`, and `plugins`) are edited for the selected machine; gateway host/port/allowed-hosts, keyboard shortcuts, and machine registry/tokens stay local.
+Rows with JSON key `—` are runtime-only environment variables, not config-file keys. `Global` means machine-global. In Settings, selected-machine-safe global keys (`pathAccess`, `uploads`, `maxUploadBytes`, `agent`, `spawnSessions`, `subsessions`, `askUser`, and `plugins`) are edited for the selected machine; gateway host/port/allowed-hosts, keyboard shortcuts, and machine registry/tokens stay local.
| Config | JSON key | Env var | Scope | Project-local behavior | Applies / restart |
| --- | --- | --- | --- | --- | --- |
@@ -275,6 +275,8 @@ In **Settings → Session daemon**, these keys are saved on the selected machine
`askUser` controls whether agents receive the core `ask_user` tool. It defaults to `true`; set it to `false`, or set `PI_WEB_ASK_USER=false`, to remove the tool. The environment override accepts `0|1|true|false` and takes precedence over the config file.
+Use **Settings → Session daemon → Allow agents to ask questions** to change `askUser` on the selected machine. An environment override makes the toggle read-only.
+
The tool accepts one set of 1–20 questions. Each question has a unique `id`, its `question` text, optional supporting `detail`, up to 12 options with stable values and user-facing labels, and an optional `multiple` flag. The browser always adds a **Custom** free-text answer, including when the model supplies no options. No question is required: the user may leave any of them unanswered.
Calling `ask_user` posts the whole set as one browser form and ends the current agent run instead of waiting for the user. The open form is owned by the session daemon, so it survives a browser disconnect, browser reload, or web/API restart while that daemon keeps running. When the user submits, the answers arrive as a follow-up that wakes the session; each question is reported with its selected option values or free text, or explicitly as unanswered.
diff --git a/src/client/src/components/settings/SettingsSessiondPanel.askUser.test.ts b/src/client/src/components/settings/SettingsSessiondPanel.askUser.test.ts
new file mode 100644
index 0000000..24a50fb
--- /dev/null
+++ b/src/client/src/components/settings/SettingsSessiondPanel.askUser.test.ts
@@ -0,0 +1,78 @@
+// @vitest-environment happy-dom
+
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import type { PiWebConfigResponse } from "../../api";
+import { SettingsSessiondPanel } from "./SettingsSessiondPanel";
+
+beforeEach(() => {
+ document.body.replaceChildren();
+});
+
+describe("SettingsSessiondPanel Ask Questions setting", () => {
+ it("lets the user disable ask_user with a daemon config patch", async () => {
+ const panel = new SettingsSessiondPanel();
+ const onSave = vi.fn();
+ panel.configResponse = configResponse(true);
+ panel.onSave = onSave;
+ document.body.append(panel);
+ await panel.updateComplete;
+
+ const toggle = askUserToggle(panel);
+ expect(toggle.checked).toBe(true);
+ expect(toggle.disabled).toBe(false);
+
+ toggle.click();
+ await Promise.resolve();
+
+ expect(onSave).toHaveBeenCalledWith({ askUser: false });
+ });
+
+ it("keeps an environment-overridden setting read-only", async () => {
+ const panel = new SettingsSessiondPanel();
+ panel.configResponse = configResponse(true, true);
+ document.body.append(panel);
+ await panel.updateComplete;
+
+ const toggle = askUserToggle(panel);
+ expect(toggle.checked).toBe(true);
+ expect(toggle.disabled).toBe(true);
+ });
+
+ it("does not offer an unsupported setting from an older selected machine", async () => {
+ const panel = new SettingsSessiondPanel();
+ panel.configResponse = configResponse(undefined);
+ document.body.append(panel);
+ await panel.updateComplete;
+
+ const toggle = askUserToggle(panel);
+ expect(toggle.checked).toBe(false);
+ expect(toggle.disabled).toBe(true);
+ });
+});
+
+function askUserToggle(panel: SettingsSessiondPanel): HTMLInputElement {
+ const toggle = panel.shadowRoot?.querySelector('input[aria-label="Enable Ask Questions"]');
+ if (toggle === undefined || toggle === null) throw new Error("Ask Questions toggle was not rendered");
+ return toggle;
+}
+
+function configResponse(askUser: boolean | undefined, askUserOverride = false): PiWebConfigResponse {
+ const askUserConfig = askUser === undefined ? {} : { askUser };
+ return {
+ path: "/tmp/pi-web/config.json",
+ exists: true,
+ config: askUserConfig,
+ effectiveConfig: askUserConfig,
+ envOverrides: {
+ host: false,
+ port: false,
+ allowedHosts: false,
+ spawnSessions: false,
+ subsessions: false,
+ askUser: askUserOverride,
+ agentCommand: false,
+ agentDir: false,
+ agentSessionDir: false,
+ },
+ };
+}
diff --git a/src/client/src/components/settings/SettingsSessiondPanel.ts b/src/client/src/components/settings/SettingsSessiondPanel.ts
index e15e489..efa9cd7 100644
--- a/src/client/src/components/settings/SettingsSessiondPanel.ts
+++ b/src/client/src/components/settings/SettingsSessiondPanel.ts
@@ -5,7 +5,7 @@ import "./SettingsPanelFrame";
import type { SettingsNotice } from "./SettingsPanelFrame";
import { agentProfileConfigPatchFromDraft, agentProfileDraftFromConfig, agentProfileDraftMatchesConfig, emptyAgentProfileConfigDraft, type AgentProfileConfigDraft } from "./settingsConfigDraft";
import type { AgentProfileSettingsSupport } from "./settingsMachineTarget";
-import { agentDirFieldOverridden, agentProfileActivationState, spawnSessionsConfigPatch, subsessionsConfigPatch } from "./settingsSessiondConfig";
+import { agentDirFieldOverridden, agentProfileActivationState, askUserConfigPatch, spawnSessionsConfigPatch, subsessionsConfigPatch } from "./settingsSessiondConfig";
@customElement("settings-sessiond-panel")
export class SettingsSessiondPanel extends LitElement {
@@ -47,6 +47,11 @@ export class SettingsSessiondPanel extends LitElement {
const subsessionsOverridden = config?.envOverrides.subsessions === true;
// Beta, off by default; also requires spawn to be enabled.
const effectiveSubsessions = config?.effectiveConfig.subsessions === true && effectiveSpawn;
+ // Current servers always resolve this on-by-default setting. Absence means
+ // an older selected machine cannot persist it yet.
+ const askUserSupported = config?.effectiveConfig.askUser !== undefined;
+ const askUserOverridden = config?.envOverrides.askUser === true;
+ const effectiveAskUser = config?.effectiveConfig.askUser === true;
const agentCommandOverridden = config?.envOverrides.agentCommand === true;
const profileEditingSupported = this.agentProfileSupport.state === "supported";
const draftCommand = agentCommandOverridden ? (config.effectiveConfig.agent?.command ?? this.agentDraft.command) : this.agentDraft.command;
@@ -141,6 +146,25 @@ export class SettingsSessiondPanel extends LitElement {
Beta: agents can start child sessions they stay attached to (spawn_subsession, list_subsessions, check_subsession, read_subsession) and are notified when a child finishes. Requires "Allow agents to start sessions". Off by default.
+
+
+ Allow agents to ask questions
+ ${askUserOverridden ? html`environment override` : null}
+
+
+ ${askUserSupported
+ ? html`Agents can post a structured question form and pause until the user responds. On by default.`
+ : html`This machine does not expose the Ask Questions setting. Update and restart PI WEB on that machine to configure it.`}
+
Desired after environment overrides
@@ -151,6 +175,7 @@ export class SettingsSessiondPanel extends LitElement {