FN-7098: clarify prompt settings ownership

Clarify where Settings and Workflow Editor prompt editing responsibilities live.

- Add explanatory Prompts settings copy and a Workflow Editor navigation affordance.
- Thread the workflow settings opener into the Prompts section while keeping agent prompt tabs available.
- Document prompt ownership and add regression coverage for the new Prompts section behavior.
- Add a patch changeset for the published CLI package.

Files changed:
 .changeset/fn-7098-prompts-workflow-alignment.md   |   7 ++
 docs/settings-reference.md                         |  11 +-
 .../dashboard/app/components/SettingsModal.tsx     |   9 +-
 .../__tests__/SettingsModal.prompts.test.tsx       | 118 +++++++++++++++++++++
 .../settings/sections/PromptsSection.tsx           |  23 +++-
 5 files changed, 164 insertions(+), 4 deletions(-)

Fusion-Task-Id: FN-7098
Fusion-Task-Lineage: 57ac1f1b-77f8-4180-abe9-f6752768b17b
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-06-26 23:51:42 -07:00
parent f34b62cef9
commit a8f51e9423
5 changed files with 164 additions and 4 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Settings → Prompts now links to Workflow Editor prompts and clarifies prompt ownership.
category: feature
dev: PromptsSection threads onOpenWorkflowSettings and reuses MovedSettingsStub; AgentPromptsManager tabs stay in Settings.

View File

@@ -605,8 +605,8 @@ Default notes:
| `missionStaleThresholdMs` | `number` | `600000` | Mission stale threshold in ms while `activating` (10 min). | | `missionStaleThresholdMs` | `number` | `600000` | Mission stale threshold in ms while `activating` (10 min). |
| `missionMaxTaskRetries` | `number` | `3` | Max automatic retries for failed mission-linked tasks. | | `missionMaxTaskRetries` | `number` | `3` | Max automatic retries for failed mission-linked tasks. |
| `missionHealthCheckIntervalMs` | `number` | `300000` | Mission health-check interval in ms (5 min). | | `missionHealthCheckIntervalMs` | `number` | `300000` | Mission health-check interval in ms (5 min). |
| `agentPrompts` | `AgentPromptsConfig` | `undefined` | Custom role prompt templates and assignments. | | `agentPrompts` | `AgentPromptsConfig` | `undefined` | Custom role prompt templates and assignments edited in Settings → Prompts. |
| `promptOverrides` | `Record<string, string \| null>` | `undefined` | Segment-level prompt overrides (set a key to `null` to clear it). | | `promptOverrides` | `Record<string, string \| null>` | `undefined` | Global PromptKey segment-level overrides edited in Settings → Prompts (set a key to `null` to clear it). |
| `reflectionEnabled` | `boolean` | `false` | Enable/disable agent self-reflection workflows. | | `reflectionEnabled` | `boolean` | `false` | Enable/disable agent self-reflection workflows. |
| `reflectionIntervalMs` | `number` | `3600000` | Periodic reflection interval in ms. | | `reflectionIntervalMs` | `number` | `3600000` | Periodic reflection interval in ms. |
| `reflectionAfterTask` | `boolean` | `true` | Trigger reflection after task completion. | | `reflectionAfterTask` | `boolean` | `true` | Trigger reflection after task completion. |
@@ -1114,8 +1114,15 @@ For runtime details, see the [OpenClaw Runtime Plugin documentation](../plugins/
## Prompt Overrides ## Prompt Overrides
<!--
FNXC:Settings 2026-06-26-23:44:
Settings → Prompts and the Workflow Editor are separate prompt-editing surfaces. Settings owns role templates plus global PromptKey segment overrides; workflow prompt/gate node text is edited per workflow and per node in the Workflow Editor, and the settings UI links there to avoid ownership ambiguity.
-->
Fusion supports fine-grained customization of AI agent prompts through the `promptOverrides` setting. This enables surgical customization of specific prompt segments without replacing entire role prompts (which `agentPrompts` does). Fusion supports fine-grained customization of AI agent prompts through the `promptOverrides` setting. This enables surgical customization of specific prompt segments without replacing entire role prompts (which `agentPrompts` does).
Settings → Prompts owns `agentPrompts` role system prompt templates, role assignments, and global PromptKey segment overrides. Per-workflow step prompts for workflow `prompt` and `gate` nodes are edited in the Workflow Editor; the Prompts settings section includes an **Open workflow settings** link to jump to that surface.
### Supported Prompt Keys ### Supported Prompt Keys
| Key | Agent Role | Description | | Key | Agent Role | Description |

View File

@@ -3021,7 +3021,14 @@ export function SettingsModal({
/> />
); );
case "prompts": case "prompts":
return <PromptsSection scopeBanner={renderScopeBanner()} form={form} setForm={setForm} />; return (
<PromptsSection
scopeBanner={renderScopeBanner()}
form={form}
setForm={setForm}
onOpenWorkflowSettings={onOpenWorkflowSettings}
/>
);
case "plugins": case "plugins":
return ( return (
<PluginsSection <PluginsSection

View File

@@ -0,0 +1,118 @@
import { describe, it, expect, vi } from "vitest";
import { screen } from "@testing-library/react";
import {
mockFetchSettings,
mockFetchSettingsByScope,
mockUpdateSettings,
mockUpdateGlobalSettings,
mockFetchAuthStatus,
mockFetchModels,
mockUseMobileKeyboard,
mockUseMemoryBackendStatus,
mockUseWorktrunkInstallStatus,
mockConfirm,
defaultSettings,
renderModal,
waitForSettingsModalReady,
settingsModalUser,
installSettingsModalEnv,
} from "./SettingsModal.test-harness";
vi.mock("../../api", async (importOriginal) => {
const { createDashboardApiMock } = await import("../../test/mockApi");
return createDashboardApiMock(() => importOriginal<typeof import("../../api")>(), {
fetchSettings: (...args: unknown[]) => mockFetchSettings(...args),
fetchSettingsByScope: (...args: unknown[]) => mockFetchSettingsByScope(...args),
updateSettings: (...args: unknown[]) => mockUpdateSettings(...args),
updateGlobalSettings: (...args: unknown[]) => mockUpdateGlobalSettings(...args),
fetchAuthStatus: (...args: unknown[]) => mockFetchAuthStatus(...args),
fetchModels: (...args: unknown[]) => mockFetchModels(...args),
});
});
vi.mock("../../hooks/useMemoryBackendStatus", () => ({
useMemoryBackendStatus: (...args: unknown[]) => mockUseMemoryBackendStatus(...args),
}));
vi.mock("../../hooks/useMobileKeyboard", () => ({
useMobileKeyboard: (...args: unknown[]) => mockUseMobileKeyboard(...args),
}));
vi.mock("../../hooks/useConfirm", () => ({
useConfirm: () => ({ confirm: (...args: unknown[]) => mockConfirm(...args) }),
}));
vi.mock("../../hooks/useWorktrunkInstallStatus", () => ({
useWorktrunkInstallStatus: (...args: unknown[]) => mockUseWorktrunkInstallStatus(...args),
}));
vi.mock("../PluginManager", () => ({
PluginManager: () => <div data-testid="plugin-manager">Plugin manager content</div>,
}));
vi.mock("../PiExtensionsManager", () => ({
PiExtensionsManager: () => <div data-testid="pi-extensions-manager">Pi extensions content</div>,
}));
describe("SettingsModal Prompts section", () => {
installSettingsModalEnv();
it("explains prompt ownership, opens workflow settings, and keeps prompt tabs", async () => {
const onOpenWorkflowSettings = vi.fn();
mockFetchSettings.mockResolvedValue({
...defaultSettings,
agentPrompts: {
templates: [
{
id: "custom-executor",
name: "Custom Executor",
description: "Custom executor system prompt",
role: "executor",
prompt: "Custom executor prompt",
},
],
},
promptOverrides: { "executor-welcome": "Custom welcome" },
});
mockFetchSettingsByScope.mockResolvedValue({
global: defaultSettings,
project: {
agentPrompts: {
templates: [
{
id: "custom-executor",
name: "Custom Executor",
description: "Custom executor system prompt",
role: "executor",
prompt: "Custom executor prompt",
},
],
},
promptOverrides: { "executor-welcome": "Custom welcome" },
},
});
renderModal({ initialSection: "prompts", onOpenWorkflowSettings });
await waitForSettingsModalReady();
expect(screen.getByText(/agent role system prompt templates, role assignments, and global PromptKey segment overrides/i)).toBeInTheDocument();
expect(screen.getByText(/Per-workflow step prompts for prompt and gate nodes live in the Workflow Editor/i)).toBeInTheDocument();
await settingsModalUser.click(screen.getByRole("button", { name: "Open workflow settings" }));
expect(onOpenWorkflowSettings).toHaveBeenCalledTimes(1);
expect(screen.getByRole("button", { name: /Templates/i })).toBeInTheDocument();
expect(screen.getByRole("button", { name: /Assignments/i })).toBeInTheDocument();
expect(screen.getByRole("button", { name: /Overrides/i })).toBeInTheDocument();
expect(screen.getByText("Custom Executor")).toBeInTheDocument();
});
it("disables the workflow settings affordance when standalone wiring is absent", async () => {
renderModal({ initialSection: "prompts" });
await waitForSettingsModalReady();
const button = screen.getByRole("button", { name: "Open workflow settings" });
expect(button).toBeDisabled();
await settingsModalUser.click(button);
});
});

View File

@@ -9,18 +9,39 @@ import type { ReactNode } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import type { AgentPromptsConfig } from "@fusion/core"; import type { AgentPromptsConfig } from "@fusion/core";
import { AgentPromptsManager } from "../../AgentPromptsManager"; import { AgentPromptsManager } from "../../AgentPromptsManager";
import { MovedSettingsStub } from "./MovedSettingsStub";
import type { SectionBaseProps } from "./context"; import type { SectionBaseProps } from "./context";
export interface PromptsSectionProps extends SectionBaseProps { export interface PromptsSectionProps extends SectionBaseProps {
scopeBanner: ReactNode; scopeBanner: ReactNode;
/**
* FNXC:Settings 2026-06-26-16:54:
* Settings Prompts and Workflow Editor prompts are distinct editing surfaces. Settings owns agent role templates plus PromptKey segment overrides, while this callback links users to per-workflow, per-node prompt/gate prompts in the Workflow Editor.
*/
onOpenWorkflowSettings?: () => void;
} }
export function PromptsSection({ scopeBanner, form, setForm }: PromptsSectionProps) { export function PromptsSection({ scopeBanner, form, setForm, onOpenWorkflowSettings }: PromptsSectionProps) {
const { t } = useTranslation("app"); const { t } = useTranslation("app");
return ( return (
<> <>
{scopeBanner} {scopeBanner}
<h4 className="settings-section-heading">{t("settings.nav.prompts", "Prompts")}</h4> <h4 className="settings-section-heading">{t("settings.nav.prompts", "Prompts")}</h4>
<div className="form-group">
<small>
{t(
"settings.prompts.surfaceExplanation",
"Use this section for agent role system prompt templates, role assignments, and global PromptKey segment overrides. Per-workflow step prompts for prompt and gate nodes are edited in the Workflow Editor.",
)}
</small>
</div>
<MovedSettingsStub
message={t(
"settings.prompts.workflowPromptsRedirect",
"Per-workflow step prompts for prompt and gate nodes live in the Workflow Editor.",
)}
onOpenWorkflowSettings={onOpenWorkflowSettings}
/>
<AgentPromptsManager <AgentPromptsManager
value={form.agentPrompts} value={form.agentPrompts}
onChange={(agentPrompts: AgentPromptsConfig) => { onChange={(agentPrompts: AgentPromptsConfig) => {