From c83c84d2ab48e683becd15513a653e48b4776b84 Mon Sep 17 00:00:00 2001 From: Antisophy <293439221+Antisophy@users.noreply.github.com> Date: Fri, 28 Aug 2026 07:19:24 -0700 Subject: [PATCH] feat(config): a suggestions option that stops backend generation Next-message suggestions cost a small-model one-shot at every turn end and on opening a live task, whether or not anyone reads them. A top-level `suggestions: false` now stops the backend from creating them at all, gated inside generateSuggestions so every trigger site goes quiet with one check; the suggestion bar simply never populates. The option is deliberately not under gui:, since it stops the work, not the display. Title generation is separate and unaffected: new tasks still get auto-titles. Default unchanged (enabled). The spec drives two full turns with suggestions disabled and asserts the bar stays empty; the second turn bounds the wait without a wall-clock sleep, since the generation one-shot answers from the same mock the turns do. --- source/cydo/runtime/config/package.d | 4 ++ source/cydo/server/app.d | 1 + source/cydo/workflow/tasks/derived_text.d | 6 +++ tests/e2e/suggestions-off.spec.ts | 47 +++++++++++++++++++++++ 4 files changed, 58 insertions(+) create mode 100644 tests/e2e/suggestions-off.spec.ts diff --git a/source/cydo/runtime/config/package.d b/source/cydo/runtime/config/package.d index d2c21393..3d7a35d5 100644 --- a/source/cydo/runtime/config/package.d +++ b/source/cydo/runtime/config/package.d @@ -112,6 +112,10 @@ struct CydoConfig @Optional string default_agent; @Optional string default_task_type; @Optional AgentConfig[string] agents; + /// Generate next-message suggestions (a small-model one-shot at each turn + /// end and on opening a live task). Set false to stop the backend from + /// creating them at all; title generation is separate and unaffected. + @Optional SetInfo!bool suggestions; @Optional bool dev_mode; @Optional string log_level = "info"; @Optional string system_keyword = "SYSTEM"; diff --git a/source/cydo/server/app.d b/source/cydo/server/app.d index c127f5ba..ba74b838 100644 --- a/source/cydo/server/app.d +++ b/source/cydo/server/app.d @@ -547,6 +547,7 @@ class App )); derivedTextJobs = new DerivedTextJobs(DerivedTextJobsHost( getTask: (int tid) => tid in tasks ? &tasks[tid] : null, + suggestionsEnabled: () => !config.suggestions.set || config.suggestions.value, snapshotTaskIds: &snapshotTaskIdsForResume, agentForTask: &agentForTask, hasSubscribers: (int tid) => clientHub.hasSubscribers(tid), diff --git a/source/cydo/workflow/tasks/derived_text.d b/source/cydo/workflow/tasks/derived_text.d index d9c4a7d6..217d4161 100644 --- a/source/cydo/workflow/tasks/derived_text.d +++ b/source/cydo/workflow/tasks/derived_text.d @@ -16,6 +16,10 @@ import cydo.workflow.history.abbrev : buildAbbreviatedHistoryFromStrings; struct DerivedTextJobsHost { TaskData* delegate(int tid) getTask; + /// Whether suggestion generation is enabled at all (config `suggestions`); + /// null means enabled. Gating here stops the backend one-shots themselves, + /// not just the display. + bool delegate() suggestionsEnabled; int[] delegate() snapshotTaskIds; Agent delegate(int tid) agentForTask; bool delegate(int tid) hasSubscribers; @@ -144,6 +148,8 @@ public: void generateSuggestions(int tid) { + if (host_.suggestionsEnabled !is null && !host_.suggestionsEnabled()) + return; auto td = host_.getTask(tid); if (td is null) return; diff --git a/tests/e2e/suggestions-off.spec.ts b/tests/e2e/suggestions-off.spec.ts new file mode 100644 index 00000000..7e692cf0 --- /dev/null +++ b/tests/e2e/suggestions-off.spec.ts @@ -0,0 +1,47 @@ +import { + test, + expect, + enterSession, + sendMessage, + responseTimeout, + assistantText, +} from "./fixtures"; +import { writeTestConfig } from "./test-config"; + +// `suggestions: false` stops the backend from generating next-message +// suggestions at all: the one-shot is never issued, so the suggestion bar +// never populates. +test("suggestions: false stops backend generation", async ({ + page, + agentType, +}) => { + writeTestConfig( + "/tmp/playwright-home/.config/cydo/config.yaml", + `default_agent: ${agentType} +log_level: trace +suggestions: false +workspaces: + local: + root: /tmp/cydo-test-workspace +`, + ); + // Give the backend's inotify watcher time to reload the config. + await page.waitForTimeout(500); + + await enterSession(page); + await sendMessage(page, 'Please reply with "one"'); + await expect(assistantText(page, "one")).toBeVisible({ + timeout: responseTimeout(agentType), + }); + + // A second full turn bounds the wait without a wall-clock sleep: had the + // first turn generated suggestions, they would have rendered long before + // the second turn completes, since the generation one-shot answers from + // the same mock as the turns themselves. + await sendMessage(page, 'Please reply with "two"'); + await expect(assistantText(page, "two")).toBeVisible({ + timeout: responseTimeout(agentType), + }); + + await expect(page.locator(".btn-suggestion")).toHaveCount(0); +});