From d4a4570b9769743899d97480b3bf482860b51d9c Mon Sep 17 00:00:00 2001 From: Wes Date: Mon, 3 Aug 2026 14:47:52 -0600 Subject: [PATCH] fix(desktop): clarify inherited agent parallelism (#4010) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - show an unambiguous `App default (10)` inherited state for parallelism in create and edit forms - explain that blank inherits the app default and suppress create-form number steppers that could silently set `1` - align the E2E mint fallback with production while preserving explicit input → definition → app-default precedence ## Why The forms displayed `1` even though an untouched field is omitted and desktop minting materializes `10`. The create-form spinner could also turn blank/inherited into an explicit `1` with one click while leaving the field looking nearly unchanged. ## Testing - `pnpm test` (desktop: 3,886 passed) - `pnpm typecheck` (desktop) - `pnpm check` (desktop) - pre-push `desktop-check` and `desktop-test` --------- Signed-off-by: Wes Co-authored-by: Carl --- desktop/src-tauri/src/key_backup_tests.rs | 12 +++++++---- .../agents/lib/agentParallelism.test.mjs | 20 +++++++++++++++++++ .../features/agents/lib/agentParallelism.ts | 18 +++++++++++++++++ .../agents/ui/EditAgentAdvancedFields.tsx | 7 ++++++- .../agents/ui/PersonaAdvancedFields.tsx | 10 +++++++--- desktop/src/testing/e2eBridge.ts | 7 +++++-- 6 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 desktop/src/features/agents/lib/agentParallelism.test.mjs create mode 100644 desktop/src/features/agents/lib/agentParallelism.ts diff --git a/desktop/src-tauri/src/key_backup_tests.rs b/desktop/src-tauri/src/key_backup_tests.rs index 35b486f78d..ff9367641a 100644 --- a/desktop/src-tauri/src/key_backup_tests.rs +++ b/desktop/src-tauri/src/key_backup_tests.rs @@ -250,12 +250,16 @@ fn generated_passphrase_respects_word_count_and_separator() { #[test] fn generated_passphrase_clamps_word_count() { + // Use a separator that cannot appear in the EFF wordlist so a generated + // word such as "yo-yo" cannot be mistaken for two words. + const SEPARATOR: &str = "|"; + // Below the floor: clamped up to MIN_PASSPHRASE_WORDS, never shorter. - let phrase = generate_passphrase(1, "-").unwrap(); - assert_eq!(phrase.split('-').count(), MIN_PASSPHRASE_WORDS); + let phrase = generate_passphrase(1, SEPARATOR).unwrap(); + assert_eq!(phrase.split(SEPARATOR).count(), MIN_PASSPHRASE_WORDS); // Above the ceiling: clamped down to MAX_PASSPHRASE_WORDS. - let phrase = generate_passphrase(50, "-").unwrap(); - assert_eq!(phrase.split('-').count(), MAX_PASSPHRASE_WORDS); + let phrase = generate_passphrase(50, SEPARATOR).unwrap(); + assert_eq!(phrase.split(SEPARATOR).count(), MAX_PASSPHRASE_WORDS); } #[test] diff --git a/desktop/src/features/agents/lib/agentParallelism.test.mjs b/desktop/src/features/agents/lib/agentParallelism.test.mjs new file mode 100644 index 0000000000..8d03d087e7 --- /dev/null +++ b/desktop/src/features/agents/lib/agentParallelism.test.mjs @@ -0,0 +1,20 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { + DEFAULT_AGENT_PARALLELISM, + resolveAgentParallelism, +} from "./agentParallelism.ts"; + +test("parallelism uses the app default only when input and definition omit it", () => { + assert.equal( + resolveAgentParallelism(undefined, undefined), + DEFAULT_AGENT_PARALLELISM, + ); + assert.equal( + resolveAgentParallelism(undefined, null), + DEFAULT_AGENT_PARALLELISM, + ); + assert.equal(resolveAgentParallelism(undefined, 4), 4); + assert.equal(resolveAgentParallelism(2, 4), 2); +}); diff --git a/desktop/src/features/agents/lib/agentParallelism.ts b/desktop/src/features/agents/lib/agentParallelism.ts new file mode 100644 index 0000000000..89544c5bb4 --- /dev/null +++ b/desktop/src/features/agents/lib/agentParallelism.ts @@ -0,0 +1,18 @@ +/** + * Desktop-managed agents materialize this value when neither the create input + * nor the linked definition sets parallelism. Keep in sync with + * `managed_agents::DEFAULT_AGENT_PARALLELISM` in the Tauri backend. + */ +export const DEFAULT_AGENT_PARALLELISM = 10; + +export const AGENT_PARALLELISM_PLACEHOLDER = `App default (${DEFAULT_AGENT_PARALLELISM})`; +export const AGENT_PARALLELISM_HELP = `Leave blank to use the app default (currently ${DEFAULT_AGENT_PARALLELISM}). Custom values may be 1–32.`; +export const EDIT_AGENT_PARALLELISM_HELP = + "Current value for this agent. Custom values may be 1–32."; + +export function resolveAgentParallelism( + input: number | undefined, + definition: number | null | undefined, +): number { + return input ?? definition ?? DEFAULT_AGENT_PARALLELISM; +} diff --git a/desktop/src/features/agents/ui/EditAgentAdvancedFields.tsx b/desktop/src/features/agents/ui/EditAgentAdvancedFields.tsx index c8e21cd6fa..972c4e287e 100644 --- a/desktop/src/features/agents/ui/EditAgentAdvancedFields.tsx +++ b/desktop/src/features/agents/ui/EditAgentAdvancedFields.tsx @@ -11,6 +11,7 @@ import { import type { AgentPersona } from "@/shared/api/types"; import { BuzzAgentModelTuningFields } from "./buzzAgentModelTuningFields"; import { isBuzzAgentRuntime } from "./buzzAgentConfig"; +import { EDIT_AGENT_PARALLELISM_HELP } from "../lib/agentParallelism"; export function EditAgentAdvancedFields({ acpCommand, @@ -173,10 +174,14 @@ export function EditAgentAdvancedFields({ id="edit-agent-parallelism" inputMode="numeric" onChange={(event) => onParallelismChange(event.target.value)} - placeholder="1" + placeholder="Current value" + type="text" value={parallelism} /> +

+ {EDIT_AGENT_PARALLELISM_HELP} +

{/* Relay URL: intentionally no editor. The legacy per-record relay pin diff --git a/desktop/src/features/agents/ui/PersonaAdvancedFields.tsx b/desktop/src/features/agents/ui/PersonaAdvancedFields.tsx index 01485dd9eb..b7d1903784 100644 --- a/desktop/src/features/agents/ui/PersonaAdvancedFields.tsx +++ b/desktop/src/features/agents/ui/PersonaAdvancedFields.tsx @@ -4,6 +4,10 @@ import { EnvVarsEditor, type EnvVarsValue } from "./EnvVarsEditor"; import { CreateAgentRespondToField } from "./RespondToField"; import type { PersonaBehaviorDraft } from "./personaBehaviorDraft"; import { isBuzzAgentRuntime } from "./buzzAgentConfig"; +import { + AGENT_PARALLELISM_HELP, + AGENT_PARALLELISM_PLACEHOLDER, +} from "../lib/agentParallelism"; import { BuzzAgentModelTuningFields } from "./buzzAgentModelTuningFields"; import { CARD_MINT_KEY_ANNOTATIONS, @@ -83,7 +87,7 @@ export function PersonaAdvancedFields({ >

- How many conversations each running instance handles at once (1–32). + {AGENT_PARALLELISM_HELP}

diff --git a/desktop/src/testing/e2eBridge.ts b/desktop/src/testing/e2eBridge.ts index 5cbfb03331..895e770f0f 100644 --- a/desktop/src/testing/e2eBridge.ts +++ b/desktop/src/testing/e2eBridge.ts @@ -12,6 +12,7 @@ import { import { relayClient } from "@/shared/api/relayClient"; import { activateRateLimit } from "@/shared/api/relayRateLimitGate"; +import { resolveAgentParallelism } from "@/features/agents/lib/agentParallelism"; import type { ConnectionState } from "@/shared/api/relayClientShared"; import type { ChannelTemplate, RelayEvent } from "@/shared/api/types"; import { getMarkdownParseCount } from "@/shared/ui/markdown/nodeCache"; @@ -8075,8 +8076,10 @@ async function handleCreateManagedAgent( args.input.respondTo !== undefined ? (args.input.respondToAllowlist ?? []) : (linkedPersona?.respond_to_allowlist ?? []); - const mintParallelism = - args.input.parallelism ?? linkedPersona?.parallelism ?? 1; + const mintParallelism = resolveAgentParallelism( + args.input.parallelism, + linkedPersona?.parallelism, + ); const personaAvatarUrl = args.input.personaId === undefined ? null