fix: the window's approval switch never reached the engine

needs_confirm() (src/agent_loop.c) decides whether a tool asks by reading
cfg->confirm_shell / confirm_write / confirm_net; the approval mode only
relaxes those. The CLI fills them in (main.c: parse_confirm), the TUI and the
WebUI do too -- the Qt window never did: AgentCore::open() memset the whole
config and cst_agent_create() keeps what it is given, so every tool ran
without asking while the sidebar said "ask". That is why nothing was ever
asked on the phone (or on a desktop).

- AgentCore: one mapping, applyApproval(), used by open() and setApproval();
  the mode is remembered like the TUI's /approval and read back on start
  (else "ask", the only mode where the window's consent dialog is reached).
- --uitest: two checks that the mode reaches the engine as those flags.

Version 0.9.7 (local).
This commit is contained in:
WHC
2026-10-11 20:22:50 +08:00
parent 4e990c8e35
commit 7b2e7a3dbd
2 changed files with 50 additions and 2 deletions
+31 -2
View File
@@ -45,6 +45,24 @@ class Turn : public QThread {
QByteArray m_prompt;
};
/* The engine asks for a tool only when its own confirm_shell / confirm_write /
* confirm_net flag says so (`needs_confirm()`, src/agent_loop.c) -- the
* approval mode on top of that only *relaxes* them (AUTO writes a file
* without asking, YOLO everything). The CLI fills those three in
* (src/main.c: parse_confirm), the TUI and the WebUI do too; the window never
* did: AgentCore::open() zeroed the whole config and cst_agent_create() keeps
* what it is given, so every tool ran without asking while the sidebar said
* "ask". One mapping, used by both open() and setApproval(). */
namespace {
void applyApproval(cst_agent_config_t *cfg, int approval) {
const int ask = (approval == CST_APPROVAL_ASK);
cfg->approval = approval;
cfg->confirm_shell = ask;
cfg->confirm_write = ask;
cfg->confirm_net = ask;
}
} // namespace
/* --- lifetime ------------------------------------------------------------ */
AgentCore::AgentCore(QObject *parent) : QObject(parent) {
@@ -141,6 +159,14 @@ bool AgentCore::open(const QString &workspace, const QString &provider,
/* The GUI answers itself: never let the engine decide behind its back. */
m_cfg.auto_approve = 0;
m_cfg.headless = 0;
/* What the person chose last -- the CLI and the TUI remember it the same
* way and the TUI writes the same file -- else "ask", which is what the
* window's default label promises and the only mode where its consent
* dialog is ever reached. */
int approval = CST_APPROVAL_ASK;
if (have_settings && st.approval[0])
cst_approval_parse(st.approval, &approval);
applyApproval(&m_cfg, approval);
if (!m_cfg.mode[0])
snprintf(m_cfg.mode, sizeof(m_cfg.mode), "%s", CST_DEFAULT_MODE);
m_agent = cst_agent_create(&m_cfg);
@@ -395,9 +421,12 @@ void AgentCore::setMode(const QString &mode) {
}
void AgentCore::setApproval(int approval) {
m_cfg.approval = approval;
applyApproval(&m_cfg, approval);
if (m_agent)
cst_agent_config_mut(m_agent)->approval = approval;
applyApproval(cst_agent_config_mut(m_agent), approval);
/* Remembered like the TUI's /approval: the next start of the window (or of
* the CLI) comes up the way it was left. */
cst_settings_remember_approval(approval);
emit approvalChanged();
}
+19
View File
@@ -416,6 +416,25 @@ int runUiTest(AgentCore &core, const QString &size) {
QStringLiteral("and the line is where the card is"));
}
/* The approval switch is not decoration: the engine asks for a tool only
* when its own confirm_* flags say so (needs_confirm() in agent_loop.c),
* and every front end that has a consent dialog has to set them -- the
* window used to leave all three at zero, which is why "ask" never asked. */
{
const int was = core.approval();
core.setApproval(CST_APPROVAL_ASK);
const cst_agent_config_t *live = cst_agent_config(core.agent());
check(live && live->approval == CST_APPROVAL_ASK && live->confirm_shell &&
live->confirm_write && live->confirm_net,
QStringLiteral("'ask' reaches the engine as the three confirm flags"));
core.setApproval(CST_APPROVAL_YOLO);
live = cst_agent_config(core.agent());
check(live && live->approval == CST_APPROVAL_YOLO && !live->confirm_shell &&
!live->confirm_write && !live->confirm_net,
QStringLiteral("and 'yolo' takes them all away"));
core.setApproval(was); /* leave the machine as it was found */
}
/* --- md_to_pdf: the front end's own document writer ------------------- */
{
const cst_tool_def_t *def = cst_tool_find("md_to_pdf");