From c158dda8a6ea69c40a7b73928b2e2e209589c2e4 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Fri, 19 Jun 2026 15:27:19 -0700 Subject: [PATCH] FN-6745: add xhigh thinking level support Adds the xhigh reasoning option across settings, task flows, APIs, and model adapter tests. - Extend the shared thinking-level enum, validation paths, generated resource types, and settings documentation for `xhigh`. - Surface `xhigh` in task, agent, and global model selectors with localized labels. - Cover task route handling, task form rendering, and Pi adapter mapping behavior with regression tests. - Add a patch changeset for the published Fusion package. Files changed: .changeset/fn-6745-xhigh-thinking-level.md | 5 + docs/settings-reference.md | 2 +- .../core/src/__tests__/thinking-levels.test.ts | 9 ++ packages/core/src/types.ts | 10 +- packages/dashboard/app/api/legacy.ts | 6 +- .../dashboard/app/components/AgentDetailView.tsx | 2 +- .../dashboard/app/components/ModelSelectorTab.tsx | 1 + .../dashboard/app/components/NewAgentDialog.tsx | 3 +- packages/dashboard/app/components/NewTaskModal.tsx | 2 +- .../dashboard/app/components/TaskDetailModal.tsx | 2 +- packages/dashboard/app/components/TaskForm.tsx | 2 + .../app/components/__tests__/TaskForm.test.tsx | 14 +++ .../settings/sections/GlobalModelsSection.tsx | 1 + .../src/__tests__/routes-tasks-ops.test.ts | 8 +- .../dashboard/src/__tests__/routes-tasks.test.ts | 8 +- packages/dashboard/src/agent-generation.ts | 4 +- packages/dashboard/src/agent-onboarding.ts | 4 +- .../src/routes/register-task-workflow-routes.ts | 5 +- packages/engine/src/__tests__/pi.test.ts | 25 ++++ packages/i18n/locales/en/app.json | 3 + packages/i18n/locales/es/app.json | 3 + packages/i18n/locales/fr/app.json | 3 + packages/i18n/locales/ko/app.json | 3 + packages/i18n/locales/zh-CN/app.json | 3 + packages/i18n/locales/zh-TW/app.json | 3 + packages/i18n/src/resources.d.ts | 126 ++++++++++++++++++++- 26 files changed, 229 insertions(+), 28 deletions(-) Fusion-Task-Id: FN-6745 Fusion-Task-Lineage: 779e1517-607b-482b-83a1-0f2bb94ae736 --- .changeset/fn-6745-xhigh-thinking-level.md | 5 + docs/settings-reference.md | 2 +- .../src/__tests__/thinking-levels.test.ts | 9 ++ packages/core/src/types.ts | 10 +- packages/dashboard/app/api/legacy.ts | 6 +- .../app/components/AgentDetailView.tsx | 2 +- .../app/components/ModelSelectorTab.tsx | 1 + .../app/components/NewAgentDialog.tsx | 3 +- .../dashboard/app/components/NewTaskModal.tsx | 2 +- .../app/components/TaskDetailModal.tsx | 2 +- .../dashboard/app/components/TaskForm.tsx | 2 + .../components/__tests__/TaskForm.test.tsx | 14 ++ .../settings/sections/GlobalModelsSection.tsx | 1 + .../src/__tests__/routes-tasks-ops.test.ts | 8 +- .../src/__tests__/routes-tasks.test.ts | 8 +- packages/dashboard/src/agent-generation.ts | 4 +- packages/dashboard/src/agent-onboarding.ts | 4 +- .../routes/register-task-workflow-routes.ts | 5 +- packages/engine/src/__tests__/pi.test.ts | 25 ++++ packages/i18n/locales/en/app.json | 3 + packages/i18n/locales/es/app.json | 3 + packages/i18n/locales/fr/app.json | 3 + packages/i18n/locales/ko/app.json | 3 + packages/i18n/locales/zh-CN/app.json | 3 + packages/i18n/locales/zh-TW/app.json | 3 + packages/i18n/src/resources.d.ts | 126 +++++++++++++++++- 26 files changed, 229 insertions(+), 28 deletions(-) create mode 100644 .changeset/fn-6745-xhigh-thinking-level.md create mode 100644 packages/core/src/__tests__/thinking-levels.test.ts diff --git a/.changeset/fn-6745-xhigh-thinking-level.md b/.changeset/fn-6745-xhigh-thinking-level.md new file mode 100644 index 0000000000..89093411c4 --- /dev/null +++ b/.changeset/fn-6745-xhigh-thinking-level.md @@ -0,0 +1,5 @@ +--- +"@runfusion/fusion": minor +--- + +Add the `xhigh` reasoning effort level to model settings and task/agent selectors. Claude CLI adapters pass the value through to runtime mapping, where non-Opus models use `high` effort and Opus models use `max` effort. diff --git a/docs/settings-reference.md b/docs/settings-reference.md index 8d630133b7..4f5e9529ef 100644 --- a/docs/settings-reference.md +++ b/docs/settings-reference.md @@ -39,7 +39,7 @@ Defaults from `DEFAULT_GLOBAL_SETTINGS`; key scope from `GLOBAL_SETTINGS_KEYS`. | `defaultModelId` | `string` | `undefined` | Default AI model ID. | | `fallbackProvider` | `string` | `undefined` | Fallback provider when the primary default model hits transient provider failures or model-compatibility/auth-tier rejections. | | `fallbackModelId` | `string` | `undefined` | Fallback model ID (must pair with `fallbackProvider`). | -| `defaultThinkingLevel` | `"off" \| "minimal" \| "low" \| "medium" \| "high"` | `undefined` | Default reasoning effort for AI sessions. If a provider/runtime rejects simultaneous `thinking` and `reasoning_effort` parameters, Fusion retries without the explicit thinking override instead of failing the run. | +| `defaultThinkingLevel` | `"off" \| "minimal" \| "low" \| "medium" \| "high" \| "xhigh"` | `undefined` | Default reasoning effort for AI sessions. `xhigh` requests maximum reasoning effort; Claude CLI adapters map it to `high` for non-Opus models and `max` for Opus models. If a provider/runtime rejects simultaneous `thinking` and `reasoning_effort` parameters, Fusion retries without the explicit thinking override instead of failing the run. | | `ntfyEnabled` | `boolean` | `false` | Enable ntfy push notifications. | | `failureNotificationMode` | `"sticky-only" \| "terminal-only" \| "all"` | `"sticky-only"` | Failure notification behavior. `sticky-only` defers failed-task notifications by `failureNotificationDelayMs` and suppresses transient self-recoveries. `terminal-only` suppresses while auto-retry is still active and only dispatches when `paused === true` or `column === "in-review"` with `status === "failed"`. `all` restores legacy immediate failure notifications. | | `failureNotificationDelayMs` | `number` | `30000` | Delay window (ms) before evaluating/sending a `failed` notification in `sticky-only` and `terminal-only` modes. Set `0` for immediate dispatch in legacy `all` mode. | diff --git a/packages/core/src/__tests__/thinking-levels.test.ts b/packages/core/src/__tests__/thinking-levels.test.ts new file mode 100644 index 0000000000..1af6ed1ba8 --- /dev/null +++ b/packages/core/src/__tests__/thinking-levels.test.ts @@ -0,0 +1,9 @@ +import { describe, expect, it } from "vitest"; + +import { THINKING_LEVELS } from "../types.js"; + +describe("THINKING_LEVELS", () => { + it("includes xhigh after high for maximum reasoning effort", () => { + expect(THINKING_LEVELS).toEqual(["off", "minimal", "low", "medium", "high", "xhigh"]); + }); +}); diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 40bb6107c7..e4687b2047 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -11,8 +11,14 @@ export { } from "./capacity.js"; export type { CapacityRiskSignal } from "./capacity.js"; -/** Valid thinking effort levels for AI agent sessions, controlling the cost/quality tradeoff of reasoning. */ -export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high"] as const; +/** + * Valid thinking effort levels for AI agent sessions, controlling the cost/quality tradeoff of reasoning. + * Includes extra-high for maximum-effort requests on reasoning-capable models. + * + * FNXC:Settings-ThinkingLevel 2026-06-19-14:55: + * The central thinking-level enum must expose `xhigh` so UI settings and API validation can pass maximum reasoning requests through to CLI adapters. Runtime adapters map `xhigh` to `high` for non-Opus models and `max` for Opus models. + */ +export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"] as const; export type ThinkingLevel = (typeof THINKING_LEVELS)[number]; /** diff --git a/packages/dashboard/app/api/legacy.ts b/packages/dashboard/app/api/legacy.ts index 489c95c7e5..3f9faf891c 100644 --- a/packages/dashboard/app/api/legacy.ts +++ b/packages/dashboard/app/api/legacy.ts @@ -3411,7 +3411,7 @@ export interface AgentOnboardingSummary { name: string; role: AgentCapability | "custom"; instructionsText: string; - thinkingLevel: "off" | "minimal" | "low" | "medium" | "high"; + thinkingLevel: "off" | "minimal" | "low" | "medium" | "high" | "xhigh"; maxTurns: number; title?: string; icon?: string; @@ -3444,7 +3444,7 @@ export interface ExistingAgentOnboardingConfig { reportsTo?: string; skills?: string[]; model?: string; - thinkingLevel?: "off" | "minimal" | "low" | "medium" | "high"; + thinkingLevel?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh"; maxTurns?: number; runtimeHint?: string; heartbeatIntervalMs?: number; @@ -6352,7 +6352,7 @@ export interface AgentGenerationSpec { /** Detailed system prompt in markdown */ systemPrompt: string; /** Suggested thinking level */ - thinkingLevel: "off" | "minimal" | "low" | "medium" | "high"; + thinkingLevel: "off" | "minimal" | "low" | "medium" | "high" | "xhigh"; /** Suggested max turns (1-500) */ maxTurns: number; } diff --git a/packages/dashboard/app/components/AgentDetailView.tsx b/packages/dashboard/app/components/AgentDetailView.tsx index 38a70ea5f9..21185cce47 100644 --- a/packages/dashboard/app/components/AgentDetailView.tsx +++ b/packages/dashboard/app/components/AgentDetailView.tsx @@ -3776,7 +3776,7 @@ function ConfigTab({ skills: selectedSkills, model: modelValue || undefined, runtimeHint: runtimeMode === "runtime" ? selectedRuntimeId || undefined : undefined, - thinkingLevel: (formValues.thinkingLevel as "off" | "minimal" | "low" | "medium" | "high" | undefined) ?? undefined, + thinkingLevel: (formValues.thinkingLevel as "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | undefined) ?? undefined, maxTurns: formValues.maxTurns ? Number(formValues.maxTurns) : undefined, heartbeatIntervalMs: heartbeatValues.heartbeatIntervalMs ? Number(heartbeatValues.heartbeatIntervalMs) * 1000 : undefined, heartbeatTimeoutMs: heartbeatValues.heartbeatTimeoutMs ? Number(heartbeatValues.heartbeatTimeoutMs) * 1000 : undefined, diff --git a/packages/dashboard/app/components/ModelSelectorTab.tsx b/packages/dashboard/app/components/ModelSelectorTab.tsx index 832f328126..2acf560dd3 100644 --- a/packages/dashboard/app/components/ModelSelectorTab.tsx +++ b/packages/dashboard/app/components/ModelSelectorTab.tsx @@ -499,6 +499,7 @@ export function ModelSelectorTab({ task, addToast, onTaskUpdated, settings }: Mo + {t("models.descriptions.thinkingLevel", "Controls the reasoning effort for the AI agent. Higher levels use more tokens.")} diff --git a/packages/dashboard/app/components/NewAgentDialog.tsx b/packages/dashboard/app/components/NewAgentDialog.tsx index 12248e3c08..e1c2d71aeb 100644 --- a/packages/dashboard/app/components/NewAgentDialog.tsx +++ b/packages/dashboard/app/components/NewAgentDialog.tsx @@ -35,7 +35,7 @@ const AGENT_ROLES: { value: AgentCapability; icon: string }[] = [ { value: "custom", icon: "✦" }, ]; -type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high"; +type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh"; /** Set of valid AgentCapability values for mapping generated roles */ const VALID_CAPABILITIES = new Set(["triage", "executor", "reviewer", "merger", "scheduler", "engineer", "custom"]); @@ -682,6 +682,7 @@ export function NewAgentDialog({ +
diff --git a/packages/dashboard/app/components/NewTaskModal.tsx b/packages/dashboard/app/components/NewTaskModal.tsx index 7734233c15..0c8793e746 100644 --- a/packages/dashboard/app/components/NewTaskModal.tsx +++ b/packages/dashboard/app/components/NewTaskModal.tsx @@ -249,7 +249,7 @@ export function NewTaskModal({ isOpen, onClose, projectId, tasks, onCreateTask, validatorModelId: validatorModel && validatorSlashIdx !== -1 ? validatorModel.slice(validatorSlashIdx + 1) : undefined, planningModelProvider: planningModel && planningSlashIdx !== -1 ? planningModel.slice(0, planningSlashIdx) : undefined, planningModelId: planningModel && planningSlashIdx !== -1 ? planningModel.slice(planningSlashIdx + 1) : undefined, - thinkingLevel: thinkingLevel !== "" ? thinkingLevel as "minimal" | "low" | "medium" | "high" : undefined, + thinkingLevel: thinkingLevel !== "" ? thinkingLevel as "minimal" | "low" | "medium" | "high" | "xhigh" : undefined, reviewLevel, ...(autoMerge !== undefined ? { autoMerge } : {}), priority, diff --git a/packages/dashboard/app/components/TaskDetailModal.tsx b/packages/dashboard/app/components/TaskDetailModal.tsx index 489cc3b2d5..9c98d6bc5b 100644 --- a/packages/dashboard/app/components/TaskDetailModal.tsx +++ b/packages/dashboard/app/components/TaskDetailModal.tsx @@ -1527,7 +1527,7 @@ export function TaskDetailContent({ } const currentThinkingLevel = task.thinkingLevel ?? ""; - if (editThinkingLevel !== currentThinkingLevel) updates.thinkingLevel = editThinkingLevel !== "" ? (editThinkingLevel as "minimal" | "low" | "medium" | "high") : null; + if (editThinkingLevel !== currentThinkingLevel) updates.thinkingLevel = editThinkingLevel !== "" ? (editThinkingLevel as "minimal" | "low" | "medium" | "high" | "xhigh") : null; if ((task.nodeId ?? undefined) !== editNodeId) updates.nodeId = editNodeId ?? null; if (editReviewLevel !== task.reviewLevel) updates.reviewLevel = editReviewLevel; if (editPriority !== normalizeTaskPriorityValue(task.priority)) updates.priority = editPriority; diff --git a/packages/dashboard/app/components/TaskForm.tsx b/packages/dashboard/app/components/TaskForm.tsx index dad5a6395f..973c88ce5f 100644 --- a/packages/dashboard/app/components/TaskForm.tsx +++ b/packages/dashboard/app/components/TaskForm.tsx @@ -1221,6 +1221,7 @@ export function TaskForm({ )} {onThinkingLevelChange && (
+ {/* FNXC:Settings-ThinkingLevel 2026-06-19-14:55: The shared task thinking selector must expose `xhigh` so new-task and task-detail edits can request maximum reasoning effort instead of being capped at `high`. */}
)} diff --git a/packages/dashboard/app/components/__tests__/TaskForm.test.tsx b/packages/dashboard/app/components/__tests__/TaskForm.test.tsx index 22bc7553cd..ecf3eba463 100644 --- a/packages/dashboard/app/components/__tests__/TaskForm.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskForm.test.tsx @@ -133,6 +133,20 @@ describe("TaskForm", () => { vi.mocked(fetchGitBranches).mockResolvedValue([]); }); + it("renders xhigh in the shared thinking-level selector", async () => { + renderTaskForm({ + thinkingLevel: "", + onThinkingLevelChange: vi.fn(), + }); + + fireEvent.click(screen.getByRole("button", { name: /More options/i })); + + await waitFor(() => { + expect(screen.getByRole("combobox", { name: /Thinking/i })).toBeTruthy(); + }); + expect(screen.getByRole("option", { name: /Very High/i })).toHaveAttribute("value", "xhigh"); + }); + it("renders description field with AI refine button when text is present", () => { renderTaskForm({ description: "Some text" }); diff --git a/packages/dashboard/app/components/settings/sections/GlobalModelsSection.tsx b/packages/dashboard/app/components/settings/sections/GlobalModelsSection.tsx index 3693c1a23a..05e682eec8 100644 --- a/packages/dashboard/app/components/settings/sections/GlobalModelsSection.tsx +++ b/packages/dashboard/app/components/settings/sections/GlobalModelsSection.tsx @@ -132,6 +132,7 @@ export function GlobalModelsSection({ if (selectedModel && !selectedModel.reasoning) return null; return (
+ {/* FNXC:Settings-ThinkingLevel 2026-06-19-14:55: This global selector renders the canonical THINKING_LEVELS list so newly added `xhigh` stays available anywhere the default reasoning effort is configured. */}