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). |
|
| `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 |
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 { 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) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user