diff --git a/desktop/app.go b/desktop/app.go index cd0101a1be..f4d2d34b60 100644 --- a/desktop/app.go +++ b/desktop/app.go @@ -6076,7 +6076,10 @@ type SkillView struct { Model string `json:"model,omitempty"` Effort string `json:"effort,omitempty"` AllowedTools []string `json:"allowedTools,omitempty"` - Color string `json:"color,omitempty"` + // ReadOnly mirrors frontmatter read-only; omitted/false keeps the legacy + // writable default for older profiles. + ReadOnly bool `json:"readOnly,omitempty"` + Color string `json:"color,omitempty"` // Invocation is the user-facing slash name; InvocationMode preserves the // frontmatter policy used by the subagent profile editor. Invocation string `json:"invocation,omitempty"` @@ -6456,6 +6459,7 @@ func (a *App) SkillsSettings() SkillsSettingsView { Model: s.Model, Effort: s.Effort, AllowedTools: append([]string{}, s.AllowedTools...), + ReadOnly: s.ReadOnly, Color: s.Color, Invocation: "/" + s.SlashName(), InvocationMode: s.Invocation, diff --git a/desktop/frontend/src/__tests__/settings-refresh-snapshot.test.tsx b/desktop/frontend/src/__tests__/settings-refresh-snapshot.test.tsx index 52b46fb930..9d1372dd95 100644 --- a/desktop/frontend/src/__tests__/settings-refresh-snapshot.test.tsx +++ b/desktop/frontend/src/__tests__/settings-refresh-snapshot.test.tsx @@ -78,7 +78,7 @@ function baseSettings(displayMode: "standard" | "compact" = "standard"): Setting permissions: { mode: "ask", allow: [], ask: [], deny: [] }, sandbox: { bash: "enforce", network: false, workspaceRoot: "", allowWrite: [], effectiveWorkspaceRoot: "/work", effectiveWriteRoots: ["/work"], shell: "auto" }, network: { proxyMode: "auto", proxyUrl: "", noProxy: "", proxy: { type: "socks5", server: "", port: 0, username: "", password: "" } }, - agent: { temperature: 0, maxSteps: 0, plannerMaxSteps: 0, maxSubagentDepth: 2, systemPrompt: "", coldResumePrune: true, reasoningLanguage: "auto" }, + agent: { temperature: 0, maxSteps: 0, plannerMaxSteps: 0, maxSubagentDepth: 2, maxSubagentConcurrency: 6, maxParallelWriters: 3, systemPrompt: "", coldResumePrune: true, reasoningLanguage: "auto" }, bot: { enabled: false, model: "", diff --git a/desktop/frontend/src/components/SettingsPanel.tsx b/desktop/frontend/src/components/SettingsPanel.tsx index 7a9e820452..d2f13401ff 100644 --- a/desktop/frontend/src/components/SettingsPanel.tsx +++ b/desktop/frontend/src/components/SettingsPanel.tsx @@ -1246,7 +1246,7 @@ function normalizeSettingsView(view: SettingsView | null | undefined): SettingsV noProxy: "", proxy: { type: "socks5", server: "", port: 0, username: "", password: "" }, }; - const agent = view.agent ?? { temperature: 0, maxSteps: 0, plannerMaxSteps: 0, maxSubagentDepth: 2, systemPrompt: "", coldResumePrune: true, reasoningLanguage: "auto" }; + const agent = view.agent ?? { temperature: 0, maxSteps: 0, plannerMaxSteps: 0, maxSubagentDepth: 2, maxSubagentConcurrency: 6, maxParallelWriters: 3, systemPrompt: "", coldResumePrune: true, reasoningLanguage: "auto" }; agent.plannerMaxSteps = Number.isFinite(agent.plannerMaxSteps) ? Math.max(0, Math.trunc(agent.plannerMaxSteps)) : 0; agent.maxSteps = Number.isFinite(agent.maxSteps) ? Math.max(0, Math.trunc(agent.maxSteps)) : 0; agent.maxSubagentDepth = Number.isFinite(agent.maxSubagentDepth) && agent.maxSubagentDepth <= 1 ? 1 : 2; @@ -3950,8 +3950,14 @@ function ModelsSection({ s, busy, apply, backgroundApply }: ModelsSectionProps) : !providerIsConfigured(defaultProviderView) ? t("settings.modelNeedsKey", { provider: modelProviderLabel(defaultProvider, defaultProviderView, t) }) : ""; - const agent = s.agent ?? { temperature: 0, maxSteps: 0, plannerMaxSteps: 0, maxSubagentDepth: 2, systemPrompt: "", coldResumePrune: true, reasoningLanguage: "auto" }; + const agent = s.agent ?? { temperature: 0, maxSteps: 0, plannerMaxSteps: 0, maxSubagentDepth: 2, maxSubagentConcurrency: 6, maxParallelWriters: 3, systemPrompt: "", coldResumePrune: true, reasoningLanguage: "auto" }; const subagentDepth = Number.isFinite(agent.maxSubagentDepth) && agent.maxSubagentDepth <= 1 ? 1 : 2; + const subagentConcurrency = Number.isFinite(agent.maxSubagentConcurrency) && agent.maxSubagentConcurrency > 0 + ? Math.max(1, Math.min(32, Math.floor(agent.maxSubagentConcurrency))) + : 6; + const parallelWriters = Number.isFinite(agent.maxParallelWriters) && agent.maxParallelWriters > 0 + ? Math.max(1, Math.min(subagentConcurrency, Math.floor(agent.maxParallelWriters))) + : Math.min(3, subagentConcurrency); useEffect(() => { if (subtab !== "usage") return; @@ -4077,6 +4083,38 @@ function ModelsSection({ s, busy, apply, backgroundApply }: ModelsSectionProps) + + { + const n = Number(e.target.value); + if (!Number.isFinite(n)) return; + void apply(() => app.SetMaxSubagentConcurrency(n)); + }} + /> + + + + { + const n = Number(e.target.value); + if (!Number.isFinite(n)) return; + void apply(() => app.SetMaxParallelWriters(n)); + }} + /> + + {modelIssue &&
{modelIssue}
} diff --git a/desktop/frontend/src/components/SubagentsPanel.tsx b/desktop/frontend/src/components/SubagentsPanel.tsx index ae69b275e2..d743b68bbd 100644 --- a/desktop/frontend/src/components/SubagentsPanel.tsx +++ b/desktop/frontend/src/components/SubagentsPanel.tsx @@ -587,6 +587,7 @@ function SubagentProfileForm({ const [selectedTools, setSelectedTools] = useState>(() => new Set(editingSkill?.allowedTools ?? [])); const hasUsedCustomMode = useRef(Boolean(editingSkill?.allowedTools?.length)); const [systemPrompt, setSystemPrompt] = useState(editingSkill?.body ?? ""); + const [readOnly, setReadOnly] = useState(Boolean(editingSkill?.readOnly)); const [scope, setScope] = useState<"global" | "project">(editingSkill?.scope === "project" ? "project" : "global"); const [tryTask, setTryTask] = useState(""); const [tryRunning, setTryRunning] = useState(false); @@ -614,6 +615,7 @@ function SubagentProfileForm({ model, effort, allowedTools: toolMode === "custom" ? Array.from(selectedTools) : [], + readOnly, scope, }); @@ -706,6 +708,27 @@ function SubagentProfileForm({ {toolMode === "custom" && } {toolMode === "custom" && !toolsReady &&
{t("subagents.selectAtLeastOneTool")}
} + +
+ + +
+
{t("subagents.readOnlyHint")}
+