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:
7
.changeset/fn-7098-prompts-workflow-alignment.md
Normal file
7
.changeset/fn-7098-prompts-workflow-alignment.md
Normal 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.
|
||||
@@ -605,8 +605,8 @@ Default notes:
|
||||
| `missionStaleThresholdMs` | `number` | `600000` | Mission stale threshold in ms while `activating` (10 min). |
|
||||
| `missionMaxTaskRetries` | `number` | `3` | Max automatic retries for failed mission-linked tasks. |
|
||||
| `missionHealthCheckIntervalMs` | `number` | `300000` | Mission health-check interval in ms (5 min). |
|
||||
| `agentPrompts` | `AgentPromptsConfig` | `undefined` | Custom role prompt templates and assignments. |
|
||||
| `promptOverrides` | `Record<string, string \| null>` | `undefined` | Segment-level prompt overrides (set a key to `null` to clear it). |
|
||||
| `agentPrompts` | `AgentPromptsConfig` | `undefined` | Custom role prompt templates and assignments edited in Settings → Prompts. |
|
||||
| `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. |
|
||||
| `reflectionIntervalMs` | `number` | `3600000` | Periodic reflection interval in ms. |
|
||||
| `reflectionAfterTask` | `boolean` | `true` | Trigger reflection after task completion. |
|
||||
@@ -1114,8 +1114,15 @@ For runtime details, see the [OpenClaw Runtime Plugin documentation](../plugins/
|
||||
|
||||
## 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).
|
||||
|
||||
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
|
||||
|
||||
| Key | Agent Role | Description |
|
||||
|
||||
@@ -3021,7 +3021,14 @@ export function SettingsModal({
|
||||
/>
|
||||
);
|
||||
case "prompts":
|
||||
return <PromptsSection scopeBanner={renderScopeBanner()} form={form} setForm={setForm} />;
|
||||
return (
|
||||
<PromptsSection
|
||||
scopeBanner={renderScopeBanner()}
|
||||
form={form}
|
||||
setForm={setForm}
|
||||
onOpenWorkflowSettings={onOpenWorkflowSettings}
|
||||
/>
|
||||
);
|
||||
case "plugins":
|
||||
return (
|
||||
<PluginsSection
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -9,18 +9,39 @@ import type { ReactNode } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { AgentPromptsConfig } from "@fusion/core";
|
||||
import { AgentPromptsManager } from "../../AgentPromptsManager";
|
||||
import { MovedSettingsStub } from "./MovedSettingsStub";
|
||||
import type { SectionBaseProps } from "./context";
|
||||
|
||||
export interface PromptsSectionProps extends SectionBaseProps {
|
||||
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");
|
||||
return (
|
||||
<>
|
||||
{scopeBanner}
|
||||
<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
|
||||
value={form.agentPrompts}
|
||||
onChange={(agentPrompts: AgentPromptsConfig) => {
|
||||
|
||||
Reference in New Issue
Block a user